Use RDF statements in normalization.

Remove special compaction of null-mapped IRIs.
This commit is contained in:
Dave Longley 2012-05-05 16:03:30 -04:00
parent 657edcec47
commit 52fa975894
2 changed files with 131 additions and 383 deletions

View file

@ -52,7 +52,7 @@ function deep_compare($expect, $result) {
return false; return false;
} }
foreach($expect as $k => $v) { foreach($expect as $k => $v) {
if(!deep_compare($v, $result->{$k})) { if(!property_exists($result, $k) || !deep_compare($v, $result->{$k})) {
return false; return false;
} }
} }
@ -152,14 +152,7 @@ class TestRunner {
public function check($test, $expect, $result) { public function check($test, $expect, $result) {
global $eol; global $eol;
if(in_array('jld:NormalizeTest', $test->{'@type'}) !== false) { if(deep_compare($expect, $result)) {
$pass = JsonLdProcessor::compareNormalized($expect, $result);
}
else {
$pass = deep_compare($expect, $result);
}
if($pass) {
$this->passed += 1; $this->passed += 1;
echo "PASS$eol"; echo "PASS$eol";
} }
@ -253,7 +246,8 @@ class TestRunner {
if(in_array('jld:NormalizeTest', $type)) { if(in_array('jld:NormalizeTest', $type)) {
$this->test($test->name); $this->test($test->name);
$input = read_test_json($test->input, $filepath); $input = read_test_json($test->input, $filepath);
$test->expect = read_test_json($test->expect, $filepath); $test->expect = read_test_nquads($test->expect, $filepath);
$options['format'] = 'application/nquads';
$result = jsonld_normalize($input, $options); $result = jsonld_normalize($input, $options);
} }
else if(in_array('jld:ExpandTest', $type)) { else if(in_array('jld:ExpandTest', $type)) {

View file

@ -90,14 +90,16 @@ function jsonld_frame($input, $frame, $options=array()) {
} }
/** /**
* Performs JSON-LD normalization. * Performs RDF normalization on the given JSON-LD input.
* *
* @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 (default).
* [resolver(url, callback(err, jsonCtx))] the URL resolver to use. * [resolver(url, callback(err, jsonCtx))] the URL resolver to use.
* *
* @return array the normalized JSON-LD output. * @return array the normalized output.
*/ */
function jsonld_normalize($input, $options=array()) { function jsonld_normalize($input, $options=array()) {
$p = new JsonLdProcessor(); $p = new JsonLdProcessor();
@ -409,7 +411,7 @@ class JsonLdProcessor {
} }
// do normalization // do normalization
return $this->_normalize($expanded); return $this->_normalize($expanded, $options);
} }
/** /**
@ -722,68 +724,6 @@ class JsonLdProcessor {
return false; return false;
} }
/**
* Compares two JSON-LD normalized inputs for equality.
*
* @param array $n1 the first normalized input.
* @param array $n2 the second normalized input.
*
* @return bool true if the inputs are equivalent, false if not.
*/
public static function compareNormalized($n1, $n2) {
if(!is_array($n1) || !is_array($n2)) {
throw new JsonLdException(
'Invalid JSON-LD syntax; normalized JSON-LD must be an array.',
'jsonld.SyntaxError');
}
// different # of subjects
if(count($n1) !== count($n2)) {
return false;
}
// assume subjects are in the same order because of normalization
foreach($n1 as $i => $s1) {
$s2 = $n2[$i];
// different @ids
if($s1->{'@id'} !== $s2->{'@id'}) {
return false;
}
// subjects have different properties
if(count(get_object_vars($s1)) !== count(get_object_vars($s2))) {
return false;
}
foreach($s1 as $p => $objects) {
// skip @id property
if($p === '@id') {
continue;
}
// s2 is missing s1 property
if(!self::hasProperty($s2, $p)) {
return false;
}
// subjects have different objects for the property
if(count($objects) !== count($s2->{$p})) {
return false;
}
foreach($objects as $o) {
// s2 is missing s1 object
if(!self::hasValue($s2, $p, $o)) {
return false;
}
}
}
}
return true;
}
/** /**
* Gets the value for the given active context key and type, null if none is * Gets the value for the given active context key and type, null if none is
* set. * set.
@ -960,10 +900,8 @@ class JsonLdProcessor {
// preserve empty arrays // preserve empty arrays
if(count($value) === 0) { if(count($value) === 0) {
$prop = $this->_compactIri($ctx, $key); $prop = $this->_compactIri($ctx, $key);
if($prop !== null) {
self::addValue($rval, $prop, array(), true); self::addValue($rval, $prop, array(), true);
} }
}
// recusively process array values // recusively process array values
foreach($value as $v) { foreach($value as $v) {
@ -972,11 +910,6 @@ class JsonLdProcessor {
// compact property // compact property
$prop = $this->_compactIri($ctx, $key, $v); $prop = $this->_compactIri($ctx, $key, $v);
// skip null properties
if($prop === null) {
continue;
}
// remove @list for recursion (will be re-added if necessary) // remove @list for recursion (will be re-added if necessary)
if($is_list) { if($is_list) {
$v = $v->{'@list'}; $v = $v->{'@list'};
@ -1258,16 +1191,30 @@ class JsonLdProcessor {
/** /**
* Performs JSON-LD normalization. * Performs JSON-LD normalization.
* *
* @param input the expanded JSON-LD object to normalize. * @param array $input the expanded JSON-LD object to normalize.
* @param assoc $options the normalization options.
* *
* @return the normalized output. * @return the normalized output.
*/ */
protected function _normalize($input) { protected function _normalize($input, $options) {
// get statements // map bnodes to RDF statements
$namer = new UniqueNamer('_:t'); $statements = array();
$bnodes = new stdClass(); $bnodes = new stdClass();
$subjects = new stdClass(); $namer = new UniqueNamer('_:t');
$this->_getStatements($input, $namer, $bnodes, $subjects); $this->_toRDF($input, $namer, null, null, null, $statements);
foreach($statements as $statement) {
foreach(array('subject', 'object') as $node) {
$id = $statement->{$node}->nominalValue;
if($statement->{$node}->interfaceName === 'BlankNode') {
if(property_exists($bnodes, $id)) {
$bnodes->{$id}->statements[] = $statement;
}
else {
$bnodes->{$id} = (object)array('statements' => array($statement));
}
}
}
}
// create canonical namer // create canonical namer
$namer = new UniqueNamer('_:c14n'); $namer = new UniqueNamer('_:c14n');
@ -1283,8 +1230,7 @@ class JsonLdProcessor {
$unique = new stdClass(); $unique = new stdClass();
foreach($unnamed as $bnode) { foreach($unnamed as $bnode) {
// hash statements for each unnamed bnode // hash statements for each unnamed bnode
$statements = $bnodes->{$bnode}; $hash = $this->_hashStatements($bnode, $bnodes, $namer);
$hash = $this->_hashStatements($statements, $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)) {
@ -1327,8 +1273,7 @@ class JsonLdProcessor {
// hash bnode paths // hash bnode paths
$path_namer = new UniqueNamer('_:t'); $path_namer = new UniqueNamer('_:t');
$path_namer->getName($bnode); $path_namer->getName($bnode);
$results[] = $this->_hashPaths( $results[] = $this->_hashPaths($bnode, $bnodes, $namer, $path_namer);
$bnodes, $bnodes->{$bnode}, $namer, $path_namer);
} }
// name bnodes in hash order // name bnodes in hash order
@ -1345,43 +1290,37 @@ class JsonLdProcessor {
} }
} }
// create JSON-LD array // create normalized array
$output = array(); $normalized = array();
// add all bnodes // update bnode names in each statement and serialize
foreach($bnodes as $id => $statements) {
// add all property statements to bnode
$bnode = (object)array('@id' => $namer->getName($id));
foreach($statements as $statement) { foreach($statements as $statement) {
if($statement->s === '_:a') { foreach(array('subject', 'object') as $node) {
$z = $this->_getBlankNodeName($statement->o); if($statement->{$node}->interfaceName === 'BlankNode') {
$o = $z ? (object)array('@id' => $namer->getName($z)) : $statement->o; $statement->{$node}->nominalValue = $namer->getName(
self::addValue($bnode, $statement->p, $o, true); $statement->{$node}->nominalValue);
} }
} }
$output[] = $bnode; $normalized[] = $this->_toNQuad($statement);
} }
// add all non-bnodes // sort normalized output
foreach($subjects as $id => $statements) { sort($normalized);
// add all statements to subject
$subject = (object)array('@id' => $id); // handle output format
foreach($statements as $statement) { if(isset($options['format'])) {
$z = $this->_getBlankNodeName($statement->o); if($options['format'] === 'application/nquads') {
$o = $z ? (object)array('@id' => $namer->getName($z)) : $statement->o; return implode($normalized);
self::addValue($subject, $statement->p, $o, true); }
else {
throw new JsonLdException(
'Unknown output format.',
'jsonld.UnknownFormat', array('format' => $options['format']));
} }
$output[] = $subject;
} }
// sort normalized output by @id // return parsed RDF statements
usort($output, function($a, $b) { return $this->_parseNQuads(implode($normalized));
$a = $a->{'@id'};
$b = $b->{'@id'};
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
});
return $output;
} }
/** /**
@ -1734,6 +1673,10 @@ class JsonLdProcessor {
$rval = self::copy($active_ctx); $rval = self::copy($active_ctx);
// normalize local context to an array // 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'};
}
$ctxs = self::arrayify($local_ctx); $ctxs = self::arrayify($local_ctx);
// process each context in order // process each context in order
@ -1848,142 +1791,6 @@ class JsonLdProcessor {
return $rval; return $rval;
} }
/**
* Recursively gets all statements from the given expanded JSON-LD input.
*
* @param mixed $input the valid expanded JSON-LD input.
* @param UniqueNamer $namer the namer to use when encountering blank nodes.
* @param stdClass $bnodes the blank node statements map to populate.
* @param stdClass $subjects the subject statements map to populate.
* @param mixed [$name] the name (@id) assigned to the current input.
*/
protected function _getStatements(
$input, $namer, $bnodes, $subjects, $name=null) {
// recurse into arrays
if(is_array($input)) {
foreach($input as $e) {
$this->_getStatements($e, $namer, $bnodes, $subjects);
}
return;
}
// Note: safe to assume input is a subject/blank node
$is_bnode = self::_isBlankNode($input);
// name blank node if appropriate, use passed name if given
if($name === null) {
if(property_exists($input, '@id')) {
$name = $input->{'@id'};
}
if($is_bnode) {
$name = $namer->getName($name);
}
}
// use a subject of '_:a' for blank node statements
$s = $is_bnode ? '_:a' : $name;
// get statements for the blank node
if($is_bnode) {
if(!property_exists($bnodes, $name)) {
$entries = $bnodes->{$name} = new ArrayObject();
}
else {
$entries = $bnodes->{$name};
}
}
else if(!property_exists($subjects, $name)) {
$entries = $subjects->{$name} = new ArrayObject();
}
else {
$entries = $subjects->{$name};
}
// add all statements in input
foreach($input as $p => $objects) {
// skip @id
if($p === '@id') {
continue;
}
// convert @lists into embedded blank node linked lists
foreach($objects as $i => $o) {
if(self::_isList($o)) {
$objects[$i] = $this->_makeLinkedList($o);
}
}
foreach($objects as $o) {
// convert boolean to @value
if(is_bool($o)) {
$o = (object)array(
'@value' => ($o ? 'true' : 'false'),
'@type' => self::XSD_BOOLEAN);
}
// convert double to @value
else if(is_double($o)) {
// do special JSON-LD double format, printf('%1.15e') equivalent
$o = preg_replace('/(e(?:\+|-))([0-9])$/', '${1}0${2}',
sprintf('%1.15e', $o));
$o = (object)array('@value' => $o, '@type' => self::XSD_DOUBLE);
}
// convert integer to @value
else if(is_integer($o)) {
$o = (object)array(
'@value' => strval($o), '@type' => self::XSD_INTEGER);
}
// object is a blank node
if(self::_isBlankNode($o)) {
// name object position blank node
$o_name = property_exists($o, '@id') ? $o->{'@id'} : null;
$o_name = $namer->getName($o_name);
// add property statement
$this->_addStatement($entries, (object)array(
's' => $s, 'p' => $p, 'o' => (object)array('@id' => $o_name)));
// add reference statement
if(!property_exists($bnodes, $o_name)) {
$o_entries = $bnodes->{$o_name} = new ArrayObject();
}
else {
$o_entries = $bnodes->{$o_name};
}
$this->_addStatement(
$o_entries, (object)array(
's' => $name, 'p' => $p, 'o' => (object)array('@id' => '_:a')));
// recurse into blank node
$this->_getStatements($o, $namer, $bnodes, $subjects, $o_name);
}
// object is a string, @value, subject reference
else if(is_string($o) || self::_isValue($o) ||
self::_isSubjectReference($o)) {
// add property statement
$this->_addStatement($entries, (object)array(
's' => $s, 'p' => $p, 'o' => $o));
// ensure a subject entry exists for subject reference
if(self::_isSubjectReference($o) &&
!property_exists($subjects, $o->{'@id'})) {
$subjects->{$o->{'@id'}} = new ArrayObject();
}
}
// object must be an embedded subject
else {
// add property statement
$this->_addStatement($entries, (object)array(
's' => $s, 'p' => $p, 'o' => (object)array(
'@id' => $o->{'@id'})));
// recurse into subject
$this->_getStatements($o, $namer, $bnodes, $subjects);
}
}
}
}
/** /**
* Converts a @list value into an embedded linked list of blank nodes in * 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 * expanded form. The resulting array can be used as an RDF-replacement for
@ -2009,93 +1816,34 @@ class JsonLdProcessor {
return $tail; return $tail;
} }
/**
* Adds a statement to an array of statements. If the statement already exists
* in the array, it will not be added.
*
* @param ArrayObject $statements the statements array.
* @param stdClass $statement the statement to add.
*/
protected function _addStatement($statements, $statement) {
foreach($statements as $s) {
if($s->s === $statement->s && $s->p === $statement->p &&
self::compareValues($s->o, $statement->o)) {
return;
}
}
$statements[] = $statement;
}
/** /**
* Hashes all of the statements about a blank node. * Hashes all of the statements about a blank node.
* *
* @param ArrayObject $statements the statements about the bnode. * @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. * @param UniqueNamer $namer the canonical bnode namer.
* *
* @return string the new hash. * @return string the new hash.
*/ */
protected function _hashStatements($statements, $namer) { protected function _hashStatements($id, $bnodes, $namer) {
// serialize all statements // return cached hash
$triples = array(); 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) { foreach($statements as $statement) {
// serialize triple $nquads[] = $this->_toNQuad($statement, $id);
$triple = '';
// serialize subject
if($statement->s === '_:a') {
$triple .= '_:a';
}
else if(strpos($statement->s, '_:') === 0) {
$id = $statement->s;
$id = $namer->isNamed($id) ? $namer->getName($id) : '_:z';
$triple .= $id;
}
else {
$triple .= '<' . $statement->s . '>';
} }
// serialize property // sort serialized quads
$p = ($statement->p === '@type') ? self::RDF_TYPE : $statement->p; sort($nquads);
$triple .= ' <' . $p . '> ';
// serialize object // cache and return hashed quads
if(self::_isBlankNode($statement->o)) { $hash = $bnodes->{$id}->hash = sha1(implode($nquads));
if($statement->o->{'@id'} === '_:a') { return $hash;
$triple .= '_:a';
}
else {
$id = $statement->o->{'@id'};
$id = $namer->isNamed($id) ? $namer->getName($id) : '_:z';
$triple .= $id;
}
}
else if(is_string($statement->o)) {
$triple .= '"' . $statement->o . '"';
}
else if(self::_isSubjectReference($statement->o)) {
$triple .= '<' . $statement->o->{'@id'} . '>';
}
// must be a value
else {
$triple .= '"' . $statement->o->{'@value'} . '"';
if(property_exists($statement->o, '@type')) {
$triple .= '^^<' . $statement->o->{'@type'} . '>';
}
else if(property_exists($statement->o, '@language')) {
$triple .= '@' . $statement->o{'@language'};
}
}
// add triple
$triples[] = $triple;
}
// sort serialized triples
sort($triples);
// return hashed triples
return sha1(implode($triples));
} }
/** /**
@ -2104,32 +1852,33 @@ class JsonLdProcessor {
* method will recursively pick adjacent bnode permutations that produce the * method will recursively pick adjacent bnode permutations that produce the
* lexicographically-least 'path' serializations. * 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 stdClass $bnodes the map of bnode statements.
* @param ArrayObject $statements the statements for the bnode to produce
* the hash for.
* @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.
* *
* @return stdClass the hash and path namer used. * @return stdClass the hash and path namer used.
*/ */
protected function _hashPaths($bnodes, $statements, $namer, $path_namer) { protected function _hashPaths($id, $bnodes, $namer, $path_namer) {
// create SHA-1 digest // create SHA-1 digest
$md = hash_init('sha1'); $md = hash_init('sha1');
// 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();
$cache = new stdClass(); $statements = $bnodes->{$id}->statements;
foreach($statements as $statement) { foreach($statements as $statement) {
if($statement->s !== '_:a' && strpos($statement->s, '_:') === 0) { // get adjacent bnode
$bnode = $statement->s; $bnode = $this->_getAdjacentBlankNodeName($statement->subject, $id);
if($bnode !== null) {
$direction = 'p'; $direction = 'p';
} }
else { else {
$bnode = $this->_getBlankNodeName($statement->o); $bnode = $this->_getAdjacentBlankNodeName($statement->object, $id);
if($bnode !== null) {
$direction = 'r'; $direction = 'r';
} }
}
if($bnode !== null) { if($bnode !== null) {
// get bnode name (try canonical, path, then hash) // get bnode name (try canonical, path, then hash)
if($namer->isNamed($bnode)) { if($namer->isNamed($bnode)) {
@ -2138,19 +1887,14 @@ class JsonLdProcessor {
else if($path_namer->isNamed($bnode)) { else if($path_namer->isNamed($bnode)) {
$name = $path_namer->getName($bnode); $name = $path_namer->getName($bnode);
} }
else if(property_exists($cache, $bnode)) {
$name = $cache->{$bnode};
}
else { else {
$name = $this->_hashStatements($bnodes->{$bnode}, $namer); $name = $this->_hashStatements($bnode, $bnodes, $namer);
$cache->{$bnode} = $name;
} }
// 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, hash_update($group_md, $statement->property->nominalValue);
($statement->p === '@type') ? self::RDF_TYPE : $statement->p);
hash_update($group_md, $name); hash_update($group_md, $name);
$group_hash = hash_final($group_md); $group_hash = hash_final($group_md);
@ -2208,14 +1952,14 @@ class JsonLdProcessor {
if(!$skipped) { if(!$skipped) {
foreach($recurse as $bnode) { foreach($recurse as $bnode) {
$result = $this->_hashPaths( $result = $this->_hashPaths(
$bnodes, $bnodes->{$bnode}, $namer, $path_namer_copy); $bnode, $bnodes, $namer, $path_namer_copy);
$path .= $path_namer_copy->getName($bnode); $path .= $path_namer_copy->getName($bnode);
$path .= "<{$result->hash}>"; $path .= "<{$result->hash}>";
$path_namer_copy = $result->pathNamer; $path_namer_copy = $result->pathNamer;
// skip permutation if path is already >= chosen path // skip permutation if path is already >= chosen path
if($chosen_path !== null && strlen($path) >= strlen($chosen_path) && if($chosen_path !== null &&
$path > $chosen_path) { strlen($path) >= strlen($chosen_path) && $path > $chosen_path) {
$skipped = true; $skipped = true;
break; break;
} }
@ -2239,17 +1983,21 @@ class JsonLdProcessor {
} }
/** /**
* A helper function that gets the blank node name from a statement value * A helper function that gets the blank node name from an RDF statement
* (a subject or object). If the statement value is not a blank node or it * node (subject or object). If the node is not a blank node or its
* has an @id of '_:a', then null will be returned. * nominal value does not match the given blank node ID, it will be
* returned.
* *
* @param mixed $value the statement value. * @param stdClass $node the RDF statement node.
* @param string $id the ID of the blank node to look next to.
* *
* @return mixed the blank node name or null if none was found. * @return mixed the adjacent blank node name or null if none was found.
*/ */
protected function _getBlankNodeName($value) { protected function _getAdjacentBlankNodeName($node, $id) {
return ((self::_isBlankNode($value) && $value->{'@id'} !== '_:a') ? if($node->interfaceName === 'BlankNode' && $node->nominalValue !== $id) {
$value->{'@id'} : null); return $node->nominalValue;
}
return null;
} }
/** /**
@ -2979,11 +2727,6 @@ class JsonLdProcessor {
// no matching terms // no matching terms
if(count($terms) === 0) { if(count($terms) === 0) {
// return null if a null mapping exists
if(property_exists($ctx->mappings, $iri) &&
$ctx->mappings->{$iri}->{'@id'} === null) {
return null;
}
// use iri // use iri
return $iri; return $iri;
} }
@ -3061,7 +2804,8 @@ class JsonLdProcessor {
} }
// clear context entry // clear context entry
if($value === null) { if($value === null or (is_object($value) &&
property_exists($value, '@id') && $value->{'@id'} === null)) {
if(property_exists($active_ctx->mappings, $key)) { if(property_exists($active_ctx->mappings, $key)) {
// if key is a keyword alias, remove it // if key is a keyword alias, remove it
$kw = $active_ctx->mappings->{$key}->{'@id'}; $kw = $active_ctx->mappings->{$key}->{'@id'};
@ -3069,8 +2813,8 @@ class JsonLdProcessor {
array_splice($active_ctx->keywords->{$kw}, array_splice($active_ctx->keywords->{$kw},
in_array($key, $active_ctx->keywords->{$kw}), 1); in_array($key, $active_ctx->keywords->{$kw}), 1);
} }
unset($active_ctx->mappings->{$key});
} }
$active_ctx->mappings->{$key} = (object)array('@id' => null);
$defined->{$key} = true; $defined->{$key} = true;
return; return;
} }
@ -3091,7 +2835,7 @@ class JsonLdProcessor {
array($this, '_compareShortestLeast')); array($this, '_compareShortestLeast'));
} }
} }
else if($value !== null) { else {
// expand value to a full IRI // expand value to a full IRI
$value = $this->_expandContextIri( $value = $this->_expandContextIri(
$active_ctx, $ctx, $value, $base, $defined); $active_ctx, $ctx, $value, $base, $defined);
@ -3195,10 +2939,8 @@ class JsonLdProcessor {
$mapping->{'@language'} = $language; $mapping->{'@language'} = $language;
} }
// if not a null mapping, merge onto parent mapping if one exists for // merge onto parent mapping if one exists for a prefix
// a prefix if($prefix !== null && property_exists($active_ctx->mappings, $prefix)) {
if($mapping->{'@id'} !== null && $prefix !== null &&
property_exists($active_ctx->mappings, $prefix)) {
$child = $mapping; $child = $mapping;
$mapping = self::copy($active_ctx->mappings->{$prefix}); $mapping = self::copy($active_ctx->mappings->{$prefix});
foreach($child as $k => $v) { foreach($child as $k => $v) {
@ -3234,8 +2976,8 @@ class JsonLdProcessor {
// recurse if value is a term // recurse if value is a term
if(property_exists($active_ctx->mappings, $value)) { if(property_exists($active_ctx->mappings, $value)) {
$id = $active_ctx->mappings->{$value}->{'@id'}; $id = $active_ctx->mappings->{$value}->{'@id'};
// value is already an absolute IRI or id is a null mapping // value is already an absolute IRI
if($value === $id || $id === null) { if($value === $id) {
return $value; return $value;
} }
return $this->_expandContextIri($active_ctx, $ctx, $id, $base, $defined); return $this->_expandContextIri($active_ctx, $ctx, $id, $base, $defined);
@ -3265,11 +3007,9 @@ class JsonLdProcessor {
// recurse if prefix is defined // recurse if prefix is defined
if(property_exists($active_ctx->mappings, $prefix)) { if(property_exists($active_ctx->mappings, $prefix)) {
$id = $active_ctx->mappings->{$prefix}->{'@id'}; $id = $active_ctx->mappings->{$prefix}->{'@id'};
if($id !== null) {
return $this->_expandContextIri( return $this->_expandContextIri(
$active_ctx, $ctx, $id, $base, $defined) . $suffix; $active_ctx, $ctx, $id, $base, $defined) . $suffix;
} }
}
// consider value an absolute IRI // consider value an absolute IRI
return $value; return $value;
@ -3815,10 +3555,12 @@ class JsonLdProcessor {
* Converts an RDF statement to an N-Quad string (a single quad). * Converts an RDF statement to an N-Quad string (a single quad).
* *
* @param stdClass $statement the RDF statement to convert. * @param stdClass $statement the RDF statement to convert.
* @param string $bnode the bnode the staetment is mapped to (optional, for
* use during normalization only).
* *
* @return the N-Quad string. * @return the N-Quad string.
*/ */
protected static function _toNQuad($statement) { protected static function _toNQuad($statement, $bnode=null) {
$s = $statement->subject; $s = $statement->subject;
$p = $statement->property; $p = $statement->property;
$o = $statement->object; $o = $statement->object;
@ -3830,6 +3572,11 @@ class JsonLdProcessor {
if($s->interfaceName === 'IRI') { if($s->interfaceName === 'IRI') {
$quad .= "<{$s->nominalValue}>"; $quad .= "<{$s->nominalValue}>";
} }
// normalization mode
else if($bnode !== null) {
$quad .= ($s->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else { else {
$quad .= $s->nominalValue; $quad .= $s->nominalValue;
} }
@ -3842,8 +3589,15 @@ class JsonLdProcessor {
$quad .= "<{$o->nominalValue}>"; $quad .= "<{$o->nominalValue}>";
} }
else if($o->interfaceName === 'BlankNode') { else if($o->interfaceName === 'BlankNode') {
// normalization mode
if($bnode !== null) {
$quad .= ($o->nominalValue === $bnode) ? '_:a' : '_:z';
}
// normal mode
else {
$quad .= $o->nominalValue; $quad .= $o->nominalValue;
} }
}
else { else {
$quad .= '"' . $o->nominalValue . '"'; $quad .= '"' . $o->nominalValue . '"';
if(property_exists($o, 'datatype')) { if(property_exists($o, 'datatype')) {