diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c4fed8..2f0c2a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,21 @@ - Keywords may not be used as prefixes. - Handle term definition on `@type` with empty map. - Handling of `@` values for `@reverse`. +- Changes in object embedding. +- Better support for graph framing. +- Better frame validation. +- Wildcard matching on `@id` and other `requireAll` semantics. ### Changed - Keep term definitions mapping to null so they may be protected. - **NOTE**: `LINK_HEADER_REL` in `lib/constants.js` has been deprecated and renamed to `LINK_HEADER_CONTEXT`. It remains for now but will be removed in a future release. +- Changed framing defaults + - `embed` to "@once" and warn on "@first" or "@last". + - `pruneBlankNodeIdentifiers` based on processingMode. + - `omitGraph` based on processingMode. +- Replaced `removePreserve` with `cleanupPreserve` and `cleanupNulls`. ### Added - Support for `"@import"`. @@ -30,6 +39,9 @@ - Support for expansion and compaction of values container `"@direction"`. - Support for RDF transformation of `@direction` when `rdfDirection` is 'i18n-datatype'. +- Top level `@graph` omitted if `omitGraph` is `true`. +- Check for invalid values of `@embed`. +- Support default values for `@type` when framing. ## 2.0.2 - 2020-01-17 diff --git a/lib/compact.js b/lib/compact.js index e0609a56..994a4ba5 100644 --- a/lib/compact.js +++ b/lib/compact.js @@ -1067,95 +1067,6 @@ api.compactValue = ({activeCtx, activeProperty, value, options}) => { }; }; -/** - * Removes the @preserve keywords as the last step of the compaction - * algorithm when it is running on framed output. - * - * @param ctx the active context used to compact the input. - * @param input the framed, compacted output. - * @param options the compaction options used. - * - * @return the resulting output. - */ -api.removePreserve = (ctx, input, options) => { - // recurse through arrays - if(_isArray(input)) { - const output = []; - for(let i = 0; i < input.length; ++i) { - const result = api.removePreserve(ctx, input[i], options); - // drop nulls from arrays - if(result !== null) { - output.push(result); - } - } - input = output; - } else if(_isObject(input)) { - // remove @preserve - if('@preserve' in input) { - if(input['@preserve'] === '@null') { - return null; - } - return input['@preserve']; - } - - // skip @values - if(_isValue(input)) { - return input; - } - - // recurse through @lists - if(_isList(input)) { - input['@list'] = api.removePreserve(ctx, input['@list'], options); - return input; - } - - // handle in-memory linked nodes - const idAlias = api.compactIri({ - activeCtx: ctx, - iri: '@id', - relativeTo: {vocab: true} - }); - if(input.hasOwnProperty(idAlias)) { - const id = input[idAlias]; - if(options.link.hasOwnProperty(id)) { - const idx = options.link[id].indexOf(input); - if(idx !== -1) { - // already visited - return options.link[id][idx]; - } - // prevent circular visitation - options.link[id].push(input); - } else { - // prevent circular visitation - options.link[id] = [input]; - } - } - - // recurse through properties - const graphAlias = api.compactIri({ - activeCtx: ctx, - iri: '@graph', - relativeTo: {vocab: true} - }); - for(const prop in input) { - // potentially remove the id, if it is an unreference bnode - if(prop === idAlias && options.bnodesToClear.includes(input[prop])) { - delete input[idAlias]; - continue; - } - - let result = api.removePreserve(ctx, input[prop], options); - const container = _getContextValue(ctx, prop, '@container') || []; - if(options.compactArrays && _isArray(result) && result.length === 1 && - container.length === 0 && prop !== graphAlias) { - result = result[0]; - } - input[prop] = result; - } - } - return input; -}; - /** * Picks the preferred compaction term from the given inverse context entry. * diff --git a/lib/context.js b/lib/context.js index b5b1dcd6..8d983d19 100644 --- a/lib/context.js +++ b/lib/context.js @@ -1284,12 +1284,12 @@ api.getContextValue = (ctx, key, type) => { } // get default language - if(type === '@language' && ctx.hasOwnProperty(type)) { + if(type === '@language' && type in ctx) { return ctx[type]; } // get default direction - if(type === '@direction' && ctx.hasOwnProperty(type)) { + if(type === '@direction' && type in ctx) { return ctx[type]; } diff --git a/lib/expand.js b/lib/expand.js index b20f1bde..44c5102f 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -466,8 +466,7 @@ async function _expandObject({ } if(expandedProperty in expandedParent && expandedProperty !== '@included' && - expandedProperty !== '@type') - { + expandedProperty !== '@type') { throw new JsonLdError( 'Invalid JSON-LD syntax; colliding keywords detected.', 'jsonld.SyntaxError', @@ -515,7 +514,17 @@ async function _expandObject({ } if(expandedProperty === '@type') { - _validateTypeValue(value); + // if framing, can be a default object, but need to expand + // key to determine that + if(_isObject(value)) { + value = Object.fromEntries(Object.entries(value).map(([k, v]) => [ + _expandIri(typeScopedContext, k, {vocab: true}), + _asArray(v).map(vv => + _expandIri(typeScopedContext, vv, {base: true, vocab: true}) + ) + ])); + } + _validateTypeValue(value, options.isFrame); _addValue( expandedParent, '@type', _asArray(value).map(v => diff --git a/lib/frame.js b/lib/frame.js index c33ad57c..9726f071 100644 --- a/lib/frame.js +++ b/lib/frame.js @@ -7,6 +7,7 @@ const {isKeyword} = require('./context'); const graphTypes = require('./graphTypes'); const types = require('./types'); const util = require('./util'); +const url = require('./url'); const JsonLdError = require('./JsonLdError'); const { createNodeMap: _createNodeMap, @@ -29,6 +30,7 @@ api.frameMergedOrDefault = (input, frame, options) => { // create framing state const state = { options, + embedded: false, graph: '@default', graphMap: {'@default': {}}, graphStack: [], @@ -58,7 +60,9 @@ api.frameMergedOrDefault = (input, frame, options) => { Object.keys(state.bnodeMap).filter(id => state.bnodeMap[id].length === 1); } - return framed; + // remove @preserve from results + options.link = {}; + return _cleanupPreserve(framed, options); }; /** @@ -83,6 +87,12 @@ api.frame = (state, subjects, frame, parent, property = null) => { requireAll: _getFrameFlag(frame, options, 'requireAll') }; + // get link for current graph + if(!state.link.hasOwnProperty(state.graph)) { + state.link[state.graph] = {}; + } + const link = state.link[state.graph]; + // filter out subjects that match the frame const matches = _filterSubjects(state, subjects, frame, flags); @@ -91,16 +101,6 @@ api.frame = (state, subjects, frame, parent, property = null) => { for(const id of ids) { const subject = matches[id]; - if(flags.embed === '@link' && id in state.link) { - // TODO: may want to also match an existing linked subject against - // the current frame ... so different frames could produce different - // subjects that are only shared in-memory when the frames are the same - - // add existing linked subject - _addFrameOutput(parent, property, state.link[id]); - continue; - } - /* Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is null, which only occurs at the top-level. */ @@ -110,20 +110,51 @@ api.frame = (state, subjects, frame, parent, property = null) => { state.uniqueEmbeds[state.graph] = state.uniqueEmbeds[state.graph] || {}; } + if(flags.embed === '@link' && id in link) { + // TODO: may want to also match an existing linked subject against + // the current frame ... so different frames could produce different + // subjects that are only shared in-memory when the frames are the same + + // add existing linked subject + _addFrameOutput(parent, property, link[id]); + continue; + } + // start output for subject - const output = {}; - output['@id'] = id; + const output = {'@id': id}; if(id.indexOf('_:') === 0) { util.addValue(state.bnodeMap, id, output, {propertyIsArray: true}); } - state.link[id] = output; + link[id] = output; + + // validate @embed + if((flags.embed === '@first' || flags.embed === '@last') && state.is11) { + throw new JsonLdError( + 'Invalid JSON-LD syntax; invalid value of @embed.', + 'jsonld.SyntaxError', {code: 'invalid @embed value', frame}); + } + + if(!state.embedded && state.uniqueEmbeds[state.graph].hasOwnProperty(id)) { + // skip adding this node object to the top level, as it was + // already included in another node object + continue; + } // if embed is @never or if a circular reference would be created by an // embed, the subject cannot be embedded, just add the reference; // note that a circular reference won't occur when the embed flag is // `@link` as the above check will short-circuit before reaching this point - if(flags.embed === '@never' || - _createsCircularReference(subject, state.graph, state.subjectStack)) { + if(state.embedded && + (flags.embed === '@never' || + _createsCircularReference(subject, state.graph, state.subjectStack))) { + _addFrameOutput(parent, property, output); + continue; + } + + // if only the first (or once) should be embedded + if(state.embedded && + (flags.embed == '@first' || flags.embed == '@once') && + state.uniqueEmbeds[state.graph].hasOwnProperty(id)) { _addFrameOutput(parent, property, output); continue; } @@ -134,10 +165,10 @@ api.frame = (state, subjects, frame, parent, property = null) => { if(id in state.uniqueEmbeds[state.graph]) { _removeEmbed(state, id); } - state.uniqueEmbeds[state.graph][id] = - {parent, property}; } + state.uniqueEmbeds[state.graph][id] = {parent, property}; + // push matching subject onto stack to enable circular embed checks state.subjectStack.push({subject, graph: state.graph}); @@ -150,27 +181,26 @@ api.frame = (state, subjects, frame, parent, property = null) => { subframe = {}; } else { subframe = frame['@graph'][0]; + recurse = !(id === '@merged' || id === '@default'); if(!types.isObject(subframe)) { subframe = {}; } - recurse = !(id === '@merged' || id === '@default'); } if(recurse) { state.graphStack.push(state.graph); - state.graph = id; // recurse into graph api.frame( - state, + {...state, graph: id, embedded: false}, Object.keys(state.graphMap[id]).sort(), [subframe], output, '@graph'); - state.graph = state.graphStack.pop; + state.graphStack.pop(); } } // if frame has @included, recurse over its sub-frame if('@included' in frame) { api.frame( - state, + {...state, embedded: false}, subjects, frame['@included'], output, '@included'); } @@ -198,36 +228,37 @@ api.frame = (state, subjects, frame, parent, property = null) => { } // add objects - for(let o of subject[prop]) { + for(const o of subject[prop]) { const subframe = (prop in frame ? frame[prop] : _createImplicitFrame(flags)); // recurse into list if(graphTypes.isList(o)) { + const subframe = (frame[prop] && types.isObject(frame[prop][0]) ? + frame[prop][0]['@list'] : _createImplicitFrame(flags)); + // add empty list const list = {'@list': []}; _addFrameOutput(output, prop, list); // add list objects const src = o['@list']; - for(const n in src) { - o = src[n]; - if(graphTypes.isSubjectReference(o)) { - const subframe = (prop in frame ? - frame[prop][0]['@list'] : _createImplicitFrame(flags)); + for(const oo of src) { + if(graphTypes.isSubjectReference(oo)) { // recurse into subject reference - api.frame(state, [o['@id']], subframe, list, '@list'); + api.frame( + {...state, embedded: true}, + [oo['@id']], subframe, list, '@list'); } else { // include other values automatically - _addFrameOutput(list, '@list', util.clone(o)); + _addFrameOutput(list, '@list', util.clone(oo)); } } - continue; - } - - if(graphTypes.isSubjectReference(o)) { + } else if(graphTypes.isSubjectReference(o)) { // recurse into subject reference - api.frame(state, [o['@id']], subframe, output, prop); + api.frame( + {...state, embedded: true}, + [o['@id']], subframe, output, prop); } else if(_valueMatch(subframe[0], o)) { // include other values, if they match _addFrameOutput(output, prop, util.clone(o)); @@ -238,7 +269,13 @@ api.frame = (state, subjects, frame, parent, property = null) => { // handle defaults for(const prop of Object.keys(frame).sort()) { // skip keywords - if(isKeyword(prop)) { + if(prop === '@type') { + if(!types.isObject(frame[prop][0]) || + !('@default' in frame[prop][0])) { + continue; + } + // allow through default types + } else if(isKeyword(prop)) { continue; } @@ -260,21 +297,20 @@ api.frame = (state, subjects, frame, parent, property = null) => { // if embed reverse values by finding nodes having this subject as a value // of the associated property - if('@reverse' in frame) { - for(const reverseProp of Object.keys(frame['@reverse']).sort()) { - const subframe = frame['@reverse'][reverseProp]; - for(const subject of Object.keys(state.subjects)) { - const nodeValues = - util.getValues(state.subjects[subject], reverseProp); - if(nodeValues.some(v => v['@id'] === id)) { - // node has property referencing this subject, recurse - output['@reverse'] = output['@reverse'] || {}; - util.addValue( - output['@reverse'], reverseProp, [], {propertyIsArray: true}); - api.frame( - state, [subject], subframe, output['@reverse'][reverseProp], - property); - } + for(const reverseProp of Object.keys(frame['@reverse'] || {}).sort()) { + const subframe = frame['@reverse'][reverseProp]; + for(const subject of Object.keys(state.subjects)) { + const nodeValues = + util.getValues(state.subjects[subject], reverseProp); + if(nodeValues.some(v => v['@id'] === id)) { + // node has property referencing this subject, recurse + output['@reverse'] = output['@reverse'] || {}; + util.addValue( + output['@reverse'], reverseProp, [], {propertyIsArray: true}); + api.frame( + {...state, embedded: true}, + [subject], subframe, output['@reverse'][reverseProp], + property); } } } @@ -287,6 +323,50 @@ api.frame = (state, subjects, frame, parent, property = null) => { } }; +/** + * Replace `@null` with `null`, removing it from arrays. + * + * @param input the framed, compacted output. + * @param options the framing options used. + * + * @return the resulting output. + */ +api.cleanupNull = (input, options) => { + // recurse through arrays + if(types.isArray(input)) { + const noNulls = input.map(v => api.cleanupNull(v, options)); + return noNulls.filter(v => v); // removes nulls from array + } + + if(input === '@null') { + return null; + } + + if(types.isObject(input)) { + // handle in-memory linked nodes + if('@id' in input) { + const id = input['@id']; + if(options.link.hasOwnProperty(id)) { + const idx = options.link[id].indexOf(input); + if(idx !== -1) { + // already visited + return options.link[id][idx]; + } + // prevent circular visitation + options.link[id].push(input); + } else { + // prevent circular visitation + options.link[id] = [input]; + } + } + + for(const key in input) { + input[key] = api.cleanupNull(input[key], options); + } + } + return input; +}; + /** * Creates an implicit frame when recursing through subject matches. If * a frame doesn't have an explicit frame for a particular property, then @@ -346,11 +426,14 @@ function _getFrameFlag(frame, options, name) { // true => "@last" // false => "@never" if(rval === true) { - rval = '@last'; + rval = '@once'; } else if(rval === false) { rval = '@never'; - } else if(rval !== '@always' && rval !== '@never' && rval !== '@link') { - rval = '@last'; + } else if(rval !== '@always' && rval !== '@never' && rval !== '@link' && + rval !== '@first' && rval !== '@last' && rval !== '@once') { + throw new JsonLdError( + 'Invalid JSON-LD syntax; invalid value of @embed.', + 'jsonld.SyntaxError', {code: 'invalid @embed value', frame}); } } return rval; @@ -367,6 +450,30 @@ function _validateFrame(frame) { 'Invalid JSON-LD syntax; a JSON-LD frame must be a single object.', 'jsonld.SyntaxError', {frame}); } + + if('@id' in frame[0]) { + for(const id of util.asArray(frame[0]['@id'])) { + // @id must be wildcard or an IRI + if(!(types.isObject(id) || url.isAbsolute(id)) || + (types.isString(id) && id.indexOf('_:') === 0)) { + throw new JsonLdError( + 'Invalid JSON-LD syntax; invalid @id in frame.', + 'jsonld.SyntaxError', {code: 'invalid frame', frame}); + } + } + } + + if('@type' in frame[0]) { + for(const type of util.asArray(frame[0]['@type'])) { + // @id must be wildcard or an IRI + if(!(types.isObject(type) || url.isAbsolute(type)) || + (types.isString(type) && type.indexOf('_:') === 0)) { + throw new JsonLdError( + 'Invalid JSON-LD syntax; invalid @type in frame.', + 'jsonld.SyntaxError', {code: 'invalid frame', frame}); + } + } + } } /** @@ -419,105 +526,105 @@ function _filterSubject(state, subject, frame, flags) { const nodeValues = util.getValues(subject, key); const isEmpty = util.getValues(frame, key).length === 0; - if(isKeyword(key)) { - // skip non-@id and non-@type - if(key !== '@id' && key !== '@type') { - continue; + if(key === '@id') { + // match on no @id or any matching @id, including wildcard + if(types.isEmptyObject(frame['@id'][0] || {})) { + matchThis = true; + } else if(frame['@id'].length >= 0) { + matchThis = frame['@id'].includes(nodeValues[0]); } + if(!flags.requireAll) { + return matchThis; + } + } else if(key === '@type') { + // check @type (object value means 'any' type, + // fall through to ducktyping) wildcard = false; - - // check @id for a specific @id value - if(key === '@id') { - // if @id is not a wildcard and is not empty, then match or not on - // specific value - if(frame['@id'].length >= 0 && !types.isEmptyObject(frame['@id'][0])) { - return frame['@id'].includes(nodeValues[0]); + if(isEmpty) { + if(nodeValues.length > 0) { + // don't match on no @type + return false; } matchThis = true; - continue; - } - - // check @type (object value means 'any' type, fall through to ducktyping) - if('@type' in frame) { - if(isEmpty) { - if(nodeValues.length > 0) { - // don't match on no @type - return false; - } - matchThis = true; - } else if(frame['@type'].length === 1 && - types.isEmptyObject(frame['@type'][0])) { - // match on wildcard @type - matchThis = nodeValues.length > 0; - } else { - // match on a specific @type - for(const type of frame['@type']) { - if(nodeValues.some(tt => tt === type)) { - return true; - } + } else if(frame['@type'].length === 1 && + types.isEmptyObject(frame['@type'][0])) { + // match on wildcard @type if there is a type + matchThis = nodeValues.length > 0; + } else { + // match on a specific @type + for(const type of frame['@type']) { + if(types.isObject(type) && '@default' in type) { + // match on default object + matchThis = true; + } else { + matchThis = matchThis || nodeValues.some(tt => tt === type); } - return false; } } - } - - // Forc a copy of this frame entry so it can be manipulated - const thisFrame = util.getValues(frame, key)[0]; - let hasDefault = false; - if(thisFrame) { - _validateFrame([thisFrame]); - hasDefault = '@default' in thisFrame; - } - - // no longer a wildcard pattern if frame has any non-keyword properties - wildcard = false; - - // skip, but allow match if node has no value for property, and frame has a - // default value - if(nodeValues.length === 0 && hasDefault) { + if(!flags.requireAll) { + return matchThis; + } + } else if(isKeyword(key)) { continue; - } + } else { + // Force a copy of this frame entry so it can be manipulated + const thisFrame = util.getValues(frame, key)[0]; + let hasDefault = false; + if(thisFrame) { + _validateFrame([thisFrame]); + hasDefault = '@default' in thisFrame; + } - // if frame value is empty, don't match if subject has any value - if(nodeValues.length > 0 && isEmpty) { - return false; - } + // no longer a wildcard pattern if frame has any non-keyword properties + wildcard = false; - if(thisFrame === undefined) { - // node does not match if values is not empty and the value of property - // in frame is match none. - if(nodeValues.length > 0) { + // skip, but allow match if node has no value for property, and frame has + // a default value + if(nodeValues.length === 0 && hasDefault) { + continue; + } + + // if frame value is empty, don't match if subject has any value + if(nodeValues.length > 0 && isEmpty) { return false; } - matchThis = true; - } else if(types.isObject(thisFrame)) { - // node matches if values is not empty and the value of property in frame - // is wildcard - matchThis = nodeValues.length > 0; - } else { - if(graphTypes.isValue(thisFrame)) { - // match on any matching value - matchThis = nodeValues.some(nv => _valueMatch(thisFrame, nv)); - } else if(graphTypes.isSubject(thisFrame) || - graphTypes.isSubjectReference(thisFrame)) { - matchThis = - nodeValues.some(nv => _nodeMatch(state, thisFrame, nv, flags)); - } else if(graphTypes.isList(thisFrame)) { - const listValue = thisFrame['@list'][0]; - if(graphTypes.isList(nodeValues[0])) { - const nodeListValues = nodeValues[0]['@list']; - - if(graphTypes.isValue(listValue)) { - // match on any matching value - matchThis = nodeListValues.some(lv => _valueMatch(listValue, lv)); - } else if(graphTypes.isSubject(listValue) || - graphTypes.isSubjectReference(listValue)) { - matchThis = nodeListValues.some(lv => _nodeMatch( - state, listValue, lv, flags)); + + if(thisFrame === undefined) { + // node does not match if values is not empty and the value of property + // in frame is match none. + if(nodeValues.length > 0) { + return false; + } + matchThis = true; + } else if(types.isObject(thisFrame)) { // XXX only framing keywords + // node matches if values is not empty and the value of property in + // frame is wildcard + matchThis = nodeValues.length > 0; + } else { + if(graphTypes.isValue(thisFrame)) { + // match on any matching value + matchThis = nodeValues.some(nv => _valueMatch(thisFrame, nv)); + } else if(graphTypes.isSubject(thisFrame) || + graphTypes.isSubjectReference(thisFrame)) { + matchThis = + nodeValues.some(nv => _nodeMatch(state, thisFrame, nv, flags)); + } else if(graphTypes.isList(thisFrame)) { + const listValue = thisFrame['@list'][0]; + if(graphTypes.isList(nodeValues[0])) { + const nodeListValues = nodeValues[0]['@list']; + + if(graphTypes.isValue(listValue)) { + // match on any matching value + matchThis = nodeListValues.some(lv => _valueMatch(listValue, lv)); + } else if(graphTypes.isSubject(listValue) || + graphTypes.isSubjectReference(listValue)) { + matchThis = nodeListValues.some(lv => _nodeMatch( + state, listValue, lv, flags)); + } + } else { + // value must be a list to match + matchThis = false; } - } else { - // value must be a list to match - matchThis = false; } } } @@ -581,6 +688,68 @@ function _removeEmbed(state, id) { removeDependents(id); } +/** + * Removes the @preserve keywords from expanded result of framing. + * + * @param input the framed, framed output. + * @param options the framing options used. + * + * @return the resulting output. + */ +function _cleanupPreserve(input, options) { + // recurse through arrays + if(types.isArray(input)) { + return input.map(value => _cleanupPreserve(value, options)); + } + + if(types.isObject(input)) { + // remove @preserve + if('@preserve' in input) { + return input['@preserve'][0]; + } + + // skip @values + if(graphTypes.isValue(input)) { + return input; + } + + // recurse through @lists + if(graphTypes.isList(input)) { + input['@list'] = _cleanupPreserve(input['@list'], options); + return input; + } + + // handle in-memory linked nodes + if('@id' in input) { + const id = input['@id']; + if(options.link.hasOwnProperty(id)) { + const idx = options.link[id].indexOf(input); + if(idx !== -1) { + // already visited + return options.link[id][idx]; + } + // prevent circular visitation + options.link[id].push(input); + } else { + // prevent circular visitation + options.link[id] = [input]; + } + } + + // recurse through properties + for(const prop in input) { + // potentially remove the id, if it is an unreference bnode + if(prop === '@id' && options.bnodesToClear.includes(input[prop])) { + delete input['@id']; + continue; + } + + input[prop] = _cleanupPreserve(input[prop], options); + } + } + return input; +} + /** * Adds framing output to the given parent. * diff --git a/lib/fromRdf.js b/lib/fromRdf.js index 84986bae..fb3567c8 100644 --- a/lib/fromRdf.js +++ b/lib/fromRdf.js @@ -329,8 +329,7 @@ function _RDFToObject(o, useNativeTypes, rdfDirection) { rval['@type'] = type; } } else if(rdfDirection === 'i18n-datatype' && - type.startsWith('https://www.w3.org/ns/i18n#')) - { + type.startsWith('https://www.w3.org/ns/i18n#')) { const [, language, direction] = type.split(/[#_]/); if(language.length > 0) { rval['@language'] = language; diff --git a/lib/index.js b/lib/index.js index c91a88ae..ffa4c3e4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,8 +5,15 @@ * * Copyright 2010-2017 Digital Bazaar, Inc. */ +// FIXME: remove after change to core-js 3 and dropping node6 support +const fromEntries = require('object.fromentries'); +if(!Object.fromEntries) { + fromEntries.shim(); +} + if(require('semver').gte(process.version, '8.6.0')) { module.exports = require('./jsonld'); } else { + require('core-js/fn/object/entries'); module.exports = require('../dist/node6/lib/jsonld'); } diff --git a/lib/jsonld.js b/lib/jsonld.js index 718309d8..ebd91bf5 100644 --- a/lib/jsonld.js +++ b/lib/jsonld.js @@ -48,7 +48,8 @@ const {fromRDF: _fromRDF} = require('./fromRdf'); const {toRDF: _toRDF} = require('./toRdf'); const { - frameMergedOrDefault: _frameMergedOrDefault + frameMergedOrDefault: _frameMergedOrDefault, + cleanupNull: _cleanupNull } = require('./frame'); const { @@ -62,14 +63,15 @@ const { } = require('./graphTypes'); const { + expandIri: _expandIri, getInitialContext: _getInitialContext, - process: _processContext + process: _processContext, + processingMode: _processingMode } = require('./context'); const { compact: _compact, - compactIri: _compactIri, - removePreserve: _removePreserve + compactIri: _compactIri } = require('./compact'); const { @@ -244,16 +246,6 @@ jsonld.compact = async function(input, ctx, options) { } } - if(options.framing) { - // get graph alias - const graph = _compactIri({ - activeCtx, iri: '@graph', relativeTo: {vocab: true} - }); - // remove @preserve from results - options.link = {}; - compacted[graph] = _removePreserve(activeCtx, compacted[graph], options); - } - return compacted; }; @@ -435,11 +427,10 @@ jsonld.frame = async function(input, frame, options) { // set default options options = _setDefaults(options, { base: _isString(input) ? input : '', - embed: '@last', + embed: '@once', explicit: false, - requireAll: true, + requireAll: false, omitDefault: false, - pruneBlankNodeIdentifiers: true, bnodesToClear: [], contextResolver: new ContextResolver( {sharedCache: _resolvedContextCache}) @@ -467,6 +458,18 @@ jsonld.frame = async function(input, frame, options) { const frameContext = frame ? frame['@context'] || {} : {}; + // process context + const activeCtx = await jsonld.processContext( + _getInitialContext(options), frameContext, options); + + // mode specific defaults + if(!options.hasOwnProperty('omitGraph')) { + options.omitGraph = _processingMode(activeCtx, 1.1); + } + if(!options.hasOwnProperty('pruneBlankNodeIdentifiers')) { + options.pruneBlankNodeIdentifiers = _processingMode(activeCtx, 1.1); + } + // expand input const expanded = await jsonld.expand(input, options); @@ -478,18 +481,23 @@ jsonld.frame = async function(input, frame, options) { // if the unexpanded frame includes a key expanding to @graph, frame the // default graph, otherwise, the merged graph - // FIXME should look for aliases of @graph - opts.merged = !('@graph' in frame); + const frameKeys = Object.keys(frame) + .map(key => _expandIri(activeCtx, key, {vocab: true})); + opts.merged = !frameKeys.includes('@graph'); + opts.is11 = _processingMode(activeCtx, 1.1); + // do framing const framed = _frameMergedOrDefault(expanded, expandedFrame, opts); - // compact result (force @graph option to true, skip expansion, - // check for linked embeds) - opts.graph = true; + opts.graph = !options.omitGraph; opts.skipExpansion = true; opts.link = {}; opts.framing = true; - const compacted = await jsonld.compact(framed, frameContext, opts); + let compacted = await jsonld.compact(framed, frameContext, opts); + + // replace @null with null, compacting arrays + opts.link = {}; + compacted = _cleanupNull(compacted, opts); return compacted; }; diff --git a/lib/toRdf.js b/lib/toRdf.js index 61a056df..d19980c1 100644 --- a/lib/toRdf.js +++ b/lib/toRdf.js @@ -245,8 +245,7 @@ function _objectToRDF(item, issuer, dataset, graphTerm, rdfDirection) { object.value = value.toFixed(0); object.datatype.value = datatype || XSD_INTEGER; } else if(rdfDirection === 'i18n-datatype' && - '@direction' in item) - { + '@direction' in item) { const datatype = 'https://www.w3.org/ns/i18n#' + (item['@language'] || '') + `_${item['@direction']}`; diff --git a/lib/util.js b/lib/util.js index b250d51d..77da8f61 100644 --- a/lib/util.js +++ b/lib/util.js @@ -147,31 +147,33 @@ api.parseLinkHeader = header => { * * @param v the value to check. */ -api.validateTypeValue = v => { - // can be a string or an empty object - if(types.isString(v) || types.isEmptyObject(v)) { +api.validateTypeValue = (v, isFrame) => { + if(types.isString(v)) { return; } - // must be an array - let isValid = false; - if(types.isArray(v)) { - // must contain only strings - isValid = true; - for(let i = 0; i < v.length; ++i) { - if(!(types.isString(v[i]))) { - isValid = false; - break; - } + if(types.isArray(v) && v.every(vv => types.isString(vv))) { + return; + } + if(isFrame && types.isObject(v)) { + switch(Object.keys(v).length) { + case 0: + // empty object is wildcard + return; + case 1: + // default entry is all strings + if('@default' in v && + api.asArray(v['@default']).every(vv => types.isString(vv))) { + return; + } } } - if(!isValid) { - throw new JsonLdError( - 'Invalid JSON-LD syntax; "@type" value must a string, an array of ' + - 'strings, or an empty object.', 'jsonld.SyntaxError', - {code: 'invalid type value', value: v}); - } + throw new JsonLdError( + 'Invalid JSON-LD syntax; "@type" value must a string, an array of ' + + 'strings, an empty object, ' + + 'or a default object.', 'jsonld.SyntaxError', + {code: 'invalid type value', value: v}); }; /** diff --git a/package.json b/package.json index 20bf730e..06886dcd 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dependencies": { "canonicalize": "^1.0.1", "lru-cache": "^5.1.1", + "object.fromentries": "^2.0.2", "rdf-canonize": "^1.0.2", "request": "^2.88.0", "semver": "^6.3.0", @@ -54,8 +55,8 @@ "core-js": "^2.6.5", "cors": "^2.7.1", "cross-env": "^5.2.0", - "eslint": "^6.7.2", - "eslint-config-digitalbazaar": "^2.0.1", + "eslint": "^6.8.0", + "eslint-config-digitalbazaar": "^2.3.0", "express": "^4.16.4", "fs-extra": "^8.1.0", "join-path-js": "0.0.0", diff --git a/tests/misc.js b/tests/misc.js index 6c2758c7..1c3758e0 100644 --- a/tests/misc.js +++ b/tests/misc.js @@ -22,7 +22,6 @@ describe('link tests', () => { p.catch(e => { assert.ifError(e); }).then(output => { - output = output['@graph'][0]; assert.equal(output, output['a:foo']); done(); }); @@ -453,13 +452,9 @@ describe('js keywords', () => { "@context": { "@vocab": "http://example.org/" }, - "@graph": [ - { - "toString": { - "valueOf": "thing" - } - } - ] + "toString": { + "valueOf": "thing" + } } ; const e = await jsonld.frame(d, frame); diff --git a/tests/new-embed-api/frame-0001-frame.jsonld b/tests/new-embed-api/frame-0001-frame.jsonld deleted file mode 100644 index 5864ae8f..00000000 --- a/tests/new-embed-api/frame-0001-frame.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always" - }, - "ex:bookmark": { - "@type": "ex:Chapter", - "@embed": "@always" - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0001-in.jsonld b/tests/new-embed-api/frame-0001-in.jsonld deleted file mode 100644 index 342c560b..00000000 --- a/tests/new-embed-api/frame-0001-in.jsonld +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:bookmark": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": "http://example.org/test#chapter", - "ex:bookmark": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One" - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0001-out.jsonld b/tests/new-embed-api/frame-0001-out.jsonld deleted file mode 100644 index 071b66e6..00000000 --- a/tests/new-embed-api/frame-0001-out.jsonld +++ /dev/null @@ -1,28 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [{ - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One" - }, - "ex:bookmark": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One" - } - } - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0002-frame.jsonld b/tests/new-embed-api/frame-0002-frame.jsonld deleted file mode 100644 index 8043b5b7..00000000 --- a/tests/new-embed-api/frame-0002-frame.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always" - }, - "ex:topic": { - "@type": "ex:Library", - "@embed": "@always" - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0002-in.jsonld b/tests/new-embed-api/frame-0002-in.jsonld deleted file mode 100644 index f2179644..00000000 --- a/tests/new-embed-api/frame-0002-in.jsonld +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:topic": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": "http://example.org/test#chapter", - "ex:topic": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One" - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0002-out.jsonld b/tests/new-embed-api/frame-0002-out.jsonld deleted file mode 100644 index 635eeb41..00000000 --- a/tests/new-embed-api/frame-0002-out.jsonld +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [{ - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One" - }, - "ex:topic": { - "@id": "http://example.org/test/#library" - } - } - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0003-frame.jsonld b/tests/new-embed-api/frame-0003-frame.jsonld deleted file mode 100644 index 44c813e2..00000000 --- a/tests/new-embed-api/frame-0003-frame.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always", - "ex:topic": { - "@type": "ex:Library", - "@embed": "@always" - } - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0003-in.jsonld b/tests/new-embed-api/frame-0003-in.jsonld deleted file mode 100644 index 3ca2b8b6..00000000 --- a/tests/new-embed-api/frame-0003-in.jsonld +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:topic": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": "http://example.org/test/#library" - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0003-out.jsonld b/tests/new-embed-api/frame-0003-out.jsonld deleted file mode 100644 index 34c91f8d..00000000 --- a/tests/new-embed-api/frame-0003-out.jsonld +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [{ - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": { - "@id": "http://example.org/test/#library" - } - } - } - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0004-frame.jsonld b/tests/new-embed-api/frame-0004-frame.jsonld deleted file mode 100644 index 7e588379..00000000 --- a/tests/new-embed-api/frame-0004-frame.jsonld +++ /dev/null @@ -1,21 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@omitDefault": "true", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always", - "ex:topic": { - "@type": "ex:Library", - "@embed": "@always" - } - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0004-in.jsonld b/tests/new-embed-api/frame-0004-in.jsonld deleted file mode 100644 index 46274748..00000000 --- a/tests/new-embed-api/frame-0004-in.jsonld +++ /dev/null @@ -1,40 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:topic": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:name": "My local library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:name": "Another library" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": { - "@set": [ - "http://example.org/test/#library", - "http://example.org/test/#library2" - ] - } - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0004-out.jsonld b/tests/new-embed-api/frame-0004-out.jsonld deleted file mode 100644 index e37cb1f2..00000000 --- a/tests/new-embed-api/frame-0004-out.jsonld +++ /dev/null @@ -1,38 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [{ - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:name": "My local library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": [ - { - "@id": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:name": "Another library" - } - ] - } - } - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:name": "Another library" - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0005-frame.jsonld b/tests/new-embed-api/frame-0005-frame.jsonld deleted file mode 100644 index 91c19b44..00000000 --- a/tests/new-embed-api/frame-0005-frame.jsonld +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Town", - "@embed": "@always", - "ex:hasLibrary": { - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always", - "ex:topic": { - "@type": "ex:Library", - "@embed": "@always" - } - } - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0005-in.jsonld b/tests/new-embed-api/frame-0005-in.jsonld deleted file mode 100644 index 6e8f9863..00000000 --- a/tests/new-embed-api/frame-0005-in.jsonld +++ /dev/null @@ -1,50 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:topic": {"@type": "@id"}, - "ex:hasLibrary": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/town/#123", - "@type": "ex:Town", - "ex:name": "My town", - "ex:hasLibrary": [ - "http://example.org/test/#library", - "http://example.org/test/#library2" - ] - }, - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:name": "My local library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:name": "Another library" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": { - "@set": [ - "http://example.org/test/#library", - "http://example.org/test/#library2" - ] - } - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0005-out.jsonld b/tests/new-embed-api/frame-0005-out.jsonld deleted file mode 100644 index a3a45cc7..00000000 --- a/tests/new-embed-api/frame-0005-out.jsonld +++ /dev/null @@ -1,48 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [ - { - "@id": "http://example.org/town/#123", - "@type": "ex:Town", - "ex:name": "My town", - "ex:hasLibrary": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:name": "My local library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book about a library", - "ex:contains": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": [ - { - "@id": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:name": "Another library" - } - ] - } - } - }, - { - "@id": "http://example.org/test/#library2", - "@type": "ex:Library", - "ex:contains": null, - "ex:name": "Another library" - } - ] - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0006-frame.jsonld b/tests/new-embed-api/frame-0006-frame.jsonld deleted file mode 100644 index 56ece9bf..00000000 --- a/tests/new-embed-api/frame-0006-frame.jsonld +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:bookmark": { - "@type": "ex:Chapter", - "@embed": "@always" - }, - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always", - "ex:topic" : { - "@type": "ex:Topic", - "@embed": "@always" - } - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0006-in.jsonld b/tests/new-embed-api/frame-0006-in.jsonld deleted file mode 100644 index fc7a766b..00000000 --- a/tests/new-embed-api/frame-0006-in.jsonld +++ /dev/null @@ -1,73 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:contains": {"@type": "@id"}, - "ex:bookmark": {"@type": "@id"}, - "ex:topic": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": [ - "http://example.org/test#chapter", - "http://example.org/test#chapter2" - ], - "ex:bookmark": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": [ - "http://example.org/test#subject1", - "http://example.org/test#subject2", - "http://example.org/test#subject3" - ] - }, - { - "@id": "http://example.org/test#chapter2", - "@type": "ex:Chapter", - "dc:description": "More Fun", - "dc:title": "Chapter Two", - "ex:topic": [ - "http://example.org/test#subject1", - "http://example.org/test#subject4", - "http://example.org/test#subject5" - ] - }, - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - }, - { - "@id": "http://example.org/test#subject4", - "@type": "ex:Topic", - "dc:description": "Topic 4" - }, - { - "@id": "http://example.org/test#subject5", - "@type": "ex:Topic", - "dc:description": "Topic 5" - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0006-out.jsonld b/tests/new-embed-api/frame-0006-out.jsonld deleted file mode 100644 index 52d0cacc..00000000 --- a/tests/new-embed-api/frame-0006-out.jsonld +++ /dev/null @@ -1,89 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": [ - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - } - ] - }, - { - "@id": "http://example.org/test#chapter2", - "@type": "ex:Chapter", - "dc:description": "More Fun", - "dc:title": "Chapter Two", - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1" - }, - { - "@id": "http://example.org/test#subject4", - "@type": "ex:Topic", - "dc:description": "Topic 4" - }, - { - "@id": "http://example.org/test#subject5", - "@type": "ex:Topic", - "dc:description": "Topic 5" - } - ] - } - ], - "ex:bookmark": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0007-frame.jsonld b/tests/new-embed-api/frame-0007-frame.jsonld deleted file mode 100644 index d1fa6658..00000000 --- a/tests/new-embed-api/frame-0007-frame.jsonld +++ /dev/null @@ -1,35 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:relatesTo": {"@type": "@id"} - }, - "@type": "ex:Library", - "@embed": "@always", - "ex:contains": { - "@type": "ex:Book", - "@embed": "@always", - "ex:bookmark": { - "@type": "ex:Chapter", - "@embed": "@always" - }, - "ex:contains": { - "@type": "ex:Chapter", - "@embed": "@always", - "dc:subject": { - "@omitDefault": "true", - "@embed": "@always", - "@type": "ex:Library" - }, - "ex:topic" : { - "@type": "ex:Topic", - "@embed": "@always", - "ex:relatesTo" : { - "@omitDefault": "true", - "@embed": "@always", - "@type": "ex:Library" - } - } - } - } -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0007-in.jsonld b/tests/new-embed-api/frame-0007-in.jsonld deleted file mode 100644 index 9d4cca97..00000000 --- a/tests/new-embed-api/frame-0007-in.jsonld +++ /dev/null @@ -1,77 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "dc:subject": {"@type": "@id"}, - "ex:contains": {"@type": "@id"}, - "ex:bookmark": {"@type": "@id"}, - "ex:topic": {"@type": "@id"}, - "ex:relatesTo": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": "http://example.org/test#book" - }, - { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": [ - "http://example.org/test#chapter", - "http://example.org/test#chapter2" - ], - "ex:bookmark": "http://example.org/test#chapter" - }, - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "dc:subject": "http://example.org/test/#library", - "ex:topic": [ - "http://example.org/test#subject1", - "http://example.org/test#subject2", - "http://example.org/test#subject3" - ] - }, - { - "@id": "http://example.org/test#chapter2", - "@type": "ex:Chapter", - "dc:description": "More Fun", - "dc:title": "Chapter Two", - "ex:topic": [ - "http://example.org/test#subject1", - "http://example.org/test#subject4", - "http://example.org/test#subject5" - ] - }, - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1", - "ex:relatesTo": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - }, - { - "@id": "http://example.org/test#subject4", - "@type": "ex:Topic", - "dc:description": "Topic 4" - }, - { - "@id": "http://example.org/test#subject5", - "@type": "ex:Topic", - "dc:description": "Topic 5" - }] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0007-out.jsonld b/tests/new-embed-api/frame-0007-out.jsonld deleted file mode 100644 index 9cb477f3..00000000 --- a/tests/new-embed-api/frame-0007-out.jsonld +++ /dev/null @@ -1,95 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#", - "ex:relatesTo": {"@type": "@id"} - }, - "@graph": [ - { - "@id": "http://example.org/test/#library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/test#book", - "@type": "ex:Book", - "dc:contributor": "Writer", - "dc:title": "My Book", - "ex:contains": [ - { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "dc:subject": {"@id": "http://example.org/test/#library"}, - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1", - "ex:relatesTo": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - } - ] - }, - { - "@id": "http://example.org/test#chapter2", - "@type": "ex:Chapter", - "dc:description": "More Fun", - "dc:title": "Chapter Two", - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1", - "ex:relatesTo": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test#subject4", - "@type": "ex:Topic", - "dc:description": "Topic 4" - }, - { - "@id": "http://example.org/test#subject5", - "@type": "ex:Topic", - "dc:description": "Topic 5" - } - ] - } - ], - "ex:bookmark": { - "@id": "http://example.org/test#chapter", - "@type": "ex:Chapter", - "dc:description": "Fun", - "dc:title": "Chapter One", - "dc:subject": {"@id": "http://example.org/test/#library"}, - "ex:topic": [ - { - "@id": "http://example.org/test#subject1", - "@type": "ex:Topic", - "dc:description": "Topic 1", - "ex:relatesTo": "http://example.org/test/#library" - }, - { - "@id": "http://example.org/test#subject2", - "@type": "ex:Topic", - "dc:description": "Topic 2" - }, - { - "@id": "http://example.org/test#subject3", - "@type": "ex:Topic", - "dc:description": "Topic 3" - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/tests/new-embed-api/frame-0008-frame.jsonld b/tests/new-embed-api/frame-0008-frame.jsonld deleted file mode 100644 index dfc4fd62..00000000 --- a/tests/new-embed-api/frame-0008-frame.jsonld +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@context": { - "ex": "http://example.com/", - "embed": { - "@id": "ex:embed", - "@container": "@set" - }, - "shouldExist": "ex:shouldExist" - }, - "@type": "http://example.com/TreeRoot", - "@embed": "@always", - "embed": { - "@embed": "@always" - } -} diff --git a/tests/new-embed-api/frame-0008-in.jsonld b/tests/new-embed-api/frame-0008-in.jsonld deleted file mode 100644 index 08d32aef..00000000 --- a/tests/new-embed-api/frame-0008-in.jsonld +++ /dev/null @@ -1,52 +0,0 @@ -{ - "@context": { - "ex": "http://example.com/", - "embed": { - "@id": "ex:embed", - "@container": "@set" - }, - "shouldExist": "ex:shouldExist" - }, - "@graph": [ - { - "@id": "ex:root", - "@type": "ex:TreeRoot", - "embed": [ - { - "@id": "ex:node-d1-with-leaf" - }, - { - "@id": "ex:node-d1-with-node" - } - ] - }, - { - "@id": "ex:node-d1-with-leaf", - "embed": [ - { - "@id": "ex:leaf" - } - ] - }, - { - "@id": "ex:node-d1-with-node", - "embed": [ - { - "@id": "ex:node-d2-with-leaf" - } - ] - }, - { - "@id": "ex:node-d2-with-leaf", - "embed": [ - { - "@id": "ex:leaf" - } - ] - }, - { - "@id": "ex:leaf", - "shouldExist": "shows when embedded" - } - ] -} diff --git a/tests/new-embed-api/frame-0008-out.jsonld b/tests/new-embed-api/frame-0008-out.jsonld deleted file mode 100644 index 5698740a..00000000 --- a/tests/new-embed-api/frame-0008-out.jsonld +++ /dev/null @@ -1,40 +0,0 @@ -{ - "@context": { - "ex": "http://example.com/", - "embed": { - "@id": "ex:embed", - "@container": "@set" - },"shouldExist": "ex:shouldExist" - }, - "@graph": [ - { - "@id": "ex:root", - "@type": "ex:TreeRoot", - "embed": [ - { - "@id": "ex:node-d1-with-leaf", - "embed": [ - { - "@id": "ex:leaf", - "shouldExist": "shows when embedded" - } - ] - }, - { - "@id": "ex:node-d1-with-node", - "embed": [ - { - "@id": "ex:node-d2-with-leaf", - "embed": [ - { - "@id": "ex:leaf", - "shouldExist": "shows when embedded" - } - ] - } - ] - } - ] - } - ] -} diff --git a/tests/new-embed-api/manifest.jsonld b/tests/new-embed-api/manifest.jsonld deleted file mode 100644 index 7b9dd043..00000000 --- a/tests/new-embed-api/manifest.jsonld +++ /dev/null @@ -1,65 +0,0 @@ -{ - "@context": "http://json-ld.org/test-suite/context.jsonld", - "@id": "", - "@type": "mf:Manifest", - "name": "New Embed API Framing", - "description": "JSON-LD framing tests use object comparison.", - "baseIri": "", - "sequence": [{ - "@id": "#t0001", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Simple embed", - "input": "frame-0001-in.jsonld", - "frame": "frame-0001-frame.jsonld", - "expect": "frame-0001-out.jsonld" - }, { - "@id": "#t0002", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Embed with direct circular reference", - "input": "frame-0002-in.jsonld", - "frame": "frame-0002-frame.jsonld", - "expect": "frame-0002-out.jsonld" - }, { - "@id": "#t0003", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Embed with indirect circular reference", - "input": "frame-0003-in.jsonld", - "frame": "frame-0003-frame.jsonld", - "expect": "frame-0003-out.jsonld" - }, { - "@id": "#t0004", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Embed with indirect circular reference via set", - "input": "frame-0004-in.jsonld", - "frame": "frame-0004-frame.jsonld", - "expect": "frame-0004-out.jsonld" - }, { - "@id": "#t0005", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Embed with nested indirect circular reference via set", - "input": "frame-0005-in.jsonld", - "frame": "frame-0005-frame.jsonld", - "expect": "frame-0005-out.jsonld" - }, { - "@id": "#t0006", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Multi-level simple embeds", - "input": "frame-0006-in.jsonld", - "frame": "frame-0006-frame.jsonld", - "expect": "frame-0006-out.jsonld" - }, { - "@id": "#t0007", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "A tangle of nastiness", - "input": "frame-0007-in.jsonld", - "frame": "frame-0007-frame.jsonld", - "expect": "frame-0007-out.jsonld" - },{ - "@id": "#t0008", - "@type": ["jld:PositiveEvaluationTest", "jld:FrameTest"], - "name": "Recursive property embed w/o circular reference", - "input": "frame-0008-in.jsonld", - "frame": "frame-0008-frame.jsonld", - "expect": "frame-0008-out.jsonld" - }] -} diff --git a/tests/test-common.js b/tests/test-common.js index 6d34a3e5..b449739a 100644 --- a/tests/test-common.js +++ b/tests/test-common.js @@ -126,71 +126,9 @@ const TEST_TYPES = { specVersion: ['json-ld-1.0'], // FIXME idRegex: [ - // ex - /frame-manifest.jsonld#tg001$/, - // graphs - /frame-manifest.jsonld#t0011$/, - /frame-manifest.jsonld#t0010$/, - /frame-manifest.jsonld#t0020$/, - /frame-manifest.jsonld#t0023$/, - /frame-manifest.jsonld#t0026$/, - /frame-manifest.jsonld#t0027$/, - /frame-manifest.jsonld#t0028$/, - /frame-manifest.jsonld#t0029$/, - /frame-manifest.jsonld#t0030$/, - /frame-manifest.jsonld#t0031$/, - /frame-manifest.jsonld#t0032$/, - /frame-manifest.jsonld#t0034$/, - /frame-manifest.jsonld#t0035$/, - /frame-manifest.jsonld#t0036$/, - /frame-manifest.jsonld#t0037$/, - /frame-manifest.jsonld#t0038$/, - /frame-manifest.jsonld#t0039$/, - /frame-manifest.jsonld#t0040$/, - /frame-manifest.jsonld#t0041$/, - /frame-manifest.jsonld#t0042$/, - /frame-manifest.jsonld#t0043$/, - /frame-manifest.jsonld#t0044$/, - /frame-manifest.jsonld#t0045$/, - /frame-manifest.jsonld#t0046$/, - /frame-manifest.jsonld#t0047$/, - /frame-manifest.jsonld#t0048$/, - /frame-manifest.jsonld#t0049$/, - /frame-manifest.jsonld#t0050$/, - /frame-manifest.jsonld#t0051$/, - /frame-manifest.jsonld#t0064$/, - /frame-manifest.jsonld#tg002$/, - /frame-manifest.jsonld#tg003$/, - /frame-manifest.jsonld#tg004$/, - /frame-manifest.jsonld#tg006$/, - /frame-manifest.jsonld#tg007$/, - /frame-manifest.jsonld#tg008$/, - /frame-manifest.jsonld#tg009$/, - /frame-manifest.jsonld#tg010$/, - /frame-manifest.jsonld#tp046$/, - /frame-manifest.jsonld#tp049$/, - // blank nodes - /frame-manifest.jsonld#t0052$/, - /frame-manifest.jsonld#t0053$/, - // embed - /frame-manifest.jsonld#t0054$/, // lists /frame-manifest.jsonld#t0055$/, /frame-manifest.jsonld#t0058$/, - // misc - /frame-manifest.jsonld#tp010$/, - /frame-manifest.jsonld#tp050$/, - /frame-manifest.jsonld#teo01$/, - /frame-manifest.jsonld#t0062$/, - /frame-manifest.jsonld#t0063$/, - // @embed:@first - /frame-manifest.jsonld#t0060$/, - // requireAll - /frame-manifest.jsonld#tra01$/, - /frame-manifest.jsonld#tra02$/, - /frame-manifest.jsonld#tra03$/, - // wildcard - /frame-manifest.jsonld#t0061$/, // included /frame-manifest.jsonld#tin01$/, /frame-manifest.jsonld#tin02$/, diff --git a/tests/test-karma.js b/tests/test-karma.js index c8f0923a..8f4b8ce1 100644 --- a/tests/test-karma.js +++ b/tests/test-karma.js @@ -35,6 +35,7 @@ require('core-js/fn/array/from'); require('core-js/fn/array/includes'); require('core-js/fn/map'); require('core-js/fn/object/assign'); +require('core-js/fn/object/entries'); require('core-js/fn/promise'); require('core-js/fn/set'); require('core-js/fn/symbol'); diff --git a/tests/test.js b/tests/test.js index 5214e28c..06bb336d 100644 --- a/tests/test.js +++ b/tests/test.js @@ -93,7 +93,6 @@ if(process.env.JSONLD_TESTS) { // other tests entries.push(path.resolve(_top, 'tests/misc.js')); entries.push(path.resolve(_top, 'tests/graph-container.js')); - entries.push(path.resolve(_top, 'tests/new-embed-api')); // TODO: avoid network traffic and re-enable //entries.push(path.resolve(_top, 'tests/node-document-loader-tests.js')); } diff --git a/webpack.config.js b/webpack.config.js index d70598ad..feda9b84 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,6 +22,7 @@ const outputs = [ 'core-js/fn/array/includes', 'core-js/fn/map', 'core-js/fn/object/assign', + 'core-js/fn/object/entries', 'core-js/fn/promise', 'core-js/fn/set', 'core-js/fn/string/starts-with',