/** * Loads a remote document or context * * @param string $url The URL of the document to load. * * @return RemoteDocument The remote document. * * @throws JsonLdException If loading the document failed. */ public static function loadDocument($url) { // if input looks like a file, try to retrieve it $input = trim($url); if (false == (isset($input[0]) && "{" === $input[0] || "[" === $input[0])) { $remoteDocument = new RemoteDocument($url); $streamContextOptions = array('method' => 'GET', 'header' => "Accept: application/ld+json, application/json; q=0.9, */*; q=0.1\r\n", 'timeout' => Processor::REMOTE_TIMEOUT); $context = stream_context_create(array('http' => $streamContextOptions, 'https' => $streamContextOptions)); $httpHeadersOffset = 0; stream_context_set_params($context, array('notification' => function ($code, $severity, $msg, $msgCode, $bytesTx, $bytesMax) use(&$remoteDocument, &$http_response_header, &$httpHeadersOffset) { if ($code === STREAM_NOTIFY_MIME_TYPE_IS) { $remoteDocument->mediaType = $msg; } elseif ($code === STREAM_NOTIFY_REDIRECTED) { $remoteDocument->documentUrl = $msg; $remoteDocument->mediaType = null; $httpHeadersOffset = count($http_response_header); } })); if (false === ($input = @file_get_contents($url, false, $context))) { throw new JsonLdException(JsonLdException::LOADING_DOCUMENT_FAILED, sprintf('Unable to load the remote document "%s".', $url), $http_response_header); } // Extract HTTP Link headers $linkHeaderValues = array(); for ($i = count($http_response_header) - 1; $i > $httpHeadersOffset; $i--) { if (0 === substr_compare($http_response_header[$i], 'Link:', 0, 5, true)) { $value = substr($http_response_header[$i], 5); $linkHeaderValues[] = $value; } } $linkHeaderValues = self::parseContextLinkHeaders($linkHeaderValues, new IRI($url)); if (count($linkHeaderValues) === 1) { $remoteDocument->contextUrl = reset($linkHeaderValues); } elseif (count($linkHeaderValues) > 1) { throw new JsonLdException(JsonLdException::MULTIPLE_CONTEXT_LINK_HEADERS, 'Found multiple contexts in HTTP Link headers', $http_response_header); } // If we got a media type, we verify it if ($remoteDocument->mediaType) { // Drop any media type parameters such as profiles if (false !== ($pos = strpos($remoteDocument->mediaType, ';'))) { $remoteDocument->mediaType = substr($remoteDocument->mediaType, 0, $pos); } $remoteDocument->mediaType = trim($remoteDocument->mediaType); if ('application/ld+json' === $remoteDocument->mediaType) { $remoteDocument->contextUrl = null; } elseif ('application/json' !== $remoteDocument->mediaType && 0 !== substr_compare($remoteDocument->mediaType, '+json', -5)) { throw new JsonLdException(JsonLdException::LOADING_DOCUMENT_FAILED, 'Invalid media type', $remoteDocument->mediaType); } } $remoteDocument->document = Processor::parse($input); return $remoteDocument; } return new RemoteDocument($url, Processor::parse($input)); }
/** * Tests the serialization of graphs */ public function testSerializeGraph() { // This is the expanded and flattened version of the test document // (the blank node labels have been renamed from _:t... to _:b...) $expected = Processor::loadDocument('[{ "@id": "_:b0", "http://vocab.com/nested": [{ "@value": "1.1" }] }, { "@id": "_:b1", "http://vocab.com/nested": [{ "@value": "2.1" }] }, { "@id": "_:b2", "http://vocab.com/nested": [{ "@value": "2.2" }] }, { "@id": "_:b3", "http://vocab.com/nested": [{ "@value": "3.1" }] }, { "@id": "http://example.com/node/1", "@type": ["http://vocab.com/type/node"], "http://vocab.com/contains": [{ "@id": "_:b0" }], "http://vocab.com/link": [{ "@id": "http://example.com/node/2" }], "http://vocab.com/name": [{ "@value": "1" }] }, { "@id": "http://example.com/node/2", "@type": ["http://vocab.com/type/nodeWithAliases"], "http://vocab.com/aliases": [{ "@value": "node2" }, { "@value": 2, "@type": "http://www.w3.org/2001/XMLSchema#integer" }], "http://vocab.com/contains": [{ "@id": "_:b1" }, { "@id": "_:b2" }], "http://vocab.com/lang": [{ "@language": "en", "@value": "language-tagged string" }], "http://vocab.com/link": [{ "@id": "http://example.com/node/3" }], "http://vocab.com/name": [{ "@value": "2" }], "http://vocab.com/typed": [{ "@type": "http://vocab.com/type/datatype", "@value": "typed value" }] }, { "@id": "http://example.com/node/3", "@type": ["http://vocab.com/type/node"], "http://vocab.com/contains": [{ "@id": "_:b3" }], "http://vocab.com/lang": [{ "@language": "en", "@value": "language-tagged string: en" }, { "@language": "de", "@value": "language-tagged string: de" }], "http://vocab.com/link": [{ "@id": "http://example.com/node/1" }], "http://vocab.com/name": [{ "@value": "3" }], "http://vocab.com/typed": [{ "@type": "http://vocab.com/type/datatype", "@value": "typed value" }, { "@language": "ex:/type/otherDataType", "@value": "typed value" }, { "@language": "ex:/type/datatype", "@value": "typed value" }] }, { "@id": "http://vocab.com/type/node" }, { "@id": "http://vocab.com/type/nodeWithAliases" }]'); $this->assertEquals($expected, $this->graph->toJsonLd(false), 'Serialize graph'); }
/** * Frames a JSON-LD document according a supplied frame * * @param array|object $element A JSON-LD element to be framed. * @param mixed $frame The frame. * * @return array $result The framed element in expanded form. * * @throws JsonLdException */ public function frame($element, $frame) { if (false === is_array($frame) || 1 !== count($frame) || false === is_object($frame[0])) { throw new JsonLdException(JsonLdException::UNSPECIFIED, 'The frame is invalid. It must be a single object.', $frame); } $frame = $frame[0]; $options = new Object(); $options->{'@embed'} = true; $options->{'@embedChildren'} = true; // TODO Change this as soon as the tests haven been updated foreach (self::$framingKeywords as $keyword) { if (property_exists($frame, $keyword)) { $options->{$keyword} = $frame->{$keyword}; unset($frame->{$keyword}); } elseif (false === property_exists($options, $keyword)) { $options->{$keyword} = false; } } $procOptions = new Object(); $procOptions->base = (string) $this->baseIri; // TODO Check which base IRI to use $procOptions->compactArrays = $this->compactArrays; $procOptions->optimize = $this->optimize; $procOptions->useNativeTypes = $this->useNativeTypes; $procOptions->useRdfType = $this->useRdfType; $procOptions->produceGeneralizedRdf = $this->generalizedRdf; $procOptions->documentFactory = $this->documentFactory; $processor = new Processor($procOptions); $graph = JsonLD::MERGED_GRAPH; if (property_exists($frame, '@graph')) { $graph = JsonLD::DEFAULT_GRAPH; } $nodeMap = new Object(); $nodeMap->{'-' . $graph} = new Object(); $processor->generateNodeMap($nodeMap, $element, $graph); // Sort the node map to ensure a deterministic output // TODO Move this to a separate function as basically the same is done in flatten()? $nodeMap = (array) $nodeMap; foreach ($nodeMap as &$nodes) { $nodes = (array) $nodes; ksort($nodes); $nodes = (object) $nodes; } $nodeMap = (object) $nodeMap; unset($processor); $result = array(); foreach ($nodeMap->{'-' . $graph} as $node) { $this->nodeMatchesFrame($node, $frame, $options, $nodeMap, $graph, $result); } return $result; }
/** * Merge the passed options with the options' default values. * * @param null|array|object $options The options. * * @return object The merged options. */ private static function mergeOptions($options) { $result = (object) array('base' => null, 'expandContext' => null, 'compactArrays' => true, 'optimize' => false, 'graph' => null, 'useNativeTypes' => false, 'useRdfType' => false, 'produceGeneralizedRdf' => false, 'documentFactory' => null); if (is_array($options) || is_object($options)) { $options = (object) $options; if (isset($options->{'base'})) { if (is_string($options->{'base'})) { $result->base = new IRI($options->{'base'}); } elseif ($options->{'base'} instanceof IRI && $options->{'base'}->isAbsolute()) { $result->base = clone $options->{'base'}; } else { throw \InvalidArgumentException('The "base" option must be set to null or an absolute IRI.'); } } if (property_exists($options, 'expandContext')) { if (is_string($options->expandContext)) { $result->expandContext = Processor::loadDocument($options->expandContext); } elseif (is_object($options->expandContext)) { $result->expandContext = $options->expandContext; } if (is_object($result->expandContext) && property_exists($result->expandContext, '@context')) { $result->expandContext = $result->expandContext->{'@context'}; } } if (property_exists($options, 'compactArrays') && is_bool($options->compactArrays)) { $result->compactArrays = $options->compactArrays; } if (property_exists($options, 'optimize') && is_bool($options->optimize)) { $result->optimize = $options->optimize; } if (property_exists($options, 'graph') && is_string($options->graph)) { $result->graph = $options->graph; } if (property_exists($options, 'useNativeTypes') && is_bool($options->useNativeTypes)) { $result->useNativeTypes = $options->useNativeTypes; } if (property_exists($options, 'useRdfType') && is_bool($options->useRdfType)) { $result->useRdfType = $options->useRdfType; } if (property_exists($options, 'produceGeneralizedRdf') && is_bool($options->produceGeneralizedRdf)) { $result->produceGeneralizedRdf = $options->produceGeneralizedRdf; } if (property_exists($options, 'documentFactory') && $options->documentFactory instanceof DocumentFactoryInterface) { $result->documentFactory = $options->documentFactory; } } return $result; }