You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
6.7 KiB
219 lines
6.7 KiB
import type { Annotation } from "shexj";
|
|
import type { ExpandedTermDefinition } from "jsonld";
|
|
import type { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy";
|
|
|
|
/**
|
|
* Name functions
|
|
*/
|
|
export function iriToName(iri: string): string {
|
|
try {
|
|
const url = new URL(iri);
|
|
if (url.hash) {
|
|
return url.hash.slice(1);
|
|
} else {
|
|
const splitPathname = url.pathname.split("/");
|
|
return splitPathname[splitPathname.length - 1];
|
|
}
|
|
} catch (err) {
|
|
return iri;
|
|
}
|
|
}
|
|
|
|
export function nameFromObject(obj: {
|
|
id?: string;
|
|
annotations?: Annotation[];
|
|
}): string | undefined {
|
|
const labelAnnotationObject = obj.annotations?.find(
|
|
(annotation) =>
|
|
annotation.predicate === "http://www.w3.org/2000/01/rdf-schema#label",
|
|
)?.object;
|
|
if (labelAnnotationObject && typeof labelAnnotationObject === "string") {
|
|
return toCamelCase(iriToName(labelAnnotationObject));
|
|
} else if (
|
|
labelAnnotationObject &&
|
|
typeof labelAnnotationObject !== "string"
|
|
) {
|
|
return toCamelCase(labelAnnotationObject.value);
|
|
} else if (obj.id) {
|
|
return toCamelCase(iriToName(obj.id));
|
|
}
|
|
}
|
|
|
|
export function toCamelCase(text: string) {
|
|
return text
|
|
.replace(/([-_ ]){1,}/g, " ")
|
|
.split(/[-_ ]/)
|
|
.reduce((cur, acc) => {
|
|
return cur + acc[0].toUpperCase() + acc.substring(1);
|
|
});
|
|
}
|
|
|
|
export function isJsonLdContextBuilder(
|
|
item: ExpandedTermDefinition | JsonLdContextBuilder,
|
|
): item is JsonLdContextBuilder {
|
|
return !!(typeof item === "object" && item instanceof JsonLdContextBuilder);
|
|
}
|
|
|
|
/**
|
|
* JsonLd Context Builder
|
|
*/
|
|
export class JsonLdContextBuilder {
|
|
protected iriAnnotations: Record<string, Annotation[]> = {};
|
|
protected iriTypes: Record<
|
|
string,
|
|
ExpandedTermDefinition | JsonLdContextBuilder
|
|
> = {};
|
|
protected generatedNames: Record<string, string> | undefined;
|
|
|
|
private getRelevantBuilder(rdfType?: string): JsonLdContextBuilder {
|
|
if (!rdfType) return this;
|
|
if (
|
|
!this.iriTypes[rdfType] ||
|
|
!isJsonLdContextBuilder(this.iriTypes[rdfType])
|
|
) {
|
|
this.iriTypes[rdfType] = new JsonLdContextBuilder();
|
|
}
|
|
return this.iriTypes[rdfType] as JsonLdContextBuilder;
|
|
}
|
|
|
|
addSubject(iri: string, rdfType?: string, annotations?: Annotation[]) {
|
|
const relevantBuilder = this.getRelevantBuilder(rdfType);
|
|
if (!relevantBuilder.iriAnnotations[iri]) {
|
|
relevantBuilder.iriAnnotations[iri] = [];
|
|
}
|
|
if (annotations && annotations.length > 0) {
|
|
relevantBuilder.iriAnnotations[iri].push(...annotations);
|
|
}
|
|
}
|
|
|
|
addPredicate(
|
|
iri: string,
|
|
expandedTermDefinition: ExpandedTermDefinition,
|
|
isContainer: boolean,
|
|
rdfType?: string,
|
|
annotations?: Annotation[],
|
|
) {
|
|
const relevantBuilder = this.getRelevantBuilder(rdfType);
|
|
relevantBuilder.addSubject(iri, undefined, annotations);
|
|
if (!relevantBuilder.iriTypes[iri]) {
|
|
relevantBuilder.iriTypes[iri] = expandedTermDefinition;
|
|
if (isContainer) {
|
|
relevantBuilder.iriTypes[iri]["@isCollection"] = true;
|
|
}
|
|
} else {
|
|
const curDef = relevantBuilder.iriTypes[iri];
|
|
const newDef = expandedTermDefinition;
|
|
// TODO: if you reuse the same predicate with a different cardinality,
|
|
// it will overwrite the past cardinality. Perhapse we might want to
|
|
// split contexts in the various shapes.
|
|
if (isContainer) {
|
|
curDef["@isCollection"] = true;
|
|
}
|
|
// If the old and new versions both have types
|
|
if (curDef["@type"] && newDef["@type"]) {
|
|
if (
|
|
Array.isArray(curDef["@type"]) &&
|
|
!(curDef["@type"] as string[]).includes(newDef["@type"])
|
|
) {
|
|
curDef["@type"].push(newDef["@type"]);
|
|
} else if (
|
|
typeof curDef["@type"] === "string" &&
|
|
curDef["@type"] !== newDef["@type"]
|
|
) {
|
|
// The typings are incorrect. String arrays are allowed on @type
|
|
// see https://w3c.github.io/json-ld-syntax/#example-specifying-multiple-types-for-a-node
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
curDef["@type"] = [curDef["@type"], newDef["@type"]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
generateNames(): Record<string, string> {
|
|
const generatedNames: Record<string, string> = {};
|
|
const claimedNames: Set<string> = new Set();
|
|
Object.entries(this.iriAnnotations).forEach(([iri, annotations]) => {
|
|
let potentialName: string | undefined;
|
|
if (annotations.length > 0) {
|
|
const labelAnnotationObject = annotations.find(
|
|
(annotation) =>
|
|
annotation.predicate ===
|
|
"http://www.w3.org/2000/01/rdf-schema#label",
|
|
)?.object;
|
|
if (
|
|
labelAnnotationObject &&
|
|
typeof labelAnnotationObject === "string"
|
|
) {
|
|
potentialName = toCamelCase(iriToName(labelAnnotationObject));
|
|
} else if (
|
|
labelAnnotationObject &&
|
|
typeof labelAnnotationObject !== "string"
|
|
) {
|
|
potentialName = toCamelCase(labelAnnotationObject.value);
|
|
}
|
|
}
|
|
if (!potentialName) {
|
|
potentialName = toCamelCase(iriToName(iri));
|
|
}
|
|
if (claimedNames.has(potentialName)) {
|
|
let i = 2;
|
|
let newName: string | undefined;
|
|
do {
|
|
if (!claimedNames.has(`${potentialName}${i}`)) {
|
|
newName = `${potentialName}${i}`;
|
|
}
|
|
i++;
|
|
} while (!newName);
|
|
potentialName = newName;
|
|
}
|
|
claimedNames.add(potentialName);
|
|
generatedNames[iri] = potentialName;
|
|
});
|
|
return generatedNames;
|
|
}
|
|
|
|
getNameFromIri(iri: string, rdfType?: string) {
|
|
const relevantBuilder = this.getRelevantBuilder(rdfType);
|
|
if (!relevantBuilder.generatedNames) {
|
|
relevantBuilder.generatedNames = relevantBuilder.generateNames();
|
|
}
|
|
if (relevantBuilder.generatedNames[iri]) {
|
|
return relevantBuilder.generatedNames[iri];
|
|
} else {
|
|
return iri;
|
|
}
|
|
}
|
|
|
|
generateJsonldContext(): LdoJsonldContext {
|
|
const contextDefnition: LdoJsonldContext = {};
|
|
const namesMap = this.generateNames();
|
|
Object.entries(namesMap).forEach(([iri, name]) => {
|
|
if (this.iriTypes[iri]) {
|
|
let subContext: ExpandedTermDefinition = {
|
|
"@id":
|
|
iri === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
|
? "@type"
|
|
: iri,
|
|
};
|
|
|
|
if (isJsonLdContextBuilder(this.iriTypes[iri])) {
|
|
subContext["@context"] = (
|
|
this.iriTypes[iri] as JsonLdContextBuilder
|
|
).generateJsonldContext();
|
|
} else {
|
|
subContext = {
|
|
...subContext,
|
|
...this.iriTypes[iri],
|
|
};
|
|
}
|
|
|
|
contextDefnition[name] = subContext;
|
|
} else {
|
|
contextDefnition[name] = iri;
|
|
}
|
|
});
|
|
|
|
return contextDefnition;
|
|
}
|
|
}
|
|
|