@@ -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 */
5251api . 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 */
300308api . 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 }
0 commit comments