From 6307b8978963502ee769792e59603a2db897e442 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Mon, 13 May 2019 18:10:24 -0400 Subject: [PATCH 01/11] Allow override of a protected term that uses the same definition. - The only caveat to the "same definition" is that if the new definition does not include `@protected`, this flag is carried forward to maintain the protection status. --- lib/context.js | 93 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/lib/context.js b/lib/context.js index 1dca2e13..9521cc09 100644 --- a/lib/context.js +++ b/lib/context.js @@ -301,33 +301,10 @@ api.createTermDefinition = ( {code: 'invalid term definition', context: localCtx}); } - // FIXME if(1.1) ... ? - if(activeCtx.protected.hasOwnProperty(term) && - !isPropertyTermScopedContext) { - const protectedMode = (options && options.protectedMode) || 'error'; - if(protectedMode === 'error') { - throw new JsonLdError( - 'Invalid JSON-LD syntax; tried to redefine a protected term.', - 'jsonld.SyntaxError', - {code: 'protected term redefinition', context: localCtx, term}); - } else if(protectedMode === 'warn') { - // FIXME: remove logging and use a handler - console.warn('WARNING: protected term redefinition', {term}); - return; - } - throw new JsonLdError( - 'Invalid protectedMode.', - 'jsonld.SyntaxError', - {code: 'invalid protected mode', context: localCtx, term, - protectedMode}); - } - // remove old mapping let previousMapping = null; if(activeCtx.mappings.has(term)) { - if(isTypeScopedContext) { - previousMapping = activeCtx.mappings.get(term); - } + previousMapping = activeCtx.mappings.get(term); activeCtx.mappings.delete(term); } @@ -652,6 +629,33 @@ api.createTermDefinition = ( 'Invalid JSON-LD syntax; @context and @preserve cannot be aliased.', 'jsonld.SyntaxError', {code: 'invalid keyword alias', context: localCtx}); } + + // FIXME if(1.1) ... ? + if(previousMapping && previousMapping.protected && + !isPropertyTermScopedContext) { + // force new term to continue to be protected and see if the mappings would + // be equal + activeCtx.protected[term] = true; + mapping.protected = true; + if(!_areTermDefinitionsEqual(previousMapping, mapping)) { + const protectedMode = (options && options.protectedMode) || 'error'; + if(protectedMode === 'error') { + throw new JsonLdError( + 'Invalid JSON-LD syntax; tried to redefine a protected term.', + 'jsonld.SyntaxError', + {code: 'protected term redefinition', context: localCtx, term}); + } else if(protectedMode === 'warn') { + // FIXME: remove logging and use a handler + console.warn('WARNING: protected term redefinition', {term}); + return; + } + throw new JsonLdError( + 'Invalid protectedMode.', + 'jsonld.SyntaxError', + {code: 'invalid protected mode', context: localCtx, term, + protectedMode}); + } + } }; /** @@ -1303,3 +1307,44 @@ function _findContextUrls(input, urls, replace, base) { } } } + +function _areTermDefinitionsEqual(d1, d2) { + const k1 = Object.keys(d1); + const k2 = Object.keys(d2); + if(k1.length !== k2.length) { + return false; + } + + for(const k of k1) { + const v1 = d1[k]; + const v2 = d2[k]; + if(k === '@context') { + if(JSON.stringify(v1) !== JSON.stringify(v2)) { + return false; + } + continue; + } + const isArray = Array.isArray(v1); + if(isArray !== Array.isArray(v2)) { + return false; + } + // `@container` + if(isArray) { + if(v1.length !== v2.length) { + return false; + } + v1.sort(); + v2.sort(); + for(let i = 0; i < v1.length; ++i) { + if(v1[i] !== v2[i]) { + return false; + } + } + } + // strings + if(v1 !== v2) { + return false; + } + } + return true; +} From ff094de0965d59216217dba0c9d162d9da26d9e7 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Mon, 13 May 2019 23:14:59 -0400 Subject: [PATCH 02/11] Add FIXME. --- lib/context.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/context.js b/lib/context.js index 9521cc09..a25e47dd 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1319,6 +1319,7 @@ function _areTermDefinitionsEqual(d1, d2) { const v1 = d1[k]; const v2 = d2[k]; if(k === '@context') { + // FIXME: temporary hack, use deep comparison instead if(JSON.stringify(v1) !== JSON.stringify(v2)) { return false; } From 1c00ec3ec829f79201cc4ae053c2887fab4d462d Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Mon, 20 May 2019 11:50:14 -0400 Subject: [PATCH 03/11] Preserve previous context when processing type-scoped contexts. --- lib/compact.js | 4 +- lib/context.js | 111 ++++++++++++++++++++++++++----------------------- lib/expand.js | 8 ++-- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/lib/compact.js b/lib/compact.js index ce990f2f..0c2c69e7 100644 --- a/lib/compact.js +++ b/lib/compact.js @@ -143,8 +143,8 @@ api.compact = ({ const rval = {}; - // revert type scoped terms - activeCtx = activeCtx.revertTypeScopedTerms(); + // revert type scoped context + activeCtx = activeCtx.revertTypeScopedContext(); if(options.link && '@id' in element) { // store linked element diff --git a/lib/context.js b/lib/context.js index a25e47dd..97dedf97 100644 --- a/lib/context.js +++ b/lib/context.js @@ -66,6 +66,23 @@ api.process = ({ return activeCtx; } + // track the previous context + const previousContext = activeCtx; + + // if context is property scoped and there's a previous context, amend it, + // not the current one + if(isPropertyTermScopedContext && activeCtx.previousContext) { + // TODO: consider optimizing to a shallow copy + activeCtx = activeCtx.clone(); + activeCtx.previousContext = api.process({ + activeCtx: activeCtx.previousContext, + localCtx: ctxs, + options, + isPropertyTermScopedContext + }); + return activeCtx; + } + // process each context in order, update active context // on each iteration to ensure proper caching let rval = activeCtx; @@ -75,15 +92,6 @@ api.process = ({ // update active context to one computed from last iteration activeCtx = rval; - // get context from cache if available - if(api.cache) { - const cached = api.cache.get(activeCtx, ctx); - if(cached) { - rval = activeCtx = cached; - continue; - } - } - // reset to initial context if(ctx === null) { // We can't nullify if there are protected terms and we're @@ -102,7 +110,7 @@ api.process = ({ console.warn('WARNING: invalid context nullification'); const oldActiveCtx = activeCtx; // copy all protected term definitions to fresh initial context - rval = activeCtx = api.getInitialContext(options); + rval = activeCtx = api.getInitialContext(options).clone(); for(const [term, _protected] of Object.entries(oldActiveCtx.protected)) { if(_protected) { @@ -124,10 +132,23 @@ api.process = ({ 'jsonld.SyntaxError', {code: 'invalid protected mode', context: localCtx, protectedMode}); } - rval = activeCtx = api.getInitialContext(options); + rval = activeCtx = api.getInitialContext(options).clone(); + // if context is type-scoped, ensure previous context has been set + if(isTypeScopedContext) { + rval.previousContext = previousContext.clone(); + } continue; } + // get context from cache if available + if(api.cache) { + const cached = api.cache.get(activeCtx, ctx); + if(cached) { + rval = activeCtx = cached; + continue; + } + } + // dereference @context key if present if(_isObject(ctx) && '@context' in ctx) { ctx = ctx['@context']; @@ -140,6 +161,9 @@ api.process = ({ 'jsonld.SyntaxError', {code: 'invalid local context', context: ctx}); } + // TODO: there is likely a `preivousContext` cloning optimization that + // could be applied here (no need to copy it under certain conditions) + // clone context before updating it rval = rval.clone(); @@ -239,7 +263,12 @@ api.process = ({ for(const key in ctx) { api.createTermDefinition( rval, ctx, key, defined, options, - isPropertyTermScopedContext, isTypeScopedContext); + isPropertyTermScopedContext); + } + + // if context is type-scoped, ensure previous context has been set + if(isTypeScopedContext && !rval.previousContext) { + rval.previousContext = previousContext.clone(); } // cache result @@ -248,6 +277,11 @@ api.process = ({ } } + // if context is type-scoped, ensure previous context has been set + if(isTypeScopedContext && !rval.previousContext) { + rval.previousContext = previousContext.clone(); + } + return rval; }; @@ -265,13 +299,10 @@ api.process = ({ * signal a warning. * @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context * from a property term. - * @param isTypeScopedContext `true` if `localCtx` is a scoped context - * from a type. */ api.createTermDefinition = ( activeCtx, localCtx, term, defined, options, - isPropertyTermScopedContext = false, - isTypeScopedContext = false) => { + isPropertyTermScopedContext = false) => { if(defined.has(term)) { // term already defined if(defined.get(term)) { @@ -301,10 +332,11 @@ api.createTermDefinition = ( {code: 'invalid term definition', context: localCtx}); } + // keep reference to previous mapping for potential `@protected` check + const previousMapping = activeCtx.mappings.get(term); + // remove old mapping - let previousMapping = null; if(activeCtx.mappings.has(term)) { - previousMapping = activeCtx.mappings.get(term); activeCtx.mappings.delete(term); } @@ -339,11 +371,6 @@ api.createTermDefinition = ( // create new mapping const mapping = {}; activeCtx.mappings.set(term, mapping); - if(isTypeScopedContext) { - activeCtx.hasTypeScopedTerms = true; - mapping.isTypeScopedTerm = true; - mapping.previousMapping = previousMapping; - } mapping.reverse = false; // make sure term definition only has expected keywords @@ -785,7 +812,7 @@ api.getInitialContext = options => { inverse: null, getInverse: _createInverseContext, clone: _cloneActiveContext, - revertTypeScopedTerms: _revertTypeScopedTerms, + revertTypeScopedContext: _revertTypeScopedContext, protected: {} }; // TODO: consider using LRU cache instead @@ -961,7 +988,10 @@ api.getInitialContext = options => { child.inverse = null; child.getInverse = this.getInverse; child.protected = util.clone(this.protected); - child.revertTypeScopedTerms = this.revertTypeScopedTerms; + if(this.previousContext) { + child.previousContext = this.previousContext.clone(); + } + child.revertTypeScopedContext = this.revertTypeScopedContext; if('@language' in this) { child['@language'] = this['@language']; } @@ -972,35 +1002,14 @@ api.getInitialContext = options => { } /** - * Reverts any type-scoped terms in this active context to their previous - * mappings. + * Reverts any type-scoped context in this active context to the previous + * context. */ - function _revertTypeScopedTerms() { - // optimization: no type-scoped terms to remove, reuse active context - if(!this.hasTypeScopedTerms) { + function _revertTypeScopedContext() { + if(!this.previousContext) { return this; } - // create clone without type scoped terms - const child = this.clone(); - const entries = child.mappings.entries(); - for(const [term, mapping] of entries) { - if(mapping.isTypeScopedTerm) { - if(mapping.previousMapping) { - child.mappings.set(term, mapping.previousMapping); - if(mapping.previousMapping.protected) { - child.protected[term] = true; - } else { - delete child.protected[term]; - } - } else { - child.mappings.delete(term); - if(child.protected[term]) { - delete child.protected[term]; - } - } - } - } - return child; + return this.previousContext.clone(); } }; diff --git a/lib/expand.js b/lib/expand.js index 4619a0d3..2574da1a 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -153,8 +153,8 @@ api.expand = ({ // recursively expand object: if(!insideIndex) { - // revert type scoped terms - activeCtx = activeCtx.revertTypeScopedTerms(); + // revert type scoped context + activeCtx = activeCtx.revertTypeScopedContext(); } // if element has a context, process it @@ -613,8 +613,8 @@ function _expandObject({ } else if(container.includes('@type') && _isObject(value)) { // handle type container (skip if value is not an object) expandedValue = _expandIndexMap({ - // since container is `@type`, revert type scoped terms when expanding - activeCtx: termCtx.revertTypeScopedTerms(), + // since container is `@type`, revert type scoped context when expanding + activeCtx: termCtx.revertTypeScopedContext(), options, activeProperty: key, value, From 32d9beb83024defaedafd3971b84e20f1097d67a Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Mon, 20 May 2019 15:21:26 -0400 Subject: [PATCH 04/11] Implement deep compare for term definitions. --- lib/context.js | 61 ++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/lib/context.js b/lib/context.js index 97dedf97..9f389199 100644 --- a/lib/context.js +++ b/lib/context.js @@ -664,7 +664,7 @@ api.createTermDefinition = ( // be equal activeCtx.protected[term] = true; mapping.protected = true; - if(!_areTermDefinitionsEqual(previousMapping, mapping)) { + if(!_deepCompare(previousMapping, mapping)) { const protectedMode = (options && options.protectedMode) || 'error'; if(protectedMode === 'error') { throw new JsonLdError( @@ -1317,42 +1317,45 @@ function _findContextUrls(input, urls, replace, base) { } } -function _areTermDefinitionsEqual(d1, d2) { - const k1 = Object.keys(d1); - const k2 = Object.keys(d2); - if(k1.length !== k2.length) { +function _deepCompare(x1, x2) { + // compare `null` or primitive types directly + if((!(x1 && typeof x1 === 'object')) || + (!(x2 && typeof x2 === 'object'))) { + return x1 === x2; + } + // x1 and x2 are objects (also potentially arrays) + const x1Array = Array.isArray(x1); + if(x1Array !== Array.isArray(x2)) { return false; } - - for(const k of k1) { - const v1 = d1[k]; - const v2 = d2[k]; - if(k === '@context') { - // FIXME: temporary hack, use deep comparison instead - if(JSON.stringify(v1) !== JSON.stringify(v2)) { - return false; - } - continue; - } - const isArray = Array.isArray(v1); - if(isArray !== Array.isArray(v2)) { + if(x1Array) { + if(x1.length !== x2.length) { return false; } - // `@container` - if(isArray) { - if(v1.length !== v2.length) { + for(let i = 0; i < x1.length; ++i) { + if(!_deepCompare(x1[i], x2[i])) { return false; } - v1.sort(); - v2.sort(); - for(let i = 0; i < v1.length; ++i) { - if(v1[i] !== v2[i]) { - return false; - } + } + return true; + } + // x1 and x2 are non-array objects + const k1s = Object.keys(x1); + const k2s = Object.keys(x2); + if(k1s.length !== k2s.length) { + return false; + } + for(const k1 in x1) { + let v1 = x1[k1]; + let v2 = x2[k1]; + // special case: `@container` can be in any order + if(k1 === '@container') { + if(Array.isArray(v1) && Array.isArray(v2)) { + v1 = v1.slice().sort(); + v2 = v2.slice().sort(); } } - // strings - if(v1 !== v2) { + if(!_deepCompare(v1, v2)) { return false; } } From 5e4e2e2710dc542aa3558e52efafad907440228e Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Mon, 20 May 2019 17:30:52 -0400 Subject: [PATCH 05/11] Process type-scoped `@type` against previous context. --- lib/context.js | 2 +- lib/expand.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 9f389199..cfcb1c01 100644 --- a/lib/context.js +++ b/lib/context.js @@ -67,7 +67,7 @@ api.process = ({ } // track the previous context - const previousContext = activeCtx; + const previousContext = activeCtx.previousContext || activeCtx; // if context is property scoped and there's a previous context, amend it, // not the current one diff --git a/lib/expand.js b/lib/expand.js index 2574da1a..c08d8a74 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -175,7 +175,8 @@ api.expand = ({ Array.isArray(value) ? (value.length > 1 ? value.slice().sort() : value) : [value]; for(const type of types) { - const ctx = _getContextValue(activeCtx, type, '@context'); + const ctx = _getContextValue( + activeCtx.previousContext || activeCtx, type, '@context'); if(!_isUndefined(ctx)) { activeCtx = _processContext({ activeCtx, @@ -449,7 +450,8 @@ function _expandObject({ expandedParent, '@type', _asArray(value).map(v => _isString(v) ? - _expandIri(activeCtx, v, {base: true, vocab: true}, options) : v), + _expandIri(activeCtx.previousContext || activeCtx, v, + {base: true, vocab: true}, options) : v), {propertyIsArray: options.isFrame}); continue; } From 42392d5511629ef2adf71dec25b7fff3a8124196 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 10:06:00 -0400 Subject: [PATCH 06/11] Resolve `@type` when using type-scoped contexts against previous context. --- lib/compact.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/compact.js b/lib/compact.js index 0c2c69e7..9d52a751 100644 --- a/lib/compact.js +++ b/lib/compact.js @@ -161,12 +161,15 @@ api.compact = ({ if(types.length > 1) { types = Array.from(types).sort(); } + // find all type-scoped contexts based on current context, prior to + // updating it + const typeContext = activeCtx; for(const type of types) { const compactedType = api.compactIri( - {activeCtx, iri: type, relativeTo: {vocab: true}}); + {activeCtx: typeContext, iri: type, relativeTo: {vocab: true}}); // Use any type-scoped context defined on this value - const ctx = _getContextValue(activeCtx, compactedType, '@context'); + const ctx = _getContextValue(typeContext, compactedType, '@context'); if(!_isUndefined(ctx)) { activeCtx = _processContext({ activeCtx, @@ -184,13 +187,16 @@ api.compact = ({ // compact @id and @type(s) if(expandedProperty === '@id' || expandedProperty === '@type') { + // if using a type-scoped context, resolve type values against previous + // context + const isType = expandedProperty === '@type'; + const valueContext = isType ? + (activeCtx.previousContext || activeCtx) : activeCtx; let compactedValue = _asArray(expandedValue).map( expandedIri => api.compactIri({ - activeCtx, + activeCtx: valueContext, iri: expandedIri, - relativeTo: { - vocab: expandedProperty === '@type' - } + relativeTo: {vocab: isType} })); if(compactedValue.length === 1) { compactedValue = compactedValue[0]; From 2ee232ae2682851260df9c4fd37bfc1db4adffab Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 10:55:45 -0400 Subject: [PATCH 07/11] Expand values and subject references under typed scope. --- lib/expand.js | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index c08d8a74..37f8c8df 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -152,7 +152,37 @@ api.expand = ({ // recursively expand object: - if(!insideIndex) { + // First determine if any type-scoped context should be reverted; it should + // only be reverted when the following are all true: + // 1. `element` is not a value or subject reference + // 2. `insideIndex` is false + const typeScopedContext = activeCtx.previousContext ? activeCtx : null; + let keys = Object.keys(element).sort(); + let mustRevert = !insideIndex; + if(mustRevert && typeScopedContext && keys[0] !== '@context') { + // check if element is a subject reference + if(keys.length === 1) { + const expandedProperty = _expandIri( + typeScopedContext, keys[0], {vocab: true}, options); + if(expandedProperty === '@id') { + // subject reference found, use type-scoped context to expand it + mustRevert = false; + } + } else if(keys.length <= 2) { + // check if element is a value + for(const key of keys) { + const expandedProperty = _expandIri( + typeScopedContext, key, {vocab: true}, options); + if(expandedProperty === '@value') { + // value found, use type-scoped context to expand it + mustRevert = false; + break; + } + } + } + } + + if(mustRevert) { // revert type scoped context activeCtx = activeCtx.revertTypeScopedContext(); } @@ -163,8 +193,7 @@ api.expand = ({ {activeCtx, localCtx: element['@context'], options}); } - // look for scoped context on @type - let keys = Object.keys(element).sort(); + // look for scoped contexts on `@type` for(const key of keys) { const expandedProperty = _expandIri(activeCtx, key, {vocab: true}, options); if(expandedProperty === '@type') { From 55d013db70ce7657e8aafb079e697876564f0abe Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 13:07:37 -0400 Subject: [PATCH 08/11] Pass type-scoped context for value expansion. --- lib/expand.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index 37f8c8df..0d4192fe 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -52,6 +52,9 @@ module.exports = api; * @param insideList true if the element is a list, false if not. * @param insideIndex true if the element is inside an index container, * false if not. + * @param typeScopedContext an optional type-scoped active context for + * expanding values of nodes that were expressed according to + * a type-scoped context. * @param expansionMap(info) a function that can be used to custom map * unmappable values (or to throw an error when they are detected); * if this function returns `undefined` then the default behavior @@ -66,6 +69,7 @@ api.expand = ({ options = {}, insideList = false, insideIndex = false, + typeScopedContext = null, expansionMap = () => undefined }) => { // nothing to expand @@ -152,11 +156,16 @@ api.expand = ({ // recursively expand object: - // First determine if any type-scoped context should be reverted; it should - // only be reverted when the following are all true: + // first, expand the active property + const expandedActiveProperty = _expandIri( + activeCtx, activeProperty, {vocab: true}, options); + + // second, determine if any type-scoped context should be reverted; it + // should only be reverted when the following are all true: // 1. `element` is not a value or subject reference // 2. `insideIndex` is false - const typeScopedContext = activeCtx.previousContext ? activeCtx : null; + typeScopedContext = typeScopedContext || + (activeCtx.previousContext ? activeCtx : null); let keys = Object.keys(element).sort(); let mustRevert = !insideIndex; if(mustRevert && typeScopedContext && keys[0] !== '@context') { @@ -165,7 +174,7 @@ api.expand = ({ const expandedProperty = _expandIri( typeScopedContext, keys[0], {vocab: true}, options); if(expandedProperty === '@id') { - // subject reference found, use type-scoped context to expand it + // subject reference found, do not revert mustRevert = false; } } else if(keys.length <= 2) { @@ -174,8 +183,9 @@ api.expand = ({ const expandedProperty = _expandIri( typeScopedContext, key, {vocab: true}, options); if(expandedProperty === '@value') { - // value found, use type-scoped context to expand it + // value found, ensure type-scoped context is used to expand it mustRevert = false; + activeCtx = typeScopedContext; break; } } @@ -218,10 +228,6 @@ api.expand = ({ } } - // expand the active property - const expandedActiveProperty = _expandIri( - activeCtx, activeProperty, {vocab: true}, options); - // process each key and value in element, ignoring @nest content let rval = {}; _expandObject({ @@ -232,6 +238,7 @@ api.expand = ({ expandedParent: rval, options, insideList, + typeScopedContext, expansionMap}); // get property count on expanded output From d453bc547c43079007f875d45ffdd0ed5afd10be Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 13:51:24 -0400 Subject: [PATCH 09/11] Ensure to pass `typeScopedContext` when processing arrays. --- lib/expand.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/expand.js b/lib/expand.js index 0d4192fe..a6f39302 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -119,7 +119,8 @@ api.expand = ({ element: element[i], options, expansionMap, - insideIndex + insideIndex, + typeScopedContext }); if(insideList && (_isArray(e) || _isList(e))) { // lists of lists are illegal From 855e377d253c3a886eaf739f9f0b48bb5b253612 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 14:03:50 -0400 Subject: [PATCH 10/11] Correct logic on value detection. --- lib/expand.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/expand.js b/lib/expand.js index a6f39302..74e466f0 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -169,26 +169,21 @@ api.expand = ({ (activeCtx.previousContext ? activeCtx : null); let keys = Object.keys(element).sort(); let mustRevert = !insideIndex; - if(mustRevert && typeScopedContext && keys[0] !== '@context') { - // check if element is a subject reference - if(keys.length === 1) { + if(mustRevert && typeScopedContext && keys.length <= 2 && + !keys.includes('@context')) { + for(const key of keys) { const expandedProperty = _expandIri( - typeScopedContext, keys[0], {vocab: true}, options); - if(expandedProperty === '@id') { - // subject reference found, do not revert + typeScopedContext, key, {vocab: true}, options); + if(expandedProperty === '@value') { + // value found, ensure type-scoped context is used to expand it mustRevert = false; + activeCtx = typeScopedContext; + break; } - } else if(keys.length <= 2) { - // check if element is a value - for(const key of keys) { - const expandedProperty = _expandIri( - typeScopedContext, key, {vocab: true}, options); - if(expandedProperty === '@value') { - // value found, ensure type-scoped context is used to expand it - mustRevert = false; - activeCtx = typeScopedContext; - break; - } + if(expandedProperty === '@id' && keys.length === 1) { + // subject reference found, do not revert + mustRevert = false; + break; } } } From e445d1de4d8890b340df442edb1b7b6b5172e8ae Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 21 May 2019 14:29:19 -0400 Subject: [PATCH 11/11] Update changelog. --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea19e942..048ad818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # jsonld ChangeLog +## 1.6.2 - 2019-05-xx + +### Fixed +- Allow overriding of protected terms when redefining to the same + definition, modulo the `protected` flag itself. +- Fix type-scoped context application: + - Ensure values and subject references are expanded and compacted + using type-scoped contexts, if available. + - Ensure `@type` values are evaluated against the previous context, + not the type-scoped context. + ## 1.6.1 - 2019-05-13 ### Fixed