46ccc1708a2080f6d551cac9c85862311025039b
[yaffs-website] / vendor / easyrdf / easyrdf / lib / EasyRdf / Graph.php
1 <?php
2
3 /**
4  * EasyRdf
5  *
6  * LICENSE
7  *
8  * Copyright (c) 2009-2013 Nicholas J Humfrey.  All rights reserved.
9  *
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
19  *    written permission.
20  *
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.
32  *
33  * @package    EasyRdf
34  * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
35  * @license    http://www.opensource.org/licenses/bsd-license.php
36  */
37
38 /**
39  * Container for collection of EasyRdf_Resources.
40  *
41  * @package    EasyRdf
42  * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
43  * @license    http://www.opensource.org/licenses/bsd-license.php
44  */
45 class EasyRdf_Graph
46 {
47     /** The URI of the graph */
48     private $uri = null;
49     private $parsedUri = null;
50
51     /** Array of resources contained in the graph */
52     private $resources = array();
53
54     private $index = array();
55     private $revIndex = array();
56
57     /** Counter for the number of bnodes */
58     private $bNodeCount = 0;
59
60     /** Array of URLs that have been loaded into the graph */
61     private $loaded = array();
62
63     private $maxRedirects = 10;
64
65
66     /**
67      * Constructor
68      *
69      * If no URI is given then an unnamed graph is created.
70      *
71      * The $data parameter is optional and will be parsed into
72      * the graph if given.
73      *
74      * The data format is optional and should be specified if it
75      * can't be guessed by EasyRdf.
76      *
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
81      */
82     public function __construct($uri = null, $data = null, $format = null)
83     {
84         $this->checkResourceParam($uri, true);
85
86         if ($uri) {
87             $this->uri = $uri;
88             $this->parsedUri = new EasyRdf_ParsedUri($uri);
89             if ($data) {
90                 $this->parse($data, $format, $this->uri);
91             }
92         }
93     }
94
95     /**
96      * Create a new graph and load RDF data from a URI into it
97      *
98      * This static function is shorthand for:
99      *     $graph = new EasyRdf_Graph($uri);
100      *     $graph->load($uri, $format);
101      *
102      * The document type is optional but should be specified if it
103      * can't be guessed or got from the HTTP headers.
104      *
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
108      */
109     public static function newAndLoad($uri, $format = null)
110     {
111         $graph = new self($uri);
112         $graph->load($uri, $format);
113         return $graph;
114     }
115
116     /** Get or create a resource stored in a graph
117      *
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.
122      *
123      * If URI is null, then the URI of the graph is used.
124      *
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
128      */
129     public function resource($uri = null, $types = array())
130     {
131         $this->checkResourceParam($uri, true);
132         if (!$uri) {
133             throw new InvalidArgumentException(
134                 '$uri is null and EasyRdf_Graph object has no URI either.'
135             );
136         }
137
138         // Resolve relative URIs
139         if ($this->parsedUri) {
140             $uri = $this->parsedUri->resolve($uri)->toString();
141         }
142
143         // Add the types
144         $this->addType($uri, $types);
145
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);
150         }
151
152         return $this->resources[$uri];
153     }
154
155     /** Work out the class to instantiate a resource as
156      *  @ignore
157      */
158     protected function classForResource($uri)
159     {
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) {
166                         return $class;
167                     }
168                 }
169             }
170         }
171
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'])
177         ) {
178             return 'EasyRdf_Collection';
179         }
180         return 'EasyRdf_Resource';
181     }
182
183     /**
184      * Create a new blank node in the graph and return it.
185      *
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.
189      *
190      * @param  mixed  $types  RDF type of a new blank node (e.g. foaf:Person)
191      * @return object EasyRdf_Resource The new blank node
192      */
193     public function newBNode($types = array())
194     {
195         return $this->resource($this->newBNodeId(), $types);
196     }
197
198     /**
199      * Create a new unique blank node identifier and return it.
200      *
201      * @return string The new blank node identifier (e.g. _:genid1)
202      */
203     public function newBNodeId()
204     {
205         return "_:genid".(++$this->bNodeCount);
206     }
207
208     /**
209      * Parse some RDF data into the graph object.
210      *
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
215      */
216     public function parse($data, $format = null, $uri = null)
217     {
218         $this->checkResourceParam($uri, true);
219
220         if (empty($format) or $format == 'guess') {
221             // Guess the format if it is Unknown
222             $format = EasyRdf_Format::guessFormat($data, $uri);
223         } else {
224             $format = EasyRdf_Format::getFormat($format);
225         }
226
227         if (!$format) {
228             throw new EasyRdf_Exception(
229                 "Unable to parse data of an unknown format."
230             );
231         }
232
233         $parser = $format->newParser();
234         return $parser->parse($this, $data, $format, $uri);
235     }
236
237     /**
238      * Parse a file containing RDF data into the graph object.
239      *
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
244      */
245     public function parseFile($filename, $format = null, $uri = null)
246     {
247         if ($uri === null) {
248             $uri = "file://$filename";
249         }
250
251         return $this->parse(
252             file_get_contents($filename),
253             $format,
254             $uri
255         );
256     }
257
258     /**
259      * Load RDF data into the graph from a URI.
260      *
261      * If no URI is given, then the URI of the graph will be used.
262      *
263      * The document type is optional but should be specified if it
264      * can't be guessed or got from the HTTP headers.
265      *
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
269      */
270     public function load($uri = null, $format = null)
271     {
272         $this->checkResourceParam($uri, true);
273
274         if (!$uri) {
275             throw new EasyRdf_Exception(
276                 "No URI given to load() and the graph does not have a URI."
277             );
278         }
279
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());
286
287         $requestUrl = $uri;
288         $response = null;
289         $redirectCounter = 0;
290         do {
291             // Have we already loaded it into the graph?
292             $requestUrl = EasyRdf_Utils::removeFragmentFromUri($requestUrl);
293             if (in_array($requestUrl, $this->loaded)) {
294                 return 0;
295             }
296
297             // Make the HTTP request
298             $client->setHeaders('host', null);
299             $client->setUri($requestUrl);
300             $response = $client->request();
301
302             // Add the URL to the list of URLs loaded
303             $this->loaded[] = $requestUrl;
304
305             if ($response->isRedirect() and $location = $response->getHeader('location')) {
306                 // Avoid problems with buggy servers that add whitespace
307                 $location = trim($location);
308
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);
314
315                 // If it is a 303 then drop the parameters
316                 if ($response->getStatus() == 303) {
317                     $client->resetParameters();
318                 }
319
320                 ++$redirectCounter;
321             } elseif ($response->isSuccessful()) {
322                 // If we didn't get any location, stop redirecting
323                 break;
324             } else {
325                 throw new EasyRdf_Http_Exception(
326                     "HTTP request for {$requestUrl} failed: ".$response->getMessage(),
327                     $response->getStatus(),
328                     null,
329                     $response->getBody()
330                 );
331             }
332         } while ($redirectCounter < $this->maxRedirects);
333
334         if (!$format or $format == 'guess') {
335             list($format, $params) = EasyRdf_Utils::parseMimeType(
336                 $response->getHeader('Content-Type')
337             );
338         }
339
340         // Parse the data
341         return $this->parse($response->getBody(), $format, $uri);
342     }
343
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.
346      *
347      * @return array Array of EasyRdf_Resource
348      */
349     public function resources()
350     {
351         foreach ($this->index as $subject => $properties) {
352             if (!isset($this->resources[$subject])) {
353                 $this->resource($subject);
354             }
355         }
356
357         foreach ($this->revIndex as $object => $properties) {
358             if (!isset($this->resources[$object])) {
359                 $this->resource($object);
360             }
361         }
362
363         return $this->resources;
364     }
365
366     /** Get an arry of resources matching a certain property and optional value.
367      *
368      * For example this routine could be used as a way of getting
369      * everyone who has name:
370      * $people = $graph->resourcesMatching('foaf:name')
371      *
372      * Or everyone who is male:
373      * $people = $graph->resourcesMatching('foaf:gender', 'male');
374      *
375      * Or all homepages:
376      * $people = $graph->resourcesMatching('^foaf:homepage');
377      *
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
381      */
382     public function resourcesMatching($property, $value = null)
383     {
384         $this->checkSinglePropertyParam($property, $inverse);
385         $this->checkValueParam($value);
386
387         // Use the reverse index if it is an inverse property
388         if ($inverse) {
389             $index = &$this->revIndex;
390         } else {
391             $index = &$this->index;
392         }
393
394         $matched = array();
395         foreach ($index as $subject => $props) {
396             if (isset($index[$subject][$property])) {
397                 if (isset($value)) {
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);
402                             break;
403                         }
404                     }
405                 } else {
406                     $matched[] = $this->resource($subject);
407                 }
408             }
409         }
410         return $matched;
411     }
412
413     /** Get the URI of the graph
414      *
415      * @return string The URI of the graph
416      */
417     public function getUri()
418     {
419         return $this->uri;
420     }
421
422     /** Check that a URI/resource parameter is valid, and convert it to a string
423      *  @ignore
424      */
425     protected function checkResourceParam(&$resource, $allowNull = false)
426     {
427         if ($allowNull == true) {
428             if ($resource === null) {
429                 if ($this->uri) {
430                     $resource = $this->uri;
431                 } else {
432                     return;
433                 }
434             }
435         } elseif ($resource === null) {
436             throw new InvalidArgumentException(
437                 "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource. got null"
438             );
439         }
440
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"
449                 );
450             } elseif (preg_match("|^<(.+)>$|", $resource, $matches)) {
451                 $resource = $matches[1];
452             } else {
453                 $resource = EasyRdf_Namespace::expand($resource);
454             }
455         } else {
456             throw new InvalidArgumentException(
457                 "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource"
458             );
459         }
460     }
461
462     /** Check that a single URI/property parameter (not a property path)
463      *  is valid, and expand it if required
464      *  @ignore
465      */
466     protected function checkSinglePropertyParam(&$property, &$inverse)
467     {
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"
476                 );
477             } elseif (substr($property, 0, 1) == '^') {
478                 $inverse = true;
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"
483                 );
484             } else {
485                 $inverse = false;
486                 $property = EasyRdf_Namespace::expand($property);
487             }
488         }
489
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"
493             );
494         }
495     }
496
497     /** Check that a value parameter is valid, and convert it to an associative array if needed
498      *  @ignore
499      */
500     protected function checkValueParam(&$value)
501     {
502         if (isset($value)) {
503             if (is_object($value)) {
504                 if (!method_exists($value, 'toRdfPhp')) {
505                     // Convert to a literal object
506                     $value = EasyRdf_Literal::create($value);
507                 }
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"
513                     );
514                 }
515
516                 if (!isset($value['value'])) {
517                     throw new InvalidArgumentException(
518                         "\$value is missing a 'value' key"
519                     );
520                 }
521
522                 // Fix ordering and remove unknown keys
523                 $value = array(
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
528                 );
529             } else {
530                 $value = array(
531                     'type' => 'literal',
532                     'value' => strval($value),
533                     'datatype' => EasyRdf_Literal::getDatatypeForValue($value)
534                 );
535             }
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'].")"
539                 );
540             }
541             if (empty($value['datatype'])) {
542                 unset($value['datatype']);
543             }
544             if (empty($value['lang'])) {
545                 unset($value['lang']);
546             }
547             if (isset($value['lang']) and isset($value['datatype'])) {
548                 throw new InvalidArgumentException(
549                     "\$value cannot have both and language and a datatype"
550                 );
551             }
552         }
553     }
554
555     /** Get a single value for a property of a resource
556      *
557      * If multiple values are set for a property then the value returned
558      * may be arbitrary.
559      *
560      * If $property is an array, then the first item in the array that matches
561      * a property that exists is returned.
562      *
563      * This method will return null if the property does not exist.
564      *
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
570      */
571     public function get($resource, $propertyPath, $type = null, $lang = null)
572     {
573         $this->checkResourceParam($resource);
574
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"
582             );
583         } elseif ($propertyPath === '') {
584             throw new InvalidArgumentException(
585                 "\$propertyPath cannot be an empty string"
586             );
587         }
588
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) {
593                 return null;
594             }
595
596             // Try each of the alternative paths
597             foreach (explode('|', $part) as $p) {
598                 $res = $this->getSingleProperty($resource, $p, $type, $lang);
599                 if ($res) {
600                     break;
601                 }
602             }
603
604             // Stop if nothing was found
605             $resource = $res;
606             if (!$resource) {
607                 break;
608             }
609         }
610
611         return $resource;
612     }
613
614     /** Get a single value for a property of a resource
615      *
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
621      *
622      * @ignore
623      */
624     protected function getSingleProperty($resource, $property, $type = null, $lang = null)
625     {
626         $this->checkResourceParam($resource);
627         $this->checkSinglePropertyParam($property, $inverse);
628
629         // Get an array of values for the property
630         $values = $this->propertyValuesArray($resource, $property, $inverse);
631         if (!isset($values)) {
632             return null;
633         }
634
635         // Filter the results
636         $result = null;
637         if ($type) {
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)) {
641                         $result = $value;
642                         break;
643                     }
644                 } elseif ($type == 'resource') {
645                     if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
646                         $result = $value;
647                         break;
648                     }
649                 }
650             }
651         } else {
652             $result = $values[0];
653         }
654
655         // Convert the internal data structure into a PHP object
656         return $this->arrayToObject($result);
657     }
658
659     /** Get a single literal value for a property of a resource
660      *
661      * If multiple values are set for a property then the value returned
662      * may be arbitrary.
663      *
664      * This method will return null if there is not literal value for the
665      * property.
666      *
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
671      */
672     public function getLiteral($resource, $property, $lang = null)
673     {
674         return $this->get($resource, $property, 'literal', $lang);
675     }
676
677     /** Get a single resource value for a property of a resource
678      *
679      * If multiple values are set for a property then the value returned
680      * may be arbitrary.
681      *
682      * This method will return null if there is not resource for the
683      * property.
684      *
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
688      */
689     public function getResource($resource, $property)
690     {
691         return $this->get($resource, $property, 'resource');
692     }
693
694     /** Return all the values for a particular property of a resource
695      *  @ignore
696      */
697     protected function propertyValuesArray($resource, $property, $inverse = false)
698     {
699         // Is an inverse property being requested?
700         if ($inverse) {
701             if (isset($this->revIndex[$resource])) {
702                 $properties = &$this->revIndex[$resource];
703             }
704         } else {
705             if (isset($this->index[$resource])) {
706                 $properties = &$this->index[$resource];
707             }
708         }
709
710         if (isset($properties[$property])) {
711             return $properties[$property];
712         } else {
713             return null;
714         }
715     }
716
717     /** Get an EasyRdf_Resource or EasyRdf_Literal object from an associative array.
718      *  @ignore
719      */
720     protected function arrayToObject($data)
721     {
722         if ($data) {
723             if ($data['type'] == 'uri' or $data['type'] == 'bnode') {
724                 return $this->resource($data['value']);
725             } else {
726                 return EasyRdf_Literal::create($data);
727             }
728         } else {
729             return null;
730         }
731     }
732
733     /** Get all values for a property path
734      *
735      * This method will return an empty array if the property does not exist.
736      *
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
742      */
743     public function all($resource, $propertyPath, $type = null, $lang = null)
744     {
745         $this->checkResourceParam($resource);
746
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"
754             );
755         } elseif ($propertyPath === '') {
756             throw new InvalidArgumentException(
757                 "\$propertyPath cannot be an empty string"
758             );
759         }
760
761         $objects = array($resource);
762
763         // Loop through each component in the path
764         foreach (explode('/', $propertyPath) as $part) {
765
766             $results = array();
767             foreach (explode('|', $part) as $p) {
768                 foreach ($objects as $o) {
769                     // Ignore literals found earlier in path
770                     if ($o instanceof EasyRdf_Literal) {
771                         continue;
772                     }
773
774                     $results = array_merge(
775                         $results,
776                         $this->allForSingleProperty($o, $p, $type, $lang)
777                     );
778                 }
779             }
780
781             // Stop if we don't have anything
782             if (empty($objects)) {
783                 break;
784             }
785
786             // Use the results as the input to the next iteration
787             $objects = $results;
788         }
789
790         return $results;
791     }
792
793     /** Get all values for a single property of a resource
794      *
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
800      *
801      * @ignore
802      */
803     protected function allForSingleProperty($resource, $property, $type = null, $lang = null)
804     {
805         $this->checkResourceParam($resource);
806         $this->checkSinglePropertyParam($property, $inverse);
807
808         // Get an array of values for the property
809         $values = $this->propertyValuesArray($resource, $property, $inverse);
810         if (!isset($values)) {
811             return array();
812         }
813
814         $objects = array();
815         if ($type) {
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);
820                     }
821                 } elseif ($type == 'resource') {
822                     if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
823                         $objects[] = $this->arrayToObject($value);
824                     }
825                 }
826             }
827         } else {
828             foreach ($values as $value) {
829                 $objects[] = $this->arrayToObject($value);
830             }
831         }
832         return $objects;
833     }
834
835     /** Get all literal values for a property of a resource
836      *
837      * This method will return an empty array if the resource does not
838      * has any literal values for that property.
839      *
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
844      */
845     public function allLiterals($resource, $property, $lang = null)
846     {
847         return $this->all($resource, $property, 'literal', $lang);
848     }
849
850     /** Get all resources for a property of a resource
851      *
852      * This method will return an empty array if the resource does not
853      * has any resources for that property.
854      *
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
858      */
859     public function allResources($resource, $property)
860     {
861         return $this->all($resource, $property, 'resource');
862     }
863
864     /** Get all the resources in the graph of a certain type
865      *
866      * If no resources of the type are available and empty
867      * array is returned.
868      *
869      * @param  string  $type   The type of the resource (e.g. foaf:Person)
870      * @return array The array of resources
871      */
872     public function allOfType($type)
873     {
874         return $this->all($type, '^rdf:type');
875     }
876
877     /** Count the number of values for a property of a resource
878      *
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
884      */
885     public function countValues($resource, $property, $type = null, $lang = null)
886     {
887         return count($this->all($resource, $property, $type, $lang));
888     }
889
890     /** Concatenate all values for a property of a resource into a string.
891      *
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.
894      *
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.
900      */
901     public function join($resource, $property, $glue = ' ', $lang = null)
902     {
903         return join($glue, $this->all($resource, $property, 'literal', $lang));
904     }
905
906     /** Add data to the graph
907      *
908      * The resource can either be a resource or the URI of a resource.
909      *
910      * Example:
911      *   $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
912      *
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)
917      */
918     public function add($resource, $property, $value)
919     {
920         $this->checkResourceParam($resource);
921         $this->checkSinglePropertyParam($property, $inverse);
922         $this->checkValueParam($value);
923
924         // No value given?
925         if ($value === null) {
926             return 0;
927         }
928
929         // Check that the value doesn't already exist
930         if (isset($this->index[$resource][$property])) {
931             foreach ($this->index[$resource][$property] as $v) {
932                 if ($v == $value) {
933                     return 0;
934                 }
935             }
936         }
937         $this->index[$resource][$property][] = $value;
938
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',
944                 'value' => $resource
945             );
946         }
947
948         // Success
949         return 1;
950     }
951
952     /** Add a literal value as a property of a resource
953      *
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.
956      *
957      * Example:
958      *   $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
959      *
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
965      */
966     public function addLiteral($resource, $property, $value, $lang = null)
967     {
968         $this->checkResourceParam($resource);
969         $this->checkSinglePropertyParam($property, $inverse);
970
971         if (is_array($value)) {
972             $added = 0;
973             foreach ($value as $v) {
974                 $added += $this->addLiteral($resource, $property, $v, $lang);
975             }
976             return $added;
977         } elseif (!is_object($value) or !$value instanceof EasyRdf_Literal) {
978             $value = EasyRdf_Literal::create($value, $lang);
979         }
980         return $this->add($resource, $property, $value);
981     }
982
983     /** Add a resource as a property of another resource
984      *
985      * The resource can either be a resource or the URI of a resource.
986      *
987      * Example:
988      *   $graph->add("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
989      *
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
994      */
995     public function addResource($resource, $property, $resource2)
996     {
997         $this->checkResourceParam($resource);
998         $this->checkSinglePropertyParam($property, $inverse);
999         $this->checkResourceParam($resource2);
1000
1001         return $this->add(
1002             $resource,
1003             $property,
1004             array(
1005                 'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
1006                 'value' => $resource2
1007             )
1008         );
1009     }
1010
1011     /** Set a value for a property
1012      *
1013      * The new value will replace the existing values for the property.
1014      *
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)
1019      */
1020     public function set($resource, $property, $value)
1021     {
1022         $this->checkResourceParam($resource);
1023         $this->checkSinglePropertyParam($property, $inverse);
1024         $this->checkValueParam($value);
1025
1026         // Delete the old values
1027         $this->delete($resource, $property);
1028
1029         // Add the new values
1030         return $this->add($resource, $property, $value);
1031     }
1032
1033     /** Delete a property (or optionally just a specific value)
1034      *
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
1039      */
1040     public function delete($resource, $property, $value = null)
1041     {
1042         $this->checkResourceParam($resource);
1043
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"
1051             );
1052         } elseif ($property === '') {
1053             throw new InvalidArgumentException(
1054                 "\$property cannot be an empty string"
1055             );
1056         }
1057
1058         // FIXME: finish implementing property paths for delete
1059         return $this->deleteSingleProperty($resource, $property, $value);
1060     }
1061
1062
1063     /** Delete a property (or optionally just a specific value)
1064      *
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
1069      *
1070      * @ignore
1071      */
1072     public function deleteSingleProperty($resource, $property, $value = null)
1073     {
1074         $this->checkResourceParam($resource);
1075         $this->checkSinglePropertyParam($property, $inverse);
1076         $this->checkValueParam($value);
1077
1078         $count = 0;
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]);
1083                     $count++;
1084                     if ($v['type'] == 'uri' or $v['type'] == 'bnode') {
1085                         $this->deleteInverse($v['value'], $property, $resource);
1086                     }
1087                 }
1088             }
1089
1090             // Clean up the indexes - remove empty properties and resources
1091             if ($count) {
1092                 if (count($this->index[$resource][$property]) == 0) {
1093                     unset($this->index[$resource][$property]);
1094                 }
1095                 if (count($this->index[$resource]) == 0) {
1096                     unset($this->index[$resource]);
1097                 }
1098             }
1099         }
1100
1101         return $count;
1102     }
1103
1104     /** Delete a resource from a property of another resource
1105      *
1106      * The resource can either be a resource or the URI of a resource.
1107      *
1108      * Example:
1109      *   $graph->delete("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
1110      *
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
1114      */
1115     public function deleteResource($resource, $property, $resource2)
1116     {
1117         $this->checkResourceParam($resource);
1118         $this->checkSinglePropertyParam($property, $inverse);
1119         $this->checkResourceParam($resource2);
1120
1121         return $this->delete(
1122             $resource,
1123             $property,
1124             array(
1125                 'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
1126                 'value' => $resource2
1127             )
1128         );
1129     }
1130
1131     /** Delete a literal value from a property of a resource
1132      *
1133      * Example:
1134      *   $graph->delete("http://www.example.com", 'dc:title', 'Title of Page');
1135      *
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
1140      */
1141     public function deleteLiteral($resource, $property, $value, $lang = null)
1142     {
1143         $this->checkResourceParam($resource);
1144         $this->checkSinglePropertyParam($property, $inverse);
1145         $this->checkValueParam($value);
1146
1147         if ($lang) {
1148             $value['lang'] = $lang;
1149         }
1150
1151         return $this->delete($resource, $property, $value);
1152     }
1153
1154     /** This function is for internal use only.
1155      *
1156      * Deletes an inverse property from a resource.
1157      *
1158      * @ignore
1159      */
1160     protected function deleteInverse($resource, $property, $value)
1161     {
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]);
1166                 }
1167             }
1168             if (count($this->revIndex[$resource][$property]) == 0) {
1169                 unset($this->revIndex[$resource][$property]);
1170             }
1171             if (count($this->revIndex[$resource]) == 0) {
1172                 unset($this->revIndex[$resource]);
1173             }
1174         }
1175     }
1176
1177     /** Check if the graph contains any statements
1178      *
1179      * @return boolean True if the graph contains no statements
1180      */
1181     public function isEmpty()
1182     {
1183         return count($this->index) == 0;
1184     }
1185
1186     /** Get a list of all the shortened property names (qnames) for a resource.
1187      *
1188      * This method will return an empty array if the resource has no properties.
1189      *
1190      * @return array            Array of shortened URIs
1191      */
1192     public function properties($resource)
1193     {
1194         $this->checkResourceParam($resource);
1195
1196         $properties = array();
1197         if (isset($this->index[$resource])) {
1198             foreach ($this->index[$resource] as $property => $value) {
1199                 $short = EasyRdf_Namespace::shorten($property);
1200                 if ($short) {
1201                     $properties[] = $short;
1202                 }
1203             }
1204         }
1205         return $properties;
1206     }
1207
1208     /** Get a list of the full URIs for the properties of a resource.
1209      *
1210      * This method will return an empty array if the resource has no properties.
1211      *
1212      * @return array            Array of full URIs
1213      */
1214     public function propertyUris($resource)
1215     {
1216         $this->checkResourceParam($resource);
1217
1218         if (isset($this->index[$resource])) {
1219             return array_keys($this->index[$resource]);
1220         } else {
1221             return array();
1222         }
1223     }
1224
1225     /** Get a list of the full URIs for the properties that point to a resource.
1226      *
1227      * @return array   Array of full property URIs
1228      */
1229     public function reversePropertyUris($resource)
1230     {
1231         $this->checkResourceParam($resource);
1232
1233         if (isset($this->revIndex[$resource])) {
1234             return array_keys($this->revIndex[$resource]);
1235         } else {
1236             return array();
1237         }
1238     }
1239
1240     /** Check to see if a property exists for a resource.
1241      *
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.
1245      *
1246      * By providing a value parameter you can use this function to check
1247      * to see if a triple exists in the graph.
1248      *
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.
1253      */
1254     public function hasProperty($resource, $property, $value = null)
1255     {
1256         $this->checkResourceParam($resource);
1257         $this->checkSinglePropertyParam($property, $inverse);
1258         $this->checkValueParam($value);
1259
1260         // Use the reverse index if it is an inverse property
1261         if ($inverse) {
1262             $index = &$this->revIndex;
1263         } else {
1264             $index = &$this->index;
1265         }
1266
1267         if (isset($index[$resource][$property])) {
1268             if (is_null($value)) {
1269                 return true;
1270             } else {
1271                 foreach ($index[$resource][$property] as $v) {
1272                     if ($v == $value) {
1273                         return true;
1274                     }
1275                 }
1276             }
1277         }
1278
1279         return false;
1280     }
1281
1282     /** Serialise the graph into RDF
1283      *
1284      * The $format parameter can be an EasyRdf_Format object, a
1285      * format name, a mime type or a file extension.
1286      *
1287      * Example:
1288      *   $turtle = $graph->serialise('turtle');
1289      *
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
1293      */
1294     public function serialise($format, array $options = array())
1295     {
1296         if (!$format instanceof EasyRdf_Format) {
1297             $format = EasyRdf_Format::getFormat($format);
1298         }
1299         $serialiser = $format->newSerialiser();
1300         return $serialiser->serialise($this, $format->getName(), $options);
1301     }
1302
1303     /** Return a human readable view of all the resources in the graph
1304      *
1305      * This method is intended to be a debugging aid and will
1306      * return a pretty-print view of all the resources and their
1307      * properties.
1308      *
1309      * @param  string  $format  Either 'html' or 'text'
1310      * @return string
1311      */
1312     public function dump($format = 'html')
1313     {
1314         $result = '';
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";
1319         } else {
1320             $result .= "Graph: ". $this->uri . "\n";
1321         }
1322
1323         foreach ($this->index as $resource => $properties) {
1324             $result .= $this->dumpResource($resource, $format);
1325         }
1326         return $result;
1327     }
1328
1329     /** Return a human readable view of a resource and its properties
1330      *
1331      * This method is intended to be a debugging aid and will
1332      * print a resource and its properties.
1333      *
1334      * @param  mixed    $resource  The resource to dump
1335      * @param  string   $format    Either 'html' or 'text'
1336      * @return string
1337      */
1338     public function dumpResource($resource, $format = 'html')
1339     {
1340         $this->checkResourceParam($resource, true);
1341
1342         if (isset($this->index[$resource])) {
1343             $properties = $this->index[$resource];
1344         } else {
1345             return '';
1346         }
1347
1348         $plist = array();
1349         foreach ($properties as $property => $values) {
1350             $olist = array();
1351             foreach ($values as $value) {
1352                 if ($value['type'] == 'literal') {
1353                     $olist []= EasyRdf_Utils::dumpLiteralValue($value, $format, 'black');
1354                 } else {
1355                     $olist []= EasyRdf_Utils::dumpResourceValue($value['value'], $format, 'blue');
1356                 }
1357             }
1358
1359             $pstr = EasyRdf_Namespace::shorten($property);
1360             if ($pstr == null) {
1361                 $pstr = $property;
1362             }
1363             if ($format == 'html') {
1364                 $plist []= "<span style='font-size:130%'>&rarr;</span> ".
1365                            "<span style='text-decoration:none;color:green'>".
1366                            htmlentities($pstr) . "</span> ".
1367                            "<span style='font-size:130%'>&rarr;</span> ".
1368                            join(", ", $olist);
1369             } else {
1370                 $plist []= "  -> $pstr -> " . join(", ", $olist);
1371             }
1372         }
1373
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>".
1383                    "</div></div>\n";
1384         } else {
1385             return $resource." (".$this->classForResource($resource).")\n" .
1386                    join("\n", $plist) . "\n\n";
1387         }
1388     }
1389
1390     /** Get the resource type of the graph
1391      *
1392      * The type will be a shortened URI as a string.
1393      * If the graph has multiple types then the type returned
1394      * may be arbitrary.
1395      * This method will return null if the resource has no type.
1396      *
1397      * @return string A type assocated with the resource (e.g. foaf:Document)
1398      */
1399     public function type($resource = null)
1400     {
1401         $type = $this->typeAsResource($resource);
1402
1403         if ($type) {
1404             return EasyRdf_Namespace::shorten($type);
1405         }
1406
1407         return null;
1408     }
1409
1410     /** Get the resource type of the graph as a EasyRdf_Resource
1411      *
1412      * If the graph has multiple types then the type returned
1413      * may be arbitrary.
1414      * This method will return null if the resource has no type.
1415      *
1416      * @return object EasyRdf_Resource  A type assocated with the resource
1417      */
1418     public function typeAsResource($resource = null)
1419     {
1420         $this->checkResourceParam($resource, true);
1421
1422         if ($resource) {
1423             return $this->get($resource, 'rdf:type', 'resource');
1424         }
1425
1426         return null;
1427     }
1428
1429     /** Get a list of types for a resource
1430      *
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.
1433      *
1434      * If $resource is null, then it will get the types for the URI of the graph.
1435      *
1436      * @return array All types assocated with the resource (e.g. foaf:Person)
1437      */
1438     public function types($resource = null)
1439     {
1440         $resources = $this->typesAsResources($resource);
1441
1442         $types = array();
1443         foreach ($resources as $type) {
1444             $types[] = EasyRdf_Namespace::shorten($type);
1445         }
1446
1447         return $types;
1448     }
1449
1450     /**
1451      * Get the resource types of the graph as a EasyRdf_Resource
1452      *
1453      * @return EasyRdf_Resource[]
1454      */
1455     public function typesAsResources($resource = null)
1456     {
1457         $this->checkResourceParam($resource, true);
1458
1459         if ($resource) {
1460             return $this->all($resource, 'rdf:type', 'resource');
1461         }
1462
1463         return array();
1464     }
1465
1466     /** Check if a resource is of the specified type
1467      *
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
1471      */
1472     public function isA($resource, $type)
1473     {
1474         $this->checkResourceParam($resource, true);
1475
1476         $type = EasyRdf_Namespace::expand($type);
1477         foreach ($this->all($resource, 'rdf:type', 'resource') as $t) {
1478             if ($t->getUri() == $type) {
1479                 return true;
1480             }
1481         }
1482         return false;
1483     }
1484
1485     /** Add one or more rdf:type properties to a resource
1486      *
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
1490      */
1491     public function addType($resource, $types)
1492     {
1493         $this->checkResourceParam($resource, true);
1494
1495         if (!is_array($types)) {
1496             $types = array($types);
1497         }
1498
1499         $count = 0;
1500         foreach ($types as $type) {
1501             $type = EasyRdf_Namespace::expand($type);
1502             $count += $this->add($resource, 'rdf:type', array('type' => 'uri', 'value' => $type));
1503         }
1504
1505         return $count;
1506     }
1507
1508     /** Change the rdf:type property for a resource
1509      *
1510      * Note that if the resource object has already previously
1511      * been created, then the PHP class of the resource will not change.
1512      *
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
1516      */
1517     public function setType($resource, $type)
1518     {
1519         $this->checkResourceParam($resource, true);
1520
1521         $this->delete($resource, 'rdf:type');
1522         return $this->addType($resource, $type);
1523     }
1524
1525     /** Get a human readable label for a resource
1526      *
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.
1531      *
1532      * @return string A label for the resource.
1533      */
1534     public function label($resource = null, $lang = null)
1535     {
1536         $this->checkResourceParam($resource, true);
1537
1538         if ($resource) {
1539             return $this->get(
1540                 $resource,
1541                 'skos:prefLabel|rdfs:label|foaf:name|rss:title|dc:title|dc11:title',
1542                 'literal',
1543                 $lang
1544             );
1545         } else {
1546             return null;
1547         }
1548     }
1549
1550     /** Get the primary topic of the graph
1551      *
1552      * @return EasyRdf_Resource The primary topic of the document.
1553      */
1554     public function primaryTopic($resource = null)
1555     {
1556         $this->checkResourceParam($resource, true);
1557
1558         if ($resource) {
1559             return $this->get(
1560                 $resource,
1561                 'foaf:primaryTopic|^foaf:isPrimaryTopicOf',
1562                 'resource'
1563             );
1564         } else {
1565             return null;
1566         }
1567     }
1568
1569     /** Returns the graph as a RDF/PHP associative array
1570      *
1571      * @return array The contents of the graph as an array.
1572      */
1573     public function toRdfPhp()
1574     {
1575         return $this->index;
1576     }
1577
1578     /** Calculates the number of triples in the graph
1579      *
1580      * @return integer The number of triples in the graph.
1581      */
1582     public function countTriples()
1583     {
1584         $count = 0;
1585         foreach ($this->index as $resource) {
1586             foreach ($resource as $property => $values) {
1587                 $count += count($values);
1588             }
1589         }
1590         return $count;
1591     }
1592
1593     /** Magic method to return URI of resource when casted to string
1594      *
1595      * @return string The URI of the resource
1596      */
1597     public function __toString()
1598     {
1599         return $this->uri == null ? '' : $this->uri;
1600     }
1601
1602     /** Magic method to get a property of the graph
1603      *
1604      * Note that only properties in the default namespace can be accessed in this way.
1605      *
1606      * Example:
1607      *   $value = $graph->title;
1608      *
1609      * @see EasyRdf_Namespace::setDefault()
1610      * @param  string $name The name of the property
1611      * @return string       A single value for the named property
1612      */
1613     public function __get($name)
1614     {
1615         return $this->get($this->uri, $name);
1616     }
1617
1618     /** Magic method to set the value for a property of the graph
1619      *
1620      * Note that only properties in the default namespace can be accessed in this way.
1621      *
1622      * Example:
1623      *   $graph->title = 'Title';
1624      *
1625      * @see EasyRdf_Namespace::setDefault()
1626      * @param  string $name The name of the property
1627      * @param  string $value The value for the property
1628      */
1629     public function __set($name, $value)
1630     {
1631         return $this->set($this->uri, $name, $value);
1632     }
1633
1634     /** Magic method to check if a property exists
1635      *
1636      * Note that only properties in the default namespace can be accessed in this way.
1637      *
1638      * Example:
1639      *   if (isset($graph->title)) { blah(); }
1640      *
1641      * @see EasyRdf_Namespace::setDefault()
1642      * @param string $name The name of the property
1643      */
1644     public function __isset($name)
1645     {
1646         return $this->hasProperty($this->uri, $name);
1647     }
1648
1649     /** Magic method to delete a property of the graph
1650      *
1651      * Note that only properties in the default namespace can be accessed in this way.
1652      *
1653      * Example:
1654      *   unset($graph->title);
1655      *
1656      * @see EasyRdf_Namespace::setDefault()
1657      * @param string $name The name of the property
1658      */
1659     public function __unset($name)
1660     {
1661         return $this->delete($this->uri, $name);
1662     }
1663 }