Use RDF datasets for RDF conversion instead of RDF statements.

This commit is contained in:
Dave Longley 2013-02-20 16:55:36 -05:00
commit 21c0934d8f
2 changed files with 526 additions and 511 deletions

View file

@ -8,6 +8,9 @@
*/ */
require_once('jsonld.php'); require_once('jsonld.php');
// send errors to stdout
ini_set('display_errors', 'stdout');
// determine EOL for output based on command line php or webpage php // determine EOL for output based on command line php or webpage php
$isCli = defined('STDIN'); $isCli = defined('STDIN');
$eol = $isCli ? "\n" : '<br/>'; $eol = $isCli ? "\n" : '<br/>';

View file

@ -108,7 +108,8 @@ function jsonld_frame($input, $frame, $options=array()) {
} }
/** /**
* Performs RDF normalization on the given JSON-LD input. * Performs RDF dataset normalization on the given JSON-LD input. The output
* is an RDF dataset unless the 'format' option is used.
* *
* @param mixed $input the JSON-LD object to normalize. * @param mixed $input the JSON-LD object to normalize.
* @param assoc [$options] the options to use: * @param assoc [$options] the options to use:
@ -117,7 +118,7 @@ function jsonld_frame($input, $frame, $options=array()) {
* 'application/nquads' for N-Quads (default). * 'application/nquads' for N-Quads (default).
* [loadContext(url)] the context loader. * [loadContext(url)] the context loader.
* *
* @return array the normalized output. * @return mixed the normalized output.
*/ */
function jsonld_normalize($input, $options=array()) { function jsonld_normalize($input, $options=array()) {
$p = new JsonLdProcessor(); $p = new JsonLdProcessor();
@ -125,11 +126,10 @@ function jsonld_normalize($input, $options=array()) {
} }
/** /**
* Converts RDF statements into JSON-LD. * Converts an RDF dataset to JSON-LD.
* *
* @param mixed $statements a serialized string of RDF statements in a format * @param mixed $input a serialized string of RDF in a format specified
* specified by the format option or an array of the RDF statements * by the format option or an RDF dataset to convert.
* to convert.
* @param assoc [$options] the options to use: * @param assoc [$options] the options to use:
* [format] the format if input not an array: * [format] the format if input not an array:
* 'application/nquads' for N-Quads (default). * 'application/nquads' for N-Quads (default).
@ -146,7 +146,7 @@ function jsonld_from_rdf($input, $options=array()) {
} }
/** /**
* Outputs the RDF statements found in the given JSON-LD object. * Outputs the RDF dataset found in the given JSON-LD object.
* *
* @param mixed $input the JSON-LD object. * @param mixed $input the JSON-LD object.
* @param assoc [$options] the options to use: * @param assoc [$options] the options to use:
@ -155,7 +155,7 @@ function jsonld_from_rdf($input, $options=array()) {
* 'application/nquads' for N-Quads (default). * 'application/nquads' for N-Quads (default).
* [loadContext(url)] the context loader. * [loadContext(url)] the context loader.
* *
* @return array all RDF statements in the JSON-LD object. * @return mixed the resulting RDF dataset (or a serialization of it).
*/ */
function jsonld_to_rdf($input, $options=array()) { function jsonld_to_rdf($input, $options=array()) {
$p = new JsonLdProcessor(); $p = new JsonLdProcessor();
@ -283,18 +283,18 @@ function jsonld_default_get_secure_url($url) {
return $result; return $result;
} }
/** Registered global RDF Statement parsers hashed by content-type. */ /** Registered global RDF dataset parsers hashed by content-type. */
global $jsonld_rdf_parsers; global $jsonld_rdf_parsers;
$jsonld_rdf_parsers = new stdClass(); $jsonld_rdf_parsers = new stdClass();
/** /**
* Registers a global RDF Statement parser by content-type, for use with * Registers a global RDF dataset parser by content-type, for use with
* jsonld_from_rdf. Global parsers will be used by JsonLdProcessors that do * jsonld_from_rdf. Global parsers will be used by JsonLdProcessors that do
* not register their own parsers. * not register their own parsers.
* *
* @param string $content_type the content-type for the parser. * @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as * @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements). * a parameter and returns an RDF dataset).
*/ */
function jsonld_register_rdf_parser($content_type, $parser) { function jsonld_register_rdf_parser($content_type, $parser) {
global $jsonld_rdf_parsers; global $jsonld_rdf_parsers;
@ -302,7 +302,7 @@ function jsonld_register_rdf_parser($content_type, $parser) {
} }
/** /**
* Unregisters a global RDF Statement parser by content-type. * Unregisters a global RDF dataset parser by content-type.
* *
* @param string $content_type the content-type for the parser. * @param string $content_type the content-type for the parser.
*/ */
@ -525,11 +525,13 @@ class JsonLdProcessor {
const RDF_REST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'; const RDF_REST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest';
const RDF_NIL = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'; const RDF_NIL = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil';
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
const RDF_LANGSTRING =
'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString';
/** Restraints */ /** Restraints */
const MAX_CONTEXT_URLS = 10; const MAX_CONTEXT_URLS = 10;
/** Processor-specific RDF Statement parsers. */ /** Processor-specific RDF dataset parsers. */
protected $rdfParsers = null; protected $rdfParsers = null;
/** /**
@ -853,9 +855,11 @@ class JsonLdProcessor {
* @param mixed $input the JSON-LD object to normalize. * @param mixed $input the JSON-LD object to normalize.
* @param assoc $options the options to use: * @param assoc $options the options to use:
* [base] the base IRI to use. * [base] the base IRI to use.
* [format] the format if output is a string:
* 'application/nquads' for N-Quads.
* [loadContext(url)] the context loader. * [loadContext(url)] the context loader.
* *
* @return array the JSON-LD normalized output. * @return mixed the normalized output.
*/ */
public function normalize($input, $options) { public function normalize($input, $options) {
// set default options // set default options
@ -864,25 +868,28 @@ class JsonLdProcessor {
'jsonld_get_url'; 'jsonld_get_url';
try { try {
// expand input then do normalization // convert to RDF dataset then do normalization
$expanded = $this->expand($input, $options); $opts = self::copy($options);
if(isset($opts['format'])) {
unset($opts['format']);
}
$dataset = $this->toRDF($input, $opts);
} }
catch(Exception $e) { catch(Exception $e) {
throw new JsonLdException( throw new JsonLdException(
'Could not expand input before normalization.', 'Could not convert input to RDF dataset before normalization.',
'jsonld.NormalizeError', null, $e); 'jsonld.NormalizeError', null, $e);
} }
// do normalization // do normalization
return $this->_normalize($expanded, $options); return $this->_normalize($dataset, $options);
} }
/** /**
* Converts RDF statements into JSON-LD. * Converts an RDF dataset to JSON-LD.
* *
* @param mixed $statements a serialized string of RDF statements in a format * @param mixed $dataset a serialized string of RDF in a format specified
* specified by the format option or an array of the RDF statements * by the format option or an RDF dataset to convert.
* to convert.
* @param assoc $options the options to use: * @param assoc $options the options to use:
* [format] the format if input is a string: * [format] the format if input is a string:
* 'application/nquads' for N-Quads (default). * 'application/nquads' for N-Quads (default).
@ -893,15 +900,20 @@ class JsonLdProcessor {
* *
* @return array the JSON-LD output. * @return array the JSON-LD output.
*/ */
public function fromRDF($statements, $options) { public function fromRDF($dataset, $options) {
global $jsonld_rdf_parsers; global $jsonld_rdf_parsers;
// set default options // set default options
isset($options['format']) or $options['format'] = 'application/nquads';
isset($options['useRdfType']) or $options['useRdfType'] = false; isset($options['useRdfType']) or $options['useRdfType'] = false;
isset($options['useNativeTypes']) or $options['useNativeTypes'] = true; isset($options['useNativeTypes']) or $options['useNativeTypes'] = true;
if(!is_array($statements)) { if(!isset($options['format']) && is_string($dataset)) {
// set default format to nquads
$options['format'] = 'application/nquads';
}
// handle special format
if(isset($options['format']) && $options['format']) {
// supported formats (processor-specific and global) // supported formats (processor-specific and global)
if(($this->rdfParsers !== null && if(($this->rdfParsers !== null &&
!property_exists($this->rdfParsers, $options['format'])) || !property_exists($this->rdfParsers, $options['format'])) ||
@ -917,15 +929,15 @@ class JsonLdProcessor {
else { else {
$callable = $jsonld_rdf_parsers->{$options['format']}; $callable = $jsonld_rdf_parsers->{$options['format']};
} }
$statements = call_user_func($callable, $statements); $dataset = call_user_func($callable, $dataset);
} }
// convert from RDF // convert from RDF
return $this->_fromRDF($statements, $options); return $this->_fromRDF($dataset, $options);
} }
/** /**
* Outputs the RDF statements found in the given JSON-LD object. * Outputs the RDF dataset found in the given JSON-LD object.
* *
* @param mixed $input the JSON-LD object. * @param mixed $input the JSON-LD object.
* @param assoc $options the options to use: * @param assoc $options the options to use:
@ -934,7 +946,7 @@ class JsonLdProcessor {
* 'application/nquads' for N-Quads (default). * 'application/nquads' for N-Quads (default).
* [loadContext(url)] the context loader. * [loadContext(url)] the context loader.
* *
* @return array all RDF statements in the JSON-LD object. * @return mixed the resulting RDF dataset (or a serialization of it).
*/ */
public function toRDF($input, $options) { public function toRDF($input, $options) {
// set default options // set default options
@ -952,21 +964,28 @@ class JsonLdProcessor {
'jsonld.RdfError', $e); 'jsonld.RdfError', $e);
} }
// get RDF statements // create node map for default graph (and any named graphs)
$namer = new UniqueNamer('_:b'); $namer = new UniqueNamer('_:b');
$statements = array(); $node_map = (object)array('@default' => new stdClass());
$this->_toRDF($expanded, $namer, null, null, null, $statements); $this->_createNodeMap($expanded, $node_map, '@default', $namer);
// output RDF dataset
$namer = new UniqueNamer('_:b');
$dataset = new stdClass();
foreach($node_map as $graph_name => $graph) {
if(strpos($graph_name, '_:') === 0) {
$graph_name = $namer->getName($graph_name);
}
$dataset->{$graph_name} = $this->_graphToRDF($graph, $namer);
}
$rval = $dataset;
// convert to output format // convert to output format
if(isset($options['format'])) { if(isset($options['format']) && $options['format']) {
// supported formats // supported formats
if($options['format'] === 'application/nquads') { if($options['format'] === 'application/nquads') {
$nquads = array(); $rval = self::toNQuads($dataset);
foreach($statements as $statement) {
$nquads[] = $this->toNQuad($statement);
}
sort($nquads);
$statements = implode($nquads);
} }
else { else {
throw new JsonLdException( throw new JsonLdException(
@ -975,8 +994,7 @@ class JsonLdProcessor {
} }
} }
// output RDF statements return $rval;
return $statements;
} }
/** /**
@ -1289,11 +1307,11 @@ class JsonLdProcessor {
} }
/** /**
* Parses statements in the form of N-Quads. * Parses RDF in the form of N-Quads.
* *
* @param string $input the N-Quads input to parse. * @param string $input the N-Quads input to parse.
* *
* @return array the resulting RDF statements. * @return stdClass an RDF dataset.
*/ */
public static function parseNQuads($input) { public static function parseNQuads($input) {
// define partial regexes // define partial regexes
@ -1311,13 +1329,13 @@ class JsonLdProcessor {
$subject = "(?:$iri|$bnode)$ws+"; $subject = "(?:$iri|$bnode)$ws+";
$property = "$iri$ws+"; $property = "$iri$ws+";
$object = "(?:$iri|$bnode|$literal)$ws*"; $object = "(?:$iri|$bnode|$literal)$ws*";
$graph = "(?:\\.|(?:(?:$iri|$bnode)$ws*\\.))"; $graph_name = "(?:\\.|(?:(?:$iri|$bnode)$ws*\\.))";
// full quad regex // full quad regex
$quad = "/^$ws*$subject$property$object$graph$ws*$/"; $quad = "/^$ws*$subject$property$object$graph_name$ws*$/";
// build RDF statements // build RDF dataset
$statements = array(); $dataset = new stdClass();
// split N-Quad input into lines // split N-Quad input into lines
$lines = preg_split($eoln, $input); $lines = preg_split($eoln, $input);
@ -1337,140 +1355,180 @@ class JsonLdProcessor {
'jsonld.ParseError', array('line' => $line_number)); 'jsonld.ParseError', array('line' => $line_number));
} }
// create RDF statement // create RDF triple
$s = (object)array( $triple = (object)array(
'subject' => new stdClass(), 'subject' => new stdClass(),
'property' => new stdClass(), 'predicate' => new stdClass(),
'object' => new stdClass()); 'object' => new stdClass());
// get subject // get subject
if($match[1] !== '') { if($match[1] !== '') {
$s->subject->nominalValue = $match[1]; $triple->subject->type = 'IRI';
$s->subject->interfaceName = 'IRI'; $triple->subject->value = $match[1];
} }
else { else {
$s->subject->nominalValue = $match[2]; $triple->subject->type = 'blank node';
$s->subject->interfaceName = 'BlankNode'; $triple->subject->value = $match[2];
} }
// get property // get predicate
$s->property->nominalValue = $match[3]; $triple->predicate->type = 'IRI';
$s->property->interfaceName = 'IRI'; $triple->predicate->value = $match[3];
// get object // get object
if($match[4] !== '') { if($match[4] !== '') {
$s->object->nominalValue = $match[4]; $triple->object->type = 'IRI';
$s->object->interfaceName = 'IRI'; $triple->object->value = $match[4];
} }
else if($match[5] !== '') { else if($match[5] !== '') {
$s->object->nominalValue = $match[5]; $triple->object->type = 'blank node';
$s->object->interfaceName = 'BlankNode'; $triple->object->value = $match[5];
} }
else { else {
$triple->object->type = 'literal';
$unescaped = str_replace( $unescaped = str_replace(
array('\"', '\t', '\n', '\r', '\\\\'), array('\"', '\t', '\n', '\r', '\\\\'),
array('"', "\t", "\n", "\r", '\\'), array('"', "\t", "\n", "\r", '\\'),
$match[6]); $match[6]);
$s->object->nominalValue = $unescaped;
$s->object->interfaceName = 'LiteralNode';
if(isset($match[7]) && $match[7] !== '') { if(isset($match[7]) && $match[7] !== '') {
$s->object->datatype = (object)array( $triple->object->datatype = $match[7];
'nominalValue' => $match[7], 'interfaceName' => 'IRI');
} }
else if(isset($match[8]) && $match[8] !== '') { else if(isset($match[8]) && $match[8] !== '') {
$s->object->language = $match[8]; $triple->object->datatype = self::RDF_LANGSTRING;
$triple->object->language = $match[8];
} }
else {
$triple->object->datatype = self::XSD_STRING;
}
$triple->object->value = $unescaped;
} }
// get graph // get graph name ('@default' is used for the default graph)
$name = '@default';
if(isset($match[9]) && $match[9] !== '') { if(isset($match[9]) && $match[9] !== '') {
$s->name = (object)array( $name = $match[9];
'nominalValue' => $match[9], 'interfaceName' => 'IRI');
} }
else if(isset($match[10]) && $match[10] !== '') { else if(isset($match[10]) && $match[10] !== '') {
$s->name = (object)array( $name = $match[10];
'nominalValue' => $match[10], 'interfaceName' => 'BlankNode');
} }
// add statement // initialize graph in dataset
self::_appendUniqueRdfStatement($statements, $s); if(!property_exists($dataset, $name)) {
$dataset->{$name} = array($triple);
}
// add triple if unique to its graph
else {
$unique = true;
$triples = &$dataset->{$name};
foreach($triples as $t) {
if(self::_compareRDFTriples($t, $triple)) {
$unique = false;
break;
}
}
if($unique) {
$triples[] = $triple;
}
}
} }
return $statements; return $dataset;
} }
/** /**
* Converts an RDF statement to an N-Quad string (a single quad). * Converts an RDF dataset to N-Quads.
* *
* @param stdClass $statement the RDF statement to convert. * @param stdClass $dataset the RDF dataset to convert.
* @param string $bnode the bnode the statement is mapped to (optional, for *
* @return string the N-Quads string.
*/
public static function toNQuads($dataset) {
$quads = array();
foreach($dataset as $graph_name => $triples) {
foreach($triples as $triple) {
if($graph_name === '@default') {
$graph_name = null;
}
$quads[] = self::toNQuad($triple, $graph_name);
}
}
sort($quads);
return implode($quads);
}
/**
* Converts an RDF triple and graph name to an N-Quad string (a single quad).
*
* @param stdClass $triple the RDF triple to convert.
* @param mixed $graph_name the name of the graph containing the triple, null
* for the default graph.
* @param string $bnode the bnode the quad is mapped to (optional, for
* use during normalization only). * use during normalization only).
* *
* @return the N-Quad string. * @return string the N-Quad string.
*/ */
public static function toNQuad($statement, $bnode=null) { public static function toNQuad($triple, $graph_name, $bnode=null) {
$s = $statement->subject; $s = $triple->subject;
$p = $statement->property; $p = $triple->predicate;
$o = $statement->object; $o = $triple->object;
$g = property_exists($statement, 'name') ? $statement->name : null; $g = $graph_name;
$quad = ''; $quad = '';
// subject is an IRI or bnode // subject is an IRI or bnode
if($s->interfaceName === 'IRI') { if($s->type === 'IRI') {
$quad .= "<{$s->nominalValue}>"; $quad .= "<{$s->value}>";
} }
// normalization mode // normalization mode
else if($bnode !== null) { else if($bnode !== null) {
$quad .= ($s->nominalValue === $bnode) ? '_:a' : '_:z'; $quad .= ($s->value === $bnode) ? '_:a' : '_:z';
} }
// normal mode // normal mode
else { else {
$quad .= $s->nominalValue; $quad .= $s->value;
} }
// property is always an IRI // predicate is always an IRI
$quad .= " <{$p->nominalValue}> "; $quad .= " <{$p->value}> ";
// object is IRI, bnode, or literal // object is IRI, bnode, or literal
if($o->interfaceName === 'IRI') { if($o->type === 'IRI') {
$quad .= "<{$o->nominalValue}>"; $quad .= "<{$o->value}>";
} }
else if($o->interfaceName === 'BlankNode') { else if($o->type === 'blank node') {
// normalization mode // normalization mode
if($bnode !== null) { if($bnode !== null) {
$quad .= ($o->nominalValue === $bnode) ? '_:a' : '_:z'; $quad .= ($o->value === $bnode) ? '_:a' : '_:z';
} }
// normal mode // normal mode
else { else {
$quad .= $o->nominalValue; $quad .= $o->value;
} }
} }
else { else {
$escaped = str_replace( $escaped = str_replace(
array('\\', "\t", "\n", "\r", '"'), array('\\', "\t", "\n", "\r", '"'),
array('\\\\', '\t', '\n', '\r', '\"'), array('\\\\', '\t', '\n', '\r', '\"'),
$o->nominalValue); $o->value);
$quad .= '"' . $escaped . '"'; $quad .= '"' . $escaped . '"';
if(property_exists($o, 'datatype') && if($o->datatype === self::RDF_LANGSTRING) {
$o->datatype->nominalValue !== self::XSD_STRING) { $quad .= "@{$o->language}";
$quad .= "^^<{$o->datatype->nominalValue}>";
} }
else if(property_exists($o, 'language')) { else if($o->datatype !== self::XSD_STRING) {
$quad .= '@' . $o->language; $quad .= "^^<{$o->datatype}>";
} }
} }
// graph // graph
if($g !== null) { if($g !== null) {
if($g->interfaceName === 'IRI') { if(strpos($g, '_:') !== 0) {
$quad .= " <{$g->nominalValue}>"; $quad .= " <$g>";
} }
else if($bnode) { else if($bnode) {
$quad .= ' _:g'; $quad .= ' _:g';
} }
else { else {
$quad .= " {$g->nominalValue}"; $quad .= " $g";
} }
} }
@ -1479,12 +1537,12 @@ class JsonLdProcessor {
} }
/** /**
* Registers a processor-specific RDF Statement parser by content-type. * Registers a processor-specific RDF dataset parser by content-type.
* Global parsers will no longer be used by this processor. * Global parsers will no longer be used by this processor.
* *
* @param string $content_type the content-type for the parser. * @param string $content_type the content-type for the parser.
* @param callable $parser(input) the parser function (takes a string as * @param callable $parser(input) the parser function (takes a string as
* a parameter and returns an array of RDF statements). * a parameter and returns an RDF dataset).
*/ */
public function registerRDFParser($content_type, $parser) { public function registerRDFParser($content_type, $parser) {
if($this->rdfParsers === null) { if($this->rdfParsers === null) {
@ -1494,7 +1552,7 @@ class JsonLdProcessor {
} }
/** /**
* Unregisters a process-specific RDF Statement parser by content-type. If * Unregisters a process-specific RDF dataset parser by content-type. If
* there are no remaining processor-specific parsers, then the global * there are no remaining processor-specific parsers, then the global
* parsers will be re-enabled. * parsers will be re-enabled.
* *
@ -2176,38 +2234,54 @@ class JsonLdProcessor {
} }
/** /**
* Performs JSON-LD normalization. * Performs normalization on the given RDF dataset.
* *
* @param array $input the expanded JSON-LD object to normalize. * @param stdClass $dataset the RDF dataset to normalize.
* @param assoc $options the normalization options. * @param assoc $options the normalization options.
* *
* @return mixed the normalized output. * @return mixed the normalized output.
*/ */
protected function _normalize($input, $options) { protected function _normalize($dataset, $options) {
// map bnodes to RDF statements // create quads and map bnodes to their associated quads
$statements = array(); $quads = array();
$bnodes = new stdClass(); $bnodes = new stdClass();
$namer = new UniqueNamer('_:b'); foreach($dataset as $graph_name => $triples) {
$this->_toRDF($input, $namer, null, null, null, $statements); if($graph_name === '@default') {
foreach($statements as $statement) { $graph_name = null;
foreach(array('subject', 'object', 'name') as $node) { }
if(property_exists($statement, $node) && foreach($triples as $triple) {
$statement->{$node}->interfaceName === 'BlankNode') { $quad = $triple;
$id = $statement->{$node}->nominalValue; if($graph_name !== null) {
if(property_exists($bnodes, $id)) { if(strpos($graph_name, '_:') === 0) {
$bnodes->{$id}->statements[] = $statement; $quad->name = (object)array(
'type' => 'blank node', 'value' => $graph_name);
} }
else { else {
$bnodes->{$id} = (object)array('statements' => array($statement)); $quad->name = (object)array(
'type' => 'IRI', 'value' => $graph_name);
}
}
$quads[] = $quad;
foreach(array('subject', 'object', 'name') as $attr) {
if(property_exists($quad, $attr) &&
$quad->{$attr}->type === 'blank node') {
$id = $quad->{$attr}->value;
if(property_exists($bnodes, $id)) {
$bnodes->{$id}->quads[] = $quad;
}
else {
$bnodes->{$id} = (object)array('quads' => array($quad));
}
} }
} }
} }
} }
// create canonical namer // mapping complete, start canonical naming
$namer = new UniqueNamer('_:c14n'); $namer = new UniqueNamer('_:c14n');
// continue to hash bnode statements while bnodes are assigned names // continue to hash bnode quads while bnodes are assigned names
$unnamed = null; $unnamed = null;
$nextUnnamed = array_keys((array)$bnodes); $nextUnnamed = array_keys((array)$bnodes);
$duplicates = null; $duplicates = null;
@ -2217,8 +2291,8 @@ class JsonLdProcessor {
$duplicates = new stdClass(); $duplicates = new stdClass();
$unique = new stdClass(); $unique = new stdClass();
foreach($unnamed as $bnode) { foreach($unnamed as $bnode) {
// hash statements for each unnamed bnode // hash quads for each unnamed bnode
$hash = $this->_hashStatements($bnode, $bnodes, $namer); $hash = $this->_hashQuads($bnode, $bnodes, $namer);
// store hash as unique or a duplicate // store hash as unique or a duplicate
if(property_exists($duplicates, $hash)) { if(property_exists($duplicates, $hash)) {
@ -2281,71 +2355,69 @@ class JsonLdProcessor {
// create normalized array // create normalized array
$normalized = array(); $normalized = array();
// update bnode names in each statement and serialize /* Note: At this point all bnodes in the set of RDF quads have been
foreach($statements as $statement) { assigned canonical names, which have been stored in the 'namer' object.
foreach(array('subject', 'object', 'name') as $node) { Here each quad is updated by assigning each of its bnodes its new name
if(property_exists($statement, $node) && via the 'namer' object. */
$statement->{$node}->interfaceName === 'BlankNode' &&
strpos($statement->{$node}->nominalValue, '_:c14n') !== 0) { // update bnode names in each quad and serialize
$statement->{$node}->nominalValue = $namer->getName( foreach($quads as $quad) {
$statement->{$node}->nominalValue); foreach(array('subject', 'object', 'name') as $attr) {
if(property_exists($quad, $attr) &&
$quad->{$attr}->type === 'blank node' &&
strpos($quad->{$attr}->value, '_:c14n') !== 0) {
$quad->{$attr}->value = $namer->getName($quad->{$attr}->value);
} }
} }
$normalized[] = $this->toNQuad($statement); $normalized[] = $this->toNQuad($quad, property_exists($quad, 'name') ?
$quad->name->value : null);
} }
// sort normalized output // sort normalized output
sort($normalized); sort($normalized);
// handle output format // handle output format
if(isset($options['format'])) { if(isset($options['format']) && $options['format']) {
if($options['format'] === 'application/nquads') { if($options['format'] === 'application/nquads') {
return implode($normalized); return implode($normalized);
} }
else {
throw new JsonLdException( throw new JsonLdException(
'Unknown output format.', 'Unknown output format.',
'jsonld.UnknownFormat', array('format' => $options['format'])); 'jsonld.UnknownFormat', array('format' => $options['format']));
} }
}
// return parsed RDF statements // return RDF dataset
return $this->parseNQuads(implode($normalized)); return $this->parseNQuads(implode($normalized));
} }
/** /**
* Converts RDF statements into JSON-LD. * Converts an RDF dataset to JSON-LD.
* *
* @param array $statements the RDF statements. * @param stdClass $dataset the RDF dataset.
* @param assoc $options the RDF conversion options. * @param assoc $options the RDF conversion options.
* *
* @return array the JSON-LD output. * @return array the JSON-LD output.
*/ */
protected function _fromRDF($statements, $options) { protected function _fromRDF($dataset, $options) {
// prepare graph map (maps graph name => subjects, lists) // prepare graph map (maps graph name => subjects, lists)
$default_graph = (object)array( $default_graph = (object)array(
'subjects' => new stdClass(), 'listMap' => new stdClass()); 'subjects' => new stdClass(), 'listMap' => new stdClass());
$graphs = new stdClass(); $graphs = (object)array('@default' => $default_graph);
foreach($statements as $statement) { foreach($dataset as $graph_name => $triples) {
// get subject, property, object, and graph name (default to '') foreach($triples as $triple) {
$s = $statement->subject->nominalValue; // get subject, predicate, object
$p = $statement->property->nominalValue; $s = $triple->subject->value;
$o = $statement->object; $p = $triple->predicate->value;
$name = (property_exists($statement, 'name') ? $o = $triple->object;
$statement->name->nominalValue : '');
// use default graph
if($name === '') {
$graph = $default_graph;
}
// create a named graph entry as needed // create a named graph entry as needed
else if(!property_exists($graphs, $name)) { if(!property_exists($graphs, $graph_name)) {
$graph = $graphs->{$name} = (object)array( $graph = $graphs->{$graph_name} = (object)array(
'subjects' => new stdClass(), 'listMap' => new stdClass()); 'subjects' => new stdClass(), 'listMap' => new stdClass());
} }
else { else {
$graph = $graphs->{$name}; $graph = $graphs->{$graph_name};
} }
// handle element in @list // handle element in @list
@ -2359,14 +2431,14 @@ class JsonLdProcessor {
$entry = $list_map->{$s}; $entry = $list_map->{$s};
} }
// set object value // set object value
$entry->first = $this->_rdfToObject($o, $options['useNativeTypes']); $entry->first = $this->_RDFToObject($o, $options['useNativeTypes']);
continue; continue;
} }
// handle other element in @list // handle other element in @list
if($p === self::RDF_REST) { if($p === self::RDF_REST) {
// set next in list // set next in list
if($o->interfaceName === 'BlankNode') { if($o->type === 'blank node') {
// create list entry as needed // create list entry as needed
$list_map = $graph->listMap; $list_map = $graph->listMap;
if(!property_exists($list_map, $s)) { if(!property_exists($list_map, $s)) {
@ -2375,14 +2447,16 @@ class JsonLdProcessor {
else { else {
$entry = $list_map->{$s}; $entry = $list_map->{$s};
} }
$entry->rest = $o->nominalValue; $entry->rest = $o->value;
} }
continue; continue;
} }
// if graph is not the default graph // add graph subject to the default graph as needed
if($name !== '' && !property_exists($default_graph->subjects, $name)) { if($graph_name !== '@default' &&
$default_graph->subjects->{$name} = (object)array('@id' => $name); !property_exists($default_graph->subjects, $graph_name)) {
$default_graph->subjects->{$graph_name} = (object)array(
'@id' => $graph_name);
} }
// add subject to graph as needed // add subject to graph as needed
@ -2399,15 +2473,15 @@ class JsonLdProcessor {
if($p === self::RDF_TYPE && !$options['useRdfType']) { if($p === self::RDF_TYPE && !$options['useRdfType']) {
// add value of object as @type // add value of object as @type
self::addValue( self::addValue(
$value, '@type', $o->nominalValue, array('propertyIsArray' => true)); $value, '@type', $o->value, array('propertyIsArray' => true));
} }
else { else {
// add property to value as needed // add property to value as needed
$object = $this->_rdfToObject($o, $options['useNativeTypes']); $object = $this->_RDFToObject($o, $options['useNativeTypes']);
self::addValue($value, $p, $object, array('propertyIsArray' => true)); self::addValue($value, $p, $object, array('propertyIsArray' => true));
// a bnode might be the beginning of a list, so add it to the list map // a bnode might be the beginning of a list, so add it to the list map
if($o->interfaceName === 'BlankNode') { if($o->type === 'blank node') {
$id = $object->{'@id'}; $id = $object->{'@id'};
$list_map = $graph->listMap; $list_map = $graph->listMap;
if(!property_exists($list_map, $id)) { if(!property_exists($list_map, $id)) {
@ -2420,6 +2494,7 @@ class JsonLdProcessor {
} }
} }
} }
}
// build @lists // build @lists
$all_graphs = array_values((array)$graphs); $all_graphs = array_values((array)$graphs);
@ -2463,11 +2538,11 @@ class JsonLdProcessor {
// output named graph in subject @id order // output named graph in subject @id order
if(property_exists($graphs, $id)) { if(property_exists($graphs, $id)) {
$graph = array(); $graph = array();
$_subjects = $graphs->{$id}->subjects; $subjects_ = $graphs->{$id}->subjects;
$_ids = array_keys((array)$_subjects); $ids_ = array_keys((array)$subjects_);
sort($_ids); sort($ids_);
foreach($_ids as $_i => $_id) { foreach($ids_ as $i_ => $id_) {
$graph[] = $_subjects->{$_id}; $graph[] = $subjects_->{$id_};
} }
$subject->{'@graph'} = $graph; $subject->{'@graph'} = $graph;
} }
@ -2475,162 +2550,6 @@ class JsonLdProcessor {
return $output; return $output;
} }
/**
* Outputs the RDF statements found in the given JSON-LD element.
*
* @param mixed element the JSON-LD element.
* @param UniqueNamer namer the UniqueNamer for assigning bnode names.
* @param mixed subject the active subject.
* @param mixed property the active property.
* @param mixed graph the graph name.
* @param &array statements the array to add statements to.
*/
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;
}
// convert @list
if(self::_isList($element)) {
$list = $this->_makeLinkedList($element);
$this->_toRDF($list, $namer, $subject, $property, $graph, $statements);
return;
}
// convert @value to object
if(self::_isValue($element)) {
$value = $element->{'@value'};
$datatype = (property_exists($element, '@type') ?
$element->{'@type'} : null);
if(is_bool($value) || is_double($value) || is_integer($value)) {
// convert to XSD datatypes as appropriate
if(is_bool($value)) {
$value = ($value ? 'true' : 'false');
$datatype or $datatype = self::XSD_BOOLEAN;
}
else if(is_double($value)) {
// canonical double representation
$value = preg_replace('/(\d)0*E\+?/', '$1E',
sprintf('%1.15E', $value));
$datatype or $datatype = self::XSD_DOUBLE;
}
else {
$value = strval($value);
$datatype or $datatype = self::XSD_INTEGER;
}
}
// default to xsd:string datatype
$datatype or $datatype = self::XSD_STRING;
$object = (object)array(
'nominalValue' => $value,
'interfaceName' => 'LiteralNode',
'datatype' => (object)array(
'nominalValue' => $datatype,
'interfaceName' => 'IRI'));
if(property_exists($element, '@language') &&
$datatype === self::XSD_STRING) {
$object->language = $element->{'@language'};
}
// emit literal
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => $object);
if($graph !== null) {
$statement->name = $graph;
}
self::_appendUniqueRdfStatement($statements, $statement);
return;
}
// Note: element must be a subject
// get subject @id (generate one if it is a bnode)
$id = property_exists($element, '@id') ? $element->{'@id'} : null;
$is_bnode = self::_isBlankNode($element);
if($is_bnode) {
$id = $namer->getName($id);
}
// create object
$object = (object)array(
'nominalValue' => $id,
'interfaceName' => $is_bnode ? 'BlankNode' : 'IRI');
// emit statement if subject isn't null
if($subject !== null) {
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => self::copy($object));
if($graph !== null) {
$statement->name = $graph;
}
self::_appendUniqueRdfStatement($statements, $statement);
}
// set new active subject to object
$subject = $object;
// recurse over subject properties in order
$props = array_keys((array)$element);
sort($props);
foreach($props as $prop) {
$p = $prop;
// convert @type to rdf:type
if($prop === '@type') {
$p = self::RDF_TYPE;
}
// recurse into @graph
if($prop === '@graph') {
$this->_toRDF(
$element->{$prop}, $namer, null, null, $subject, $statements);
continue;
}
// skip keywords
if(self::_isKeyword($p)) {
continue;
}
// create new active property
$property = (object)array(
'nominalValue' => $p,
'interfaceName' => 'IRI');
// recurse into value
$this->_toRDF(
$element->{$prop}, $namer, $subject, $property, $graph, $statements);
}
}
/** /**
* Processes a local context and returns a new active context. * Processes a local context and returns a new active context.
* *
@ -2885,30 +2804,186 @@ class JsonLdProcessor {
} }
/** /**
* Converts an RDF statement object to a JSON-LD object. * Creates an array of RDF triples for the given graph.
* *
* @param stdClass $o the RDF statement object to convert. * @param stdClass $graph the graph to create RDF triples for.
* @param UniqueNamer $namer for assigning bnode names.
*
* @return array the array of RDF triples for the given graph.
*/
protected function _graphToRDF($graph, $namer) {
$rval = array();
foreach($graph as $id => $node) {
foreach($node as $property => $items) {
if($property === '@type') {
$property = self::RDF_TYPE;
}
else if(self::_isKeyword($property)) {
continue;
}
foreach($items as $item) {
// RDF subject
$subject = new stdClass();
if(strpos($id, '_:') === 0) {
$subject->type = 'blank node';
$subject->value = $namer->getName($id);
}
else {
$subject->type = 'IRI';
$subject->value = $id;
}
// RDF predicate
$predicate = new stdClass();
$predicate->type = 'IRI';
$predicate->value = $property;
// convert @list to triples
if(self::_isList($item)) {
$this->_listToRDF(
$item->{'@list'}, $namer, $subject, $predicate, $rval);
}
// convert value or node object to triple
else {
$object = $this->_objectToRDF($item, $namer);
$rval[] = (object)array(
'subject' => $subject,
'predicate' => $predicate,
'object' => $object);
}
}
}
}
return $rval;
}
/**
* Converts a @list value into linked list of blank node RDF triples
* (an RDF collection).
*
* @param array $list the @list value.
* @param UniqueNamer $namer for assigning blank node names.
* @param stdClass $subject the subject for the head of the list.
* @param stdClass $predicate the predicate for the head of the list.
* @param &array $triples the array of triples to append to.
*/
protected function _listToRDF(
$list, $namer, $subject, $predicate, &$triples) {
$first = (object)array('type' => 'IRI', 'value' => self::RDF_FIRST);
$rest = (object)array('type' => 'IRI', 'value' => self::RDF_REST);
$nil = (object)array('type' => 'IRI', 'value' => self::RDF_NIL);
foreach($list as $item) {
$blank_node = (object)array(
'type' => 'blank node', 'value' => $namer->getName());
$triples[] = (object)array(
'subject' => $subject,
'predicate' => $predicate,
'object' => $blank_node);
$subject = $blank_node;
$predicate = $first;
$object = $this->_objectToRDF($item, $namer);
$triples[] = (object)array(
'subject' => $subject, 'predicate' => $predicate, 'object' => $object);
$predicate = $rest;
}
$triples[] = (object)array(
'subject' => $subject, 'predicate' => $predicate, 'object' => $nil);
}
/**
* Converts a JSON-LD value object to an RDF literal or a JSON-LD string or
* node object to an RDF resource.
*
* @param mixed $item the JSON-LD value or node object.
* @param UniqueNamer $namer for assign blank node names.
*
* @return stdClass the RDF literal or RDF resource.
*/
protected function _objectToRDF($item, $namer) {
$object = new stdClass();
if(self::_isValue($item)) {
$object->type = 'literal';
$value = $item->{'@value'};
$datatype = property_exists($item, '@type') ? $item->{'@type'} : null;
// convert to XSD datatypes as appropriate
if(is_bool($value)) {
$object->value = ($value ? 'true' : 'false');
$object->datatype = $datatype ? $datatype : self::XSD_BOOLEAN;
}
else if(is_double($value)) {
// canonical double representation
$object->value = preg_replace(
'/(\d)0*E\+?/', '$1E', sprintf('%1.15E', $value));
$object->datatype = $datatype ? $datatype : self::XSD_DOUBLE;
}
else if(is_integer($value)) {
$object->value = strval($value);
$object->datatype = $datatype ? $datatype : self::XSD_INTEGER;
}
else if(property_exists($item, '@language')) {
$object->value = $value;
$object->datatype = $datatype ? $datatype : self::RDF_LANGSTRING;
$object->language = $item->{'@language'};
}
else {
$object->value = $value;
$object->datatype = $datatype ? $datatype : self::XSD_STRING;
}
}
// convert string/node object to RDF
else {
$id = is_object($item) ? $item->{'@id'} : $item;
if(strpos($id, '_:') === 0) {
$object->type = 'blank node';
$object->value = $namer->getName($id);
}
else {
$object->type = 'IRI';
$object->value = $id;
}
}
return $object;
}
/**
* Converts an RDF triple object to a JSON-LD object.
*
* @param stdClass $o the RDF triple object to convert.
* @param bool $use_native_types true to output native types, false not to. * @param bool $use_native_types true to output native types, false not to.
* *
* @return stdClass the JSON-LD object. * @return stdClass the JSON-LD object.
*/ */
protected function _rdfToObject($o, $use_native_types) { protected function _RDFToObject($o, $use_native_types) {
// convert empty list // convert empty list
if($o->interfaceName === 'IRI' && $o->nominalValue === self::RDF_NIL) { if($o->type === 'IRI' && $o->value === self::RDF_NIL) {
return (object)array('@list' => array()); return (object)array('@list' => array());
} }
// convert IRI/BlankNode object to JSON-LD // convert IRI/blank node object to JSON-LD
if($o->interfaceName === 'IRI' || $o->interfaceName === 'BlankNode') { if($o->type === 'IRI' || $o->type === 'blank node') {
return (object)array('@id' => $o->nominalValue); return (object)array('@id' => $o->value);
} }
// convert literal object to JSON-LD // convert literal object to JSON-LD
$rval = (object)array('@value' => $o->nominalValue); $rval = (object)array('@value' => $o->value);
// add language
if(property_exists($o, 'language')) {
$rval->{'@language'} = $o->language;
}
// add datatype // add datatype
if(property_exists($o, 'datatype')) { else {
$type = $o->datatype->nominalValue; $type = $o->datatype;
// use native types for certain xsd types // use native types for certain xsd types
if($use_native_types) { if($use_native_types) {
if($type === self::XSD_BOOLEAN) { if($type === self::XSD_BOOLEAN) {
@ -2941,43 +3016,17 @@ class JsonLdProcessor {
$rval->{'@type'} = $type; $rval->{'@type'} = $type;
} }
} }
// add language
if(property_exists($o, 'language')) {
$rval->{'@language'} = $o->language;
}
return $rval; return $rval;
} }
/**
* Converts a @list value into an embedded linked list of blank nodes in
* expanded form. The resulting array can be used as an RDF-replacement for
* a property that used a @list.
*
* @param array $value the @list value.
*
* @return stdClass the head of the linked list of blank nodes.
*/
protected function _makeLinkedList($value) {
// convert @list array into embedded blank node linked list in reverse
$list = $value->{'@list'};
$len = count($list);
$tail = (object)array('@id' => self::RDF_NIL);
for($i = $len - 1; $i >= 0; --$i) {
$tail = (object)array(
self::RDF_FIRST => array($list[$i]),
self::RDF_REST => array($tail));
}
return $tail;
}
/** /**
* Recursively flattens the subjects in the given JSON-LD expanded input * Recursively flattens the subjects in the given JSON-LD expanded input
* into a node map. * into a node map.
* *
* @param mixed $input the JSON-LD expanded input. * @param mixed $input the JSON-LD expanded input.
* @param stdClass $graphs a map of graph name to subject map. * @param stdClass $graphs a map of graph name to subject map.
* @[ara, string $graph the name of the current graph. * @param string $graph the name of the current graph.
* @param UniqueNamer $namer the blank node namer. * @param UniqueNamer $namer the blank node namer.
* @param mixed $name the name assigned to the current input if it is a bnode. * @param mixed $name the name assigned to the current input if it is a bnode.
* @param mixed $list the list to append to, null for none. * @param mixed $list the list to append to, null for none.
@ -3540,25 +3589,55 @@ class JsonLdProcessor {
} }
/** /**
* Hashes all of the statements about a blank node. * Compares two RDF triples for equality.
* *
* @param string $id the ID of the bnode to hash statements for. * @param stdClass $t1 the first triple.
* @param stdClass $bnodes the mapping of bnodes to statements. * @param stdClass $t2 the second triple.
*
* @return true if the triples are the same, false if not.
*/
protected static function _compareRDFTriples($t1, $t2) {
foreach(array('subject', 'predicate', 'object') as $attr) {
if($t1->{$attr}->type !== $t2->{$attr}->type ||
$t1->{$attr}->value !== $t2->{$attr}->value) {
return false;
}
}
if(property_exists($t1->object, 'language') !==
property_exists($t1->object, 'language')) {
return false;
}
if(property_exists($t1->object, 'language') &&
$t1->object->language !== $t2->object->language) {
return false;
}
if($t1->object->datatype !== $t2->object->datatype) {
return false;
}
return true;
}
/**
* Hashes all of the quads about a blank node.
*
* @param string $id the ID of the bnode to hash quads for.
* @param stdClass $bnodes the mapping of bnodes to quads.
* @param UniqueNamer $namer the canonical bnode namer. * @param UniqueNamer $namer the canonical bnode namer.
* *
* @return string the new hash. * @return string the new hash.
*/ */
protected function _hashStatements($id, $bnodes, $namer) { protected function _hashQuads($id, $bnodes, $namer) {
// return cached hash // return cached hash
if(property_exists($bnodes->{$id}, 'hash')) { if(property_exists($bnodes->{$id}, 'hash')) {
return $bnodes->{$id}->hash; return $bnodes->{$id}->hash;
} }
// serialize all of bnode's statements // serialize all of bnode's quads
$statements = $bnodes->{$id}->statements; $quads = $bnodes->{$id}->quads;
$nquads = array(); $nquads = array();
foreach($statements as $statement) { foreach($quads as $quad) {
$nquads[] = $this->toNQuad($statement, $id); $nquads[] = $this->toNQuad($quad, property_exists($quad, 'name') ?
$quad->name->value : null, $id);
} }
// sort serialized quads // sort serialized quads
@ -3576,7 +3655,7 @@ class JsonLdProcessor {
* lexicographically-least 'path' serializations. * lexicographically-least 'path' serializations.
* *
* @param string $id the ID of the bnode to hash paths for. * @param string $id the ID of the bnode to hash paths for.
* @param stdClass $bnodes the map of bnode statements. * @param stdClass $bnodes the map of bnode quads.
* @param UniqueNamer $namer the canonical bnode namer. * @param UniqueNamer $namer the canonical bnode namer.
* @param UniqueNamer $path_namer the namer used to assign names to adjacent * @param UniqueNamer $path_namer the namer used to assign names to adjacent
* bnodes. * bnodes.
@ -3589,16 +3668,18 @@ class JsonLdProcessor {
// group adjacent bnodes by hash, keep properties and references separate // group adjacent bnodes by hash, keep properties and references separate
$groups = new stdClass(); $groups = new stdClass();
$statements = $bnodes->{$id}->statements; $quads = $bnodes->{$id}->quads;
foreach($statements as $statement) { foreach($quads as $quad) {
// get adjacent bnode // get adjacent bnode
$bnode = $this->_getAdjacentBlankNodeName($statement->subject, $id); $bnode = $this->_getAdjacentBlankNodeName($quad->subject, $id);
if($bnode !== null) { if($bnode !== null) {
// normal property
$direction = 'p'; $direction = 'p';
} }
else { else {
$bnode = $this->_getAdjacentBlankNodeName($statement->object, $id); $bnode = $this->_getAdjacentBlankNodeName($quad->object, $id);
if($bnode !== null) { if($bnode !== null) {
// reverse property
$direction = 'r'; $direction = 'r';
} }
} }
@ -3611,13 +3692,13 @@ class JsonLdProcessor {
$name = $path_namer->getName($bnode); $name = $path_namer->getName($bnode);
} }
else { else {
$name = $this->_hashStatements($bnode, $bnodes, $namer); $name = $this->_hashQuads($bnode, $bnodes, $namer);
} }
// hash direction, property, and bnode name/hash // hash direction, property, and bnode name/hash
$group_md = hash_init('sha1'); $group_md = hash_init('sha1');
hash_update($group_md, $direction); hash_update($group_md, $direction);
hash_update($group_md, $statement->property->nominalValue); hash_update($group_md, $quad->predicate->value);
hash_update($group_md, $name); hash_update($group_md, $name);
$group_hash = hash_final($group_md); $group_hash = hash_final($group_md);
@ -3706,19 +3787,18 @@ class JsonLdProcessor {
} }
/** /**
* A helper function that gets the blank node name from an RDF statement * A helper function that gets the blank node name from an RDF quad
* node (subject or object). If the node is not a blank node or its * 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 * value does not match the given blank node ID, it will be returned.
* returned.
* *
* @param stdClass $node the RDF statement node. * @param stdClass $node the RDF quad node.
* @param string $id the ID of the blank node to look next to. * @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. * @return mixed the adjacent blank node name or null if none was found.
*/ */
protected function _getAdjacentBlankNodeName($node, $id) { protected function _getAdjacentBlankNodeName($node, $id) {
if($node->interfaceName === 'BlankNode' && $node->nominalValue !== $id) { if($node->type === 'blank node' && $node->value !== $id) {
return $node->nominalValue; return $node->value;
} }
return null; return null;
} }
@ -4900,74 +4980,6 @@ class JsonLdProcessor {
return $rval; 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.
*/
protected static function _appendUniqueRdfStatement(
&$statements, $statement) {
foreach($statements as $s) {
if(self::_compareRdfStatements($s, $statement)) {
return;
}
}
$statements[] = $statement;
}
/** /**
* Returns whether or not the given value is a keyword. * Returns whether or not the given value is a keyword.
* *