* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace ML\JsonLD; use ML\IRI\IRI; /** * A Graph represents a JSON-LD graph. * * @author Markus Lanthaler */ class Graph implements GraphInterface, JsonLdSerializable { /** * @var DocumentInterface The document this graph belongs to. */ private $document; /** * @var array An associative array holding all nodes in the graph */ protected $nodes = array(); /** * A term map containing terms/prefixes mapped to IRIs. This is similar * to a JSON-LD context but ignores all definitions except the IRI. * * @var array */ protected $termMap = array(); /** * @var int Blank node counter */ private $blankNodeCounter = 0; /** * Constructor * * @param null|DocumentInterface $document The document the graph belongs to. */ public function __construct(DocumentInterface $document = null) { $this->document = $document; } /** * {@inheritdoc} */ public function createNode($id = null, $preserveBnodeId = false) { if (!is_string($id) || (!$preserveBnodeId && ('_:' === substr($id, 0, 2)))) { $id = $this->createBlankNodeId(); } else { $id = (string) $this->resolveIri($id); if (isset($this->nodes[$id])) { return $this->nodes[$id]; } } return $this->nodes[$id] = new Node($this, $id); } /** * {@inheritdoc} */ public function removeNode(NodeInterface $node) { if ($node->getGraph() === $this) { $node->removeFromGraph(); } $id = $node->getId(); if (!$node->isBlankNode()) { $id = (string) $this->resolveIri($id); } unset($this->nodes[$id]); return $this; } /** * {@inheritdoc} */ public function getNodes() { return array_values($this->nodes); } /** * {@inheritdoc} */ public function getNode($id) { if (!((strlen($id) >= 2) && ('_:' === substr($id, 0, 2)))) { $id = (string) $this->resolveIri($id); } return isset($this->nodes[$id]) ? $this->nodes[$id] : null; } /** * {@inheritdoc} */ public function getNodesByType($type) { if (is_string($type)) { if (null === ($type = $this->getNode($type))) { return array(); } } return $type->getNodesWithThisType(); } /** * {@inheritdoc} */ public function containsNode($id) { $node = $id; if ($node instanceof Node) { $id = $node->getId(); } if ((null === $id) || !is_string($id)) { return false; } if ((strlen($id) >= 2) && ('_:' === substr($id, 0, 2))) { if (isset($this->nodes[$id]) && ($node === $this->nodes[$id])) { return true; } return false; } $id = (string) $this->resolveIri($id); return isset($this->nodes[$id]); } /** * {@inheritdoc} */ public function getDocument() { return $this->document; } /** * {@inheritdoc} */ public function removeFromDocument() { $doc = $this->document; $this->document = null; $doc->removeGraph($this); return $this; } /** * {@inheritdoc} */ public function merge(GraphInterface $graph) { $nodes = $graph->getNodes(); $bnodeMap = array(); foreach ($nodes as $node) { if ($node->isBlankNode()) { if (false === isset($bnodeMap[$node->getId()])) { $bnodeMap[$node->getId()] = $this->createNode(); } $n = $bnodeMap[$node->getId()]; } else { $n = $this->createNode($node->getId()); } foreach ($node->getProperties() as $property => $values) { if (false === is_array($values)) { $values = array($values); } foreach ($values as $val) { if ($val instanceof NodeInterface) { // If the value is another node, we just need to // create a reference to the corresponding node // in this graph. The properties will be merged // in the outer loop if ($val->isBlankNode()) { if (false === isset($bnodeMap[$val->getId()])) { $bnodeMap[$val->getId()] = $this->createNode(); } $val = $bnodeMap[$val->getId()]; } else { $val = $this->createNode($val->getId()); } } elseif (is_object($val)) { // Clone typed values and language-tagged strings $val = clone $val; } $n->addPropertyValue($property, $val); } } } return $this; } /** * {@inheritdoc} */ public function toJsonLd($useNativeTypes = true) { // Bring nodes into a deterministic order $nodes = $this->nodes; ksort($nodes); $nodes = array_values($nodes); $serializeNode = function ($node) use ($useNativeTypes) { return $node->toJsonLd($useNativeTypes); }; return array_map($serializeNode, $nodes); } /** * Create a new blank node identifier unique to the document. * * @return string The new blank node identifier. */ protected function createBlankNodeId() { return '_:b' . $this->blankNodeCounter++; } /** * Resolves an IRI against the document's IRI * * If the graph isn't attached to a document or the document's IRI is * not set, the IRI is returned as-is. * * @param string|IRI $iri The (relative) IRI to resolve * * @return IRI The resolved IRI. */ protected function resolveIri($iri) { if (null === $this->document) { $base = new IRI(); } else { $base = $this->document->getIri(true); } return $base->resolve($iri); } }