Skip to content

Commit a121a8e

Browse files
gkelloggdavidlehn
authored andcommitted
Fixes for object embedding and graph framing.
- Changes in object embedding. - Better support for graph framing.
1 parent ed79ad7 commit a121a8e

File tree

3 files changed

+80
-95
lines changed

3 files changed

+80
-95
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
- Keywords may not be used as prefixes.
1717
- Handle term definition on `@type` with empty map.
1818
- Handling of `@` values for `@reverse`.
19+
- Changes in object embedding.
20+
- Better support for graph framing.
1921

2022
### Changed
2123
- Keep term definitions mapping to null so they may be protected.
@@ -34,7 +36,7 @@
3436
- Support for expansion and compaction of values container `"@direction"`.
3537
- Support for RDF transformation of `@direction` when `rdfDirection` is
3638
'i18n-datatype'.
37-
- Top level `@graph` omitted if `omitGraph` is `true`.
39+
- Top level `@graph` omitted if `omitGraph` is `true`.
3840
- Check for invalid values of `@embed`.
3941

4042
## 2.0.2 - 2020-01-17

lib/frame.js

Lines changed: 75 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ api.frameMergedOrDefault = (input, frame, options) => {
2929
// create framing state
3030
const state = {
3131
options,
32+
embedded: false,
3233
graph: '@default',
3334
graphMap: {'@default': {}},
3435
graphStack: [],
@@ -83,6 +84,12 @@ api.frame = (state, subjects, frame, parent, property = null) => {
8384
requireAll: _getFrameFlag(frame, options, 'requireAll')
8485
};
8586

87+
// get link for current graph
88+
if(!state.link.hasOwnProperty(state.graph)) {
89+
state.link[state.graph] = {};
90+
}
91+
const link = state.link[state.graph];
92+
8693
// filter out subjects that match the frame
8794
const matches = _filterSubjects(state, subjects, frame, flags);
8895

@@ -91,16 +98,6 @@ api.frame = (state, subjects, frame, parent, property = null) => {
9198
for(const id of ids) {
9299
const subject = matches[id];
93100

94-
if(flags.embed === '@link' && id in state.link) {
95-
// TODO: may want to also match an existing linked subject against
96-
// the current frame ... so different frames could produce different
97-
// subjects that are only shared in-memory when the frames are the same
98-
99-
// add existing linked subject
100-
_addFrameOutput(parent, property, state.link[id]);
101-
continue;
102-
}
103-
104101
/* Note: In order to treat each top-level match as a compartmentalized
105102
result, clear the unique embedded subjects map when the property is null,
106103
which only occurs at the top-level. */
@@ -110,27 +107,53 @@ api.frame = (state, subjects, frame, parent, property = null) => {
110107
state.uniqueEmbeds[state.graph] = state.uniqueEmbeds[state.graph] || {};
111108
}
112109

110+
if(flags.embed === '@link' && id in link) {
111+
// TODO: may want to also match an existing linked subject against
112+
// the current frame ... so different frames could produce different
113+
// subjects that are only shared in-memory when the frames are the same
114+
115+
// add existing linked subject
116+
_addFrameOutput(parent, property, link[id]);
117+
continue;
118+
}
119+
113120
// start output for subject
114-
const output = {};
115-
output['@id'] = id;
121+
const output = {'@id': id};
116122
if(id.indexOf('_:') === 0) {
117123
util.addValue(state.bnodeMap, id, output, {propertyIsArray: true});
118124
}
119-
state.link[id] = output;
125+
link[id] = output;
120126

121127
// validate @embed
122128
if((flags.embed === '@first' || flags.embed === '@last') && state.is11) {
123129
throw new JsonLdError(
124130
'Invalid JSON-LD syntax; invalid value of @embed.',
125-
'jsonld.SyntaxError', {code: 'invalid @embed value', frame: frame});
131+
'jsonld.SyntaxError', {code: 'invalid @embed value', frame});
132+
}
133+
134+
if(!state.embedded && state.uniqueEmbeds[state.graph].hasOwnProperty(id)) {
135+
// skip adding this node object to the top level, as it was
136+
// already included in another node object
137+
continue;
126138
}
127139

128140
// if embed is @never or if a circular reference would be created by an
129141
// embed, the subject cannot be embedded, just add the reference;
130142
// note that a circular reference won't occur when the embed flag is
131143
// `@link` as the above check will short-circuit before reaching this point
132-
if(flags.embed === '@never' ||
133-
_createsCircularReference(subject, state.graph, state.subjectStack)) {
144+
if(state.embedded &&
145+
(flags.embed === '@never' ||
146+
_createsCircularReference(subject, state.graph, state.subjectStack)))
147+
{
148+
_addFrameOutput(parent, property, output);
149+
continue;
150+
}
151+
152+
// if only the first (or once) should be embedded
153+
if(state.embedded &&
154+
(flags.embed == '@first' || flags.embed == '@once') &&
155+
state.uniqueEmbeds[state.graph].hasOwnProperty(id))
156+
{
134157
_addFrameOutput(parent, property, output);
135158
continue;
136159
}
@@ -141,43 +164,42 @@ api.frame = (state, subjects, frame, parent, property = null) => {
141164
if(id in state.uniqueEmbeds[state.graph]) {
142165
_removeEmbed(state, id);
143166
}
144-
state.uniqueEmbeds[state.graph][id] =
145-
{parent, property};
146167
}
147168

169+
state.uniqueEmbeds[state.graph][id] = {parent, property};
170+
148171
// push matching subject onto stack to enable circular embed checks
149172
state.subjectStack.push({subject, graph: state.graph});
150173

151174
// subject is also the name of a graph
152175
if(id in state.graphMap) {
153176
let recurse = false;
154177
let subframe = null;
155-
if(!('@graph' in frame)) {
178+
if(!frame.hasOwnProperty('@graph')) {
156179
recurse = state.graph !== '@merged';
157180
subframe = {};
158181
} else {
159182
subframe = frame['@graph'][0];
183+
recurse = !(id === '@merged' || id === '@default');
160184
if(!types.isObject(subframe)) {
161185
subframe = {};
162186
}
163-
recurse = !(id === '@merged' || id === '@default');
164187
}
165188

166189
if(recurse) {
167190
state.graphStack.push(state.graph);
168-
state.graph = id;
169191
// recurse into graph
170192
api.frame(
171-
state,
193+
Object.assign({}, state, {graph: id, embedded: false}),
172194
Object.keys(state.graphMap[id]).sort(), [subframe], output, '@graph');
173-
state.graph = state.graphStack.pop;
195+
state.graphStack.pop;
174196
}
175197
}
176198

177199
// if frame has @included, recurse over its sub-frame
178200
if('@included' in frame) {
179201
api.frame(
180-
state,
202+
Object.assign({}, state, {embedded: false}),
181203
subjects, frame['@included'], output, '@included');
182204
}
183205

@@ -205,36 +227,37 @@ api.frame = (state, subjects, frame, parent, property = null) => {
205227
}
206228

207229
// add objects
208-
for(let o of subject[prop]) {
230+
for(const o of subject[prop]) {
209231
const subframe = (prop in frame ?
210232
frame[prop] : _createImplicitFrame(flags));
211233

212234
// recurse into list
213235
if(graphTypes.isList(o)) {
236+
const subframe = (frame[prop] && types.isObject(frame[prop][0]) ?
237+
frame[prop][0]['@list'] : _createImplicitFrame(flags));
238+
214239
// add empty list
215240
const list = {'@list': []};
216241
_addFrameOutput(output, prop, list);
217242

218243
// add list objects
219244
const src = o['@list'];
220-
for(const n in src) {
221-
o = src[n];
222-
if(graphTypes.isSubjectReference(o)) {
223-
const subframe = (prop in frame ?
224-
frame[prop][0]['@list'] : _createImplicitFrame(flags));
245+
for(const oo of src) {
246+
if(graphTypes.isSubjectReference(oo)) {
225247
// recurse into subject reference
226-
api.frame(state, [o['@id']], subframe, list, '@list');
248+
api.frame(
249+
Object.assign({}, state, {embedded: true}),
250+
[oo['@id']], subframe, list, '@list');
227251
} else {
228252
// include other values automatically
229-
_addFrameOutput(list, '@list', util.clone(o));
253+
_addFrameOutput(list, '@list', util.clone(oo));
230254
}
231255
}
232-
continue;
233-
}
234-
235-
if(graphTypes.isSubjectReference(o)) {
256+
} else if(graphTypes.isSubjectReference(o)) {
236257
// recurse into subject reference
237-
api.frame(state, [o['@id']], subframe, output, prop);
258+
api.frame(
259+
Object.assign({}, state, {embedded: true}),
260+
[o['@id']], subframe, output, prop);
238261
} else if(_valueMatch(subframe[0], o)) {
239262
// include other values, if they match
240263
_addFrameOutput(output, prop, util.clone(o));
@@ -267,21 +290,20 @@ api.frame = (state, subjects, frame, parent, property = null) => {
267290

268291
// if embed reverse values by finding nodes having this subject as a value
269292
// of the associated property
270-
if('@reverse' in frame) {
271-
for(const reverseProp of Object.keys(frame['@reverse']).sort()) {
272-
const subframe = frame['@reverse'][reverseProp];
273-
for(const subject of Object.keys(state.subjects)) {
274-
const nodeValues =
275-
util.getValues(state.subjects[subject], reverseProp);
276-
if(nodeValues.some(v => v['@id'] === id)) {
277-
// node has property referencing this subject, recurse
278-
output['@reverse'] = output['@reverse'] || {};
279-
util.addValue(
280-
output['@reverse'], reverseProp, [], {propertyIsArray: true});
281-
api.frame(
282-
state, [subject], subframe, output['@reverse'][reverseProp],
283-
property);
284-
}
293+
for(const reverseProp of Object.keys(frame['@reverse'] || {}).sort()) {
294+
const subframe = frame['@reverse'][reverseProp];
295+
for(const subject of Object.keys(state.subjects)) {
296+
const nodeValues =
297+
util.getValues(state.subjects[subject], reverseProp);
298+
if(nodeValues.some(v => v['@id'] === id)) {
299+
// node has property referencing this subject, recurse
300+
output['@reverse'] = output['@reverse'] || {};
301+
util.addValue(
302+
output['@reverse'], reverseProp, [], {propertyIsArray: true});
303+
api.frame(
304+
Object.assign({}, state, {embedded: true}),
305+
[subject], subframe, output['@reverse'][reverseProp],
306+
property);
285307
}
286308
}
287309
}
@@ -361,7 +383,7 @@ function _getFrameFlag(frame, options, name) {
361383
{
362384
throw new JsonLdError(
363385
'Invalid JSON-LD syntax; invalid value of @embed.',
364-
'jsonld.SyntaxError', {code: 'invalid @embed value', frame: frame});
386+
'jsonld.SyntaxError', {code: 'invalid @embed value', frame});
365387
}
366388
}
367389
return rval;

tests/test-common.js

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -126,47 +126,10 @@ const TEST_TYPES = {
126126
specVersion: ['json-ld-1.0'],
127127
// FIXME
128128
idRegex: [
129-
// graphs
130-
/frame-manifest.jsonld#t0011$/,
131-
/frame-manifest.jsonld#t0010$/,
132-
/frame-manifest.jsonld#t0020$/,
133-
/frame-manifest.jsonld#t0023$/,
134-
/frame-manifest.jsonld#t0026$/,
135-
/frame-manifest.jsonld#t0027$/,
136-
/frame-manifest.jsonld#t0028$/,
137-
/frame-manifest.jsonld#t0029$/,
138-
/frame-manifest.jsonld#t0030$/,
139-
/frame-manifest.jsonld#t0031$/,
140-
/frame-manifest.jsonld#t0032$/,
141-
/frame-manifest.jsonld#t0034$/,
142-
/frame-manifest.jsonld#t0035$/,
143-
/frame-manifest.jsonld#t0036$/,
144-
/frame-manifest.jsonld#t0037$/,
145-
/frame-manifest.jsonld#t0038$/,
146-
/frame-manifest.jsonld#t0039$/,
147-
/frame-manifest.jsonld#t0040$/,
148-
/frame-manifest.jsonld#t0041$/,
149-
/frame-manifest.jsonld#t0042$/,
150-
/frame-manifest.jsonld#t0043$/,
151-
/frame-manifest.jsonld#t0044$/,
152-
/frame-manifest.jsonld#t0045$/,
153-
/frame-manifest.jsonld#t0046$/,
154-
/frame-manifest.jsonld#t0047$/,
155-
/frame-manifest.jsonld#t0048$/,
156-
/frame-manifest.jsonld#t0049$/,
157-
/frame-manifest.jsonld#t0050$/,
158-
/frame-manifest.jsonld#t0051$/,
129+
// default value for @type
159130
/frame-manifest.jsonld#t0064$/,
160-
/frame-manifest.jsonld#tg002$/,
161-
/frame-manifest.jsonld#tg003$/,
162-
/frame-manifest.jsonld#tg004$/,
163-
/frame-manifest.jsonld#tg006$/,
164-
/frame-manifest.jsonld#tg007$/,
165-
/frame-manifest.jsonld#tg008$/,
166-
/frame-manifest.jsonld#tg009$/,
131+
// @container: @graph
167132
/frame-manifest.jsonld#tg010$/,
168-
/frame-manifest.jsonld#tp046$/,
169-
/frame-manifest.jsonld#tp049$/,
170133
// blank nodes
171134
/frame-manifest.jsonld#t0052$/,
172135
/frame-manifest.jsonld#t0053$/,
@@ -175,8 +138,6 @@ const TEST_TYPES = {
175138
/frame-manifest.jsonld#t0058$/,
176139
// @preserve and @container: @set
177140
/frame-manifest.jsonld#t0062$/,
178-
// @embed:@first
179-
/frame-manifest.jsonld#t0060$/,
180141
// requireAll
181142
/frame-manifest.jsonld#tra01$/,
182143
/frame-manifest.jsonld#tra02$/,

0 commit comments

Comments
 (0)