Skip to content

Fix type scoped terms. #312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/compact.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ api.compact = ({

const rval = {};

// revert type scoped terms
activeCtx = activeCtx.revertTypeScopedTerms();

if(options.link && '@id' in element) {
// store linked element
if(!options.link.hasOwnProperty(element['@id'])) {
Expand All @@ -162,10 +165,15 @@ api.compact = ({
const compactedType = api.compactIri(
{activeCtx, iri: type, relativeTo: {vocab: true}});

// Use any scoped context defined on this value
// Use any type-scoped context defined on this value
const ctx = _getContextValue(activeCtx, compactedType, '@context');
if(!_isUndefined(ctx)) {
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
activeCtx = _processContext({
activeCtx,
localCtx: ctx,
options,
isTypeScopedContext: true
});
}
}

Expand Down
61 changes: 57 additions & 4 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ api.cache = new ActiveContextCache();
* @param options the context processing options.
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
* from a property term.
* @param isTypeScopedContext `true` if `localCtx` is a scoped context
* from a type.
*
* @return the new active context.
*/
api.process = (
{activeCtx, localCtx, options, isPropertyTermScopedContext = false}) => {
api.process = ({
activeCtx, localCtx, options,
isPropertyTermScopedContext = false,
isTypeScopedContext = false
}) => {
// normalize local context to an array of @context objects
if(_isObject(localCtx) && '@context' in localCtx &&
_isArray(localCtx['@context'])) {
Expand Down Expand Up @@ -233,7 +238,8 @@ api.process = (
// process all other keys
for(const key in ctx) {
api.createTermDefinition(
rval, ctx, key, defined, options, isPropertyTermScopedContext);
rval, ctx, key, defined, options,
isPropertyTermScopedContext, isTypeScopedContext);
}

// cache result
Expand All @@ -259,10 +265,13 @@ api.process = (
* signal a warning.
* @param isPropertyTermScopedContext `true` if `localCtx` is a scoped context
* from a property term.
* @param isTypeScopedContext `true` if `localCtx` is a scoped context
* from a type.
*/
api.createTermDefinition = (
activeCtx, localCtx, term, defined, options,
isPropertyTermScopedContext = false) => {
isPropertyTermScopedContext = false,
isTypeScopedContext = false) => {
if(defined.has(term)) {
// term already defined
if(defined.get(term)) {
Expand Down Expand Up @@ -314,7 +323,11 @@ api.createTermDefinition = (
}

// remove old mapping
let previousMapping = null;
if(activeCtx.mappings.has(term)) {
if(isTypeScopedContext) {
previousMapping = activeCtx.mappings.get(term);
}
activeCtx.mappings.delete(term);
}

Expand Down Expand Up @@ -349,6 +362,11 @@ api.createTermDefinition = (
// create new mapping
const mapping = {};
activeCtx.mappings.set(term, mapping);
if(isTypeScopedContext) {
activeCtx.hasTypeScopedTerms = true;
mapping.isTypeScopedTerm = true;
mapping.previousMapping = previousMapping;
}
mapping.reverse = false;

// make sure term definition only has expected keywords
Expand Down Expand Up @@ -466,6 +484,7 @@ api.createTermDefinition = (
if(value['@protected'] === true ||
(defined.get('@protected') === true && value['@protected'] !== false)) {
activeCtx.protected[term] = true;
mapping.protected = true;
}

// IRI mapping now defined
Expand Down Expand Up @@ -762,6 +781,7 @@ api.getInitialContext = options => {
inverse: null,
getInverse: _createInverseContext,
clone: _cloneActiveContext,
revertTypeScopedTerms: _revertTypeScopedTerms,
protected: {}
};
// TODO: consider using LRU cache instead
Expand Down Expand Up @@ -937,6 +957,7 @@ api.getInitialContext = options => {
child.inverse = null;
child.getInverse = this.getInverse;
child.protected = util.clone(this.protected);
child.revertTypeScopedTerms = this.revertTypeScopedTerms;
if('@language' in this) {
child['@language'] = this['@language'];
}
Expand All @@ -945,6 +966,38 @@ api.getInitialContext = options => {
}
return child;
}

/**
* Reverts any type-scoped terms in this active context to their previous
* mappings.
*/
function _revertTypeScopedTerms() {
// optimization: no type-scoped terms to remove, reuse active context
if(!this.hasTypeScopedTerms) {
return this;
}
// create clone without type scoped terms
const child = this.clone();
const entries = child.mappings.entries();
for(const [term, mapping] of entries) {
if(mapping.isTypeScopedTerm) {
if(mapping.previousMapping) {
child.mappings.set(term, mapping.previousMapping);
if(mapping.previousMapping.protected) {
child.protected[term] = true;
} else {
delete child.protected[term];
}
} else {
child.mappings.delete(term);
if(child.protected[term]) {
delete child.protected[term];
}
}
}
}
return child;
}
};

/**
Expand Down
40 changes: 32 additions & 8 deletions lib/expand.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ module.exports = api;
* @param element the element to expand.
* @param options the expansion options.
* @param insideList true if the element is a list, false if not.
* @param insideIndex true if the element is inside an index container,
* false if not.
* @param expansionMap(info) a function that can be used to custom map
* unmappable values (or to throw an error when they are detected);
* if this function returns `undefined` then the default behavior
Expand All @@ -63,6 +65,7 @@ api.expand = ({
element,
options = {},
insideList = false,
insideIndex = false,
expansionMap = () => undefined
}) => {
// nothing to expand
Expand Down Expand Up @@ -111,7 +114,8 @@ api.expand = ({
activeProperty,
element: element[i],
options,
expansionMap
expansionMap,
insideIndex
});
if(insideList && (_isArray(e) || _isList(e))) {
// lists of lists are illegal
Expand Down Expand Up @@ -148,6 +152,11 @@ api.expand = ({

// recursively expand object:

if(!insideIndex) {
// revert type scoped terms
activeCtx = activeCtx.revertTypeScopedTerms();
}

// if element has a context, process it
if('@context' in element) {
activeCtx = _processContext(
Expand All @@ -159,7 +168,7 @@ api.expand = ({
for(const key of keys) {
const expandedProperty = _expandIri(activeCtx, key, {vocab: true}, options);
if(expandedProperty === '@type') {
// set scopped contexts from @type
// set scoped contexts from @type
// avoid sorting if possible
const value = element[key];
const types =
Expand All @@ -168,7 +177,12 @@ api.expand = ({
for(const type of types) {
const ctx = _getContextValue(activeCtx, type, '@context');
if(!_isUndefined(ctx)) {
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
activeCtx = _processContext({
activeCtx,
localCtx: ctx,
options,
isTypeScopedContext: true
});
}
}
}
Expand Down Expand Up @@ -599,7 +613,8 @@ function _expandObject({
} else if(container.includes('@type') && _isObject(value)) {
// handle type container (skip if value is not an object)
expandedValue = _expandIndexMap({
activeCtx: termCtx,
// since container is `@type`, revert type scoped terms when expanding
activeCtx: termCtx.revertTypeScopedTerms(),
options,
activeProperty: key,
value,
Expand Down Expand Up @@ -840,11 +855,19 @@ function _expandIndexMap(
indexKey}) {
const rval = [];
const keys = Object.keys(value).sort();
const isTypeIndex = indexKey === '@type';
for(let key of keys) {
// if indexKey is @type, there may be a context defined for it
const ctx = _getContextValue(activeCtx, key, '@context');
if(!_isUndefined(ctx)) {
activeCtx = _processContext({activeCtx, localCtx: ctx, options});
if(isTypeIndex) {
const ctx = _getContextValue(activeCtx, key, '@context');
if(!_isUndefined(ctx)) {
activeCtx = _processContext({
activeCtx,
localCtx: ctx,
isTypeScopedContext: true,
options
});
}
}

let val = value[key];
Expand All @@ -857,7 +880,7 @@ function _expandIndexMap(
if(indexKey === '@id') {
// expand document relative
key = _expandIri(activeCtx, key, {base: true}, options);
} else if(indexKey === '@type') {
} else if(isTypeIndex) {
key = expandedKey;
}

Expand All @@ -867,6 +890,7 @@ function _expandIndexMap(
element: val,
options,
insideList: false,
insideIndex: true,
expansionMap
});
for(let item of val) {
Expand Down