@@ -29,6 +29,7 @@ api.frameMergedOrDefault = (input, frame, options) => {
29
29
// create framing state
30
30
const state = {
31
31
options,
32
+ embedded : false ,
32
33
graph : '@default' ,
33
34
graphMap : { '@default' : { } } ,
34
35
graphStack : [ ] ,
@@ -83,6 +84,12 @@ api.frame = (state, subjects, frame, parent, property = null) => {
83
84
requireAll : _getFrameFlag ( frame , options , 'requireAll' )
84
85
} ;
85
86
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
+
86
93
// filter out subjects that match the frame
87
94
const matches = _filterSubjects ( state , subjects , frame , flags ) ;
88
95
@@ -91,16 +98,6 @@ api.frame = (state, subjects, frame, parent, property = null) => {
91
98
for ( const id of ids ) {
92
99
const subject = matches [ id ] ;
93
100
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
-
104
101
/* Note: In order to treat each top-level match as a compartmentalized
105
102
result, clear the unique embedded subjects map when the property is null,
106
103
which only occurs at the top-level. */
@@ -110,27 +107,53 @@ api.frame = (state, subjects, frame, parent, property = null) => {
110
107
state . uniqueEmbeds [ state . graph ] = state . uniqueEmbeds [ state . graph ] || { } ;
111
108
}
112
109
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
+
113
120
// start output for subject
114
- const output = { } ;
115
- output [ '@id' ] = id ;
121
+ const output = { '@id' : id } ;
116
122
if ( id . indexOf ( '_:' ) === 0 ) {
117
123
util . addValue ( state . bnodeMap , id , output , { propertyIsArray : true } ) ;
118
124
}
119
- state . link [ id ] = output ;
125
+ link [ id ] = output ;
120
126
121
127
// validate @embed
122
128
if ( ( flags . embed === '@first' || flags . embed === '@last' ) && state . is11 ) {
123
129
throw new JsonLdError (
124
130
'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 ;
126
138
}
127
139
128
140
// if embed is @never or if a circular reference would be created by an
129
141
// embed, the subject cannot be embedded, just add the reference;
130
142
// note that a circular reference won't occur when the embed flag is
131
143
// `@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
+ {
134
157
_addFrameOutput ( parent , property , output ) ;
135
158
continue ;
136
159
}
@@ -141,43 +164,42 @@ api.frame = (state, subjects, frame, parent, property = null) => {
141
164
if ( id in state . uniqueEmbeds [ state . graph ] ) {
142
165
_removeEmbed ( state , id ) ;
143
166
}
144
- state . uniqueEmbeds [ state . graph ] [ id ] =
145
- { parent, property} ;
146
167
}
147
168
169
+ state . uniqueEmbeds [ state . graph ] [ id ] = { parent, property} ;
170
+
148
171
// push matching subject onto stack to enable circular embed checks
149
172
state . subjectStack . push ( { subject, graph : state . graph } ) ;
150
173
151
174
// subject is also the name of a graph
152
175
if ( id in state . graphMap ) {
153
176
let recurse = false ;
154
177
let subframe = null ;
155
- if ( ! ( '@graph' in frame ) ) {
178
+ if ( ! frame . hasOwnProperty ( '@graph' ) ) {
156
179
recurse = state . graph !== '@merged' ;
157
180
subframe = { } ;
158
181
} else {
159
182
subframe = frame [ '@graph' ] [ 0 ] ;
183
+ recurse = ! ( id === '@merged' || id === '@default' ) ;
160
184
if ( ! types . isObject ( subframe ) ) {
161
185
subframe = { } ;
162
186
}
163
- recurse = ! ( id === '@merged' || id === '@default' ) ;
164
187
}
165
188
166
189
if ( recurse ) {
167
190
state . graphStack . push ( state . graph ) ;
168
- state . graph = id ;
169
191
// recurse into graph
170
192
api . frame (
171
- state ,
193
+ Object . assign ( { } , state , { graph : id , embedded : false } ) ,
172
194
Object . keys ( state . graphMap [ id ] ) . sort ( ) , [ subframe ] , output , '@graph' ) ;
173
- state . graph = state . graphStack . pop ;
195
+ state . graphStack . pop ;
174
196
}
175
197
}
176
198
177
199
// if frame has @included , recurse over its sub-frame
178
200
if ( '@included' in frame ) {
179
201
api . frame (
180
- state ,
202
+ Object . assign ( { } , state , { embedded : false } ) ,
181
203
subjects , frame [ '@included' ] , output , '@included' ) ;
182
204
}
183
205
@@ -205,36 +227,37 @@ api.frame = (state, subjects, frame, parent, property = null) => {
205
227
}
206
228
207
229
// add objects
208
- for ( let o of subject [ prop ] ) {
230
+ for ( const o of subject [ prop ] ) {
209
231
const subframe = ( prop in frame ?
210
232
frame [ prop ] : _createImplicitFrame ( flags ) ) ;
211
233
212
234
// recurse into list
213
235
if ( graphTypes . isList ( o ) ) {
236
+ const subframe = ( frame [ prop ] && types . isObject ( frame [ prop ] [ 0 ] ) ?
237
+ frame [ prop ] [ 0 ] [ '@list' ] : _createImplicitFrame ( flags ) ) ;
238
+
214
239
// add empty list
215
240
const list = { '@list' : [ ] } ;
216
241
_addFrameOutput ( output , prop , list ) ;
217
242
218
243
// add list objects
219
244
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 ) ) {
225
247
// 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' ) ;
227
251
} else {
228
252
// include other values automatically
229
- _addFrameOutput ( list , '@list' , util . clone ( o ) ) ;
253
+ _addFrameOutput ( list , '@list' , util . clone ( oo ) ) ;
230
254
}
231
255
}
232
- continue ;
233
- }
234
-
235
- if ( graphTypes . isSubjectReference ( o ) ) {
256
+ } else if ( graphTypes . isSubjectReference ( o ) ) {
236
257
// 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 ) ;
238
261
} else if ( _valueMatch ( subframe [ 0 ] , o ) ) {
239
262
// include other values, if they match
240
263
_addFrameOutput ( output , prop , util . clone ( o ) ) ;
@@ -267,21 +290,20 @@ api.frame = (state, subjects, frame, parent, property = null) => {
267
290
268
291
// if embed reverse values by finding nodes having this subject as a value
269
292
// 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 ) ;
285
307
}
286
308
}
287
309
}
@@ -361,7 +383,7 @@ function _getFrameFlag(frame, options, name) {
361
383
{
362
384
throw new JsonLdError (
363
385
'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} ) ;
365
387
}
366
388
}
367
389
return rval ;
0 commit comments