diff --git a/CHANGELOG.md b/CHANGELOG.md index e8024753..01f734a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ ### Changed - Default processing mode changed to json-ld-1.1. Allows a 1.1 context to be used after non-1.1 contexts. +- `@vocab` can be relative or a Compact IRI in 1.1, resolved against either a previous `@vocab`, + `@base` or document base. +- Better checking of absolute IRIs. +- Terms that begin with a ':' are not considered absolute or compact IRIs. +- Don't use terms with `"@prefix": false` or expanded term definitions to construct compact IRIs. ### Removed - **BREAKING**: Remove callback API support. This includes removing support diff --git a/lib/compact.js b/lib/compact.js index 67aea23f..8206d618 100644 --- a/lib/compact.js +++ b/lib/compact.js @@ -824,6 +824,17 @@ api.compactIri = ({ return choice; } + // If iri could be confused with a compact IRI using a term in this context, + // signal an error + for(const [term, td] of activeCtx.mappings) { + if(td && td._prefix && iri.startsWith(term + ':')) { + throw new JsonLdError( + `Absolute IRI "${iri}" confused with prefix "${term}".`, + 'jsonld.SyntaxError', + {code: 'IRI confused with prefix', context: activeCtx}); + } + } + // compact IRI relative to base if(!relativeTo.vocab) { return _removeBase(activeCtx['@base'], iri); diff --git a/lib/context.js b/lib/context.js index 262973b6..e151fb47 100644 --- a/lib/context.js +++ b/lib/context.js @@ -194,7 +194,7 @@ api.process = async ({ // if not set explicitly, set processingMode to "json-ld-1.1" rval.processingMode = - rval.processingMode || activeCtx.processingMode || 'json-ld-1.1'; + rval.processingMode || activeCtx.processingMode; // handle @base if('@base' in ctx) { @@ -205,7 +205,7 @@ api.process = async ({ } else if(_isAbsoluteIri(base)) { base = parseUrl(base); } else if(_isRelativeIri(base)) { - base = parseUrl(prependBase(activeCtx['@base'].href, base)); + base = parseUrl(prependBase(rval['@base'].href, base)); } else { throw new JsonLdError( 'Invalid JSON-LD syntax; the value of "@base" in a ' + @@ -227,13 +227,14 @@ api.process = async ({ 'Invalid JSON-LD syntax; the value of "@vocab" in a ' + '@context must be a string or null.', 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx}); - } else if(!_isAbsoluteIri(value)) { + } else if(!_isAbsoluteIri(value) && api.processingMode(rval, 1.0)) { throw new JsonLdError( 'Invalid JSON-LD syntax; the value of "@vocab" in a ' + '@context must be an absolute IRI.', 'jsonld.SyntaxError', {code: 'invalid vocab mapping', context: ctx}); } else { - rval['@vocab'] = value; + rval['@vocab'] = _expandIri(rval, value, {vocab: true, base: true}, + undefined, undefined, options); } defined.set('@vocab', true); } @@ -389,7 +390,7 @@ api.createTermDefinition = ( // always compute whether term has a colon as an optimization for // _compactIri const colon = term.indexOf(':'); - mapping._termHasColon = (colon !== -1); + mapping._termHasColon = (colon > 0); if('@reverse' in value) { if('@id' in value) { @@ -444,9 +445,9 @@ api.createTermDefinition = ( } mapping['@id'] = id; // indicate if this term may be used as a compact IRI prefix - mapping._prefix = (!mapping._termHasColon && - id.match(/[:\/\?#\[\]@]$/) && - (simpleTerm || api.processingMode(activeCtx, 1.0))); + mapping._prefix = (simpleTerm && + !mapping._termHasColon && + id.match(/[:\/\?#\[\]@]$/)); } } @@ -753,7 +754,7 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { // split value into prefix:suffix const colon = value.indexOf(':'); - if(colon !== -1) { + if(colon > 0) { const prefix = value.substr(0, colon); const suffix = value.substr(colon + 1); @@ -769,13 +770,15 @@ function _expandIri(activeCtx, value, relativeTo, localCtx, defined, options) { } // use mapping if prefix is defined - if(activeCtx.mappings.has(prefix)) { - const mapping = activeCtx.mappings.get(prefix); + const mapping = activeCtx.mappings.get(prefix); + if(mapping && mapping._prefix) { return mapping['@id'] + suffix; } // already absolute IRI - return value; + if(_isAbsoluteIri(value)) { + return value; + } } // prepend vocab @@ -1086,11 +1089,10 @@ api.getAllContexts = async (input, options) => { */ api.processingMode = (activeCtx, version) => { if(version.toString() >= '1.1') { - return activeCtx.processingMode && + return !activeCtx.processingMode || activeCtx.processingMode >= 'json-ld-' + version.toString(); } else { - return !activeCtx.processingMode || - activeCtx.processingMode === 'json-ld-1.0'; + return activeCtx.processingMode === 'json-ld-1.0'; } }; diff --git a/lib/url.js b/lib/url.js index 5d96e4ff..239e7a5e 100644 --- a/lib/url.js +++ b/lib/url.js @@ -66,7 +66,7 @@ api.prependBase = (base, iri) => { return iri; } // already an absolute IRI - if(iri.indexOf(':') !== -1) { + if(api.isAbsolute(iri)) { return iri; } diff --git a/tests/test-common.js b/tests/test-common.js index f965a787..729a8135 100644 --- a/tests/test-common.js +++ b/tests/test-common.js @@ -32,15 +32,9 @@ const TEST_TYPES = { specVersion: ['json-ld-1.0'], // FIXME idRegex: [ - // terms - /compact-manifest.jsonld#tp001$/, - // rel iri - /compact-manifest.jsonld#t0095$/, // type set /compact-manifest.jsonld#t0104$/, /compact-manifest.jsonld#t0105$/, - // rel vocab - /compact-manifest.jsonld#t0107$/, // @type: @none /compact-manifest.jsonld#ttn01$/, /compact-manifest.jsonld#ttn02$/, @@ -52,8 +46,6 @@ const TEST_TYPES = { /compact-manifest.jsonld#tpi04$/, /compact-manifest.jsonld#tpi05$/, /compact-manifest.jsonld#tpi06$/, - // IRI confusion - /compact-manifest.jsonld#te002$/, // @propogate /compact-manifest.jsonld#tc026$/, /compact-manifest.jsonld#tc027$/, @@ -117,17 +109,6 @@ const TEST_TYPES = { /expand-manifest.jsonld#t0102$/, // multiple graphs /expand-manifest.jsonld#t0103$/, - // rel iri - /expand-manifest.jsonld#t0092$/, - // iris - /expand-manifest.jsonld#t0109$/, - // rel vocab - /expand-manifest.jsonld#t0110$/, - /expand-manifest.jsonld#t0111$/, - /expand-manifest.jsonld#t0112$/, - // terms beginning with ':' - /expand-manifest.jsonld#t0117$/, - /expand-manifest.jsonld#t0118$/, // terms having form of keyword /expand-manifest.jsonld#t0119$/, /expand-manifest.jsonld#t0120$/, @@ -180,8 +161,6 @@ const TEST_TYPES = { /expand-manifest.jsonld#te049$/, // invalid keyword alias /expand-manifest.jsonld#te051$/, - // IRI prefixes - /expand-manifest.jsonld#tpr29$/, // protected null IRI mapping /expand-manifest.jsonld#tpr28$/, // remote @@ -214,9 +193,6 @@ const TEST_TYPES = { /expand-manifest.jsonld#tso11$/, // colliding keywords /expand-manifest.jsonld#t0114$/, - // vocab iri/term - /expand-manifest.jsonld#te046$/, - /expand-manifest.jsonld#te047$/, // included /expand-manifest.jsonld#tin01$/, /expand-manifest.jsonld#tin02$/, @@ -416,9 +392,6 @@ const TEST_TYPES = { idRegex: [ // blank node properties /toRdf-manifest.jsonld#t0118$/, - // terms beginning with ':' - /toRdf-manifest.jsonld#te117$/, - /toRdf-manifest.jsonld#te118$/, // terms having form of keyword /toRdf-manifest.jsonld#te119$/, /toRdf-manifest.jsonld#te120$/, @@ -460,10 +433,8 @@ const TEST_TYPES = { /toRdf-manifest.jsonld#t0132$/, // @vocab mapping /toRdf-manifest.jsonld#te075$/, - // rel IRI - /toRdf-manifest.jsonld#te092$/, /toRdf-manifest.jsonld#te109$/, - /toRdf-manifest.jsonld#te110$/, + // Invalid Statement /toRdf-manifest.jsonld#te111$/, /toRdf-manifest.jsonld#te112$/, // index maps