|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* PHP implementation of the JSON-LD API.
|
|
|
|
* Version: 0.0.34
|
|
|
|
*
|
|
|
|
* @author Dave Longley
|
|
|
|
*
|
|
|
|
* BSD 3-Clause License
|
|
|
|
* Copyright (c) 2011-2013 Digital Bazaar, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* Neither the name of the Digital Bazaar, Inc. nor the names of its
|
|
|
|
* contributors may be used to endorse or promote products derived from
|
|
|
|
* this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
|
|
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD compaction.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to compact.
|
|
|
|
* @param mixed $ctx the context to compact with.
|
|
|
|
* @param assoc [$options] options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [strict] use strict mode (default: true).
|
|
|
|
* [graph] true to always output a top-level graph (default: false).
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return mixed the compacted JSON-LD output.
|
|
|
|
*/
|
|
|
|
function jsonld_compact($input, $ctx, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->compact($input, $ctx, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD expansion.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to expand.
|
|
|
|
* @param assoc[$options] the options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return array the expanded JSON-LD output.
|
|
|
|
*/
|
|
|
|
function jsonld_expand($input, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->expand($input, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD flattening.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD to flatten.
|
|
|
|
* @param mixed $ctx the context to use to compact the flattened output, or
|
|
|
|
* null.
|
|
|
|
* @param [options] the options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return mixed the flattened JSON-LD output.
|
|
|
|
*/
|
|
|
|
function jsonld_flatten($input, $ctx, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->flatten($input, $ctx, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD framing.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to frame.
|
|
|
|
* @param stdClass $frame the JSON-LD frame to use.
|
|
|
|
* @param assoc [$options] the framing options.
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [embed] default @embed flag (default: true).
|
|
|
|
* [explicit] default @explicit flag (default: false).
|
|
|
|
* [omitDefault] default @omitDefault flag (default: false).
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return stdClass the framed JSON-LD output.
|
|
|
|
*/
|
|
|
|
function jsonld_frame($input, $frame, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->frame($input, $frame, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 assoc [$options] the options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [format] the format if output is a string:
|
|
|
|
* 'application/nquads' for N-Quads (default).
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return mixed the normalized output.
|
|
|
|
*/
|
|
|
|
function jsonld_normalize($input, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->normalize($input, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts an RDF dataset to JSON-LD.
|
|
|
|
*
|
|
|
|
* @param mixed $input a serialized string of RDF in a format specified
|
|
|
|
* by the format option or an RDF dataset to convert.
|
|
|
|
* @param assoc [$options] the options to use:
|
|
|
|
* [format] the format if input not an array:
|
|
|
|
* 'application/nquads' for N-Quads (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.
|
|
|
|
*/
|
|
|
|
function jsonld_from_rdf($input, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->fromRDF($input, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Outputs the RDF dataset found in the given JSON-LD object.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object.
|
|
|
|
* @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).
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return mixed the resulting RDF dataset (or a serialization of it).
|
|
|
|
*/
|
|
|
|
function jsonld_to_rdf($input, $options=array()) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->toRDF($input, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Relabels all blank nodes in the given JSON-LD input.
|
|
|
|
*
|
|
|
|
* @param mixed input the JSON-LD input.
|
|
|
|
*/
|
|
|
|
function jsonld_relabel_blank_nodes($input) {
|
|
|
|
$p = new JsonLdProcessor();
|
|
|
|
return $p->_labelBlankNodes(new UniqueNamer('_:b'), $input);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** JSON-LD shared in-memory cache. */
|
|
|
|
global $jsonld_cache;
|
|
|
|
$jsonld_cache = new stdClass();
|
|
|
|
|
|
|
|
/** The default active context cache. */
|
|
|
|
$jsonld_cache->activeCtx = new ActiveContextCache();
|
|
|
|
|
|
|
|
/** The default JSON-LD context loader. */
|
|
|
|
global $jsonld_default_load_context;
|
|
|
|
$jsonld_default_load_context = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the default JSON-LD context loader.
|
|
|
|
*
|
|
|
|
* @param callable load_context(url) the context loader.
|
|
|
|
*/
|
|
|
|
function jsonld_set_context_loader($load_context) {
|
|
|
|
global $jsonld_default_load_context;
|
|
|
|
$jsonld_default_load_context = $load_context;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves JSON-LD at the given URL.
|
|
|
|
*
|
|
|
|
* @param string $url the URL to retrieve.
|
|
|
|
*
|
|
|
|
* @return the JSON-LD.
|
|
|
|
*/
|
|
|
|
function jsonld_get_url($url) {
|
|
|
|
global $jsonld_default_load_context;
|
|
|
|
if($jsonld_default_load_context !== null) {
|
|
|
|
return call_user_func($jsonld_default_load_context, $url);
|
|
|
|
}
|
|
|
|
|
|
|
|
// default JSON-LD GET implementation
|
|
|
|
return jsonld_default_get_url($url);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The default implementation to retrieve JSON-LD at the given URL.
|
|
|
|
*
|
|
|
|
* @param string $url the URL to to retrieve.
|
|
|
|
*
|
|
|
|
* @return the JSON-LD.
|
|
|
|
*/
|
|
|
|
function jsonld_default_get_url($url) {
|
|
|
|
// default JSON-LD GET implementation
|
|
|
|
$opts = array(
|
|
|
|
'http' => array(
|
|
|
|
'method' => "GET",
|
|
|
|
'header' =>
|
|
|
|
"Accept: application/ld+json\r\n" .
|
|
|
|
"User-Agent: PaySwarm PHP Client/1.0\r\n"),
|
|
|
|
'https' => array(
|
|
|
|
'verify_peer' => true,
|
|
|
|
'method' => "GET",
|
|
|
|
'header' =>
|
|
|
|
"Accept: application/ld+json\r\n" .
|
|
|
|
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
|
|
|
|
$stream = stream_context_create($opts);
|
|
|
|
$result = @file_get_contents($url, false, $stream);
|
|
|
|
if($result === false) {
|
|
|
|
throw new Exception("Could not GET url: '$url'");
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The default implementation to retrieve JSON-LD at the given secure URL.
|
|
|
|
*
|
|
|
|
* @param string $url the secure URL to to retrieve.
|
|
|
|
*
|
|
|
|
* @return the JSON-LD.
|
|
|
|
*/
|
|
|
|
function jsonld_default_get_secure_url($url) {
|
|
|
|
if(strpos($url, 'https') !== 0) {
|
|
|
|
throw new Exception("Could not GET url: '$url'; 'https' is required.");
|
|
|
|
}
|
|
|
|
|
|
|
|
$redirects = array();
|
|
|
|
|
|
|
|
// default JSON-LD https GET implementation
|
|
|
|
$opts = array(
|
|
|
|
'https' => array(
|
|
|
|
'verify_peer' => true,
|
|
|
|
'method' => "GET",
|
|
|
|
'header' =>
|
|
|
|
"Accept: application/ld+json\r\n" .
|
|
|
|
"User-Agent: PaySwarm PHP Client/1.0\r\n"));
|
|
|
|
$stream = stream_context_create($opts);
|
|
|
|
stream_context_set_params($stream, array('notification' =>
|
|
|
|
function($notification_code, $severity, $message) use (&$redirects) {
|
|
|
|
switch($notification_code) {
|
|
|
|
case STREAM_NOTIFY_REDIRECTED:
|
|
|
|
$redirects[] = $message;
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
}));
|
|
|
|
$result = @file_get_contents($url, false, $stream);
|
|
|
|
if($result === false) {
|
|
|
|
throw new Exception("Could not GET url: '$url'");
|
|
|
|
}
|
|
|
|
foreach($redirects as $redirect) {
|
|
|
|
if(strpos($redirect, 'https') !== 0) {
|
|
|
|
throw new Exception(
|
|
|
|
"Could not GET redirected url: '$redirect'; 'https' is required.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Registered global RDF dataset parsers hashed by content-type. */
|
|
|
|
global $jsonld_rdf_parsers;
|
|
|
|
$jsonld_rdf_parsers = new stdClass();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a global RDF dataset parser by content-type, for use with
|
|
|
|
* jsonld_from_rdf. Global parsers will be used by JsonLdProcessors that do
|
|
|
|
* not register their own parsers.
|
|
|
|
*
|
|
|
|
* @param string $content_type the content-type for the parser.
|
|
|
|
* @param callable $parser(input) the parser function (takes a string as
|
|
|
|
* a parameter and returns an RDF dataset).
|
|
|
|
*/
|
|
|
|
function jsonld_register_rdf_parser($content_type, $parser) {
|
|
|
|
global $jsonld_rdf_parsers;
|
|
|
|
$jsonld_rdf_parsers->{$content_type} = $parser;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregisters a global RDF dataset parser by content-type.
|
|
|
|
*
|
|
|
|
* @param string $content_type the content-type for the parser.
|
|
|
|
*/
|
|
|
|
function jsonld_unregister_rdf_parser($content_type) {
|
|
|
|
global $jsonld_rdf_parsers;
|
|
|
|
if(property_exists($jsonld_rdf_parsers, $content_type)) {
|
|
|
|
unset($jsonld_rdf_parsers->{$content_type});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a URL into its component parts.
|
|
|
|
*
|
|
|
|
* @param string $url the URL to parse.
|
|
|
|
*
|
|
|
|
* @return assoc the parsed URL.
|
|
|
|
*/
|
|
|
|
function jsonld_parse_url($url) {
|
|
|
|
$rval = parse_url($url);
|
|
|
|
|
|
|
|
// malformed url
|
|
|
|
if($rval === false) {
|
|
|
|
$rval = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$rval['href'] = $url;
|
|
|
|
if(!isset($rval['scheme'])) {
|
|
|
|
$rval['scheme'] = '';
|
|
|
|
$rval['protocol'] = '';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$rval['protocol'] = $rval['scheme'] . ':';
|
|
|
|
}
|
|
|
|
if(!isset($rval['host'])) {
|
|
|
|
$rval['host'] = '';
|
|
|
|
}
|
|
|
|
if(!isset($rval['path'])) {
|
|
|
|
$rval['path'] = '';
|
|
|
|
}
|
|
|
|
if(isset($rval['user']) || isset($rval['pass'])) {
|
|
|
|
$rval['auth'] = '';
|
|
|
|
if(isset($rval['user'])) {
|
|
|
|
$rval['auth'] = $rval['user'];
|
|
|
|
}
|
|
|
|
if(isset($rval['pass'])) {
|
|
|
|
$rval['auth'] .= ":{$rval['pass']}";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// parse authority for unparsed relative network-path reference
|
|
|
|
if(strpos($rval['href'], ':') === false &&
|
|
|
|
strpos($rval['href'], '//') === 0 && $rval['host'] === '') {
|
|
|
|
// must parse authority from pathname
|
|
|
|
$rval['path'] = substr($rval['path'], 2);
|
|
|
|
$idx = strpos($rval['path'], '/');
|
|
|
|
if($idx === false) {
|
|
|
|
$rval['authority'] = $rval['path'];
|
|
|
|
$rval['path'] = '';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$rval['authority'] = substr($rval['path'], 0, $idx);
|
|
|
|
$rval['path'] = substr($rval['path'], $idx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$rval['authority'] = $rval['host'];
|
|
|
|
if(isset($rval['port'])) {
|
|
|
|
$rval['authority'] .= ":{$rval['port']}";
|
|
|
|
}
|
|
|
|
if(isset($rval['auth'])) {
|
|
|
|
$rval['authority'] = "{$rval['auth']}@{$rval['authority']}";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$rval['normalizedPath'] = jsonld_remove_dot_segments(
|
|
|
|
$rval['path'], $rval['authority'] !== '');
|
|
|
|
|
|
|
|
return $rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes dot segments from a URL path.
|
|
|
|
*
|
|
|
|
* @param string $path the path to remove dot segments from.
|
|
|
|
* @param bool $has_authority true if the URL has an authority, false if not.
|
|
|
|
*/
|
|
|
|
function jsonld_remove_dot_segments($path, $has_authority) {
|
|
|
|
$rval = '';
|
|
|
|
|
|
|
|
if(strpos($path, '/') === 0) {
|
|
|
|
$rval = '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
// RFC 3986 5.2.4 (reworked)
|
|
|
|
$input = explode('/', $path);
|
|
|
|
$output = array();
|
|
|
|
while(count($input) > 0) {
|
|
|
|
if($input[0] === '.' || ($input[0] === '' && count($input) > 1)) {
|
|
|
|
array_shift($input);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if($input[0] === '..') {
|
|
|
|
array_shift($input);
|
|
|
|
if($has_authority ||
|
|
|
|
(count($output) > 0 && $output[count($output) - 1] !== '..')) {
|
|
|
|
array_pop($output);
|
|
|
|
}
|
|
|
|
// leading relative URL '..'
|
|
|
|
else {
|
|
|
|
$output[] = '..';
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$output[] = array_shift($input);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rval . implode('/', $output);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepends a base IRI to the given relative IRI.
|
|
|
|
*
|
|
|
|
* @param mixed $base a string or the parsed base IRI.
|
|
|
|
* @param string $iri the relative IRI.
|
|
|
|
*
|
|
|
|
* @return string the absolute IRI.
|
|
|
|
*/
|
|
|
|
function jsonld_prepend_base($base, $iri) {
|
|
|
|
// already an absolute IRI
|
|
|
|
if(strpos($iri, ':') !== false) {
|
|
|
|
return $iri;
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse base if it is a string
|
|
|
|
if(is_string($base)) {
|
|
|
|
$base = jsonld_parse_url($base);
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse given IRI
|
|
|
|
$rel = jsonld_parse_url($iri);
|
|
|
|
|
|
|
|
// start hierarchical part
|
|
|
|
$hierPart = $base['protocol'];
|
|
|
|
if($rel['authority']) {
|
|
|
|
$hierPart .= "//{$rel['authority']}";
|
|
|
|
}
|
|
|
|
else if($base['href'] !== '') {
|
|
|
|
$hierPart .= "//{$base['authority']}";
|
|
|
|
}
|
|
|
|
|
|
|
|
// per RFC3986 normalize
|
|
|
|
|
|
|
|
// IRI represents an absolute path
|
|
|
|
if(strpos($rel['path'], '/') === 0) {
|
|
|
|
$path = $rel['path'];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$path = $base['path'];
|
|
|
|
|
|
|
|
// append relative path to the end of the last directory from base
|
|
|
|
if($rel['path'] !== '') {
|
|
|
|
$idx = strrpos($path, '/');
|
|
|
|
$idx = ($idx === false) ? 0 : $idx + 1;
|
|
|
|
$path = substr($path, 0, $idx);
|
|
|
|
if(strlen($path) > 0 && substr($path, -1) !== '/') {
|
|
|
|
$path .= '/';
|
|
|
|
}
|
|
|
|
$path .= $rel['path'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove slashes and dots in path
|
|
|
|
$path = jsonld_remove_dot_segments($path, $hierPart !== '');
|
|
|
|
|
|
|
|
// add query and hash
|
|
|
|
if(isset($rel['query'])) {
|
|
|
|
$path .= "?{$rel['query']}";
|
|
|
|
}
|
|
|
|
if(isset($rel['fragment'])) {
|
|
|
|
$path .= "#{$rel['fragment']}";
|
|
|
|
}
|
|
|
|
|
|
|
|
$rval = $hierPart . $path;
|
|
|
|
|
|
|
|
if($rval === '') {
|
|
|
|
$rval = './';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a base IRI from the given absolute IRI.
|
|
|
|
*
|
|
|
|
* @param mixed $base the base IRI.
|
|
|
|
* @param string $iri the absolute IRI.
|
|
|
|
*
|
|
|
|
* @return string the relative IRI if relative to base, otherwise the absolute
|
|
|
|
* IRI.
|
|
|
|
*/
|
|
|
|
function jsonld_remove_base($base, $iri) {
|
|
|
|
if(is_string($base)) {
|
|
|
|
$base = jsonld_parse_url($base);
|
|
|
|
}
|
|
|
|
|
|
|
|
// establish base root
|
|
|
|
$root = '';
|
|
|
|
if($base['href'] !== '') {
|
|
|
|
$root .= "{$base['protocol']}//{$base['authority']}";
|
|
|
|
}
|
|
|
|
// support network-path reference with empty base
|
|
|
|
else if(strpos($iri, '//') === false) {
|
|
|
|
$root .= '//';
|
|
|
|
}
|
|
|
|
|
|
|
|
// IRI not relative to base
|
|
|
|
if($root === '' || strpos($iri, $root) !== 0) {
|
|
|
|
return $iri;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove root from IRI
|
|
|
|
$rel = jsonld_parse_url(substr($iri, strlen($root)));
|
|
|
|
|
|
|
|
// remove path segments that match
|
|
|
|
$base_segments = explode('/', $base['normalizedPath']);
|
|
|
|
$iri_segments = explode('/', $rel['normalizedPath']);
|
|
|
|
while(count($base_segments) > 0 && count($iri_segments) > 0) {
|
|
|
|
if($base_segments[0] !== $iri_segments[0]) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
array_shift($base_segments);
|
|
|
|
array_shift($iri_segments);
|
|
|
|
}
|
|
|
|
|
|
|
|
// use '../' for each non-matching base segment
|
|
|
|
$rval = '';
|
|
|
|
if(count($base_segments) > 0) {
|
|
|
|
// don't count the last segment if it isn't a path (doesn't end in '/')
|
|
|
|
// don't count empty first segment, it means base began with '/'
|
|
|
|
if(substr($base['normalizedPath'], -1) !== '/' ||
|
|
|
|
$base_segments[0] === '') {
|
|
|
|
array_pop($base_segments);
|
|
|
|
}
|
|
|
|
foreach($base_segments as $segment) {
|
|
|
|
$rval .= '../';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepend remaining segments
|
|
|
|
$rval .= implode('/', $iri_segments);
|
|
|
|
|
|
|
|
// add query and hash
|
|
|
|
if(isset($rel['query'])) {
|
|
|
|
$rval .= "?{$rel['query']}";
|
|
|
|
}
|
|
|
|
if(isset($rel['fragment'])) {
|
|
|
|
$rval .= "#{$rel['fragment']}";
|
|
|
|
}
|
|
|
|
|
|
|
|
if($rval === '') {
|
|
|
|
$rval = './';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A JSON-LD processor.
|
|
|
|
*/
|
|
|
|
class JsonLdProcessor {
|
|
|
|
/** XSD constants */
|
|
|
|
const XSD_BOOLEAN = 'http://www.w3.org/2001/XMLSchema#boolean';
|
|
|
|
const XSD_DOUBLE = 'http://www.w3.org/2001/XMLSchema#double';
|
|
|
|
const XSD_INTEGER = 'http://www.w3.org/2001/XMLSchema#integer';
|
|
|
|
const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
|
|
|
|
|
|
|
|
/** RDF constants */
|
|
|
|
const RDF_FIRST = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first';
|
|
|
|
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_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 */
|
|
|
|
const MAX_CONTEXT_URLS = 10;
|
|
|
|
|
|
|
|
/** Processor-specific RDF dataset parsers. */
|
|
|
|
protected $rdfParsers = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a JSON-LD processor.
|
|
|
|
*/
|
|
|
|
public function __construct() {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD compaction.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to compact.
|
|
|
|
* @param mixed $ctx the context to compact with.
|
|
|
|
* @param assoc $options the compaction options.
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [strict] use strict mode (default: true).
|
|
|
|
* [compactArrays] true to compact arrays to single values when
|
|
|
|
* appropriate, false not to (default: true).
|
|
|
|
* [graph] true to always output a top-level graph (default: false).
|
|
|
|
* [skipExpansion] true to assume the input is expanded and skip
|
|
|
|
* expansion, false not to, defaults to false.
|
|
|
|
* [activeCtx] true to also return the active context used.
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return mixed the compacted JSON-LD output.
|
|
|
|
*/
|
|
|
|
public function compact($input, $ctx, $options) {
|
|
|
|
if($ctx === null) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'The compaction context must not be null.',
|
|
|
|
'jsonld.CompactError');
|
|
|
|
}
|
|
|
|
|
|
|
|
// nothing to compact
|
|
|
|
if($input === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set default options
|
|
|
|
isset($options['base']) or $options['base'] = '';
|
|
|
|
isset($options['strict']) or $options['strict'] = true;
|
|
|
|
isset($options['compactArrays']) or $options['compactArrays'] = true;
|
|
|
|
isset($options['graph']) or $options['graph'] = false;
|
|
|
|
isset($options['skipExpansion']) or $options['skipExpansion'] = false;
|
|
|
|
isset($options['activeCtx']) or $options['activeCtx'] = false;
|
|
|
|
isset($options['loadContext']) or $options['loadContext'] =
|
|
|
|
'jsonld_get_url';
|
|
|
|
|
|
|
|
if($options['skipExpansion'] === true) {
|
|
|
|
$expanded = $input;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// expand input
|
|
|
|
try {
|
|
|
|
$expanded = $this->expand($input, $options);
|
|
|
|
}
|
|
|
|
catch(JsonLdException $e) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'Could not expand input before compaction.',
|
|
|
|
'jsonld.CompactError', null, $e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// process context
|
|
|
|
$active_ctx = $this->_getInitialContext($options);
|
|
|
|
try {
|
|
|
|
$active_ctx = $this->processContext($active_ctx, $ctx, $options);
|
|
|
|
}
|
|
|
|
catch(JsonLdException $e) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'Could not process context before compaction.',
|
|
|
|
'jsonld.CompactError', null, $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// do compaction
|
|
|
|
$compacted = $this->_compact($active_ctx, null, $expanded, $options);
|
|
|
|
|
|
|
|
if($options['compactArrays'] &&
|
|
|
|
!$options['graph'] && is_array($compacted)) {
|
|
|
|
// simplify to a single item
|
|
|
|
if(count($compacted) === 1) {
|
|
|
|
$compacted = $compacted[0];
|
|
|
|
}
|
|
|
|
// simplify to an empty object
|
|
|
|
else if(count($compacted) === 0) {
|
|
|
|
$compacted = new stdClass();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// always use array if graph option is on
|
|
|
|
else if($options['graph']) {
|
|
|
|
$compacted = self::arrayify($compacted);
|
|
|
|
}
|
|
|
|
|
|
|
|
// follow @context key
|
|
|
|
if(is_object($ctx) && property_exists($ctx, '@context')) {
|
|
|
|
$ctx = $ctx->{'@context'};
|
|
|
|
}
|
|
|
|
|
|
|
|
// build output context
|
|
|
|
$ctx = self::copy($ctx);
|
|
|
|
$ctx = self::arrayify($ctx);
|
|
|
|
|
|
|
|
// remove empty contexts
|
|
|
|
$tmp = $ctx;
|
|
|
|
$ctx = array();
|
|
|
|
foreach($tmp as $v) {
|
|
|
|
if(!is_object($v) || count(array_keys((array)$v)) > 0) {
|
|
|
|
$ctx[] = $v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove array if only one context
|
|
|
|
$ctx_length = count($ctx);
|
|
|
|
$has_context = ($ctx_length > 0);
|
|
|
|
if($ctx_length === 1) {
|
|
|
|
$ctx = $ctx[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
// add context and/or @graph
|
|
|
|
if(is_array($compacted)) {
|
|
|
|
// use '@graph' keyword
|
|
|
|
$kwgraph = $this->_compactIri($active_ctx, '@graph');
|
|
|
|
$graph = $compacted;
|
|
|
|
$compacted = new stdClass();
|
|
|
|
if($has_context) {
|
|
|
|
$compacted->{'@context'} = $ctx;
|
|
|
|
}
|
|
|
|
$compacted->{$kwgraph} = $graph;
|
|
|
|
}
|
|
|
|
else if(is_object($compacted) && $has_context) {
|
|
|
|
// reorder keys so @context is first
|
|
|
|
$graph = $compacted;
|
|
|
|
$compacted = new stdClass();
|
|
|
|
$compacted->{'@context'} = $ctx;
|
|
|
|
foreach($graph as $k => $v) {
|
|
|
|
$compacted->{$k} = $v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if($options['activeCtx']) {
|
|
|
|
return array('compacted' => $compacted, 'activeCtx' => $active_ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $compacted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD expansion.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to expand.
|
|
|
|
* @param assoc $options the options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [keepFreeFloatingNodes] true to keep free-floating nodes,
|
|
|
|
* false not to, defaults to false.
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return array the expanded JSON-LD output.
|
|
|
|
*/
|
|
|
|
public function expand($input, $options) {
|
|
|
|
// set default options
|
|
|
|
isset($options['base']) or $options['base'] = '';
|
|
|
|
isset($options['keepFreeFloatingNodes']) or
|
|
|
|
$options['keepFreeFloatingNodes'] = false;
|
|
|
|
isset($options['loadContext']) or $options['loadContext'] =
|
|
|
|
'jsonld_get_url';
|
|
|
|
|
|
|
|
// retrieve all @context URLs in the input
|
|
|
|
$input = self::copy($input);
|
|
|
|
try {
|
|
|
|
$this->_retrieveContextUrls(
|
|
|
|
$input, new stdClass(), $options['loadContext'], $options['base']);
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'Could not perform JSON-LD expansion.',
|
|
|
|
'jsonld.ExpandError', null, $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// do expansion
|
|
|
|
$active_ctx = $this->_getInitialContext($options);
|
|
|
|
$expanded = $this->_expand($active_ctx, null, $input, $options, false);
|
|
|
|
|
|
|
|
// optimize away @graph with no other properties
|
|
|
|
if(is_object($expanded) && property_exists($expanded, '@graph') &&
|
|
|
|
count(array_keys((array)$expanded)) === 1) {
|
|
|
|
$expanded = $expanded->{'@graph'};
|
|
|
|
}
|
|
|
|
else if($expanded === null) {
|
|
|
|
$expanded = array();
|
|
|
|
}
|
|
|
|
// normalize to an array
|
|
|
|
return self::arrayify($expanded);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD flattening.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD to flatten.
|
|
|
|
* @param ctx the context to use to compact the flattened output, or null.
|
|
|
|
* @param assoc $options the options to use:
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return array the flattened output.
|
|
|
|
*/
|
|
|
|
public function flatten($input, $ctx, $options) {
|
|
|
|
// set default options
|
|
|
|
isset($options['base']) or $options['base'] = '';
|
|
|
|
isset($options['loadContext']) or $options['loadContext'] =
|
|
|
|
'jsonld_get_url';
|
|
|
|
|
|
|
|
try {
|
|
|
|
// expand input
|
|
|
|
$expanded = $this->expand($input, $options);
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'Could not expand input before flattening.',
|
|
|
|
'jsonld.FlattenError', null, $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// do flattening
|
|
|
|
$flattened = $this->_flatten($expanded);
|
|
|
|
|
|
|
|
if($ctx === null) {
|
|
|
|
return $flattened;
|
|
|
|
}
|
|
|
|
|
|
|
|
// compact result (force @graph option to true, skip expansion)
|
|
|
|
$options['graph'] = true;
|
|
|
|
$options['skipExpansion'] = true;
|
|
|
|
try {
|
|
|
|
$compacted = $this->compact($flattened, $ctx, $options);
|
|
|
|
}
|
|
|
|
catch(Exception $e) {
|
|
|
|
throw new JsonLdException(
|
|
|
|
'Could not compact flattened output.',
|
|
|
|
'jsonld.FlattenError', null, $e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $compacted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs JSON-LD framing.
|
|
|
|
*
|
|
|
|
* @param mixed $input the JSON-LD object to frame.
|
|
|
|
* @param stdClass $frame the JSON-LD frame to use.
|
|
|
|
* @param $options the framing options.
|
|
|
|
* [base] the base IRI to use.
|
|
|
|
* [embed] default @embed flag (default: true).
|
|
|
|
* [explicit] default @explicit flag (default: false).
|
|
|
|
* [omitDefault] default @omitDefault flag (default: false).
|
|
|
|
* [loadContext(url)] the context loader.
|
|
|
|
*
|
|
|
|
* @return stdClass the framed JSON-LD output.
|
|
|
|
*/
|
|
|
|
public function frame($input, $frame, $options) {
|
|
|
|
// set default options
|
|
|
|
isset($options['base']) or $options['base'] = '';
|
|
|
|
isset($options['compactArrays']) or $options['compactArrays'] = true;
|
|
|