Skip to content

Commit 0a67ca2

Browse files
gkelloggdavidlehn
authored andcommitted
* Use propagate and overrideProtected options instead of isTypeScopedContext/isPropertyScopedContext.
1 parent e763f2b commit 0a67ca2

File tree

3 files changed

+88
-53
lines changed

3 files changed

+88
-53
lines changed

lib/compact.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ api.compact = async ({
102102
// use any scoped context on activeProperty
103103
const ctx = _getContextValue(activeCtx, activeProperty, '@context');
104104
if(!_isUndefined(ctx)) {
105-
// Note: spec's `from term` var is named `isPropertyTermScopedContext`
106105
activeCtx = await _processContext({
107106
activeCtx,
108107
localCtx: ctx,
109-
isPropertyTermScopedContext: true,
108+
propagate: true,
109+
overrideProtected: true,
110110
options
111111
});
112112
}
@@ -159,8 +159,26 @@ api.compact = async ({
159159

160160
const rval = {};
161161

162-
// revert type scoped context
163-
activeCtx = activeCtx.revertTypeScopedContext();
162+
// original context before applying property-scoped and local contexts
163+
const inputCtx = activeCtx;
164+
165+
// revert to previous context, if there is one,
166+
// and element is not a value object or a node reference
167+
if(!_isValue(element) && !_isSubjectReference(element)) {
168+
activeCtx = activeCtx.revertToPreviousContext();
169+
}
170+
171+
// apply property-scoped context after reverting term-scoped context
172+
const propertyScopedCtx = _getContextValue(inputCtx, activeProperty, '@context');
173+
if(!_isUndefined(propertyScopedCtx)) {
174+
activeCtx = await _processContext({
175+
activeCtx,
176+
localCtx: propertyScopedCtx,
177+
propagate: true,
178+
overrideProtected: true,
179+
options
180+
});
181+
}
164182

165183
if(options.link && '@id' in element) {
166184
// store linked element
@@ -185,13 +203,13 @@ api.compact = async ({
185203
{activeCtx: typeContext, iri: type, relativeTo: {vocab: true}});
186204

187205
// Use any type-scoped context defined on this value
188-
const ctx = _getContextValue(typeContext, compactedType, '@context');
206+
const ctx = _getContextValue(inputCtx, compactedType, '@context');
189207
if(!_isUndefined(ctx)) {
190208
activeCtx = await _processContext({
191209
activeCtx,
192210
localCtx: ctx,
193211
options,
194-
isTypeScopedContext: true
212+
propagate: false
195213
});
196214
}
197215
}

lib/context.js

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,16 @@ api.cache = new ActiveContextCache();
4242
* @param activeCtx the current active context.
4343
* @param localCtx the local context to process.
4444
* @param options the context processing options.
45-
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
46-
* from a property term.
47-
* @param isTypeScopedContext `true` if `localCtx` is a scoped context
48-
* from a type.
45+
* @param propagate `true` if `false`, retains any previously defined term,
46+
* which can be rolled back when the descending into a new node object changes.
47+
* @param overrideProtected `false` allows protected terms to be modified.
4948
*
5049
* @return a Promise that resolves to the new active context.
5150
*/
5251
api.process = async ({
5352
activeCtx, localCtx, options,
54-
isPropertyTermScopedContext = false,
55-
isTypeScopedContext = false
53+
propagate = true,
54+
overrideProtected = false,
5655
}) => {
5756
// normalize local context to an array of @context objects
5857
if(_isObject(localCtx) && '@context' in localCtx &&
@@ -66,27 +65,22 @@ api.process = async ({
6665
return activeCtx;
6766
}
6867

69-
// track the previous context
70-
const previousContext = activeCtx.previousContext || activeCtx;
71-
72-
// if context is property scoped and there's a previous context, amend it,
73-
// not the current one
74-
if(isPropertyTermScopedContext && activeCtx.previousContext) {
75-
// TODO: consider optimizing to a shallow copy
76-
activeCtx = activeCtx.clone();
77-
activeCtx.isPropertyTermScoped = true;
78-
activeCtx.previousContext = await api.process({
79-
activeCtx: activeCtx.previousContext,
80-
localCtx: ctxs,
81-
options,
82-
isPropertyTermScopedContext
83-
});
84-
return activeCtx;
68+
// override propagate if localCtx has `@propagate`
69+
if(_isObject(ctxs[0]) && '@propagate' in ctxs[0]) {
70+
// Retrieve early, error checking done later
71+
propagate = ctxs[0]['@propagate'];
8572
}
8673

8774
// process each context in order, update active context
8875
// on each iteration to ensure proper caching
8976
let rval = activeCtx;
77+
78+
// track the previous context
79+
// If not propagating, make sure rval has a previous context
80+
if(!propagate && !rval.previousContext) {
81+
rval.previousContext = activeCtx.clone();
82+
}
83+
9084
for(let i = 0; i < ctxs.length; ++i) {
9185
let ctx = ctxs[i];
9286

@@ -97,7 +91,7 @@ api.process = async ({
9791
if(ctx === null) {
9892
// We can't nullify if there are protected terms and we're
9993
// not processing a property term scoped context
100-
if(!isPropertyTermScopedContext &&
94+
if(!overrideProtected &&
10195
Object.keys(activeCtx.protected).length !== 0) {
10296
const protectedMode = (options && options.protectedMode) || 'error';
10397
if(protectedMode === 'error') {
@@ -134,10 +128,6 @@ api.process = async ({
134128
{code: 'invalid protected mode', context: localCtx, protectedMode});
135129
}
136130
rval = activeCtx = api.getInitialContext(options).clone();
137-
// if context is type-scoped, ensure previous context has been set
138-
if(isTypeScopedContext) {
139-
rval.previousContext = previousContext.clone();
140-
}
141131
continue;
142132
}
143133

@@ -256,6 +246,25 @@ api.process = async ({
256246
defined.set('@language', true);
257247
}
258248

249+
// handle @propagate
250+
// note: we've already extracted it, here we just do error checking
251+
if('@propagate' in ctx) {
252+
const value = ctx['@propagate'];
253+
if(activeCtx.processingMode === 'json-ld-1.0') {
254+
throw new JsonLdError(
255+
'Invalid JSON-LD syntax; @propagate not compatible with ' +
256+
activeCtx.processingMode,
257+
'jsonld.SyntaxError',
258+
{code: 'invalid context member', context: ctx});
259+
}
260+
if(typeof value !== 'boolean') {
261+
throw new JsonLdError(
262+
'Invalid JSON-LD syntax; @propagate must be boolean valued',
263+
'jsonld.SyntaxError',
264+
{code: 'invalid @propagate value', context: localCtx});
265+
}
266+
}
267+
259268
// handle @protected; determine whether this sub-context is declaring
260269
// all its terms to be "protected" (exceptions can be made on a
261270
// per-definition basis)
@@ -294,12 +303,11 @@ api.process = async ({
294303
* @param {string} [options.protectedMode="error"] - "error" to throw error
295304
* on `@protected` constraint violation, "warn" to allow violations and
296305
* signal a warning.
297-
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
298-
* from a property term.
306+
* @param overrideProtected `false` allows protected terms to be modified.
299307
*/
300308
api.createTermDefinition = (
301309
activeCtx, localCtx, term, defined, options,
302-
isPropertyTermScopedContext = false) => {
310+
overrideProtected = false) => {
303311
if(defined.has(term)) {
304312
// term already defined
305313
if(defined.get(term)) {
@@ -699,9 +707,8 @@ api.createTermDefinition = (
699707
'jsonld.SyntaxError', {code: 'invalid keyword alias', context: localCtx});
700708
}
701709

702-
// FIXME if(1.1) ... ?
703-
if(previousMapping && previousMapping.protected &&
704-
!isPropertyTermScopedContext) {
710+
// Check for overriding protected terms
711+
if(previousMapping && previousMapping.protected && !overrideProtected) {
705712
// force new term to continue to be protected and see if the mappings would
706713
// be equal
707714
activeCtx.protected[term] = true;
@@ -776,11 +783,6 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) {
776783
api.createTermDefinition(activeCtx, localCtx, value, defined, options);
777784
}
778785

779-
// if context is from a property term scoped context composed with a
780-
// type-scoped context, then use previous context instead
781-
if(activeCtx.isPropertyTermScoped && activeCtx.previousContext) {
782-
activeCtx = activeCtx.previousContext;
783-
}
784786

785787
relativeTo = relativeTo || {};
786788
if(relativeTo.vocab) {
@@ -862,7 +864,7 @@ api.getInitialContext = options => {
862864
inverse: null,
863865
getInverse: _createInverseContext,
864866
clone: _cloneActiveContext,
865-
revertTypeScopedContext: _revertTypeScopedContext,
867+
revertToPreviousContext: _revertToPreviousContext,
866868
protected: {}
867869
};
868870
// TODO: consider using LRU cache instead
@@ -1039,10 +1041,9 @@ api.getInitialContext = options => {
10391041
child.getInverse = this.getInverse;
10401042
child.protected = util.clone(this.protected);
10411043
if(this.previousContext) {
1042-
child.isPropertyTermScoped = this.previousContext.isPropertyTermScoped;
10431044
child.previousContext = this.previousContext.clone();
10441045
}
1045-
child.revertTypeScopedContext = this.revertTypeScopedContext;
1046+
child.revertToPreviousContext = this.revertToPreviousContext;
10461047
if('@language' in this) {
10471048
child['@language'] = this['@language'];
10481049
}
@@ -1056,7 +1057,7 @@ api.getInitialContext = options => {
10561057
* Reverts any type-scoped context in this active context to the previous
10571058
* context.
10581059
*/
1059-
function _revertTypeScopedContext() {
1060+
function _revertToPreviousContext() {
10601061
if(!this.previousContext) {
10611062
return this;
10621063
}

lib/expand.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ api.expand = async ({
157157
const expandedActiveProperty = _expandIri(
158158
activeCtx, activeProperty, {vocab: true}, options);
159159

160+
// Get any property-scoped context for activeProperty
161+
const propertyScopedCtx = _getContextValue(activeCtx, activeProperty, '@context');
162+
160163
// second, determine if any type-scoped context should be reverted; it
161164
// should only be reverted when the following are all true:
162165
// 1. `element` is not a value or subject reference
@@ -186,7 +189,18 @@ api.expand = async ({
186189

187190
if(mustRevert) {
188191
// revert type scoped context
189-
activeCtx = activeCtx.revertTypeScopedContext();
192+
activeCtx = activeCtx.revertToPreviousContext();
193+
}
194+
195+
// apply property-scoped context after reverting term-scoped context
196+
if(!_isUndefined(propertyScopedCtx)) {
197+
activeCtx = await _processContext({
198+
activeCtx,
199+
localCtx: propertyScopedCtx,
200+
propagate: true,
201+
overrideProtected: true,
202+
options
203+
});
190204
}
191205

192206
// if element has a context, process it
@@ -370,6 +384,7 @@ api.expand = async ({
370384
* @param expandedParent the expanded result into which to add values.
371385
* @param options the expansion options.
372386
* @param insideList true if the element is a list, false if not.
387+
* @param typeScopedContext the context before reverting.
373388
* @param expansionMap(info) a function that can be used to custom map
374389
* unmappable values (or to throw an error when they are detected);
375390
* if this function returns `undefined` then the default behavior
@@ -383,6 +398,7 @@ async function _expandObject({
383398
expandedParent,
384399
options = {},
385400
insideList,
401+
typeScopedContext,
386402
expansionMap
387403
}) {
388404
const keys = Object.keys(element).sort();
@@ -593,11 +609,11 @@ async function _expandObject({
593609
let termCtx = activeCtx;
594610
const ctx = _getContextValue(activeCtx, key, '@context');
595611
if(!_isUndefined(ctx)) {
596-
// Note: spec's `from term` var is named `isPropertyTermScopedContext`
597612
termCtx = await _processContext({
598613
activeCtx,
599614
localCtx: ctx,
600-
isPropertyTermScopedContext: true,
615+
propagate: true,
616+
overrideProtected: true,
601617
options
602618
});
603619
}
@@ -640,7 +656,7 @@ async function _expandObject({
640656
// handle type container (skip if value is not an object)
641657
expandedValue = await _expandIndexMap({
642658
// since container is `@type`, revert type scoped context when expanding
643-
activeCtx: termCtx.revertTypeScopedContext(),
659+
activeCtx: termCtx.revertToPreviousContext(),
644660
options,
645661
activeProperty: key,
646662
value,
@@ -907,7 +923,7 @@ async function _expandIndexMap(
907923
activeCtx = await _processContext({
908924
activeCtx,
909925
localCtx: ctx,
910-
isTypeScopedContext: true,
926+
propagate: false,
911927
options
912928
});
913929
}

0 commit comments

Comments
 (0)