8 * Copyright (c) 2009-2013 Nicholas J Humfrey. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
18 * promote products derived from this software without specific prior
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
34 * @copyright Copyright (c) 2009-2013 Nicholas J Humfrey
35 * @license http://www.opensource.org/licenses/bsd-license.php
39 * Container for collection of EasyRdf_Resources.
42 * @copyright Copyright (c) 2009-2013 Nicholas J Humfrey
43 * @license http://www.opensource.org/licenses/bsd-license.php
47 /** The URI of the graph */
49 private $parsedUri = null;
51 /** Array of resources contained in the graph */
52 private $resources = array();
54 private $index = array();
55 private $revIndex = array();
57 /** Counter for the number of bnodes */
58 private $bNodeCount = 0;
60 /** Array of URLs that have been loaded into the graph */
61 private $loaded = array();
63 private $maxRedirects = 10;
69 * If no URI is given then an unnamed graph is created.
71 * The $data parameter is optional and will be parsed into
74 * The data format is optional and should be specified if it
75 * can't be guessed by EasyRdf.
77 * @param string $uri The URI of the graph
78 * @param string $data Data for the graph
79 * @param string $format The document type of the data (e.g. rdfxml)
80 * @return object EasyRdf_Graph
82 public function __construct($uri = null, $data = null, $format = null)
84 $this->checkResourceParam($uri, true);
88 $this->parsedUri = new EasyRdf_ParsedUri($uri);
90 $this->parse($data, $format, $this->uri);
96 * Create a new graph and load RDF data from a URI into it
98 * This static function is shorthand for:
99 * $graph = new EasyRdf_Graph($uri);
100 * $graph->load($uri, $format);
102 * The document type is optional but should be specified if it
103 * can't be guessed or got from the HTTP headers.
105 * @param string $uri The URI of the data to load
106 * @param string $format Optional format of the data (eg. rdfxml)
107 * @return object EasyRdf_Graph The new the graph object
109 public static function newAndLoad($uri, $format = null)
111 $graph = new self($uri);
112 $graph->load($uri, $format);
116 /** Get or create a resource stored in a graph
118 * If the resource did not previously exist, then a new resource will
119 * be created. If you provide an RDF type and that type is registered
120 * with the EasyRdf_TypeMapper, then the resource will be an instance
121 * of the class registered.
123 * If URI is null, then the URI of the graph is used.
125 * @param string $uri The URI of the resource
126 * @param mixed $types RDF type of a new resource (e.g. foaf:Person)
127 * @return object EasyRdf_Resource
129 public function resource($uri = null, $types = array())
131 $this->checkResourceParam($uri, true);
133 throw new InvalidArgumentException(
134 '$uri is null and EasyRdf_Graph object has no URI either.'
138 // Resolve relative URIs
139 if ($this->parsedUri) {
140 $uri = $this->parsedUri->resolve($uri)->toString();
144 $this->addType($uri, $types);
146 // Create resource object if it doesn't already exist
147 if (!isset($this->resources[$uri])) {
148 $resClass = $this->classForResource($uri);
149 $this->resources[$uri] = new $resClass($uri, $this);
152 return $this->resources[$uri];
155 /** Work out the class to instantiate a resource as
158 protected function classForResource($uri)
160 $rdfType = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
161 if (isset($this->index[$uri][$rdfType])) {
162 foreach ($this->index[$uri][$rdfType] as $type) {
163 if ($type['type'] == 'uri' or $type['type'] == 'bnode') {
164 $class = EasyRdf_TypeMapper::get($type['value']);
165 if ($class != null) {
172 // Parsers don't typically add a rdf:type to rdf:List, so we have to
173 // do a bit of 'inference' here using properties.
174 if ($uri == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil' or
175 isset($this->index[$uri]['http://www.w3.org/1999/02/22-rdf-syntax-ns#first']) or
176 isset($this->index[$uri]['http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'])
178 return 'EasyRdf_Collection';
180 return 'EasyRdf_Resource';
184 * Create a new blank node in the graph and return it.
186 * If you provide an RDF type and that type is registered
187 * with the EasyRdf_TypeMapper, then the resource will be an instance
188 * of the class registered.
190 * @param mixed $types RDF type of a new blank node (e.g. foaf:Person)
191 * @return object EasyRdf_Resource The new blank node
193 public function newBNode($types = array())
195 return $this->resource($this->newBNodeId(), $types);
199 * Create a new unique blank node identifier and return it.
201 * @return string The new blank node identifier (e.g. _:genid1)
203 public function newBNodeId()
205 return "_:genid".(++$this->bNodeCount);
209 * Parse some RDF data into the graph object.
211 * @param string $data Data to parse for the graph
212 * @param string $format Optional format of the data
213 * @param string $uri The URI of the data to load
214 * @return integer The number of triples added to the graph
216 public function parse($data, $format = null, $uri = null)
218 $this->checkResourceParam($uri, true);
220 if (empty($format) or $format == 'guess') {
221 // Guess the format if it is Unknown
222 $format = EasyRdf_Format::guessFormat($data, $uri);
224 $format = EasyRdf_Format::getFormat($format);
228 throw new EasyRdf_Exception(
229 "Unable to parse data of an unknown format."
233 $parser = $format->newParser();
234 return $parser->parse($this, $data, $format, $uri);
238 * Parse a file containing RDF data into the graph object.
240 * @param string $filename The path of the file to load
241 * @param string $format Optional format of the file
242 * @param string $uri The URI of the file to load
243 * @return integer The number of triples added to the graph
245 public function parseFile($filename, $format = null, $uri = null)
248 $uri = "file://$filename";
252 file_get_contents($filename),
259 * Load RDF data into the graph from a URI.
261 * If no URI is given, then the URI of the graph will be used.
263 * The document type is optional but should be specified if it
264 * can't be guessed or got from the HTTP headers.
266 * @param string $uri The URI of the data to load
267 * @param string $format Optional format of the data (eg. rdfxml)
268 * @return integer The number of triples added to the graph
270 public function load($uri = null, $format = null)
272 $this->checkResourceParam($uri, true);
275 throw new EasyRdf_Exception(
276 "No URI given to load() and the graph does not have a URI."
280 // Setup the HTTP client
281 $client = EasyRdf_Http::getDefaultHttpClient();
282 $client->resetParameters(true);
283 $client->setConfig(array('maxredirects' => 0));
284 $client->setMethod('GET');
285 $client->setHeaders('Accept', EasyRdf_Format::getHttpAcceptHeader());
289 $redirectCounter = 0;
291 // Have we already loaded it into the graph?
292 $requestUrl = EasyRdf_Utils::removeFragmentFromUri($requestUrl);
293 if (in_array($requestUrl, $this->loaded)) {
297 // Make the HTTP request
298 $client->setHeaders('host', null);
299 $client->setUri($requestUrl);
300 $response = $client->request();
302 // Add the URL to the list of URLs loaded
303 $this->loaded[] = $requestUrl;
305 if ($response->isRedirect() and $location = $response->getHeader('location')) {
306 // Avoid problems with buggy servers that add whitespace
307 $location = trim($location);
309 // Some servers return relative URLs in the location header
310 // resolve it in relation to previous request
311 $baseUri = new EasyRdf_ParsedUri($requestUrl);
312 $requestUrl = $baseUri->resolve($location)->toString();
313 $requestUrl = EasyRdf_Utils::removeFragmentFromUri($requestUrl);
315 // If it is a 303 then drop the parameters
316 if ($response->getStatus() == 303) {
317 $client->resetParameters();
321 } elseif ($response->isSuccessful()) {
322 // If we didn't get any location, stop redirecting
325 throw new EasyRdf_Http_Exception(
326 "HTTP request for {$requestUrl} failed: ".$response->getMessage(),
327 $response->getStatus(),
332 } while ($redirectCounter < $this->maxRedirects);
334 if (!$format or $format == 'guess') {
335 list($format, $params) = EasyRdf_Utils::parseMimeType(
336 $response->getHeader('Content-Type')
341 return $this->parse($response->getBody(), $format, $uri);
344 /** Get an associative array of all the resources stored in the graph.
345 * The keys of the array is the URI of the EasyRdf_Resource.
347 * @return array Array of EasyRdf_Resource
349 public function resources()
351 foreach ($this->index as $subject => $properties) {
352 if (!isset($this->resources[$subject])) {
353 $this->resource($subject);
357 foreach ($this->revIndex as $object => $properties) {
358 if (!isset($this->resources[$object])) {
359 $this->resource($object);
363 return $this->resources;
366 /** Get an arry of resources matching a certain property and optional value.
368 * For example this routine could be used as a way of getting
369 * everyone who has name:
370 * $people = $graph->resourcesMatching('foaf:name')
372 * Or everyone who is male:
373 * $people = $graph->resourcesMatching('foaf:gender', 'male');
376 * $people = $graph->resourcesMatching('^foaf:homepage');
378 * @param string $property The property to check.
379 * @param mixed $value Optional, the value of the propery to check for.
380 * @return array Array of EasyRdf_Resource
382 public function resourcesMatching($property, $value = null)
384 $this->checkSinglePropertyParam($property, $inverse);
385 $this->checkValueParam($value);
387 // Use the reverse index if it is an inverse property
389 $index = &$this->revIndex;
391 $index = &$this->index;
395 foreach ($index as $subject => $props) {
396 if (isset($index[$subject][$property])) {
398 foreach ($this->index[$subject][$property] as $v) {
399 if ($v['type'] == $value['type'] and
400 $v['value'] == $value['value']) {
401 $matched[] = $this->resource($subject);
406 $matched[] = $this->resource($subject);
413 /** Get the URI of the graph
415 * @return string The URI of the graph
417 public function getUri()
422 /** Check that a URI/resource parameter is valid, and convert it to a string
425 protected function checkResourceParam(&$resource, $allowNull = false)
427 if ($allowNull == true) {
428 if ($resource === null) {
430 $resource = $this->uri;
435 } elseif ($resource === null) {
436 throw new InvalidArgumentException(
437 "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource. got null"
441 if (is_object($resource) and $resource instanceof EasyRdf_Resource) {
442 $resource = $resource->getUri();
443 } elseif (is_object($resource) and $resource instanceof EasyRdf_ParsedUri) {
444 $resource = strval($resource);
445 } elseif (is_string($resource)) {
446 if ($resource == '') {
447 throw new InvalidArgumentException(
448 "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource. got empty string"
450 } elseif (preg_match("|^<(.+)>$|", $resource, $matches)) {
451 $resource = $matches[1];
453 $resource = EasyRdf_Namespace::expand($resource);
456 throw new InvalidArgumentException(
457 "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource"
462 /** Check that a single URI/property parameter (not a property path)
463 * is valid, and expand it if required
466 protected function checkSinglePropertyParam(&$property, &$inverse)
468 if (is_object($property) and $property instanceof EasyRdf_Resource) {
469 $property = $property->getUri();
470 } elseif (is_object($property) and $property instanceof EasyRdf_ParsedUri) {
471 $property = strval($property);
472 } elseif (is_string($property)) {
473 if ($property == '') {
474 throw new InvalidArgumentException(
475 "\$property cannot be an empty string"
477 } elseif (substr($property, 0, 1) == '^') {
479 $property = EasyRdf_Namespace::expand(substr($property, 1));
480 } elseif (substr($property, 0, 2) == '_:') {
481 throw new InvalidArgumentException(
482 "\$property cannot be a blank node"
486 $property = EasyRdf_Namespace::expand($property);
490 if ($property === null or !is_string($property)) {
491 throw new InvalidArgumentException(
492 "\$property should be a string or EasyRdf_Resource and cannot be null"
497 /** Check that a value parameter is valid, and convert it to an associative array if needed
500 protected function checkValueParam(&$value)
503 if (is_object($value)) {
504 if (!method_exists($value, 'toRdfPhp')) {
505 // Convert to a literal object
506 $value = EasyRdf_Literal::create($value);
508 $value = $value->toRdfPhp();
509 } elseif (is_array($value)) {
510 if (!isset($value['type'])) {
511 throw new InvalidArgumentException(
512 "\$value is missing a 'type' key"
516 if (!isset($value['value'])) {
517 throw new InvalidArgumentException(
518 "\$value is missing a 'value' key"
522 // Fix ordering and remove unknown keys
524 'type' => strval($value['type']),
525 'value' => strval($value['value']),
526 'lang' => isset($value['lang']) ? strval($value['lang']) : null,
527 'datatype' => isset($value['datatype']) ? strval($value['datatype']) : null
532 'value' => strval($value),
533 'datatype' => EasyRdf_Literal::getDatatypeForValue($value)
536 if (!in_array($value['type'], array('uri', 'bnode', 'literal'), true)) {
537 throw new InvalidArgumentException(
538 "\$value does not have a valid type (".$value['type'].")"
541 if (empty($value['datatype'])) {
542 unset($value['datatype']);
544 if (empty($value['lang'])) {
545 unset($value['lang']);
547 if (isset($value['lang']) and isset($value['datatype'])) {
548 throw new InvalidArgumentException(
549 "\$value cannot have both and language and a datatype"
555 /** Get a single value for a property of a resource
557 * If multiple values are set for a property then the value returned
560 * If $property is an array, then the first item in the array that matches
561 * a property that exists is returned.
563 * This method will return null if the property does not exist.
565 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
566 * @param string $propertyPath A valid property path
567 * @param string $type The type of value to filter by (e.g. literal or resource)
568 * @param string $lang The language to filter by (e.g. en)
569 * @return mixed A value associated with the property
571 public function get($resource, $propertyPath, $type = null, $lang = null)
573 $this->checkResourceParam($resource);
575 if (is_object($propertyPath) and $propertyPath instanceof EasyRdf_Resource) {
576 return $this->getSingleProperty($resource, $propertyPath->getUri(), $type, $lang);
577 } elseif (is_string($propertyPath) and preg_match('|^(\^?)<(.+)>|', $propertyPath, $matches)) {
578 return $this->getSingleProperty($resource, "$matches[1]$matches[2]", $type, $lang);
579 } elseif ($propertyPath === null or !is_string($propertyPath)) {
580 throw new InvalidArgumentException(
581 "\$propertyPath should be a string or EasyRdf_Resource and cannot be null"
583 } elseif ($propertyPath === '') {
584 throw new InvalidArgumentException(
585 "\$propertyPath cannot be an empty string"
589 // Loop through each component in the path
590 foreach (explode('/', $propertyPath) as $part) {
591 // Stop if we come to a literal
592 if ($resource instanceof EasyRdf_Literal) {
596 // Try each of the alternative paths
597 foreach (explode('|', $part) as $p) {
598 $res = $this->getSingleProperty($resource, $p, $type, $lang);
604 // Stop if nothing was found
614 /** Get a single value for a property of a resource
616 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
617 * @param string $property The name of the property (e.g. foaf:name)
618 * @param string $type The type of value to filter by (e.g. literal or resource)
619 * @param string $lang The language to filter by (e.g. en)
620 * @return mixed A value associated with the property
624 protected function getSingleProperty($resource, $property, $type = null, $lang = null)
626 $this->checkResourceParam($resource);
627 $this->checkSinglePropertyParam($property, $inverse);
629 // Get an array of values for the property
630 $values = $this->propertyValuesArray($resource, $property, $inverse);
631 if (!isset($values)) {
635 // Filter the results
638 foreach ($values as $value) {
639 if ($type == 'literal' and $value['type'] == 'literal') {
640 if ($lang == null or (isset($value['lang']) and $value['lang'] == $lang)) {
644 } elseif ($type == 'resource') {
645 if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
652 $result = $values[0];
655 // Convert the internal data structure into a PHP object
656 return $this->arrayToObject($result);
659 /** Get a single literal value for a property of a resource
661 * If multiple values are set for a property then the value returned
664 * This method will return null if there is not literal value for the
667 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
668 * @param string|array $property The name of the property (e.g. foaf:name)
669 * @param string $lang The language to filter by (e.g. en)
670 * @return object EasyRdf_Literal Literal value associated with the property
672 public function getLiteral($resource, $property, $lang = null)
674 return $this->get($resource, $property, 'literal', $lang);
677 /** Get a single resource value for a property of a resource
679 * If multiple values are set for a property then the value returned
682 * This method will return null if there is not resource for the
685 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
686 * @param string|array $property The name of the property (e.g. foaf:name)
687 * @return object EasyRdf_Resource Resource associated with the property
689 public function getResource($resource, $property)
691 return $this->get($resource, $property, 'resource');
694 /** Return all the values for a particular property of a resource
697 protected function propertyValuesArray($resource, $property, $inverse = false)
699 // Is an inverse property being requested?
701 if (isset($this->revIndex[$resource])) {
702 $properties = &$this->revIndex[$resource];
705 if (isset($this->index[$resource])) {
706 $properties = &$this->index[$resource];
710 if (isset($properties[$property])) {
711 return $properties[$property];
717 /** Get an EasyRdf_Resource or EasyRdf_Literal object from an associative array.
720 protected function arrayToObject($data)
723 if ($data['type'] == 'uri' or $data['type'] == 'bnode') {
724 return $this->resource($data['value']);
726 return EasyRdf_Literal::create($data);
733 /** Get all values for a property path
735 * This method will return an empty array if the property does not exist.
737 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
738 * @param string $propertyPath A valid property path
739 * @param string $type The type of value to filter by (e.g. literal)
740 * @param string $lang The language to filter by (e.g. en)
741 * @return array An array of values associated with the property
743 public function all($resource, $propertyPath, $type = null, $lang = null)
745 $this->checkResourceParam($resource);
747 if (is_object($propertyPath) and $propertyPath instanceof EasyRdf_Resource) {
748 return $this->allForSingleProperty($resource, $propertyPath->getUri(), $type, $lang);
749 } elseif (is_string($propertyPath) and preg_match('|^(\^?)<(.+)>|', $propertyPath, $matches)) {
750 return $this->allForSingleProperty($resource, "$matches[1]$matches[2]", $type, $lang);
751 } elseif ($propertyPath === null or !is_string($propertyPath)) {
752 throw new InvalidArgumentException(
753 "\$propertyPath should be a string or EasyRdf_Resource and cannot be null"
755 } elseif ($propertyPath === '') {
756 throw new InvalidArgumentException(
757 "\$propertyPath cannot be an empty string"
761 $objects = array($resource);
763 // Loop through each component in the path
764 foreach (explode('/', $propertyPath) as $part) {
767 foreach (explode('|', $part) as $p) {
768 foreach ($objects as $o) {
769 // Ignore literals found earlier in path
770 if ($o instanceof EasyRdf_Literal) {
774 $results = array_merge(
776 $this->allForSingleProperty($o, $p, $type, $lang)
781 // Stop if we don't have anything
782 if (empty($objects)) {
786 // Use the results as the input to the next iteration
793 /** Get all values for a single property of a resource
795 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
796 * @param string $property The name of the property (e.g. foaf:name)
797 * @param string $type The type of value to filter by (e.g. literal)
798 * @param string $lang The language to filter by (e.g. en)
799 * @return array An array of values associated with the property
803 protected function allForSingleProperty($resource, $property, $type = null, $lang = null)
805 $this->checkResourceParam($resource);
806 $this->checkSinglePropertyParam($property, $inverse);
808 // Get an array of values for the property
809 $values = $this->propertyValuesArray($resource, $property, $inverse);
810 if (!isset($values)) {
816 foreach ($values as $value) {
817 if ($type == 'literal' and $value['type'] == 'literal') {
818 if ($lang == null or (isset($value['lang']) and $value['lang'] == $lang)) {
819 $objects[] = $this->arrayToObject($value);
821 } elseif ($type == 'resource') {
822 if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
823 $objects[] = $this->arrayToObject($value);
828 foreach ($values as $value) {
829 $objects[] = $this->arrayToObject($value);
835 /** Get all literal values for a property of a resource
837 * This method will return an empty array if the resource does not
838 * has any literal values for that property.
840 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
841 * @param string $property The name of the property (e.g. foaf:name)
842 * @param string $lang The language to filter by (e.g. en)
843 * @return array An array of values associated with the property
845 public function allLiterals($resource, $property, $lang = null)
847 return $this->all($resource, $property, 'literal', $lang);
850 /** Get all resources for a property of a resource
852 * This method will return an empty array if the resource does not
853 * has any resources for that property.
855 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
856 * @param string $property The name of the property (e.g. foaf:name)
857 * @return array An array of values associated with the property
859 public function allResources($resource, $property)
861 return $this->all($resource, $property, 'resource');
864 /** Get all the resources in the graph of a certain type
866 * If no resources of the type are available and empty
869 * @param string $type The type of the resource (e.g. foaf:Person)
870 * @return array The array of resources
872 public function allOfType($type)
874 return $this->all($type, '^rdf:type');
877 /** Count the number of values for a property of a resource
879 * @param string $resource The URI of the resource (e.g. http://example.com/joe#me)
880 * @param string $property The name of the property (e.g. foaf:name)
881 * @param string $type The type of value to filter by (e.g. literal)
882 * @param string $lang The language to filter by (e.g. en)
883 * @return integer The number of values for this property
885 public function countValues($resource, $property, $type = null, $lang = null)
887 return count($this->all($resource, $property, $type, $lang));
890 /** Concatenate all values for a property of a resource into a string.
892 * The default is to join the values together with a space character.
893 * This method will return an empty string if the property does not exist.
895 * @param mixed $resource The resource to get the property on
896 * @param string $property The name of the property (e.g. foaf:name)
897 * @param string $glue The string to glue the values together with.
898 * @param string $lang The language to filter by (e.g. en)
899 * @return string Concatenation of all the values.
901 public function join($resource, $property, $glue = ' ', $lang = null)
903 return join($glue, $this->all($resource, $property, 'literal', $lang));
906 /** Add data to the graph
908 * The resource can either be a resource or the URI of a resource.
911 * $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
913 * @param mixed $resource The resource to add data to
914 * @param mixed $property The property name
915 * @param mixed $value The new value for the property
916 * @return integer The number of values added (1 or 0)
918 public function add($resource, $property, $value)
920 $this->checkResourceParam($resource);
921 $this->checkSinglePropertyParam($property, $inverse);
922 $this->checkValueParam($value);
925 if ($value === null) {
929 // Check that the value doesn't already exist
930 if (isset($this->index[$resource][$property])) {
931 foreach ($this->index[$resource][$property] as $v) {
937 $this->index[$resource][$property][] = $value;
939 // Add to the reverse index if it is a resource
940 if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
941 $uri = $value['value'];
942 $this->revIndex[$uri][$property][] = array(
943 'type' => substr($resource, 0, 2) == '_:' ? 'bnode' : 'uri',
952 /** Add a literal value as a property of a resource
954 * The resource can either be a resource or the URI of a resource.
955 * The value can either be a single value or an array of values.
958 * $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
960 * @param mixed $resource The resource to add data to
961 * @param mixed $property The property name
962 * @param mixed $value The value or values for the property
963 * @param string $lang The language of the literal
964 * @return integer The number of values added
966 public function addLiteral($resource, $property, $value, $lang = null)
968 $this->checkResourceParam($resource);
969 $this->checkSinglePropertyParam($property, $inverse);
971 if (is_array($value)) {
973 foreach ($value as $v) {
974 $added += $this->addLiteral($resource, $property, $v, $lang);
977 } elseif (!is_object($value) or !$value instanceof EasyRdf_Literal) {
978 $value = EasyRdf_Literal::create($value, $lang);
980 return $this->add($resource, $property, $value);
983 /** Add a resource as a property of another resource
985 * The resource can either be a resource or the URI of a resource.
988 * $graph->add("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
990 * @param mixed $resource The resource to add data to
991 * @param mixed $property The property name
992 * @param mixed $resource2 The resource to be value of the property
993 * @return integer The number of values added
995 public function addResource($resource, $property, $resource2)
997 $this->checkResourceParam($resource);
998 $this->checkSinglePropertyParam($property, $inverse);
999 $this->checkResourceParam($resource2);
1005 'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
1006 'value' => $resource2
1011 /** Set a value for a property
1013 * The new value will replace the existing values for the property.
1015 * @param string $resource The resource to set the property on
1016 * @param string $property The name of the property (e.g. foaf:name)
1017 * @param mixed $value The value for the property
1018 * @return integer The number of values added (1 or 0)
1020 public function set($resource, $property, $value)
1022 $this->checkResourceParam($resource);
1023 $this->checkSinglePropertyParam($property, $inverse);
1024 $this->checkValueParam($value);
1026 // Delete the old values
1027 $this->delete($resource, $property);
1029 // Add the new values
1030 return $this->add($resource, $property, $value);
1033 /** Delete a property (or optionally just a specific value)
1035 * @param mixed $resource The resource to delete the property from
1036 * @param string $property The name of the property (e.g. foaf:name)
1037 * @param mixed $value The value to delete (null to delete all values)
1038 * @return integer The number of values deleted
1040 public function delete($resource, $property, $value = null)
1042 $this->checkResourceParam($resource);
1044 if (is_object($property) and $property instanceof EasyRdf_Resource) {
1045 return $this->deleteSingleProperty($resource, $property->getUri(), $value);
1046 } elseif (is_string($property) and preg_match('|^(\^?)<(.+)>|', $property, $matches)) {
1047 return $this->deleteSingleProperty($resource, "$matches[1]$matches[2]", $value);
1048 } elseif ($property === null or !is_string($property)) {
1049 throw new InvalidArgumentException(
1050 "\$property should be a string or EasyRdf_Resource and cannot be null"
1052 } elseif ($property === '') {
1053 throw new InvalidArgumentException(
1054 "\$property cannot be an empty string"
1058 // FIXME: finish implementing property paths for delete
1059 return $this->deleteSingleProperty($resource, $property, $value);
1063 /** Delete a property (or optionally just a specific value)
1065 * @param mixed $resource The resource to delete the property from
1066 * @param string $property The name of the property (e.g. foaf:name)
1067 * @param mixed $value The value to delete (null to delete all values)
1068 * @return integer The number of values deleted
1072 public function deleteSingleProperty($resource, $property, $value = null)
1074 $this->checkResourceParam($resource);
1075 $this->checkSinglePropertyParam($property, $inverse);
1076 $this->checkValueParam($value);
1079 if (isset($this->index[$resource][$property])) {
1080 foreach ($this->index[$resource][$property] as $k => $v) {
1081 if (!$value or $v == $value) {
1082 unset($this->index[$resource][$property][$k]);
1084 if ($v['type'] == 'uri' or $v['type'] == 'bnode') {
1085 $this->deleteInverse($v['value'], $property, $resource);
1090 // Clean up the indexes - remove empty properties and resources
1092 if (count($this->index[$resource][$property]) == 0) {
1093 unset($this->index[$resource][$property]);
1095 if (count($this->index[$resource]) == 0) {
1096 unset($this->index[$resource]);
1104 /** Delete a resource from a property of another resource
1106 * The resource can either be a resource or the URI of a resource.
1109 * $graph->delete("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
1111 * @param mixed $resource The resource to delete data from
1112 * @param mixed $property The property name
1113 * @param mixed $resource2 The resource value of the property to be deleted
1115 public function deleteResource($resource, $property, $resource2)
1117 $this->checkResourceParam($resource);
1118 $this->checkSinglePropertyParam($property, $inverse);
1119 $this->checkResourceParam($resource2);
1121 return $this->delete(
1125 'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
1126 'value' => $resource2
1131 /** Delete a literal value from a property of a resource
1134 * $graph->delete("http://www.example.com", 'dc:title', 'Title of Page');
1136 * @param mixed $resource The resource to add data to
1137 * @param mixed $property The property name
1138 * @param mixed $value The value of the property
1139 * @param string $lang The language of the literal
1141 public function deleteLiteral($resource, $property, $value, $lang = null)
1143 $this->checkResourceParam($resource);
1144 $this->checkSinglePropertyParam($property, $inverse);
1145 $this->checkValueParam($value);
1148 $value['lang'] = $lang;
1151 return $this->delete($resource, $property, $value);
1154 /** This function is for internal use only.
1156 * Deletes an inverse property from a resource.
1160 protected function deleteInverse($resource, $property, $value)
1162 if (isset($this->revIndex[$resource])) {
1163 foreach ($this->revIndex[$resource][$property] as $k => $v) {
1164 if ($v['value'] === $value) {
1165 unset($this->revIndex[$resource][$property][$k]);
1168 if (count($this->revIndex[$resource][$property]) == 0) {
1169 unset($this->revIndex[$resource][$property]);
1171 if (count($this->revIndex[$resource]) == 0) {
1172 unset($this->revIndex[$resource]);
1177 /** Check if the graph contains any statements
1179 * @return boolean True if the graph contains no statements
1181 public function isEmpty()
1183 return count($this->index) == 0;
1186 /** Get a list of all the shortened property names (qnames) for a resource.
1188 * This method will return an empty array if the resource has no properties.
1190 * @return array Array of shortened URIs
1192 public function properties($resource)
1194 $this->checkResourceParam($resource);
1196 $properties = array();
1197 if (isset($this->index[$resource])) {
1198 foreach ($this->index[$resource] as $property => $value) {
1199 $short = EasyRdf_Namespace::shorten($property);
1201 $properties[] = $short;
1208 /** Get a list of the full URIs for the properties of a resource.
1210 * This method will return an empty array if the resource has no properties.
1212 * @return array Array of full URIs
1214 public function propertyUris($resource)
1216 $this->checkResourceParam($resource);
1218 if (isset($this->index[$resource])) {
1219 return array_keys($this->index[$resource]);
1225 /** Get a list of the full URIs for the properties that point to a resource.
1227 * @return array Array of full property URIs
1229 public function reversePropertyUris($resource)
1231 $this->checkResourceParam($resource);
1233 if (isset($this->revIndex[$resource])) {
1234 return array_keys($this->revIndex[$resource]);
1240 /** Check to see if a property exists for a resource.
1242 * This method will return true if the property exists.
1243 * If the value parameter is given, then it will only return true
1244 * if the value also exists for that property.
1246 * By providing a value parameter you can use this function to check
1247 * to see if a triple exists in the graph.
1249 * @param mixed $resource The resource to check
1250 * @param string $property The name of the property (e.g. foaf:name)
1251 * @param mixed $value An optional value of the property
1252 * @return boolean True if value the property exists.
1254 public function hasProperty($resource, $property, $value = null)
1256 $this->checkResourceParam($resource);
1257 $this->checkSinglePropertyParam($property, $inverse);
1258 $this->checkValueParam($value);
1260 // Use the reverse index if it is an inverse property
1262 $index = &$this->revIndex;
1264 $index = &$this->index;
1267 if (isset($index[$resource][$property])) {
1268 if (is_null($value)) {
1271 foreach ($index[$resource][$property] as $v) {
1282 /** Serialise the graph into RDF
1284 * The $format parameter can be an EasyRdf_Format object, a
1285 * format name, a mime type or a file extension.
1288 * $turtle = $graph->serialise('turtle');
1290 * @param mixed $format The format to serialise to
1291 * @param array $options Serialiser-specific options, for fine-tuning the output
1292 * @return mixed The serialised graph
1294 public function serialise($format, array $options = array())
1296 if (!$format instanceof EasyRdf_Format) {
1297 $format = EasyRdf_Format::getFormat($format);
1299 $serialiser = $format->newSerialiser();
1300 return $serialiser->serialise($this, $format->getName(), $options);
1303 /** Return a human readable view of all the resources in the graph
1305 * This method is intended to be a debugging aid and will
1306 * return a pretty-print view of all the resources and their
1309 * @param string $format Either 'html' or 'text'
1312 public function dump($format = 'html')
1315 if ($format == 'html') {
1316 $result .= "<div style='font-family:arial; font-weight: bold; padding:0.5em; ".
1317 "color: black; background-color:lightgrey;border:dashed 1px grey;'>".
1318 "Graph: ". $this->uri . "</div>\n";
1320 $result .= "Graph: ". $this->uri . "\n";
1323 foreach ($this->index as $resource => $properties) {
1324 $result .= $this->dumpResource($resource, $format);
1329 /** Return a human readable view of a resource and its properties
1331 * This method is intended to be a debugging aid and will
1332 * print a resource and its properties.
1334 * @param mixed $resource The resource to dump
1335 * @param string $format Either 'html' or 'text'
1338 public function dumpResource($resource, $format = 'html')
1340 $this->checkResourceParam($resource, true);
1342 if (isset($this->index[$resource])) {
1343 $properties = $this->index[$resource];
1349 foreach ($properties as $property => $values) {
1351 foreach ($values as $value) {
1352 if ($value['type'] == 'literal') {
1353 $olist []= EasyRdf_Utils::dumpLiteralValue($value, $format, 'black');
1355 $olist []= EasyRdf_Utils::dumpResourceValue($value['value'], $format, 'blue');
1359 $pstr = EasyRdf_Namespace::shorten($property);
1360 if ($pstr == null) {
1363 if ($format == 'html') {
1364 $plist []= "<span style='font-size:130%'>→</span> ".
1365 "<span style='text-decoration:none;color:green'>".
1366 htmlentities($pstr) . "</span> ".
1367 "<span style='font-size:130%'>→</span> ".
1370 $plist []= " -> $pstr -> " . join(", ", $olist);
1374 if ($format == 'html') {
1375 return "<div id='".htmlentities($resource, ENT_QUOTES)."' " .
1376 "style='font-family:arial; padding:0.5em; ".
1377 "background-color:lightgrey;border:dashed 1px grey;'>\n".
1378 "<div>".EasyRdf_Utils::dumpResourceValue($resource, $format, 'blue')." ".
1379 "<span style='font-size: 0.8em'>(".
1380 $this->classForResource($resource).")</span></div>\n".
1381 "<div style='padding-left: 3em'>\n".
1382 "<div>".join("</div>\n<div>", $plist)."</div>".
1385 return $resource." (".$this->classForResource($resource).")\n" .
1386 join("\n", $plist) . "\n\n";
1390 /** Get the resource type of the graph
1392 * The type will be a shortened URI as a string.
1393 * If the graph has multiple types then the type returned
1395 * This method will return null if the resource has no type.
1397 * @return string A type assocated with the resource (e.g. foaf:Document)
1399 public function type($resource = null)
1401 $type = $this->typeAsResource($resource);
1404 return EasyRdf_Namespace::shorten($type);
1410 /** Get the resource type of the graph as a EasyRdf_Resource
1412 * If the graph has multiple types then the type returned
1414 * This method will return null if the resource has no type.
1416 * @return object EasyRdf_Resource A type assocated with the resource
1418 public function typeAsResource($resource = null)
1420 $this->checkResourceParam($resource, true);
1423 return $this->get($resource, 'rdf:type', 'resource');
1429 /** Get a list of types for a resource
1431 * The types will each be a shortened URI as a string.
1432 * This method will return an empty array if the resource has no types.
1434 * If $resource is null, then it will get the types for the URI of the graph.
1436 * @return array All types assocated with the resource (e.g. foaf:Person)
1438 public function types($resource = null)
1440 $resources = $this->typesAsResources($resource);
1443 foreach ($resources as $type) {
1444 $types[] = EasyRdf_Namespace::shorten($type);
1451 * Get the resource types of the graph as a EasyRdf_Resource
1453 * @return EasyRdf_Resource[]
1455 public function typesAsResources($resource = null)
1457 $this->checkResourceParam($resource, true);
1460 return $this->all($resource, 'rdf:type', 'resource');
1466 /** Check if a resource is of the specified type
1468 * @param string $resource The resource to check the type of
1469 * @param string $type The type to check (e.g. foaf:Person)
1470 * @return boolean True if resource is of specified type
1472 public function isA($resource, $type)
1474 $this->checkResourceParam($resource, true);
1476 $type = EasyRdf_Namespace::expand($type);
1477 foreach ($this->all($resource, 'rdf:type', 'resource') as $t) {
1478 if ($t->getUri() == $type) {
1485 /** Add one or more rdf:type properties to a resource
1487 * @param string $resource The resource to add the type to
1488 * @param string $types One or more types to add (e.g. foaf:Person)
1489 * @return integer The number of types added
1491 public function addType($resource, $types)
1493 $this->checkResourceParam($resource, true);
1495 if (!is_array($types)) {
1496 $types = array($types);
1500 foreach ($types as $type) {
1501 $type = EasyRdf_Namespace::expand($type);
1502 $count += $this->add($resource, 'rdf:type', array('type' => 'uri', 'value' => $type));
1508 /** Change the rdf:type property for a resource
1510 * Note that if the resource object has already previously
1511 * been created, then the PHP class of the resource will not change.
1513 * @param string $resource The resource to change the type of
1514 * @param string $type The new type (e.g. foaf:Person)
1515 * @return integer The number of types added
1517 public function setType($resource, $type)
1519 $this->checkResourceParam($resource, true);
1521 $this->delete($resource, 'rdf:type');
1522 return $this->addType($resource, $type);
1525 /** Get a human readable label for a resource
1527 * This method will check a number of properties for a resource
1528 * (in the order: skos:prefLabel, rdfs:label, foaf:name, dc:title)
1529 * and return an approriate first that is available. If no label
1530 * is available then it will return null.
1532 * @return string A label for the resource.
1534 public function label($resource = null, $lang = null)
1536 $this->checkResourceParam($resource, true);
1541 'skos:prefLabel|rdfs:label|foaf:name|rss:title|dc:title|dc11:title',
1550 /** Get the primary topic of the graph
1552 * @return EasyRdf_Resource The primary topic of the document.
1554 public function primaryTopic($resource = null)
1556 $this->checkResourceParam($resource, true);
1561 'foaf:primaryTopic|^foaf:isPrimaryTopicOf',
1569 /** Returns the graph as a RDF/PHP associative array
1571 * @return array The contents of the graph as an array.
1573 public function toRdfPhp()
1575 return $this->index;
1578 /** Calculates the number of triples in the graph
1580 * @return integer The number of triples in the graph.
1582 public function countTriples()
1585 foreach ($this->index as $resource) {
1586 foreach ($resource as $property => $values) {
1587 $count += count($values);
1593 /** Magic method to return URI of resource when casted to string
1595 * @return string The URI of the resource
1597 public function __toString()
1599 return $this->uri == null ? '' : $this->uri;
1602 /** Magic method to get a property of the graph
1604 * Note that only properties in the default namespace can be accessed in this way.
1607 * $value = $graph->title;
1609 * @see EasyRdf_Namespace::setDefault()
1610 * @param string $name The name of the property
1611 * @return string A single value for the named property
1613 public function __get($name)
1615 return $this->get($this->uri, $name);
1618 /** Magic method to set the value for a property of the graph
1620 * Note that only properties in the default namespace can be accessed in this way.
1623 * $graph->title = 'Title';
1625 * @see EasyRdf_Namespace::setDefault()
1626 * @param string $name The name of the property
1627 * @param string $value The value for the property
1629 public function __set($name, $value)
1631 return $this->set($this->uri, $name, $value);
1634 /** Magic method to check if a property exists
1636 * Note that only properties in the default namespace can be accessed in this way.
1639 * if (isset($graph->title)) { blah(); }
1641 * @see EasyRdf_Namespace::setDefault()
1642 * @param string $name The name of the property
1644 public function __isset($name)
1646 return $this->hasProperty($this->uri, $name);
1649 /** Magic method to delete a property of the graph
1651 * Note that only properties in the default namespace can be accessed in this way.
1654 * unset($graph->title);
1656 * @see EasyRdf_Namespace::setDefault()
1657 * @param string $name The name of the property
1659 public function __unset($name)
1661 return $this->delete($this->uri, $name);