@@ -66,6 +66,23 @@ api.process = ({
6666 return activeCtx ;
6767 }
6868
69+ // track the previous context
70+ const 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 . previousContext = api . process ( {
78+ activeCtx : activeCtx . previousContext ,
79+ localCtx : ctxs ,
80+ options,
81+ isPropertyTermScopedContext
82+ } ) ;
83+ return activeCtx ;
84+ }
85+
6986 // process each context in order, update active context
7087 // on each iteration to ensure proper caching
7188 let rval = activeCtx ;
@@ -75,15 +92,6 @@ api.process = ({
7592 // update active context to one computed from last iteration
7693 activeCtx = rval ;
7794
78- // get context from cache if available
79- if ( api . cache ) {
80- const cached = api . cache . get ( activeCtx , ctx ) ;
81- if ( cached ) {
82- rval = activeCtx = cached ;
83- continue ;
84- }
85- }
86-
8795 // reset to initial context
8896 if ( ctx === null ) {
8997 // We can't nullify if there are protected terms and we're
@@ -102,7 +110,7 @@ api.process = ({
102110 console . warn ( 'WARNING: invalid context nullification' ) ;
103111 const oldActiveCtx = activeCtx ;
104112 // copy all protected term definitions to fresh initial context
105- rval = activeCtx = api . getInitialContext ( options ) ;
113+ rval = activeCtx = api . getInitialContext ( options ) . clone ( ) ;
106114 for ( const [ term , _protected ] of
107115 Object . entries ( oldActiveCtx . protected ) ) {
108116 if ( _protected ) {
@@ -124,10 +132,23 @@ api.process = ({
124132 'jsonld.SyntaxError' ,
125133 { code : 'invalid protected mode' , context : localCtx , protectedMode} ) ;
126134 }
127- rval = activeCtx = api . getInitialContext ( options ) ;
135+ rval = activeCtx = api . getInitialContext ( options ) . clone ( ) ;
136+ // if context is type-scoped, ensure previous context has been set
137+ if ( isTypeScopedContext ) {
138+ rval . previousContext = previousContext . clone ( ) ;
139+ }
128140 continue ;
129141 }
130142
143+ // get context from cache if available
144+ if ( api . cache ) {
145+ const cached = api . cache . get ( activeCtx , ctx ) ;
146+ if ( cached ) {
147+ rval = activeCtx = cached ;
148+ continue ;
149+ }
150+ }
151+
131152 // dereference @context key if present
132153 if ( _isObject ( ctx ) && '@context' in ctx ) {
133154 ctx = ctx [ '@context' ] ;
@@ -140,6 +161,9 @@ api.process = ({
140161 'jsonld.SyntaxError' , { code : 'invalid local context' , context : ctx } ) ;
141162 }
142163
164+ // TODO: there is likely a `preivousContext` cloning optimization that
165+ // could be applied here (no need to copy it under certain conditions)
166+
143167 // clone context before updating it
144168 rval = rval . clone ( ) ;
145169
@@ -239,7 +263,12 @@ api.process = ({
239263 for ( const key in ctx ) {
240264 api . createTermDefinition (
241265 rval , ctx , key , defined , options ,
242- isPropertyTermScopedContext , isTypeScopedContext ) ;
266+ isPropertyTermScopedContext ) ;
267+ }
268+
269+ // if context is type-scoped, ensure previous context has been set
270+ if ( isTypeScopedContext && ! rval . previousContext ) {
271+ rval . previousContext = previousContext . clone ( ) ;
243272 }
244273
245274 // cache result
@@ -248,6 +277,11 @@ api.process = ({
248277 }
249278 }
250279
280+ // if context is type-scoped, ensure previous context has been set
281+ if ( isTypeScopedContext && ! rval . previousContext ) {
282+ rval . previousContext = previousContext . clone ( ) ;
283+ }
284+
251285 return rval ;
252286} ;
253287
@@ -265,13 +299,10 @@ api.process = ({
265299 * signal a warning.
266300 * @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
267301 * from a property term.
268- * @param isTypeScopedContext `true` if `localCtx` is a scoped context
269- * from a type.
270302 */
271303api . createTermDefinition = (
272304 activeCtx , localCtx , term , defined , options ,
273- isPropertyTermScopedContext = false ,
274- isTypeScopedContext = false ) => {
305+ isPropertyTermScopedContext = false ) => {
275306 if ( defined . has ( term ) ) {
276307 // term already defined
277308 if ( defined . get ( term ) ) {
@@ -301,10 +332,11 @@ api.createTermDefinition = (
301332 { code : 'invalid term definition' , context : localCtx } ) ;
302333 }
303334
335+ // keep reference to previous mapping for potential `@protected` check
336+ const previousMapping = activeCtx . mappings . get ( term ) ;
337+
304338 // remove old mapping
305- let previousMapping = null ;
306339 if ( activeCtx . mappings . has ( term ) ) {
307- previousMapping = activeCtx . mappings . get ( term ) ;
308340 activeCtx . mappings . delete ( term ) ;
309341 }
310342
@@ -339,11 +371,6 @@ api.createTermDefinition = (
339371 // create new mapping
340372 const mapping = { } ;
341373 activeCtx . mappings . set ( term , mapping ) ;
342- if ( isTypeScopedContext ) {
343- activeCtx . hasTypeScopedTerms = true ;
344- mapping . isTypeScopedTerm = true ;
345- mapping . previousMapping = previousMapping ;
346- }
347374 mapping . reverse = false ;
348375
349376 // make sure term definition only has expected keywords
@@ -785,7 +812,7 @@ api.getInitialContext = options => {
785812 inverse : null ,
786813 getInverse : _createInverseContext ,
787814 clone : _cloneActiveContext ,
788- revertTypeScopedTerms : _revertTypeScopedTerms ,
815+ revertTypeScopedContext : _revertTypeScopedContext ,
789816 protected : { }
790817 } ;
791818 // TODO: consider using LRU cache instead
@@ -961,7 +988,10 @@ api.getInitialContext = options => {
961988 child . inverse = null ;
962989 child . getInverse = this . getInverse ;
963990 child . protected = util . clone ( this . protected ) ;
964- child . revertTypeScopedTerms = this . revertTypeScopedTerms ;
991+ if ( this . previousContext ) {
992+ child . previousContext = this . previousContext . clone ( ) ;
993+ }
994+ child . revertTypeScopedContext = this . revertTypeScopedContext ;
965995 if ( '@language' in this ) {
966996 child [ '@language' ] = this [ '@language' ] ;
967997 }
@@ -972,35 +1002,14 @@ api.getInitialContext = options => {
9721002 }
9731003
9741004 /**
975- * Reverts any type-scoped terms in this active context to their previous
976- * mappings .
1005+ * Reverts any type-scoped context in this active context to the previous
1006+ * context .
9771007 */
978- function _revertTypeScopedTerms ( ) {
979- // optimization: no type-scoped terms to remove, reuse active context
980- if ( ! this . hasTypeScopedTerms ) {
1008+ function _revertTypeScopedContext ( ) {
1009+ if ( ! this . previousContext ) {
9811010 return this ;
9821011 }
983- // create clone without type scoped terms
984- const child = this . clone ( ) ;
985- const entries = child . mappings . entries ( ) ;
986- for ( const [ term , mapping ] of entries ) {
987- if ( mapping . isTypeScopedTerm ) {
988- if ( mapping . previousMapping ) {
989- child . mappings . set ( term , mapping . previousMapping ) ;
990- if ( mapping . previousMapping . protected ) {
991- child . protected [ term ] = true ;
992- } else {
993- delete child . protected [ term ] ;
994- }
995- } else {
996- child . mappings . delete ( term ) ;
997- if ( child . protected [ term ] ) {
998- delete child . protected [ term ] ;
999- }
1000- }
1001- }
1002- }
1003- return child ;
1012+ return this . previousContext . clone ( ) ;
10041013 }
10051014} ;
10061015
0 commit comments