Skip to content

Commit f6a91c0

Browse files
committed
Add support for @graph @container.
1 parent 7316fad commit f6a91c0

File tree

2 files changed

+241
-19
lines changed

2 files changed

+241
-19
lines changed

lib/jsonld.js

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2543,7 +2543,9 @@ Processor.prototype.compact = function(
25432543
var value = compactedValue[compactedProperty];
25442544
var container = jsonld.getContextValue(
25452545
activeCtx, compactedProperty, '@container');
2546-
var useArray = (container === '@set' || !options.compactArrays);
2546+
container = [].concat(container);
2547+
var useArray = (
2548+
container.includes('@set') || !options.compactArrays);
25472549
jsonld.addValue(
25482550
rval, compactedProperty, value, {propertyIsArray: useArray});
25492551
delete compactedValue[compactedProperty];
@@ -2604,17 +2606,22 @@ Processor.prototype.compact = function(
26042606
insideReverse);
26052607
var container = jsonld.getContextValue(
26062608
activeCtx, itemActiveProperty, '@container');
2609+
container = [].concat(container);
26072610

2608-
// get @list value if appropriate
2611+
// get simple @graph or @list value if appropriate
2612+
var isSimpleGraph = _isSimpleGraph(expandedItem);
26092613
var isList = _isList(expandedItem);
2610-
var list = null;
2614+
var inner;
26112615
if(isList) {
2612-
list = expandedItem['@list'];
2616+
inner = expandedItem['@list'];
2617+
} else if(isSimpleGraph) {
2618+
inner = expandedItem['@graph'];
26132619
}
26142620

26152621
// recursively compact expanded item
26162622
var compactedItem = this.compact(
2617-
activeCtx, itemActiveProperty, isList ? list : expandedItem, options);
2623+
activeCtx, itemActiveProperty,
2624+
(isList || isSimpleGraph) ? inner : expandedItem, options);
26182625

