forked from friendica/php-json-ld
Implement fromRDF.
This commit is contained in:
parent
28e8502122
commit
273c9c88de
|
@ -83,6 +83,27 @@ function read_test_json($file, $filepath) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads test N-Quads files.
|
||||
*
|
||||
* @param string $file the file to read.
|
||||
* @param string $filepath the test filepath.
|
||||
*
|
||||
* @return string the read N-Quads.
|
||||
*/
|
||||
function read_test_nquads($file, $filepath) {
|
||||
global $eol;
|
||||
|
||||
try {
|
||||
$file = $filepath . '/' . $file;
|
||||
return file_get_contents($file);
|
||||
}
|
||||
catch(Exception $e) {
|
||||
echo "Exception while parsing file: '$file'$eol";
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
class TestRunner {
|
||||
public function __construct() {
|
||||
// set up groups, add root group
|
||||
|
@ -255,6 +276,12 @@ class TestRunner {
|
|||
$test->expect = read_test_json($test->expect, $filepath);
|
||||
$result = jsonld_frame($input, $test->frame, $options);
|
||||
}
|
||||
else if(in_array('jld:FromRDFTest', $type)) {
|
||||
$this->test($test->name);
|
||||
$input = read_test_nquads($test->input, $filepath);
|
||||
$test->expect = read_test_json($test->expect, $filepath);
|
||||
$result = jsonld_from_rdf($input, $options);
|
||||
}
|
||||
else {
|
||||
echo "Skipping test \"{$test->name}\" of type: " .
|
||||
json_encode($type) . $eol;
|
||||
|
|
412
jsonld.php
412
jsonld.php
|
@ -104,6 +104,24 @@ function jsonld_normalize($input, $options=array()) {
|
|||
return $p->normalize($input, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts RDF statements into JSON-LD.
|
||||
*
|
||||
* @param mixed $statements a serialized string of RDF statements in a format
|
||||
* specified by the format option or an array of the RDF statements
|
||||
* to convert.
|
||||
* @param assoc [options] the options to use:
|
||||
* [format] the format if input is a string:
|
||||
* 'application/nquads' for N-Quads (default).
|
||||
* [notType] true to use rdf:type, false to use @type (default).
|
||||
*
|
||||
* @return array the JSON-LD output.
|
||||
*/
|
||||
function jsonld_from_rdf($input, $options=array()) {
|
||||
$p = new JsonLdProcessor();
|
||||
return $p->fromRDF($input, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the RDF statements found in the given JSON-LD object.
|
||||
*
|
||||
|
@ -115,7 +133,7 @@ function jsonld_normalize($input, $options=array()) {
|
|||
*/
|
||||
function jsonld_to_rdf($input, $options=array()) {
|
||||
$p = new JsonLdProcessor();
|
||||
return $p->toRdf($input, $options);
|
||||
return $p->toRDF($input, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -391,6 +409,40 @@ class JsonLdProcessor {
|
|||
return $this->_normalize($expanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts RDF statements into JSON-LD.
|
||||
*
|
||||
* @param mixed $statements a serialized string of RDF statements in a format
|
||||
* specified by the format option or an array of the RDF statements
|
||||
* to convert.
|
||||
* @param assoc options the options to use:
|
||||
* [format] the format if input is a string:
|
||||
* 'application/nquads' for N-Quads (default).
|
||||
* [notType] true to use rdf:type, false to use @type (default).
|
||||
*
|
||||
* @return array the JSON-LD output.
|
||||
*/
|
||||
public function fromRDF($statements, $options) {
|
||||
// set default options
|
||||
isset($options['format']) or $options['format'] = 'application/nquads';
|
||||
isset($options['notType']) or $options['notType'] = false;
|
||||
|
||||
if(is_string($statements)) {
|
||||
// supported formats
|
||||
if($options['format'] === 'application/nquads') {
|
||||
$statements = $this->_parseNQuads($statements);
|
||||
}
|
||||
else {
|
||||
throw new JsonLdException(
|
||||
'Unknown input format.',
|
||||
'jsonld.UnknownFormat', array('format' => $options['format']));
|
||||
}
|
||||
}
|
||||
|
||||
// convert from RDF
|
||||
return $this->_fromRDF($statements, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the RDF statements found in the given JSON-LD object.
|
||||
*
|
||||
|
@ -1297,6 +1349,172 @@ class JsonLdProcessor {
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts RDF statements into JSON-LD.
|
||||
*
|
||||
* @param array $statements the RDF statements.
|
||||
* @param assoc $options the RDF conversion options.
|
||||
*/
|
||||
protected function _fromRDF($statements, $options) {
|
||||
// prepare graph map (maps graph name => subjects, lists, etc)
|
||||
$default_graph = (object)array(
|
||||
'subjects' => new stdClass(), 'listMap' => new stdClass());
|
||||
$graphs = new stdClass();
|
||||
|
||||
foreach($statements as $statement) {
|
||||
// get subject, property, object, and graph name (default to '')
|
||||
$s = $statement->subject->nominalValue;
|
||||
$p = $statement->property->nominalValue;
|
||||
$o = $statement->object;
|
||||
$name = (property_exists($statement, 'name') ?
|
||||
$statement->name->nominalValue : '');
|
||||
|
||||
// use default graph
|
||||
if($name === '') {
|
||||
$graph = $default_graph;
|
||||
}
|
||||
// create a named graph entry as needed
|
||||
else if(!property_exists($graphs, $name)) {
|
||||
$graph = $graphs->{$name} = (object)array(
|
||||
'subjects' => new stdClass(), 'listMap' => new stdClass());
|
||||
}
|
||||
else {
|
||||
$graph = $graphs->{$name};
|
||||
}
|
||||
|
||||
// handle element in @list
|
||||
if($p === self::RDF_FIRST) {
|
||||
// create list entry as needed
|
||||
$list_map = $graph->listMap;
|
||||
if(!property_exists($list_map, $s)) {
|
||||
$entry = $list_map->{$s} = new stdClass();
|
||||
}
|
||||
else {
|
||||
$entry = $list_map->{$s};
|
||||
}
|
||||
// set object value
|
||||
$entry->first = $this->_rdfToObject($o);
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle other element in @list
|
||||
if($p === self::RDF_REST) {
|
||||
// set next in list
|
||||
if($o->interfaceName === 'BlankNode') {
|
||||
// create list entry as needed
|
||||
$list_map = $graph->listMap;
|
||||
if(!property_exists($list_map, $s)) {
|
||||
$entry = $list_map->{$s} = new stdClass();
|
||||
}
|
||||
else {
|
||||
$entry = $list_map->{$s};
|
||||
}
|
||||
$entry->rest = $o->nominalValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// if graph is not the default graph
|
||||
if($name !== '') {
|
||||
// add graph subject to default graph as needed
|
||||
if(!property_exists($default_graph->subjects, $name)) {
|
||||
$value = $default_graph->subjects->{$name} = (object)array(
|
||||
'@id' => $name);
|
||||
}
|
||||
else {
|
||||
$value = $default_graph->subjects->{$name};
|
||||
}
|
||||
}
|
||||
|
||||
// add subject to graph as needed
|
||||
$subjects = $graph->subjects;
|
||||
if(!property_exists($subjects, $s)) {
|
||||
$value = $subjects->{$s} = (object)array('@id' => $s);
|
||||
}
|
||||
// use existing subject value
|
||||
else {
|
||||
$value = $subjects->{$s};
|
||||
}
|
||||
|
||||
// convert to @type unless options indicate to treat rdf:type as property
|
||||
if($p === self::RDF_TYPE && !$options['notType']) {
|
||||
// add value of object as @type
|
||||
self::addValue($value, '@type', $o->nominalValue, true);
|
||||
}
|
||||
else {
|
||||
// add property to value as needed
|
||||
$object = $this->_rdfToObject($o);
|
||||
self::addValue($value, $p, $object, true);
|
||||
|
||||
// a bnode might be the beginning of a list, so add it to the list map
|
||||
if($o->interfaceName === 'BlankNode') {
|
||||
$id = $object->{'@id'};
|
||||
$list_map = $graph->listMap;
|
||||
if(!property_exists($list_map, $id)) {
|
||||
$entry = $list_map->{$id} = new stdClass();
|
||||
}
|
||||
else {
|
||||
$entry = $list_map->{$id};
|
||||
}
|
||||
$entry->head = $object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build @lists
|
||||
$all_graphs = array_values((array)$graphs);
|
||||
$all_graphs[] = $default_graph;
|
||||
foreach($all_graphs as $graph) {
|
||||
// find list head
|
||||
$list_map = $graph->listMap;
|
||||
foreach($list_map as $subject => $entry) {
|
||||
// head found, build lists
|
||||
if(property_exists($entry, 'head') &&
|
||||
property_exists($entry, 'first')) {
|
||||
// replace bnode @id with @list
|
||||
$value = $entry->head;
|
||||
unset($value->{'@id'});
|
||||
$list = array($entry->first);
|
||||
while(property_exists($entry, 'rest')) {
|
||||
$rest = $entry->rest;
|
||||
$entry = $list_map->{$rest};
|
||||
if(!property_exists($entry, 'first')) {
|
||||
throw new JsonLdException(
|
||||
'Invalid RDF list entry.',
|
||||
'jsonld.RdfError', array('bnode' => $rest));
|
||||
}
|
||||
$list[] = $entry->first;
|
||||
}
|
||||
$value->{'@list'} = $list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build default graph in subject @id order
|
||||
$output = array();
|
||||
$subjects = $default_graph->subjects;
|
||||
$ids = array_keys((array)$subjects);
|
||||
sort($ids);
|
||||
foreach($ids as $i => $id) {
|
||||
// add subject to default graph
|
||||
$subject = $subjects->{$id};
|
||||
$output[] = $subject;
|
||||
|
||||
// output named graph in subject @id order
|
||||
if(property_exists($graphs, $id)) {
|
||||
$graph = array();
|
||||
$_subjects = $graphs->{$id}->subjects;
|
||||
$_ids = array_keys((array)$_subjects);
|
||||
sort($_ids);
|
||||
foreach($_ids as $_i => $_id) {
|
||||
$graph[] = $_subjects->{$_id};
|
||||
}
|
||||
$subject->{'@graph'} = $graph;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the RDF statements found in the given JSON-LD object.
|
||||
*
|
||||
|
@ -1404,6 +1622,39 @@ class JsonLdProcessor {
|
|||
return $rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RDF statement object to a JSON-LD object.
|
||||
*
|
||||
* @param stdClass $o the RDF statement object to convert.
|
||||
*
|
||||
* @return stdClass the JSON-LD object.
|
||||
*/
|
||||
protected function _rdfToObject($o) {
|
||||
// convert empty list
|
||||
if($o->interfaceName === 'IRI' && $o->nominalValue === self::RDF_NIL) {
|
||||
return (object)array('@list' => array());
|
||||
}
|
||||
|
||||
// convert IRI/BlankNode object to JSON-LD
|
||||
if($o->interfaceName === 'IRI' || $o->interfaceName === 'BlankNode') {
|
||||
return (object)array('@id' => $o->nominalValue);
|
||||
}
|
||||
|
||||
// convert literal object to JSON-LD
|
||||
$rval = (object)array('@value' => $o->nominalValue);
|
||||
|
||||
// add datatype
|
||||
if(property_exists($o, 'datatype')) {
|
||||
$rval->{'@type'} = $o->datatype->nominalValue;
|
||||
}
|
||||
// add language
|
||||
else if(property_exists($o, 'language')) {
|
||||
$rval->{'@language'} = $o->language;
|
||||
}
|
||||
|
||||
return $rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively gets all statements from the given expanded JSON-LD input.
|
||||
*
|
||||
|
@ -3259,6 +3510,165 @@ class JsonLdProcessor {
|
|||
protected static function _isAbsoluteIri($v) {
|
||||
return strpos($v, ':') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses statements in the form of N-Quads.
|
||||
*
|
||||
* @param string $input the N-Quads input to parse.
|
||||
*
|
||||
* @return array the resulting RDF statements.
|
||||
*/
|
||||
protected 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';
|
||||
}
|
||||
else {
|
||||
$s->object->nominalValue = $match[6];
|
||||
$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
|
||||
$statements[] = $s;
|
||||
}
|
||||
|
||||
return $statements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RDF statement to an N-Quad string (a single quad).
|
||||
*
|
||||
* @param stdClass $statement the RDF statement to convert.
|
||||
*
|
||||
* @return the N-Quad string.
|
||||
*/
|
||||
protected static function _toNQuad($statement) {
|
||||
$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}>";
|
||||
}
|
||||
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') {
|
||||
$quad .= $o->nominalValue;
|
||||
}
|
||||
else {
|
||||
$quad .= '"' . $o->nominalValue . '"';
|
||||
if(propert_exists($o, 'datatype')) {
|
||||
$quad .= "^^<{$o->datatype->nominalValue}>";
|
||||
}
|
||||
else if(property_exists($o, 'language')) {
|
||||
$quad .= '@' . $o->language;
|
||||
}
|
||||
}
|
||||
|
||||
// graph
|
||||
if($g !== null) {
|
||||
$quad .= " <{$g->nominalValue}>";
|
||||
}
|
||||
|
||||
$quad .= " .\n";
|
||||
return $quad;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue