diff --git a/packages/jsonld-dataset-proxy/src/ContextUtil.ts b/packages/jsonld-dataset-proxy/src/ContextUtil.ts index 5b2164c..075b546 100644 --- a/packages/jsonld-dataset-proxy/src/ContextUtil.ts +++ b/packages/jsonld-dataset-proxy/src/ContextUtil.ts @@ -3,6 +3,7 @@ import type { LdoJsonldContext, LdoJsonldContextExpandedTermDefinition, } from "./LdoJsonldContext"; +import type { NamedNode } from "@rdfjs/types"; // Create JSONLD Shorthands const shorthandToIriMap: Record = { @@ -64,15 +65,18 @@ export class ContextUtil { */ private getRelevantContext( key: string, - typeName?: string, + typeNames?: NamedNode[], ): ContextDefinition | LdoJsonldContext { - if ( - typeName && - typeof this.context[typeName] === "object" && - this.context[typeName]?.["@context"] && - this.context[typeName]?.["@context"][key] - ) { - return this.context[typeName]?.["@context"]; + if (!typeNames) return this.context; + for (const typeNameNode of typeNames) { + const typeName = this.iriToKey((typeNameNode as NamedNode).value, []); + if ( + typeof this.context[typeName] === "object" && + this.context[typeName]?.["@context"] && + this.context[typeName]?.["@context"][key] + ) { + return this.context[typeName]?.["@context"]; + } } return this.context; } @@ -91,7 +95,7 @@ export class ContextUtil { /** * Converts a given JsonLd key into an RDF IRI */ - public keyToIri(key: string, typeName: string): string { + public keyToIri(key: string, typeName: NamedNode[]): string { const relevantContext = this.getRelevantContext(key, typeName); if (!relevantContext[key]) { return key; @@ -108,9 +112,12 @@ export class ContextUtil { /** * Converts a given RDF IRI into the JsonLd key */ - public iriToKey(iri: string, typeName: string): string { - const relevantMap = - this.typeNameToIriToKeyMap[typeName] || this.iriToKeyMap; + public iriToKey(iri: string, typeNames: NamedNode[]): string { + let relevantMap = this.iriToKeyMap; + for (const typeNameNode of typeNames) { + const typeName = this.iriToKey((typeNameNode as NamedNode).value, []); + relevantMap = this.typeNameToIriToKeyMap[typeName] || this.iriToKeyMap; + } if (relevantMap[iri]) { return relevantMap[iri]; } @@ -120,7 +127,7 @@ export class ContextUtil { /** * Returns the IRI of a datatype of a specific object */ - public getDataType(key: string, typeName: string): string { + public getDataType(key: string, typeName: NamedNode[]): string { const relevantContext = this.getRelevantContext(key, typeName); if ( typeof relevantContext[key] === "object" && @@ -136,7 +143,7 @@ export class ContextUtil { /** * Returns true if the object is a collection */ - public isArray(key: string, typeName: string): boolean { + public isArray(key: string, typeName: NamedNode[]): boolean { const relevantContext = this.getRelevantContext(key, typeName); return !!( relevantContext[key] && @@ -153,7 +160,7 @@ export class ContextUtil { /** * Returns true if the object is a language string */ - public isLangString(key: string, typeName: string): boolean { + public isLangString(key: string, typeName: NamedNode[]): boolean { const relevantContext = this.getRelevantContext(key, typeName); return !!( relevantContext[key] && diff --git a/packages/jsonld-dataset-proxy/src/ProxyContext.ts b/packages/jsonld-dataset-proxy/src/ProxyContext.ts index ab89bcb..6220c5d 100644 --- a/packages/jsonld-dataset-proxy/src/ProxyContext.ts +++ b/packages/jsonld-dataset-proxy/src/ProxyContext.ts @@ -1,4 +1,4 @@ -import type { GraphNode, QuadMatch } from "@ldo/rdf-utils"; +import type { GraphNode, QuadMatch, SubjectNode } from "@ldo/rdf-utils"; import type { BlankNode, Dataset, NamedNode } from "@rdfjs/types"; import type { ArrayProxyTarget } from "./arrayProxy/createArrayHandler"; import { createArrayHandler } from "./arrayProxy/createArrayHandler"; @@ -8,6 +8,7 @@ import type { ArrayProxy } from "./arrayProxy/ArrayProxy"; import { _getUnderlyingArrayTarget } from "./types"; import type { ContextUtil } from "./ContextUtil"; import type { LanguageOrdering } from "./language/languageTypes"; +import { namedNode } from "@rdfjs/data-model"; export interface ProxyContextOptions { dataset: Dataset; @@ -18,6 +19,8 @@ export interface ProxyContextOptions { state?: Record; } +const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + /** * This file keeps track of the target objects used in the proxies. * The reason is so that JSON.stringify does not recurse inifinitely @@ -107,4 +110,12 @@ export class ProxyContext { }; return new ProxyContext(fullOptions); } + + public getRdfType(subjectNode: SubjectNode): NamedNode[] { + return this.dataset + .match(subjectNode, rdfType) + .toArray() + .map((quad) => quad.object) + .filter((object): object is NamedNode => object.termType === "NamedNode"); + } } diff --git a/packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts b/packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts index b1ae921..f58758d 100644 --- a/packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts +++ b/packages/jsonld-dataset-proxy/src/arrayProxy/modifyArray.ts @@ -114,7 +114,10 @@ export function modifyArray( addObjectToDataset( { "@id": target[0][0], - [contextUtil.iriToKey(target[0][1].value)]: added, + [contextUtil.iriToKey( + target[0][1].value, + proxyContext.getRdfType(target[0][0]), + )]: added, } as RawObject, false, proxyContext, @@ -123,7 +126,12 @@ export function modifyArray( const addedNodes = added ? (added .map((addedValue) => { - return getNodeFromRawValue(key, addedValue, proxyContext); + return getNodeFromRawValue( + key, + addedValue, + target[0][0] ? proxyContext.getRdfType(target[0][0]) : [], + proxyContext, + ); }) .filter((val) => val != undefined) as ObjectNode[]) : []; diff --git a/packages/jsonld-dataset-proxy/src/graphOf.ts b/packages/jsonld-dataset-proxy/src/graphOf.ts index 982c09f..db50e68 100644 --- a/packages/jsonld-dataset-proxy/src/graphOf.ts +++ b/packages/jsonld-dataset-proxy/src/graphOf.ts @@ -31,7 +31,10 @@ export function graphOf( const proxyContext = subjectProxy[_proxyContext]; const subjectNode = subjectProxy[_getUnderlyingNode]; const predicateNode = namedNode( - proxyContext.contextUtil.keyToIri(predicate as string), + proxyContext.contextUtil.keyToIri( + predicate as string, + proxyContext.getRdfType(subjectNode), + ), ); let objectNode: ObjectNode | null; if (object == null) { diff --git a/packages/jsonld-dataset-proxy/src/language/languagesOf.ts b/packages/jsonld-dataset-proxy/src/language/languagesOf.ts index 0a111b2..44c0174 100644 --- a/packages/jsonld-dataset-proxy/src/language/languagesOf.ts +++ b/packages/jsonld-dataset-proxy/src/language/languagesOf.ts @@ -51,11 +51,14 @@ export function languagesOf< const proxy = getSubjectProxyFromObject(subjectObject); const proxyContext = proxy[_proxyContext]; const subject = proxy[_getUnderlyingNode]; - const predicate = namedNode(proxyContext.contextUtil.keyToIri(key as string)); + const rdfTypes = proxyContext.getRdfType(subject); + const predicate = namedNode( + proxyContext.contextUtil.keyToIri(key as string, rdfTypes), + ); return createLanguageMapProxy( subject, predicate, proxyContext, - proxyContext.contextUtil.isArray(key as string), + proxyContext.contextUtil.isArray(key as string, rdfTypes), ) as LanguageOfConditionalReturn; } diff --git a/packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts b/packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts index 02b2cb4..15a41c7 100644 --- a/packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts +++ b/packages/jsonld-dataset-proxy/src/subjectProxy/createSubjectHandler.ts @@ -48,7 +48,12 @@ export function createSubjectHandler( const tripleDataset = proxyContext.dataset.match(subject); const keys: Set = new Set(["@id"]); tripleDataset.toArray().forEach((quad) => { - keys.add(proxyContext.contextUtil.iriToKey(quad.predicate.value)); + keys.add( + proxyContext.contextUtil.iriToKey( + quad.predicate.value, + proxyContext.getRdfType(subject), + ), + ); }); return Array.from(keys); }, diff --git a/packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts b/packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts index 604b29e..fabe85e 100644 --- a/packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts +++ b/packages/jsonld-dataset-proxy/src/subjectProxy/deleteFromDataset.ts @@ -19,7 +19,9 @@ export function deleteValueFromDataset( return true; } const subject = target["@id"]; - const predicate = namedNode(proxyContext.contextUtil.keyToIri(key)); + const predicate = namedNode( + proxyContext.contextUtil.keyToIri(key, proxyContext.getRdfType(subject)), + ); if (key === "@id") { nodesToRemove.push(target["@id"]); } else { diff --git a/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts b/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts index 37f5141..a22b9ec 100644 --- a/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts +++ b/packages/jsonld-dataset-proxy/src/subjectProxy/getValueForKey.ts @@ -19,7 +19,9 @@ export function getValueForKey( if (target["@id"].termType === "BlankNode") { return undefined; } - return contextUtil.iriToKey(target["@id"].value); + // Purposly don't provide a typeName because we don't want to use the nested + // context + return contextUtil.iriToKey(target["@id"].value, []); } if (key === "toString" || key === Symbol.toStringTag) { // TODO: this toString method right now returns [object Object], @@ -31,18 +33,19 @@ export function getValueForKey( return; } const subject = target["@id"]; - const predicate = namedNode(contextUtil.keyToIri(key)); - if (contextUtil.isArray(key)) { + const rdfType = proxyContext.getRdfType(subject); + const predicate = namedNode(contextUtil.keyToIri(key, rdfType)); + if (contextUtil.isArray(key, rdfType)) { const arrayProxy = proxyContext.createArrayProxy( [subject, predicate, null, null], false, undefined, - contextUtil.isLangString(key), + contextUtil.isLangString(key, rdfType), ); return arrayProxy; } let objectDataset = dataset.match(subject, predicate); - if (contextUtil.isLangString(key)) { + if (contextUtil.isLangString(key, rdfType)) { objectDataset = filterQuadsByLanguageOrdering( objectDataset, proxyContext.languageOrdering, diff --git a/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts b/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts index a24e437..a0f2a97 100644 --- a/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts +++ b/packages/jsonld-dataset-proxy/src/util/addObjectToDataset.ts @@ -22,15 +22,16 @@ export function addRawValueToDatasetRecursive( proxyContext: ProxyContext, ): void { const { dataset, contextUtil } = proxyContext; - const predicate = namedNode(contextUtil.keyToIri(key)); + const rdfType = proxyContext.getRdfType(subject); + const predicate = namedNode(contextUtil.keyToIri(key, rdfType)); // Get the Object Node - const object = getNodeFromRawValue(key, value, proxyContext); + const object = getNodeFromRawValue(key, value, rdfType, proxyContext); if (object == undefined) { dataset.deleteMatches(subject, predicate); } else if (object.termType === "Literal") { let languageAppliedObject = object; // Handle language use case - if (contextUtil.isLangString(key)) { + if (contextUtil.isLangString(key, rdfType)) { const languageKey = getLanguageKeyForWriteOperation( proxyContext.languageOrdering, ); @@ -81,6 +82,7 @@ export function addRawObjectToDatasetRecursive( } const { dataset } = proxyContext; const subject = getNodeFromRawObject(item, proxyContext.contextUtil); + const rdfType = proxyContext.getRdfType(subject); if (visitedObjects.has(subject)) { return proxyContext.createSubjectProxy(subject); } @@ -89,9 +91,11 @@ export function addRawObjectToDatasetRecursive( if (key === "@id") { return; } - const predicate = namedNode(proxyContext.contextUtil.keyToIri(key)); + const predicate = namedNode( + proxyContext.contextUtil.keyToIri(key, rdfType), + ); if (shouldDeleteOldTriples) { - if (proxyContext.contextUtil.isLangString(key)) { + if (proxyContext.contextUtil.isLangString(key, rdfType)) { const languageKey = getLanguageKeyForWriteOperation( proxyContext.languageOrdering, ); diff --git a/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts b/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts index 4ea9a73..f66cd90 100644 --- a/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts +++ b/packages/jsonld-dataset-proxy/src/util/getNodeFromRaw.ts @@ -14,7 +14,9 @@ export function getNodeFromRawObject( } else if (!item["@id"]) { return blankNode(); } else if (typeof item["@id"] === "string") { - return namedNode(contextUtil.keyToIri(item["@id"])); + // Purposly do not include typeName because we don't want to reference + // nested context + return namedNode(contextUtil.keyToIri(item["@id"], [])); } else { return item["@id"]; } @@ -23,6 +25,7 @@ export function getNodeFromRawObject( export function getNodeFromRawValue( key: string, value: RawValue, + rdfTypes: NamedNode[], proxyContext: ProxyContext, ): BlankNode | NamedNode | Literal | undefined { // Get the Object Node @@ -33,7 +36,8 @@ export function getNodeFromRawValue( typeof value === "boolean" || typeof value === "number" ) { - const datatype = proxyContext.contextUtil.getType(key); + // PICKUP: figure out how to handle looking for the RDF Types of a raw value + const datatype = proxyContext.contextUtil.getDataType(key, rdfTypes); if (datatype === "@id") { return namedNode(value.toString()); } else { diff --git a/packages/jsonld-dataset-proxy/test/ContextUtil.test.ts b/packages/jsonld-dataset-proxy/test/ContextUtil.test.ts index eafff63..18f8005 100644 --- a/packages/jsonld-dataset-proxy/test/ContextUtil.test.ts +++ b/packages/jsonld-dataset-proxy/test/ContextUtil.test.ts @@ -7,13 +7,13 @@ describe("ContextUtil", () => { name: "http://hl7.org/fhir/name", }; const contextUtil = new ContextUtil(fakeContext); - expect(contextUtil.keyToIri("name")).toBe("http://hl7.org/fhir/name"); + expect(contextUtil.keyToIri("name", [])).toBe("http://hl7.org/fhir/name"); }); it("returns the given key if it is not in the context", () => { const contextUtil = new ContextUtil({}); - expect(contextUtil.keyToIri("name")).toBe("name"); - expect(contextUtil.iriToKey("http://hl7.org/fhir/name")).toBe( + expect(contextUtil.keyToIri("name", [])).toBe("name"); + expect(contextUtil.iriToKey("http://hl7.org/fhir/name", [])).toBe( "http://hl7.org/fhir/name", ); }); @@ -22,7 +22,7 @@ describe("ContextUtil", () => { const contextUtil = new ContextUtil({ name: { "@type": "http://www.w3.org/2001/XMLSchema#string" }, }); - expect(contextUtil.keyToIri("name")).toBe("name"); + expect(contextUtil.keyToIri("name", [])).toBe("name"); }); }); @@ -31,7 +31,7 @@ describe("ContextUtil", () => { const contextUtil = new ContextUtil({ name: { "@id": "http://hl7.org/fhir/name" }, }); - expect(contextUtil.getType("name")).toBe( + expect(contextUtil.getDataType("name", [])).toBe( "http://www.w3.org/2001/XMLSchema#string", ); });