Skip to content

Commit 71ac843

Browse files
committed
Revert type-scoped terms when recursing into typed object.
1 parent aa17c67 commit 71ac843

File tree

3 files changed

+85
-10
lines changed

3 files changed

+85
-10
lines changed

lib/compact.js

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

144144
const rval = {};
145145

146+
// revert type scoped terms
147+
activeCtx = activeCtx.revertTypeScopedTerms();
148+
146149
if(options.link && '@id' in element) {
147150
// store linked element
148151
if(!options.link.hasOwnProperty(element['@id'])) {
@@ -162,10 +165,15 @@ api.compact = ({
162165
const compactedType = api.compactIri(
163166
{activeCtx, iri: type, relativeTo: {vocab: true}});
164167

165-
// Use any scoped context defined on this value
168+
// Use any type-scoped context defined on this value
166169
const ctx = _getContextValue(activeCtx, compactedType, '@context');
167170
if(!_isUndefined(ctx)) {
168-
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
171+
activeCtx = _processContext({
172+
activeCtx,
173+
localCtx: ctx,
174+
options,
175+
isTypeScopedContext: true
176+
});
169177
}
170178
}
171179

lib/context.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@ api.cache = new ActiveContextCache();
4444
* @param options the context processing options.
4545
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
4646
* from a property term.
47+
* @param isTypeScopedContext `true` if `localCtx` is a scoped context
48+
* from a type.
4749
*
4850
* @return the new active context.
4951
*/
50-
api.process = (
51-
{activeCtx, localCtx, options, isPropertyTermScopedContext = false}) => {
52+
api.process = ({
53+
activeCtx, localCtx, options,
54+
isPropertyTermScopedContext = false,
55+
isTypeScopedContext = false
56+
}) => {
5257
// normalize local context to an array of @context objects
5358
if(_isObject(localCtx) && '@context' in localCtx &&
5459
_isArray(localCtx['@context'])) {
@@ -233,7 +238,8 @@ api.process = (
233238
// process all other keys
234239
for(const key in ctx) {
235240
api.createTermDefinition(
236-
rval, ctx, key, defined, options, isPropertyTermScopedContext);
241+
rval, ctx, key, defined, options,
242+
isPropertyTermScopedContext, isTypeScopedContext);
237243
}
238244

239245
// cache result
@@ -259,10 +265,13 @@ api.process = (
259265
* signal a warning.
260266
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
261267
* from a property term.
268+
* @param isTypeScopedContext `true` if `localCtx` is a scoped context
269+
* from a type.
262270
*/
263271
api.createTermDefinition = (
264272
activeCtx, localCtx, term, defined, options,
265-
isPropertyTermScopedContext = false) => {
273+
isPropertyTermScopedContext = false,
274+
isTypeScopedContext = false) => {
266275
if(defined.has(term)) {
267276
// term already defined
268277
if(defined.get(term)) {
@@ -314,7 +323,11 @@ api.createTermDefinition = (
314323
}
315324

316325
// remove old mapping
326+
let previousMapping = null;
317327
if(activeCtx.mappings.has(term)) {
328+
if(isTypeScopedContext) {
329+
previousMapping = activeCtx.mappings.get(term);
330+
}
318331
activeCtx.mappings.delete(term);
319332
}
320333

@@ -349,6 +362,11 @@ api.createTermDefinition = (
349362
// create new mapping
350363
const mapping = {};
351364
activeCtx.mappings.set(term, mapping);
365+
if(isTypeScopedContext) {
366+
activeCtx.hasTypeScopedTerms = true;
367+
mapping.isTypeScopedTerm = true;
368+
mapping.previousMapping = previousMapping;
369+
}
352370
mapping.reverse = false;
353371

354372
// make sure term definition only has expected keywords
@@ -762,6 +780,7 @@ api.getInitialContext = options => {
762780
inverse: null,
763781
getInverse: _createInverseContext,
764782
clone: _cloneActiveContext,
783+
revertTypeScopedTerms: _revertTypeScopedTerms,
765784
protected: {}
766785
};
767786
// TODO: consider using LRU cache instead
@@ -937,6 +956,7 @@ api.getInitialContext = options => {
937956
child.inverse = null;
938957
child.getInverse = this.getInverse;
939958
child.protected = util.clone(this.protected);
959+
child.revertTypeScopedTerms = this.revertTypeScopedTerms;
940960
if('@language' in this) {
941961
child['@language'] = this['@language'];
942962
}
@@ -945,6 +965,30 @@ api.getInitialContext = options => {
945965
}
946966
return child;
947967
}
968+
969+
/**
970+
* Reverts any type-scoped terms in this active context to their previous
971+
* mappings.
972+
*/
973+
function _revertTypeScopedTerms() {
974+
// optimization: no type-scoped terms to remove, reuse active context
975+
if(!this.hasTypeScopedTerms) {
976+
return this;
977+
}
978+
// create clone without type scoped terms
979+
const child = this.clone();
980+
const entries = child.mappings.entries();
981+
for(const [term, mapping] of entries) {
982+
if(mapping.isTypeScopedTerm) {
983+
if(mapping.previousMapping) {
984+
child.mappings.set(term, mapping.previousMapping);
985+
} else {
986+
child.mappings.delete(term);
987+
}
988+
}
989+
}
990+
return child;
991+
}
948992
};
949993

950994
/**

lib/expand.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ module.exports = api;
5050
* @param element the element to expand.
5151
* @param options the expansion options.
5252
* @param insideList true if the element is a list, false if not.
53+
* @param insideTypeContainer true if the element is inside a type container,
54+
* false if not.
5355
* @param expansionMap(info) a function that can be used to custom map
5456
* unmappable values (or to throw an error when they are detected);
5557
* if this function returns `undefined` then the default behavior
@@ -63,6 +65,7 @@ api.expand = ({
6365
element,
6466
options = {},
6567
insideList = false,
68+
insideTypeContainer = false,
6669
expansionMap = () => undefined
6770
}) => {
6871
// nothing to expand
@@ -111,7 +114,8 @@ api.expand = ({
111114
activeProperty,
112115
element: element[i],
113116
options,
114-
expansionMap
117+
expansionMap,
118+
insideTypeContainer
115119
});
116120
if(insideList && (_isArray(e) || _isList(e))) {
117121
// lists of lists are illegal
@@ -148,6 +152,11 @@ api.expand = ({
148152

149153
// recursively expand object:
150154

155+
if(!insideTypeContainer) {
156+
// revert type scoped terms
157+
activeCtx = activeCtx.revertTypeScopedTerms();
158+
}
159+
151160
// if element has a context, process it
152161
if('@context' in element) {
153162
activeCtx = _processContext(
@@ -159,7 +168,7 @@ api.expand = ({
159168
for(const key of keys) {
160169
const expandedProperty = _expandIri(activeCtx, key, {vocab: true}, options);
161170
if(expandedProperty === '@type') {
162-
// set scopped contexts from @type
171+
// set scoped contexts from @type
163172
// avoid sorting if possible
164173
const value = element[key];
165174
const types =
@@ -168,7 +177,12 @@ api.expand = ({
168177
for(const type of types) {
169178
const ctx = _getContextValue(activeCtx, type, '@context');
170179
if(!_isUndefined(ctx)) {
171-
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
180+
activeCtx = _processContext({
181+
activeCtx,
182+
localCtx: ctx,
183+
options,
184+
isTypeScopedContext: true
185+
});
172186
}
173187
}
174188
}
@@ -844,7 +858,15 @@ function _expandIndexMap(
844858
// if indexKey is @type, there may be a context defined for it
845859
const ctx = _getContextValue(activeCtx, key, '@context');
846860
if(!_isUndefined(ctx)) {
847-
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
861+
// TODO: check that `key` is for `@type` once property indexes
862+
// are permitted -- and pass `isPropertyTermScopedContext: true` if
863+
// key is a property not an `@type`
864+
activeCtx = _processContext({
865+
activeCtx,
866+
localCtx: ctx,
867+
isTypeScopedContext: true,
868+
options
869+
});
848870
}
849871

850872
let val = value[key];
@@ -867,6 +889,7 @@ function _expandIndexMap(
867889
element: val,
868890
options,
869891
insideList: false,
892+
insideTypeContainer: indexKey === '@type',
870893
expansionMap
871894
});
872895
for(let item of val) {

0 commit comments

Comments
 (0)