Skip to content

Commit 0449996

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

File tree

3 files changed

+66
-57
lines changed

3 files changed

+66
-57
lines changed

lib/compact.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ api.compact = ({
143143

144144
const rval = {};
145145

146-
// revert type scoped terms
147-
activeCtx = activeCtx.revertTypeScopedTerms();
146+
// revert type scoped context
147+
activeCtx = activeCtx.revertTypeScopedContext();
148148

149149
if(options.link && '@id' in element) {
150150
// store linked element

lib/context.js

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
271303
api.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

lib/expand.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ api.expand = ({
153153
// recursively expand object:
154154

155155
if(!insideIndex) {
156-
// revert type scoped terms
157-
activeCtx = activeCtx.revertTypeScopedTerms();
156+
// revert type scoped context
157+
activeCtx = activeCtx.revertTypeScopedContext();
158158
}
159159

160160
// if element has a context, process it
@@ -613,8 +613,8 @@ function _expandObject({
613613
} else if(container.includes('@type') && _isObject(value)) {
614614
// handle type container (skip if value is not an object)
615615
expandedValue = _expandIndexMap({
616-
// since container is `@type`, revert type scoped terms when expanding
617-
activeCtx: termCtx.revertTypeScopedTerms(),
616+
// since container is `@type`, revert type scoped context when expanding
617+
activeCtx: termCtx.revertTypeScopedContext(),
618618
options,
619619
activeProperty: key,
620620
value,

0 commit comments

Comments
 (0)