forked from friendica/php-json-ld
Initial update to port over changes from jsonld.js.
This commit is contained in:
parent
3309e2bd2b
commit
89dd522d45
2 changed files with 2204 additions and 1239 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
*
|
*
|
||||||
* @author Dave Longley
|
* @author Dave Longley
|
||||||
*
|
*
|
||||||
* Copyright (c) 2011-2012 Digital Bazaar, Inc. All rights reserved.
|
* Copyright (c) 2011-2013 Digital Bazaar, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
require_once('jsonld.php');
|
require_once('jsonld.php');
|
||||||
|
|
||||||
|
|
@ -261,7 +261,12 @@ class TestRunner {
|
||||||
'base' => 'http://json-ld.org/test-suite/tests/' . $test->input);
|
'base' => 'http://json-ld.org/test-suite/tests/' . $test->input);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(in_array('jld:NormalizeTest', $type)) {
|
if(in_array('jld:ApiErrorTest', $type)) {
|
||||||
|
echo "Skipping test \"{$test->name}\" of type: " .
|
||||||
|
json_encode($type) . $eol;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(in_array('jld:NormalizeTest', $type)) {
|
||||||
$this->test($test->name);
|
$this->test($test->name);
|
||||||
$input = read_test_json($test->input, $filepath);
|
$input = read_test_json($test->input, $filepath);
|
||||||
$test->expect = read_test_nquads($test->expect, $filepath);
|
$test->expect = read_test_nquads($test->expect, $filepath);
|
||||||
|
|
@ -281,6 +286,12 @@ class TestRunner {
|
||||||
$test->expect = read_test_json($test->expect, $filepath);
|
$test->expect = read_test_json($test->expect, $filepath);
|
||||||
$result = jsonld_compact($input, $test->context, $options);
|
$result = jsonld_compact($input, $test->context, $options);
|
||||||
}
|
}
|
||||||
|
else if(in_array('jld:FlattenTest', $type)) {
|
||||||
|
$this->test($test->name);
|
||||||
|
$input = read_test_json($test->input, $filepath);
|
||||||
|
$test->expect = read_test_json($test->expect, $filepath);
|
||||||
|
$result = jsonld_flatten($input, null, $options);
|
||||||
|
}
|
||||||
else if(in_array('jld:FrameTest', $type)) {
|
else if(in_array('jld:FrameTest', $type)) {
|
||||||
$this->test($test->name);
|
$this->test($test->name);
|
||||||
$input = read_test_json($test->input, $filepath);
|
$input = read_test_json($test->input, $filepath);
|
||||||
|
|
|
||||||
3384
jsonld.php
3384
jsonld.php
|
|
@ -70,6 +70,23 @@ function jsonld_expand($input, $options=array()) {
|
||||||
return $p->expand($input, $options);
|
return $p->expand($input, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs JSON-LD flattening.
|
||||||
|
*
|
||||||
|
* @param mixed $input the JSON-LD to flatten.
|
||||||
|
* @param mixed $ctx the context to use to compact the flattened output, or
|
||||||
|
* null.
|
||||||
|
* @param [options] the options to use:
|
||||||
|
* [base] the base IRI to use.
|
||||||
|
* [resolver(url, callback(err, jsonCtx))] the URL resolver to use.
|
||||||
|
*
|
||||||
|
* @return mixed the flattened JSON-LD output.
|
||||||
|
*/
|
||||||
|
function jsonld_flatten($input, $ctx, $options=array()) {
|
||||||
|
$p = new JsonLdProcessor();
|
||||||
|
return $p->flatten($input, $ctx, $options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs JSON-LD framing.
|
* Performs JSON-LD framing.
|
||||||
*
|
*
|
||||||
|
|
@ -145,6 +162,14 @@ function jsonld_to_rdf($input, $options=array()) {
|
||||||
return $p->toRDF($input, $options);
|
return $p->toRDF($input, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** JSON-LD shared in-memory cache. */
|
||||||
|
global $jsonld_cache;
|
||||||
|
$jsonld_cache = new stdClass();
|
||||||
|
|
||||||
|
/** The default active context cache. */
|
||||||
|
// FIXME: turn on
|
||||||
|
//$jsonld_cache->activeCtx = new ActiveContextCache();
|
||||||
|
|
||||||
/** The default JSON-LD URL resolver. */
|
/** The default JSON-LD URL resolver. */
|
||||||
global $jsonld_default_url_resolver;
|
global $jsonld_default_url_resolver;
|
||||||
$jsonld_default_url_resolver = null;
|
$jsonld_default_url_resolver = null;
|
||||||
|
|
@ -186,11 +211,11 @@ function jsonld_resolve_url($url) {
|
||||||
function jsonld_default_resolve_url($url) {
|
function jsonld_default_resolve_url($url) {
|
||||||
// default JSON-LD GET implementation
|
// default JSON-LD GET implementation
|
||||||
$opts = array('http' =>
|
$opts = array('http' =>
|
||||||
array(
|
array(
|
||||||
'method' => "GET",
|
'method' => "GET",
|
||||||
'header' =>
|
'header' =>
|
||||||
"Accept: application/ld+json\r\n" .
|
"Accept: application/ld+json\r\n" .
|
||||||
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
|
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
|
||||||
$stream = stream_context_create($opts);
|
$stream = stream_context_create($opts);
|
||||||
$result = @file_get_contents($url, false, $stream);
|
$result = @file_get_contents($url, false, $stream);
|
||||||
if($result === false) {
|
if($result === false) {
|
||||||
|
|
@ -230,67 +255,110 @@ function jsonld_unregister_rdf_parser($content_type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a relative path into an absolute URL.
|
* Parses a URL into its component parts.
|
||||||
*
|
*
|
||||||
* @param string $path the relative path.
|
* @param string $url the URL to parse.
|
||||||
* @param string $url the absolute URL base.
|
|
||||||
*
|
*
|
||||||
* @return string the absolute URL for the given relative path.
|
* @return assoc the parsed URL.
|
||||||
*/
|
*/
|
||||||
function jsonld_to_absolute_url($path, $url) {
|
function jsonld_parse_url($url) {
|
||||||
// validate path (it may already be an absolute URL)
|
$rval = parse_url($url);
|
||||||
if(filter_var($path, FILTER_VALIDATE_URL) === false) {
|
if(isset($rval['host'])) {
|
||||||
$url = parse_url($url);
|
if(!isset($rval['path']) || $rval['path'] === '') {
|
||||||
$rval = "{$url['scheme']}://";
|
$rval['path'] = '/';
|
||||||
|
|
||||||
if(isset($url['user']) || isset($url['pass'])) {
|
|
||||||
$rval .= "{$url['user']}:{$url['pass']}@";
|
|
||||||
}
|
|
||||||
|
|
||||||
$rval .= $url['host'];
|
|
||||||
if(isset($url['port'])) {
|
|
||||||
$rval .= ":{$url['port']}";
|
|
||||||
}
|
|
||||||
|
|
||||||
$rval .= jsonld_prepend_base($url['path'], $path);
|
|
||||||
if(strrpos($rval, '?') === false && isset($url['query'])) {
|
|
||||||
$rval .= "?{$url['query']}";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(strrpos($rval, '#') === false && isset($url['fragment'])) {
|
|
||||||
$rval .= "#{$url['fragment']}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if(!isset($rval['path'])) {
|
||||||
$rval = $path;
|
$rval['path'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate result
|
|
||||||
if(filter_var($rval, FILTER_VALIDATE_URL) === false) {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Malformed URL.', 'jsonld.InvalidUrl', array('url' => $rval));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend a relative IRI to a base IRI.
|
* Prepends a base IRI to the given relative IRI.
|
||||||
*
|
*
|
||||||
* @param string $base the base IRI.
|
* @param mixed $base a string or the parsed base IRI.
|
||||||
* @param string $iri the relative IRI.
|
* @param string $iri the relative IRI.
|
||||||
|
*
|
||||||
|
* @return string the absolute IRI.
|
||||||
*/
|
*/
|
||||||
function jsonld_prepend_base($base, $iri) {
|
function jsonld_prepend_base($base, $iri) {
|
||||||
if($iri === '' || strpos($iri, '#') === 0) {
|
if(is_string($base)) {
|
||||||
return "$base$iri";
|
$base = jsonld_parse_url($base);
|
||||||
|
}
|
||||||
|
$authority = $base['host'];
|
||||||
|
$rel = jsonld_parse_url($iri);
|
||||||
|
print_r($rel);
|
||||||
|
|
||||||
|
// per RFC3986 normalize slashes and dots in path
|
||||||
|
|
||||||
|
// IRI contains authority
|
||||||
|
if(strpos($iri, '//') === 0) {
|
||||||
|
$path = substr($iri, 2);
|
||||||
|
$authority = substr($path, 0, strrpos($path, '/'));
|
||||||
|
$path = substr($path, strlen($authority));
|
||||||
|
}
|
||||||
|
// IRI represents an absolue path
|
||||||
|
else if(strpos($rel['path'], '/') === 0) {
|
||||||
|
$path = $rel['path'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$path = $base['path'];
|
||||||
|
|
||||||
|
// prepend last directory for base
|
||||||
|
if($rel['path'] !== '') {
|
||||||
|
$idx = strrpos($path, '/');
|
||||||
|
$idx = ($idx === false) ? 0 : $idx + 1;
|
||||||
|
$path = substr($path, 0, $idx) + $rel['path'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepend last directory for base
|
$segments = explode($path, '/');
|
||||||
$idx = strrpos($base, '/');
|
|
||||||
if($idx === false) {
|
// remove '.' and '' (do not remove trailing empty path)
|
||||||
return $iri;
|
$idx = 0;
|
||||||
|
$end = count($segments) - 1;
|
||||||
|
$filter = function($e) use ($idx, $end) {
|
||||||
|
$idx += 1;
|
||||||
|
return $e !== '.' && ($e !== '' || $idx === $end);
|
||||||
|
};
|
||||||
|
$segments = array_filter($segments, $filter);
|
||||||
|
|
||||||
|
// remove as many '..' as possible
|
||||||
|
for($i = 0; $i < count($segments);) {
|
||||||
|
$segment = $segments[$i];
|
||||||
|
if($segment === '..') {
|
||||||
|
// too many reverse dots
|
||||||
|
if($i === 0) {
|
||||||
|
$last = $segments[count($segments) - 1];
|
||||||
|
if($last !== '..') {
|
||||||
|
$segments = array($last);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$segments = array();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove '..' and previous segment
|
||||||
|
array_splice($segments, $i - 1, 2);
|
||||||
|
$i -= 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return substr($base, 0, $idx + 1) . $iri;
|
|
||||||
|
$path = '/' . implode(',', $segments);
|
||||||
|
|
||||||
|
// add query and hash
|
||||||
|
if(isset($rel['query'])) {
|
||||||
|
$path .= '?' . $rel['query'];
|
||||||
|
}
|
||||||
|
if(isset($rel['hash'])) {
|
||||||
|
$path .= '#' . $rel['hash'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $base['scheme'] . "://$authority$path";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -326,7 +394,11 @@ class JsonLdProcessor {
|
||||||
* @param mixed $input the JSON-LD object to compact.
|
* @param mixed $input the JSON-LD object to compact.
|
||||||
* @param mixed $ctx the context to compact with.
|
* @param mixed $ctx the context to compact with.
|
||||||
* @param assoc $options the compaction options.
|
* @param assoc $options the compaction options.
|
||||||
|
* [base] the base IRI to use.
|
||||||
|
* [skipExpansion] true to assume the input is expanded and skip
|
||||||
|
* expansion, false not to, defaults to false.
|
||||||
* [activeCtx] true to also return the active context used.
|
* [activeCtx] true to also return the active context used.
|
||||||
|
* [resolver(url)] the URL resolver to use.
|
||||||
*
|
*
|
||||||
* @return mixed the compacted JSON-LD output.
|
* @return mixed the compacted JSON-LD output.
|
||||||
*/
|
*/
|
||||||
|
|
@ -338,24 +410,32 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// set default options
|
// set default options
|
||||||
isset($options['base']) or $options['base'] = '';
|
isset($options['base']) or $options['base'] = '';
|
||||||
|
isset($options['renameBlankNodes']) or $options['renameBlankNodes'] =
|
||||||
|
true;
|
||||||
isset($options['strict']) or $options['strict'] = true;
|
isset($options['strict']) or $options['strict'] = true;
|
||||||
isset($options['optimize']) or $options['optimize'] = false;
|
isset($options['optimize']) or $options['optimize'] = false;
|
||||||
isset($options['graph']) or $options['graph'] = false;
|
isset($options['graph']) or $options['graph'] = false;
|
||||||
|
isset($options['skipExpansion']) or $options['skipExpansion'] = false;
|
||||||
isset($options['activeCtx']) or $options['activeCtx'] = false;
|
isset($options['activeCtx']) or $options['activeCtx'] = false;
|
||||||
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
||||||
|
|
||||||
// expand input
|
if($options['skipExpansion'] === true) {
|
||||||
try {
|
$expanded = $input;
|
||||||
$expanded = $this->expand($input, $options);
|
|
||||||
}
|
}
|
||||||
catch(JsonLdException $e) {
|
else {
|
||||||
throw new JsonLdException(
|
// expand input
|
||||||
'Could not expand input before compaction.',
|
try {
|
||||||
'jsonld.CompactError', null, $e);
|
$expanded = $this->expand($input, $options);
|
||||||
|
}
|
||||||
|
catch(JsonLdException $e) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Could not expand input before compaction.',
|
||||||
|
'jsonld.CompactError', null, $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process context
|
// process context
|
||||||
$active_ctx = $this->_getInitialContext();
|
$active_ctx = $this->_getInitialContext($options);
|
||||||
try {
|
try {
|
||||||
$active_ctx = $this->processContext($active_ctx, $ctx, $options);
|
$active_ctx = $this->processContext($active_ctx, $ctx, $options);
|
||||||
}
|
}
|
||||||
|
|
@ -368,14 +448,16 @@ class JsonLdProcessor {
|
||||||
// do compaction
|
// do compaction
|
||||||
$compacted = $this->_compact($active_ctx, null, $expanded, $options);
|
$compacted = $this->_compact($active_ctx, null, $expanded, $options);
|
||||||
|
|
||||||
// always use an array if graph options is on
|
// if compacted is an array with 1 entry, remove array unless
|
||||||
if($options['graph'] === true) {
|
// graph option is set
|
||||||
$compacted = self::arrayify($compacted);
|
if($options['graph'] !== true &&
|
||||||
}
|
is_array($compacted) && count($compacted) === 1) {
|
||||||
// else if compacted is an array with 1 entry, remove array
|
|
||||||
else if(is_array($compacted) && count($compacted) === 1) {
|
|
||||||
$compacted = $compacted[0];
|
$compacted = $compacted[0];
|
||||||
}
|
}
|
||||||
|
// always use array if graph option is on
|
||||||
|
else if($options['graph'] === true) {
|
||||||
|
$compacted = self::arrayify($compacted);
|
||||||
|
}
|
||||||
|
|
||||||
// follow @context key
|
// follow @context key
|
||||||
if(is_object($ctx) && property_exists($ctx, '@context')) {
|
if(is_object($ctx) && property_exists($ctx, '@context')) {
|
||||||
|
|
@ -426,13 +508,10 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if($options['activeCtx']) {
|
if($options['activeCtx']) {
|
||||||
return array(
|
return array('compacted' => $compacted, 'activeCtx' => $active_ctx);
|
||||||
'compacted' => $compacted,
|
|
||||||
'activeCtx' => $active_ctx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return $compacted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $compacted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -441,6 +520,10 @@ class JsonLdProcessor {
|
||||||
* @param mixed $input the JSON-LD object to expand.
|
* @param mixed $input the JSON-LD object to expand.
|
||||||
* @param assoc $options the options to use:
|
* @param assoc $options the options to use:
|
||||||
* [base] the base IRI to use.
|
* [base] the base IRI to use.
|
||||||
|
* [renameBlankNodes] true to rename blank nodes, false not to,
|
||||||
|
* defaults to true.
|
||||||
|
* [keepFreeFloatingNodes] true to keep free-floating nodes,
|
||||||
|
* false not to, defaults to false.
|
||||||
* [resolver(url)] the URL resolver to use.
|
* [resolver(url)] the URL resolver to use.
|
||||||
*
|
*
|
||||||
* @return array the expanded JSON-LD output.
|
* @return array the expanded JSON-LD output.
|
||||||
|
|
@ -448,6 +531,10 @@ class JsonLdProcessor {
|
||||||
public function expand($input, $options) {
|
public function expand($input, $options) {
|
||||||
// set default options
|
// set default options
|
||||||
isset($options['base']) or $options['base'] = '';
|
isset($options['base']) or $options['base'] = '';
|
||||||
|
isset($options['renameBlankNodes']) or $options['renameBlankNodes'] =
|
||||||
|
true;
|
||||||
|
isset($options['keepFreeFloatingNodes']) or
|
||||||
|
$options['keepFreeFloatingNodes'] = false;
|
||||||
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
||||||
|
|
||||||
// resolve all @context URLs in the input
|
// resolve all @context URLs in the input
|
||||||
|
|
@ -463,18 +550,69 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do expansion
|
// do expansion
|
||||||
$ctx = $this->_getInitialContext();
|
$active_ctx = $this->_getInitialContext($options);
|
||||||
$expanded = $this->_expand($ctx, null, $input, $options, false);
|
$expanded = $this->_expand($active_ctx, null, $input, $options, false);
|
||||||
|
|
||||||
// optimize away @graph with no other properties
|
// optimize away @graph with no other properties
|
||||||
if(is_object($expanded) && property_exists($expanded, '@graph') &&
|
if(is_object($expanded) && property_exists($expanded, '@graph') &&
|
||||||
count(get_object_vars($expanded)) === 1) {
|
count(array_keys((array)$expanded)) === 1) {
|
||||||
$expanded = $expanded->{'@graph'};
|
$expanded = $expanded->{'@graph'};
|
||||||
}
|
}
|
||||||
|
else if($expanded === null) {
|
||||||
|
$expanded = array();
|
||||||
|
}
|
||||||
// normalize to an array
|
// normalize to an array
|
||||||
return self::arrayify($expanded);
|
return self::arrayify($expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs JSON-LD flattening.
|
||||||
|
*
|
||||||
|
* @param mixed $input the JSON-LD to flatten.
|
||||||
|
* @param ctx the context to use to compact the flattened output, or null.
|
||||||
|
* @param assoc $options the options to use:
|
||||||
|
* [base] the base IRI to use.
|
||||||
|
* [resolver(url)] the URL resolver to use.
|
||||||
|
*
|
||||||
|
* @return array the flattened output.
|
||||||
|
*/
|
||||||
|
public function flatten($input, $ctx, $options) {
|
||||||
|
// set default options
|
||||||
|
isset($options['base']) or $options['base'] = '';
|
||||||
|
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// expand input
|
||||||
|
$expanded = $this->expand($input, $options);
|
||||||
|
}
|
||||||
|
catch(Exception $e) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Could not expand input before flattening.',
|
||||||
|
'jsonld.FlattenError', null, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// do flattening
|
||||||
|
$flattened = $this->_flatten($expanded);
|
||||||
|
|
||||||
|
if($ctx === null) {
|
||||||
|
return $flattened;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compact result (force @graph option to true, skip expansion)
|
||||||
|
$options['graph'] = true;
|
||||||
|
$options['skipExpansion'] = true;
|
||||||
|
try {
|
||||||
|
$compacted = $this->compact($flattened, $ctx, $options);
|
||||||
|
}
|
||||||
|
catch(Exception $e) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Could not compact flattened output.',
|
||||||
|
'jsonld.FlattenError', null, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $compacted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs JSON-LD framing.
|
* Performs JSON-LD framing.
|
||||||
*
|
*
|
||||||
|
|
@ -505,7 +643,7 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// expand input
|
// expand input
|
||||||
$_input = $this->expand($input, $options);
|
$expanded = $this->expand($input, $options);
|
||||||
}
|
}
|
||||||
catch(Exception $e) {
|
catch(Exception $e) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
|
|
@ -515,7 +653,9 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// expand frame
|
// expand frame
|
||||||
$_frame = $this->expand($frame, $options);
|
$opts = self::copy($options);
|
||||||
|
$opts['keepFreeFloatingNodes'] = true;
|
||||||
|
$expanded_frame = $this->expand($frame, $opts);
|
||||||
}
|
}
|
||||||
catch(Exception $e) {
|
catch(Exception $e) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
|
|
@ -524,11 +664,12 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// do framing
|
// do framing
|
||||||
$framed = $this->_frame($_input, $_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)
|
||||||
$options['graph'] = true;
|
$options['graph'] = true;
|
||||||
|
$options['skipExpansion'] = true;
|
||||||
$options['activeCtx'] = true;
|
$options['activeCtx'] = true;
|
||||||
$result = $this->compact($framed, $ctx, $options);
|
$result = $this->compact($framed, $ctx, $options);
|
||||||
}
|
}
|
||||||
|
|
@ -539,12 +680,13 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
$compacted = $result['compacted'];
|
$compacted = $result['compacted'];
|
||||||
$ctx = $result['activeCtx'];
|
$active_ctx = $result['activeCtx'];
|
||||||
|
|
||||||
// get graph alias
|
// get graph alias
|
||||||
$graph = $this->_compactIri($ctx, '@graph');
|
$graph = $this->_compactIri($active_ctx, '@graph');
|
||||||
// remove @preserve from results
|
// remove @preserve from results
|
||||||
$compacted->{$graph} = $this->_removePreserve($ctx, $compacted->{$graph});
|
$compacted->{$graph} = $this->_removePreserve(
|
||||||
|
$active_ctx, $compacted->{$graph});
|
||||||
return $compacted;
|
return $compacted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,20 +827,24 @@ class JsonLdProcessor {
|
||||||
* @param stdClass $active_ctx the current active context.
|
* @param stdClass $active_ctx the current active context.
|
||||||
* @param mixed $local_ctx the local context to process.
|
* @param mixed $local_ctx the local context to process.
|
||||||
* @param assoc $options the options to use:
|
* @param assoc $options the options to use:
|
||||||
|
* [renameBlankNodes] true to rename blank nodes, false not to,
|
||||||
|
* defaults to true.
|
||||||
* [resolver(url)] the URL resolver to use.
|
* [resolver(url)] the URL resolver to use.
|
||||||
*
|
*
|
||||||
* @return stdClass the new active context.
|
* @return stdClass the new active context.
|
||||||
*/
|
*/
|
||||||
public function processContext($active_ctx, $local_ctx, $options) {
|
public function processContext($active_ctx, $local_ctx, $options) {
|
||||||
// return initial context early for null context
|
|
||||||
if($local_ctx === null) {
|
|
||||||
return $this->_getInitialContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
// set default options
|
// set default options
|
||||||
isset($options['base']) or $options['base'] = '';
|
isset($options['base']) or $options['base'] = '';
|
||||||
|
isset($options['renameBlankNodes']) or $options['renameBlankNodes'] =
|
||||||
|
true;
|
||||||
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
|
||||||
|
|
||||||
|
// return initial context early for null context
|
||||||
|
if($local_ctx === null) {
|
||||||
|
return $this->_getInitialContext($options);
|
||||||
|
}
|
||||||
|
|
||||||
// resolve URLs in local_ctx
|
// resolve URLs in local_ctx
|
||||||
$ctx = self::copy($local_ctx);
|
$ctx = self::copy($local_ctx);
|
||||||
if(is_object($ctx) && !property_exists($ctx, '@context')) {
|
if(is_object($ctx) && !property_exists($ctx, '@context')) {
|
||||||
|
|
@ -884,8 +1030,10 @@ class JsonLdProcessor {
|
||||||
* considered equal if:
|
* considered equal if:
|
||||||
*
|
*
|
||||||
* 1. They are both primitives of the same type and value.
|
* 1. They are both primitives of the same type and value.
|
||||||
* 2. They are both @values with the same @value, @type, and @language, OR
|
* 2. They are both @values with the same @value, @type, @language,
|
||||||
* 3. They both have @ids they are the same.
|
* and @index, OR
|
||||||
|
* 3. They are both @lists with the same @list and @index, OR
|
||||||
|
* 4. They both have @ids they are the same.
|
||||||
*
|
*
|
||||||
* @param mixed $v1 the first value.
|
* @param mixed $v1 the first value.
|
||||||
* @param mixed $v2 the second value.
|
* @param mixed $v2 the second value.
|
||||||
|
|
@ -899,17 +1047,33 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. equal @values
|
// 2. equal @values
|
||||||
if(self::_isValue($v1) && self::_isValue($v2) &&
|
if(self::_isValue($v1) || self::_isValue($v2)) {
|
||||||
$v1->{'@value'} === $v2->{'@value'} &&
|
return (
|
||||||
property_exists($v1, '@type') === property_exists($v2, '@type') &&
|
self::_compareKeyValues($v1, $v2, '@value') &&
|
||||||
property_exists($v1, '@language') === property_exists($v2, '@language') &&
|
self::_compareKeyValues($v1, $v2, '@type') &&
|
||||||
(!property_exists($v1, '@type') || $v1->{'@type'} === $v2->{'@type'}) &&
|
self::_compareKeyValues($v1, $v2, '@language') &&
|
||||||
(!property_exists($v1, '@language') ||
|
self::_compareKeyValues($v1, $v2, '@index'));
|
||||||
$v2->{'@language'} === $v2->{'@language'})) {
|
}
|
||||||
|
|
||||||
|
// 3. equal @lists
|
||||||
|
if(self::_isList($v1) && self::_isList($v2)) {
|
||||||
|
if(!self::_compareKeyValues($v1, $v2, '@index')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$list1 = $v1->{'@list'};
|
||||||
|
$list2 = $v2->{'@list'};
|
||||||
|
if(count($list1) !== count($list2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for($i = 0; $i < count($list1); ++$i) {
|
||||||
|
if(!self::compareValues($list1[$i], $list2[$i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. equal @ids
|
// 4. equal @ids
|
||||||
if(is_object($v1) && property_exists($v1, '@id') &&
|
if(is_object($v1) && property_exists($v1, '@id') &&
|
||||||
is_object($v2) && property_exists($v2, '@id')) {
|
is_object($v2) && property_exists($v2, '@id')) {
|
||||||
return $v1->{'@id'} === $v2->{'@id'};
|
return $v1->{'@id'} === $v2->{'@id'};
|
||||||
|
|
@ -945,6 +1109,9 @@ class JsonLdProcessor {
|
||||||
// get specific entry information
|
// get specific entry information
|
||||||
if(property_exists($ctx->mappings, $key)) {
|
if(property_exists($ctx->mappings, $key)) {
|
||||||
$entry = $ctx->mappings->{$key};
|
$entry = $ctx->mappings->{$key};
|
||||||
|
if($entry === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// return whole entry
|
// return whole entry
|
||||||
if($type === null) {
|
if($type === null) {
|
||||||
|
|
@ -1064,7 +1231,7 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add statement
|
// add statement
|
||||||
JsonLdProcessor::_appendUniqueRdfStatement($statements, $s);
|
self::_appendUniqueRdfStatement($statements, $s);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $statements;
|
return $statements;
|
||||||
|
|
@ -1124,7 +1291,7 @@ class JsonLdProcessor {
|
||||||
$o->nominalValue);
|
$o->nominalValue);
|
||||||
$quad .= '"' . $escaped . '"';
|
$quad .= '"' . $escaped . '"';
|
||||||
if(property_exists($o, 'datatype') &&
|
if(property_exists($o, 'datatype') &&
|
||||||
$o->datatype->nominalValue !== JsonLdProcessor::XSD_STRING) {
|
$o->datatype->nominalValue !== self::XSD_STRING) {
|
||||||
$quad .= "^^<{$o->datatype->nominalValue}>";
|
$quad .= "^^<{$o->datatype->nominalValue}>";
|
||||||
}
|
}
|
||||||
else if(property_exists($o, 'language')) {
|
else if(property_exists($o, 'language')) {
|
||||||
|
|
@ -1213,29 +1380,33 @@ class JsonLdProcessor {
|
||||||
* Recursively compacts an element using the given active context. All values
|
* Recursively compacts an element using the given active context. All values
|
||||||
* must be in expanded form before this method is called.
|
* must be in expanded form before this method is called.
|
||||||
*
|
*
|
||||||
* @param stdClass $ctx the active context to use.
|
* @param stdClass $active_ctx the active context to use.
|
||||||
* @param mixed $property the property that points to the element, null for
|
* @param mixed $active_property the compacted property with the element
|
||||||
* none.
|
* to compact, null for none.
|
||||||
* @param mixed $element the element to compact.
|
* @param mixed $element the element to compact.
|
||||||
* @param array $options the compaction options.
|
* @param assoc $options the compaction options.
|
||||||
*
|
*
|
||||||
* @return mixed the compacted value.
|
* @return mixed the compacted value.
|
||||||
*/
|
*/
|
||||||
protected function _compact($ctx, $property, $element, $options) {
|
protected function _compact(
|
||||||
|
$active_ctx, $active_property, $element, $options) {
|
||||||
// recursively compact array
|
// recursively compact array
|
||||||
if(is_array($element)) {
|
if(is_array($element)) {
|
||||||
$rval = array();
|
$rval = array();
|
||||||
foreach($element as $e) {
|
foreach($element as $e) {
|
||||||
$e = $this->_compact($ctx, $property, $e, $options);
|
// compact, dropping any null values
|
||||||
|
$compacted = $this->_compact(
|
||||||
|
$active_ctx, $active_property, $e, $options);
|
||||||
// drop null values
|
// drop null values
|
||||||
if($e !== null) {
|
if($compacted !== null) {
|
||||||
$rval[] = $e;
|
$rval[] = $compacted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(count($rval) === 1) {
|
if(count($rval) === 1) {
|
||||||
// use single element if no container is specified
|
// use single element if no container is specified
|
||||||
$container = self::getContextValue($ctx, $property, '@container');
|
$container = self::getContextValue(
|
||||||
if($container !== '@list' && $container !== '@set') {
|
$active_ctx, $active_property, '@container');
|
||||||
|
if($container === null) {
|
||||||
$rval = $rval[0];
|
$rval = $rval[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1244,143 +1415,184 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// recursively compact object
|
// recursively compact object
|
||||||
if(is_object($element)) {
|
if(is_object($element)) {
|
||||||
// element is a @value
|
// do value compaction on @values and subject references
|
||||||
if(self::_isValue($element)) {
|
if(self::_isValue($element) || self::_isSubjectReference($element)) {
|
||||||
// if @value is the only key
|
return $this->_compactValue($active_ctx, $active_property, $element);
|
||||||
if(count(get_object_vars($element)) === 1) {
|
}
|
||||||
// if there is no default language or @value is not a string,
|
|
||||||
// return value of @value
|
|
||||||
if(!property_exists($ctx, '@language') ||
|
|
||||||
!is_string($element->{'@value'})) {
|
|
||||||
return $element->{'@value'};
|
|
||||||
}
|
|
||||||
// return full element, alias @value
|
|
||||||
$rval = new stdClass();
|
|
||||||
$rval->{$this->_compactIri($ctx, '@value')} = $element->{'@value'};
|
|
||||||
return $rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get type and language context rules
|
// shallow copy element and arrays so keys and values can be removed
|
||||||
$type = self::getContextValue($ctx, $property, '@type');
|
// during property generator compaction
|
||||||
$language = self::getContextValue($ctx, $property, '@language');
|
$shallow = new stdClass();
|
||||||
|
foreach($element as $expanded_property => $expanded_value) {
|
||||||
// matching @type specified in context, compact element
|
if(is_array($element->{$expanded_property})) {
|
||||||
if($type !== null &&
|
$shallow->{$expanded_property} = $element->{$expanded_property};
|
||||||
property_exists($element, '@type') && $element->{'@type'} === $type) {
|
|
||||||
return $element->{'@value'};
|
|
||||||
}
|
|
||||||
// matching @language specified in context, compact element
|
|
||||||
else if($language !== null &&
|
|
||||||
property_exists($element, '@language') &&
|
|
||||||
$element->{'@language'} === $language) {
|
|
||||||
return $element->{'@value'};
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$rval = new stdClass();
|
$shallow->{$expanded_property} = $element->{$expanded_property};
|
||||||
// compact @type IRI
|
|
||||||
if(property_exists($element, '@type')) {
|
|
||||||
$rval->{$this->_compactIri($ctx, '@type')} =
|
|
||||||
$this->_compactIri($ctx, $element->{'@type'});
|
|
||||||
}
|
|
||||||
// alias @language
|
|
||||||
else if(property_exists($element, '@language')) {
|
|
||||||
$rval->{$this->_compactIri($ctx, '@language')} =
|
|
||||||
$element->{'@language'};
|
|
||||||
}
|
|
||||||
$rval->{$this->_compactIri($ctx, '@value')} = $element->{'@value'};
|
|
||||||
return $rval;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$element = $shallow;
|
||||||
|
|
||||||
// compact subject references
|
// process element keys in order
|
||||||
if(self::_isSubjectReference($element)) {
|
$keys = array_keys((array)$element);
|
||||||
$type = self::getContextValue($ctx, $property, '@type');
|
sort($keys);
|
||||||
if($type === '@id' || $property === '@graph') {
|
|
||||||
return $this->_compactIri($ctx, $element->{'@id'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursively process element keys
|
|
||||||
$rval = new stdClass();
|
$rval = new stdClass();
|
||||||
foreach($element as $key => $value) {
|
foreach($keys as $expanded_property) {
|
||||||
// compact @id and @type(s)
|
// skip key if removed during property generator duplicate handling
|
||||||
if($key === '@id' || $key === '@type') {
|
if(!property_exists($element, $expanded_property)) {
|
||||||
// compact single @id
|
|
||||||
if(is_string($value)) {
|
|
||||||
$value = $this->_compactIri($ctx, $value);
|
|
||||||
}
|
|
||||||
// value must be a @type array
|
|
||||||
else {
|
|
||||||
$types = array();
|
|
||||||
foreach($value as $v) {
|
|
||||||
$types[] = $this->_compactIri($ctx, $v);
|
|
||||||
}
|
|
||||||
$value = $types;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compact property and add value
|
|
||||||
$prop = $this->_compactIri($ctx, $key);
|
|
||||||
$isArray = (is_array($value) && count($value) === 0);
|
|
||||||
self::addValue(
|
|
||||||
$rval, $prop, $value, array('propertyIsArray' => $isArray));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: value must be an array due to expansion algorithm.
|
$expanded_value = $element->{$expanded_property};
|
||||||
|
|
||||||
|
// compact @id and @type(s)
|
||||||
|
if($expanded_property === '@id' || $expanded_property === '@type') {
|
||||||
|
// compact single @id
|
||||||
|
if(is_string($expanded_value)) {
|
||||||
|
$compacted_value = $this->_compactIri(
|
||||||
|
$active_ctx, $expanded_value, null, array(
|
||||||
|
'base' => true, 'vocab' => ($expanded_property === '@type')));
|
||||||
|
}
|
||||||
|
// expanded value must be a @type array
|
||||||
|
else {
|
||||||
|
$compacted_value = array();
|
||||||
|
foreach($expanded_value as $ev) {
|
||||||
|
$compacted_value[] = $this->_compactIri(
|
||||||
|
$active_ctx, $ev, null, array('base' => true, 'vocab' => true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use keyword alias and add value
|
||||||
|
$alias = $this->_compactIri($active_ctx, $expanded_property);
|
||||||
|
$is_array = (is_array($compacted_value) &&
|
||||||
|
count($expanded_value) === 0);
|
||||||
|
self::addValue(
|
||||||
|
$rval, $alias, $compacted_value,
|
||||||
|
array('propertyIsArray' => $is_array));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle @index property
|
||||||
|
if($expanded_property === '@index') {
|
||||||
|
// drop @index if inside an @index container
|
||||||
|
$container = self::getContextValue(
|
||||||
|
$active_ctx, $active_property, '@container');
|
||||||
|
if($container === '@index') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use keyword alias and add value
|
||||||
|
$alias = $this->_compactIri($active_ctx, $expanded_property);
|
||||||
|
self::addValue($rval, $alias, $expanded_value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: expanded value must be an array due to expansion algorithm.
|
||||||
|
|
||||||
// preserve empty arrays
|
// preserve empty arrays
|
||||||
if(count($value) === 0) {
|
if(count($expanded_value) === 0) {
|
||||||
$prop = $this->_compactIri($ctx, $key);
|
$active_property = $this->_compactIri(
|
||||||
|
$active_ctx, $expanded_property, null, array('vocab' => true),
|
||||||
|
$element);
|
||||||
self::addValue(
|
self::addValue(
|
||||||
$rval, $prop, array(), array('propertyIsArray' => true));
|
$rval, $active_property, array(), array('propertyIsArray' => true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// recusively process array values
|
// recusively process array values
|
||||||
foreach($value as $v) {
|
foreach($expanded_value as $expanded_item) {
|
||||||
$is_list = self::_isList($v);
|
// compact property and get container type
|
||||||
|
$active_property = $this->_compactIri(
|
||||||
|
$active_ctx, $expanded_property, $expanded_item,
|
||||||
|
array('vocab' => true), $element);
|
||||||
|
$container = self::getContextValue(
|
||||||
|
$active_ctx, $active_property, '@container');
|
||||||
|
|
||||||
// compact property
|
// remove any duplicates that were (presumably) generated by a
|
||||||
$prop = $this->_compactIri($ctx, $key, $v);
|
// property generator
|
||||||
|
if(property_exists($active_ctx->mappings, $active_property)) {
|
||||||
// remove @list for recursion (will be re-added if necessary)
|
$mapping = $active_ctx->mappings->{$active_property};
|
||||||
if($is_list) {
|
if($mapping && $mapping->propertyGenerator) {
|
||||||
$v = $v->{'@list'};
|
$this->_findAndRemovePropertyGeneratorDuplicates(
|
||||||
|
$active_ctx, $element, $expanded_property, $expanded_item,
|
||||||
|
$active_property);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursively compact value
|
// get @list value if appropriate
|
||||||
$v = $this->_compact($ctx, $prop, $v, $options);
|
$is_list = self::_isList($expanded_item);
|
||||||
|
$list = null;
|
||||||
|
if($is_list) {
|
||||||
|
$list = $expanded_item->{'@list'};
|
||||||
|
}
|
||||||
|
|
||||||
// get container type for property
|
// recursively compact expanded item
|
||||||
$container = self::getContextValue($ctx, $prop, '@container');
|
$compacted_item = $this->_compact(
|
||||||
|
$active_ctx, $active_property, $is_list ? $list : $expanded_item,
|
||||||
|
$options);
|
||||||
|
|
||||||
// handle @list
|
// handle @list
|
||||||
if($is_list && $container !== '@list') {
|
if($is_list) {
|
||||||
// handle messy @list compaction
|
// ensure $list value is an array
|
||||||
if(property_exists($rval, $prop) && $options['strict']) {
|
$compacted_item = self::arrayify($compacted_item);
|
||||||
|
|
||||||
|
if($container !== '@list') {
|
||||||
|
// wrap using @list alias
|
||||||
|
$compacted_item = (object)array(
|
||||||
|
$this->_compactIri($active_ctx, '@list'), $compacted_item);
|
||||||
|
|
||||||
|
// include @index from expanded @list, if any
|
||||||
|
if(property_exists($expanded_item, '@index')) {
|
||||||
|
$compacted_item->{$this->_compactIri($active_ctx, '@index')} =
|
||||||
|
$expanded_item->{'@index'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// can't use @list container for more than 1 list
|
||||||
|
else if(property_exists($rval, $active_property)) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'JSON-LD compact error; property has a "@list" @container ' +
|
'JSON-LD compact error; property has a "@list" @container ' .
|
||||||
'rule but there is more than a single @list that matches ' +
|
'rule but there is more than a single @list that matches ' .
|
||||||
'the compacted term in the document. Compaction might mix ' +
|
'the compacted term in the document. Compaction might mix ' .
|
||||||
'unwanted items into the list.',
|
'unwanted items into the list.',
|
||||||
'jsonld.SyntaxError');
|
'jsonld.SyntaxError');
|
||||||
}
|
}
|
||||||
// reintroduce @list keyword
|
|
||||||
$kwlist = $this->_compactIri($ctx, '@list');
|
|
||||||
$val = new stdClass();
|
|
||||||
$val->{$kwlist} = $v;
|
|
||||||
$v = $val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if @container is @set or @list or value is an empty array, use
|
// handle language and index maps
|
||||||
// an array when adding value
|
if($container === '@language' || $container === '@index') {
|
||||||
$is_array = ($container === '@set' || $container === '@list' ||
|
// get or create the map object
|
||||||
(is_array($v) && count($v) === 0));
|
if(property_exists($rval, $active_property)) {
|
||||||
|
$map_object = $rval->{$active_property};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rval->{$active_property} = $map_object = new stdClass();
|
||||||
|
}
|
||||||
|
|
||||||
// add compact value
|
// if container is a language map, simplify compacted value to
|
||||||
self::addValue(
|
// a simple string
|
||||||
$rval, $prop, $v, array('propertyIsArray' => $is_array));
|
if($container === '@language' && self::_isValue($compacted_item)) {
|
||||||
|
$compacted_item = $compacted_item->{'@value'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// add compact value to map object using key from expanded value
|
||||||
|
// based on the container type
|
||||||
|
self::addValue(
|
||||||
|
$map_object, $expanded_item->{$container}, $compacted_item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// use an array if: @container is @set or @list , value is an empty
|
||||||
|
// array, or key is @graph
|
||||||
|
$is_array = ($container === '@set' || $container === '@list' ||
|
||||||
|
(is_array($compacted_item) && count($compacted_item) === 0) ||
|
||||||
|
$expanded_property === '@graph');
|
||||||
|
|
||||||
|
// add compact value
|
||||||
|
self::addValue(
|
||||||
|
$rval, $active_property, $compacted_item,
|
||||||
|
array('propertyIsArray' => $is_array));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rval;
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1393,23 +1605,29 @@ class JsonLdProcessor {
|
||||||
* the element will be removed. All context URLs must have been resolved
|
* the element will be removed. All context URLs must have been resolved
|
||||||
* before calling this method.
|
* before calling this method.
|
||||||
*
|
*
|
||||||
* @param stdClass $ctx the context to use.
|
* @param stdClass $active_ctx the active context to use.
|
||||||
* @param mixed $property the property for the element, null for none.
|
* @param mixed $active_property the property for the element, null for none.
|
||||||
* @param mixed $element the element to expand.
|
* @param mixed $element the element to expand.
|
||||||
* @param array $options the expansion options.
|
* @param assoc $options the expansion options.
|
||||||
* @param bool $propertyIsList true if the property is a list, false if not.
|
* @param bool $inside_list true if the property is a list, false if not.
|
||||||
*
|
*
|
||||||
* @return mixed the expanded value.
|
* @return mixed the expanded value.
|
||||||
*/
|
*/
|
||||||
protected function _expand(
|
protected function _expand(
|
||||||
$ctx, $property, $element, $options, $propertyIsList) {
|
$active_ctx, $active_property, $element, $options, $inside_list) {
|
||||||
|
// nothing to expand
|
||||||
|
if($element === null) {
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
|
||||||
// recursively expand array
|
// recursively expand array
|
||||||
if(is_array($element)) {
|
if(is_array($element)) {
|
||||||
$rval = array();
|
$rval = array();
|
||||||
foreach($element as $e) {
|
foreach($element as $e) {
|
||||||
// expand element
|
// expand element
|
||||||
$e = $this->_expand($ctx, $property, $e, $options, $propertyIsList);
|
$e = $this->_expand(
|
||||||
if(is_array($e) && $propertyIsList) {
|
$active_ctx, $active_property, $e, $options, $inside_list);
|
||||||
|
if($inside_list && (is_array($e) || self::_isList($e))) {
|
||||||
// lists of lists are illegal
|
// lists of lists are illegal
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; lists of lists are not permitted.',
|
'Invalid JSON-LD syntax; lists of lists are not permitted.',
|
||||||
|
|
@ -1417,179 +1635,342 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
// drop null values
|
// drop null values
|
||||||
else if($e !== null) {
|
else if($e !== null) {
|
||||||
$rval[] = $e;
|
if(is_array($e)) {
|
||||||
|
$rval = array_merge($rval, $e);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rval[] = $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $rval;
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
// expand non-object element according to value expansion rules
|
// recursively expand object
|
||||||
if(!is_object($element)) {
|
if(is_object($element)) {
|
||||||
return $this->_expandValue($ctx, $property, $element, $options['base']);
|
// if element has a context, process it
|
||||||
}
|
if(property_exists($element, '@context')) {
|
||||||
|
$active_ctx = $this->_processContext($active_ctx, $element->{'@context'}, $options);
|
||||||
// Note: element must be an object, recursively expand it
|
unset($element->{'@context'});
|
||||||
|
|
||||||
// if element has a context, process it
|
|
||||||
if(property_exists($element, '@context')) {
|
|
||||||
$ctx = $this->_processContext($ctx, $element->{'@context'}, $options);
|
|
||||||
unset($element->{'@context'});
|
|
||||||
}
|
|
||||||
|
|
||||||
$rval = new stdClass();
|
|
||||||
foreach($element as $key => $value) {
|
|
||||||
// expand property
|
|
||||||
$prop = $this->_expandTerm($ctx, $key);
|
|
||||||
|
|
||||||
// drop non-absolute IRI keys that aren't keywords
|
|
||||||
if(!self::_isAbsoluteIri($prop) && !self::_isKeyword($prop, $ctx)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if value is null and property is not @value, continue
|
// expand the active property
|
||||||
$value = $element->{$key};
|
$expanded_active_property = $this->_expandIri(
|
||||||
if($value === null && $prop !== '@value') {
|
$active_ctx, $active_property);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// syntax error if @id is not a string
|
$rval = new stdClass();
|
||||||
if($prop === '@id' && !is_string($value)) {
|
$keys = array_keys((array)$element);
|
||||||
throw new JsonLdException(
|
sort($keys);
|
||||||
'Invalid JSON-LD syntax; "@id" value must a string.',
|
foreach($keys as $key) {
|
||||||
'jsonld.SyntaxError', array('value' => $value));
|
$value = $element->{$key};
|
||||||
}
|
|
||||||
|
|
||||||
// validate @type value
|
// expand key using property generator
|
||||||
if($prop === '@type') {
|
if(property_exists($active_ctx->mappings, $key)) {
|
||||||
$this->_validateTypeValue($value);
|
$mapping = $active_ctx->mappings->{$key};
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$mapping = null;
|
||||||
|
}
|
||||||
|
|
||||||
// @graph must be an array or an object
|
if($mapping && $mapping->propertyGenerator) {
|
||||||
if($prop === '@graph' && !(is_object($value) || is_array($value))) {
|
$expanded_property = $mapping->{'@id'};
|
||||||
throw new JsonLdException(
|
}
|
||||||
'Invalid JSON-LD syntax; "@value" value must not be an ' +
|
// expand key to IRI
|
||||||
'object or an array.',
|
else {
|
||||||
'jsonld.SyntaxError', array('value' => $value));
|
$expanded_property = $this->_expandIri(
|
||||||
}
|
$active_ctx, $key, array('vocab' => true));
|
||||||
|
}
|
||||||
|
|
||||||
// @value must not be an object or an array
|
// drop non-absolute IRI keys that aren't keywords
|
||||||
if($prop === '@value' && (is_object($value) || is_array($value))) {
|
if($expanded_property === null ||
|
||||||
throw new JsonLdException(
|
!(is_array($expanded_property) ||
|
||||||
'Invalid JSON-LD syntax; "@value" value must not be an ' +
|
self::_isAbsoluteIri($expanded_property) ||
|
||||||
'object or an array.',
|
self::_isKeyword($expanded_property))) {
|
||||||
'jsonld.SyntaxError', array('value' => $value));
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @language must be a string
|
// syntax error if @id is not a string
|
||||||
if($prop === '@language' && !is_string($value)) {
|
if($expanded_property === '@id' && !is_string($value)) {
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; "@language" value must not be a string.',
|
|
||||||
'jsonld.SyntaxError', array('value' => $value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// recurse into @list or @set keeping the active property
|
|
||||||
$is_list = ($prop === '@list');
|
|
||||||
if($is_list || $prop === '@set') {
|
|
||||||
$value = $this->_expand($ctx, $property, $value, $options, $is_list);
|
|
||||||
if($is_list && self::_isList($value)) {
|
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; lists of lists are not permitted.',
|
'Invalid JSON-LD syntax; "@id" value must a string.',
|
||||||
'jsonld.SyntaxError');
|
'jsonld.SyntaxError', array('value' => $value));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
// update active property and recursively expand value
|
|
||||||
$property = $key;
|
|
||||||
$value = $this->_expand($ctx, $property, $value, $options, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop null values if property is not @value (dropped below)
|
// validate @type value
|
||||||
if($value !== null || $prop === '@value') {
|
if($expanded_property === '@type') {
|
||||||
// convert value to @list if container specifies it
|
$this->_validateTypeValue($value);
|
||||||
if($prop !== '@list' && !self::_isList($value)) {
|
}
|
||||||
$container = self::getContextValue($ctx, $property, '@container');
|
|
||||||
if($container === '@list') {
|
// @graph must be an array or an object
|
||||||
// ensure value is an array
|
if($expanded_property === '@graph' &&
|
||||||
$value = (object)array('@list' => self::arrayify($value));
|
!(is_object($value) || is_array($value))) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; "@value" value must not be an ' .
|
||||||
|
'object or an array.',
|
||||||
|
'jsonld.SyntaxError', array('value' => $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @value must not be an object or an array
|
||||||
|
if($expanded_property === '@value' &&
|
||||||
|
(is_object($value) || is_array($value))) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; "@value" value must not be an ' .
|
||||||
|
'object or an array.',
|
||||||
|
'jsonld.SyntaxError', array('value' => $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @language must be a string
|
||||||
|
if($expanded_property === '@language' && !is_string($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; "@language" value must not be a string.',
|
||||||
|
'jsonld.SyntaxError', array('value' => $value));
|
||||||
|
// ensure language value is lowercase
|
||||||
|
$value = strtolower($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserve @index
|
||||||
|
if($expanded_property === '@index') {
|
||||||
|
if(!is_string($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; "@index" value must be a string.',
|
||||||
|
'jsonld.SyntaxError', array('value' => $value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optimize away @id for @type
|
$container = self::getContextValue($active_ctx, $key, '@container');
|
||||||
if($prop === '@type') {
|
|
||||||
if(self::_isSubjectReference($value)) {
|
// handle language map container (skip if value is not an object)
|
||||||
$value = $value->{'@id'};
|
if($container === '@language' && is_object($value)) {
|
||||||
}
|
$expanded_value = $this->_expandLanguageMap($value);
|
||||||
else if(is_array($value)) {
|
}
|
||||||
$val = array();
|
// handle index container (skip if value is not an object)
|
||||||
foreach($value as $v) {
|
else if($container === '@index' && is_object($value)) {
|
||||||
if(self::_isSubjectReference($v)) {
|
$expand_index_map = function($active_property) use (
|
||||||
$val[] = $v->{'@id'};
|
$active_ctx, $active_property, $options, $value) {
|
||||||
}
|
$rval = array();
|
||||||
else {
|
$keys = array_keys((array)$value);
|
||||||
$val[] = $v;
|
sort($keys);
|
||||||
|
foreach($keys as $key) {
|
||||||
|
$val = $value->{$key};
|
||||||
|
$val = self::arrayify($val);
|
||||||
|
$val = $this->_expand(
|
||||||
|
$active_ctx, $active_property, $val, $options, false);
|
||||||
|
foreach($val as $item) {
|
||||||
|
if(!property_exists($item, '@index')) {
|
||||||
|
$item->{'@index'} = $key;
|
||||||
|
}
|
||||||
|
$rval[] = $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$value = $val;
|
return $rval;
|
||||||
|
};
|
||||||
|
$expanded_value = $expand_index_map($key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// recurse into @list or @set keeping the active property
|
||||||
|
$is_list = ($expanded_property === '@list');
|
||||||
|
if($is_list || $expanded_property === '@set') {
|
||||||
|
$next_active_property = $active_property;
|
||||||
|
if($is_list && $expanded_active_property === '@graph') {
|
||||||
|
$next_active_property = null;
|
||||||
|
}
|
||||||
|
$expanded_value = $this->_expand(
|
||||||
|
$active_ctx, $active_property, $value, $options, $is_list);
|
||||||
|
if($is_list && self::_isList($expanded_value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; lists of lists are not permitted.',
|
||||||
|
'jsonld.SyntaxError');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// recursively expand value with key as new active property
|
||||||
|
$expanded_value = $this->_expand(
|
||||||
|
$active_ctx, $key, $value, $options, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add value, use an array if not @id, @type, @value, or @language
|
// drop null values if property is not @value
|
||||||
$use_array = !($prop === '@id' || $prop === '@type' ||
|
if($expanded_value === null && $expanded_property !== '@value') {
|
||||||
$prop === '@value' || $prop === '@language');
|
continue;
|
||||||
self::addValue(
|
}
|
||||||
$rval, $prop, $value, array('propertyIsArray' => $use_array));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get property count on expanded output
|
// convert expanded value to @list if container specifies it
|
||||||
$count = count(get_object_vars($rval));
|
if($expanded_property !== '@list' && !self::_isList($expanded_value) &&
|
||||||
|
$container === '@list') {
|
||||||
|
// ensure expanded value is an array
|
||||||
|
$expanded_value = (object)array(
|
||||||
|
'@list' => self::arrayify($expanded_value));
|
||||||
|
}
|
||||||
|
|
||||||
// @value must only have @language or @type
|
// add copy of value for each property from property generator
|
||||||
if(property_exists($rval, '@value')) {
|
if(is_array($expanded_property)) {
|
||||||
if(($count === 2 && !property_exists($rval, '@type') &&
|
$this->_labelBlankNodes($active_ctx->namer, $expanded_value);
|
||||||
!property_exists($rval, '@language')) ||
|
foreach($expanded_property as $iri) {
|
||||||
$count > 2) {
|
self::addValue(
|
||||||
throw new JsonLdException(
|
$rval, $iri, self::copy($expanded_value),
|
||||||
'Invalid JSON-LD syntax; an element containing "@value" must have ' +
|
array('propertyIsArray' => true));
|
||||||
'at most one other property which can be "@type" or "@language".',
|
}
|
||||||
'jsonld.SyntaxError', array('element' => $rval));
|
}
|
||||||
|
// add value for property
|
||||||
|
else {
|
||||||
|
// use an array except for certain keywords
|
||||||
|
$use_array = (!in_array(
|
||||||
|
$expanded_property, array(
|
||||||
|
'@index', '@id', '@type', '@value', '@language')));
|
||||||
|
self::addValue(
|
||||||
|
$rval, $expanded_property, $expanded_value,
|
||||||
|
array('propertyIsArray' => $use_array));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// value @type must be a string
|
|
||||||
if(property_exists($rval, '@type') && !is_string($rval->{'@type'})) {
|
// get property count on expanded output
|
||||||
throw new JsonLdException(
|
$keys = array_keys((array)$rval);
|
||||||
'Invalid JSON-LD syntax; the "@type" value of an element ' +
|
$count = count($keys);
|
||||||
'containing "@value" must be a string.',
|
|
||||||
'jsonld.SyntaxError', array('element' => $rval));
|
// @value must only have @language or @type
|
||||||
|
if(property_exists($rval, '@value')) {
|
||||||
|
// @value must only have @language or @type
|
||||||
|
if(property_exists($rval, '@type') &&
|
||||||
|
property_exists($rval, '@language')) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; an element containing "@value" may not ' .
|
||||||
|
'contain both "@type" and "@language".',
|
||||||
|
'jsonld.SyntaxError', array('element' => $rval));
|
||||||
|
}
|
||||||
|
$valid_count = $count - 1;
|
||||||
|
if(property_exists($rval, '@type')) {
|
||||||
|
$valid_count -= 1;
|
||||||
|
}
|
||||||
|
if(property_exists($rval, '@index')) {
|
||||||
|
$valid_count -= 1;
|
||||||
|
}
|
||||||
|
if(property_exists($rval, '@language')) {
|
||||||
|
$valid_count -= 1;
|
||||||
|
}
|
||||||
|
if($valid_count !== 0) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; an element containing "@value" may only ' .
|
||||||
|
'have an "@index" property and at most one other property ' .
|
||||||
|
'which can be "@type" or "@language".',
|
||||||
|
'jsonld.SyntaxError', array('element' => $rval));
|
||||||
|
}
|
||||||
|
// drop null @values
|
||||||
|
if($rval->{'@value'} === null) {
|
||||||
|
$rval = null;
|
||||||
|
}
|
||||||
|
// drop @language if @value isn't a string
|
||||||
|
else if(property_exists($rval, '@language') &&
|
||||||
|
!is_string($rval->{'@value'})) {
|
||||||
|
unset($rval->{'@language'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// drop null @values
|
// convert @type to an array
|
||||||
else if($rval->{'@value'} === null) {
|
else if(property_exists($rval, '@type') && !is_array($rval->{'@type'})) {
|
||||||
|
$rval->{'@type'} = array($rval->{'@type'});
|
||||||
|
}
|
||||||
|
// handle @set and @list
|
||||||
|
else if(property_exists($rval, '@set') ||
|
||||||
|
property_exists($rval, '@list')) {
|
||||||
|
if($count > 1 && ($count !== 2 && property_exists($rval, '@index'))) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; if an element has the property "@set" ' .
|
||||||
|
'or "@list", then it can have at most one other property that is ' .
|
||||||
|
'"@index".',
|
||||||
|
'jsonld.SyntaxError', array('element' => $rval));
|
||||||
|
}
|
||||||
|
// optimize away @set
|
||||||
|
if(property_exists($rval, '@set')) {
|
||||||
|
$rval = $rval->{'@set'};
|
||||||
|
$keys = array_keys((array)$rval);
|
||||||
|
$count = count($keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// drop objects with only @language
|
||||||
|
else if($count === 1 && property_exists($rval, '@language')) {
|
||||||
$rval = null;
|
$rval = null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// convert @type to an array
|
// drop certain top-level objects that do not occur in lists
|
||||||
else if(property_exists($rval, '@type') && !is_array($rval->{'@type'})) {
|
if(is_object($rval) &&
|
||||||
$rval->{'@type'} = array($rval->{'@type'});
|
!$options['keepFreeFloatingNodes'] && !$inside_list &&
|
||||||
}
|
($active_property === null || $expanded_active_property === '@graph')) {
|
||||||
// handle @set and @list
|
// drop empty object or top-level @value
|
||||||
else if(property_exists($rval, '@set') ||
|
if($count === 0 || property_exists($rval, '@value')) {
|
||||||
property_exists($rval, '@list')) {
|
$rval = null;
|
||||||
if($count !== 1) {
|
}
|
||||||
throw new JsonLdException(
|
else {
|
||||||
'Invalid JSON-LD syntax; if an element has the property "@set" ' +
|
// drop subjects that generate no triples
|
||||||
'or "@list", then it must be its only property.',
|
$has_triples = false;
|
||||||
'jsonld.SyntaxError', array('element' => $rval));
|
$ignore = array('@graph', '@type', '@list');
|
||||||
|
foreach($keys as $key) {
|
||||||
|
if(!self::_isKeyword($key) || in_array($key, $ignore)) {
|
||||||
|
$has_triples = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$has_triples) {
|
||||||
|
$rval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// optimize away @set
|
|
||||||
if(property_exists($rval, '@set')) {
|
return $rval;
|
||||||
$rval = $rval->{'@set'};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// drop objects with only @language
|
|
||||||
else if(property_exists($rval, '@language') && $count === 1) {
|
|
||||||
$rval = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rval;
|
// drop top-level scalars that are not in lists
|
||||||
|
if(!$inside_list &&
|
||||||
|
($active_property === null ||
|
||||||
|
$this->_expandIri($active_ctx, $active_property) === '@graph')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand element according to value expansion rules
|
||||||
|
return $this->_expandValue($active_ctx, $active_property, $element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs JSON-LD flattening.
|
||||||
|
*
|
||||||
|
* @param array $input the expanded JSON-LD to flatten.
|
||||||
|
*
|
||||||
|
* @return array the flattened output.
|
||||||
|
*/
|
||||||
|
protected function _flatten($input) {
|
||||||
|
// produce a map of all subjects and name each bnode
|
||||||
|
$namer = new UniqueNamer('_:t');
|
||||||
|
$graphs = (object)array('@default' => new stdClass());
|
||||||
|
$this->_createNodeMap($input, $graphs, '@default', $namer);
|
||||||
|
|
||||||
|
// add all non-default graphs to default graph
|
||||||
|
$default_graph = $graphs->{'@default'};
|
||||||
|
foreach($graphs as $graph_name => $node_map) {
|
||||||
|
if($graph_name === '@default') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!property_exists($default_graph, $graph_name)) {
|
||||||
|
$default_graph->{$graph_name} = (object)array(
|
||||||
|
'@id' => $graph_name, '@graph' => array());
|
||||||
|
}
|
||||||
|
$subject = $default_graph->{$graph_name};
|
||||||
|
if(!property_exists($subject, '@graph')) {
|
||||||
|
$subject->{'@graph'} = array();
|
||||||
|
}
|
||||||
|
$nodeMap = $graphs->{$graph_name};
|
||||||
|
$ids = array_keys((array)$node_map);
|
||||||
|
sort($ids);
|
||||||
|
foreach($ids as $id) {
|
||||||
|
$subject->{'@graph'}[] = $node_map->{$id};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// produce flattened output
|
||||||
|
$flattened = array();
|
||||||
|
$keys = array_keys((array)$default_graph);
|
||||||
|
sort($keys);
|
||||||
|
foreach($keys as $key) {
|
||||||
|
$flattened[] = $default_graph->{$key};
|
||||||
|
}
|
||||||
|
return $flattened;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1597,7 +1978,7 @@ class JsonLdProcessor {
|
||||||
*
|
*
|
||||||
* @param array $input the expanded JSON-LD to frame.
|
* @param array $input the expanded JSON-LD to frame.
|
||||||
* @param array $frame the expanded JSON-LD frame to use.
|
* @param array $frame the expanded JSON-LD frame to use.
|
||||||
* @param array $options the framing options.
|
* @param assoc $options the framing options.
|
||||||
*
|
*
|
||||||
* @return array the framed output.
|
* @return array the framed output.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1630,7 +2011,7 @@ class JsonLdProcessor {
|
||||||
* @param array $input the expanded JSON-LD object to normalize.
|
* @param array $input the expanded JSON-LD object to normalize.
|
||||||
* @param assoc $options the normalization options.
|
* @param assoc $options the normalization options.
|
||||||
*
|
*
|
||||||
* @return the normalized output.
|
* @return mixed the normalized output.
|
||||||
*/
|
*/
|
||||||
protected function _normalize($input, $options) {
|
protected function _normalize($input, $options) {
|
||||||
// map bnodes to RDF statements
|
// map bnodes to RDF statements
|
||||||
|
|
@ -1933,128 +2314,7 @@ class JsonLdProcessor {
|
||||||
*/
|
*/
|
||||||
protected function _toRDF(
|
protected function _toRDF(
|
||||||
$element, $namer, $subject, $property, $graph, &$statements) {
|
$element, $namer, $subject, $property, $graph, &$statements) {
|
||||||
if(is_object($element)) {
|
// recurse into arrays
|
||||||
// convert @value to object
|
|
||||||
if(self::_isValue($element)) {
|
|
||||||
$value = $element->{'@value'};
|
|
||||||
$datatype = (property_exists($element, '@type') ?
|
|
||||||
$element->{'@type'} : null);
|
|
||||||
if(is_bool($value) || is_double($value) || is_integer($value)) {
|
|
||||||
// convert to XSD datatype
|
|
||||||
if(is_bool($value)) {
|
|
||||||
$value = ($value ? 'true' : 'false');
|
|
||||||
$datatype or $datatype = self::XSD_BOOLEAN;
|
|
||||||
}
|
|
||||||
else if(is_double($value)) {
|
|
||||||
// canonical double representation
|
|
||||||
$value = preg_replace('/(\d)0*E\+?/', '$1E',
|
|
||||||
sprintf('%1.15E', $value));
|
|
||||||
$datatype or $datatype = self::XSD_DOUBLE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$value = strval($value);
|
|
||||||
$datatype or $datatype = self::XSD_INTEGER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default to xsd:string datatype
|
|
||||||
$datatype or $datatype = self::XSD_STRING;
|
|
||||||
|
|
||||||
$object = (object)array(
|
|
||||||
'nominalValue' => $value,
|
|
||||||
'interfaceName' => 'LiteralNode',
|
|
||||||
'datatype' => (object)array(
|
|
||||||
'nominalValue' => $datatype,
|
|
||||||
'interfaceName' => 'IRI'));
|
|
||||||
if(property_exists($element, '@language') &&
|
|
||||||
$datatype === self::XSD_STRING) {
|
|
||||||
$object->language = $element->{'@language'};
|
|
||||||
}
|
|
||||||
|
|
||||||
// emit literal
|
|
||||||
$statement = (object)array(
|
|
||||||
'subject' => self::copy($subject),
|
|
||||||
'property' => self::copy($property),
|
|
||||||
'object' => $object);
|
|
||||||
if($graph !== null) {
|
|
||||||
$statement->name = $graph;
|
|
||||||
}
|
|
||||||
JsonLdProcessor::_appendUniqueRdfStatement($statements, $statement);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert @list
|
|
||||||
if(self::_isList($element)) {
|
|
||||||
$list = $this->_makeLinkedList($element);
|
|
||||||
$this->_toRDF($list, $namer, $subject, $property, $graph, $statements);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: element must be a subject
|
|
||||||
|
|
||||||
// get subject @id (generate one if it is a bnode)
|
|
||||||
$id = property_exists($element, '@id') ? $element->{'@id'} : null;
|
|
||||||
$is_bnode = self::_isBlankNode($element);
|
|
||||||
if($is_bnode) {
|
|
||||||
$id = $namer->getName($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create object
|
|
||||||
$object = (object)array(
|
|
||||||
'nominalValue' => $id,
|
|
||||||
'interfaceName' => $is_bnode ? 'BlankNode' : 'IRI');
|
|
||||||
|
|
||||||
// emit statement if subject isn't null
|
|
||||||
if($subject !== null) {
|
|
||||||
$statement = (object)array(
|
|
||||||
'subject' => self::copy($subject),
|
|
||||||
'property' => self::copy($property),
|
|
||||||
'object' => self::copy($object));
|
|
||||||
if($graph !== null) {
|
|
||||||
$statement->name = $graph;
|
|
||||||
}
|
|
||||||
JsonLdProcessor::_appendUniqueRdfStatement($statements, $statement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set new active subject to object
|
|
||||||
$subject = $object;
|
|
||||||
|
|
||||||
// recurse over subject properties in order
|
|
||||||
$props = array_keys((array)$element);
|
|
||||||
sort($props);
|
|
||||||
foreach($props as $prop) {
|
|
||||||
$p = $prop;
|
|
||||||
|
|
||||||
// convert @type to rdf:type
|
|
||||||
if($prop === '@type') {
|
|
||||||
$p = self::RDF_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// recurse into @graph
|
|
||||||
if($prop === '@graph') {
|
|
||||||
$this->_toRDF(
|
|
||||||
$element->{$prop}, $namer, null, null, $subject, $statements);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip keywords
|
|
||||||
if(self::_isKeyword($p)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new active property
|
|
||||||
$property = (object)array(
|
|
||||||
'nominalValue' => $p,
|
|
||||||
'interfaceName' => 'IRI');
|
|
||||||
|
|
||||||
// recurse into value
|
|
||||||
$this->_toRDF(
|
|
||||||
$element->{$prop}, $namer, $subject, $property, $graph, $statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_array($element)) {
|
if(is_array($element)) {
|
||||||
// recurse into arrays
|
// recurse into arrays
|
||||||
foreach($element as $e) {
|
foreach($element as $e) {
|
||||||
|
|
@ -2067,17 +2327,135 @@ class JsonLdProcessor {
|
||||||
if(is_string($element)) {
|
if(is_string($element)) {
|
||||||
// emit IRI
|
// emit IRI
|
||||||
$statement = (object)array(
|
$statement = (object)array(
|
||||||
'subject' => self::copy($subject),
|
'subject' => self::copy($subject),
|
||||||
'property' => self::copy($property),
|
'property' => self::copy($property),
|
||||||
'object' => (object)array(
|
'object' => (object)array(
|
||||||
'nominalValue' => $element,
|
'nominalValue' => $element,
|
||||||
'interfaceName' => 'IRI'));
|
'interfaceName' => 'IRI'));
|
||||||
if($graph !== null) {
|
if($graph !== null) {
|
||||||
$statement->name = $graph;
|
$statement->name = $graph;
|
||||||
}
|
}
|
||||||
JsonLdProcessor::_appendUniqueRdfStatement($statements, $statement);
|
self::_appendUniqueRdfStatement($statements, $statement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert @list
|
||||||
|
if(self::_isList($element)) {
|
||||||
|
$list = $this->_makeLinkedList($element);
|
||||||
|
$this->_toRDF($list, $namer, $subject, $property, $graph, $statements);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert @value to object
|
||||||
|
if(self::_isValue($element)) {
|
||||||
|
$value = $element->{'@value'};
|
||||||
|
$datatype = (property_exists($element, '@type') ?
|
||||||
|
$element->{'@type'} : null);
|
||||||
|
if(is_bool($value) || is_double($value) || is_integer($value)) {
|
||||||
|
// convert to XSD datatypes as appropriate
|
||||||
|
if(is_bool($value)) {
|
||||||
|
$value = ($value ? 'true' : 'false');
|
||||||
|
$datatype or $datatype = self::XSD_BOOLEAN;
|
||||||
|
}
|
||||||
|
else if(is_double($value)) {
|
||||||
|
// canonical double representation
|
||||||
|
$value = preg_replace('/(\d)0*E\+?/', '$1E',
|
||||||
|
sprintf('%1.15E', $value));
|
||||||
|
$datatype or $datatype = self::XSD_DOUBLE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$value = strval($value);
|
||||||
|
$datatype or $datatype = self::XSD_INTEGER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to xsd:string datatype
|
||||||
|
$datatype or $datatype = self::XSD_STRING;
|
||||||
|
|
||||||
|
$object = (object)array(
|
||||||
|
'nominalValue' => $value,
|
||||||
|
'interfaceName' => 'LiteralNode',
|
||||||
|
'datatype' => (object)array(
|
||||||
|
'nominalValue' => $datatype,
|
||||||
|
'interfaceName' => 'IRI'));
|
||||||
|
if(property_exists($element, '@language') &&
|
||||||
|
$datatype === self::XSD_STRING) {
|
||||||
|
$object->language = $element->{'@language'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit literal
|
||||||
|
$statement = (object)array(
|
||||||
|
'subject' => self::copy($subject),
|
||||||
|
'property' => self::copy($property),
|
||||||
|
'object' => $object);
|
||||||
|
if($graph !== null) {
|
||||||
|
$statement->name = $graph;
|
||||||
|
}
|
||||||
|
self::_appendUniqueRdfStatement($statements, $statement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: element must be a subject
|
||||||
|
|
||||||
|
// get subject @id (generate one if it is a bnode)
|
||||||
|
$id = property_exists($element, '@id') ? $element->{'@id'} : null;
|
||||||
|
$is_bnode = self::_isBlankNode($element);
|
||||||
|
if($is_bnode) {
|
||||||
|
$id = $namer->getName($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create object
|
||||||
|
$object = (object)array(
|
||||||
|
'nominalValue' => $id,
|
||||||
|
'interfaceName' => $is_bnode ? 'BlankNode' : 'IRI');
|
||||||
|
|
||||||
|
// emit statement if subject isn't null
|
||||||
|
if($subject !== null) {
|
||||||
|
$statement = (object)array(
|
||||||
|
'subject' => self::copy($subject),
|
||||||
|
'property' => self::copy($property),
|
||||||
|
'object' => self::copy($object));
|
||||||
|
if($graph !== null) {
|
||||||
|
$statement->name = $graph;
|
||||||
|
}
|
||||||
|
self::_appendUniqueRdfStatement($statements, $statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set new active subject to object
|
||||||
|
$subject = $object;
|
||||||
|
|
||||||
|
// recurse over subject properties in order
|
||||||
|
$props = array_keys((array)$element);
|
||||||
|
sort($props);
|
||||||
|
foreach($props as $prop) {
|
||||||
|
$p = $prop;
|
||||||
|
|
||||||
|
// convert @type to rdf:type
|
||||||
|
if($prop === '@type') {
|
||||||
|
$p = self::RDF_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recurse into @graph
|
||||||
|
if($prop === '@graph') {
|
||||||
|
$this->_toRDF(
|
||||||
|
$element->{$prop}, $namer, null, null, $subject, $statements);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip keywords
|
||||||
|
if(self::_isKeyword($p)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new active property
|
||||||
|
$property = (object)array(
|
||||||
|
'nominalValue' => $p,
|
||||||
|
'interfaceName' => 'IRI');
|
||||||
|
|
||||||
|
// recurse into value
|
||||||
|
$this->_toRDF(
|
||||||
|
$element->{$prop}, $namer, $subject, $property, $graph, $statements);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2085,13 +2463,25 @@ class JsonLdProcessor {
|
||||||
*
|
*
|
||||||
* @param stdClass $active_ctx the current active context.
|
* @param stdClass $active_ctx the current active context.
|
||||||
* @param mixed $local_ctx the local context to process.
|
* @param mixed $local_ctx the local context to process.
|
||||||
* @param array $options the context processing options.
|
* @param assoc $options the context processing options.
|
||||||
*
|
*
|
||||||
* @return stdClass the new active context.
|
* @return stdClass the new active context.
|
||||||
*/
|
*/
|
||||||
protected function _processContext($active_ctx, $local_ctx, $options) {
|
protected function _processContext($active_ctx, $local_ctx, $options) {
|
||||||
|
global $jsonld_cache;
|
||||||
|
|
||||||
|
$rval = null;
|
||||||
|
|
||||||
|
// get context from cache if available
|
||||||
|
if(property_exists($jsonld_cache, 'activeCtx')) {
|
||||||
|
$rval = $jsonld_cache->activeCtx->get($active_ctx, $local_ctx);
|
||||||
|
if($rval) {
|
||||||
|
return $rval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// initialize the resulting context
|
// initialize the resulting context
|
||||||
$rval = self::copy($active_ctx);
|
$rval = self::_cloneActiveContext($active_ctx);
|
||||||
|
|
||||||
// normalize local context to an array
|
// normalize local context to an array
|
||||||
if(is_object($local_ctx) && property_exists($local_ctx, '@context') &&
|
if(is_object($local_ctx) && property_exists($local_ctx, '@context') &&
|
||||||
|
|
@ -2104,7 +2494,8 @@ class JsonLdProcessor {
|
||||||
foreach($ctxs as $ctx) {
|
foreach($ctxs as $ctx) {
|
||||||
// reset to initial context
|
// reset to initial context
|
||||||
if($ctx === null) {
|
if($ctx === null) {
|
||||||
$rval = $this->_getInitialContext();
|
$rval = $this->_getInitialContext($options);
|
||||||
|
$rval->namer = $active_ctx->namer;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2122,64 +2513,198 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// define context mappings for keys in local context
|
// define context mappings for keys in local context
|
||||||
$defined = new stdClass();
|
$defined = new stdClass();
|
||||||
foreach($ctx as $k => $v) {
|
|
||||||
$this->_defineContextMapping(
|
// handle @vocab
|
||||||
$rval, $ctx, $k, $options['base'], $defined);
|
if(property_exists($ctx, '@vocab')) {
|
||||||
|
$value = $ctx->{'@vocab'};
|
||||||
|
if($value === null) {
|
||||||
|
unset($rval->{'@vocab'});
|
||||||
|
}
|
||||||
|
else if(!is_string($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; the value of "@vocab" in a ' .
|
||||||
|
'@context must be a string or null.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $ctx));
|
||||||
|
}
|
||||||
|
else if(!self::_isAbsoluteIri($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; the value of "@vocab" in a ' .
|
||||||
|
'@context must be an absolute IRI.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $ctx));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rval->{'@vocab'} = $value;
|
||||||
|
}
|
||||||
|
$defined->{'@vocab'} = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle @language
|
||||||
|
if(property_exists($ctx, '@language')) {
|
||||||
|
$value = $ctx->{'@language'};
|
||||||
|
if($value === null) {
|
||||||
|
unset($rval->{'@language'});
|
||||||
|
}
|
||||||
|
else if(!is_string($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; the value of "@language" in a ' .
|
||||||
|
'@context must be a string or null.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $ctx));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rval->{'@language'} = strtolower($value);
|
||||||
|
}
|
||||||
|
$defined->{'@language'} = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process all other keys
|
||||||
|
foreach($ctx as $k => $v) {
|
||||||
|
$this->_createTermDefinition($rval, $ctx, $k, $defined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache result
|
||||||
|
if(property_exists($jsonld_cache, 'activeCtx')) {
|
||||||
|
$jsonld_cache->activeCtx->set($active_ctx, $local_ctx, $rval);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rval;
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands a language map.
|
||||||
|
*
|
||||||
|
* @param stdClass $language_map the language map to expand.
|
||||||
|
*
|
||||||
|
* @return array the expanded language map.
|
||||||
|
*/
|
||||||
|
protected function _expandLanguageMap($language_map) {
|
||||||
|
$rval = array();
|
||||||
|
$keys = array_keys((array)$language_map);
|
||||||
|
sort($keys);
|
||||||
|
foreach($keys as $key) {
|
||||||
|
$values = $language_map->{$key};
|
||||||
|
$values = self::arrayify($values);
|
||||||
|
foreach($values as $item) {
|
||||||
|
if(!is_string($item)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; language map values must be strings.',
|
||||||
|
'jsonld.SyntaxError', array('languageMap', $language_map));
|
||||||
|
}
|
||||||
|
$rval[] = (object)array(
|
||||||
|
'@value' => $item,
|
||||||
|
'@language' => strtolower($key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Labels the blank nodes in the given value using the given UniqueNamer.
|
||||||
|
*
|
||||||
|
* @param UniqueNamer $namer the UniqueNamer to use.
|
||||||
|
* @param mixed $element the element with blank nodes to rename.
|
||||||
|
* @param bool $is_id true if the given element is an @id (or @type).
|
||||||
|
*
|
||||||
|
* @return mixed the element.
|
||||||
|
*/
|
||||||
|
protected function _labelBlankNodes($namer, $element, $is_id=false) {
|
||||||
|
if(is_array($element)) {
|
||||||
|
for($i = 0; $i < count($element); ++$i) {
|
||||||
|
$element[$i] = $this->_labelBlankNodes($namer, $element[$i], $is_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(self::_isList($element)) {
|
||||||
|
$element->{'@list'} = $this->_labelBlankNodes(
|
||||||
|
$namer, $element->{'@list'}, $is_id);
|
||||||
|
}
|
||||||
|
else if(is_object($element)) {
|
||||||
|
// rename blank node
|
||||||
|
if(self::_isBlankNode($element)) {
|
||||||
|
$name = null;
|
||||||
|
if(property_exists($element, '@id')) {
|
||||||
|
$name = $element->{'@id'};
|
||||||
|
}
|
||||||
|
$element->{'@id'} = $namer->getName($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively apply to all keys
|
||||||
|
$keys = array_keys((array)$element);
|
||||||
|
sort($keys);
|
||||||
|
foreach($keys as $key) {
|
||||||
|
if($key !== '@id') {
|
||||||
|
$element->{$key} = $this->_labelBlankNodes(
|
||||||
|
$namer, $element->{$key}, $key === '@type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// rename blank node identifier
|
||||||
|
else if(is_string($element) && $is_id && strpos($element, '_:') === 0) {
|
||||||
|
$element = $namer->getName($element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expands the given value by using the coercion and keyword rules in the
|
* Expands the given value by using the coercion and keyword rules in the
|
||||||
* given context.
|
* given context.
|
||||||
*
|
*
|
||||||
* @param stdClass $ctx the active context to use.
|
* @param stdClass $active_ctx the active context to use.
|
||||||
* @param string $property the property the value is associated with.
|
* @param string $active_property the property the value is associated with.
|
||||||
* @param mixed $value the value to expand.
|
* @param mixed $value the value to expand.
|
||||||
* @param string $base the base IRI to use.
|
|
||||||
*
|
*
|
||||||
* @return mixed the expanded value.
|
* @return mixed the expanded value.
|
||||||
*/
|
*/
|
||||||
protected function _expandValue($ctx, $property, $value, $base) {
|
protected function _expandValue($active_ctx, $active_property, $value) {
|
||||||
// nothing to expand
|
// nothing to expand
|
||||||
if($value === null) {
|
if($value === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// default to simple string return value
|
|
||||||
$rval = $value;
|
|
||||||
|
|
||||||
// special-case expand @id and @type (skips '@id' expansion)
|
// special-case expand @id and @type (skips '@id' expansion)
|
||||||
$prop = $this->_expandTerm($ctx, $property);
|
$expanded_property = $this->_expandIri(
|
||||||
if($prop === '@id' || $prop === '@type') {
|
$active_ctx, $active_property, array('vocab' => true));
|
||||||
$rval = $this->_expandTerm($ctx, $value, $base);
|
if($expanded_property === '@id') {
|
||||||
|
return $this->_expandIri($active_ctx, $value, array('base' => true));
|
||||||
|
}
|
||||||
|
else if($expanded_property === '@type') {
|
||||||
|
return $this->_expandIri(
|
||||||
|
$active_ctx, $value, array('vocab' => true, 'base' => true));
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// get type definition from context
|
|
||||||
$type = self::getContextValue($ctx, $property, '@type');
|
|
||||||
|
|
||||||
// do @id expansion (automatic for @graph)
|
// get type definition from context
|
||||||
if($type === '@id' || $prop === '@graph') {
|
$type = self::getContextValue($active_ctx, $active_property, '@type');
|
||||||
$rval = (object)array('@id' => $this->_expandTerm($ctx, $value, $base));
|
|
||||||
|
// do @id expansion (automatic for @graph)
|
||||||
|
if($type === '@id' || $expanded_property === '@graph') {
|
||||||
|
return (object)array('@id' => $this->_expandIri(
|
||||||
|
$active_ctx, $value, array('base' => true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not expand keyword values
|
||||||
|
if(self::_isKeyword($expanded_property)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rval = new stdClass();
|
||||||
|
|
||||||
|
// other type
|
||||||
|
if($type !== null) {
|
||||||
|
// rename blank node if requested
|
||||||
|
if($active_ctx->namer !== null && strpos($type, '_:') === 0) {
|
||||||
|
$type = $active_ctx->namer->getName($type);
|
||||||
}
|
}
|
||||||
else if(!self::_isKeyword($prop)) {
|
$rval->{'@type'} = $type;
|
||||||
$rval = (object)array('@value' => $value);
|
}
|
||||||
|
// check for language tagging for strings
|
||||||
// other type
|
else if(is_string($value)) {
|
||||||
if($type !== null) {
|
$language = self::getContextValue(
|
||||||
$rval->{'@type'} = $type;
|
$active_ctx, $active_property, '@language');
|
||||||
}
|
if($language !== null) {
|
||||||
// check for language tagging
|
$rval->{'@language'} = $language;
|
||||||
else {
|
|
||||||
$language = self::getContextValue($ctx, $property, '@language');
|
|
||||||
if($language !== null) {
|
|
||||||
$rval->{'@language'} = $language;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$rval->{'@value'} = $value;
|
||||||
|
|
||||||
return $rval;
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
@ -2230,8 +2755,10 @@ class JsonLdProcessor {
|
||||||
$rval->{'@value'} = doubleval($rval->{'@value'});
|
$rval->{'@value'} = doubleval($rval->{'@value'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// do not add xsd:string type
|
// do not add native type
|
||||||
if($type !== self::XSD_STRING) {
|
if(!in_array($type, array(
|
||||||
|
self::XSD_BOOLEAN, self::XSD_INTEGER, self::XSD_DOUBLE,
|
||||||
|
self::XSD_STRING))) {
|
||||||
$rval->{'@type'} = $type;
|
$rval->{'@type'} = $type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2257,10 +2784,8 @@ class JsonLdProcessor {
|
||||||
* @return stdClass the head of the linked list of blank nodes.
|
* @return stdClass the head of the linked list of blank nodes.
|
||||||
*/
|
*/
|
||||||
protected function _makeLinkedList($value) {
|
protected function _makeLinkedList($value) {
|
||||||
// convert @list array into embedded blank node linked list
|
// convert @list array into embedded blank node linked list in reverse
|
||||||
$list = $value->{'@list'};
|
$list = $value->{'@list'};
|
||||||
|
|
||||||
// build linked list in reverse
|
|
||||||
$len = count($list);
|
$len = count($list);
|
||||||
$tail = (object)array('@id' => self::RDF_NIL);
|
$tail = (object)array('@id' => self::RDF_NIL);
|
||||||
for($i = $len - 1; $i >= 0; --$i) {
|
for($i = $len - 1; $i >= 0; --$i) {
|
||||||
|
|
@ -2268,196 +2793,12 @@ class JsonLdProcessor {
|
||||||
self::RDF_FIRST => array($list[$i]),
|
self::RDF_FIRST => array($list[$i]),
|
||||||
self::RDF_REST => array($tail));
|
self::RDF_REST => array($tail));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tail;
|
return $tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hashes all of the statements about a blank node.
|
* Recursively flattens the subjects in the given JSON-LD expanded input
|
||||||
*
|
* into a node map.
|
||||||
* @param string $id the ID of the bnode to hash statements for.
|
|
||||||
* @param stdClass $bnodes the mapping of bnodes to statements.
|
|
||||||
* @param UniqueNamer $namer the canonical bnode namer.
|
|
||||||
*
|
|
||||||
* @return string the new hash.
|
|
||||||
*/
|
|
||||||
protected function _hashStatements($id, $bnodes, $namer) {
|
|
||||||
// return cached hash
|
|
||||||
if(property_exists($bnodes->{$id}, 'hash')) {
|
|
||||||
return $bnodes->{$id}->hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize all of bnode's statements
|
|
||||||
$statements = $bnodes->{$id}->statements;
|
|
||||||
$nquads = array();
|
|
||||||
foreach($statements as $statement) {
|
|
||||||
$nquads[] = $this->toNQuad($statement, $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort serialized quads
|
|
||||||
sort($nquads);
|
|
||||||
|
|
||||||
// cache and return hashed quads
|
|
||||||
$hash = $bnodes->{$id}->hash = sha1(implode($nquads));
|
|
||||||
return $hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Produces a hash for the paths of adjacent bnodes for a bnode,
|
|
||||||
* incorporating all information about its subgraph of bnodes. This
|
|
||||||
* method will recursively pick adjacent bnode permutations that produce the
|
|
||||||
* lexicographically-least 'path' serializations.
|
|
||||||
*
|
|
||||||
* @param string $id the ID of the bnode to hash paths for.
|
|
||||||
* @param stdClass $bnodes the map of bnode statements.
|
|
||||||
* @param UniqueNamer $namer the canonical bnode namer.
|
|
||||||
* @param UniqueNamer $path_namer the namer used to assign names to adjacent
|
|
||||||
* bnodes.
|
|
||||||
*
|
|
||||||
* @return stdClass the hash and path namer used.
|
|
||||||
*/
|
|
||||||
protected function _hashPaths($id, $bnodes, $namer, $path_namer) {
|
|
||||||
// create SHA-1 digest
|
|
||||||
$md = hash_init('sha1');
|
|
||||||
|
|
||||||
// group adjacent bnodes by hash, keep properties and references separate
|
|
||||||
$groups = new stdClass();
|
|
||||||
$statements = $bnodes->{$id}->statements;
|
|
||||||
foreach($statements as $statement) {
|
|
||||||
// get adjacent bnode
|
|
||||||
$bnode = $this->_getAdjacentBlankNodeName($statement->subject, $id);
|
|
||||||
if($bnode !== null) {
|
|
||||||
$direction = 'p';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$bnode = $this->_getAdjacentBlankNodeName($statement->object, $id);
|
|
||||||
if($bnode !== null) {
|
|
||||||
$direction = 'r';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($bnode !== null) {
|
|
||||||
// get bnode name (try canonical, path, then hash)
|
|
||||||
if($namer->isNamed($bnode)) {
|
|
||||||
$name = $namer->getName($bnode);
|
|
||||||
}
|
|
||||||
else if($path_namer->isNamed($bnode)) {
|
|
||||||
$name = $path_namer->getName($bnode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$name = $this->_hashStatements($bnode, $bnodes, $namer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash direction, property, and bnode name/hash
|
|
||||||
$group_md = hash_init('sha1');
|
|
||||||
hash_update($group_md, $direction);
|
|
||||||
hash_update($group_md, $statement->property->nominalValue);
|
|
||||||
hash_update($group_md, $name);
|
|
||||||
$group_hash = hash_final($group_md);
|
|
||||||
|
|
||||||
// add bnode to hash group
|
|
||||||
if(property_exists($groups, $group_hash)) {
|
|
||||||
$groups->{$group_hash}[] = $bnode;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$groups->{$group_hash} = array($bnode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate over groups in sorted hash order
|
|
||||||
$group_hashes = array_keys((array)$groups);
|
|
||||||
sort($group_hashes);
|
|
||||||
foreach($group_hashes as $group_hash) {
|
|
||||||
// digest group hash
|
|
||||||
hash_update($md, $group_hash);
|
|
||||||
|
|
||||||
// choose a path and namer from the permutations
|
|
||||||
$chosen_path = null;
|
|
||||||
$chosen_namer = null;
|
|
||||||
$permutator = new Permutator($groups->{$group_hash});
|
|
||||||
while($permutator->hasNext()) {
|
|
||||||
$permutation = $permutator->next();
|
|
||||||
$path_namer_copy = clone $path_namer;
|
|
||||||
|
|
||||||
// build adjacent path
|
|
||||||
$path = '';
|
|
||||||
$skipped = false;
|
|
||||||
$recurse = array();
|
|
||||||
foreach($permutation as $bnode) {
|
|
||||||
// use canonical name if available
|
|
||||||
if($namer->isNamed($bnode)) {
|
|
||||||
$path .= $namer->getName($bnode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// recurse if bnode isn't named in the path yet
|
|
||||||
if(!$path_namer_copy->isNamed($bnode)) {
|
|
||||||
$recurse[] = $bnode;
|
|
||||||
}
|
|
||||||
$path .= $path_namer_copy->getName($bnode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip permutation if path is already >= chosen path
|
|
||||||
if($chosen_path !== null && strlen($path) >= strlen($chosen_path) &&
|
|
||||||
$path > $chosen_path) {
|
|
||||||
$skipped = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recurse
|
|
||||||
if(!$skipped) {
|
|
||||||
foreach($recurse as $bnode) {
|
|
||||||
$result = $this->_hashPaths(
|
|
||||||
$bnode, $bnodes, $namer, $path_namer_copy);
|
|
||||||
$path .= $path_namer_copy->getName($bnode);
|
|
||||||
$path .= "<{$result->hash}>";
|
|
||||||
$path_namer_copy = $result->pathNamer;
|
|
||||||
|
|
||||||
// skip permutation if path is already >= chosen path
|
|
||||||
if($chosen_path !== null &&
|
|
||||||
strlen($path) >= strlen($chosen_path) && $path > $chosen_path) {
|
|
||||||
$skipped = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$skipped && ($chosen_path === null || $path < $chosen_path)) {
|
|
||||||
$chosen_path = $path;
|
|
||||||
$chosen_namer = $path_namer_copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// digest chosen path and update namer
|
|
||||||
hash_update($md, $chosen_path);
|
|
||||||
$path_namer = $chosen_namer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return SHA-1 hash and path namer
|
|
||||||
return (object)array(
|
|
||||||
'hash' => hash_final($md), 'pathNamer' => $path_namer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function that gets the blank node name from an RDF statement
|
|
||||||
* node (subject or object). If the node is not a blank node or its
|
|
||||||
* nominal value does not match the given blank node ID, it will be
|
|
||||||
* returned.
|
|
||||||
*
|
|
||||||
* @param stdClass $node the RDF statement node.
|
|
||||||
* @param string $id the ID of the blank node to look next to.
|
|
||||||
*
|
|
||||||
* @return mixed the adjacent blank node name or null if none was found.
|
|
||||||
*/
|
|
||||||
protected function _getAdjacentBlankNodeName($node, $id) {
|
|
||||||
if($node->interfaceName === 'BlankNode' && $node->nominalValue !== $id) {
|
|
||||||
return $node->nominalValue;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively flattens the subjects in the given JSON-LD expanded input.
|
|
||||||
*
|
*
|
||||||
* @param mixed $input the JSON-LD expanded input.
|
* @param mixed $input the JSON-LD expanded input.
|
||||||
* @param stdClass $graphs a map of graph name to subject map.
|
* @param stdClass $graphs a map of graph name to subject map.
|
||||||
|
|
@ -2466,16 +2807,17 @@ class JsonLdProcessor {
|
||||||
* @param mixed $name the name assigned to the current input if it is a bnode.
|
* @param mixed $name the name assigned to the current input if it is a bnode.
|
||||||
* @param mixed $list the list to append to, null for none.
|
* @param mixed $list the list to append to, null for none.
|
||||||
*/
|
*/
|
||||||
protected function _flatten($input, $graphs, $graph, $namer, $name, $list) {
|
protected function _createNodeMap(
|
||||||
|
$input, $graphs, $graph, $namer, $name=null, $list=null) {
|
||||||
// recurse through array
|
// recurse through array
|
||||||
if(is_array($input)) {
|
if(is_array($input)) {
|
||||||
foreach($input as $e) {
|
foreach($input as $e) {
|
||||||
$this->_flatten($e, $graphs, $graph, $namer, null, $list);
|
$this->_createNodeMap($e, $graphs, $graph, $namer, null, $list);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add non-object or value
|
// add non-object to list
|
||||||
if(!is_object($input) || self::_isValue($input)) {
|
if(!is_object($input) || self::_isValue($input)) {
|
||||||
if($list !== null) {
|
if($list !== null) {
|
||||||
$list[] = $input;
|
$list[] = $input;
|
||||||
|
|
@ -2483,6 +2825,26 @@ class JsonLdProcessor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add entries for @type
|
||||||
|
if(property_exists($input, '@type')) {
|
||||||
|
$types = $input->{'@type'};
|
||||||
|
$types = self::arrayify($types);
|
||||||
|
foreach($types as $type) {
|
||||||
|
$id = (strpos($type, '_:') === 0) ? $namer->getName($type) : $type;
|
||||||
|
if(!property_exists($graphs->{$graph}, $id)) {
|
||||||
|
$graphs->{$graph}->{$id} = (object)array('@id' => $id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add add values to list
|
||||||
|
if(self::_isValue($input)) {
|
||||||
|
if($list !== null) {
|
||||||
|
$list[] = $input;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Note: At this point, input must be a subject.
|
// Note: At this point, input must be a subject.
|
||||||
|
|
||||||
// get name for subject
|
// get name for subject
|
||||||
|
|
@ -2510,30 +2872,39 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
$subject = $subjects->{$name};
|
$subject = $subjects->{$name};
|
||||||
$subject->{'@id'} = $name;
|
$subject->{'@id'} = $name;
|
||||||
foreach($input as $prop => $objects) {
|
$properties = array_keys((array)$input);
|
||||||
|
sort($properties);
|
||||||
|
foreach($properties as $property) {
|
||||||
// skip @id
|
// skip @id
|
||||||
if($prop === '@id') {
|
if($property === '@id') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// recurse into graph
|
// recurse into graph
|
||||||
if($prop === '@graph') {
|
if($property === '@graph') {
|
||||||
// add graph subjects map entry
|
// add graph subjects map entry
|
||||||
if(!property_exists($graphs, $name)) {
|
if(!property_exists($graphs, $name)) {
|
||||||
$graphs->{$name} = new stdClass();
|
$graphs->{$name} = new stdClass();
|
||||||
}
|
}
|
||||||
$g = ($graph === '@merged') ? $graph : $name;
|
$g = ($graph === '@merged') ? $graph : $name;
|
||||||
$this->_flatten($objects, $graphs, $g, $namer, null, null);
|
$this->_createNodeMap(
|
||||||
|
$input->{$property}, $graphs, $g, $namer, null, null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy non-@type keywords
|
// copy non-@type keywords
|
||||||
if($prop !== '@type' && self::_isKeyword($prop)) {
|
if($property !== '@type' && self::_isKeyword($property)) {
|
||||||
$subject->{$prop} = $objects;
|
$subject->{$property} = $input->{$property};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate over objects
|
// iterate over objects
|
||||||
|
$objects = $input->{$property};
|
||||||
|
if(count($objects) === 0) {
|
||||||
|
self::addValue(
|
||||||
|
$subject, $property, array(), array('propertyIsArray' => true));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
foreach($objects as $o) {
|
foreach($objects as $o) {
|
||||||
// handle embedded subject or subject reference
|
// handle embedded subject or subject reference
|
||||||
if(self::_isSubject($o) || self::_isSubjectReference($o)) {
|
if(self::_isSubject($o) || self::_isSubjectReference($o)) {
|
||||||
|
|
@ -2545,25 +2916,24 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// add reference and recurse
|
// add reference and recurse
|
||||||
self::addValue(
|
self::addValue(
|
||||||
$subject, $prop, (object)array('@id' => $id),
|
$subject, $property, (object)array('@id' => $id),
|
||||||
array('propertyIsArray' => true));
|
array('propertyIsArray' => true));
|
||||||
$this->_flatten($o, $graphs, $graph, $namer, $id, null);
|
$this->_createNodeMap($o, $graphs, $graph, $namer, $id, null);
|
||||||
}
|
}
|
||||||
|
// handle $list
|
||||||
|
else if(self::_isList($o)) {
|
||||||
|
$_list = new ArrayObject();
|
||||||
|
$this->_createNodeMap(
|
||||||
|
$o->{'@list'}, $graphs, $graph, $namer, $name, $_list);
|
||||||
|
$o = (object)array('@list' => (array)$_list);
|
||||||
|
self::addValue(
|
||||||
|
$subject, $property, $o, array('propertyIsArray' => true));
|
||||||
|
}
|
||||||
|
// handle @value
|
||||||
else {
|
else {
|
||||||
// recurse into list
|
$this->_createNodeMap($o, $graphs, $graph, $namer, $name, null);
|
||||||
if(self::_isList($o)) {
|
self::addValue(
|
||||||
$_list = new ArrayObject();
|
$subject, $property, $o, array('propertyIsArray' => true));
|
||||||
$this->_flatten(
|
|
||||||
$o->{'@list'}, $graphs, $graph, $namer, $name, $_list);
|
|
||||||
$o = (object)array('@list' => (array)$_list);
|
|
||||||
}
|
|
||||||
// special-handle @type blank nodes
|
|
||||||
else if($prop === '@type' && strpos($o, '_:') === 0) {
|
|
||||||
$o = $namer->getName($o);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add non-subject
|
|
||||||
self::addValue($subject, $prop, $o, array('propertyIsArray' => true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2991,6 +3361,190 @@ class JsonLdProcessor {
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashes all of the statements about a blank node.
|
||||||
|
*
|
||||||
|
* @param string $id the ID of the bnode to hash statements for.
|
||||||
|
* @param stdClass $bnodes the mapping of bnodes to statements.
|
||||||
|
* @param UniqueNamer $namer the canonical bnode namer.
|
||||||
|
*
|
||||||
|
* @return string the new hash.
|
||||||
|
*/
|
||||||
|
protected function _hashStatements($id, $bnodes, $namer) {
|
||||||
|
// return cached hash
|
||||||
|
if(property_exists($bnodes->{$id}, 'hash')) {
|
||||||
|
return $bnodes->{$id}->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize all of bnode's statements
|
||||||
|
$statements = $bnodes->{$id}->statements;
|
||||||
|
$nquads = array();
|
||||||
|
foreach($statements as $statement) {
|
||||||
|
$nquads[] = $this->toNQuad($statement, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort serialized quads
|
||||||
|
sort($nquads);
|
||||||
|
|
||||||
|
// cache and return hashed quads
|
||||||
|
$hash = $bnodes->{$id}->hash = sha1(implode($nquads));
|
||||||
|
return $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a hash for the paths of adjacent bnodes for a bnode,
|
||||||
|
* incorporating all information about its subgraph of bnodes. This
|
||||||
|
* method will recursively pick adjacent bnode permutations that produce the
|
||||||
|
* lexicographically-least 'path' serializations.
|
||||||
|
*
|
||||||
|
* @param string $id the ID of the bnode to hash paths for.
|
||||||
|
* @param stdClass $bnodes the map of bnode statements.
|
||||||
|
* @param UniqueNamer $namer the canonical bnode namer.
|
||||||
|
* @param UniqueNamer $path_namer the namer used to assign names to adjacent
|
||||||
|
* bnodes.
|
||||||
|
*
|
||||||
|
* @return stdClass the hash and path namer used.
|
||||||
|
*/
|
||||||
|
protected function _hashPaths($id, $bnodes, $namer, $path_namer) {
|
||||||
|
// create SHA-1 digest
|
||||||
|
$md = hash_init('sha1');
|
||||||
|
|
||||||
|
// group adjacent bnodes by hash, keep properties and references separate
|
||||||
|
$groups = new stdClass();
|
||||||
|
$statements = $bnodes->{$id}->statements;
|
||||||
|
foreach($statements as $statement) {
|
||||||
|
// get adjacent bnode
|
||||||
|
$bnode = $this->_getAdjacentBlankNodeName($statement->subject, $id);
|
||||||
|
if($bnode !== null) {
|
||||||
|
$direction = 'p';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$bnode = $this->_getAdjacentBlankNodeName($statement->object, $id);
|
||||||
|
if($bnode !== null) {
|
||||||
|
$direction = 'r';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($bnode !== null) {
|
||||||
|
// get bnode name (try canonical, path, then hash)
|
||||||
|
if($namer->isNamed($bnode)) {
|
||||||
|
$name = $namer->getName($bnode);
|
||||||
|
}
|
||||||
|
else if($path_namer->isNamed($bnode)) {
|
||||||
|
$name = $path_namer->getName($bnode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$name = $this->_hashStatements($bnode, $bnodes, $namer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hash direction, property, and bnode name/hash
|
||||||
|
$group_md = hash_init('sha1');
|
||||||
|
hash_update($group_md, $direction);
|
||||||
|
hash_update($group_md, $statement->property->nominalValue);
|
||||||
|
hash_update($group_md, $name);
|
||||||
|
$group_hash = hash_final($group_md);
|
||||||
|
|
||||||
|
// add bnode to hash group
|
||||||
|
if(property_exists($groups, $group_hash)) {
|
||||||
|
$groups->{$group_hash}[] = $bnode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$groups->{$group_hash} = array($bnode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over groups in sorted hash order
|
||||||
|
$group_hashes = array_keys((array)$groups);
|
||||||
|
sort($group_hashes);
|
||||||
|
foreach($group_hashes as $group_hash) {
|
||||||
|
// digest group hash
|
||||||
|
hash_update($md, $group_hash);
|
||||||
|
|
||||||
|
// choose a path and namer from the permutations
|
||||||
|
$chosen_path = null;
|
||||||
|
$chosen_namer = null;
|
||||||
|
$permutator = new Permutator($groups->{$group_hash});
|
||||||
|
while($permutator->hasNext()) {
|
||||||
|
$permutation = $permutator->next();
|
||||||
|
$path_namer_copy = clone $path_namer;
|
||||||
|
|
||||||
|
// build adjacent path
|
||||||
|
$path = '';
|
||||||
|
$skipped = false;
|
||||||
|
$recurse = array();
|
||||||
|
foreach($permutation as $bnode) {
|
||||||
|
// use canonical name if available
|
||||||
|
if($namer->isNamed($bnode)) {
|
||||||
|
$path .= $namer->getName($bnode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// recurse if bnode isn't named in the path yet
|
||||||
|
if(!$path_namer_copy->isNamed($bnode)) {
|
||||||
|
$recurse[] = $bnode;
|
||||||
|
}
|
||||||
|
$path .= $path_namer_copy->getName($bnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip permutation if path is already >= chosen path
|
||||||
|
if($chosen_path !== null && strlen($path) >= strlen($chosen_path) &&
|
||||||
|
$path > $chosen_path) {
|
||||||
|
$skipped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recurse
|
||||||
|
if(!$skipped) {
|
||||||
|
foreach($recurse as $bnode) {
|
||||||
|
$result = $this->_hashPaths(
|
||||||
|
$bnode, $bnodes, $namer, $path_namer_copy);
|
||||||
|
$path .= $path_namer_copy->getName($bnode);
|
||||||
|
$path .= "<{$result->hash}>";
|
||||||
|
$path_namer_copy = $result->pathNamer;
|
||||||
|
|
||||||
|
// skip permutation if path is already >= chosen path
|
||||||
|
if($chosen_path !== null &&
|
||||||
|
strlen($path) >= strlen($chosen_path) && $path > $chosen_path) {
|
||||||
|
$skipped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$skipped && ($chosen_path === null || $path < $chosen_path)) {
|
||||||
|
$chosen_path = $path;
|
||||||
|
$chosen_namer = $path_namer_copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// digest chosen path and update namer
|
||||||
|
hash_update($md, $chosen_path);
|
||||||
|
$path_namer = $chosen_namer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return SHA-1 hash and path namer
|
||||||
|
return (object)array(
|
||||||
|
'hash' => hash_final($md), 'pathNamer' => $path_namer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function that gets the blank node name from an RDF statement
|
||||||
|
* node (subject or object). If the node is not a blank node or its
|
||||||
|
* nominal value does not match the given blank node ID, it will be
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* @param stdClass $node the RDF statement node.
|
||||||
|
* @param string $id the ID of the blank node to look next to.
|
||||||
|
*
|
||||||
|
* @return mixed the adjacent blank node name or null if none was found.
|
||||||
|
*/
|
||||||
|
protected function _getAdjacentBlankNodeName($node, $id) {
|
||||||
|
if($node->interfaceName === 'BlankNode' && $node->nominalValue !== $id) {
|
||||||
|
return $node->nominalValue;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two strings first based on length and then lexicographically.
|
* Compares two strings first based on length and then lexicographically.
|
||||||
*
|
*
|
||||||
|
|
@ -3000,107 +3554,112 @@ class JsonLdProcessor {
|
||||||
* @return integer -1 if a < b, 1 if a > b, 0 if a == b.
|
* @return integer -1 if a < b, 1 if a > b, 0 if a == b.
|
||||||
*/
|
*/
|
||||||
protected function _compareShortestLeast($a, $b) {
|
protected function _compareShortestLeast($a, $b) {
|
||||||
if(strlen($a) < strlen($b)) {
|
$len_a = strlen($a);
|
||||||
|
$len_b = strlen($b);
|
||||||
|
if($len_a < $len_b) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else if(strlen($b) < strlen($a)) {
|
else if($len_b < $len_a) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
|
else if($a === $b) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ($a < $b) ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ranks a term that is possible choice for compacting an IRI associated with
|
* Picks the preferred compaction term from the given inverse context entry.
|
||||||
* the given value.
|
|
||||||
*
|
*
|
||||||
* @param stdClass $ctx the active context.
|
* @param active_ctx the active context.
|
||||||
* @param string $term the term to rank.
|
* @param iri the IRI to pick the term for.
|
||||||
* @param mixed $value the associated value.
|
* @param value the value to pick the term for.
|
||||||
|
* @param parent the parent of the value (required for property generators).
|
||||||
|
* @param containers the preferred containers.
|
||||||
|
* @param type_or_language either '@type' or '@language'.
|
||||||
|
* @param type_or_language_value the preferred value for '@type' or
|
||||||
|
* '@language'.
|
||||||
*
|
*
|
||||||
* @return integer the term rank.
|
* @return mixed the preferred term.
|
||||||
*/
|
*/
|
||||||
protected function _rankTerm($ctx, $term, $value) {
|
protected function _selectTerm(
|
||||||
// no term restrictions for a null value
|
$active_ctx, $iri, $value, $parent, $containers,
|
||||||
if($value === null) {
|
$type_or_language, $type_or_language_value) {
|
||||||
return 3;
|
$containers[] = '@none';
|
||||||
|
if($type_or_language_value === null) {
|
||||||
|
$type_or_language_value = '@null';
|
||||||
}
|
}
|
||||||
|
// options for the value of @type or @language
|
||||||
// get context entry for term
|
$options = array($type_or_language_value, '@none');
|
||||||
$entry = $ctx->mappings->{$term};
|
$term = null;
|
||||||
$has_type = property_exists($entry, '@type');
|
$container_map = $active_ctx->inverse->{$iri};
|
||||||
$has_language = property_exists($entry, '@language');
|
foreach($containers as $container) {
|
||||||
$has_default_language = property_exists($ctx, '@language');
|
if($term !== null) {
|
||||||
|
break;
|
||||||
// @list rank is the sum of its values' ranks
|
|
||||||
if(self::_isList($value)) {
|
|
||||||
$list = $value->{'@list'};
|
|
||||||
if(count($list) === 0) {
|
|
||||||
return ($entry->{'@container'} === '@list') ? 1 : 0;
|
|
||||||
}
|
}
|
||||||
// sum term ranks for each list value
|
|
||||||
$sum = 0;
|
// if container not available in the map, continue
|
||||||
foreach($list as $v) {
|
if(!property_exists($container_map, $container)) {
|
||||||
$sum += $this->_rankTerm($ctx, $term, $v);
|
continue;
|
||||||
}
|
}
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Value must be an object that is a @value or subject/reference.
|
$type_or_language_value_map =
|
||||||
|
$container_map->{$container}->{$type_or_language};
|
||||||
if(self::_isValue($value)) {
|
foreach($options as $option) {
|
||||||
// value has a @type
|
if($term !== null) {
|
||||||
if(property_exists($value, '@type')) {
|
break;
|
||||||
// @types match
|
|
||||||
if($has_type && $value->{'@type'} === $entry->{'@type'}) {
|
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
return (!$has_type && !$has_language) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rank non-string value
|
// if type/language option not available in the map, continue
|
||||||
if(!is_string($value->{'@value'})) {
|
if(!property_exists($type_or_language_value_map, $option)) {
|
||||||
return (!$has_type && !$has_language) ? 2 : 1;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// value has no @type or @language
|
|
||||||
if(!property_exists($value, '@language')) {
|
|
||||||
// entry @language is specifically null or no @type, @language, or
|
|
||||||
// default
|
|
||||||
if(($has_language && $entry->{'@language'} === null) ||
|
|
||||||
(!$has_type && !$has_language && !$has_default_language)) {
|
|
||||||
return 3;
|
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @languages match or entry has no @type or @language but default
|
$term_info = $type_or_language_value_map->{$option};
|
||||||
// @language matches
|
|
||||||
if(($has_language && $value->{'@language'} === $entry->{'@language'}) ||
|
|
||||||
(!$has_type && !$has_language && $has_default_language &&
|
|
||||||
$value->{'@language'} === $ctx->{'@language'})) {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
return (!$has_type && !$has_language) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value must be a subject/reference
|
// see if a property generator matches
|
||||||
if($has_type && $entry->{'@type'} === '@id') {
|
if(is_object($parent)) {
|
||||||
return 3;
|
foreach($term_info->propertyGenerators as $property_generator) {
|
||||||
|
$iris = $active_ctx->mappings->{$property_generator}->{'@id'};
|
||||||
|
$match = true;
|
||||||
|
foreach($iris as $iri_) {
|
||||||
|
if(!property_exists($parent, $iri_)) {
|
||||||
|
$match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($match) {
|
||||||
|
$term = $property_generator;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no matching property generator, use a simple term instead
|
||||||
|
if($term === null) {
|
||||||
|
$term = $term_info->term;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (!$has_type && !$has_language) ? 1 : 0;
|
return $term;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compacts an IRI or keyword into a term or prefix if it can be. If the
|
* Compacts an IRI or keyword into a term or prefix if it can be. If the
|
||||||
* IRI has an associated value it may be passed.
|
* IRI has an associated value it may be passed.
|
||||||
*
|
*
|
||||||
* @param stdClass $ctx the active context to use.
|
* @param stdClass $active_ctx the active context to use.
|
||||||
* @param string $iri the IRI to compact.
|
* @param string $iri the IRI to compact.
|
||||||
* @param mixed $value the value to check or null.
|
* @param mixed $value the value to check or null.
|
||||||
|
* @param assoc $relative_to options for how to compact IRIs:
|
||||||
|
* base: true to compact against the base IRI, false not to.
|
||||||
|
* vocab: true to split after @vocab, false not to.
|
||||||
|
* @param mixed $parent the parent element for the value.
|
||||||
*
|
*
|
||||||
* @return string the compacted term, prefix, keyword alias, or original IRI.
|
* @return string the compacted term, prefix, keyword alias, or original IRI.
|
||||||
*/
|
*/
|
||||||
protected function _compactIri($ctx, $iri, $value=null) {
|
protected function _compactIri(
|
||||||
|
$active_ctx, $iri, $value=null, $relative_to=array(), $parent=null) {
|
||||||
// can't compact null
|
// can't compact null
|
||||||
if($iri === null) {
|
if($iri === null) {
|
||||||
return $iri;
|
return $iri;
|
||||||
|
|
@ -3109,219 +3668,355 @@ class JsonLdProcessor {
|
||||||
// term is a keyword
|
// term is a keyword
|
||||||
if(self::_isKeyword($iri)) {
|
if(self::_isKeyword($iri)) {
|
||||||
// return alias if available
|
// return alias if available
|
||||||
$aliases = $ctx->keywords->{$iri};
|
$aliases = $active_ctx->keywords->{$iri};
|
||||||
if(count($aliases) > 0) {
|
return (count($aliases) > 0) ? $aliases[0] : $iri;
|
||||||
return $aliases[0];
|
}
|
||||||
|
|
||||||
|
// use inverse context to pick a term
|
||||||
|
$inverse_ctx = $this->_getInverseContext($active_ctx);
|
||||||
|
|
||||||
|
$default_language = '@none';
|
||||||
|
if(property_exists($active_ctx, '@language')) {
|
||||||
|
$default_language = $active_ctx->{'@language'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(property_exists($inverse_ctx, $iri)) {
|
||||||
|
// prefer @index if available in value
|
||||||
|
$containers = array();
|
||||||
|
if(is_object($value) && property_exists($value, '@index')) {
|
||||||
|
$containers[] = '@index';
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaults for term selection based on type/language
|
||||||
|
$type_or_language = '@language';
|
||||||
|
$type_or_language_value = '@null';
|
||||||
|
|
||||||
|
// choose the most specific term that works for all elements in @list
|
||||||
|
if(self::_isList($value)) {
|
||||||
|
// only select @list containers if @index is NOT in value
|
||||||
|
if(!property_exists($value, '@index')) {
|
||||||
|
$containers[] = '@list';
|
||||||
|
}
|
||||||
|
$list = $value->{'@list'};
|
||||||
|
$common_language = (count($list) === 0) ? $default_language : null;
|
||||||
|
$common_type = null;
|
||||||
|
foreach($list as $item) {
|
||||||
|
$item_language = '@none';
|
||||||
|
$item_type = '@none';
|
||||||
|
if(self::_isValue($item)) {
|
||||||
|
if(property_exists($item, '@language')) {
|
||||||
|
$item_language = $item->{'@language'};
|
||||||
|
}
|
||||||
|
else if(property_exists($item, '@type')) {
|
||||||
|
$item_type = $item->{'@type'};
|
||||||
|
}
|
||||||
|
// plain literal
|
||||||
|
else {
|
||||||
|
$item_language = '@null';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if($common_language === null) {
|
||||||
|
$common_language = $item_language;
|
||||||
|
}
|
||||||
|
else if($item_language !== $common_language &&
|
||||||
|
self::_isValue($item)) {
|
||||||
|
$common_language = '@none';
|
||||||
|
}
|
||||||
|
if($common_type === null) {
|
||||||
|
$common_type = $item_type;
|
||||||
|
}
|
||||||
|
else if($item_type !== $common_type) {
|
||||||
|
$common_type = '@none';
|
||||||
|
}
|
||||||
|
// there are different languages and types in the list, so choose
|
||||||
|
// the most generic term, no need to keep iterating the list
|
||||||
|
if($common_language === '@none' && $common_type === '@none') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$common_language = $common_language || '@none';
|
||||||
|
$common_type = $common_type || '@none';
|
||||||
|
if($common_type !== '@none') {
|
||||||
|
$type_or_language = '@type';
|
||||||
|
$type_or_language_value = $common_type;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$type_or_language_value = $common_language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// no alias, keep original keyword
|
if(self::_isValue($value)) {
|
||||||
return $iri;
|
if(property_exists($value, '@language')) {
|
||||||
}
|
$containers[] = '@language';
|
||||||
}
|
$type_or_language_value = $value->{'@language'};
|
||||||
|
}
|
||||||
// find all possible term matches
|
else if(property_exists($value, '@type')) {
|
||||||
$terms = array();
|
$type_or_language = '@type';
|
||||||
$highest = 0;
|
$type_or_language_value = $value->{'@type'};
|
||||||
$list_container = false;
|
|
||||||
$is_list = self::_isList($value);
|
|
||||||
foreach($ctx->mappings as $term => $entry) {
|
|
||||||
$has_container = property_exists($entry, '@container');
|
|
||||||
|
|
||||||
// skip terms with non-matching iris
|
|
||||||
if($entry->{'@id'} !== $iri) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// skip @set containers for @lists
|
|
||||||
if($is_list && $has_container && $entry->{'@container'} === '@set') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// skip @list containers for non-@lists
|
|
||||||
if(!$is_list && $has_container && $entry->{'@container'} === '@list' &&
|
|
||||||
$value !== null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// for @lists, if list_container is set, skip non-list containers
|
|
||||||
if($is_list && $list_container && (!$has_container ||
|
|
||||||
$entry->{'@container'} !== '@list')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// rank term
|
|
||||||
$rank = $this->_rankTerm($ctx, $term, $value);
|
|
||||||
if($rank > 0) {
|
|
||||||
// add 1 to rank if container is a @set
|
|
||||||
if($has_container && $entry->{'@container'} === '@set') {
|
|
||||||
$rank += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for @lists, give preference to @list containers
|
|
||||||
if($is_list && !$list_container && $has_container &&
|
|
||||||
$entry->{'@container'} === '@list') {
|
|
||||||
$list_container = true;
|
|
||||||
$terms = array();
|
|
||||||
$highest = $rank;
|
|
||||||
$terms[] = $term;
|
|
||||||
}
|
|
||||||
// only push match if rank meets current threshold
|
|
||||||
else if($rank >= $highest) {
|
|
||||||
if($rank > $highest) {
|
|
||||||
$terms = array();
|
|
||||||
$highest = $rank;
|
|
||||||
}
|
}
|
||||||
$terms[] = $term;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$type_or_language = '@type';
|
||||||
|
$type_or_language_value = '@id';
|
||||||
|
}
|
||||||
|
$containers[] = '@set';
|
||||||
|
}
|
||||||
|
|
||||||
|
// do term selection
|
||||||
|
$term = $this->_selectTerm(
|
||||||
|
$active_ctx, $iri, $value, $parent,
|
||||||
|
$containers, $type_or_language, $type_or_language_value);
|
||||||
|
if($term !== null) {
|
||||||
|
return $term;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no matching terms, use @vocab if available
|
// no term match, check for possible CURIEs
|
||||||
if(count($terms) === 0 && property_exists($ctx, '@vocab') &&
|
$choice = null;
|
||||||
$ctx->{'@vocab'} !== null) {
|
foreach($active_ctx->mappings as $term => $definition) {
|
||||||
|
// skip terms with colons, they can't be prefixes
|
||||||
|
if(strpos($term, ':') !== false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// skip entries with @ids that are not partial matches
|
||||||
|
if($definition === null || $definition->propertyGenerator ||
|
||||||
|
$definition->{'@id'} === $iri ||
|
||||||
|
strpos($iri, $definition->{'@id'}) !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a CURIE is usable if:
|
||||||
|
// 1. it has no mapping, OR
|
||||||
|
// 2. value is null, which means we're not compacting an @value, AND
|
||||||
|
// the mapping matches the IRI)
|
||||||
|
$curie = $term . ':' . substr($iri, strlen($definition->{'@id'}));
|
||||||
|
$is_usable_curie = (!property_exists($active_ctx->mappings, $curie) ||
|
||||||
|
($value === null && $active_ctx->mappings->{$curie} &&
|
||||||
|
$active_ctx->mappings->{$curie}->{'@id'} === $iri));
|
||||||
|
|
||||||
|
// select curie if it is shorter or the same length but lexicographically
|
||||||
|
// less than the current choice
|
||||||
|
if($is_usable_curie && ($choice === null ||
|
||||||
|
self::_compareShortestLeast($curie, $choice) < 0)) {
|
||||||
|
$choice = $curie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return chosen curie
|
||||||
|
if($choice !== null) {
|
||||||
|
return $choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no matching terms or curies, use @vocab if available
|
||||||
|
if(isset($relative_to['vocab']) && $relative_to['vocab'] &&
|
||||||
|
property_exists($active_ctx, '@vocab')) {
|
||||||
// determine if vocab is a prefix of the iri
|
// determine if vocab is a prefix of the iri
|
||||||
$vocab = $ctx->{'@vocab'};
|
$vocab = $active_ctx->{'@vocab'};
|
||||||
if(strpos($iri, $vocab) === 0) {
|
if(strpos($iri, $vocab) === 0 && $iri !== $vocab) {
|
||||||
// use suffix as relative iri if it is not a term in the active context
|
// use suffix as relative iri if it is not a term in the active context
|
||||||
$suffix = substr($iri, strlen($vocab));
|
$suffix = substr($iri, strlen($vocab));
|
||||||
if(!property_exists($ctx->mappings, $suffix)) {
|
if(!property_exists($active_ctx->mappings, $suffix)) {
|
||||||
return $suffix;
|
return $suffix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no term matches, add possible CURIEs
|
// no compaction choices, return IRI as is
|
||||||
if(count($terms) === 0) {
|
return $iri;
|
||||||
foreach($ctx->mappings as $term => $entry) {
|
|
||||||
// skip terms with colons, they can't be prefixes
|
|
||||||
if(strpos($term, ':') !== false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// skip entries with @ids that are not partial matches
|
|
||||||
if($entry->{'@id'} === $iri || strpos($iri, $entry->{'@id'}) !== 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add CURIE as term if it has no mapping
|
|
||||||
$curie = $term . ':' . substr($iri, strlen($entry->{'@id'}));
|
|
||||||
if(!property_exists($ctx->mappings, $curie)) {
|
|
||||||
$terms[] = $curie;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no matching terms
|
|
||||||
if(count($terms) === 0) {
|
|
||||||
// use iri
|
|
||||||
return $iri;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return shortest and lexicographically-least term
|
|
||||||
usort($terms, array($this, '_compareShortestLeast'));
|
|
||||||
return $terms[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a context mapping during context processing.
|
* Performs value compaction on an object with '@value' or '@id' as the only
|
||||||
|
* property.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the active context.
|
||||||
|
* @param string $active_property the active property that points to the
|
||||||
|
* value.
|
||||||
|
* @param mixed $value the value to compact.
|
||||||
|
*
|
||||||
|
* @return mixed the compaction result.
|
||||||
|
*/
|
||||||
|
protected function _compactValue($active_ctx, $active_property, $value) {
|
||||||
|
// value is a @value
|
||||||
|
if(self::_isValue($value)) {
|
||||||
|
// get context rules
|
||||||
|
$type = self::getContextValue($active_ctx, $active_property, '@type');
|
||||||
|
$language = self::getContextValue(
|
||||||
|
$active_ctx, $active_property, '@language');
|
||||||
|
$container = self::getContextValue(
|
||||||
|
$active_ctx, $active_property, '@container');
|
||||||
|
|
||||||
|
// whether or not the value has an @index that must be preserved
|
||||||
|
$preserve_index = (property_exists($value, '@index') &&
|
||||||
|
$container !== '@index');
|
||||||
|
|
||||||
|
// if there's no @index to preserve ...
|
||||||
|
if(!$preserve_index) {
|
||||||
|
// matching @type or @language specified in context, compact value
|
||||||
|
if(self::_hasKeyValue($value, '@type', $type) ||
|
||||||
|
self::_hasKeyValue($value, '@language', $language)) {
|
||||||
|
return $value->{'@value'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return just the value of @value if all are true:
|
||||||
|
// 1. @value is the only key or @index isn't being preserved
|
||||||
|
// 2. there is no default language or @value is not a string or
|
||||||
|
// the key has a mapping with a null @language
|
||||||
|
$key_count = count(array_keys((array)$value));
|
||||||
|
$is_value_only_key = ($key_count === 1 ||
|
||||||
|
($key_count === 2 && property_exists($value, '@index') &&
|
||||||
|
!$preserve_index));
|
||||||
|
$has_default_language = property_exists($active_ctx, '@language');
|
||||||
|
$is_value_string = is_string($value->{'@value'});
|
||||||
|
$has_null_mapping = (
|
||||||
|
property_exists($active_ctx->mappings, $active_property) &&
|
||||||
|
$active_ctx->mappings->{$active_property} !== null &&
|
||||||
|
self::_hasKeyValue(
|
||||||
|
$active_ctx->mappings->{$active_property}, '@language', null));
|
||||||
|
if($is_value_only_key &&
|
||||||
|
(!$has_default_language || !$is_value_string || $has_null_mapping)) {
|
||||||
|
return $value->{'@value'};
|
||||||
|
}
|
||||||
|
|
||||||
|
$rval = new stdClass();
|
||||||
|
|
||||||
|
// preserve @index
|
||||||
|
if($preserve_index) {
|
||||||
|
$rval->{$this->_compactIri($active_ctx, '@index')} = $value->{'@index'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// compact @type IRI
|
||||||
|
if(property_exists($value, '@type')) {
|
||||||
|
$rval->{$this->_compactIri($active_ctx, '@type')} = $this->_compactIri(
|
||||||
|
$active_ctx, $value->{'@type'}, null,
|
||||||
|
array('base' => true, 'vocab' => true));
|
||||||
|
}
|
||||||
|
// alias @language
|
||||||
|
else if(property_exists($value, '@language')) {
|
||||||
|
$rval->{$this->_compactIri($active_ctx, '@language')} =
|
||||||
|
$value->{'@language'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias @value
|
||||||
|
$rval->{$this->_compactIri($active_ctx, '@value')} = $value->{'@value'};
|
||||||
|
|
||||||
|
return $rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// value is a subject reference
|
||||||
|
$expanded_property = $this->_expandIri($active_ctx, $active_property);
|
||||||
|
$type = self::getContextValue($active_ctx, $active_property, '@type');
|
||||||
|
$term = $this->_compactIri(
|
||||||
|
$active_ctx, $value->{'@id'}, null, array('base' => true));
|
||||||
|
|
||||||
|
// compact to scalar
|
||||||
|
if($type === '@id' || $expanded_property === '@graph') {
|
||||||
|
return $term;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rval = (object)array(
|
||||||
|
$this->_compactIri($active_ctx, '@id') => $term);
|
||||||
|
return $rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and removes any duplicate values that were presumably generated by
|
||||||
|
* a property generator in the given element.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the active context.
|
||||||
|
* @param stdClass $element the element to remove duplicates from.
|
||||||
|
* @param string $expanded_property the property to map to a property
|
||||||
|
* generator.
|
||||||
|
* @param mixed $value the value to compare against when duplicate checking.
|
||||||
|
* @param string $active_property the property generator term.
|
||||||
|
*/
|
||||||
|
protected function _findAndRemovePropertyGeneratorDuplicates(
|
||||||
|
$active_ctx, $element, $expanded_property, $value, $active_property) {
|
||||||
|
// get property generator IRIs
|
||||||
|
$iris = $active_ctx->mappings->{$active_property}->{'@id'};
|
||||||
|
|
||||||
|
// for each IRI that isn't 'expandedProperty', remove a single duplicate
|
||||||
|
// from element, if found
|
||||||
|
foreach($iris as $iri) {
|
||||||
|
if($iri === $expanded_property) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for($pi = 0; $pi < count($element->{$iri}); ++$pi) {
|
||||||
|
if(self::compareValues($element->{$iri}[$pi], $value)) {
|
||||||
|
// duplicate found, remove it in place
|
||||||
|
array_splice($element->{$iri}, $pi, 1);
|
||||||
|
if(count($element->{$iri}) === 0) {
|
||||||
|
unset($element->{$iri});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a term definition during context processing.
|
||||||
*
|
*
|
||||||
* @param stdClass $active_ctx the current active context.
|
* @param stdClass $active_ctx the current active context.
|
||||||
* @param stdClass $ctx the local context being processed.
|
* @param stdClass $local_ctx the local context being processed.
|
||||||
* @param string $key the key in the local context to define the mapping for.
|
* @param string $term the key in the local context to define the mapping for.
|
||||||
* @param string $base the base IRI.
|
|
||||||
* @param stdClass $defined a map of defining/defined keys to detect cycles
|
* @param stdClass $defined a map of defining/defined keys to detect cycles
|
||||||
* and prevent double definitions.
|
* and prevent double definitions.
|
||||||
*/
|
*/
|
||||||
protected function _defineContextMapping(
|
protected function _createTermDefinition(
|
||||||
$active_ctx, $ctx, $key, $base, $defined) {
|
$active_ctx, $local_ctx, $term, $defined) {
|
||||||
if(property_exists($defined, $key)) {
|
if(property_exists($defined, $term)) {
|
||||||
// key already defined
|
// term already defined
|
||||||
if($defined->{$key}) {
|
if($defined->{$term}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// cycle detected
|
// cycle detected
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Cyclical context definition detected.',
|
'Cyclical context definition detected.',
|
||||||
'jsonld.CyclicalContext',
|
'jsonld.CyclicalContext',
|
||||||
(object)array('context' => $ctx, 'key' => $key));
|
(object)array('context' => $local_ctx, 'term' => $term));
|
||||||
}
|
}
|
||||||
|
|
||||||
// now defining key
|
// now defining term
|
||||||
$defined->{$key} = false;
|
$defined->{$term} = false;
|
||||||
|
|
||||||
// if key has a prefix, define it first
|
// if term has a prefix, define it first
|
||||||
$colon = strpos($key, ':');
|
$colon = strpos($term, ':');
|
||||||
$prefix = null;
|
$prefix = null;
|
||||||
if($colon !== false) {
|
if($colon !== false) {
|
||||||
$prefix = substr($key, 0, $colon);
|
$prefix = substr($term, 0, $colon);
|
||||||
if(property_exists($ctx, $prefix)) {
|
if(property_exists($local_ctx, $prefix)) {
|
||||||
// define parent prefix
|
// define parent prefix
|
||||||
$this->_defineContextMapping(
|
$this->_createTermDefinition(
|
||||||
$active_ctx, $ctx, $prefix, $base, $defined);
|
$active_ctx, $local_ctx, $prefix, $defined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get context key value
|
if(self::_isKeyword($term)) {
|
||||||
$value = $ctx->{$key};
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; keywords cannot be overridden.',
|
||||||
if(self::_isKeyword($key)) {
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
// support vocab
|
|
||||||
if($key === '@vocab') {
|
|
||||||
if($value !== null && !is_string($value)) {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
|
||||||
'@context must be a string or null.',
|
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
|
||||||
}
|
|
||||||
if(!self::_isAbsoluteIri($value)) {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
|
||||||
'@context must be an absolute IRI.',
|
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
|
||||||
}
|
|
||||||
if($value === null) {
|
|
||||||
unset($active_ctx->{'@vocab'});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$active_ctx->{'@vocab'} = $value;
|
|
||||||
}
|
|
||||||
$defined->{$key} = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only @language is permitted
|
|
||||||
if($key !== '@language') {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; keywords cannot be overridden.',
|
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
if($value !== null && !is_string($value)) {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; the value of "@language" in a ' +
|
|
||||||
'@context must be a string or null.',
|
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
if($value === null) {
|
|
||||||
unset($active_ctx->{'@language'});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$active_ctx->{'@language'} = $value;
|
|
||||||
}
|
|
||||||
$defined->{$key} = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(property_exists($active_ctx->mappings, $term) &&
|
||||||
|
$active_ctx->mappings->{$term} !== null) {
|
||||||
|
// if term is a keyword alias, remove it
|
||||||
|
$kw = $active_ctx->mappings->{$term}->{'@id'};
|
||||||
|
if(self::_isKeyword($kw)) {
|
||||||
|
array_splice($active_ctx->keywords->{$kw},
|
||||||
|
in_array($term, $active_ctx->keywords->{$kw}), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get context term value
|
||||||
|
$value = $local_ctx->{$term};
|
||||||
|
|
||||||
// clear context entry
|
// clear context entry
|
||||||
if($value === null or (is_object($value) &&
|
if($value === null || (is_object($value) &&
|
||||||
property_exists($value, '@id') && $value->{'@id'} === null)) {
|
self::_hasKeyValue($value, '@id', null))) {
|
||||||
if(property_exists($active_ctx->mappings, $key)) {
|
$active_ctx->mappings->{$term} = null;
|
||||||
// if key is a keyword alias, remove it
|
$defined->{$term} = true;
|
||||||
$kw = $active_ctx->mappings->{$key}->{'@id'};
|
|
||||||
if(self::_isKeyword($kw)) {
|
|
||||||
array_splice($active_ctx->keywords->{$kw},
|
|
||||||
in_array($key, $active_ctx->keywords->{$kw}), 1);
|
|
||||||
}
|
|
||||||
unset($active_ctx->mappings->{$key});
|
|
||||||
}
|
|
||||||
$defined->{$key} = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3334,69 +4029,113 @@ class JsonLdProcessor {
|
||||||
'jsonld.SyntaxError');
|
'jsonld.SyntaxError');
|
||||||
}
|
}
|
||||||
|
|
||||||
// uniquely add key as a keyword alias and resort
|
// uniquely add term as a keyword alias and resort
|
||||||
if(in_array($key, $active_ctx->keywords->{$value}) === false) {
|
if(!in_array($term, $active_ctx->keywords->{$value})) {
|
||||||
$active_ctx->keywords->{$value}[] = $key;
|
$active_ctx->keywords->{$value}[] = $term;
|
||||||
usort($active_ctx->keywords->{$value},
|
usort($active_ctx->keywords->{$value},
|
||||||
array($this, '_compareShortestLeast'));
|
array($this, '_compareShortestLeast'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// expand value to a full IRI
|
// expand value to a full IRI
|
||||||
$value = $this->_expandContextIri(
|
$value = $this->_expandIri(
|
||||||
$active_ctx, $ctx, $value, $base, $defined);
|
$active_ctx, $value, array('base' => true), $local_ctx, $defined);
|
||||||
}
|
}
|
||||||
|
|
||||||
// define/redefine key to expanded IRI/keyword
|
// define/redefine term to expanded IRI/keyword
|
||||||
$active_ctx->mappings->{$key} = (object)array('@id' => $value);
|
$active_ctx->mappings->{$term} = (object)array(
|
||||||
$defined->{$key} = true;
|
'@id' => $value, 'propertyGenerator' => false);
|
||||||
|
$defined->{$term} = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!is_object($value)) {
|
if(!is_object($value)) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; @context property values must be ' +
|
'Invalid JSON-LD syntax; @context property values must be ' .
|
||||||
'strings or objects.',
|
'strings or objects.',
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new mapping
|
// create new mapping
|
||||||
$mapping = new stdClass();
|
$mapping = new stdClass();
|
||||||
|
$mapping->propertyGenerator = false;
|
||||||
|
|
||||||
|
// merge onto parent mapping if one exists for a prefix
|
||||||
|
if($prefix !== null &&
|
||||||
|
property_exists($active_ctx->mappings, $prefix) &&
|
||||||
|
$active_ctx->mappings->{$prefix} !== null) {
|
||||||
|
$mapping = self::copy($active_ctx->mappings->{$prefix});
|
||||||
|
}
|
||||||
|
|
||||||
if(property_exists($value, '@id')) {
|
if(property_exists($value, '@id')) {
|
||||||
$id = $value->{'@id'};
|
$id = $value->{'@id'};
|
||||||
if(!is_string($id)) {
|
// handle property generator
|
||||||
|
if(is_array($id)) {
|
||||||
|
if($active_ctx->namer === null) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Incompatible JSON-LD options; a property generator was found ' .
|
||||||
|
'in the @context, but blank node renaming has been disabled; ' .
|
||||||
|
'it must be enabled to use property generators.',
|
||||||
|
'jsonld.OptionsError', array('context' => $local_ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
$property_generator = array();
|
||||||
|
$ids = $id;
|
||||||
|
foreach($ids as $id) {
|
||||||
|
if(!is_string($id)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; property generators must consist of ' .
|
||||||
|
'an @id array containing only strings.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
|
}
|
||||||
|
// expand @id if it is not @type
|
||||||
|
if($id !== '@type') {
|
||||||
|
$id = $this->_expandIri(
|
||||||
|
$active_ctx, $id, array('base' => true), $local_ctx, $defined);
|
||||||
|
}
|
||||||
|
$property_generator[] = $id;
|
||||||
|
}
|
||||||
|
// add sorted property generator as @id in mapping
|
||||||
|
sort($property_generator);
|
||||||
|
$mapping->{'@id'} = $property_generator;
|
||||||
|
$mapping->propertyGenerator = true;
|
||||||
|
}
|
||||||
|
else if(!is_string($id)) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; @context @id values must be strings.',
|
'Invalid JSON-LD syntax; @context @id value must be an array ' .
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
'of strings or a string.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// expand @id if it is not @type
|
// add @id to mapping, expanding it if it is not @type
|
||||||
if($id !== '@type') {
|
if($id !== '@type') {
|
||||||
// expand @id to full IRI
|
// expand @id to full IRI
|
||||||
$id = $this->_expandContextIri(
|
$id = $this->_expandIri(
|
||||||
$active_ctx, $ctx, $id, $base, $defined);
|
$active_ctx, $id, array('base' => true), $local_ctx, $defined);
|
||||||
|
}
|
||||||
|
$mapping->{'@id'} = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add @id to mapping
|
|
||||||
$mapping->{'@id'} = $id;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// non-IRIs *must* define @ids
|
|
||||||
if($prefix === null) {
|
if($prefix === null) {
|
||||||
throw new JsonLdException(
|
// non-IRIs *must* define @ids if @vocab is not available
|
||||||
'Invalid JSON-LD syntax; @context terms must define an @id.',
|
if(!property_exists($active_ctx, '@vocab')) {
|
||||||
'jsonld.SyntaxError', array('context' => $ctx, 'key' => $key));
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; @context terms must define an @id.',
|
||||||
|
'jsonld.SyntaxError',
|
||||||
|
array('context' => $local_ctx, 'term' => $term));
|
||||||
|
}
|
||||||
|
// prepend vocab to term
|
||||||
|
$mapping->{'@id'} = $active_ctx->{'@vocab'} . $term;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set @id based on prefix parent
|
// set @id based on prefix parent
|
||||||
if(property_exists($active_ctx->mappings, $prefix)) {
|
else if(property_exists($active_ctx->mappings, $prefix)) {
|
||||||
$suffix = substr($key, $colon + 1);
|
$suffix = substr($term, $colon + 1);
|
||||||
$mapping->{'@id'} = $active_ctx->mappings->{$prefix}->{'@id'} . $suffix;
|
$mapping->{'@id'} = $active_ctx->mappings->{$prefix}->{'@id'} . $suffix;
|
||||||
}
|
}
|
||||||
// key is an absolute IRI
|
// term is an absolute IRI
|
||||||
else {
|
else {
|
||||||
$mapping->{'@id'} = $key;
|
$mapping->{'@id'} = $term;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3405,13 +4144,14 @@ class JsonLdProcessor {
|
||||||
if(!is_string($type)) {
|
if(!is_string($type)) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; @context @type values must be strings.',
|
'Invalid JSON-LD syntax; @context @type values must be strings.',
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
if($type !== '@id') {
|
if($type !== '@id') {
|
||||||
// expand @type to full IRI
|
// expand @type to full IRI
|
||||||
$type = $this->_expandContextIri(
|
$type = $this->_expandIri(
|
||||||
$active_ctx, $ctx, $type, '', $defined);
|
$active_ctx, $type, array('vocab' => true, 'base' => true),
|
||||||
|
$local_ctx, $defined);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add @type to mapping
|
// add @type to mapping
|
||||||
|
|
@ -3420,11 +4160,12 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
if(property_exists($value, '@container')) {
|
if(property_exists($value, '@container')) {
|
||||||
$container = $value->{'@container'};
|
$container = $value->{'@container'};
|
||||||
if($container !== '@list' && $container !== '@set') {
|
if($container !== '@list' && $container !== '@set' &&
|
||||||
|
$container !== '@index' && $container !== '@language') {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; @context @container value must be ' +
|
'Invalid JSON-LD syntax; @context @container value must be ' .
|
||||||
'"@list" or "@set".',
|
'one of the following: @list, @set, @index, or @language.',
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add @container to mapping
|
// add @container to mapping
|
||||||
|
|
@ -3436,172 +4177,149 @@ class JsonLdProcessor {
|
||||||
$language = $value->{'@language'};
|
$language = $value->{'@language'};
|
||||||
if($language !== null && !is_string($language)) {
|
if($language !== null && !is_string($language)) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; @context @language value must be ' +
|
'Invalid JSON-LD syntax; @context @language value must be ' .
|
||||||
'a string or null.',
|
'a string or null.',
|
||||||
'jsonld.SyntaxError', array('context' => $ctx));
|
'jsonld.SyntaxError', array('context' => $local_ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add @language to mapping
|
// add @language to mapping
|
||||||
|
if($language !== null) {
|
||||||
|
$language = strtolower($language);
|
||||||
|
}
|
||||||
$mapping->{'@language'} = $language;
|
$mapping->{'@language'} = $language;
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge onto parent mapping if one exists for a prefix
|
// define term mapping
|
||||||
if($prefix !== null && property_exists($active_ctx->mappings, $prefix)) {
|
$active_ctx->mappings->{$term} = $mapping;
|
||||||
$child = $mapping;
|
$defined->{$term} = true;
|
||||||
$mapping = self::copy($active_ctx->mappings->{$prefix});
|
|
||||||
foreach($child as $k => $v) {
|
|
||||||
$mapping->{$k} = $v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// define key mapping
|
|
||||||
$active_ctx->mappings->{$key} = $mapping;
|
|
||||||
$defined->{$key} = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expands a string value to a full IRI during context processing. It can
|
* Expands a string to a full IRI. The string may be a term, a prefix, a
|
||||||
* be assumed that the value is not a keyword.
|
* relative IRI, or an absolute IRI. The associated absolute IRI will be
|
||||||
|
* returned.
|
||||||
*
|
*
|
||||||
* @param stdClass $active_ctx the current active context.
|
* @param stdClass $active_ctx the current active context.
|
||||||
* @param stdClass $ctx the local context being processed.
|
* @param string $value the string to expand.
|
||||||
* @param string $value the string value to expand.
|
* @param assoc $relative_to options for how to resolve relative IRIs:
|
||||||
* @param string $base the base IRI.
|
* base: true to resolve against the base IRI, false not to.
|
||||||
* @param stdClass $defined a map for tracking cycles in context definitions.
|
* vocab: true to concatenate after @vocab, false not to.
|
||||||
|
* @param stdClass $local_ctx the local context being processed (only given
|
||||||
|
* if called during document processing).
|
||||||
|
* @param defined a map for tracking cycles in context definitions (only given
|
||||||
|
* if called during document processing).
|
||||||
*
|
*
|
||||||
* @return mixed the expanded value.
|
* @return mixed the expanded value.
|
||||||
*/
|
*/
|
||||||
protected function _expandContextIri(
|
function _expandIri(
|
||||||
$active_ctx, $ctx, $value, $base, $defined) {
|
$active_ctx, $value, $relative_to=array(), $local_ctx=null, $defined=null) {
|
||||||
// dependency not defined, define it
|
|
||||||
if(property_exists($ctx, $value) &&
|
|
||||||
(!property_exists($defined, $value) || !$defined->{$value})) {
|
|
||||||
$this->_defineContextMapping($active_ctx, $ctx, $value, $base, $defined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// recurse if value is a term
|
|
||||||
if(property_exists($active_ctx->mappings, $value)) {
|
|
||||||
$id = $active_ctx->mappings->{$value}->{'@id'};
|
|
||||||
// value is already an absolute IRI
|
|
||||||
if($value === $id) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
return $this->_expandContextIri($active_ctx, $ctx, $id, $base, $defined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// split value into prefix:suffix
|
|
||||||
if(strpos($value, ':') !== false) {
|
|
||||||
list($prefix, $suffix) = explode(':', $value, 2);
|
|
||||||
|
|
||||||
// a prefix of '_' indicates a blank node
|
|
||||||
if($prefix === '_') {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a suffix of '//' indicates value is an absolute IRI
|
|
||||||
if(strpos($suffix, '//') === 0) {
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dependency not defined, define it
|
|
||||||
if(property_exists($ctx, $prefix) &&
|
|
||||||
(!property_exists($defined, $prefix) || !$defined->{$prefix})) {
|
|
||||||
$this->_defineContextMapping(
|
|
||||||
$active_ctx, $ctx, $prefix, $base, $defined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// recurse if prefix is defined
|
|
||||||
if(property_exists($active_ctx->mappings, $prefix)) {
|
|
||||||
$id = $active_ctx->mappings->{$prefix}->{'@id'};
|
|
||||||
return $this->_expandContextIri(
|
|
||||||
$active_ctx, $ctx, $id, $base, $defined) . $suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
// consider value an absolute IRI
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepend vocab
|
|
||||||
if(property_exists($ctx, '@vocab') && $ctx->{'@vocab'} !== null) {
|
|
||||||
$value = $this->_prependBase($ctx->{'@vocab'}, $value);
|
|
||||||
}
|
|
||||||
// prepend base
|
|
||||||
else {
|
|
||||||
$value = $this->_prependBase($base, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// value must now be an absolute IRI
|
|
||||||
if(!self::_isAbsoluteIri($value)) {
|
|
||||||
throw new JsonLdException(
|
|
||||||
'Invalid JSON-LD syntax; a @context value does not expand to ' +
|
|
||||||
'an absolute IRI.',
|
|
||||||
'jsonld.SyntaxError', array('context' => $ctx, 'value' => $value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expands a term into an absolute IRI. The term may be a regular term, a
|
|
||||||
* prefix, a relative IRI, or an absolute IRI. In any case, the associated
|
|
||||||
* absolute IRI will be returned.
|
|
||||||
*
|
|
||||||
* @param stdClass $ctx the active context to use.
|
|
||||||
* @param string $term the term to expand.
|
|
||||||
* @param string $base the base IRI to use if a relative IRI is detected.
|
|
||||||
*
|
|
||||||
* @return string the expanded term as an absolute IRI.
|
|
||||||
*/
|
|
||||||
protected function _expandTerm($ctx, $term, $base='') {
|
|
||||||
// nothing to expand
|
// nothing to expand
|
||||||
if($term === null) {
|
if($value === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the term has a mapping, so it is a plain term
|
// define term dependency if not defined
|
||||||
if(property_exists($ctx->mappings, $term)) {
|
if($local_ctx !== null && property_exists($local_ctx, $value) &&
|
||||||
$id = $ctx->mappings->{$term}->{'@id'};
|
!self::_hasKeyValue($defined, $value, true)) {
|
||||||
// term is already an absolute IRI
|
$this->_createTermDefinition($active_ctx, $local_ctx, $value, $defined);
|
||||||
if($term === $id) {
|
|
||||||
return $term;
|
|
||||||
}
|
|
||||||
return $this->_expandTerm($ctx, $id, $base);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// split term into prefix:suffix
|
if(property_exists($active_ctx->mappings, $value)) {
|
||||||
if(strpos($term, ':') !== false) {
|
$mapping = $active_ctx->mappings->{$value};
|
||||||
list($prefix, $suffix) = explode(':', $term, 2);
|
|
||||||
|
|
||||||
// a prefix of '_' indicates a blank node
|
|
||||||
if($prefix === '_') {
|
|
||||||
return $term;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a suffix of '//' indicates value is an absolute IRI
|
|
||||||
if(strpos($suffix, '//') === 0) {
|
|
||||||
return $term;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the term's prefix has a mapping, so it is a CURIE
|
|
||||||
if(property_exists($ctx->mappings, $prefix)) {
|
|
||||||
return $this->_expandTerm(
|
|
||||||
$ctx, $ctx->mappings->{$prefix}->{'@id'}, $base) . $suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
// consider term an absolute IRI
|
|
||||||
return $term;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use vocab
|
if(isset($mapping)) {
|
||||||
if(property_exists($ctx, '@vocab') && $ctx->{'@vocab'} !== null) {
|
// value is explicitly ignored with a null mapping
|
||||||
$term = $this->_prependBase($ctx->{'@vocab'}, $term);
|
if($mapping === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// prepend base to term
|
|
||||||
else {
|
else {
|
||||||
$term = $this->_prependBase($base, $term);
|
$mapping = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $term;
|
// term dependency cannot be a property generator
|
||||||
|
if($local_ctx !== null && $mapping &&
|
||||||
|
$mapping->propertyGenerator) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; a term definition cannot have a property ' .
|
||||||
|
'generator as a dependency.',
|
||||||
|
'jsonld.SyntaxError',
|
||||||
|
array('context' => $local_ctx, 'value' => $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_absolute = false;
|
||||||
|
$rval = $value;
|
||||||
|
|
||||||
|
// value is a term
|
||||||
|
if($mapping && !$mapping->propertyGenerator) {
|
||||||
|
$is_absolute = true;
|
||||||
|
$rval = $mapping->{'@id'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// keywords need no expanding (aliasing already handled by now)
|
||||||
|
if(self::_isKeyword($rval)) {
|
||||||
|
return $rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$is_absolute) {
|
||||||
|
// split value into prefix:suffix
|
||||||
|
$colon = strpos($rval, ':');
|
||||||
|
if($colon !== false) {
|
||||||
|
$is_absolute = true;
|
||||||
|
$prefix = substr($rval, 0, $colon);
|
||||||
|
$suffix = substr($rval, $colon + 1);
|
||||||
|
|
||||||
|
// do not expand blank nodes (prefix of '_') or already-absolute
|
||||||
|
// IRIs (suffix of '//')
|
||||||
|
if($prefix !== '_' && strpos($suffix, '//') !== 0) {
|
||||||
|
// prefix dependency not defined, define it
|
||||||
|
if($local_ctx !== null && property_exists($local_ctx, $prefix) &&
|
||||||
|
!self::_hasKeyValue($defined, $prefix, true)) {
|
||||||
|
$this->_createTermDefinition(
|
||||||
|
$active_ctx, $local_ctx, $prefix, $defined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use mapping if prefix is defined and not a property generator
|
||||||
|
if(property_exists($active_ctx->mappings, $prefix)) {
|
||||||
|
$mapping = $active_ctx->mappings->{$prefix};
|
||||||
|
if($mapping && !$mapping->propertyGenerator) {
|
||||||
|
$rval = $active_ctx->mappings->{$prefix}->{'@id'} . $suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($is_absolute) {
|
||||||
|
// rename blank node if requested
|
||||||
|
if(!$local_ctx && strpos($rval, '_:') === 0 &&
|
||||||
|
$active_ctx->namer !== null) {
|
||||||
|
$rval = $active_ctx->namer->getName($rval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// prepend vocab
|
||||||
|
else if(isset($relative_to['vocab']) && $relative_to['vocab'] &&
|
||||||
|
property_exists($active_ctx, '@vocab')) {
|
||||||
|
$rval = $active_ctx->{'@vocab'} . $rval;
|
||||||
|
}
|
||||||
|
// prepend base
|
||||||
|
else if(isset($relative_to['base']) && $relative_to['base']) {
|
||||||
|
$rval = jsonld_prepend_base($active_ctx->{'@base'}, $rval);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($local_ctx) {
|
||||||
|
// value must now be an absolute IRI
|
||||||
|
if(!self::_isAbsoluteIri($rval)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; a @context value does not expand to ' .
|
||||||
|
'an absolute IRI.',
|
||||||
|
'jsonld.SyntaxError',
|
||||||
|
array('context' => $local_ctx, 'value' => $value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3631,7 +4349,7 @@ class JsonLdProcessor {
|
||||||
$length = count($v);
|
$length = count($v);
|
||||||
for($i = 0; $i < $length; ++$i) {
|
for($i = 0; $i < $length; ++$i) {
|
||||||
if(is_string($v[$i])) {
|
if(is_string($v[$i])) {
|
||||||
$url = jsonld_to_absolute_url($v[$i], $base);
|
$url = jsonld_prepend_base($base, $v[$i]);
|
||||||
// replace w/@context if requested
|
// replace w/@context if requested
|
||||||
if($replace) {
|
if($replace) {
|
||||||
$ctx = $urls->{$url};
|
$ctx = $urls->{$url};
|
||||||
|
|
@ -3654,7 +4372,7 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
// string @context
|
// string @context
|
||||||
else if(is_string($v)) {
|
else if(is_string($v)) {
|
||||||
$v = jsonld_to_absolute_url($v, $base);
|
$v = jsonld_prepend_base($base, $v);
|
||||||
// replace w/@context if requested
|
// replace w/@context if requested
|
||||||
if($replace) {
|
if($replace) {
|
||||||
$input->{$k} = $urls->{$v};
|
$input->{$k} = $urls->{$v};
|
||||||
|
|
@ -3767,25 +4485,21 @@ class JsonLdProcessor {
|
||||||
$this->_findContextUrls($input, $urls, true, $base);
|
$this->_findContextUrls($input, $urls, true, $base);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepends a base IRI to the given relative IRI.
|
|
||||||
*
|
|
||||||
* @param string $base the base IRI.
|
|
||||||
* @param string $iri the relative IRI.
|
|
||||||
*
|
|
||||||
* @return string the absolute IRI.
|
|
||||||
*/
|
|
||||||
protected function _prependBase($base, $iri) {
|
|
||||||
return jsonld_prepend_base($base, $iri);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the initial context.
|
* Gets the initial context.
|
||||||
*
|
*
|
||||||
|
* @param assoc $options the options to use.
|
||||||
|
* base the document base IRI.
|
||||||
|
*
|
||||||
* @return stdClass the initial context.
|
* @return stdClass the initial context.
|
||||||
*/
|
*/
|
||||||
protected function _getInitialContext() {
|
protected function _getInitialContext($options) {
|
||||||
|
$namer = null;
|
||||||
|
if(isset($options['renameBlankNodes']) && $options['renameBlankNodes']) {
|
||||||
|
$namer = new UniqueNamer('_:t');
|
||||||
|
}
|
||||||
return (object)array(
|
return (object)array(
|
||||||
|
'@base' => jsonld_parse_url($options['base']),
|
||||||
'mappings' => new stdClass(),
|
'mappings' => new stdClass(),
|
||||||
'keywords' => (object)array(
|
'keywords' => (object)array(
|
||||||
'@context' => array(),
|
'@context' => array(),
|
||||||
|
|
@ -3795,6 +4509,7 @@ class JsonLdProcessor {
|
||||||
'@explicit' => array(),
|
'@explicit' => array(),
|
||||||
'@graph' => array(),
|
'@graph' => array(),
|
||||||
'@id' => array(),
|
'@id' => array(),
|
||||||
|
'@index' => array(),
|
||||||
'@language' => array(),
|
'@language' => array(),
|
||||||
'@list' => array(),
|
'@list' => array(),
|
||||||
'@omitDefault' => array(),
|
'@omitDefault' => array(),
|
||||||
|
|
@ -3802,8 +4517,161 @@ class JsonLdProcessor {
|
||||||
'@set' => array(),
|
'@set' => array(),
|
||||||
'@type' => array(),
|
'@type' => array(),
|
||||||
'@value' => array(),
|
'@value' => array(),
|
||||||
'@vocab' => array()
|
'@vocab' => array()),
|
||||||
));
|
'namer' => $namer,
|
||||||
|
'inverse' => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an inverse context for use in the compaction algorithm, if
|
||||||
|
* not already generated for the given active context.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the active context to use.
|
||||||
|
*
|
||||||
|
* @return stdClass the inverse context.
|
||||||
|
*/
|
||||||
|
protected function _getInverseContext($active_ctx) {
|
||||||
|
$inverse = $active_ctx->inverse = new stdClass();
|
||||||
|
|
||||||
|
// handle default language
|
||||||
|
$default_language = '@none';
|
||||||
|
if(property_exists($active_ctx, '@language')) {
|
||||||
|
$default_language = $active_ctx->{'@language'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// create term selections for each mapping in the context, ordered by
|
||||||
|
// shortest and then lexicographically least
|
||||||
|
$mappings = $active_ctx->mappings;
|
||||||
|
$terms = array_keys((array)$mappings);
|
||||||
|
usort($terms, array($this, '_compareShortestLeast'));
|
||||||
|
foreach($terms as $term) {
|
||||||
|
$mapping = $mappings->{$term};
|
||||||
|
if($mapping === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over every IRI in the mapping
|
||||||
|
$iris = $mapping->{'@id'};
|
||||||
|
$iris = self::arrayify($iris);
|
||||||
|
foreach($iris as $iri) {
|
||||||
|
// initialize container map
|
||||||
|
if(!property_exists($inverse, $iri)) {
|
||||||
|
$inverse->{$iri} = new stdClass();
|
||||||
|
}
|
||||||
|
$container_map = $inverse->{$iri};
|
||||||
|
|
||||||
|
// add term selection where it applies
|
||||||
|
if(property_exists($mapping, '@container')) {
|
||||||
|
$container = $mapping->{'@container'};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$container = '@none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entry
|
||||||
|
if(!property_exists($container_map, $container)) {
|
||||||
|
$container_map->{$container} = (object)array(
|
||||||
|
'@language' => new stdClass(),
|
||||||
|
'@type' => new stdClass());
|
||||||
|
$container_map->{$container}->{'@language'}->{$default_language} =
|
||||||
|
(object)array('term' => null, 'propertyGenerators' => array());
|
||||||
|
}
|
||||||
|
$entry = $container_map->{$container};
|
||||||
|
|
||||||
|
// consider updating @language entry if @type is not specified
|
||||||
|
if(!property_exists($mapping, '@type')) {
|
||||||
|
// if a @language is specified, update its specific entry
|
||||||
|
if(property_exists($mapping, '@language')) {
|
||||||
|
$language = $mapping->{'@language'};
|
||||||
|
if($language === null) {
|
||||||
|
$language = '@null';
|
||||||
|
}
|
||||||
|
$this->_addPreferredTerm(
|
||||||
|
$mapping, $term, $entry->{'@language'}, $language);
|
||||||
|
}
|
||||||
|
// add an entry for the default language and for no @language
|
||||||
|
else {
|
||||||
|
$this->_addPreferredTerm(
|
||||||
|
$mapping, $term, $entry->{'@language'}, $default_language);
|
||||||
|
$this->_addPreferredTerm(
|
||||||
|
$mapping, $term, $entry->{'@language'}, '@none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider updating @type entry if @language is not specified
|
||||||
|
if(!property_exists($mapping, '@language')) {
|
||||||
|
if(property_exists($mapping, '@type')) {
|
||||||
|
$type = $mapping->{'@type'};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$type = '@none';
|
||||||
|
}
|
||||||
|
$this->_addPreferredTerm($mapping, $term, $entry->{'@type'}, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or updates the term or property generator for the given entry.
|
||||||
|
*
|
||||||
|
* @param stdClass $mapping the term mapping.
|
||||||
|
* @param string $term the term to add.
|
||||||
|
* @param stdClass $entry the inverse context type_or_language entry to
|
||||||
|
* add to.
|
||||||
|
* @param string $type_or_language_value the key in the entry to add to.
|
||||||
|
*/
|
||||||
|
function _addPreferredTerm($mapping, $term, $entry, $type_or_language_value) {
|
||||||
|
if(!property_exists($entry, $type_or_language_value)) {
|
||||||
|
$entry->{$type_or_language_value} = (object)array(
|
||||||
|
'term' => null, 'propertyGenerators' => array());
|
||||||
|
}
|
||||||
|
|
||||||
|
$e = $entry->{$type_or_language_value};
|
||||||
|
if($mapping->propertyGenerator) {
|
||||||
|
$e->propertyGenerators[] = $term;
|
||||||
|
}
|
||||||
|
else if($e->term === null) {
|
||||||
|
$e->term = $term;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones an active context, creating a child active context.
|
||||||
|
*
|
||||||
|
* @return stdClass a clone (child) of the active context.
|
||||||
|
*/
|
||||||
|
protected function _cloneActiveContext($active_ctx) {
|
||||||
|
$child = new stdClass();
|
||||||
|
$child->{'@base'} = $active_ctx->{'@base'};
|
||||||
|
$child->keywords = self::copy($active_ctx->keywords);
|
||||||
|
$child->mappings = self::copy($active_ctx->mappings);
|
||||||
|
$child->namer = $active_ctx->namer;
|
||||||
|
$child->inverse = null;
|
||||||
|
return $child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of this active context that can be shared between
|
||||||
|
* different processing algorithms. This method only copies the parts
|
||||||
|
* of the active context that can't be shared.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the active context to use.
|
||||||
|
*
|
||||||
|
* @return stdClass a shareable copy of the active context.
|
||||||
|
*/
|
||||||
|
public function _shareActiveContext($active_ctx) {
|
||||||
|
$rval = new stdClass();
|
||||||
|
$rval->{'@base'} = $active_ctx->{'@base'};
|
||||||
|
$rval->keywords = $active_ctx->keywords;
|
||||||
|
$rval->mappings = $active_ctx->mappings;
|
||||||
|
if($active_ctx->namer !== null) {
|
||||||
|
$rval->namer = clone $active_ctx->namer;
|
||||||
|
}
|
||||||
|
$rval->inverse = $active_ctx->inverse;
|
||||||
|
return $rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -3867,7 +4735,7 @@ class JsonLdProcessor {
|
||||||
protected static function _appendUniqueRdfStatement(
|
protected static function _appendUniqueRdfStatement(
|
||||||
&$statements, $statement) {
|
&$statements, $statement) {
|
||||||
foreach($statements as $s) {
|
foreach($statements as $s) {
|
||||||
if(JsonLdProcessor::_compareRdfStatements($s, $statement)) {
|
if(self::_compareRdfStatements($s, $statement)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3875,43 +4743,34 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given value is a keyword (or a keyword alias).
|
* Returns whether or not the given value is a keyword.
|
||||||
*
|
*
|
||||||
* @param string $v the value to check.
|
* @param string $v the value to check.
|
||||||
* @param stdClass [$ctx] the active context to check against.
|
|
||||||
*
|
*
|
||||||
* @return bool true if the value is a keyword, false if not.
|
* @return bool true if the value is a keyword, false if not.
|
||||||
*/
|
*/
|
||||||
protected static function _isKeyword($v, $ctx=null) {
|
protected static function _isKeyword($v) {
|
||||||
if($ctx !== null) {
|
if(!is_string($v)) {
|
||||||
if(property_exists($ctx->keywords, $v)) {
|
return false;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
foreach($ctx->keywords as $kw => $aliases) {
|
|
||||||
if(in_array($v, $aliases) !== false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
switch($v) {
|
||||||
switch($v) {
|
case '@context':
|
||||||
case '@context':
|
case '@container':
|
||||||
case '@container':
|
case '@default':
|
||||||
case '@default':
|
case '@embed':
|
||||||
case '@embed':
|
case '@explicit':
|
||||||
case '@explicit':
|
case '@graph':
|
||||||
case '@graph':
|
case '@id':
|
||||||
case '@id':
|
case '@index':
|
||||||
case '@language':
|
case '@language':
|
||||||
case '@list':
|
case '@list':
|
||||||
case '@omitDefault':
|
case '@omitDefault':
|
||||||
case '@preserve':
|
case '@preserve':
|
||||||
case '@set':
|
case '@set':
|
||||||
case '@type':
|
case '@type':
|
||||||
case '@value':
|
case '@value':
|
||||||
case '@vocab':
|
case '@vocab':
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -3933,18 +4792,18 @@ class JsonLdProcessor {
|
||||||
* @param mixed $v the value to check.
|
* @param mixed $v the value to check.
|
||||||
*/
|
*/
|
||||||
protected static function _validateTypeValue($v) {
|
protected static function _validateTypeValue($v) {
|
||||||
// must be a string, subject reference, or empty object
|
// must be a string or empty object
|
||||||
if(is_string($v) || self::_isSubjectReference($v) ||
|
if(is_string($v) || self::_isEmptyObject($v)) {
|
||||||
self::_isEmptyObject($v)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be an array
|
// must be an array
|
||||||
$is_valid = false;
|
$is_valid = false;
|
||||||
if(is_array($v)) {
|
if(is_array($v)) {
|
||||||
|
// must contain only strings
|
||||||
$is_valid = true;
|
$is_valid = true;
|
||||||
foreach($v as $e) {
|
foreach($v as $e) {
|
||||||
if(!(is_string($e) || self::_isSubjectReference($e))) {
|
if(!(is_string($e))) {
|
||||||
$is_valid = false;
|
$is_valid = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -3953,7 +4812,7 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
if(!$is_valid) {
|
if(!$is_valid) {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
'Invalid JSON-LD syntax; "@type" value must a string, an array ' +
|
'Invalid JSON-LD syntax; "@type" value must a string, an array ' .
|
||||||
'of strings, or an empty object.',
|
'of strings, or an empty object.',
|
||||||
'jsonld.SyntaxError', array('value' => $v));
|
'jsonld.SyntaxError', array('value' => $v));
|
||||||
}
|
}
|
||||||
|
|
@ -4062,6 +4921,39 @@ class JsonLdProcessor {
|
||||||
protected static function _isAbsoluteIri($v) {
|
protected static function _isAbsoluteIri($v) {
|
||||||
return strpos($v, ':') !== false;
|
return strpos($v, ':') !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given target has the given key and its
|
||||||
|
* value equals is the given value.
|
||||||
|
*
|
||||||
|
* @param stdClass $target the target object.
|
||||||
|
* @param string key the key to check.
|
||||||
|
* @param mixed $value the value to check.
|
||||||
|
*
|
||||||
|
* @return bool true if the target has the given key and its value matches.
|
||||||
|
*/
|
||||||
|
// FIXME: use this function throughout processor, check for "property_exists"
|
||||||
|
protected static function _hasKeyValue($target, $key, $value) {
|
||||||
|
return (property_exists($target, $key) && $target->{$key} === $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if both of the given objects have the same value for the
|
||||||
|
* given key or if neither of the objects contain the given key.
|
||||||
|
*
|
||||||
|
* @param stdClass $o1 the first object.
|
||||||
|
* @param stdClass $o2 the second object.
|
||||||
|
* @param string key the key to check.
|
||||||
|
*
|
||||||
|
* @return bool true if both objects have the same value for the key or
|
||||||
|
* neither has the key.
|
||||||
|
*/
|
||||||
|
protected static function _compareKeyValues($o1, $o2, $key) {
|
||||||
|
if(property_exists($o1, $key)) {
|
||||||
|
return property_exists($o2, $key) && $o1->{$key} === $o2->{$key};
|
||||||
|
}
|
||||||
|
return !property_exists($o2, $key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the N-Quads RDF parser
|
// register the N-Quads RDF parser
|
||||||
|
|
@ -4240,4 +5132,66 @@ class Permutator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ActiveContextCache caches active contexts so they can be reused without
|
||||||
|
* the overhead of recomputing them.
|
||||||
|
*/
|
||||||
|
class ActiveContextCache {
|
||||||
|
/**
|
||||||
|
* Constructs a new ActiveContextCache.
|
||||||
|
*
|
||||||
|
* @param int size the maximum size of the cache, defaults to 100.
|
||||||
|
*/
|
||||||
|
public function __construct($size=100) {
|
||||||
|
$this->order = array();
|
||||||
|
$this->cache = new stdClass();
|
||||||
|
$this->size = $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an active context from the cache based on the current active
|
||||||
|
* context and the new local context.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the current active context.
|
||||||
|
* @param stdClass $local_ctx the new local context.
|
||||||
|
*
|
||||||
|
* @return mixed a shared copy of the cached active context or null.
|
||||||
|
*/
|
||||||
|
public function get($active_ctx, $local_ctx) {
|
||||||
|
$key1 = serialize($active_ctx);
|
||||||
|
$key2 = serialize($local_ctx);
|
||||||
|
if(property_exists($this->cache, $key1)) {
|
||||||
|
$level1 = $this->cache->{$key1};
|
||||||
|
if(property_exists($level1, $key2)) {
|
||||||
|
// get shareable copy of cached active context
|
||||||
|
return JsonLdProcessor::_shareActiveContext($level1->{$key2});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an active context in the cache based on the previous active
|
||||||
|
* context and the just-processed local context.
|
||||||
|
*
|
||||||
|
* @param stdClass $active_ctx the previous active context.
|
||||||
|
* @param stdClass $local_ctx the just-processed local context.
|
||||||
|
* @param stdClass $result the resulting active context.
|
||||||
|
*/
|
||||||
|
public function set($active_ctx, $local_ctx, $result) {
|
||||||
|
if(count($this->order) === $this->size) {
|
||||||
|
$entry = array_shift($this->order);
|
||||||
|
unset($this->cache->{$entry->activeCtx}->{$entry->localCtx});
|
||||||
|
}
|
||||||
|
$key1 = serialize($active_ctx);
|
||||||
|
$key2 = serialize($local_ctx);
|
||||||
|
$this->order[] = (object)array(
|
||||||
|
'activeCtx' => $key1, 'localCtx' => $key2);
|
||||||
|
if(!property_exists($this->cache)) {
|
||||||
|
$this->cache->{$key1} = new stdClass();
|
||||||
|
}
|
||||||
|
$this->cache->{$key1}->{$key2} = $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* end of file, omit ?> */
|
/* end of file, omit ?> */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue