Skip to content

Commit afd8547

Browse files
committed
Preserve previous context when processing type-scoped contexts.
1 parent ff094de commit afd8547

File tree

1 file changed

+51
-41
lines changed

1 file changed

+51
-41
lines changed

lib/context.js

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ 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+
activeCtx.previousContext = api.process({
76+
activeCtx: activeCtx.previousContext,
77+
localCtx: ctxs,
78+
options,
79+
isPropertyTermScopedContext
80+
});
81+
return activeCtx;
82+
}
83+
6984
// process each context in order, update active context
7085
// on each iteration to ensure proper caching
7186
let rval = activeCtx;
@@ -75,15 +90,6 @@ api.process = ({
7590
// update active context to one computed from last iteration
7691
activeCtx = rval;
7792

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-
8793
// reset to initial context
8894
if(ctx === null) {
8995
// We can't nullify if there are protected terms and we're
@@ -102,7 +108,7 @@ api.process = ({
102108
console.warn('WARNING: invalid context nullification');
103109
const oldActiveCtx = activeCtx;
104110
// copy all protected term definitions to fresh initial context
105-
rval = activeCtx = api.getInitialContext(options);
111+
rval = activeCtx = api.getInitialContext(options).clone();
106112
for(const [term, _protected] of
107113
Object.entries(oldActiveCtx.protected)) {
108114
if(_protected) {
@@ -124,10 +130,23 @@ api.process = ({
124130
'jsonld.SyntaxError',
125131
{code: 'invalid protected mode', context: localCtx, protectedMode});
126132
}
127-
rval = activeCtx = api.getInitialContext(options);
133+
rval = activeCtx = api.getInitialContext(options).clone();
134+
// if context is type-scoped, ensure previous context has been set
135+
if(isTypeScopedContext) {
136+
rval.previousContext = previousContext.clone();
137+
}
128138
continue;
129139
}
130140

141+
// get context from cache if available
142+
if(api.cache) {
143+
const cached = api.cache.get(activeCtx, ctx);
144+
if(cached) {
145+
rval = activeCtx = cached;
146+
continue;
147+
}
148+
}
149+
131150
// dereference @context key if present
132151
if(_isObject(ctx) && '@context' in ctx) {
133152
ctx = ctx['@context'];
@@ -140,6 +159,9 @@ api.process = ({
140159
'jsonld.SyntaxError', {code: 'invalid local context', context: ctx});
141160
}
142161

162+
// TODO: there is likely a `preivousContext` cloning optimization that
163+
// could be applied here (no need to copy it under certain conditions)
164+
143165
// clone context before updating it
144166
rval = rval.clone();
145167

@@ -242,12 +264,22 @@ api.process = ({
242264
isPropertyTermScopedContext, isTypeScopedContext);
243265
}
244266

267+
// if context is type-scoped, ensure previous context has been set
268+
if(isTypeScopedContext && !rval.previousContext) {
269+
rval.previousContext = previousContext.clone();
270+
}
271+
245272
// cache result
246273
if(api.cache) {
247274
api.cache.set(activeCtx, ctx, rval);
248275
}
249276
}
250277

278+
// if context is type-scoped, ensure previous context has been set
279+
if(isTypeScopedContext && !rval.previousContext) {
280+
rval.previousContext = previousContext.clone();
281+
}
282+
251283
return rval;
252284
};
253285

@@ -301,10 +333,11 @@ api.createTermDefinition = (
301333
{code: 'invalid term definition', context: localCtx});
302334
}
303335

336+
// keep reference to previous mapping for potential `@protected` check
337+
const previousMapping = activeCtx.mappings.get(term);
338+
304339
// remove old mapping
305-
let previousMapping = null;
306340
if(activeCtx.mappings.has(term)) {
307-
previousMapping = activeCtx.mappings.get(term);
308341
activeCtx.mappings.delete(term);
309342
}
310343

@@ -339,11 +372,6 @@ api.createTermDefinition = (
339372
// create new mapping
340373
const mapping = {};
341374
activeCtx.mappings.set(term, mapping);
342-
if(isTypeScopedContext) {
343-
activeCtx.hasTypeScopedTerms = true;
344-
mapping.isTypeScopedTerm = true;
345-
mapping.previousMapping = previousMapping;
346-
}
347375
mapping.reverse = false;
348376

349377
// make sure term definition only has expected keywords
@@ -961,6 +989,9 @@ api.getInitialContext = options => {
961989
child.inverse = null;
962990
child.getInverse = this.getInverse;
963991
child.protected = util.clone(this.protected);
992+
if(this.previousContext) {
993+
child.previousContext = this.previousContext.clone();
994+
}
964995
child.revertTypeScopedTerms = this.revertTypeScopedTerms;
965996
if('@language' in this) {
966997
child['@language'] = this['@language'];
@@ -976,31 +1007,10 @@ api.getInitialContext = options => {
9761007
* mappings.
9771008
*/
9781009
function _revertTypeScopedTerms() {
979-
// optimization: no type-scoped terms to remove, reuse active context
980-
if(!this.hasTypeScopedTerms) {
1010+
if(!this.previousContext) {
9811011
return this;
9821012
}
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;
1013+
return this.previousContext.clone();
10041014
}
10051015
};
10061016

0 commit comments

Comments
 (0)