Implement new experimental embed API.
- See: https://github.com/json-ld/json-ld.org/issues/377
This commit is contained in:
parent
237a405175
commit
7be06f2ee2
439
jsonld.php
439
jsonld.php
|
@ -105,6 +105,31 @@ function jsonld_frame($input, $frame, $options=array()) {
|
||||||
return $p->frame($input, $frame, $options);
|
return $p->frame($input, $frame, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **Experimental**
|
||||||
|
*
|
||||||
|
* Links a JSON-LD document's nodes in memory.
|
||||||
|
*
|
||||||
|
* @param mixed $input the JSON-LD document to link.
|
||||||
|
* @param mixed $ctx the JSON-LD context to apply or null.
|
||||||
|
* @param assoc [$options] the options to use:
|
||||||
|
* [base] the base IRI to use.
|
||||||
|
* [expandContext] a context to expand with.
|
||||||
|
* [documentLoader(url)] the document loader.
|
||||||
|
*
|
||||||
|
* @return the linked JSON-LD output.
|
||||||
|
*/
|
||||||
|
function jsonld_link($input, $ctx, $options) {
|
||||||
|
// API matches running frame with a wildcard frame and embed: '@link'
|
||||||
|
// get arguments
|
||||||
|
$frame = new stdClass();
|
||||||
|
if($ctx) {
|
||||||
|
$frame->{'@context'} = $ctx;
|
||||||
|
}
|
||||||
|
$frame->{'@embed'} = '@link';
|
||||||
|
return jsonld_frame($input, $frame, $options);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs RDF dataset normalization on the given JSON-LD input. The output
|
* Performs RDF dataset normalization on the given JSON-LD input. The output
|
||||||
* is an RDF dataset unless the 'format' option is used.
|
* is an RDF dataset unless the 'format' option is used.
|
||||||
|
@ -827,7 +852,13 @@ class JsonLdProcessor {
|
||||||
'graph' => false,
|
'graph' => false,
|
||||||
'skipExpansion' => false,
|
'skipExpansion' => false,
|
||||||
'activeCtx' => false,
|
'activeCtx' => false,
|
||||||
'documentLoader' => $jsonld_default_load_document));
|
'documentLoader' => $jsonld_default_load_document,
|
||||||
|
'link' => false));
|
||||||
|
if($options['link']) {
|
||||||
|
// force skip expansion when linking, "link" is not part of the
|
||||||
|
// public API, it should only be called from framing
|
||||||
|
$options['skipExpansion'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if($options['skipExpansion'] === true) {
|
if($options['skipExpansion'] === true) {
|
||||||
$expanded = $input;
|
$expanded = $input;
|
||||||
|
@ -1080,7 +1111,8 @@ class JsonLdProcessor {
|
||||||
* @param $options the framing options.
|
* @param $options the framing options.
|
||||||
* [base] the base IRI to use.
|
* [base] the base IRI to use.
|
||||||
* [expandContext] a context to expand with.
|
* [expandContext] a context to expand with.
|
||||||
* [embed] default @embed flag (default: true).
|
* [embed] default @embed flag: '@last', '@always', '@never', '@link'
|
||||||
|
* (default: '@last').
|
||||||
* [explicit] default @explicit flag (default: false).
|
* [explicit] default @explicit flag (default: false).
|
||||||
* [requireAll] default @requireAll flag (default: true).
|
* [requireAll] default @requireAll flag (default: true).
|
||||||
* [omitDefault] default @omitDefault flag (default: false).
|
* [omitDefault] default @omitDefault flag (default: false).
|
||||||
|
@ -1093,7 +1125,7 @@ class JsonLdProcessor {
|
||||||
self::setdefaults($options, array(
|
self::setdefaults($options, array(
|
||||||
'base' => is_string($input) ? $input : '',
|
'base' => is_string($input) ? $input : '',
|
||||||
'compactArrays' => true,
|
'compactArrays' => true,
|
||||||
'embed' => true,
|
'embed' => '@last',
|
||||||
'explicit' => false,
|
'explicit' => false,
|
||||||
'requireAll' => true,
|
'requireAll' => true,
|
||||||
'omitDefault' => false,
|
'omitDefault' => false,
|
||||||
|
@ -1165,9 +1197,11 @@ class JsonLdProcessor {
|
||||||
$framed = $this->_frame($expanded, $expanded_frame, $options);
|
$framed = $this->_frame($expanded, $expanded_frame, $options);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// compact result (force @graph option to true)
|
// compact result (force @graph option to true, skip expansion, check
|
||||||
|
// for linked embeds)
|
||||||
$options['graph'] = true;
|
$options['graph'] = true;
|
||||||
$options['skipExpansion'] = true;
|
$options['skipExpansion'] = true;
|
||||||
|
$options['link'] = new ArrayObject();
|
||||||
$options['activeCtx'] = true;
|
$options['activeCtx'] = true;
|
||||||
$result = $this->compact($framed, $ctx, $options);
|
$result = $this->compact($framed, $ctx, $options);
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
|
@ -1182,6 +1216,7 @@ class JsonLdProcessor {
|
||||||
// get graph alias
|
// get graph alias
|
||||||
$graph = $this->_compactIri($active_ctx, '@graph');
|
$graph = $this->_compactIri($active_ctx, '@graph');
|
||||||
// remove @preserve from results
|
// remove @preserve from results
|
||||||
|
$options['link'] = new ArrayObject();
|
||||||
$compacted->{$graph} = $this->_removePreserve(
|
$compacted->{$graph} = $this->_removePreserve(
|
||||||
$active_ctx, $compacted->{$graph}, $options);
|
$active_ctx, $compacted->{$graph}, $options);
|
||||||
return $compacted;
|
return $compacted;
|
||||||
|
@ -1968,18 +2003,48 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// recursively compact object
|
// recursively compact object
|
||||||
if(is_object($element)) {
|
if(is_object($element)) {
|
||||||
|
if($options['link'] && property_exists($element, '@id') &&
|
||||||
|
isset($options['link'][$element->{'@id'}])) {
|
||||||
|
// check for a linked element to reuse
|
||||||
|
$linked = $options['link'][$element->{'@id'}];
|
||||||
|
foreach($linked as $link) {
|
||||||
|
if($link['expanded'] === $element) {
|
||||||
|
return $link['compacted'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// do value compaction on @values and subject references
|
// do value compaction on @values and subject references
|
||||||
if(self::_isValue($element) || self::_isSubjectReference($element)) {
|
if(self::_isValue($element) || self::_isSubjectReference($element)) {
|
||||||
return $this->_compactValue($active_ctx, $active_property, $element);
|
$rval = $this->_compactValue($active_ctx, $active_property, $element);
|
||||||
|
if($options['link'] && self::_isSubjectReference($element)) {
|
||||||
|
// store linked element
|
||||||
|
if(!isset($options['link'][$element->{'@id'}])) {
|
||||||
|
$options['link'][$element->{'@id'}] = array();
|
||||||
|
}
|
||||||
|
$options['link'][$element->{'@id'}][] = array(
|
||||||
|
'expanded' => $element, 'compacted' => $rval);
|
||||||
|
}
|
||||||
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: avoid misuse of active property as an expanded property?
|
// FIXME: avoid misuse of active property as an expanded property?
|
||||||
$inside_reverse = ($active_property === '@reverse');
|
$inside_reverse = ($active_property === '@reverse');
|
||||||
|
|
||||||
|
$rval = new stdClass();
|
||||||
|
|
||||||
|
if($options['link'] && property_exists($element, '@id')) {
|
||||||
|
// store linked element
|
||||||
|
if(!isset($options['link'][$element->{'@id'}])) {
|
||||||
|
$options['link'][$element->{'@id'}] = array();
|
||||||
|
}
|
||||||
|
$options['link'][$element->{'@id'}][] = array(
|
||||||
|
'expanded' => $element, 'compacted' => $rval);
|
||||||
|
}
|
||||||
|
|
||||||
// process element keys in order
|
// process element keys in order
|
||||||
$keys = array_keys((array)$element);
|
$keys = array_keys((array)$element);
|
||||||
sort($keys);
|
sort($keys);
|
||||||
$rval = new stdClass();
|
|
||||||
foreach($keys as $expanded_property) {
|
foreach($keys as $expanded_property) {
|
||||||
$expanded_value = $element->{$expanded_property};
|
$expanded_value = $element->{$expanded_property};
|
||||||
|
|
||||||
|
@ -2643,7 +2708,9 @@ class JsonLdProcessor {
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
'graphs' => (object)array(
|
'graphs' => (object)array(
|
||||||
'@default' => new stdClass(),
|
'@default' => new stdClass(),
|
||||||
'@merged' => new stdClass()));
|
'@merged' => new stdClass()),
|
||||||
|
'subjectStack' => array(),
|
||||||
|
'link' => new stdClass());
|
||||||
|
|
||||||
// produce a map of all graphs and name each bnode
|
// produce a map of all graphs and name each bnode
|
||||||
// FIXME: currently uses subjects from @merged graph only
|
// FIXME: currently uses subjects from @merged graph only
|
||||||
|
@ -3699,157 +3766,187 @@ class JsonLdProcessor {
|
||||||
protected function _matchFrame(
|
protected function _matchFrame(
|
||||||
$state, $subjects, $frame, $parent, $property) {
|
$state, $subjects, $frame, $parent, $property) {
|
||||||
// validate the frame
|
// validate the frame
|
||||||
$this->_validateFrame($state, $frame);
|
$this->_validateFrame($frame);
|
||||||
$frame = $frame[0];
|
$frame = $frame[0];
|
||||||
|
|
||||||
// get flags for current frame
|
// get flags for current frame
|
||||||
$options = $state->options;
|
$options = $state->options;
|
||||||
$embed_on = $this->_getFrameFlag($frame, $options, 'embed');
|
|
||||||
$explicit_on = $this->_getFrameFlag($frame, $options, 'explicit');
|
|
||||||
$require_all_on = $this->_getFrameFlag($frame, $options, 'requireAll');
|
|
||||||
$flags = array(
|
$flags = array(
|
||||||
'embed' => $embed_on,
|
'embed' => $this->_getFrameFlag($frame, $options, 'embed'),
|
||||||
'explicit' => $explicit_on,
|
'explicit' => $this->_getFrameFlag($frame, $options, 'explicit'),
|
||||||
'requireAll' => $require_all_on);
|
'requireAll' => $this->_getFrameFlag($frame, $options, 'requireAll'));
|
||||||
|
|
||||||
// filter out subjects that match the frame
|
// filter out subjects that match the frame
|
||||||
$matches = $this->_filterSubjects($state, $subjects, $frame, $flags);
|
$matches = $this->_filterSubjects($state, $subjects, $frame, $flags);
|
||||||
|
|
||||||
// add matches to output
|
// add matches to output
|
||||||
foreach($matches as $id => $subject) {
|
foreach($matches as $id => $subject) {
|
||||||
/* Note: In order to treat each top-level match as a compartmentalized
|
if($flags['embed'] === '@link' && property_exists($state->link, $id)) {
|
||||||
result, create an independent copy of the embedded subjects map when the
|
// TODO: may want to also match an existing linked subject against
|
||||||
property is null, which only occurs at the top-level. */
|
// the current frame ... so different frames could produce different
|
||||||
if($property === null) {
|
// subjects that are only shared in-memory when the frames are the same
|
||||||
$state->embeds = new stdClass();
|
|
||||||
|
// add existing linked subject
|
||||||
|
$this->_addFrameOutput($parent, $property, $state->link->{$id});
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start output
|
/* 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. */
|
||||||
|
if($property === null) {
|
||||||
|
$state->uniqueEmbeds = new stdClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
// start output for subject
|
||||||
$output = new stdClass();
|
$output = new stdClass();
|
||||||
$output->{'@id'} = $id;
|
$output->{'@id'} = $id;
|
||||||
|
$state->link->{$id} = $output;
|
||||||
|
|
||||||
// prepare embed meta info
|
// if embed is @never or if a circular reference would be created by an
|
||||||
$embed = (object)array('parent' => $parent, 'property' => $property);
|
// 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' ||
|
||||||
|
$this->_createsCircularReference($subject, $state->subjectStack)) {
|
||||||
|
$this->_addFrameOutput($parent, $property, $output);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// if embed is on and there is an existing embed
|
// if only the last match should be embedded
|
||||||
if($embed_on && property_exists($state->embeds, $id)) {
|
if($flags['embed'] === '@last') {
|
||||||
// only overwrite an existing embed if it has already been added to its
|
// remove any existing embed
|
||||||
// parent -- otherwise its parent is somewhere up the tree from this
|
if(property_exists($state->uniqueEmbeds, $id)) {
|
||||||
// embed and the embed would occur twice once the tree is added
|
|
||||||
$embed_on = false;
|
|
||||||
|
|
||||||
// existing embed's parent is an array
|
|
||||||
$existing = $state->embeds->{$id};
|
|
||||||
if(is_array($existing->parent)) {
|
|
||||||
foreach($existing->parent as $p) {
|
|
||||||
if(self::compareValues($output, $p)) {
|
|
||||||
$embed_on = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(self::hasValue(
|
|
||||||
$existing->parent, $existing->property, $output)) {
|
|
||||||
// existing embed's parent is an object
|
|
||||||
$embed_on = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// existing embed has already been added, so allow an overwrite
|
|
||||||
if($embed_on) {
|
|
||||||
$this->_removeEmbed($state, $id);
|
$this->_removeEmbed($state, $id);
|
||||||
}
|
}
|
||||||
|
$state->uniqueEmbeds->{$id} = array(
|
||||||
|
'parent' => $parent, 'property' => $property);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not embedding, add output without any other properties
|
// push matching subject onto stack to enable circular embed checks
|
||||||
if(!$embed_on) {
|
$state->subjectStack[] = $subject;
|
||||||
$this->_addFrameOutput($state, $parent, $property, $output);
|
|
||||||
} else {
|
|
||||||
// add embed meta info
|
|
||||||
$state->embeds->{$id} = $embed;
|
|
||||||
|
|
||||||
// iterate over subject properties
|
// iterate over subject properties
|
||||||
$props = array_keys((array)$subject);
|
$props = array_keys((array)$subject);
|
||||||
sort($props);
|
sort($props);
|
||||||
foreach($props as $prop) {
|
foreach($props as $prop) {
|
||||||
// copy keywords to output
|
// copy keywords to output
|
||||||
if(self::_isKeyword($prop)) {
|
if(self::_isKeyword($prop)) {
|
||||||
$output->{$prop} = self::copy($subject->{$prop});
|
$output->{$prop} = self::copy($subject->{$prop});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if property isn't in the frame
|
// explicit is on and property isn't in the frame, skip processing
|
||||||
if(!property_exists($frame, $prop)) {
|
if($flags['explicit'] && !property_exists($frame, $prop)) {
|
||||||
// if explicit is off, embed values
|
continue;
|
||||||
if(!$explicit_on) {
|
}
|
||||||
$this->_embedValues($state, $subject, $prop, $output);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add objects
|
// add objects
|
||||||
$objects = $subject->{$prop};
|
$objects = $subject->{$prop};
|
||||||
foreach($objects as $o) {
|
foreach($objects as $o) {
|
||||||
// recurse into list
|
// recurse into list
|
||||||
if(self::_isList($o)) {
|
if(self::_isList($o)) {
|
||||||
// add empty list
|
// add empty list
|
||||||
$list = (object)array('@list' => array());
|
$list = (object)array('@list' => array());
|
||||||
$this->_addFrameOutput($state, $output, $prop, $list);
|
$this->_addFrameOutput($output, $prop, $list);
|
||||||
|
|
||||||
// add list objects
|
// add list objects
|
||||||
$src = $o->{'@list'};
|
$src = $o->{'@list'};
|
||||||
foreach($src as $o) {
|
foreach($src as $o) {
|
||||||
if(self::_isSubjectReference($o)) {
|
if(self::_isSubjectReference($o)) {
|
||||||
// recurse into subject reference
|
// recurse into subject reference
|
||||||
$this->_matchFrame(
|
$subframe = (property_exists($frame, $prop) ?
|
||||||
$state, array($o->{'@id'}), $frame->{$prop}[0]->{'@list'},
|
$frame->{$prop}[0]->{'@list'} :
|
||||||
$list, '@list');
|
$this->_createImplicitFrame($flags));
|
||||||
} else {
|
$this->_matchFrame(
|
||||||
// include other values automatically
|
$state, array($o->{'@id'}), $subframe, $list, '@list');
|
||||||
$this->_addFrameOutput(
|
} else {
|
||||||
$state, $list, '@list', self::copy($o));
|
// include other values automatically
|
||||||
}
|
$this->_addFrameOutput($list, '@list', self::copy($o));
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self::_isSubjectReference($o)) {
|
|
||||||
// recurse into subject reference
|
|
||||||
$this->_matchFrame(
|
|
||||||
$state, array($o->{'@id'}), $frame->{$prop}, $output, $prop);
|
|
||||||
} else {
|
|
||||||
// include other values automatically
|
|
||||||
$this->_addFrameOutput($state, $output, $prop, self::copy($o));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle defaults
|
|
||||||
$props = array_keys((array)$frame);
|
|
||||||
sort($props);
|
|
||||||
foreach($props as $prop) {
|
|
||||||
// skip keywords
|
|
||||||
if(self::_isKeyword($prop)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if omit default is off, then include default values for properties
|
if(self::_isSubjectReference($o)) {
|
||||||
// that appear in the next frame but are not in the matching subject
|
// recurse into subject reference
|
||||||
$next = $frame->{$prop}[0];
|
$subframe = (property_exists($frame, $prop) ?
|
||||||
$omit_default_on = $this->_getFrameFlag(
|
$frame->{$prop} : $this->_createImplicitFrame($flags));
|
||||||
$next, $options, 'omitDefault');
|
$this->_matchFrame(
|
||||||
if(!$omit_default_on && !property_exists($output, $prop)) {
|
$state, array($o->{'@id'}), $subframe, $output, $prop);
|
||||||
$preserve = '@null';
|
} else {
|
||||||
if(property_exists($next, '@default')) {
|
// include other values automatically
|
||||||
$preserve = self::copy($next->{'@default'});
|
$this->_addFrameOutput($output, $prop, self::copy($o));
|
||||||
}
|
|
||||||
$preserve = self::arrayify($preserve);
|
|
||||||
$output->{$prop} = array((object)array('@preserve' => $preserve));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add output to parent
|
// handle defaults
|
||||||
$this->_addFrameOutput($state, $parent, $property, $output);
|
$props = array_keys((array)$frame);
|
||||||
|
sort($props);
|
||||||
|
foreach($props as $prop) {
|
||||||
|
// skip keywords
|
||||||
|
if(self::_isKeyword($prop)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if omit default is off, then include default values for properties
|
||||||
|
// that appear in the next frame but are not in the matching subject
|
||||||
|
$next = $frame->{$prop}[0];
|
||||||
|
$omit_default_on = $this->_getFrameFlag(
|
||||||
|
$next, $options, 'omitDefault');
|
||||||
|
if(!$omit_default_on && !property_exists($output, $prop)) {
|
||||||
|
$preserve = '@null';
|
||||||
|
if(property_exists($next, '@default')) {
|
||||||
|
$preserve = self::copy($next->{'@default'});
|
||||||
|
}
|
||||||
|
$preserve = self::arrayify($preserve);
|
||||||
|
$output->{$prop} = array((object)array('@preserve' => $preserve));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add output to parent
|
||||||
|
$this->_addFrameOutput($parent, $property, $output);
|
||||||
|
|
||||||
|
// pop matching subject from circular ref-checking stack
|
||||||
|
array_pop($state->subjectStack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an implicit frame when recursing through subject matches. If
|
||||||
|
* a frame doesn't have an explicit frame for a particular property, then
|
||||||
|
* a wildcard child frame will be created that uses the same flags that the
|
||||||
|
* parent frame used.
|
||||||
|
*
|
||||||
|
* @param assoc flags the current framing flags.
|
||||||
|
*
|
||||||
|
* @return array the implicit frame.
|
||||||
|
*/
|
||||||
|
function _createImplicitFrame($flags) {
|
||||||
|
$frame = new stdClass();
|
||||||
|
foreach($flags as $key => $value) {
|
||||||
|
$frame->{'@' . $key} = array($flags[$key]);
|
||||||
|
}
|
||||||
|
return array($frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the current subject stack to see if embedding the given subject
|
||||||
|
* would cause a circular reference.
|
||||||
|
*
|
||||||
|
* @param stdClass subject_to_embed the subject to embed.
|
||||||
|
* @param assoc subject_stack the current stack of subjects.
|
||||||
|
*
|
||||||
|
* @return bool true if a circular reference would be created, false if not.
|
||||||
|
*/
|
||||||
|
function _createsCircularReference($subject_to_embed, $subject_stack) {
|
||||||
|
for($i = count($subject_stack) - 1; $i >= 0; --$i) {
|
||||||
|
if($subject_stack[$i]->{'@id'} === $subject_to_embed->{'@id'}) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3863,17 +3960,31 @@ class JsonLdProcessor {
|
||||||
*/
|
*/
|
||||||
protected function _getFrameFlag($frame, $options, $name) {
|
protected function _getFrameFlag($frame, $options, $name) {
|
||||||
$flag = "@$name";
|
$flag = "@$name";
|
||||||
return (property_exists($frame, $flag) ?
|
$rval = (property_exists($frame, $flag) ?
|
||||||
$frame->{$flag}[0] : $options[$name]);
|
$frame->{$flag}[0] : $options[$name]);
|
||||||
|
if($name === 'embed') {
|
||||||
|
// default is "@last"
|
||||||
|
// backwards-compatibility support for "embed" maps:
|
||||||
|
// true => "@last"
|
||||||
|
// false => "@never"
|
||||||
|
if($rval === true) {
|
||||||
|
$rval = '@last';
|
||||||
|
} else if($rval === false) {
|
||||||
|
$rval = '@never';
|
||||||
|
} else if($rval !== '@always' && $rval !== '@never' &&
|
||||||
|
$rval !== '@link') {
|
||||||
|
$rval = '@last';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a JSON-LD frame, throwing an exception if the frame is invalid.
|
* Validates a JSON-LD frame, throwing an exception if the frame is invalid.
|
||||||
*
|
*
|
||||||
* @param stdClass $state the current frame state.
|
|
||||||
* @param array $frame the frame to validate.
|
* @param array $frame the frame to validate.
|
||||||
*/
|
*/
|
||||||
protected function _validateFrame($state, $frame) {
|
protected function _validateFrame($frame) {
|
||||||
if(!is_array($frame) || count($frame) !== 1 || !is_object($frame[0])) {
|
if(!is_array($frame) || count($frame) !== 1 || !is_object($frame[0])) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; a JSON-LD frame must be a single object.',
|
'Invalid JSON-LD syntax; a JSON-LD frame must be a single object.',
|
||||||
|
@ -3970,58 +4081,6 @@ class JsonLdProcessor {
|
||||||
return $wildcard || $matches_some;
|
return $wildcard || $matches_some;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeds values for the given subject and property into the given output
|
|
||||||
* during the framing algorithm.
|
|
||||||
*
|
|
||||||
* @param stdClass $state the current framing state.
|
|
||||||
* @param stdClass $subject the subject.
|
|
||||||
* @param string $property the property.
|
|
||||||
* @param mixed $output the output.
|
|
||||||
*/
|
|
||||||
protected function _embedValues($state, $subject, $property, $output) {
|
|
||||||
// embed subject properties in output
|
|
||||||
$objects = $subject->{$property};
|
|
||||||
foreach($objects as $o) {
|
|
||||||
// recurse into @list
|
|
||||||
if(self::_isList($o)) {
|
|
||||||
$list = (object)array('@list' => new ArrayObject());
|
|
||||||
$this->_addFrameOutput($state, $output, $property, $list);
|
|
||||||
$this->_embedValues($state, $o, '@list', $list->{'@list'});
|
|
||||||
$list->{'@list'} = (array)$list->{'@list'};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle subject reference
|
|
||||||
if(self::_isSubjectReference($o)) {
|
|
||||||
$id = $o->{'@id'};
|
|
||||||
|
|
||||||
// embed full subject if isn't already embedded
|
|
||||||
if(!property_exists($state->embeds, $id)) {
|
|
||||||
// add embed
|
|
||||||
$embed = (object)array('parent' => $output, 'property' => $property);
|
|
||||||
$state->embeds->{$id} = $embed;
|
|
||||||
|
|
||||||
// recurse into subject
|
|
||||||
$o = new stdClass();
|
|
||||||
$s = $state->subjects->{$id};
|
|
||||||
foreach($s as $prop => $v) {
|
|
||||||
// copy keywords
|
|
||||||
if(self::_isKeyword($prop)) {
|
|
||||||
$o->{$prop} = self::copy($v);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$this->_embedValues($state, $s, $prop, $o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->_addFrameOutput($state, $output, $property, $o);
|
|
||||||
} else {
|
|
||||||
// copy non-subject value
|
|
||||||
$this->_addFrameOutput($state, $output, $property, self::copy($o));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an existing embed.
|
* Removes an existing embed.
|
||||||
*
|
*
|
||||||
|
@ -4030,7 +4089,7 @@ class JsonLdProcessor {
|
||||||
*/
|
*/
|
||||||
protected function _removeEmbed($state, $id) {
|
protected function _removeEmbed($state, $id) {
|
||||||
// get existing embed
|
// get existing embed
|
||||||
$embeds = $state->embeds;
|
$embeds = $state->uniqueEmbeds;
|
||||||
$embed = $embeds->{$id};
|
$embed = $embeds->{$id};
|
||||||
$property = $embed->property;
|
$property = $embed->property;
|
||||||
|
|
||||||
|
@ -4074,12 +4133,11 @@ class JsonLdProcessor {
|
||||||
/**
|
/**
|
||||||
* Adds framing output to the given parent.
|
* Adds framing output to the given parent.
|
||||||
*
|
*
|
||||||
* @param stdClass $state the current framing state.
|
|
||||||
* @param mixed $parent the parent to add to.
|
* @param mixed $parent the parent to add to.
|
||||||
* @param string $property the parent property.
|
* @param string $property the parent property.
|
||||||
* @param mixed $output the output to add.
|
* @param mixed $output the output to add.
|
||||||
*/
|
*/
|
||||||
protected function _addFrameOutput($state, $parent, $property, $output) {
|
protected function _addFrameOutput($parent, $property, $output) {
|
||||||
if(is_object($parent) && !($parent instanceof ArrayObject)) {
|
if(is_object($parent) && !($parent instanceof ArrayObject)) {
|
||||||
self::addValue(
|
self::addValue(
|
||||||
$parent, $property, $output, array('propertyIsArray' => true));
|
$parent, $property, $output, array('propertyIsArray' => true));
|
||||||
|
@ -4130,6 +4188,25 @@ class JsonLdProcessor {
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle in-memory linked nodes
|
||||||
|
$id_alias = $this->_compactIri($ctx, '@id');
|
||||||
|
if(property_exists($input, $id_alias)) {
|
||||||
|
$id = $input->{$id_alias};
|
||||||
|
if(isset($options['link'][$id])) {
|
||||||
|
$idx = array_search($input, $options['link'][$id]);
|
||||||
|
if($idx === false) {
|
||||||
|
// prevent circular visitation
|
||||||
|
$options['link'][$id][] = $input;
|
||||||
|
} else {
|
||||||
|
// already visited
|
||||||
|
return $options['link'][$id][$idx];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// prevent circular visitation
|
||||||
|
$options['link'][$id] = [$input];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// recurse through properties
|
// recurse through properties
|
||||||
foreach($input as $prop => $v) {
|
foreach($input as $prop => $v) {
|
||||||
$result = $this->_removePreserve($ctx, $v, $options);
|
$result = $this->_removePreserve($ctx, $v, $options);
|
||||||
|
|
Loading…
Reference in a new issue