From d80c0acb4f31c490ad48ed64abd27c6b30037a16 Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Fri, 15 Feb 2013 16:25:30 -0500 Subject: [PATCH] Bump version. --- jsonld.php | 2064 ++++++++++++++++++++++++++-------------------------- 1 file changed, 1032 insertions(+), 1032 deletions(-) diff --git a/jsonld.php b/jsonld.php index ac891d6..ec59553 100644 --- a/jsonld.php +++ b/jsonld.php @@ -1,7 +1,7 @@ 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 ?> */