Implement toRDF.

This commit is contained in:
Dave Longley 2012-04-30 17:43:38 -04:00
parent 273c9c88de
commit ad531248c6
2 changed files with 221 additions and 21 deletions

View file

@ -282,6 +282,13 @@ class TestRunner {
$test->expect = read_test_json($test->expect, $filepath); $test->expect = read_test_json($test->expect, $filepath);
$result = jsonld_from_rdf($input, $options); $result = jsonld_from_rdf($input, $options);
} }
else if(in_array('jld:ToRDFTest', $type)) {
$this->test($test->name);
$input = read_test_json($test->input, $filepath);
$test->expect = read_test_nquads($test->expect, $filepath);
$options['format'] = 'application/nquads';
$result = jsonld_to_rdf($input, $options);
}
else { else {
echo "Skipping test \"{$test->name}\" of type: " . echo "Skipping test \"{$test->name}\" of type: " .
json_encode($type) . $eol; json_encode($type) . $eol;

View file

@ -110,7 +110,7 @@ function jsonld_normalize($input, $options=array()) {
* @param mixed $statements a serialized string of RDF statements in a format * @param mixed $statements a serialized string of RDF statements in a format
* specified by the format option or an array of the RDF statements * specified by the format option or an array of the RDF statements
* 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).
* [notType] true to use rdf:type, false to use @type (default). * [notType] true to use rdf:type, false to use @type (default).
@ -127,6 +127,9 @@ function jsonld_from_rdf($input, $options=array()) {
* *
* @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:
* [base] the base IRI to use.
* [format] the format to use to output 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 all RDF statements in the JSON-LD object. * @return array all RDF statements in the JSON-LD object.
@ -415,7 +418,7 @@ class JsonLdProcessor {
* @param mixed $statements a serialized string of RDF statements in a format * @param mixed $statements a serialized string of RDF statements in a format
* specified by the format option or an array of the RDF statements * specified by the format option or an array of the RDF statements
* 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).
* [notType] true to use rdf:type, false to use @type (default). * [notType] true to use rdf:type, false to use @type (default).
@ -448,9 +451,12 @@ class JsonLdProcessor {
* *
* @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:
* [base] the base IRI to use.
* [format] the format to use to output 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 RDF statements. * @return array all RDF statements in the JSON-LD object.
*/ */
public function toRDF($input, $options) { public function toRDF($input, $options) {
// set default options // set default options
@ -458,12 +464,40 @@ class JsonLdProcessor {
// FIXME: implement jsonld_resolve_url // FIXME: implement jsonld_resolve_url
isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url'; isset($options['resolver']) or $options['resolver'] = 'jsonld_resolve_url';
// resolve all @context URLs in the input try {
$input = self::copy($input); // expand input
$this->_resolveUrls($input, $options['resolver']); $expanded = $this->expand($input, $options);
}
catch(JsonLdException $e) {
throw new JsonLdException(
'Could not expand input before conversion to RDF.',
'jsonld.RdfError', $e);
}
// get RDF statements
$namer = new UniqueNamer('_:t');
$statements = array();
$this->_toRDF($expanded, $namer, null, null, null, $statements);
// convert to output format
if(isset($options['format'])) {
// supported formats
if($options['format'] === 'application/nquads') {
$nquads = '';
foreach($statements as $statement) {
$nquads .= $this->_toNQuad($statement);
}
$statements = $nquads;
}
else {
throw new JsonLdException(
'Unknown output format.',
'jsonld.UnknownFormat', array('format' => $options['format']));
}
}
// output RDF statements // output RDF statements
return $this->_toRDF($input); return $statements;
} }
/** /**
@ -1516,15 +1550,174 @@ class JsonLdProcessor {
} }
/** /**
* Outputs the RDF statements found in the given JSON-LD object. * Outputs the RDF statements found in the given JSON-LD element.
* *
* @param mixed $input the JSON-LD object. * @param mixed element the JSON-LD element.
* * @param UniqueNamer namer the UniqueNamer for assigning bnode names.
* @return array the RDF statements. * @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($input) { protected function _toRDF(
// FIXME: implement $element, $namer, $subject, $property, $graph, &$statements) {
throw new JsonLdException('Not implemented', 'jsonld.NotImplemented'); // recurse into arrays
if(is_array($element)) {
foreach($element as $e) {
$this->_toRDF($e, $namer, $subject, $property, $graph, $statements);
}
return;
}
if(is_object($element)) {
// convert @value to object
if(self::_isValue($element)) {
$object = (object)array(
'nominalValue' => $element->{'@value'},
'interfaceName' => 'LiteralNode');
if(property_exists($element, '@type')) {
$object->datatype = (object)array(
'nominalValue' => $element->{'@type'},
'interfaceName' => 'IRI');
}
else if(property_exists($element, '@language')) {
$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;
}
$statements[] = $statement;
return;
}
// convert @list
if(self::_isList($element)) {
$list = $this->_makeLinkedList($element);
$this->_toRDF($list, $namer, $subject, $property, $graph, $statements);
return;
}
// Note: element must be a subject
// get subject @id (generate one if it is a bnode)
$id = property_exists($element, '@id') ? $element->{'@id'} : null;
$is_bnode = self::_isBlankNode($element);
if($is_bnode) {
$id = $namer->getName($id);
}
// create object
$object = (object)array(
'nominalValue' => $id,
'interfaceName' => $is_bnode ? 'BlankNode' : 'IRI');
// emit statement if subject isn't null
if($subject !== null) {
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => self::copy($object));
if($graph !== null) {
$statement->name = $graph;
}
$statements[] = $statement;
}
// set new active subject to object
$subject = $object;
// recurse over subject properties in order
$props = array_keys((array)$element);
sort($props);
foreach($props as $prop) {
$p = $prop;
// convert @type to rdf:type
if($prop === '@type') {
$p = self::RDF_TYPE;
}
// recurse into @graph
if($prop === '@graph') {
$this->_toRDF(
$element->{$prop}, $namer, null, null, $subject, $statements);
continue;
}
// skip keywords
if(self::_isKeyword($p)) {
continue;
}
// create new active property
$property = (object)array(
'nominalValue' => $p,
'interfaceName' => 'IRI');
// recurse into value
$this->_toRDF(
$element->{$prop}, $namer, $subject, $property, $graph, $statements);
}
return;
}
if(is_string($element)) {
// emit IRI for rdf:type, else plain literal
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => (object)array(
'nominalValue' => $element,
'interfaceName' => (($property->nominalValue === self::RDF_TYPE) ?
'IRI' : 'LiteralNode')));
if($graph !== null) {
$statement->name = $graph;
}
$statements[] = $statement;
return;
}
if(is_bool($element) || is_double($element) || is_integer($element)) {
// convert to XSD datatype
if(is_bool($element)) {
$datatype = self::XSD_BOOLEAN;
$value = ($element ? 'true' : 'false');
}
else if(is_double($element)) {
$datatype = self::XSD_DOUBLE;
// do special JSON-LD double format, printf('%1.15e') equivalent
$value = preg_replace('/(e(?:\+|-))([0-9])$/', '${1}0${2}',
sprintf('%1.15e', $element));
}
else {
$datatype = self::XSD_INTEGER;
$value = strval($element);
}
// emit typed literal
$statement = (object)array(
'subject' => self::copy($subject),
'property' => self::copy($property),
'object' => (object)array(
'nominalValue' => $value,
'interfaceName' => 'LiteralNode',
'datatype' => (object)array(
'nominalValue' => $datatype,
'interfaceName' => 'IRI')));
if($graph !== null) {
$statement->name = $graph;
}
$statements[] = $statement;
return;
}
} }
/** /**
@ -1675,23 +1868,23 @@ class JsonLdProcessor {
} }
// Note: safe to assume input is a subject/blank node // Note: safe to assume input is a subject/blank node
$isBnode = self::_isBlankNode($input); $is_bnode = self::_isBlankNode($input);
// name blank node if appropriate, use passed name if given // name blank node if appropriate, use passed name if given
if($name === null) { if($name === null) {
if(property_exists($input, '@id')) { if(property_exists($input, '@id')) {
$name = $input->{'@id'}; $name = $input->{'@id'};
} }
if($isBnode) { if($is_bnode) {
$name = $namer->getName($name); $name = $namer->getName($name);
} }
} }
// use a subject of '_:a' for blank node statements // use a subject of '_:a' for blank node statements
$s = $isBnode ? '_:a' : $name; $s = $is_bnode ? '_:a' : $name;
// get statements for the blank node // get statements for the blank node
if($isBnode) { if($is_bnode) {
if(!property_exists($bnodes, $name)) { if(!property_exists($bnodes, $name)) {
$entries = $bnodes->{$name} = new ArrayObject(); $entries = $bnodes->{$name} = new ArrayObject();
} }
@ -3645,15 +3838,15 @@ class JsonLdProcessor {
$quad .= " <{$p->nominalValue}> "; $quad .= " <{$p->nominalValue}> ";
// object is IRI, bnode, or literal // object is IRI, bnode, or literal
if(o.interfaceName === 'IRI') { if($o->interfaceName === 'IRI') {
$quad .= "<{$o->nominalValue}>"; $quad .= "<{$o->nominalValue}>";
} }
else if(o.interfaceName === 'BlankNode') { else if($o->interfaceName === 'BlankNode') {
$quad .= $o->nominalValue; $quad .= $o->nominalValue;
} }
else { else {
$quad .= '"' . $o->nominalValue . '"'; $quad .= '"' . $o->nominalValue . '"';
if(propert_exists($o, 'datatype')) { if(property_exists($o, 'datatype')) {
$quad .= "^^<{$o->datatype->nominalValue}>"; $quad .= "^^<{$o->datatype->nominalValue}>";
} }
else if(property_exists($o, 'language')) { else if(property_exists($o, 'language')) {