Comparing version 1.6.1 to 1.6.2
# jsonld ChangeLog | ||
## 1.6.2 - 2019-05-21 | ||
### 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 | ||
@@ -4,0 +15,0 @@ |
@@ -160,5 +160,5 @@ /* | ||
const insideReverse = activeProperty === '@reverse'; | ||
const rval = {}; // revert type scoped terms | ||
const rval = {}; // revert type scoped context | ||
activeCtx = activeCtx.revertTypeScopedTerms(); | ||
activeCtx = activeCtx.revertTypeScopedContext(); | ||
@@ -184,7 +184,11 @@ if (options.link && '@id' in element) { | ||
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, | ||
activeCtx: typeContext, | ||
iri: type, | ||
@@ -196,3 +200,3 @@ relativeTo: { | ||
const ctx = _getContextValue(activeCtx, compactedType, '@context'); | ||
const ctx = _getContextValue(typeContext, compactedType, '@context'); | ||
@@ -216,7 +220,12 @@ if (!_isUndefined(ctx)) { | ||
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' | ||
vocab: isType | ||
} | ||
@@ -648,2 +657,8 @@ })); | ||
return iri; | ||
} // if context is from a property term scoped context composed with a | ||
// type-scoped context, then use the previous context instead | ||
if (activeCtx.isPropertyTermScoped && activeCtx.previousContext) { | ||
activeCtx = activeCtx.previousContext; | ||
} | ||
@@ -650,0 +665,0 @@ |
@@ -72,2 +72,19 @@ /* | ||
return activeCtx; | ||
} // track the previous context | ||
const previousContext = activeCtx.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.isPropertyTermScoped = true; | ||
activeCtx.previousContext = api.process({ | ||
activeCtx: activeCtx.previousContext, | ||
localCtx: ctxs, | ||
options, | ||
isPropertyTermScopedContext | ||
}); | ||
return activeCtx; | ||
} // process each context in order, update active context | ||
@@ -82,14 +99,4 @@ // on each iteration to ensure proper caching | ||
activeCtx = rval; // get context from cache if available | ||
activeCtx = rval; // reset to initial context | ||
if (api.cache) { | ||
const cached = api.cache.get(activeCtx, ctx); | ||
if (cached) { | ||
rval = activeCtx = cached; | ||
continue; | ||
} | ||
} // reset to initial context | ||
if (ctx === null) { | ||
@@ -110,3 +117,3 @@ // We can't nullify if there are protected terms and we're | ||
rval = activeCtx = api.getInitialContext(options); | ||
rval = activeCtx = api.getInitialContext(options).clone(); | ||
@@ -135,4 +142,19 @@ for (const [term, _protected] of Object.entries(oldActiveCtx.protected)) { | ||
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 | ||
@@ -151,3 +173,5 @@ | ||
}); | ||
} // clone context before updating it | ||
} // 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 | ||
@@ -248,3 +272,8 @@ | ||
for (const key in ctx) { | ||
api.createTermDefinition(rval, ctx, key, defined, options, isPropertyTermScopedContext, isTypeScopedContext); | ||
api.createTermDefinition(rval, ctx, key, defined, options, isPropertyTermScopedContext); | ||
} // if context is type-scoped, ensure previous context has been set | ||
if (isTypeScopedContext && !rval.previousContext) { | ||
rval.previousContext = previousContext.clone(); | ||
} // cache result | ||
@@ -274,8 +303,6 @@ | ||
* 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) => { | ||
api.createTermDefinition = (activeCtx, localCtx, term, defined, options, isPropertyTermScopedContext = false) => { | ||
if (defined.has(term)) { | ||
@@ -311,38 +338,8 @@ // term already defined | ||
}); | ||
} // FIXME if(1.1) ... ? | ||
} // keep reference to previous mapping for potential `@protected` check | ||
if (activeCtx.protected.hasOwnProperty(term) && !isPropertyTermScopedContext) { | ||
const protectedMode = options && options.protectedMode || 'error'; | ||
const previousMapping = activeCtx.mappings.get(term); // remove old mapping | ||
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); | ||
} | ||
activeCtx.mappings.delete(term); | ||
@@ -385,9 +382,2 @@ } // get context term value | ||
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 | ||
@@ -530,3 +520,3 @@ | ||
if (!_isString(type)) { | ||
throw new JsonLdError('Invalid JSON-LD syntax; an @context @type values must be a string.', 'jsonld.SyntaxError', { | ||
throw new JsonLdError('Invalid JSON-LD syntax; an @context @type value must be a string.', 'jsonld.SyntaxError', { | ||
code: 'invalid type mapping', | ||
@@ -552,3 +542,3 @@ context: localCtx | ||
if (type.indexOf('_:') === 0) { | ||
throw new JsonLdError('Invalid JSON-LD syntax; an @context @type values must be an IRI, ' + 'not a blank node identifier.', 'jsonld.SyntaxError', { | ||
throw new JsonLdError('Invalid JSON-LD syntax; an @context @type value must be an IRI, ' + 'not a blank node identifier.', 'jsonld.SyntaxError', { | ||
code: 'invalid type mapping', | ||
@@ -686,2 +676,35 @@ 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 (!_deepCompare(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 | ||
}); | ||
} | ||
} | ||
@@ -737,2 +760,8 @@ }; | ||
api.createTermDefinition(activeCtx, localCtx, value, defined, options); | ||
} // if context is from a property term scoped context composed with a | ||
// type-scoped context, then use previous context instead | ||
if (activeCtx.isPropertyTermScoped && activeCtx.previousContext) { | ||
activeCtx = activeCtx.previousContext; | ||
} | ||
@@ -823,3 +852,3 @@ | ||
clone: _cloneActiveContext, | ||
revertTypeScopedTerms: _revertTypeScopedTerms, | ||
revertTypeScopedContext: _revertTypeScopedContext, | ||
protected: {} | ||
@@ -1014,4 +1043,10 @@ }; // TODO: consider using LRU cache instead | ||
child.protected = util.clone(this.protected); | ||
child.revertTypeScopedTerms = this.revertTypeScopedTerms; | ||
if (this.previousContext) { | ||
child.isPropertyTermScoped = this.previousContext.isPropertyTermScoped; | ||
child.previousContext = this.previousContext.clone(); | ||
} | ||
child.revertTypeScopedContext = this.revertTypeScopedContext; | ||
if ('@language' in this) { | ||
@@ -1028,38 +1063,13 @@ child['@language'] = this['@language']; | ||
/** | ||
* 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(); | ||
} | ||
@@ -1410,2 +1420,56 @@ }; | ||
} | ||
} | ||
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; | ||
} | ||
if (x1Array) { | ||
if (x1.length !== x2.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < x1.length; ++i) { | ||
if (!_deepCompare(x1[i], x2[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(); | ||
} | ||
} | ||
if (!_deepCompare(v1, v2)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
@@ -54,2 +54,5 @@ /* | ||
* 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 | ||
@@ -70,2 +73,3 @@ * unmappable values (or to throw an error when they are detected); | ||
insideIndex = false, | ||
typeScopedContext = null, | ||
expansionMap = () => undefined | ||
@@ -129,3 +133,4 @@ }) => { | ||
expansionMap, | ||
insideIndex | ||
insideIndex, | ||
typeScopedContext | ||
}); | ||
@@ -167,7 +172,41 @@ | ||
} // recursively expand object: | ||
// first, expand the active property | ||
if (!insideIndex) { | ||
// revert type scoped terms | ||
activeCtx = activeCtx.revertTypeScopedTerms(); | ||
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 | ||
typeScopedContext = typeScopedContext || (activeCtx.previousContext ? activeCtx : null); | ||
let keys = Object.keys(element).sort(); | ||
let mustRevert = !insideIndex; | ||
if (mustRevert && typeScopedContext && keys.length <= 2 && !keys.includes('@context')) { | ||
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; | ||
} | ||
} | ||
} | ||
if (mustRevert) { | ||
// revert type scoped context | ||
activeCtx = activeCtx.revertTypeScopedContext(); | ||
} // if element has a context, process it | ||
@@ -182,7 +221,5 @@ | ||
}); | ||
} // look for scoped context on @type | ||
} // look for scoped contexts on `@type` | ||
let keys = Object.keys(element).sort(); | ||
for (const key of keys) { | ||
@@ -200,3 +237,3 @@ const expandedProperty = _expandIri(activeCtx, key, { | ||
for (const type of types) { | ||
const ctx = _getContextValue(activeCtx, type, '@context'); | ||
const ctx = _getContextValue(activeCtx.previousContext || activeCtx, type, '@context'); | ||
@@ -213,10 +250,5 @@ if (!_isUndefined(ctx)) { | ||
} | ||
} // expand the active property | ||
} // process each key and value in element, ignoring @nest content | ||
const expandedActiveProperty = _expandIri(activeCtx, activeProperty, { | ||
vocab: true | ||
}, options); // process each key and value in element, ignoring @nest content | ||
let rval = {}; | ||
@@ -232,2 +264,3 @@ | ||
insideList, | ||
typeScopedContext, | ||
expansionMap | ||
@@ -488,3 +521,3 @@ }); // get property count on expanded output | ||
_addValue(expandedParent, '@type', _asArray(value).map(v => _isString(v) ? _expandIri(activeCtx, v, { | ||
_addValue(expandedParent, '@type', _asArray(value).map(v => _isString(v) ? _expandIri(activeCtx.previousContext || activeCtx, v, { | ||
base: true, | ||
@@ -677,4 +710,4 @@ vocab: true | ||
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, | ||
@@ -681,0 +714,0 @@ activeProperty: key, |
@@ -146,4 +146,4 @@ /* | ||
// revert type scoped terms | ||
activeCtx = activeCtx.revertTypeScopedTerms(); | ||
// revert type scoped context | ||
activeCtx = activeCtx.revertTypeScopedContext(); | ||
@@ -165,8 +165,11 @@ if(options.link && '@id' in element) { | ||
} | ||
// 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)) { | ||
@@ -189,9 +192,12 @@ activeCtx = _processContext({ | ||
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} | ||
})); | ||
@@ -587,2 +593,8 @@ if(compactedValue.length === 1) { | ||
// if context is from a property term scoped context composed with a | ||
// type-scoped context, then use the previous context instead | ||
if(activeCtx.isPropertyTermScoped && activeCtx.previousContext) { | ||
activeCtx = activeCtx.previousContext; | ||
} | ||
const inverseCtx = activeCtx.getInverse(); | ||
@@ -589,0 +601,0 @@ |
@@ -69,2 +69,20 @@ /* | ||
// track the previous context | ||
const previousContext = activeCtx.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.isPropertyTermScoped = true; | ||
activeCtx.previousContext = api.process({ | ||
activeCtx: activeCtx.previousContext, | ||
localCtx: ctxs, | ||
options, | ||
isPropertyTermScopedContext | ||
}); | ||
return activeCtx; | ||
} | ||
// process each context in order, update active context | ||
@@ -79,11 +97,2 @@ // on each iteration to ensure proper caching | ||
// 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 | ||
@@ -107,3 +116,3 @@ if(ctx === null) { | ||
// 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 | ||
@@ -130,6 +139,19 @@ Object.entries(oldActiveCtx.protected)) { | ||
} | ||
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 | ||
@@ -147,2 +169,5 @@ if(_isObject(ctx) && '@context' in 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 | ||
@@ -247,5 +272,10 @@ rval = rval.clone(); | ||
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 | ||
@@ -274,9 +304,6 @@ if(api.cache) { | ||
* 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)) { | ||
@@ -311,29 +338,7 @@ // term already defined | ||
// 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}); | ||
} | ||
// 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)) { | ||
if(isTypeScopedContext) { | ||
previousMapping = activeCtx.mappings.get(term); | ||
} | ||
activeCtx.mappings.delete(term); | ||
@@ -373,7 +378,2 @@ } | ||
activeCtx.mappings.set(term, mapping); | ||
if(isTypeScopedContext) { | ||
activeCtx.hasTypeScopedTerms = true; | ||
mapping.isTypeScopedTerm = true; | ||
mapping.previousMapping = previousMapping; | ||
} | ||
mapping.reverse = false; | ||
@@ -506,3 +506,3 @@ | ||
throw new JsonLdError( | ||
'Invalid JSON-LD syntax; an @context @type values must be a string.', | ||
'Invalid JSON-LD syntax; an @context @type value must be a string.', | ||
'jsonld.SyntaxError', | ||
@@ -526,3 +526,3 @@ {code: 'invalid type mapping', context: localCtx}); | ||
throw new JsonLdError( | ||
'Invalid JSON-LD syntax; an @context @type values must be an IRI, ' + | ||
'Invalid JSON-LD syntax; an @context @type value must be an IRI, ' + | ||
'not a blank node identifier.', | ||
@@ -666,2 +666,29 @@ 'jsonld.SyntaxError', | ||
} | ||
// 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(!_deepCompare(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}); | ||
} | ||
} | ||
}; | ||
@@ -718,2 +745,8 @@ | ||
// if context is from a property term scoped context composed with a | ||
// type-scoped context, then use previous context instead | ||
if(activeCtx.isPropertyTermScoped && activeCtx.previousContext) { | ||
activeCtx = activeCtx.previousContext; | ||
} | ||
relativeTo = relativeTo || {}; | ||
@@ -797,3 +830,3 @@ if(relativeTo.vocab) { | ||
clone: _cloneActiveContext, | ||
revertTypeScopedTerms: _revertTypeScopedTerms, | ||
revertTypeScopedContext: _revertTypeScopedContext, | ||
protected: {} | ||
@@ -974,3 +1007,7 @@ }; | ||
child.protected = util.clone(this.protected); | ||
child.revertTypeScopedTerms = this.revertTypeScopedTerms; | ||
if(this.previousContext) { | ||
child.isPropertyTermScoped = this.previousContext.isPropertyTermScoped; | ||
child.previousContext = this.previousContext.clone(); | ||
} | ||
child.revertTypeScopedContext = this.revertTypeScopedContext; | ||
if('@language' in this) { | ||
@@ -986,31 +1023,10 @@ child['@language'] = this['@language']; | ||
/** | ||
* 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(); | ||
} | ||
@@ -1322,1 +1338,46 @@ }; | ||
} | ||
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; | ||
} | ||
if(x1Array) { | ||
if(x1.length !== x2.length) { | ||
return false; | ||
} | ||
for(let i = 0; i < x1.length; ++i) { | ||
if(!_deepCompare(x1[i], x2[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(); | ||
} | ||
} | ||
if(!_deepCompare(v1, v2)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
@@ -55,2 +55,5 @@ /* | ||
* 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 | ||
@@ -70,2 +73,3 @@ * unmappable values (or to throw an error when they are detected); | ||
insideIndex = false, | ||
typeScopedContext = null, | ||
expansionMap = () => undefined | ||
@@ -120,3 +124,4 @@ }) => { | ||
expansionMap, | ||
insideIndex | ||
insideIndex, | ||
typeScopedContext | ||
}); | ||
@@ -158,7 +163,38 @@ if(insideList && (_isArray(e) || _isList(e))) { | ||
if(!insideIndex) { | ||
// revert type scoped terms | ||
activeCtx = activeCtx.revertTypeScopedTerms(); | ||
// 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 | ||
typeScopedContext = typeScopedContext || | ||
(activeCtx.previousContext ? activeCtx : null); | ||
let keys = Object.keys(element).sort(); | ||
let mustRevert = !insideIndex; | ||
if(mustRevert && typeScopedContext && keys.length <= 2 && | ||
!keys.includes('@context')) { | ||
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; | ||
} | ||
} | ||
} | ||
if(mustRevert) { | ||
// revert type scoped context | ||
activeCtx = activeCtx.revertTypeScopedContext(); | ||
} | ||
// if element has a context, process it | ||
@@ -170,4 +206,3 @@ if('@context' in element) { | ||
// look for scoped context on @type | ||
let keys = Object.keys(element).sort(); | ||
// look for scoped contexts on `@type` | ||
for(const key of keys) { | ||
@@ -183,3 +218,4 @@ const expandedProperty = _expandIri(activeCtx, key, {vocab: true}, options); | ||
for(const type of types) { | ||
const ctx = _getContextValue(activeCtx, type, '@context'); | ||
const ctx = _getContextValue( | ||
activeCtx.previousContext || activeCtx, type, '@context'); | ||
if(!_isUndefined(ctx)) { | ||
@@ -197,6 +233,2 @@ activeCtx = _processContext({ | ||
// expand the active property | ||
const expandedActiveProperty = _expandIri( | ||
activeCtx, activeProperty, {vocab: true}, options); | ||
// process each key and value in element, ignoring @nest content | ||
@@ -212,2 +244,3 @@ let rval = {}; | ||
insideList, | ||
typeScopedContext, | ||
expansionMap}); | ||
@@ -460,3 +493,4 @@ | ||
_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}); | ||
@@ -625,4 +659,4 @@ continue; | ||
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, | ||
@@ -629,0 +663,0 @@ activeProperty: key, |
{ | ||
"name": "jsonld", | ||
"version": "1.6.1", | ||
"version": "1.6.2", | ||
"description": "A JSON-LD Processor and API implementation in JavaScript.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/digitalbazaar/jsonld.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1580644
18293