26192626
// handle @list
26202627
if(isList) {
@@ -2623,7 +2630,7 @@ Processor.prototype.compact = function(
26232630
compactedItem = [compactedItem];
26242631
}
26252632

2626-
if(container !== '@list') {
2633+
if(!container.includes('@list')) {
26272634
// wrap using @list alias
26282635
var wrapper = {};
26292636
wrapper[_compactIri(activeCtx, '@list')] = compactedItem;
@@ -2645,8 +2652,22 @@ Processor.prototype.compact = function(
26452652
}
26462653
}
26472654

2655+
// handle simple @graph
2656+
if(isSimpleGraph && !container.includes('@graph')) {
2657+
// wrap using @graph alias
2658+
var wrapper = {};
2659+
wrapper[_compactIri(activeCtx, '@graph')] = compactedItem;
2660+
compactedItem = wrapper;
2661+
2662+
// include @index from expanded @graph, if any
2663+
if('@index' in expandedItem) {
2664+
compactedItem[_compactIri(activeCtx, '@index')] =
2665+
expandedItem['@index'];
2666+
}
2667+
}
2668+
26482669
// handle language and index maps
2649-
if(container === '@language' || container === '@index') {
2670+
if(container.includes('@language') || container.includes('@index')) {
26502671
// get or create the map object
26512672
var mapObject;
26522673
if(itemActiveProperty in rval) {
@@ -2657,7 +2678,7 @@ Processor.prototype.compact = function(
26572678

26582679
// if container is a language map, simplify compacted value to
26592680
// a simple string
2660-
if(container === '@language' && _isValue(compactedItem)) {
2681+
if(container.includes('@language') && _isValue(compactedItem)) {
26612682
compactedItem = compactedItem['@value'];
26622683
}
26632684

@@ -2668,8 +2689,8 @@ Processor.prototype.compact = function(
26682689
// use an array if: compactArrays flag is false,
26692690
// @container is @set or @list , value is an empty
26702691
// array, or key is @graph
2671-
var isArray = (!options.compactArrays || container === '@set' ||
2672-
container === '@list' ||
2692+
var isArray = (!options.compactArrays || container.includes('@set') ||
2693+
container.includes('@list') ||
26732694
(_isArray(compactedItem) && compactedItem.length === 0) ||
26742695
expandedProperty === '@list' || expandedProperty === '@graph');
26752696

@@ -2907,11 +2928,12 @@ Processor.prototype.expand = function(
29072928
}
29082929

29092930
var container = jsonld.getContextValue(activeCtx, key, '@container');
2931+
container = [].concat(container);
29102932

2911-
if(container === '@language' && _isObject(value)) {
2933+
if(container.includes('@language') && _isObject(value)) {
29122934
// handle language map container (skip if value is not an object)
29132935
expandedValue = _expandLanguageMap(value);
2914-
} else if(container === '@index' && _isObject(value)) {
2936+
} else if(container.includes('@index') && _isObject(value)) {
29152937
// handle index container (skip if value is not an object)
29162938
expandedValue = (function _expandIndexMap(activeProperty) {
29172939
var rval = [];
@@ -2961,13 +2983,20 @@ Processor.prototype.expand = function(
29612983

29622984
// convert expanded value to @list if container specifies it
29632985
if(expandedProperty !== '@list' && !_isList(expandedValue) &&
2964-
container === '@list') {
2986+
container.includes('@list')) {
29652987
// ensure expanded value is an array
29662988
expandedValue = (_isArray(expandedValue) ?
29672989
expandedValue : [expandedValue]);
29682990
expandedValue = {'@list': expandedValue};
29692991
}
29702992

2993+
// convert expanded value to @graph if container specifies it
2994+
if(container.includes('@graph')) {
2995+
// ensure expanded value is an array
2996+
expandedValue = [].concat(expandedValue);
2997+
expandedValue = {'@graph': expandedValue};
2998+
}
2999+
29713000
// FIXME: can this be merged with code above to simplify?
29723001
// merge in reverse properties
29733002
if(activeCtx.mappings[key] && activeCtx.mappings[key].reverse) {
@@ -3439,6 +3468,18 @@ Processor.prototype.processContext = function(activeCtx, localCtx, options) {
34393468
// define context mappings for keys in local context
34403469
var defined = {};
34413470

3471+
// handle @version
3472+
if('@version' in ctx) {
3473+
if(ctx['@version'] !== 1.1) {
3474+
throw new JsonLdError(
3475+
'Unsupported JSON-LD version: ' + ctx['@version'],
3476+
'jsonld.UnsupportedVersion',
3477+
{code: 'unsupported version', context: ctx});
3478+
}
3479+
rval['@version'] = ctx['@version'];
3480+
defined['@version'] = true;
3481+
}
3482+
34423483
// handle @base
34433484
if('@base' in ctx) {
34443485
var base = ctx['@base'];
@@ -4710,6 +4751,13 @@ function _compactIri(activeCtx, iri, value, relativeTo, reverse) {
47104751
containers.push('@index');
47114752
}
47124753

4754+
// prefer `['@graph', '@set']` and then `@graph` if value is a simple graph
4755+
// TODO: support `@graphId`?
4756+
if(_isSimpleGraph(value)) {
4757+
containers.push('@graph@set');
4758+
containers.push('@graph');
4759+
}
4760+
47134761
// defaults for term selection based on type/language
47144762
var typeOrLanguage = '@language';
47154763
var typeOrLanguageValue = '@null';
@@ -5004,7 +5052,7 @@ function _createTermDefinition(activeCtx, localCtx, term, defined) {
50045052

50055053
if(!_isObject(value)) {
50065054
throw new JsonLdError(
5007-
'Invalid JSON-LD syntax; @context property values must be ' +
5055+
'Invalid JSON-LD syntax; @context term values must be ' +
50085056
'strings or objects.',
50095057
'jsonld.SyntaxError',
50105058
{code: 'invalid term definition', context: localCtx});
@@ -5137,11 +5185,25 @@ function _createTermDefinition(activeCtx, localCtx, term, defined) {
51375185
var container = value['@container'];
51385186
if(container !== '@list' && container !== '@set' &&
51395187
container !== '@index' && container !== '@language') {
5140-
throw new JsonLdError(
5141-
'Invalid JSON-LD syntax; @context @container value must be ' +
5142-
'one of the following: @list, @set, @index, or @language.',
5143-
'jsonld.SyntaxError',
5144-
{code: 'invalid container mapping', context: localCtx});
5188+
let isValid = false;
5189+
const validContainers = ['@list', '@set', '@index', '@language'];
5190+
// JSON-LD 1.1 support
5191+
if(activeCtx['@version'] === 1.1) {
5192+
// || processingMode === 'jsonld-1.1') {
5193+
validContainers.push('@graph');
5194+
if(container === '@graph' ||
5195+
(_isArray(container) && container.length === 2 &&
5196+
container.includes('@graph') && container.includes('@set'))) {
5197+
isValid = true;
5198+
}
5199+
}
5200+
if(!isValid) {
5201+
throw new JsonLdError(
5202+
'Invalid JSON-LD syntax; @context @container value must be ' +
5203+
'one of the following: ' + validContainers.join(', '),
5204+
'jsonld.SyntaxError',
5205+
{code: 'invalid container mapping', context: localCtx});
5206+
}
51455207
}
51465208
if(mapping.reverse && container !== '@index' && container !== '@set' &&
51475209
container !== null) {
@@ -5441,6 +5503,7 @@ function _getInitialContext(options) {
54415503
var base = jsonld.url.parse(options.base || '');
54425504
return {
54435505
'@base': base,
5506+
'@version': 1.0,
54445507
mappings: {},
54455508
inverse: null,
54465509
getInverse: _createInverseContext,
@@ -5481,6 +5544,7 @@ function _getInitialContext(options) {
54815544
}
54825545

54835546
var container = mapping['@container'] || '@none';
5547+
container = [].concat(container).sort().join('');
54845548

54855549
// iterate over every IRI in the mapping
54865550
var ids = mapping['@id'];
@@ -5653,6 +5717,7 @@ function _isKeyword(v) {
56535717
case '@set':
56545718
case '@type':
56555719
case '@value':
5720+
case '@version':
56565721
case '@vocab':
56575722
return true;
56585723
}
@@ -5856,6 +5921,24 @@ function _isList(v) {
58565921
return _isObject(v) && ('@list' in v);
58575922
}
58585923

5924+
/**
5925+
* Returns true if the given value is a simple @graph.
5926+
*
5927+
* @return true if the value is a simple @graph, false if not.
5928+
*/
5929+
function _isSimpleGraph(v) {
5930+
// Note: A value is a simple graph if all of these hold true:
5931+
// 1. It is an object.
5932+
// 2. It has an `@graph` key.
5933+
// 3. It has only 1 key or 2 keys where one of them is `@index`.
5934+
if(!_isObject(v)) {
5935+
return false;
5936+
}
5937+
const keyLength = Object.keys(v).length;
5938+
return ('@graph' in v &&
5939+
(keyLength === 1 || (keyLength === 2 && '@index' in v)));
5940+
}
5941+
58595942
/**
58605943
* Returns true if the given value is a blank node.
58615944
*

test/graph-container.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Temporary graph-container tests.
3+
*/
4+
const jsonld = require('..');
5+
const assert = require('assert');
6+
7+
describe('@graph container', () => {
8+
it('should expand @graph container', done => {
9+
const doc = {
10+
'@context': {
11+
'@version': 1.1,
12+
'input': {'@id': 'foo:input', '@container': '@graph'},
13+
'value': 'foo:value'
14+
},
15+
input: {
16+
value: 'x'
17+
}
18+
};
19+
jsonld.expand(doc, (err, expanded) => {
20+
assert.ifError(err);
21+
assert.deepEqual(expanded, [{
22+
"foo:input": [{
23+
"@graph": [{
24+
"foo:value": [{
25+
"@value": "x"
26+
}]
27+
}]
28+
}]
29+
}]);
30+
done();
31+
});
32+
});
33+
34+
it('should expand ["@graph", "@set"] container', done => {
35+
const doc = {
36+
'@context': {
37+
'@version': 1.1,
38+
'input': {'@id': 'foo:input', '@container': ['@graph', '@set']},
39+
'value': 'foo:value'
40+
},
41+
input: [{
42+
value: 'x'
43+
}]
44+
};
45+
jsonld.expand(doc, (err, expanded) => {
46+
assert.ifError(err);
47+
assert.deepEqual(expanded, [{
48+
"foo:input": [{
49+
"@graph": [{
50+
"foo:value": [{
51+
"@value": "x"
52+
}]
53+
}]
54+
}]
55+
}]);
56+
done();
57+
});
58+
});
59+
60+
it('should expand and then compact @graph container', done => {
61+
const doc = {
62+
'@context': {
63+
'@version': 1.1,
64+
'input': {'@id': 'foo:input', '@container': '@graph'},
65+
'value': 'foo:value'
66+
},
67+
input: {
68+
value: 'x'
69+
}
70+
};
71+
jsonld.expand(doc, (err, expanded) => {
72+
assert.ifError(err);
73+
74+
jsonld.compact(expanded, doc['@context'], (err, compacted) => {
75+
assert.ifError(err);
76+
assert.deepEqual(compacted, {
77+
"@context": {
78+
"@version": 1.1,
79+
"input": {
80+
"@id": "foo:input",
81+
"@container": "@graph"
82+
},
83+
"value": "foo:value"
84+
},
85+
"input": {
86+
"value": "x"
87+
}
88+
});
89+
done();
90+
});
91+
});
92+
});
93+
94+
it('should expand and then compact @graph container into a @set', done => {
95+
const doc = {
96+
'@context': {
97+
'@version': 1.1,
98+
'input': {'@id': 'foo:input', '@container': '@graph'},
99+
'value': 'foo:value'
100+
},
101+
input: {
102+
value: 'x'
103+
}
104+
};
105+
const newContext = {
106+
'@context': {
107+
'@version': 1.1,
108+
'input': {'@id': 'foo:input', '@container': ['@graph', '@set']},
109+
'value': 'foo:value'
110+
}
111+
};
112+
jsonld.expand(doc, (err, expanded) => {
113+
assert.ifError(err);
114+
115+
jsonld.compact(expanded, newContext, (err, compacted) => {
116+
assert.ifError(err);
117+
assert.deepEqual(compacted, {
118+
"@context": {
119+
"@version": 1.1,
120+
"input": {
121+
"@id": "foo:input",
122+
"@container": [
123+
"@graph",
124+
"@set"
125+
]
126+
},
127+
"value": "foo:value"
128+
},
129+
"input": [
130+
{
131+
"value": "x"
132+
}
133+
]
134+
});
135+
done();
136+
});
137+
});
138+
});
139+
});

0 commit comments

Comments
 (0)