@@ -66,6 +66,23 @@ api.process = ({
66
66
return activeCtx ;
67
67
}
68
68
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
+
69
86
// process each context in order, update active context
70
87
// on each iteration to ensure proper caching
71
88
let rval = activeCtx ;
@@ -75,15 +92,6 @@ api.process = ({
75
92
// update active context to one computed from last iteration
76
93
activeCtx = rval ;
77
94
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
-
87
95
// reset to initial context
88
96
if ( ctx === null ) {
89
97
// We can't nullify if there are protected terms and we're
@@ -102,7 +110,7 @@ api.process = ({
102
110
console . warn ( 'WARNING: invalid context nullification' ) ;
103
111
const oldActiveCtx = activeCtx ;
104
112
// copy all protected term definitions to fresh initial context
105
- rval = activeCtx = api . getInitialContext ( options ) ;
113
+ rval = activeCtx = api . getInitialContext ( options ) . clone ( ) ;
106
114
for ( const [ term , _protected ] of
107
115
Object . entries ( oldActiveCtx . protected ) ) {
108
116
if ( _protected ) {
@@ -124,10 +132,23 @@ api.process = ({
124
132
'jsonld.SyntaxError' ,
125
133
{ code : 'invalid protected mode' , context : localCtx , protectedMode} ) ;
126
134
}
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
+ }
128
140
continue ;
129
141
}
130
142
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
+
131
152
// dereference @context key if present
132
153
if ( _isObject ( ctx ) && '@context' in ctx ) {
133
154
ctx = ctx [ '@context' ] ;
@@ -140,6 +161,9 @@ api.process = ({
140
161
'jsonld.SyntaxError' , { code : 'invalid local context' , context : ctx } ) ;
141
162
}
142
163
164
+ // TODO: there is likely a `preivousContext` cloning optimization that
165
+ // could be applied here (no need to copy it under certain conditions)
166
+
143
167
// clone context before updating it
144
168
rval = rval . clone ( ) ;
145
169
@@ -239,7 +263,12 @@ api.process = ({
239
263
for ( const key in ctx ) {
240
264
api . createTermDefinition (
241
265
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 ( ) ;
243
272
}
244
273
245
274
// cache result
@@ -248,6 +277,11 @@ api.process = ({
248
277
}
249
278
}
250
279
280
+ // if context is type-scoped, ensure previous context has been set
281
+ if ( isTypeScopedContext && ! rval . previousContext ) {
282
+ rval . previousContext = previousContext . clone ( ) ;
283
+ }
284
+
251
285
return rval ;
252
286
} ;
253
287
@@ -265,13 +299,10 @@ api.process = ({
265
299
* signal a warning.
266
300
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
267
301
* from a property term.
268
- * @param isTypeScopedContext `true` if `localCtx` is a scoped context
269
- * from a type.
270
302
*/
271
303
api . createTermDefinition = (
272
304
activeCtx , localCtx , term , defined , options ,
273
- isPropertyTermScopedContext = false ,
274
- isTypeScopedContext = false ) => {
305
+ isPropertyTermScopedContext = false ) => {
275
306
if ( defined . has ( term ) ) {
276
307
// term already defined
277
308
if ( defined . get ( term ) ) {
@@ -301,10 +332,11 @@ api.createTermDefinition = (
301
332
{ code : 'invalid term definition' , context : localCtx } ) ;
302
333
}
303
334
335
+ // keep reference to previous mapping for potential `@protected` check
336
+ const previousMapping = activeCtx . mappings . get ( term ) ;
337
+
304
338
// remove old mapping
305
- let previousMapping = null ;
306
339
if ( activeCtx . mappings . has ( term ) ) {
307
- previousMapping = activeCtx . mappings . get ( term ) ;
308
340
activeCtx . mappings . delete ( term ) ;
309
341
}
310
342
@@ -339,11 +371,6 @@ api.createTermDefinition = (
339
371
// create new mapping
340
372
const mapping = { } ;
341
373
activeCtx . mappings . set ( term , mapping ) ;
342
- if ( isTypeScopedContext ) {
343
- activeCtx . hasTypeScopedTerms = true ;
344
- mapping . isTypeScopedTerm = true ;
345
- mapping . previousMapping = previousMapping ;
346
- }
347
374
mapping . reverse = false ;
348
375
349
376
// make sure term definition only has expected keywords
@@ -785,7 +812,7 @@ api.getInitialContext = options => {
785
812
inverse : null ,
786
813
getInverse : _createInverseContext ,
787
814
clone : _cloneActiveContext ,
788
- revertTypeScopedTerms : _revertTypeScopedTerms ,
815
+ revertTypeScopedContext : _revertTypeScopedContext ,
789
816
protected : { }
790
817
} ;
791
818
// TODO: consider using LRU cache instead
@@ -961,7 +988,10 @@ api.getInitialContext = options => {
961
988
child . inverse = null ;
962
989
child . getInverse = this . getInverse ;
963
990
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 ;
965
995
if ( '@language' in this ) {
966
996
child [ '@language' ] = this [ '@language' ] ;
967
997
}
@@ -972,35 +1002,14 @@ api.getInitialContext = options => {
972
1002
}
973
1003
974
1004
/**
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 .
977
1007
*/
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 ) {
981
1010
return this ;
982
1011
}
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 ( ) ;
1004
1013
}
1005
1014
} ;
1006
1015
0 commit comments