Bump version.

This commit is contained in:
Dave Longley 2013-02-15 16:25:30 -05:00
commit d80c0acb4f

2064
jsonld.php
View file

@ -1,7 +1,7 @@
<?php
/**
* PHP implementation of the JSON-LD API.
* Version: 0.0.2
* Version: 0.0.3
*
* @author Dave Longley
*
@ -70,22 +70,22 @@ function jsonld_expand($input, $options=array()) {
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.
* [urlClient(url)] the URL client to use.
/**
* Performs JSON-LD flattening.
*
* @return mixed the flattened JSON-LD output.
*/
* @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.
* [urlClient(url)] the URL client 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);
}
$p = new JsonLdProcessor();
return $p->flatten($input, $ctx, $options);
}
/**
* Performs JSON-LD framing.
@ -210,66 +210,66 @@ function jsonld_get_url($url) {
return jsonld_default_get_url($url);
}
/**
* The default implementation to retrieve JSON-LD at the given URL.
*
* @param string $url the URL to to retrieve.
*
* @return the JSON-LD.
*/
function jsonld_default_get_url($url) {
// default JSON-LD GET implementation
$opts = array('http' =>
array(
'method' => "GET",
'header' =>
"Accept: application/ld+json\r\n" .
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
$stream = stream_context_create($opts);
$result = @file_get_contents($url, false, $stream);
if($result === false) {
throw new Exception("Could not GET url: '$url'");
}
return $result;
}
/** Registered global RDF Statement parsers hashed by content-type. */
global $jsonld_rdf_parsers;
$jsonld_rdf_parsers = new stdClass();
/**
* Registers a global RDF Statement parser by content-type, for use with
* jsonld_from_rdf. Global parsers will be used by JsonLdProcessors that do
* not register their own parsers.
*
* @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements).
*/
function jsonld_register_rdf_parser($content_type, $parser) {
global $jsonld_rdf_parsers;
$jsonld_rdf_parsers->{$content_type} = $parser;
}
/**
* Unregisters a global RDF Statement parser by content-type.
*
* @param string $content_type the content-type for the parser.
*/
function jsonld_unregister_rdf_parser($content_type) {
global $jsonld_rdf_parsers;
if(property_exists($jsonld_rdf_parsers, $content_type)) {
unset($jsonld_rdf_parsers->{$content_type});
}
/**
* The default implementation to retrieve JSON-LD at the given URL.
*
* @param string $url the URL to to retrieve.
*
* @return the JSON-LD.
*/
function jsonld_default_get_url($url) {
// default JSON-LD GET implementation
$opts = array('http' =>
array(
'method' => "GET",
'header' =>
"Accept: application/ld+json\r\n" .
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
$stream = stream_context_create($opts);
$result = @file_get_contents($url, false, $stream);
if($result === false) {
throw new Exception("Could not GET url: '$url'");
}
return $result;
}
/**
* Parses a URL into its component parts.
*
* @param string $url the URL to parse.
*
* @return assoc the parsed URL.
*/
/** Registered global RDF Statement parsers hashed by content-type. */
global $jsonld_rdf_parsers;
$jsonld_rdf_parsers = new stdClass();
/**
* Registers a global RDF Statement parser by content-type, for use with
* jsonld_from_rdf. Global parsers will be used by JsonLdProcessors that do
* not register their own parsers.
*
* @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements).
*/
function jsonld_register_rdf_parser($content_type, $parser) {
global $jsonld_rdf_parsers;
$jsonld_rdf_parsers->{$content_type} = $parser;
}
/**
* Unregisters a global RDF Statement parser by content-type.
*
* @param string $content_type the content-type for the parser.
*/
function jsonld_unregister_rdf_parser($content_type) {
global $jsonld_rdf_parsers;
if(property_exists($jsonld_rdf_parsers, $content_type)) {
unset($jsonld_rdf_parsers->{$content_type});
}
}
/**
* Parses a URL into its component parts.
*
* @param string $url the URL to parse.
*
* @return assoc the parsed URL.
*/
function jsonld_parse_url($url) {
$rval = parse_url($url);
if(isset($rval['host'])) {
@ -307,96 +307,96 @@ function jsonld_parse_url($url) {
* @return string the absolute IRI.
*/
function jsonld_prepend_base($base, $iri) {
// already an absolute IRI
if(strpos($iri, ':') !== false) {
return $iri;
// already an absolute IRI
if(strpos($iri, ':') !== false) {
return $iri;
}
if(is_string($base)) {
$base = jsonld_parse_url($base);
}
$authority = $base['host'];
if(isset($base['port'])) {
$authority .= ":{$base['port']}";
if(isset($base['port'])) {
$authority .= ":{$base['port']}";
}
$rel = jsonld_parse_url($iri);
// per RFC3986 normalize slashes and dots in path
// IRI contains authority
if(strpos($iri, '//') === 0) {
$path = substr($iri, 2);
// 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 absolute path
else if(strpos($rel['path'], '/') === 0) {
$path = $rel['path'];
}
else {
$path = $base['path'];
// prepend last directory for base
$path = substr($path, strlen($authority));
}
// IRI represents an absolute 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'];
}
$idx = ($idx === false) ? 0 : $idx + 1;
$path = substr($path, 0, $idx) . $rel['path'];
}
}
$segments = explode('/', $path);
$segments = explode('/', $path);
// remove '.' and '' (do not remove trailing empty path)
$idx = -1;
$end = count($segments) - 1;
$filter = function($e) use (&$idx, $end) {
$idx += 1;
return $e !== '.' && ($e !== '' || $idx === $end);
};
return $e !== '.' && ($e !== '' || $idx === $end);
};
$segments = array_values(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;
}
$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);
array_splice($segments, $i - 1, 2);
$segments = array_values($segments);
$i -= 1;
}
else {
$i += 1;
}
}
$i -= 1;
}
else {
$i += 1;
}
}
$path = '/' . implode('/', $segments);
// add query and hash
if(isset($rel['query'])) {
$path .= "?{$rel['query']}";
}
if(isset($rel['fragment'])) {
$path .= "#{$rel['fragment']}";
// add query and hash
if(isset($rel['query'])) {
$path .= "?{$rel['query']}";
}
if(isset($rel['fragment'])) {
$path .= "#{$rel['fragment']}";
}
$absolute_iri = "{$base['scheme']}://";
if(isset($base['auth'])) {
$absolute_iri .= "{$base['auth']}@";
if(isset($base['auth'])) {
$absolute_iri .= "{$base['auth']}@";
}
$absolute_iri .= "$authority$path";
$absolute_iri .= "$authority$path";
return $absolute_iri;
}
@ -557,9 +557,9 @@ class JsonLdProcessor {
$compacted = new stdClass();
}
}
// always use array if graph option is on
else if($options['graph']) {
$compacted = self::arrayify($compacted);
// always use array if graph option is on
else if($options['graph']) {
$compacted = self::arrayify($compacted);
}
// follow @context key
@ -647,9 +647,9 @@ class JsonLdProcessor {
$input, new stdClass(), $options['urlClient'], $options['base']);
}
catch(Exception $e) {
throw new JsonLdException(
'Could not perform JSON-LD expansion.',
'jsonld.ExpandError', null, $e);
throw new JsonLdException(
'Could not perform JSON-LD expansion.',
'jsonld.ExpandError', null, $e);
}
// do expansion
@ -668,53 +668,53 @@ class JsonLdProcessor {
return self::arrayify($expanded);
}
/**
* Performs JSON-LD flattening.
*
/**
* 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.
* [urlClient(url)] the URL client to use.
*
* @return array the flattened output.
*/
*
* @return array the flattened output.
*/
public function flatten($input, $ctx, $options) {
// set default options
isset($options['base']) or $options['base'] = '';
// set default options
isset($options['base']) or $options['base'] = '';
isset($options['urlClient']) or $options['urlClient'] = 'jsonld_get_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
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;
// 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(
throw new JsonLdException(
'Could not compact flattened output.',
'jsonld.FlattenError', null, $e);
'jsonld.FlattenError', null, $e);
}
return $compacted;
}
return $compacted;
}
/**
* Performs JSON-LD framing.
@ -852,9 +852,9 @@ class JsonLdProcessor {
!property_exists($this->rdfParsers, $options['format'])) ||
$this->rdfParsers === null &&
!property_exists($jsonld_rdf_parsers, $options['format'])) {
throw new JsonLdException(
'Unknown input format.',
'jsonld.UnknownFormat', array('format' => $options['format']));
throw new JsonLdException(
'Unknown input format.',
'jsonld.UnknownFormat', array('format' => $options['format']));
}
if($this->rdfParsers !== null) {
$callable = $this->rdfParsers->{$options['format']};
@ -939,14 +939,14 @@ class JsonLdProcessor {
public function processContext($active_ctx, $local_ctx, $options) {
// set default options
isset($options['base']) or $options['base'] = '';
isset($options['renameBlankNodes']) or $options['renameBlankNodes'] =
true;
isset($options['renameBlankNodes']) or $options['renameBlankNodes'] =
true;
isset($options['urlClient']) or $options['urlClient'] = 'jsonld_get_url';
// return initial context early for null context
if($local_ctx === null) {
return $this->_getInitialContext($options);
}
// return initial context early for null context
if($local_ctx === null) {
return $this->_getInitialContext($options);
}
// retrieve URLs in local_ctx
$ctx = self::copy($local_ctx);
@ -957,11 +957,11 @@ class JsonLdProcessor {
$this->_retrieveContextUrls(
$ctx, new stdClass(), $options['urlClient'], $options['base']);
}
catch(Exception $e) {
throw new JsonLdException(
'Could not process JSON-LD context.',
'jsonld.ContextError', null, $e);
}
catch(Exception $e) {
throw new JsonLdException(
'Could not process JSON-LD context.',
'jsonld.ContextError', null, $e);
}
// process context
return $this->_processContext($active_ctx, $ctx, $options);
@ -1231,182 +1231,182 @@ class JsonLdProcessor {
return $rval;
}
/**
* Parses statements in the form of N-Quads.
*
* @param string $input the N-Quads input to parse.
*
* @return array the resulting RDF statements.
*/
public static function parseNQuads($input) {
// define partial regexes
$iri = '(?:<([^:]+:[^>]*)>)';
$bnode = '(_:(?:[A-Za-z][A-Za-z0-9]*))';
$plain = '"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"';
$datatype = "(?:\\^\\^$iri)";
$language = '(?:@([a-z]+(?:-[a-z0-9]+)*))';
$literal = "(?:$plain(?:$datatype|$language)?)";
$ws = '[ \\t]';
$eoln = '/(?:\r\n)|(?:\n)|(?:\r)/';
$empty = "/^$ws*$/";
// define quad part regexes
$subject = "(?:$iri|$bnode)$ws+";
$property = "$iri$ws+";
$object = "(?:$iri|$bnode|$literal)$ws*";
$graph = "(?:\\.|(?:(?:$iri|$bnode)$ws*\\.))";
// full quad regex
$quad = "/^$ws*$subject$property$object$graph$ws*$/";
// build RDF statements
$statements = array();
// split N-Quad input into lines
$lines = preg_split($eoln, $input);
$line_number = 0;
foreach($lines as $line) {
$line_number += 1;
// skip empty lines
if(preg_match($empty, $line)) {
continue;
}
// parse quad
if(!preg_match($quad, $line, $match)) {
throw new JsonLdException(
'Error while parsing N-Quads; invalid quad.',
'jsonld.ParseError', array('line' => $line_number));
}
// create RDF statement
$s = (object)array(
'subject' => new stdClass(),
'property' => new stdClass(),
'object' => new stdClass());
// get subject
if($match[1] !== '') {
$s->subject->nominalValue = $match[1];
$s->subject->interfaceName = 'IRI';
}
else {
$s->subject->nominalValue = $match[2];
$s->subject->interfaceName = 'BlankNode';
}
// get property
$s->property->nominalValue = $match[3];
$s->property->interfaceName = 'IRI';
// get object
if($match[4] !== '') {
$s->object->nominalValue = $match[4];
$s->object->interfaceName = 'IRI';
}
else if($match[5] !== '') {
$s->object->nominalValue = $match[5];
$s->object->interfaceName = 'BlankNode';
}
/**
* Parses statements in the form of N-Quads.
*
* @param string $input the N-Quads input to parse.
*
* @return array the resulting RDF statements.
*/
public static function parseNQuads($input) {
// define partial regexes
$iri = '(?:<([^:]+:[^>]*)>)';
$bnode = '(_:(?:[A-Za-z][A-Za-z0-9]*))';
$plain = '"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"';
$datatype = "(?:\\^\\^$iri)";
$language = '(?:@([a-z]+(?:-[a-z0-9]+)*))';
$literal = "(?:$plain(?:$datatype|$language)?)";
$ws = '[ \\t]';
$eoln = '/(?:\r\n)|(?:\n)|(?:\r)/';
$empty = "/^$ws*$/";
// define quad part regexes
$subject = "(?:$iri|$bnode)$ws+";
$property = "$iri$ws+";
$object = "(?:$iri|$bnode|$literal)$ws*";
$graph = "(?:\\.|(?:(?:$iri|$bnode)$ws*\\.))";
// full quad regex
$quad = "/^$ws*$subject$property$object$graph$ws*$/";
// build RDF statements
$statements = array();
// split N-Quad input into lines
$lines = preg_split($eoln, $input);
$line_number = 0;
foreach($lines as $line) {
$line_number += 1;
// skip empty lines
if(preg_match($empty, $line)) {
continue;
}
// parse quad
if(!preg_match($quad, $line, $match)) {
throw new JsonLdException(
'Error while parsing N-Quads; invalid quad.',
'jsonld.ParseError', array('line' => $line_number));
}
// create RDF statement
$s = (object)array(
'subject' => new stdClass(),
'property' => new stdClass(),
'object' => new stdClass());
// get subject
if($match[1] !== '') {
$s->subject->nominalValue = $match[1];
$s->subject->interfaceName = 'IRI';
}
else {
$unescaped = str_replace(
$s->subject->nominalValue = $match[2];
$s->subject->interfaceName = 'BlankNode';
}
// get property
$s->property->nominalValue = $match[3];
$s->property->interfaceName = 'IRI';
// get object
if($match[4] !== '') {
$s->object->nominalValue = $match[4];
$s->object->interfaceName = 'IRI';
}
else if($match[5] !== '') {
$s->object->nominalValue = $match[5];
$s->object->interfaceName = 'BlankNode';
}
else {
$unescaped = str_replace(
array('\"', '\t', '\n', '\r', '\\\\'),
array('"', "\t", "\n", "\r", '\\'),
$match[6]);
$s->object->nominalValue = $unescaped;
$s->object->interfaceName = 'LiteralNode';
if(isset($match[7]) && $match[7] !== '') {
$s->object->datatype = (object)array(
'nominalValue' => $match[7], 'interfaceName' => 'IRI');
}
else if(isset($match[8]) && $match[8] !== '') {
$s->object->language = $match[8];
}
}
// get graph
if(isset($match[9]) && $match[9] !== '') {
$s->name = (object)array(
'nominalValue' => $match[9], 'interfaceName' => 'IRI');
}
else if(isset($match[10]) && $match[10] !== '') {
$s->name = (object)array(
'nominalValue' => $match[10], 'interfaceName' => 'BlankNode');
}
array('"', "\t", "\n", "\r", '\\'),
$match[6]);
$s->object->nominalValue = $unescaped;
$s->object->interfaceName = 'LiteralNode';
if(isset($match[7]) && $match[7] !== '') {
$s->object->datatype = (object)array(
'nominalValue' => $match[7], 'interfaceName' => 'IRI');
}
else if(isset($match[8]) && $match[8] !== '') {
$s->object->language = $match[8];
}
}
// get graph
if(isset($match[9]) && $match[9] !== '') {
$s->name = (object)array(
'nominalValue' => $match[9], 'interfaceName' => 'IRI');
}
else if(isset($match[10]) && $match[10] !== '') {
$s->name = (object)array(
'nominalValue' => $match[10], 'interfaceName' => 'BlankNode');
}
// add statement
self::_appendUniqueRdfStatement($statements, $s);
}
return $statements;
}
/**
* Converts an RDF statement to an N-Quad string (a single quad).
*
* @param stdClass $statement the RDF statement to convert.
* @param string $bnode the bnode the statement is mapped to (optional, for
* use during normalization only).
*
* @return the N-Quad string.
*/
public static function toNQuad($statement, $bnode=null) {
$s = $statement->subject;
$p = $statement->property;
$o = $statement->object;
$g = property_exists($statement, 'name') ? $statement->name : null;
$quad = '';
// subject is an IRI or bnode
if($s->interfaceName === 'IRI') {
$quad .= "<{$s->nominalValue}>";
}
// normalization mode
else if($bnode !== null) {
$quad .= ($s->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else {
$quad .= $s->nominalValue;
}
// property is always an IRI
$quad .= " <{$p->nominalValue}> ";
// object is IRI, bnode, or literal
if($o->interfaceName === 'IRI') {
$quad .= "<{$o->nominalValue}>";
}
else if($o->interfaceName === 'BlankNode') {
// normalization mode
if($bnode !== null) {
$quad .= ($o->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else {
$quad .= $o->nominalValue;
}
}
self::_appendUniqueRdfStatement($statements, $s);
}
return $statements;
}
/**
* Converts an RDF statement to an N-Quad string (a single quad).
*
* @param stdClass $statement the RDF statement to convert.
* @param string $bnode the bnode the statement is mapped to (optional, for
* use during normalization only).
*
* @return the N-Quad string.
*/
public static function toNQuad($statement, $bnode=null) {
$s = $statement->subject;
$p = $statement->property;
$o = $statement->object;
$g = property_exists($statement, 'name') ? $statement->name : null;
$quad = '';
// subject is an IRI or bnode
if($s->interfaceName === 'IRI') {
$quad .= "<{$s->nominalValue}>";
}
// normalization mode
else if($bnode !== null) {
$quad .= ($s->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else {
$escaped = str_replace(
array('\\', "\t", "\n", "\r", '"'),
array('\\\\', '\t', '\n', '\r', '\"'),
$o->nominalValue);
$quad .= '"' . $escaped . '"';
$quad .= $s->nominalValue;
}
// property is always an IRI
$quad .= " <{$p->nominalValue}> ";
// object is IRI, bnode, or literal
if($o->interfaceName === 'IRI') {
$quad .= "<{$o->nominalValue}>";
}
else if($o->interfaceName === 'BlankNode') {
// normalization mode
if($bnode !== null) {
$quad .= ($o->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else {
$quad .= $o->nominalValue;
}
}
else {
$escaped = str_replace(
array('\\', "\t", "\n", "\r", '"'),
array('\\\\', '\t', '\n', '\r', '\"'),
$o->nominalValue);
$quad .= '"' . $escaped . '"';
if(property_exists($o, 'datatype') &&
$o->datatype->nominalValue !== self::XSD_STRING) {
$quad .= "^^<{$o->datatype->nominalValue}>";
}
else if(property_exists($o, 'language')) {
$quad .= '@' . $o->language;
}
}
// graph
$o->datatype->nominalValue !== self::XSD_STRING) {
$quad .= "^^<{$o->datatype->nominalValue}>";
}
else if(property_exists($o, 'language')) {
$quad .= '@' . $o->language;
}
}
// graph
if($g !== null) {
if($g->interfaceName === 'IRI') {
if($g->interfaceName === 'IRI') {
$quad .= " <{$g->nominalValue}>";
}
else if($bnode) {
@ -1414,35 +1414,35 @@ class JsonLdProcessor {
}
else {
$quad .= " {$g->nominalValue}";
}
}
$quad .= " .\n";
return $quad;
}
}
}
/**
$quad .= " .\n";
return $quad;
}
/**
* Registers a processor-specific RDF Statement parser by content-type.
* Global parsers will no longer be used by this processor.
*
* @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements).
*/
* Global parsers will no longer be used by this processor.
*
* @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements).
*/
public function registerRDFParser($content_type, $parser) {
if($this->rdfParsers === null) {
$this->rdfParsers = new stdClass();
}
$this->rdfParsers->{$content_type} = $parser;
}
/**
$this->rdfParsers->{$content_type} = $parser;
}
/**
* Unregisters a process-specific RDF Statement parser by content-type. If
* there are no remaining processor-specific parsers, then the global
* parsers will be re-enabled.
*
* @param string $content_type the content-type for the parser.
*/
* parsers will be re-enabled.
*
* @param string $content_type the content-type for the parser.
*/
public function unregisterRDFParser($content_type) {
if($this->rdfParsers !== null &&
property_exists($this->rdfParsers, $content_type)) {
@ -1450,8 +1450,8 @@ class JsonLdProcessor {
if(count(get_object_vars($content_type)) === 0) {
$this->rdfParsers = null;
}
}
}
}
}
/**
* If $value is an array, returns $value, otherwise returns an array
@ -1913,25 +1913,25 @@ class JsonLdProcessor {
'@list' => self::arrayify($expanded_value));
}
// add copy of value for each property from property generator
if(is_array($expanded_property)) {
// add copy of value for each property from property generator
if(is_array($expanded_property)) {
$expanded_value = $this->_labelBlankNodes(
$active_ctx->namer, $expanded_value);
foreach($expanded_property as $iri) {
foreach($expanded_property as $iri) {
self::addValue(
$rval, $iri, self::copy($expanded_value),
array('propertyIsArray' => true));
}
}
// add value for property
else {
// use an array except for certain keywords
array('propertyIsArray' => true));
}
}
// 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(
$expanded_property, array(
'@index', '@id', '@type', '@value', '@language')));
self::addValue(
$rval, $expanded_property, $expanded_value,
array('propertyIsArray' => $use_array));
array('propertyIsArray' => $use_array));
}
}
@ -1941,77 +1941,77 @@ class JsonLdProcessor {
// @value must only have @language or @type
if(property_exists($rval, '@value')) {
// @value must only have @language or @type
// @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'});
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));
}
}
// convert @type to an array
else if(property_exists($rval, '@type') && !is_array($rval->{'@type'})) {
$rval->{'@type'} = array($rval->{'@type'});
}
// handle @set and @list
$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'});
}
}
// convert @type to an array
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;
}
// drop certain top-level objects that do not occur in lists
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;
}
// drop certain top-level objects that do not occur in lists
if(is_object($rval) &&
!$options['keepFreeFloatingNodes'] && !$inside_list &&
($active_property === null || $expanded_active_property === '@graph')) {
// drop empty object or top-level @value
!$options['keepFreeFloatingNodes'] && !$inside_list &&
($active_property === null || $expanded_active_property === '@graph')) {
// drop empty object or top-level @value
if($count === 0 || property_exists($rval, '@value')) {
$rval = null;
}
else {
// drop subjects that generate no triples
$rval = null;
}
else {
// drop subjects that generate no triples
$has_triples = false;
$ignore = array('@graph', '@type');
foreach($keys as $key) {
@ -2019,23 +2019,23 @@ class JsonLdProcessor {
$has_triples = true;
break;
}
}
}
if(!$has_triples) {
$rval = null;
}
}
}
$rval = null;
}
}
}
return $rval;
}
// drop top-level scalars that are not in lists
if(!$inside_list &&
($active_property === null ||
// drop top-level scalars that are not in lists
if(!$inside_list &&
($active_property === null ||
$this->_expandIri($active_ctx, $active_property,
array('vocab' => true)) === '@graph')) {
return null;
}
array('vocab' => true)) === '@graph')) {
return null;
}
// expand element according to value expansion rules
return $this->_expandValue($active_ctx, $active_property, $element);
@ -2049,42 +2049,42 @@ class JsonLdProcessor {
* @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
// 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'};
$graph_names = array_keys((array)$graphs);
foreach($graph_names as $graph_name) {
if($graph_name === '@default') {
continue;
foreach($graph_names as $graph_name) {
if($graph_name === '@default') {
continue;
}
$node_map = $graphs->{$graph_name};
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();
}
$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;
$node_map = $graphs->{$graph_name};
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();
}
$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;
}
/**
@ -2146,7 +2146,7 @@ class JsonLdProcessor {
}
}
}
// create canonical namer
$namer = new UniqueNamer('_:c14n');
@ -2431,87 +2431,87 @@ class JsonLdProcessor {
protected function _toRDF(
$element, $namer, $subject, $property, $graph, &$statements) {
// recurse into arrays
if(is_array($element)) {
// recurse into arrays
foreach($element as $e) {
$this->_toRDF($e, $namer, $subject, $property, $graph, $statements);
}
return;
}
// element must be an rdf:type IRI (@values covered above)
if(is_string($element)) {
// emit IRI
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => (object)array(
'nominalValue' => $element,
'interfaceName' => 'IRI'));
if($graph !== null) {
$statement->name = $graph;
}
self::_appendUniqueRdfStatement($statements, $statement);
return;
if(is_array($element)) {
// recurse into arrays
foreach($element as $e) {
$this->_toRDF($e, $namer, $subject, $property, $graph, $statements);
}
return;
}
// convert @list
if(self::_isList($element)) {
$list = $this->_makeLinkedList($element);
$this->_toRDF($list, $namer, $subject, $property, $graph, $statements);
return;
// element must be an rdf:type IRI (@values covered above)
if(is_string($element)) {
// emit IRI
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => (object)array(
'nominalValue' => $element,
'interfaceName' => 'IRI'));
if($graph !== null) {
$statement->name = $graph;
}
self::_appendUniqueRdfStatement($statements, $statement);
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;
// 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
// 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;
@ -2601,10 +2601,10 @@ class JsonLdProcessor {
$rval = self::_cloneActiveContext($active_ctx);
// normalize local context to an array
if(is_object($local_ctx) && property_exists($local_ctx, '@context') &&
is_array($local_ctx->{'@context'})) {
$local_ctx = $local_ctx->{'@context'};
}
if(is_object($local_ctx) && property_exists($local_ctx, '@context') &&
is_array($local_ctx->{'@context'})) {
$local_ctx = $local_ctx->{'@context'};
}
$ctxs = self::arrayify($local_ctx);
// process each context in order
@ -2859,7 +2859,7 @@ class JsonLdProcessor {
$rval->{'@value'} = true;
}
else if($rval->{'@value'} === 'false') {
$rval->{'@value'} = false;
$rval->{'@value'} = false;
}
}
else if(is_numeric($rval->{'@value'})) {
@ -2871,7 +2871,7 @@ class JsonLdProcessor {
}
else if($type === self::XSD_DOUBLE) {
$rval->{'@value'} = doubleval($rval->{'@value'});
}
}
}
// do not add native type
if(!in_array($type, array(
@ -2936,9 +2936,9 @@ class JsonLdProcessor {
}
// add non-object to list
if(!is_object($input)) {
if($list !== null) {
$list[] = $input;
if(!is_object($input)) {
if($list !== null) {
$list[] = $input;
}
return;
}
@ -3482,189 +3482,189 @@ class JsonLdProcessor {
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;
}
/**
* 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.
@ -3713,23 +3713,23 @@ class JsonLdProcessor {
// options for the value of @type or @language,
// determine for @id based on whether or not value compacts to a term
if($type_or_language_value === '@id' && self::_isSubjectReference($value)) {
// try to compact value to a term
$term = $this->_compactIri(
$active_ctx, $value->{'@id'}, null, array('vocab' => true));
if($type_or_language_value === '@id' && self::_isSubjectReference($value)) {
// try to compact value to a term
$term = $this->_compactIri(
$active_ctx, $value->{'@id'}, null, array('vocab' => true));
if(property_exists($active_ctx->mappings, $term) &&
$active_ctx->mappings->{$term} &&
$active_ctx->mappings->{$term}->{'@id'} === $value->{'@id'}) {
$active_ctx->mappings->{$term}->{'@id'} === $value->{'@id'}) {
// prefer @vocab
$options = array('@vocab', '@id', '@none');
}
else {
$options = array('@vocab', '@id', '@none');
}
else {
// prefer @id
$options = array('@id', '@vocab', '@none');
}
}
$options = array('@id', '@vocab', '@none');
}
}
else {
$options = array($type_or_language_value, '@none');
$options = array($type_or_language_value, '@none');
}
$term = null;
@ -3809,10 +3809,10 @@ class JsonLdProcessor {
// use inverse context to pick a term if iri is relative to vocab
if(isset($relative_to['vocab']) && $relative_to['vocab'] &&
property_exists($this->_getInverseContext($active_ctx), $iri)) {
$default_language = '@none';
if(property_exists($active_ctx, '@language')) {
$default_language = $active_ctx->{'@language'};
}
$default_language = '@none';
if(property_exists($active_ctx, '@language')) {
$default_language = $active_ctx->{'@language'};
}
// prefer @index if available in value
$containers = array();
@ -4492,167 +4492,167 @@ class JsonLdProcessor {
return $rval;
}
/**
* Finds all @context URLs in the given JSON-LD input.
*
* @param mixed $input the JSON-LD input.
* @param stdClass $urls a map of URLs (url => false/@contexts).
* @param bool $replace true to replace the URLs in the given input with
/**
* Finds all @context URLs in the given JSON-LD input.
*
* @param mixed $input the JSON-LD input.
* @param stdClass $urls a map of URLs (url => false/@contexts).
* @param bool $replace true to replace the URLs in the given input with
* the @contexts from the urls map, false not to.
* @param string $base the base URL to resolve relative URLs with.
*/
* @param string $base the base URL to resolve relative URLs with.
*/
protected function _findContextUrls($input, $urls, $replace, $base) {
if(is_array($input)) {
foreach($input as $e) {
$this->_findContextUrls($e, $urls, $replace, $base);
}
}
else if(is_object($input)) {
foreach($input as $k => &$v) {
if($k !== '@context') {
$this->_findContextUrls($v, $urls, $replace, $base);
continue;
}
// array @context
if(is_array($v)) {
$length = count($v);
for($i = 0; $i < $length; ++$i) {
if(is_string($v[$i])) {
if(is_array($input)) {
foreach($input as $e) {
$this->_findContextUrls($e, $urls, $replace, $base);
}
}
else if(is_object($input)) {
foreach($input as $k => &$v) {
if($k !== '@context') {
$this->_findContextUrls($v, $urls, $replace, $base);
continue;
}
// array @context
if(is_array($v)) {
$length = count($v);
for($i = 0; $i < $length; ++$i) {
if(is_string($v[$i])) {
$url = jsonld_prepend_base($base, $v[$i]);
// replace w/@context if requested
if($replace) {
$ctx = $urls->{$url};
if(is_array($ctx)) {
// replace w/@context if requested
if($replace) {
$ctx = $urls->{$url};
if(is_array($ctx)) {
// add flattened context
array_splice($v, $i, 1, $ctx);
$i += count($ctx);
$length += count($ctx);
}
else {
$v[$i] = $ctx;
}
}
// @context URL found
else if(!property_exists($urls, $url)) {
$urls->{$url} = false;
}
}
}
}
// string @context
array_splice($v, $i, 1, $ctx);
$i += count($ctx);
$length += count($ctx);
}
else {
$v[$i] = $ctx;
}
}
// @context URL found
else if(!property_exists($urls, $url)) {
$urls->{$url} = false;
}
}
}
}
// string @context
else if(is_string($v)) {
$v = jsonld_prepend_base($base, $v);
// replace w/@context if requested
if($replace) {
$input->{$k} = $urls->{$v};
}
// @context URL found
else if(!property_exists($urls, $v)) {
$urls->{$v} = false;
}
}
}
}
}
/**
* Retrieves external @context URLs using the given URL client. Each
* instance of @context in the input that refers to a URL will be replaced
* with the JSON @context found at that URL.
*
$v = jsonld_prepend_base($base, $v);
// replace w/@context if requested
if($replace) {
$input->{$k} = $urls->{$v};
}
// @context URL found
else if(!property_exists($urls, $v)) {
$urls->{$v} = false;
}
}
}
}
}
/**
* Retrieves external @context URLs using the given URL client. Each
* instance of @context in the input that refers to a URL will be replaced
* with the JSON @context found at that URL.
*
* @param mixed $input the JSON-LD input with possible contexts.
* @param stdClass $cycles an object for tracking context cycles.
* @param callable $url_client(url) the URL client.
* @param base $base the base URL to resolve relative URLs against.
*
* @return mixed the result.
*/
*/
protected function _retrieveContextUrls(
&$input, $cycles, $url_client, $base='') {
if(count(get_object_vars($cycles)) > self::MAX_CONTEXT_URLS) {
throw new JsonLdException(
'Maximum number of @context URLs exceeded.',
'jsonld.ContextUrlError', array('max' => self::MAX_CONTEXT_URLS));
}
// for tracking the URLs to retrieve
&$input, $cycles, $url_client, $base='') {
if(count(get_object_vars($cycles)) > self::MAX_CONTEXT_URLS) {
throw new JsonLdException(
'Maximum number of @context URLs exceeded.',
'jsonld.ContextUrlError', array('max' => self::MAX_CONTEXT_URLS));
}
// for tracking the URLs to retrieve
$urls = new stdClass();
// find all URLs in the given input
$this->_findContextUrls($input, $urls, false, $base);
// queue all unretrieved URLs
$queue = array();
foreach($urls as $url => $ctx) {
// queue all unretrieved URLs
$queue = array();
foreach($urls as $url => $ctx) {
if($ctx === false) {
$queue[] = $url;
}
}
// retrieve URLs in queue
// retrieve URLs in queue
foreach($queue as $url) {
// check for context URL cycle
if(property_exists($cycles, $url)) {
throw new JsonLdException(
'Cyclical @context URLs detected.',
'jsonld.ContextUrlError', array('url' => $url));
}
$_cycles = self::copy($cycles);
// check for context URL cycle
if(property_exists($cycles, $url)) {
throw new JsonLdException(
'Cyclical @context URLs detected.',
'jsonld.ContextUrlError', array('url' => $url));
}
$_cycles = self::copy($cycles);
$_cycles->{$url} = true;
// retrieve URL
// retrieve URL
$ctx = $url_client($url);
// parse string context as JSON
if(is_string($ctx)) {
$ctx = json_decode($ctx);
switch(json_last_error()) {
case JSON_ERROR_NONE:
break;
case JSON_ERROR_DEPTH:
throw new JsonLdException(
'Could not parse JSON from URL; the maximum stack depth has ' .
'been exceeded.', 'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_STATE_MISMATCH:
throw new JsonLdException(
'Could not parse JSON from URL; invalid or malformed JSON.',
'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_CTRL_CHAR:
case JSON_ERROR_SYNTAX:
throw new JsonLdException(
'Could not parse JSON from URL; syntax error, malformed JSON.',
'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_UTF8:
throw new JsonLdException(
'Could not parse JSON from URL; malformed UTF-8 characters.',
'jsonld.ParseError', array('url' => $url));
default:
throw new JsonLdException(
'Could not parse JSON from URL; unknown error.',
'jsonld.ParseError', array('url' => $url));
}
}
// ensure ctx is an object
if(!is_object($ctx)) {
throw new JsonLdException(
'Derefencing a URL did not result in a valid JSON-LD object.',
'jsonld.InvalidUrl', array('url' => $url));
// parse string context as JSON
if(is_string($ctx)) {
$ctx = json_decode($ctx);
switch(json_last_error()) {
case JSON_ERROR_NONE:
break;
case JSON_ERROR_DEPTH:
throw new JsonLdException(
'Could not parse JSON from URL; the maximum stack depth has ' .
'been exceeded.', 'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_STATE_MISMATCH:
throw new JsonLdException(
'Could not parse JSON from URL; invalid or malformed JSON.',
'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_CTRL_CHAR:
case JSON_ERROR_SYNTAX:
throw new JsonLdException(
'Could not parse JSON from URL; syntax error, malformed JSON.',
'jsonld.ParseError', array('url' => $url));
case JSON_ERROR_UTF8:
throw new JsonLdException(
'Could not parse JSON from URL; malformed UTF-8 characters.',
'jsonld.ParseError', array('url' => $url));
default:
throw new JsonLdException(
'Could not parse JSON from URL; unknown error.',
'jsonld.ParseError', array('url' => $url));
}
}
// use empty context if no @context key is present
if(!property_exists($ctx, '@context')) {
$ctx = (object)array('@context' => new stdClass());
// ensure ctx is an object
if(!is_object($ctx)) {
throw new JsonLdException(
'Derefencing a URL did not result in a valid JSON-LD object.',
'jsonld.InvalidUrl', array('url' => $url));
}
// recurse
// use empty context if no @context key is present
if(!property_exists($ctx, '@context')) {
$ctx = (object)array('@context' => new stdClass());
}
// recurse
$this->_retrieveContextUrls($ctx, $_cycles, $url_client, $url);
$urls->{$url} = $ctx->{'@context'};
$urls->{$url} = $ctx->{'@context'};
}
// replace all URLS in the input
$this->_findContextUrls($input, $urls, true, $base);
// replace all URLS in the input
$this->_findContextUrls($input, $urls, true, $base);
}
/**
@ -4706,7 +4706,7 @@ class JsonLdProcessor {
// handle default language
$default_language = '@none';
if(property_exists($active_ctx, '@language')) {
$default_language = $active_ctx->{'@language'};
$default_language = $active_ctx->{'@language'};
}
// create term selections for each mapping in the context, ordered by
@ -4720,13 +4720,13 @@ class JsonLdProcessor {
continue;
}
// add term selection where it applies
if(property_exists($mapping, '@container')) {
$container = $mapping->{'@container'};
}
else {
$container = '@none';
}
// add term selection where it applies
if(property_exists($mapping, '@container')) {
$container = $mapping->{'@container'};
}
else {
$container = '@none';
}
// iterate over every IRI in the mapping
$iris = $mapping->{'@id'};
@ -4844,73 +4844,73 @@ class JsonLdProcessor {
return $rval;
}
/**
* Compares two RDF statements for equality.
*
* @param stdClass $s1 the first statement.
* @param stdClass $s2 the second statement.
*
* @return true if the statements are the same, false if not.
*/
protected static function _compareRdfStatements($s1, $s2) {
if(is_string($s1) || is_string($s2)) {
return $s1 === $s2;
}
$attrs = array('subject', 'property', 'object');
foreach($attrs as $attr) {
if($s1->{$attr}->interfaceName !== $s2->{$attr}->interfaceName ||
$s1->{$attr}->nominalValue !== $s2->{$attr}->nominalValue) {
return false;
}
}
if(property_exists($s1->object, 'language') !==
property_exists($s1->object, 'language')) {
return false;
}
if(property_exists($s1->object, 'language')) {
if($s1->object->language !== $s2->object->language) {
return false;
}
}
if(property_exists($s1->object, 'datatype') !==
property_exists($s1->object, 'datatype')) {
return false;
}
if(property_exists($s1->object, 'datatype')) {
if($s1->object->datatype->interfaceName !==
$s2->object->datatype->interfaceName ||
$s1->object->datatype->nominalValue !==
$s2->object->datatype->nominalValue) {
return false;
}
}
if(property_exists($s1, 'name') !== property_exists($s1, 'name')) {
return false;
}
if(property_exists($s1, 'name')) {
if($s1->name !== $s2->name) {
return false;
}
}
return true;
}
/**
* Appends an RDF statement to the given array of statements if it is unique.
*
* @param array $statements the array to add to.
* @param stdClass $statement the statement to add.
*/
/**
* Compares two RDF statements for equality.
*
* @param stdClass $s1 the first statement.
* @param stdClass $s2 the second statement.
*
* @return true if the statements are the same, false if not.
*/
protected static function _compareRdfStatements($s1, $s2) {
if(is_string($s1) || is_string($s2)) {
return $s1 === $s2;
}
$attrs = array('subject', 'property', 'object');
foreach($attrs as $attr) {
if($s1->{$attr}->interfaceName !== $s2->{$attr}->interfaceName ||
$s1->{$attr}->nominalValue !== $s2->{$attr}->nominalValue) {
return false;
}
}
if(property_exists($s1->object, 'language') !==
property_exists($s1->object, 'language')) {
return false;
}
if(property_exists($s1->object, 'language')) {
if($s1->object->language !== $s2->object->language) {
return false;
}
}
if(property_exists($s1->object, 'datatype') !==
property_exists($s1->object, 'datatype')) {
return false;
}
if(property_exists($s1->object, 'datatype')) {
if($s1->object->datatype->interfaceName !==
$s2->object->datatype->interfaceName ||
$s1->object->datatype->nominalValue !==
$s2->object->datatype->nominalValue) {
return false;
}
}
if(property_exists($s1, 'name') !== property_exists($s1, 'name')) {
return false;
}
if(property_exists($s1, 'name')) {
if($s1->name !== $s2->name) {
return false;
}
}
return true;
}
/**
* Appends an RDF statement to the given array of statements if it is unique.
*
* @param array $statements the array to add to.
* @param stdClass $statement the statement to add.
*/
protected static function _appendUniqueRdfStatement(
&$statements, $statement) {
foreach($statements as $s) {
if(self::_compareRdfStatements($s, $statement)) {
return;
}
}
$statements[] = $statement;
}
&$statements, $statement) {
foreach($statements as $s) {
if(self::_compareRdfStatements($s, $statement)) {
return;
}
}
$statements[] = $statement;
}
/**
* Returns whether or not the given value is a keyword.
@ -5125,9 +5125,9 @@ class JsonLdProcessor {
}
}
// register the N-Quads RDF parser
// register the N-Quads RDF parser
jsonld_register_rdf_parser(
'application/nquads', 'JsonLdProcessor::parseNQuads');
'application/nquads', 'JsonLdProcessor::parseNQuads');
/**
* A JSON-LD Exception.
@ -5301,66 +5301,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;
* 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;
}
/**
* 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.
/**
* 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.
*/
* @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);
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, $key1)) {
$this->cache->{$key1} = new stdClass();
}
$this->cache->{$key1}->{$key2} = $result;
}
}
}
}
/* end of file, omit ?> */