forked from friendica/php-json-ld
Fixes to comply with updates to the spec(s).
- Add @vocab support. - Convert native types in fromRDF(). - Use new canonical form for doubles in toRDF(). - Support xsd:string special cases. - Keep going if a JSON-LD exception occurs in test runner.
This commit is contained in:
parent
9b557af763
commit
ef8769fe5a
|
@ -104,6 +104,30 @@ function read_test_nquads($file, $filepath) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-encodes the given input (does not escape slashes).
|
||||||
|
*
|
||||||
|
* @param mixed $input the input to encode.
|
||||||
|
*
|
||||||
|
* @return the encoded input.
|
||||||
|
*/
|
||||||
|
function jsonld_encode($input) {
|
||||||
|
// newer PHP has a flag to avoid escaped '/'
|
||||||
|
if(defined('JSON_UNESCAPED_SLASHES')) {
|
||||||
|
$options = JSON_UNESCAPED_SLASHES;
|
||||||
|
if(defined('JSON_PRETTY_PRINT')) {
|
||||||
|
$options |= JSON_PRETTY_PRINT;
|
||||||
|
}
|
||||||
|
$json = json_encode($input, $options);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// use a simple string replacement of '\/' to '/'.
|
||||||
|
$json = str_replace('\\/', '/', json_encode($input));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
class TestRunner {
|
class TestRunner {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
// set up groups, add root group
|
// set up groups, add root group
|
||||||
|
@ -159,16 +183,8 @@ class TestRunner {
|
||||||
else {
|
else {
|
||||||
$this->failed += 1;
|
$this->failed += 1;
|
||||||
echo "FAIL$eol";
|
echo "FAIL$eol";
|
||||||
echo 'Expect: ' . print_r($expect, true) . $eol;
|
echo 'Expect: ' . jsonld_encode($expect) . $eol;
|
||||||
echo 'Result: ' . print_r($result, true) . $eol;
|
echo 'Result: ' . jsonld_encode($result) . $eol;
|
||||||
|
|
||||||
/*
|
|
||||||
$flags = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT;
|
|
||||||
echo 'JSON Expect: ' .
|
|
||||||
json_encode(json_decode(expect, $flags)) . $eol;
|
|
||||||
echo 'JSON Result: ' .
|
|
||||||
json_encode(json_decode(result, $flags)) . $eol;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +259,8 @@ class TestRunner {
|
||||||
$type = $test->{'@type'};
|
$type = $test->{'@type'};
|
||||||
$options = array(
|
$options = array(
|
||||||
'base' => 'http://json-ld.org/test-suite/tests/' . $test->input);
|
'base' => 'http://json-ld.org/test-suite/tests/' . $test->input);
|
||||||
|
|
||||||
|
try {
|
||||||
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);
|
||||||
|
@ -292,6 +310,12 @@ class TestRunner {
|
||||||
// check results
|
// check results
|
||||||
$this->check($test, $test->expect, $result);
|
$this->check($test, $test->expect, $result);
|
||||||
}
|
}
|
||||||
|
catch(JsonLdException $e) {
|
||||||
|
echo $eol . $e;
|
||||||
|
$this->failed += 1;
|
||||||
|
echo "FAIL$eol";
|
||||||
|
}
|
||||||
|
}
|
||||||
if(property_exists($manifest, 'name')) {
|
if(property_exists($manifest, 'name')) {
|
||||||
$this->ungroup();
|
$this->ungroup();
|
||||||
}
|
}
|
||||||
|
|
140
jsonld.php
140
jsonld.php
|
@ -115,7 +115,10 @@ function jsonld_normalize($input, $options=array()) {
|
||||||
* @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).
|
||||||
* [notType] true to use rdf:type, false to use @type (default).
|
* [useRdfType] true to use rdf:type, false to use @type
|
||||||
|
* (default: false).
|
||||||
|
* [useNativeTypes] true to convert XSD types into native types
|
||||||
|
* (boolean, integer, double), false not to (default: true).
|
||||||
*
|
*
|
||||||
* @return array the JSON-LD output.
|
* @return array the JSON-LD output.
|
||||||
*/
|
*/
|
||||||
|
@ -221,6 +224,7 @@ class JsonLdProcessor {
|
||||||
const XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
|
const XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
|
||||||
const XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
|
const XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
|
||||||
const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
|
const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
|
||||||
|
const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
|
||||||
|
|
||||||
/** RDF constants */
|
/** RDF constants */
|
||||||
const RDF_FIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first';
|
const RDF_FIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first';
|
||||||
|
@ -504,7 +508,10 @@ class JsonLdProcessor {
|
||||||
* @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).
|
* [useRdfType] true to use rdf:type, false to use @type
|
||||||
|
* (default: false).
|
||||||
|
* [useNativeTypes] true to convert XSD types into native types
|
||||||
|
* (boolean, integer, double), false not to (default: true).
|
||||||
*
|
*
|
||||||
* @return array the JSON-LD output.
|
* @return array the JSON-LD output.
|
||||||
*/
|
*/
|
||||||
|
@ -513,7 +520,8 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// set default options
|
// set default options
|
||||||
isset($options['format']) or $options['format'] = 'application/nquads';
|
isset($options['format']) or $options['format'] = 'application/nquads';
|
||||||
isset($options['notType']) or $options['notType'] = false;
|
isset($options['useRdfType']) or $options['useRdfType'] = false;
|
||||||
|
isset($options['useNativeTypes']) or $options['useNativeTypes'] = true;
|
||||||
|
|
||||||
if(!is_array($statements)) {
|
if(!is_array($statements)) {
|
||||||
// supported formats (processor-specific and global)
|
// supported formats (processor-specific and global)
|
||||||
|
@ -1036,7 +1044,8 @@ class JsonLdProcessor {
|
||||||
array('\\\\', '\t', '\n', '\r', '\"'),
|
array('\\\\', '\t', '\n', '\r', '\"'),
|
||||||
$o->nominalValue);
|
$o->nominalValue);
|
||||||
$quad .= '"' . $escaped . '"';
|
$quad .= '"' . $escaped . '"';
|
||||||
if(property_exists($o, 'datatype')) {
|
if(property_exists($o, 'datatype') &&
|
||||||
|
$o->datatype->nominalValue !== JsonLdProcessor::XSD_STRING) {
|
||||||
$quad .= "^^<{$o->datatype->nominalValue}>";
|
$quad .= "^^<{$o->datatype->nominalValue}>";
|
||||||
}
|
}
|
||||||
else if(property_exists($o, 'language')) {
|
else if(property_exists($o, 'language')) {
|
||||||
|
@ -1717,7 +1726,7 @@ class JsonLdProcessor {
|
||||||
$entry = $list_map->{$s};
|
$entry = $list_map->{$s};
|
||||||
}
|
}
|
||||||
// set object value
|
// set object value
|
||||||
$entry->first = $this->_rdfToObject($o);
|
$entry->first = $this->_rdfToObject($o, $options['useNativeTypes']);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1754,14 +1763,14 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to @type unless options indicate to treat rdf:type as property
|
// convert to @type unless options indicate to treat rdf:type as property
|
||||||
if($p === self::RDF_TYPE && !$options['notType']) {
|
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->nominalValue, array('propertyIsArray' => true));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// add property to value as needed
|
// add property to value as needed
|
||||||
$object = $this->_rdfToObject($o);
|
$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
|
||||||
|
@ -1858,9 +1867,9 @@ class JsonLdProcessor {
|
||||||
$datatype or $datatype = self::XSD_BOOLEAN;
|
$datatype or $datatype = self::XSD_BOOLEAN;
|
||||||
}
|
}
|
||||||
else if(is_double($value)) {
|
else if(is_double($value)) {
|
||||||
// do special JSON-LD double format, printf('%1.15e') equivalent
|
// canonical double representation
|
||||||
$value = preg_replace('/(e(?:\+|-))([0-9])$/', '${1}0${2}',
|
$value = preg_replace('/(\d)0*E\+?/', '$1E',
|
||||||
sprintf('%1.15e', $value));
|
sprintf('%1.15E', $value));
|
||||||
$datatype or $datatype = self::XSD_DOUBLE;
|
$datatype or $datatype = self::XSD_DOUBLE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1869,16 +1878,17 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default to xsd:string datatype
|
||||||
|
$datatype or $datatype = self::XSD_STRING;
|
||||||
|
|
||||||
$object = (object)array(
|
$object = (object)array(
|
||||||
'nominalValue' => $value,
|
'nominalValue' => $value,
|
||||||
'interfaceName' => 'LiteralNode');
|
'interfaceName' => 'LiteralNode',
|
||||||
|
'datatype' => (object)array(
|
||||||
if($datatype !== null) {
|
|
||||||
$object->datatype = (object)array(
|
|
||||||
'nominalValue' => $datatype,
|
'nominalValue' => $datatype,
|
||||||
'interfaceName' => 'IRI');
|
'interfaceName' => 'IRI'));
|
||||||
}
|
if(property_exists($element, '@language') &&
|
||||||
else if(property_exists($element, '@language')) {
|
$datatype === self::XSD_STRING) {
|
||||||
$object->language = $element->{'@language'};
|
$object->language = $element->{'@language'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2102,10 +2112,11 @@ class JsonLdProcessor {
|
||||||
* Converts an RDF statement object to a JSON-LD object.
|
* Converts an RDF statement object to a JSON-LD object.
|
||||||
*
|
*
|
||||||
* @param stdClass $o the RDF statement object to convert.
|
* @param stdClass $o the RDF statement object to convert.
|
||||||
|
* @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) {
|
protected function _rdfToObject($o, $use_native_types) {
|
||||||
// convert empty list
|
// convert empty list
|
||||||
if($o->interfaceName === 'IRI' && $o->nominalValue === self::RDF_NIL) {
|
if($o->interfaceName === 'IRI' && $o->nominalValue === self::RDF_NIL) {
|
||||||
return (object)array('@list' => array());
|
return (object)array('@list' => array());
|
||||||
|
@ -2121,22 +2132,39 @@ class JsonLdProcessor {
|
||||||
|
|
||||||
// add datatype
|
// add datatype
|
||||||
if(property_exists($o, 'datatype')) {
|
if(property_exists($o, 'datatype')) {
|
||||||
/*
|
|
||||||
// use native datatypes for certain xsd types
|
|
||||||
$type = $o->datatype->nominalValue;
|
$type = $o->datatype->nominalValue;
|
||||||
|
// use native types for certain xsd types
|
||||||
|
if($use_native_types) {
|
||||||
if($type === self::XSD_BOOLEAN) {
|
if($type === self::XSD_BOOLEAN) {
|
||||||
$element = !($element === 'false' || $element === '0');
|
if($rval->{'@value'} === 'true') {
|
||||||
|
$rval->{'@value'} = true;
|
||||||
|
}
|
||||||
|
else if($rval->{'@value'} === 'false') {
|
||||||
|
$rval->{'@value'} = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(is_numeric($rval->{'@value'})) {
|
||||||
|
if($type === self::XSD_INTEGER) {
|
||||||
|
$i = intval($rval->{'@value'});
|
||||||
|
if(strval($i) === $rval->{'@value'}) {
|
||||||
|
$rval->{'@value'} = $i;
|
||||||
}
|
}
|
||||||
else if($type === self::XSD_INTEGER) {
|
|
||||||
$element = intval($element);
|
|
||||||
}
|
}
|
||||||
else if($type === self::XSD_DOUBLE) {
|
else if($type === self::XSD_DOUBLE) {
|
||||||
$element = doubleval($element);
|
$rval->{'@value'} = doubleval($rval->{'@value'});
|
||||||
}*/
|
}
|
||||||
$rval->{'@type'} = $o->datatype->nominalValue;
|
}
|
||||||
|
// do not add xsd:string type
|
||||||
|
if($type !== self::XSD_STRING) {
|
||||||
|
$rval->{'@type'} = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rval->{'@type'} = $type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// add language
|
// add language
|
||||||
else if(property_exists($o, 'language')) {
|
if(property_exists($o, 'language')) {
|
||||||
$rval->{'@language'} = $o->language;
|
$rval->{'@language'} = $o->language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3069,6 +3097,20 @@ class JsonLdProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no matching terms, use @vocab if available
|
||||||
|
if(count($terms) === 0 && property_exists($ctx, '@vocab') &&
|
||||||
|
$ctx->{'@vocab'} !== null) {
|
||||||
|
// determine if vocab is a prefix of the iri
|
||||||
|
$vocab = $ctx->{'@vocab'};
|
||||||
|
if(strpos($iri, $vocab) === 0) {
|
||||||
|
// use suffix as relative iri if it is not a term in the active context
|
||||||
|
$suffix = substr($iri, strlen($vocab));
|
||||||
|
if(!property_exists($ctx->mappings, $suffix)) {
|
||||||
|
return $suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// no term matches, add possible CURIEs
|
// no term matches, add possible CURIEs
|
||||||
if(count($terms) === 0) {
|
if(count($terms) === 0) {
|
||||||
foreach($ctx->mappings as $term => $entry) {
|
foreach($ctx->mappings as $term => $entry) {
|
||||||
|
@ -3143,6 +3185,30 @@ class JsonLdProcessor {
|
||||||
$value = $ctx->{$key};
|
$value = $ctx->{$key};
|
||||||
|
|
||||||
if(self::_isKeyword($key)) {
|
if(self::_isKeyword($key)) {
|
||||||
|
// support vocab
|
||||||
|
if($key === '@vocab') {
|
||||||
|
if($value !== null && !is_string($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
||||||
|
'@context must be a string or null.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $ctx));
|
||||||
|
}
|
||||||
|
if(!self::_isAbsoluteIri($value)) {
|
||||||
|
throw new JsonLdException(
|
||||||
|
'Invalid JSON-LD syntax; the value of "@vocab" in a ' +
|
||||||
|
'@context must be an absolute IRI.',
|
||||||
|
'jsonld.SyntaxError', array('context' => $ctx));
|
||||||
|
}
|
||||||
|
if($value === null) {
|
||||||
|
unset($active_ctx->{'@vocab'});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$active_ctx->{'@vocab'} = $value;
|
||||||
|
}
|
||||||
|
$defined->{$key} = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// only @language is permitted
|
// only @language is permitted
|
||||||
if($key !== '@language') {
|
if($key !== '@language') {
|
||||||
throw new JsonLdException(
|
throw new JsonLdException(
|
||||||
|
@ -3379,8 +3445,14 @@ class JsonLdProcessor {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepend vocab
|
||||||
|
if(property_exists($ctx, '@vocab') && $ctx->{'@vocab'} !== null) {
|
||||||
|
$value = $this->_prependBase($ctx->{'@vocab'}, $value);
|
||||||
|
}
|
||||||
// prepend base
|
// prepend base
|
||||||
|
else {
|
||||||
$value = $this->_prependBase($base, $value);
|
$value = $this->_prependBase($base, $value);
|
||||||
|
}
|
||||||
|
|
||||||
// value must now be an absolute IRI
|
// value must now be an absolute IRI
|
||||||
if(!self::_isAbsoluteIri($value)) {
|
if(!self::_isAbsoluteIri($value)) {
|
||||||
|
@ -3444,8 +3516,16 @@ class JsonLdProcessor {
|
||||||
return $term;
|
return $term;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use vocab
|
||||||
|
if(property_exists($ctx, '@vocab') && $ctx->{'@vocab'} !== null) {
|
||||||
|
$term = $this->_prependBase($ctx->{'@vocab'}, $term);
|
||||||
|
}
|
||||||
// prepend base to term
|
// prepend base to term
|
||||||
return $this->_prependBase($base, $term);
|
else {
|
||||||
|
$term = $this->_prependBase($base, $term);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $term;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3658,7 +3738,8 @@ class JsonLdProcessor {
|
||||||
'@preserve' => array(),
|
'@preserve' => array(),
|
||||||
'@set' => array(),
|
'@set' => array(),
|
||||||
'@type' => array(),
|
'@type' => array(),
|
||||||
'@value'=> array()
|
'@value' => array(),
|
||||||
|
'@vocab' => array()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3765,6 +3846,7 @@ class JsonLdProcessor {
|
||||||
case '@set':
|
case '@set':
|
||||||
case '@type':
|
case '@type':
|
||||||
case '@value':
|
case '@value':
|
||||||
|
case '@vocab':
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue