From 60f3bc2b9a13eac91c646ea57880f2e64b516e34 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Mon, 17 May 2021 16:49:40 +1200 Subject: [PATCH 01/16] feat: extend expansion map for detecting relative IRIs for @id and @type terms --- lib/expand.js | 47 ++++++++++-- tests/misc.js | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 6 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index 44c5102f..50a455fa 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -505,10 +505,27 @@ async function _expandObject({ } } + const expandedValues = _asArray(value).map(v => + _isString(v) ? _expandIri(activeCtx, v, {base: true}, options) : v); + + for(const i in expandedValues) { + if(_isString(expandedValues[i]) && !_isAbsoluteIri(expandedValues[i])) { + const expansionMapResult = await expansionMap({ + relativeIri: {type: key, value: expandedValues[i]}, + activeCtx, + activeProperty, + options, + insideList + }); + if(expansionMapResult !== undefined) { + expandedValues[i] = expansionMapResult; + } + } + } + _addValue( expandedParent, '@id', - _asArray(value).map(v => - _isString(v) ? _expandIri(activeCtx, v, {base: true}, options) : v), + expandedValues, {propertyIsArray: options.isFrame}); continue; } @@ -525,12 +542,30 @@ async function _expandObject({ ])); } _validateTypeValue(value, options.isFrame); + + const expandedValues = _asArray(value).map(v => + _isString(v) ? + _expandIri(typeScopedContext, v, + {base: true, vocab: true}, options) : v); + + for(const i in expandedValues) { + if(_isString(expandedValues[i]) && !_isAbsoluteIri(expandedValues[i])) { + const expansionMapResult = await expansionMap({ + relativeIri: {type: key, value: expandedValues[i]}, + activeCtx, + activeProperty, + options, + insideList + }); + if(expansionMapResult !== undefined) { + expandedValues[i] = expansionMapResult; + } + } + } + _addValue( expandedParent, '@type', - _asArray(value).map(v => - _isString(v) ? - _expandIri(typeScopedContext, v, - {base: true, vocab: true}, options) : v), + expandedValues, {propertyIsArray: options.isFrame}); continue; } diff --git a/tests/misc.js b/tests/misc.js index 1c3758e0..c7156209 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -478,3 +478,207 @@ describe('literal JSON', () => { }); }); }); + +describe('expansionMap', () => { + it('should be called on un-mapped term', async () => { + const docWithUnMappedTerm = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + definedTerm: "is defined", + testUndefined: "is undefined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.unmappedProperty === 'testUndefined') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithUnMappedTerm, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on nested un-mapped term', async () => { + const docWithUnMappedTerm = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + definedTerm: { + testUndefined: "is undefined" + } + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.unmappedProperty === 'testUndefined') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithUnMappedTerm, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for id term', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + '@id': "relativeiri", + definedTerm: "is defined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for id term (nested)', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + '@id': "urn:absoluteIri", + definedTerm: { + '@id': "relativeiri" + } + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for aliased id term', async () => { + const docWithRelativeIriId = { + '@context': { + 'id': '@id', + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "relativeiri", + definedTerm: "is defined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for type term', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': "relativeiri", + definedTerm: "is defined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for \ + type term with multiple relative iri types', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': ["relativeiri", "anotherRelativeiri" ], + definedTerm: "is defined" + }; + + let expansionMapCalledTimes = 0; + const expansionMap = info => { + if(info.relativeIri && + (info.relativeIri.value === 'relativeiri' || + info.relativeIri.value === 'anotherRelativeiri')) { + expansionMapCalledTimes++; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalledTimes, 2); + }); + + it('should be called on relative iri for \ + type term with multiple types', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': ["relativeiri", "definedTerm" ], + definedTerm: "is defined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it('should be called on relative iri for aliased type term', async () => { + const docWithRelativeIriId = { + '@context': { + 'type': "@type", + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + 'type': "relativeiri", + definedTerm: "is defined" + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); +}); From 9a8117ff49fc9438699791a14e46f0b33335d8af Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Mon, 17 May 2021 16:49:49 +1200 Subject: [PATCH 02/16] fix: linting --- lib/util.js | 2 +- tests/test-common.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/util.js b/lib/util.js index 77da8f61..1458005a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -130,7 +130,7 @@ api.parseLinkHeader = header => { while((match = REGEX_LINK_HEADER_PARAMS.exec(params))) { result[match[1]] = (match[2] === undefined) ? match[3] : match[2]; } - const rel = result['rel'] || ''; + const rel = result.rel || ''; if(Array.isArray(rval[rel])) { rval[rel].push(result); } else if(rval.hasOwnProperty(rel)) { diff --git a/tests/test-common.js b/tests/test-common.js index b9982182..9cc520c7 100644 --- a/tests/test-common.js +++ b/tests/test-common.js @@ -398,7 +398,7 @@ function addManifest(manifest, parent) { */ function addTest(manifest, test, tests) { // expand @id and input base - const test_id = test['@id'] || test['id']; + const test_id = test['@id'] || test.id; //var number = test_id.substr(2); test['@id'] = manifest.baseIri + @@ -958,10 +958,10 @@ function createDocumentLoader(test) { } // If not JSON-LD, alternate may point there - if(linkHeaders['alternate'] && - linkHeaders['alternate'].type == 'application/ld+json' && + if(linkHeaders.alternate && + linkHeaders.alternate.type == 'application/ld+json' && !(contentType || '').match(/^application\/(\w*\+)?json$/)) { - doc.documentUrl = prependBase(url, linkHeaders['alternate'].target); + doc.documentUrl = prependBase(url, linkHeaders.alternate.target); } } } From 15775b06eea60a48106e3eec14460bd4261195cc Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Tue, 18 May 2021 11:05:39 +1200 Subject: [PATCH 03/16] fix: call expansion map from _expandIri instead of _expandObject --- lib/context.js | 13 ++++++++++++- lib/expand.js | 3 +++ tests/misc.js | 19 +++++++++---------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/context.js b/lib/context.js index 1a0161b5..0ad282ef 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1053,7 +1053,18 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { return prependBase(prependBase(options.base, activeCtx['@base']), value); } } else if(relativeTo.base) { - return prependBase(options.base, value); + value = prependBase(options.base, value); + } + + if(!_isAbsoluteIri(value) && options.expansionMap) { + // TODO: use `await` to support async + const expandedResult = options.expansionMap({ + relativeIri: value, + activeCtx, + }); + if(expandedResult !== undefined) { + value = expandedResult; + } } return value; diff --git a/lib/expand.js b/lib/expand.js index 50a455fa..a9caa6e9 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -420,6 +420,9 @@ async function _expandObject({ const nests = []; let unexpandedValue; + // Add expansion map to the processing options + options = {...options, expansionMap}; + // Figure out if this is the type for a JSON literal const isJsonType = element[typeKey] && _expandIri(activeCtx, diff --git a/tests/misc.js b/tests/misc.js index c7156209..bc8c7aac 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -534,7 +534,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; @@ -557,7 +557,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; @@ -579,7 +579,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; @@ -601,7 +601,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; @@ -624,16 +624,15 @@ describe('expansionMap', () => { let expansionMapCalledTimes = 0; const expansionMap = info => { - if(info.relativeIri && - (info.relativeIri.value === 'relativeiri' || - info.relativeIri.value === 'anotherRelativeiri')) { + if(info.relativeIri === 'relativeiri' || + info.relativeIri === 'anotherRelativeiri') { expansionMapCalledTimes++; } }; await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalledTimes, 2); + assert.equal(expansionMapCalledTimes, 3); }); it('should be called on relative iri for \ @@ -649,7 +648,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; @@ -672,7 +671,7 @@ describe('expansionMap', () => { let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri && info.relativeIri.value === 'relativeiri') { + if(info.relativeIri === 'relativeiri') { expansionMapCalled = true; } }; From 53048bab9a7675eac871da424ed453fb250edc76 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Tue, 18 May 2021 11:11:25 +1200 Subject: [PATCH 04/16] fix: duplicate expansionMap logic --- lib/expand.js | 46 ++++++---------------------------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index a9caa6e9..6c852294 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -508,27 +508,10 @@ async function _expandObject({ } } - const expandedValues = _asArray(value).map(v => - _isString(v) ? _expandIri(activeCtx, v, {base: true}, options) : v); - - for(const i in expandedValues) { - if(_isString(expandedValues[i]) && !_isAbsoluteIri(expandedValues[i])) { - const expansionMapResult = await expansionMap({ - relativeIri: {type: key, value: expandedValues[i]}, - activeCtx, - activeProperty, - options, - insideList - }); - if(expansionMapResult !== undefined) { - expandedValues[i] = expansionMapResult; - } - } - } - _addValue( expandedParent, '@id', - expandedValues, + _asArray(value).map(v => + _isString(v) ? _expandIri(activeCtx, v, {base: true}, options) : v), {propertyIsArray: options.isFrame}); continue; } @@ -546,29 +529,12 @@ async function _expandObject({ } _validateTypeValue(value, options.isFrame); - const expandedValues = _asArray(value).map(v => - _isString(v) ? - _expandIri(typeScopedContext, v, - {base: true, vocab: true}, options) : v); - - for(const i in expandedValues) { - if(_isString(expandedValues[i]) && !_isAbsoluteIri(expandedValues[i])) { - const expansionMapResult = await expansionMap({ - relativeIri: {type: key, value: expandedValues[i]}, - activeCtx, - activeProperty, - options, - insideList - }); - if(expansionMapResult !== undefined) { - expandedValues[i] = expansionMapResult; - } - } - } - _addValue( expandedParent, '@type', - expandedValues, + _asArray(value).map(v => + _isString(v) ? + _expandIri(typeScopedContext, v, + {base: true, vocab: true}, options) : v), {propertyIsArray: options.isFrame}); continue; } From 229bdb15d9fb248b68e8f045fd1e7ba8a02c6b48 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Tue, 18 May 2021 11:46:58 +1200 Subject: [PATCH 05/16] feat: add support for @base being './' --- lib/context.js | 2 +- lib/expand.js | 1 - tests/misc.js | 20 ++++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/context.js b/lib/context.js index 0ad282ef..d3a613a1 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1050,7 +1050,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { if(relativeTo.base && '@base' in activeCtx) { if(activeCtx['@base']) { // The null case preserves value as potentially relative - return prependBase(prependBase(options.base, activeCtx['@base']), value); + value = prependBase(prependBase(options.base, activeCtx['@base']), value); } } else if(relativeTo.base) { value = prependBase(options.base, value); diff --git a/lib/expand.js b/lib/expand.js index 6c852294..f0236e30 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -528,7 +528,6 @@ async function _expandObject({ ])); } _validateTypeValue(value, options.isFrame); - _addValue( expandedParent, '@type', _asArray(value).map(v => diff --git a/tests/misc.js b/tests/misc.js index bc8c7aac..72509fe7 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -680,4 +680,24 @@ describe('expansionMap', () => { assert.equal(expansionMapCalled, true); }); + + it("should be called on relative iri when @base value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@base": "./", + }, + '@id': "absoluteiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/absoluteiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); }); From 72bb834f9419361f1141726c9b607fbda17eec43 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Thu, 20 May 2021 13:52:12 +1200 Subject: [PATCH 06/16] fix: add support for when @vocab results in relative iri --- lib/context.js | 5 +++-- lib/expand.js | 4 ++++ tests/misc.js | 44 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/context.js b/lib/context.js index d3a613a1..60db67ea 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1043,11 +1043,11 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { // prepend vocab if(relativeTo.vocab && '@vocab' in activeCtx) { - return activeCtx['@vocab'] + value; + value = activeCtx['@vocab'] + value; } // prepend base - if(relativeTo.base && '@base' in activeCtx) { + else if(relativeTo.base && '@base' in activeCtx) { if(activeCtx['@base']) { // The null case preserves value as potentially relative value = prependBase(prependBase(options.base, activeCtx['@base']), value); @@ -1061,6 +1061,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { const expandedResult = options.expansionMap({ relativeIri: value, activeCtx, + options }); if(expandedResult !== undefined) { value = expandedResult; diff --git a/lib/expand.js b/lib/expand.js index f0236e30..6201661e 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -75,6 +75,10 @@ api.expand = async ({ typeScopedContext = null, expansionMap = () => undefined }) => { + + // Add expansion map to the processing options + options = { ...options, expansionMap }; + // nothing to expand if(element === null || element === undefined) { return null; diff --git a/tests/misc.js b/tests/misc.js index 72509fe7..73aca8ff 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -686,12 +686,52 @@ describe('expansionMap', () => { '@context': { "@base": "./", }, - '@id': "absoluteiri", + '@id': "relativeiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called on relative iri when @base value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@base": "./", + }, + '@id': "relativeiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called on relative iri when @vocab value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@vocab": "./", + }, + '@type': "relativeiri", }; let expansionMapCalled = false; const expansionMap = info => { - if(info.relativeIri === '/absoluteiri') { + if(info.relativeIri === '/relativeiri') { expansionMapCalled = true; } }; From bc55956800ec57c141049f853793edcffee3c5e8 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 05:05:47 +1200 Subject: [PATCH 07/16] Update lib/expand.js Co-authored-by: Dave Longley --- lib/expand.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/expand.js b/lib/expand.js index 6201661e..518f1428 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -76,7 +76,7 @@ api.expand = async ({ expansionMap = () => undefined }) => { - // Add expansion map to the processing options + // add expansion map to the processing options options = { ...options, expansionMap }; // nothing to expand From 1910df919a664e623b8f78eb602cf7a6390b350a Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 05:06:14 +1200 Subject: [PATCH 08/16] Update lib/expand.js Co-authored-by: Dave Longley --- lib/expand.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/expand.js b/lib/expand.js index 518f1428..62e41e59 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -424,7 +424,7 @@ async function _expandObject({ const nests = []; let unexpandedValue; - // Add expansion map to the processing options + // add expansion map to the processing options options = {...options, expansionMap}; // Figure out if this is the type for a JSON literal From 24c1e30a2038cb9f10b8a335a6320fda9b72b5cb Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 05:06:24 +1200 Subject: [PATCH 09/16] Update tests/misc.js Co-authored-by: Dave Longley --- tests/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/misc.js b/tests/misc.js index 73aca8ff..f98f0abd 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -480,7 +480,7 @@ describe('literal JSON', () => { }); describe('expansionMap', () => { - it('should be called on un-mapped term', async () => { + it('should be called on unmapped term', async () => { const docWithUnMappedTerm = { '@context': { 'definedTerm': 'https://example.com#definedTerm' From 560b72941a1b1143ee92e86184e704a361b353c3 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 05:06:32 +1200 Subject: [PATCH 10/16] Update tests/misc.js Co-authored-by: Dave Longley --- tests/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/misc.js b/tests/misc.js index f98f0abd..39b44cfa 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -501,7 +501,7 @@ describe('expansionMap', () => { assert.equal(expansionMapCalled, true); }); - it('should be called on nested un-mapped term', async () => { + it('should be called on nested unmapped term', async () => { const docWithUnMappedTerm = { '@context': { 'definedTerm': 'https://example.com#definedTerm' From 6045c7a63b5c574056bbe0344f855778c23f731d Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 05:58:16 +1200 Subject: [PATCH 11/16] fix: style nit --- lib/context.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/context.js b/lib/context.js index 60db67ea..308c4445 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1041,13 +1041,11 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { } } - // prepend vocab if(relativeTo.vocab && '@vocab' in activeCtx) { + // prepend vocab value = activeCtx['@vocab'] + value; - } - - // prepend base - else if(relativeTo.base && '@base' in activeCtx) { + } else if(relativeTo.base && '@base' in activeCtx) { + // prepend base if(activeCtx['@base']) { // The null case preserves value as potentially relative value = prependBase(prependBase(options.base, activeCtx['@base']), value); From 5c3dad0d36829d4b484e7dafc73b95ec8d7c1b7c Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Fri, 21 May 2021 10:05:35 +1200 Subject: [PATCH 12/16] feat: add prependIri expansionMap hook and tests --- lib/context.js | 88 ++++++- lib/expand.js | 2 +- tests/misc.js | 609 ++++++++++++++++++++++++++++++++----------------- 3 files changed, 486 insertions(+), 213 deletions(-) diff --git a/lib/context.js b/lib/context.js index 308c4445..f6fdd85e 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1043,18 +1043,96 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { if(relativeTo.vocab && '@vocab' in activeCtx) { // prepend vocab - value = activeCtx['@vocab'] + value; + const prependedResult = activeCtx['@vocab'] + value; + let expansionMapResult = undefined; + if(options && options.expansionMap) { + // if we are about to expand the value by prepending + // @vocab then call the expansion map to inform + // interested callers that this is occurring + + // TODO: use `await` to support async + expansionMapResult = options.expansionMap({ + prependedIri: { + type: '@vocab', + vocab: activeCtx['@vocab'], + value, + result: prependedResult + }, + activeCtx, + options + }); + + } + if(expansionMapResult !== undefined) { + value = expansionMapResult; + } else { + // the null case preserves value as potentially relative + value = prependedResult; + } } else if(relativeTo.base && '@base' in activeCtx) { // prepend base if(activeCtx['@base']) { - // The null case preserves value as potentially relative - value = prependBase(prependBase(options.base, activeCtx['@base']), value); + const prependedResult = prependBase( + prependBase(options.base, activeCtx['@base']), value); + + let expansionMapResult = undefined; + if(options && options.expansionMap) { + // if we are about to expand the value by prepending + // @base then call the expansion map to inform + // interested callers that this is occurring + + // TODO: use `await` to support async + expansionMapResult = options.expansionMap({ + prependedIri: { + type: '@base', + base: activeCtx['@base'], + value, + result: prependedResult + }, + activeCtx, + options + }); + } + if(expansionMapResult !== undefined) { + value = expansionMapResult; + } else { + // the null case preserves value as potentially relative + value = prependedResult; + } } } else if(relativeTo.base) { - value = prependBase(options.base, value); + const prependedResult = prependBase(options.base, value); + let expansionMapResult = undefined; + if(options && options.expansionMap) { + // if we are about to expand the value by prepending + // @base then call the expansion map to inform + // interested callers that this is occurring + + // TODO: use `await` to support async + expansionMapResult = options.expansionMap({ + prependedIri: { + type: '@base', + base: options.base, + value, + result: prependedResult + }, + activeCtx, + options + }); + } + if(expansionMapResult !== undefined) { + value = expansionMapResult; + } else { + value = prependedResult; + } } - if(!_isAbsoluteIri(value) && options.expansionMap) { + if(!_isAbsoluteIri(value) && options && options.expansionMap) { + // if the result of the expansion is not an absolute iri then + // call the expansion map to inform interested callers that + // the resulting value is a relative iri, which can result in + // it being dropped when converting to other RDF representations + // TODO: use `await` to support async const expandedResult = options.expansionMap({ relativeIri: value, diff --git a/lib/expand.js b/lib/expand.js index 62e41e59..0cf50746 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -77,7 +77,7 @@ api.expand = async ({ }) => { // add expansion map to the processing options - options = { ...options, expansionMap }; + options = {...options, expansionMap}; // nothing to expand if(element === null || element === undefined) { diff --git a/tests/misc.js b/tests/misc.js index 39b44cfa..4ac9578e 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -480,264 +480,459 @@ describe('literal JSON', () => { }); describe('expansionMap', () => { - it('should be called on unmapped term', async () => { - const docWithUnMappedTerm = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - definedTerm: "is defined", - testUndefined: "is undefined" - }; + describe('unmappedProperty', () => { + it('should be called on unmapped term', async () => { + const docWithUnMappedTerm = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + definedTerm: "is defined", + testUndefined: "is undefined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.unmappedProperty === 'testUndefined') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.unmappedProperty === 'testUndefined') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithUnMappedTerm, {expansionMap}); + await jsonld.expand(docWithUnMappedTerm, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on nested unmapped term', async () => { - const docWithUnMappedTerm = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - definedTerm: { - testUndefined: "is undefined" - } - }; + it('should be called on nested unmapped term', async () => { + const docWithUnMappedTerm = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + definedTerm: { + testUndefined: "is undefined" + } + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.unmappedProperty === 'testUndefined') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.unmappedProperty === 'testUndefined') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithUnMappedTerm, {expansionMap}); + await jsonld.expand(docWithUnMappedTerm, {expansionMap}); - assert.equal(expansionMapCalled, true); + assert.equal(expansionMapCalled, true); + }); }); - it('should be called on relative iri for id term', async () => { - const docWithRelativeIriId = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - '@id': "relativeiri", - definedTerm: "is defined" - }; + describe('relativeIri', () => { + it('should be called on relative iri for id term', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + '@id': "relativeiri", + definedTerm: "is defined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on relative iri for id term (nested)', async () => { - const docWithRelativeIriId = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - '@id': "urn:absoluteIri", - definedTerm: { - '@id': "relativeiri" - } - }; + it('should be called on relative iri for id term (nested)', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + '@id': "urn:absoluteIri", + definedTerm: { + '@id': "relativeiri" + } + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on relative iri for aliased id term', async () => { - const docWithRelativeIriId = { - '@context': { - 'id': '@id', - 'definedTerm': 'https://example.com#definedTerm' - }, - 'id': "relativeiri", - definedTerm: "is defined" - }; + it('should be called on relative iri for aliased id term', async () => { + const docWithRelativeIriId = { + '@context': { + 'id': '@id', + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "relativeiri", + definedTerm: "is defined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on relative iri for type term', async () => { - const docWithRelativeIriId = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - 'id': "urn:absoluteiri", - '@type': "relativeiri", - definedTerm: "is defined" - }; + it('should be called on relative iri for type term', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': "relativeiri", + definedTerm: "is defined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on relative iri for \ - type term with multiple relative iri types', async () => { - const docWithRelativeIriId = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - 'id': "urn:absoluteiri", - '@type': ["relativeiri", "anotherRelativeiri" ], - definedTerm: "is defined" - }; + it('should be called on relative iri for \ + type term with multiple relative iri types', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': ["relativeiri", "anotherRelativeiri" ], + definedTerm: "is defined" + }; - let expansionMapCalledTimes = 0; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri' || - info.relativeIri === 'anotherRelativeiri') { - expansionMapCalledTimes++; - } - }; + let expansionMapCalledTimes = 0; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri' || + info.relativeIri === 'anotherRelativeiri') { + expansionMapCalledTimes++; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalledTimes, 3); - }); + assert.equal(expansionMapCalledTimes, 3); + }); - it('should be called on relative iri for \ - type term with multiple types', async () => { - const docWithRelativeIriId = { - '@context': { - 'definedTerm': 'https://example.com#definedTerm' - }, - 'id': "urn:absoluteiri", - '@type': ["relativeiri", "definedTerm" ], - definedTerm: "is defined" - }; + it('should be called on relative iri for \ + type term with multiple types', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + '@type': ["relativeiri", "definedTerm" ], + definedTerm: "is defined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it('should be called on relative iri for aliased type term', async () => { - const docWithRelativeIriId = { - '@context': { - 'type': "@type", - 'definedTerm': 'https://example.com#definedTerm' - }, - 'id': "urn:absoluteiri", - 'type': "relativeiri", - definedTerm: "is defined" - }; + it('should be called on relative iri for aliased type term', async () => { + const docWithRelativeIriId = { + '@context': { + 'type': "@type", + 'definedTerm': 'https://example.com#definedTerm' + }, + 'id': "urn:absoluteiri", + 'type': "relativeiri", + definedTerm: "is defined" + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === 'relativeiri') { - expansionMapCalled = true; - } - }; + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called on relative iri when \ + @base value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@base": "./", + }, + '@id': "relativeiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/relativeiri') { + expansionMapCalled = true; + } + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(docWithRelativeIriId, {expansionMap}); - assert.equal(expansionMapCalled, true); + assert.equal(expansionMapCalled, true); + }); + + it("should be called on relative iri when \ + @base value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@base": "./", + }, + '@id': "relativeiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called on relative iri when \ + @vocab value is './'", async () => { + const docWithRelativeIriId = { + '@context': { + "@vocab": "./", + }, + '@type': "relativeiri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === '/relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); }); - it("should be called on relative iri when @base value is './'", async () => { - const docWithRelativeIriId = { - '@context': { - "@base": "./", - }, - '@id': "relativeiri", - }; + describe('prependedIri', () => { + it("should be called when property is \ + being expanded with `@vocab`", async () => { + const doc = { + '@context': { + "@vocab": "http://example.com/", + }, + 'term': "termValue", + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === '/relativeiri') { + let expansionMapCalled = false; + const expansionMap = info => { + assert.deepStrictEqual(info.prependedIri, { + type: '@vocab', + vocab: 'http://example.com/', + value: 'term', + result: 'http://example.com/term' + }); expansionMapCalled = true; - } - }; + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(doc, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it("should be called on relative iri when @base value is './'", async () => { - const docWithRelativeIriId = { - '@context': { - "@base": "./", - }, - '@id': "relativeiri", - }; + it("should be called when '@type' is \ + being expanded with `@vocab`", async () => { + const doc = { + '@context': { + "@vocab": "http://example.com/", + }, + '@type': "relativeIri", + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === '/relativeiri') { + let expansionMapCalled = false; + const expansionMap = info => { + assert.deepStrictEqual(info.prependedIri, { + type: '@vocab', + vocab: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); expansionMapCalled = true; - } - }; + }; - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + await jsonld.expand(doc, {expansionMap}); - assert.equal(expansionMapCalled, true); - }); + assert.equal(expansionMapCalled, true); + }); - it("should be called on relative iri when @vocab value is './'", async () => { - const docWithRelativeIriId = { - '@context': { - "@vocab": "./", - }, - '@type': "relativeiri", - }; + it("should be called when aliased '@type' is \ + being expanded with `@vocab`", async () => { + const doc = { + '@context': { + "@vocab": "http://example.com/", + "type": "@type" + }, + 'type': "relativeIri", + }; - let expansionMapCalled = false; - const expansionMap = info => { - if(info.relativeIri === '/relativeiri') { + let expansionMapCalled = false; + const expansionMap = info => { + assert.deepStrictEqual(info.prependedIri, { + type: '@vocab', + vocab: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); expansionMapCalled = true; - } - }; + }; + + await jsonld.expand(doc, {expansionMap}); - await jsonld.expand(docWithRelativeIriId, {expansionMap}); + assert.equal(expansionMapCalled, true); + }); + + it("should be called when '@id' is being \ + expanded with `@base`", async () => { + const doc = { + '@context': { + "@base": "http://example.com/", + }, + '@id': "relativeIri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.prependedIri) { + assert.deepStrictEqual(info.prependedIri, { + type: '@base', + base: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); + expansionMapCalled = true; + } + }; - assert.equal(expansionMapCalled, true); + await jsonld.expand(doc, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called when aliased '@id' \ + is being expanded with `@base`", async () => { + const doc = { + '@context': { + "@base": "http://example.com/", + "id": "@id" + }, + 'id': "relativeIri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.prependedIri) { + assert.deepStrictEqual(info.prependedIri, { + type: '@base', + base: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); + expansionMapCalled = true; + } + }; + + await jsonld.expand(doc, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called when '@type' is \ + being expanded with `@base`", async () => { + const doc = { + '@context': { + "@base": "http://example.com/", + }, + '@type': "relativeIri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.prependedIri) { + assert.deepStrictEqual(info.prependedIri, { + type: '@base', + base: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); + expansionMapCalled = true; + } + }; + + await jsonld.expand(doc, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + + it("should be called when aliased '@type' is \ + being expanded with `@base`", async () => { + const doc = { + '@context': { + "@base": "http://example.com/", + "type": "@type" + }, + 'type': "relativeIri", + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.prependedIri) { + assert.deepStrictEqual(info.prependedIri, { + type: '@base', + base: 'http://example.com/', + value: 'relativeIri', + result: 'http://example.com/relativeIri' + }); + expansionMapCalled = true; + } + }; + + await jsonld.expand(doc, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); }); }); From fd132f5b2bd1ee8945c4355fb11449620ba8057b Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Wed, 26 May 2021 10:37:24 +1200 Subject: [PATCH 13/16] fix: refactor @base expansionLogic --- lib/context.js | 49 +++++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/lib/context.js b/lib/context.js index f6fdd85e..9e559c09 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1069,42 +1069,22 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { // the null case preserves value as potentially relative value = prependedResult; } - } else if(relativeTo.base && '@base' in activeCtx) { + } else if(relativeTo.base) { // prepend base - if(activeCtx['@base']) { - const prependedResult = prependBase( - prependBase(options.base, activeCtx['@base']), value); - - let expansionMapResult = undefined; - if(options && options.expansionMap) { - // if we are about to expand the value by prepending - // @base then call the expansion map to inform - // interested callers that this is occurring - - // TODO: use `await` to support async - expansionMapResult = options.expansionMap({ - prependedIri: { - type: '@base', - base: activeCtx['@base'], - value, - result: prependedResult - }, - activeCtx, - options - }); - } - if(expansionMapResult !== undefined) { - value = expansionMapResult; - } else { - // the null case preserves value as potentially relative - value = prependedResult; + let prependedResult; + let expansionMapResult; + let base; + if('@base' in activeCtx) { + if(activeCtx['@base']) { + base = prependBase(options.base, activeCtx['@base']); + prependedResult = prependBase(base, value); } + } else { + base = options.base; + prependedResult = prependBase(options.base, value); } - } else if(relativeTo.base) { - const prependedResult = prependBase(options.base, value); - let expansionMapResult = undefined; if(options && options.expansionMap) { - // if we are about to expand the value by prepending + // if we are about to expand the value by pre-pending // @base then call the expansion map to inform // interested callers that this is occurring @@ -1112,7 +1092,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { expansionMapResult = options.expansionMap({ prependedIri: { type: '@base', - base: options.base, + base, value, result: prependedResult }, @@ -1122,7 +1102,8 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { } if(expansionMapResult !== undefined) { value = expansionMapResult; - } else { + } else if(prependedResult !== undefined) { + // the null case preserves value as potentially relative value = prependedResult; } } From f0b704528150c4fca49b86536bd8fedd59b710cb Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Wed, 9 Jun 2021 12:06:47 +1200 Subject: [PATCH 14/16] fix: add hook for typeExpansion, cover @base null --- lib/context.js | 20 ++++++++++++--- lib/expand.js | 8 +++--- tests/misc.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/lib/context.js b/lib/context.js index 9e559c09..cc2aefa1 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1041,6 +1041,14 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { } } + // A flag that captures whether the iri being expanded is + // the value for an @type + let typeExpansion = false; + + if (options !== undefined && options.typeExpansion !== undefined) { + typeExpansion = options.typeExpansion; + } + if(relativeTo.vocab && '@vocab' in activeCtx) { // prepend vocab const prependedResult = activeCtx['@vocab'] + value; @@ -1056,7 +1064,8 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { type: '@vocab', vocab: activeCtx['@vocab'], value, - result: prependedResult + result: prependedResult, + typeExpansion, }, activeCtx, options @@ -1078,6 +1087,9 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { if(activeCtx['@base']) { base = prependBase(options.base, activeCtx['@base']); prependedResult = prependBase(base, value); + } else { + base = activeCtx['@base']; + prependedResult = value; } } else { base = options.base; @@ -1094,7 +1106,8 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { type: '@base', base, value, - result: prependedResult + result: prependedResult, + typeExpansion, }, activeCtx, options @@ -1102,7 +1115,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { } if(expansionMapResult !== undefined) { value = expansionMapResult; - } else if(prependedResult !== undefined) { + } else { // the null case preserves value as potentially relative value = prependedResult; } @@ -1118,6 +1131,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { const expandedResult = options.expansionMap({ relativeIri: value, activeCtx, + typeExpansion, options }); if(expandedResult !== undefined) { diff --git a/lib/expand.js b/lib/expand.js index 0cf50746..5503555b 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -431,7 +431,7 @@ async function _expandObject({ const isJsonType = element[typeKey] && _expandIri(activeCtx, (_isArray(element[typeKey]) ? element[typeKey][0] : element[typeKey]), - {vocab: true}, options) === '@json'; + {vocab: true}, { ...options, typeExpansion: true }) === '@json'; for(const key of keys) { let value = element[key]; @@ -527,7 +527,7 @@ async function _expandObject({ value = Object.fromEntries(Object.entries(value).map(([k, v]) => [ _expandIri(typeScopedContext, k, {vocab: true}), _asArray(v).map(vv => - _expandIri(typeScopedContext, vv, {base: true, vocab: true}) + _expandIri(typeScopedContext, vv, {base: true, vocab: true}, { ...options, typeExpansion: true }) ) ])); } @@ -537,7 +537,7 @@ async function _expandObject({ _asArray(value).map(v => _isString(v) ? _expandIri(typeScopedContext, v, - {base: true, vocab: true}, options) : v), + {base: true, vocab: true}, { ...options, typeExpansion: true }) : v), {propertyIsArray: options.isFrame}); continue; } @@ -937,7 +937,7 @@ function _expandValue({activeCtx, activeProperty, value, options}) { if(expandedProperty === '@id') { return _expandIri(activeCtx, value, {base: true}, options); } else if(expandedProperty === '@type') { - return _expandIri(activeCtx, value, {vocab: true, base: true}, options); + return _expandIri(activeCtx, value, {vocab: true, base: true}, { ...options, typeExpansion: true }); } // get type definition from context diff --git a/tests/misc.js b/tests/misc.js index 4ac9578e..aace2768 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -614,6 +614,36 @@ describe('expansionMap', () => { assert.equal(expansionMapCalled, true); }); + it('should be called on relative iri for type term in scoped context', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedType': { + '@id': 'https://example.com#definedType', + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + + } + } + }, + 'id': "urn:absoluteiri", + '@type': "definedType", + definedTerm: { + '@type': 'relativeiri' + } + }; + + let expansionMapCalled = false; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri') { + expansionMapCalled = true; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalled, true); + }); + it('should be called on relative iri for \ type term with multiple relative iri types', async () => { const docWithRelativeIriId = { @@ -638,6 +668,38 @@ describe('expansionMap', () => { assert.equal(expansionMapCalledTimes, 3); }); + it('should be called on relative iri for \ + type term with multiple relative iri types in scoped context', async () => { + const docWithRelativeIriId = { + '@context': { + 'definedType': { + '@id': 'https://example.com#definedType', + '@context': { + 'definedTerm': 'https://example.com#definedTerm' + + } + } + }, + 'id': "urn:absoluteiri", + '@type': "definedType", + definedTerm: { + '@type': ["relativeiri", "anotherRelativeiri" ] + } + }; + + let expansionMapCalledTimes = 0; + const expansionMap = info => { + if(info.relativeIri === 'relativeiri' || + info.relativeIri === 'anotherRelativeiri') { + expansionMapCalledTimes++; + } + }; + + await jsonld.expand(docWithRelativeIriId, {expansionMap}); + + assert.equal(expansionMapCalledTimes, 3); + }); + it('should be called on relative iri for \ type term with multiple types', async () => { const docWithRelativeIriId = { @@ -764,6 +826,7 @@ describe('expansionMap', () => { type: '@vocab', vocab: 'http://example.com/', value: 'term', + typeExpansion: false, result: 'http://example.com/term' }); expansionMapCalled = true; @@ -789,6 +852,7 @@ describe('expansionMap', () => { type: '@vocab', vocab: 'http://example.com/', value: 'relativeIri', + typeExpansion: true, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; @@ -815,6 +879,7 @@ describe('expansionMap', () => { type: '@vocab', vocab: 'http://example.com/', value: 'relativeIri', + typeExpansion: true, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; @@ -841,6 +906,7 @@ describe('expansionMap', () => { type: '@base', base: 'http://example.com/', value: 'relativeIri', + typeExpansion: false, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; @@ -869,6 +935,7 @@ describe('expansionMap', () => { type: '@base', base: 'http://example.com/', value: 'relativeIri', + typeExpansion: false, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; @@ -896,6 +963,7 @@ describe('expansionMap', () => { type: '@base', base: 'http://example.com/', value: 'relativeIri', + typeExpansion: true, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; @@ -924,6 +992,7 @@ describe('expansionMap', () => { type: '@base', base: 'http://example.com/', value: 'relativeIri', + typeExpansion: true, result: 'http://example.com/relativeIri' }); expansionMapCalled = true; From 57ae8a66207a9f5f04bf9028d5a9ddf27feee416 Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Wed, 9 Jun 2021 13:35:16 +1200 Subject: [PATCH 15/16] Update lib/context.js Co-authored-by: Dave Longley --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index cc2aefa1..5f0de789 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1045,7 +1045,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { // the value for an @type let typeExpansion = false; - if (options !== undefined && options.typeExpansion !== undefined) { + if(options !== undefined && options.typeExpansion !== undefined) { typeExpansion = options.typeExpansion; } From 38144732a63c34ba2130ced4646f71e4a2f1822a Mon Sep 17 00:00:00 2001 From: Tobias Looker Date: Wed, 9 Jun 2021 13:38:15 +1200 Subject: [PATCH 16/16] fix: minor linting issues --- lib/expand.js | 11 +++++++---- tests/misc.js | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index 5503555b..737def7b 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -431,7 +431,7 @@ async function _expandObject({ const isJsonType = element[typeKey] && _expandIri(activeCtx, (_isArray(element[typeKey]) ? element[typeKey][0] : element[typeKey]), - {vocab: true}, { ...options, typeExpansion: true }) === '@json'; + {vocab: true}, {...options, typeExpansion: true}) === '@json'; for(const key of keys) { let value = element[key]; @@ -527,7 +527,8 @@ async function _expandObject({ value = Object.fromEntries(Object.entries(value).map(([k, v]) => [ _expandIri(typeScopedContext, k, {vocab: true}), _asArray(v).map(vv => - _expandIri(typeScopedContext, vv, {base: true, vocab: true}, { ...options, typeExpansion: true }) + _expandIri(typeScopedContext, vv, {base: true, vocab: true}, + {...options, typeExpansion: true}) ) ])); } @@ -537,7 +538,8 @@ async function _expandObject({ _asArray(value).map(v => _isString(v) ? _expandIri(typeScopedContext, v, - {base: true, vocab: true}, { ...options, typeExpansion: true }) : v), + {base: true, vocab: true}, + {...options, typeExpansion: true}) : v), {propertyIsArray: options.isFrame}); continue; } @@ -937,7 +939,8 @@ function _expandValue({activeCtx, activeProperty, value, options}) { if(expandedProperty === '@id') { return _expandIri(activeCtx, value, {base: true}, options); } else if(expandedProperty === '@type') { - return _expandIri(activeCtx, value, {vocab: true, base: true}, { ...options, typeExpansion: true }); + return _expandIri(activeCtx, value, {vocab: true, base: true}, + {...options, typeExpansion: true}); } // get type definition from context diff --git a/tests/misc.js b/tests/misc.js index aace2768..d9dae4fc 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -614,7 +614,8 @@ describe('expansionMap', () => { assert.equal(expansionMapCalled, true); }); - it('should be called on relative iri for type term in scoped context', async () => { + it('should be called on relative iri for type\ + term in scoped context', async () => { const docWithRelativeIriId = { '@context': { 'definedType': {