@inrupt/solid-client
Version:
Make your web apps work with Solid Pods.
305 lines (279 loc) • 8.88 kB
text/typescript
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import { DataFactory } from "n3";
import type * as RdfJs from "@rdfjs/types";
import type {
Graph,
ImmutableDataset,
Objects,
Predicates,
Subject,
} from "./rdf.internal";
import {
freeze,
getBlankNodeId,
getBlankNodeValue,
isBlankNodeId,
} from "./rdf.internal";
import type { ToRdfJsOptions } from "./rdfjs";
import type { IriString } from "./interfaces";
import { xmlSchemaTypes } from "./datatypes";
export { DataFactory };
export function addRdfJsQuadToDataset(
dataset: ImmutableDataset,
quad: RdfJs.Quad,
): ImmutableDataset {
const supportedGraphTypes: Array<typeof quad.graph.termType> = [
"NamedNode",
"DefaultGraph",
];
if (!supportedGraphTypes.includes(quad.graph.termType)) {
throw new Error(
`Cannot parse Quads with nodes of type [${quad.graph.termType}] as their Graph node.`,
);
}
const graphId =
quad.graph.termType === "DefaultGraph" ? "default" : quad.graph.value;
const graph: Graph = dataset.graphs[graphId] ?? {};
return freeze({
...dataset,
graphs: freeze({
...dataset.graphs,
[graphId]: addRdfJsQuadToGraph(graph, quad),
}),
});
}
function addRdfJsQuadToGraph(graph: Graph, quad: RdfJs.Quad): Graph {
const supportedSubjectTypes: Array<typeof quad.subject.termType> = [
"NamedNode",
"BlankNode",
];
if (!supportedSubjectTypes.includes(quad.subject.termType)) {
throw new Error(
`Cannot parse Quads with nodes of type [${quad.subject.termType}] as their Subject node.`,
);
}
const subjectIri =
quad.subject.termType === "BlankNode"
? `_:${quad.subject.value}`
: quad.subject.value;
const subject: Subject = graph[subjectIri] ?? {
type: "Subject",
url: subjectIri,
predicates: {},
};
return freeze({
...graph,
[subjectIri]: addRdfJsQuadToSubject(subject, quad),
});
}
function addRdfJsQuadToSubject(subject: Subject, quad: RdfJs.Quad): Subject {
return freeze({
...subject,
predicates: addRdfJsQuadToPredicates(subject.predicates, quad),
});
}
function addRdfJsQuadToPredicates(
predicates: Predicates,
quad: RdfJs.Quad,
): Predicates {
const supportedPredicateTypes: Array<typeof quad.predicate.termType> = [
"NamedNode",
];
if (!supportedPredicateTypes.includes(quad.predicate.termType)) {
throw new Error(
`Cannot parse Quads with nodes of type [${quad.predicate.termType}] as their Predicate node.`,
);
}
const predicateIri = quad.predicate.value;
const objects = predicates[predicateIri] ?? {};
return freeze({
...predicates,
[predicateIri]: addRdfJsQuadToObjects(objects, quad),
});
}
function addRdfJsQuadToObjects(objects: Objects, quad: RdfJs.Quad): Objects {
if (quad.object.termType === "NamedNode") {
const namedNodes = freeze([
...(objects.namedNodes ?? []),
quad.object.value,
]);
return freeze({
...objects,
namedNodes,
});
}
if (quad.object.termType === "Literal") {
if (quad.object.datatype.value === xmlSchemaTypes.langString) {
const locale = quad.object.language.toLowerCase();
const thisLocaleStrings = freeze([
...(objects.langStrings?.[locale] ?? []),
quad.object.value,
]);
const langStrings = freeze({
...(objects.langStrings ?? {}),
[locale]: thisLocaleStrings,
});
return freeze({
...objects,
langStrings,
});
}
// If the Object is a non-langString Literal
const thisTypeValues = freeze([
...(objects.literals?.[quad.object.datatype.value] ?? []),
quad.object.value,
]);
const literals = freeze({
...(objects.literals ?? {}),
[quad.object.datatype.value]: thisTypeValues,
});
return freeze({
...objects,
literals,
});
}
if (quad.object.termType === "BlankNode") {
const blankNodes = freeze([
...(objects.blankNodes ?? []),
getBlankNodeId(quad.object),
]);
return freeze({
...objects,
blankNodes,
});
}
throw new Error(
`Objects of type [${quad.object.termType}] are not supported.`,
);
}
export function toRdfJsQuads(
dataset: ImmutableDataset,
options: ToRdfJsOptions = {},
): RdfJs.Quad[] {
const quads: RdfJs.Quad[] = [];
const dataFactory = options.dataFactory ?? DataFactory;
Object.keys(dataset.graphs).forEach((graphIri: IriString) => {
const graph = dataset.graphs[graphIri];
const graphNode =
graphIri === "default"
? dataFactory.defaultGraph()
: dataFactory.namedNode(graphIri);
Object.keys(graph).forEach((subjectIri) => {
const { predicates } = graph[subjectIri];
const subjectNode = isBlankNodeId(subjectIri)
? dataFactory.blankNode(getBlankNodeValue(subjectIri))
: dataFactory.namedNode(subjectIri);
quads.push(
...subjectToRdfJsQuads(predicates, subjectNode, graphNode, options),
);
});
});
return quads;
}
export function subjectToRdfJsQuads(
predicates: Predicates,
subjectNode: RdfJs.NamedNode | RdfJs.BlankNode,
graphNode: RdfJs.NamedNode | RdfJs.DefaultGraph,
options: ToRdfJsOptions = {},
): RdfJs.Quad[] {
const quads: RdfJs.Quad[] = [];
const dataFactory = options.dataFactory ?? DataFactory;
Object.keys(predicates).forEach((predicateIri) => {
const predicateNode = dataFactory.namedNode(predicateIri);
const langStrings = predicates[predicateIri].langStrings ?? {};
const namedNodes = predicates[predicateIri].namedNodes ?? [];
const literals = predicates[predicateIri].literals ?? {};
const blankNodes = predicates[predicateIri].blankNodes ?? [];
const literalTypes = Object.keys(literals);
literalTypes.forEach((typeIri) => {
const typeNode = dataFactory.namedNode(typeIri);
const literalValues = literals[typeIri];
literalValues.forEach((value) => {
const literalNode = dataFactory.literal(value, typeNode);
quads.push(
dataFactory.quad(
subjectNode,
predicateNode,
literalNode,
graphNode,
) as RdfJs.Quad,
);
});
});
const locales = Object.keys(langStrings);
locales.forEach((locale) => {
const localeValues = langStrings[locale];
localeValues.forEach((value) => {
const langStringNode = dataFactory.literal(value, locale);
quads.push(
dataFactory.quad(
subjectNode,
predicateNode,
langStringNode,
graphNode,
) as RdfJs.Quad,
);
});
});
namedNodes.forEach((namedNodeIri) => {
const node = dataFactory.namedNode(namedNodeIri);
quads.push(
dataFactory.quad(
subjectNode,
predicateNode,
node,
graphNode,
) as RdfJs.Quad,
);
});
blankNodes.forEach((blankNodeIdOrPredicates) => {
if (isBlankNodeId(blankNodeIdOrPredicates)) {
const blankNode = dataFactory.blankNode(
getBlankNodeValue(blankNodeIdOrPredicates),
);
quads.push(
dataFactory.quad(
subjectNode,
predicateNode,
blankNode,
graphNode,
) as RdfJs.Quad,
);
} else {
const node = dataFactory.blankNode();
const blankNodeObjectQuad = dataFactory.quad(
subjectNode,
predicateNode,
node,
graphNode,
) as RdfJs.Quad;
const blankNodeSubjectQuads = subjectToRdfJsQuads(
blankNodeIdOrPredicates,
node,
graphNode,
);
quads.push(blankNodeObjectQuad);
quads.push(...blankNodeSubjectQuads);
}
});
});
return quads;
}