Version 1
[yaffs-website] / vendor / easyrdf / easyrdf / lib / EasyRdf / Graph.php
diff --git a/vendor/easyrdf/easyrdf/lib/EasyRdf/Graph.php b/vendor/easyrdf/easyrdf/lib/EasyRdf/Graph.php
new file mode 100644 (file)
index 0000000..46ccc17
--- /dev/null
@@ -0,0 +1,1663 @@
+<?php
+
+/**
+ * EasyRdf
+ *
+ * LICENSE
+ *
+ * Copyright (c) 2009-2013 Nicholas J Humfrey.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
+ *    promote products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package    EasyRdf
+ * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
+ * @license    http://www.opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * Container for collection of EasyRdf_Resources.
+ *
+ * @package    EasyRdf
+ * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
+ * @license    http://www.opensource.org/licenses/bsd-license.php
+ */
+class EasyRdf_Graph
+{
+    /** The URI of the graph */
+    private $uri = null;
+    private $parsedUri = null;
+
+    /** Array of resources contained in the graph */
+    private $resources = array();
+
+    private $index = array();
+    private $revIndex = array();
+
+    /** Counter for the number of bnodes */
+    private $bNodeCount = 0;
+
+    /** Array of URLs that have been loaded into the graph */
+    private $loaded = array();
+
+    private $maxRedirects = 10;
+
+
+    /**
+     * Constructor
+     *
+     * If no URI is given then an unnamed graph is created.
+     *
+     * The $data parameter is optional and will be parsed into
+     * the graph if given.
+     *
+     * The data format is optional and should be specified if it
+     * can't be guessed by EasyRdf.
+     *
+     * @param  string  $uri     The URI of the graph
+     * @param  string  $data    Data for the graph
+     * @param  string  $format  The document type of the data (e.g. rdfxml)
+     * @return object EasyRdf_Graph
+     */
+    public function __construct($uri = null, $data = null, $format = null)
+    {
+        $this->checkResourceParam($uri, true);
+
+        if ($uri) {
+            $this->uri = $uri;
+            $this->parsedUri = new EasyRdf_ParsedUri($uri);
+            if ($data) {
+                $this->parse($data, $format, $this->uri);
+            }
+        }
+    }
+
+    /**
+     * Create a new graph and load RDF data from a URI into it
+     *
+     * This static function is shorthand for:
+     *     $graph = new EasyRdf_Graph($uri);
+     *     $graph->load($uri, $format);
+     *
+     * The document type is optional but should be specified if it
+     * can't be guessed or got from the HTTP headers.
+     *
+     * @param  string  $uri     The URI of the data to load
+     * @param  string  $format  Optional format of the data (eg. rdfxml)
+     * @return object EasyRdf_Graph    The new the graph object
+     */
+    public static function newAndLoad($uri, $format = null)
+    {
+        $graph = new self($uri);
+        $graph->load($uri, $format);
+        return $graph;
+    }
+
+    /** Get or create a resource stored in a graph
+     *
+     * If the resource did not previously exist, then a new resource will
+     * be created. If you provide an RDF type and that type is registered
+     * with the EasyRdf_TypeMapper, then the resource will be an instance
+     * of the class registered.
+     *
+     * If URI is null, then the URI of the graph is used.
+     *
+     * @param  string  $uri    The URI of the resource
+     * @param  mixed   $types  RDF type of a new resource (e.g. foaf:Person)
+     * @return object EasyRdf_Resource
+     */
+    public function resource($uri = null, $types = array())
+    {
+        $this->checkResourceParam($uri, true);
+        if (!$uri) {
+            throw new InvalidArgumentException(
+                '$uri is null and EasyRdf_Graph object has no URI either.'
+            );
+        }
+
+        // Resolve relative URIs
+        if ($this->parsedUri) {
+            $uri = $this->parsedUri->resolve($uri)->toString();
+        }
+
+        // Add the types
+        $this->addType($uri, $types);
+
+        // Create resource object if it doesn't already exist
+        if (!isset($this->resources[$uri])) {
+            $resClass = $this->classForResource($uri);
+            $this->resources[$uri] = new $resClass($uri, $this);
+        }
+
+        return $this->resources[$uri];
+    }
+
+    /** Work out the class to instantiate a resource as
+     *  @ignore
+     */
+    protected function classForResource($uri)
+    {
+        $rdfType = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
+        if (isset($this->index[$uri][$rdfType])) {
+            foreach ($this->index[$uri][$rdfType] as $type) {
+                if ($type['type'] == 'uri' or $type['type'] == 'bnode') {
+                    $class = EasyRdf_TypeMapper::get($type['value']);
+                    if ($class != null) {
+                        return $class;
+                    }
+                }
+            }
+        }
+
+        // Parsers don't typically add a rdf:type to rdf:List, so we have to
+        // do a bit of 'inference' here using properties.
+        if ($uri == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil' or
+            isset($this->index[$uri]['http://www.w3.org/1999/02/22-rdf-syntax-ns#first']) or
+            isset($this->index[$uri]['http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'])
+        ) {
+            return 'EasyRdf_Collection';
+        }
+        return 'EasyRdf_Resource';
+    }
+
+    /**
+     * Create a new blank node in the graph and return it.
+     *
+     * If you provide an RDF type and that type is registered
+     * with the EasyRdf_TypeMapper, then the resource will be an instance
+     * of the class registered.
+     *
+     * @param  mixed  $types  RDF type of a new blank node (e.g. foaf:Person)
+     * @return object EasyRdf_Resource The new blank node
+     */
+    public function newBNode($types = array())
+    {
+        return $this->resource($this->newBNodeId(), $types);
+    }
+
+    /**
+     * Create a new unique blank node identifier and return it.
+     *
+     * @return string The new blank node identifier (e.g. _:genid1)
+     */
+    public function newBNodeId()
+    {
+        return "_:genid".(++$this->bNodeCount);
+    }
+
+    /**
+     * Parse some RDF data into the graph object.
+     *
+     * @param  string  $data    Data to parse for the graph
+     * @param  string  $format  Optional format of the data
+     * @param  string  $uri     The URI of the data to load
+     * @return integer          The number of triples added to the graph
+     */
+    public function parse($data, $format = null, $uri = null)
+    {
+        $this->checkResourceParam($uri, true);
+
+        if (empty($format) or $format == 'guess') {
+            // Guess the format if it is Unknown
+            $format = EasyRdf_Format::guessFormat($data, $uri);
+        } else {
+            $format = EasyRdf_Format::getFormat($format);
+        }
+
+        if (!$format) {
+            throw new EasyRdf_Exception(
+                "Unable to parse data of an unknown format."
+            );
+        }
+
+        $parser = $format->newParser();
+        return $parser->parse($this, $data, $format, $uri);
+    }
+
+    /**
+     * Parse a file containing RDF data into the graph object.
+     *
+     * @param  string  $filename The path of the file to load
+     * @param  string  $format   Optional format of the file
+     * @param  string  $uri      The URI of the file to load
+     * @return integer           The number of triples added to the graph
+     */
+    public function parseFile($filename, $format = null, $uri = null)
+    {
+        if ($uri === null) {
+            $uri = "file://$filename";
+        }
+
+        return $this->parse(
+            file_get_contents($filename),
+            $format,
+            $uri
+        );
+    }
+
+    /**
+     * Load RDF data into the graph from a URI.
+     *
+     * If no URI is given, then the URI of the graph will be used.
+     *
+     * The document type is optional but should be specified if it
+     * can't be guessed or got from the HTTP headers.
+     *
+     * @param  string  $uri     The URI of the data to load
+     * @param  string  $format  Optional format of the data (eg. rdfxml)
+     * @return integer          The number of triples added to the graph
+     */
+    public function load($uri = null, $format = null)
+    {
+        $this->checkResourceParam($uri, true);
+
+        if (!$uri) {
+            throw new EasyRdf_Exception(
+                "No URI given to load() and the graph does not have a URI."
+            );
+        }
+
+        // Setup the HTTP client
+        $client = EasyRdf_Http::getDefaultHttpClient();
+        $client->resetParameters(true);
+        $client->setConfig(array('maxredirects' => 0));
+        $client->setMethod('GET');
+        $client->setHeaders('Accept', EasyRdf_Format::getHttpAcceptHeader());
+
+        $requestUrl = $uri;
+        $response = null;
+        $redirectCounter = 0;
+        do {
+            // Have we already loaded it into the graph?
+            $requestUrl = EasyRdf_Utils::removeFragmentFromUri($requestUrl);
+            if (in_array($requestUrl, $this->loaded)) {
+                return 0;
+            }
+
+            // Make the HTTP request
+            $client->setHeaders('host', null);
+            $client->setUri($requestUrl);
+            $response = $client->request();
+
+            // Add the URL to the list of URLs loaded
+            $this->loaded[] = $requestUrl;
+
+            if ($response->isRedirect() and $location = $response->getHeader('location')) {
+                // Avoid problems with buggy servers that add whitespace
+                $location = trim($location);
+
+                // Some servers return relative URLs in the location header
+                // resolve it in relation to previous request
+                $baseUri = new EasyRdf_ParsedUri($requestUrl);
+                $requestUrl = $baseUri->resolve($location)->toString();
+                $requestUrl = EasyRdf_Utils::removeFragmentFromUri($requestUrl);
+
+                // If it is a 303 then drop the parameters
+                if ($response->getStatus() == 303) {
+                    $client->resetParameters();
+                }
+
+                ++$redirectCounter;
+            } elseif ($response->isSuccessful()) {
+                // If we didn't get any location, stop redirecting
+                break;
+            } else {
+                throw new EasyRdf_Http_Exception(
+                    "HTTP request for {$requestUrl} failed: ".$response->getMessage(),
+                    $response->getStatus(),
+                    null,
+                    $response->getBody()
+                );
+            }
+        } while ($redirectCounter < $this->maxRedirects);
+
+        if (!$format or $format == 'guess') {
+            list($format, $params) = EasyRdf_Utils::parseMimeType(
+                $response->getHeader('Content-Type')
+            );
+        }
+
+        // Parse the data
+        return $this->parse($response->getBody(), $format, $uri);
+    }
+
+    /** Get an associative array of all the resources stored in the graph.
+     *  The keys of the array is the URI of the EasyRdf_Resource.
+     *
+     * @return array Array of EasyRdf_Resource
+     */
+    public function resources()
+    {
+        foreach ($this->index as $subject => $properties) {
+            if (!isset($this->resources[$subject])) {
+                $this->resource($subject);
+            }
+        }
+
+        foreach ($this->revIndex as $object => $properties) {
+            if (!isset($this->resources[$object])) {
+                $this->resource($object);
+            }
+        }
+
+        return $this->resources;
+    }
+
+    /** Get an arry of resources matching a certain property and optional value.
+     *
+     * For example this routine could be used as a way of getting
+     * everyone who has name:
+     * $people = $graph->resourcesMatching('foaf:name')
+     *
+     * Or everyone who is male:
+     * $people = $graph->resourcesMatching('foaf:gender', 'male');
+     *
+     * Or all homepages:
+     * $people = $graph->resourcesMatching('^foaf:homepage');
+     *
+     * @param  string  $property   The property to check.
+     * @param  mixed   $value      Optional, the value of the propery to check for.
+     * @return array   Array of EasyRdf_Resource
+     */
+    public function resourcesMatching($property, $value = null)
+    {
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        // Use the reverse index if it is an inverse property
+        if ($inverse) {
+            $index = &$this->revIndex;
+        } else {
+            $index = &$this->index;
+        }
+
+        $matched = array();
+        foreach ($index as $subject => $props) {
+            if (isset($index[$subject][$property])) {
+                if (isset($value)) {
+                    foreach ($this->index[$subject][$property] as $v) {
+                        if ($v['type'] == $value['type'] and
+                            $v['value'] == $value['value']) {
+                            $matched[] = $this->resource($subject);
+                            break;
+                        }
+                    }
+                } else {
+                    $matched[] = $this->resource($subject);
+                }
+            }
+        }
+        return $matched;
+    }
+
+    /** Get the URI of the graph
+     *
+     * @return string The URI of the graph
+     */
+    public function getUri()
+    {
+        return $this->uri;
+    }
+
+    /** Check that a URI/resource parameter is valid, and convert it to a string
+     *  @ignore
+     */
+    protected function checkResourceParam(&$resource, $allowNull = false)
+    {
+        if ($allowNull == true) {
+            if ($resource === null) {
+                if ($this->uri) {
+                    $resource = $this->uri;
+                } else {
+                    return;
+                }
+            }
+        } elseif ($resource === null) {
+            throw new InvalidArgumentException(
+                "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource. got null"
+            );
+        }
+
+        if (is_object($resource) and $resource instanceof EasyRdf_Resource) {
+            $resource = $resource->getUri();
+        } elseif (is_object($resource) and $resource instanceof EasyRdf_ParsedUri) {
+            $resource = strval($resource);
+        } elseif (is_string($resource)) {
+            if ($resource == '') {
+                throw new InvalidArgumentException(
+                    "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource. got empty string"
+                );
+            } elseif (preg_match("|^<(.+)>$|", $resource, $matches)) {
+                $resource = $matches[1];
+            } else {
+                $resource = EasyRdf_Namespace::expand($resource);
+            }
+        } else {
+            throw new InvalidArgumentException(
+                "\$resource should be either IRI, blank-node identifier or EasyRdf_Resource"
+            );
+        }
+    }
+
+    /** Check that a single URI/property parameter (not a property path)
+     *  is valid, and expand it if required
+     *  @ignore
+     */
+    protected function checkSinglePropertyParam(&$property, &$inverse)
+    {
+        if (is_object($property) and $property instanceof EasyRdf_Resource) {
+            $property = $property->getUri();
+        } elseif (is_object($property) and $property instanceof EasyRdf_ParsedUri) {
+            $property = strval($property);
+        } elseif (is_string($property)) {
+            if ($property == '') {
+                throw new InvalidArgumentException(
+                    "\$property cannot be an empty string"
+                );
+            } elseif (substr($property, 0, 1) == '^') {
+                $inverse = true;
+                $property = EasyRdf_Namespace::expand(substr($property, 1));
+            } elseif (substr($property, 0, 2) == '_:') {
+                throw new InvalidArgumentException(
+                    "\$property cannot be a blank node"
+                );
+            } else {
+                $inverse = false;
+                $property = EasyRdf_Namespace::expand($property);
+            }
+        }
+
+        if ($property === null or !is_string($property)) {
+            throw new InvalidArgumentException(
+                "\$property should be a string or EasyRdf_Resource and cannot be null"
+            );
+        }
+    }
+
+    /** Check that a value parameter is valid, and convert it to an associative array if needed
+     *  @ignore
+     */
+    protected function checkValueParam(&$value)
+    {
+        if (isset($value)) {
+            if (is_object($value)) {
+                if (!method_exists($value, 'toRdfPhp')) {
+                    // Convert to a literal object
+                    $value = EasyRdf_Literal::create($value);
+                }
+                $value = $value->toRdfPhp();
+            } elseif (is_array($value)) {
+                if (!isset($value['type'])) {
+                    throw new InvalidArgumentException(
+                        "\$value is missing a 'type' key"
+                    );
+                }
+
+                if (!isset($value['value'])) {
+                    throw new InvalidArgumentException(
+                        "\$value is missing a 'value' key"
+                    );
+                }
+
+                // Fix ordering and remove unknown keys
+                $value = array(
+                    'type' => strval($value['type']),
+                    'value' => strval($value['value']),
+                    'lang' => isset($value['lang']) ? strval($value['lang']) : null,
+                    'datatype' => isset($value['datatype']) ? strval($value['datatype']) : null
+                );
+            } else {
+                $value = array(
+                    'type' => 'literal',
+                    'value' => strval($value),
+                    'datatype' => EasyRdf_Literal::getDatatypeForValue($value)
+                );
+            }
+            if (!in_array($value['type'], array('uri', 'bnode', 'literal'), true)) {
+                throw new InvalidArgumentException(
+                    "\$value does not have a valid type (".$value['type'].")"
+                );
+            }
+            if (empty($value['datatype'])) {
+                unset($value['datatype']);
+            }
+            if (empty($value['lang'])) {
+                unset($value['lang']);
+            }
+            if (isset($value['lang']) and isset($value['datatype'])) {
+                throw new InvalidArgumentException(
+                    "\$value cannot have both and language and a datatype"
+                );
+            }
+        }
+    }
+
+    /** Get a single value for a property of a resource
+     *
+     * If multiple values are set for a property then the value returned
+     * may be arbitrary.
+     *
+     * If $property is an array, then the first item in the array that matches
+     * a property that exists is returned.
+     *
+     * This method will return null if the property does not exist.
+     *
+     * @param  string    $resource       The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string    $propertyPath   A valid property path
+     * @param  string    $type           The type of value to filter by (e.g. literal or resource)
+     * @param  string    $lang           The language to filter by (e.g. en)
+     * @return mixed                     A value associated with the property
+     */
+    public function get($resource, $propertyPath, $type = null, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+
+        if (is_object($propertyPath) and $propertyPath instanceof EasyRdf_Resource) {
+            return $this->getSingleProperty($resource, $propertyPath->getUri(), $type, $lang);
+        } elseif (is_string($propertyPath) and preg_match('|^(\^?)<(.+)>|', $propertyPath, $matches)) {
+            return $this->getSingleProperty($resource, "$matches[1]$matches[2]", $type, $lang);
+        } elseif ($propertyPath === null or !is_string($propertyPath)) {
+            throw new InvalidArgumentException(
+                "\$propertyPath should be a string or EasyRdf_Resource and cannot be null"
+            );
+        } elseif ($propertyPath === '') {
+            throw new InvalidArgumentException(
+                "\$propertyPath cannot be an empty string"
+            );
+        }
+
+        // Loop through each component in the path
+        foreach (explode('/', $propertyPath) as $part) {
+            // Stop if we come to a literal
+            if ($resource instanceof EasyRdf_Literal) {
+                return null;
+            }
+
+            // Try each of the alternative paths
+            foreach (explode('|', $part) as $p) {
+                $res = $this->getSingleProperty($resource, $p, $type, $lang);
+                if ($res) {
+                    break;
+                }
+            }
+
+            // Stop if nothing was found
+            $resource = $res;
+            if (!$resource) {
+                break;
+            }
+        }
+
+        return $resource;
+    }
+
+    /** Get a single value for a property of a resource
+     *
+     * @param  string    $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string    $property The name of the property (e.g. foaf:name)
+     * @param  string    $type     The type of value to filter by (e.g. literal or resource)
+     * @param  string    $lang     The language to filter by (e.g. en)
+     * @return mixed               A value associated with the property
+     *
+     * @ignore
+     */
+    protected function getSingleProperty($resource, $property, $type = null, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+
+        // Get an array of values for the property
+        $values = $this->propertyValuesArray($resource, $property, $inverse);
+        if (!isset($values)) {
+            return null;
+        }
+
+        // Filter the results
+        $result = null;
+        if ($type) {
+            foreach ($values as $value) {
+                if ($type == 'literal' and $value['type'] == 'literal') {
+                    if ($lang == null or (isset($value['lang']) and $value['lang'] == $lang)) {
+                        $result = $value;
+                        break;
+                    }
+                } elseif ($type == 'resource') {
+                    if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
+                        $result = $value;
+                        break;
+                    }
+                }
+            }
+        } else {
+            $result = $values[0];
+        }
+
+        // Convert the internal data structure into a PHP object
+        return $this->arrayToObject($result);
+    }
+
+    /** Get a single literal value for a property of a resource
+     *
+     * If multiple values are set for a property then the value returned
+     * may be arbitrary.
+     *
+     * This method will return null if there is not literal value for the
+     * property.
+     *
+     * @param  string       $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string|array $property The name of the property (e.g. foaf:name)
+     * @param  string       $lang     The language to filter by (e.g. en)
+     * @return object EasyRdf_Literal Literal value associated with the property
+     */
+    public function getLiteral($resource, $property, $lang = null)
+    {
+        return $this->get($resource, $property, 'literal', $lang);
+    }
+
+    /** Get a single resource value for a property of a resource
+     *
+     * If multiple values are set for a property then the value returned
+     * may be arbitrary.
+     *
+     * This method will return null if there is not resource for the
+     * property.
+     *
+     * @param  string       $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string|array $property The name of the property (e.g. foaf:name)
+     * @return object EasyRdf_Resource Resource associated with the property
+     */
+    public function getResource($resource, $property)
+    {
+        return $this->get($resource, $property, 'resource');
+    }
+
+    /** Return all the values for a particular property of a resource
+     *  @ignore
+     */
+    protected function propertyValuesArray($resource, $property, $inverse = false)
+    {
+        // Is an inverse property being requested?
+        if ($inverse) {
+            if (isset($this->revIndex[$resource])) {
+                $properties = &$this->revIndex[$resource];
+            }
+        } else {
+            if (isset($this->index[$resource])) {
+                $properties = &$this->index[$resource];
+            }
+        }
+
+        if (isset($properties[$property])) {
+            return $properties[$property];
+        } else {
+            return null;
+        }
+    }
+
+    /** Get an EasyRdf_Resource or EasyRdf_Literal object from an associative array.
+     *  @ignore
+     */
+    protected function arrayToObject($data)
+    {
+        if ($data) {
+            if ($data['type'] == 'uri' or $data['type'] == 'bnode') {
+                return $this->resource($data['value']);
+            } else {
+                return EasyRdf_Literal::create($data);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /** Get all values for a property path
+     *
+     * This method will return an empty array if the property does not exist.
+     *
+     * @param  string  $resource      The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string  $propertyPath  A valid property path
+     * @param  string  $type          The type of value to filter by (e.g. literal)
+     * @param  string  $lang          The language to filter by (e.g. en)
+     * @return array                  An array of values associated with the property
+     */
+    public function all($resource, $propertyPath, $type = null, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+
+        if (is_object($propertyPath) and $propertyPath instanceof EasyRdf_Resource) {
+            return $this->allForSingleProperty($resource, $propertyPath->getUri(), $type, $lang);
+        } elseif (is_string($propertyPath) and preg_match('|^(\^?)<(.+)>|', $propertyPath, $matches)) {
+            return $this->allForSingleProperty($resource, "$matches[1]$matches[2]", $type, $lang);
+        } elseif ($propertyPath === null or !is_string($propertyPath)) {
+            throw new InvalidArgumentException(
+                "\$propertyPath should be a string or EasyRdf_Resource and cannot be null"
+            );
+        } elseif ($propertyPath === '') {
+            throw new InvalidArgumentException(
+                "\$propertyPath cannot be an empty string"
+            );
+        }
+
+        $objects = array($resource);
+
+        // Loop through each component in the path
+        foreach (explode('/', $propertyPath) as $part) {
+
+            $results = array();
+            foreach (explode('|', $part) as $p) {
+                foreach ($objects as $o) {
+                    // Ignore literals found earlier in path
+                    if ($o instanceof EasyRdf_Literal) {
+                        continue;
+                    }
+
+                    $results = array_merge(
+                        $results,
+                        $this->allForSingleProperty($o, $p, $type, $lang)
+                    );
+                }
+            }
+
+            // Stop if we don't have anything
+            if (empty($objects)) {
+                break;
+            }
+
+            // Use the results as the input to the next iteration
+            $objects = $results;
+        }
+
+        return $results;
+    }
+
+    /** Get all values for a single property of a resource
+     *
+     * @param  string  $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  string  $type     The type of value to filter by (e.g. literal)
+     * @param  string  $lang     The language to filter by (e.g. en)
+     * @return array             An array of values associated with the property
+     *
+     * @ignore
+     */
+    protected function allForSingleProperty($resource, $property, $type = null, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+
+        // Get an array of values for the property
+        $values = $this->propertyValuesArray($resource, $property, $inverse);
+        if (!isset($values)) {
+            return array();
+        }
+
+        $objects = array();
+        if ($type) {
+            foreach ($values as $value) {
+                if ($type == 'literal' and $value['type'] == 'literal') {
+                    if ($lang == null or (isset($value['lang']) and $value['lang'] == $lang)) {
+                        $objects[] = $this->arrayToObject($value);
+                    }
+                } elseif ($type == 'resource') {
+                    if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
+                        $objects[] = $this->arrayToObject($value);
+                    }
+                }
+            }
+        } else {
+            foreach ($values as $value) {
+                $objects[] = $this->arrayToObject($value);
+            }
+        }
+        return $objects;
+    }
+
+    /** Get all literal values for a property of a resource
+     *
+     * This method will return an empty array if the resource does not
+     * has any literal values for that property.
+     *
+     * @param  string  $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  string  $lang     The language to filter by (e.g. en)
+     * @return array             An array of values associated with the property
+     */
+    public function allLiterals($resource, $property, $lang = null)
+    {
+        return $this->all($resource, $property, 'literal', $lang);
+    }
+
+    /** Get all resources for a property of a resource
+     *
+     * This method will return an empty array if the resource does not
+     * has any resources for that property.
+     *
+     * @param  string  $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @return array             An array of values associated with the property
+     */
+    public function allResources($resource, $property)
+    {
+        return $this->all($resource, $property, 'resource');
+    }
+
+    /** Get all the resources in the graph of a certain type
+     *
+     * If no resources of the type are available and empty
+     * array is returned.
+     *
+     * @param  string  $type   The type of the resource (e.g. foaf:Person)
+     * @return array The array of resources
+     */
+    public function allOfType($type)
+    {
+        return $this->all($type, '^rdf:type');
+    }
+
+    /** Count the number of values for a property of a resource
+     *
+     * @param  string  $resource The URI of the resource (e.g. http://example.com/joe#me)
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  string  $type     The type of value to filter by (e.g. literal)
+     * @param  string  $lang     The language to filter by (e.g. en)
+     * @return integer           The number of values for this property
+     */
+    public function countValues($resource, $property, $type = null, $lang = null)
+    {
+        return count($this->all($resource, $property, $type, $lang));
+    }
+
+    /** Concatenate all values for a property of a resource into a string.
+     *
+     * The default is to join the values together with a space character.
+     * This method will return an empty string if the property does not exist.
+     *
+     * @param  mixed   $resource The resource to get the property on
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  string  $glue     The string to glue the values together with.
+     * @param  string  $lang     The language to filter by (e.g. en)
+     * @return string            Concatenation of all the values.
+     */
+    public function join($resource, $property, $glue = ' ', $lang = null)
+    {
+        return join($glue, $this->all($resource, $property, 'literal', $lang));
+    }
+
+    /** Add data to the graph
+     *
+     * The resource can either be a resource or the URI of a resource.
+     *
+     * Example:
+     *   $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
+     *
+     * @param  mixed $resource   The resource to add data to
+     * @param  mixed $property   The property name
+     * @param  mixed $value      The new value for the property
+     * @return integer           The number of values added (1 or 0)
+     */
+    public function add($resource, $property, $value)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        // No value given?
+        if ($value === null) {
+            return 0;
+        }
+
+        // Check that the value doesn't already exist
+        if (isset($this->index[$resource][$property])) {
+            foreach ($this->index[$resource][$property] as $v) {
+                if ($v == $value) {
+                    return 0;
+                }
+            }
+        }
+        $this->index[$resource][$property][] = $value;
+
+        // Add to the reverse index if it is a resource
+        if ($value['type'] == 'uri' or $value['type'] == 'bnode') {
+            $uri = $value['value'];
+            $this->revIndex[$uri][$property][] = array(
+                'type' => substr($resource, 0, 2) == '_:' ? 'bnode' : 'uri',
+                'value' => $resource
+            );
+        }
+
+        // Success
+        return 1;
+    }
+
+    /** Add a literal value as a property of a resource
+     *
+     * The resource can either be a resource or the URI of a resource.
+     * The value can either be a single value or an array of values.
+     *
+     * Example:
+     *   $graph->add("http://www.example.com", 'dc:title', 'Title of Page');
+     *
+     * @param  mixed  $resource  The resource to add data to
+     * @param  mixed  $property  The property name
+     * @param  mixed  $value     The value or values for the property
+     * @param  string $lang      The language of the literal
+     * @return integer           The number of values added
+     */
+    public function addLiteral($resource, $property, $value, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+
+        if (is_array($value)) {
+            $added = 0;
+            foreach ($value as $v) {
+                $added += $this->addLiteral($resource, $property, $v, $lang);
+            }
+            return $added;
+        } elseif (!is_object($value) or !$value instanceof EasyRdf_Literal) {
+            $value = EasyRdf_Literal::create($value, $lang);
+        }
+        return $this->add($resource, $property, $value);
+    }
+
+    /** Add a resource as a property of another resource
+     *
+     * The resource can either be a resource or the URI of a resource.
+     *
+     * Example:
+     *   $graph->add("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
+     *
+     * @param  mixed $resource   The resource to add data to
+     * @param  mixed $property   The property name
+     * @param  mixed $resource2  The resource to be value of the property
+     * @return integer           The number of values added
+     */
+    public function addResource($resource, $property, $resource2)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkResourceParam($resource2);
+
+        return $this->add(
+            $resource,
+            $property,
+            array(
+                'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
+                'value' => $resource2
+            )
+        );
+    }
+
+    /** Set a value for a property
+     *
+     * The new value will replace the existing values for the property.
+     *
+     * @param  string  $resource The resource to set the property on
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  mixed   $value    The value for the property
+     * @return integer           The number of values added (1 or 0)
+     */
+    public function set($resource, $property, $value)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        // Delete the old values
+        $this->delete($resource, $property);
+
+        // Add the new values
+        return $this->add($resource, $property, $value);
+    }
+
+    /** Delete a property (or optionally just a specific value)
+     *
+     * @param  mixed   $resource The resource to delete the property from
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  mixed   $value The value to delete (null to delete all values)
+     * @return integer The number of values deleted
+     */
+    public function delete($resource, $property, $value = null)
+    {
+        $this->checkResourceParam($resource);
+
+        if (is_object($property) and $property instanceof EasyRdf_Resource) {
+            return $this->deleteSingleProperty($resource, $property->getUri(), $value);
+        } elseif (is_string($property) and preg_match('|^(\^?)<(.+)>|', $property, $matches)) {
+            return $this->deleteSingleProperty($resource, "$matches[1]$matches[2]", $value);
+        } elseif ($property === null or !is_string($property)) {
+            throw new InvalidArgumentException(
+                "\$property should be a string or EasyRdf_Resource and cannot be null"
+            );
+        } elseif ($property === '') {
+            throw new InvalidArgumentException(
+                "\$property cannot be an empty string"
+            );
+        }
+
+        // FIXME: finish implementing property paths for delete
+        return $this->deleteSingleProperty($resource, $property, $value);
+    }
+
+
+    /** Delete a property (or optionally just a specific value)
+     *
+     * @param  mixed   $resource The resource to delete the property from
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  mixed   $value The value to delete (null to delete all values)
+     * @return integer The number of values deleted
+     *
+     * @ignore
+     */
+    public function deleteSingleProperty($resource, $property, $value = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        $count = 0;
+        if (isset($this->index[$resource][$property])) {
+            foreach ($this->index[$resource][$property] as $k => $v) {
+                if (!$value or $v == $value) {
+                    unset($this->index[$resource][$property][$k]);
+                    $count++;
+                    if ($v['type'] == 'uri' or $v['type'] == 'bnode') {
+                        $this->deleteInverse($v['value'], $property, $resource);
+                    }
+                }
+            }
+
+            // Clean up the indexes - remove empty properties and resources
+            if ($count) {
+                if (count($this->index[$resource][$property]) == 0) {
+                    unset($this->index[$resource][$property]);
+                }
+                if (count($this->index[$resource]) == 0) {
+                    unset($this->index[$resource]);
+                }
+            }
+        }
+
+        return $count;
+    }
+
+    /** Delete a resource from a property of another resource
+     *
+     * The resource can either be a resource or the URI of a resource.
+     *
+     * Example:
+     *   $graph->delete("http://example.com/bob", 'foaf:knows', 'http://example.com/alice');
+     *
+     * @param  mixed $resource   The resource to delete data from
+     * @param  mixed $property   The property name
+     * @param  mixed $resource2  The resource value of the property to be deleted
+     */
+    public function deleteResource($resource, $property, $resource2)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkResourceParam($resource2);
+
+        return $this->delete(
+            $resource,
+            $property,
+            array(
+                'type' => substr($resource2, 0, 2) == '_:' ? 'bnode' : 'uri',
+                'value' => $resource2
+            )
+        );
+    }
+
+    /** Delete a literal value from a property of a resource
+     *
+     * Example:
+     *   $graph->delete("http://www.example.com", 'dc:title', 'Title of Page');
+     *
+     * @param  mixed  $resource  The resource to add data to
+     * @param  mixed  $property  The property name
+     * @param  mixed  $value     The value of the property
+     * @param  string $lang      The language of the literal
+     */
+    public function deleteLiteral($resource, $property, $value, $lang = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        if ($lang) {
+            $value['lang'] = $lang;
+        }
+
+        return $this->delete($resource, $property, $value);
+    }
+
+    /** This function is for internal use only.
+     *
+     * Deletes an inverse property from a resource.
+     *
+     * @ignore
+     */
+    protected function deleteInverse($resource, $property, $value)
+    {
+        if (isset($this->revIndex[$resource])) {
+            foreach ($this->revIndex[$resource][$property] as $k => $v) {
+                if ($v['value'] === $value) {
+                    unset($this->revIndex[$resource][$property][$k]);
+                }
+            }
+            if (count($this->revIndex[$resource][$property]) == 0) {
+                unset($this->revIndex[$resource][$property]);
+            }
+            if (count($this->revIndex[$resource]) == 0) {
+                unset($this->revIndex[$resource]);
+            }
+        }
+    }
+
+    /** Check if the graph contains any statements
+     *
+     * @return boolean True if the graph contains no statements
+     */
+    public function isEmpty()
+    {
+        return count($this->index) == 0;
+    }
+
+    /** Get a list of all the shortened property names (qnames) for a resource.
+     *
+     * This method will return an empty array if the resource has no properties.
+     *
+     * @return array            Array of shortened URIs
+     */
+    public function properties($resource)
+    {
+        $this->checkResourceParam($resource);
+
+        $properties = array();
+        if (isset($this->index[$resource])) {
+            foreach ($this->index[$resource] as $property => $value) {
+                $short = EasyRdf_Namespace::shorten($property);
+                if ($short) {
+                    $properties[] = $short;
+                }
+            }
+        }
+        return $properties;
+    }
+
+    /** Get a list of the full URIs for the properties of a resource.
+     *
+     * This method will return an empty array if the resource has no properties.
+     *
+     * @return array            Array of full URIs
+     */
+    public function propertyUris($resource)
+    {
+        $this->checkResourceParam($resource);
+
+        if (isset($this->index[$resource])) {
+            return array_keys($this->index[$resource]);
+        } else {
+            return array();
+        }
+    }
+
+    /** Get a list of the full URIs for the properties that point to a resource.
+     *
+     * @return array   Array of full property URIs
+     */
+    public function reversePropertyUris($resource)
+    {
+        $this->checkResourceParam($resource);
+
+        if (isset($this->revIndex[$resource])) {
+            return array_keys($this->revIndex[$resource]);
+        } else {
+            return array();
+        }
+    }
+
+    /** Check to see if a property exists for a resource.
+     *
+     * This method will return true if the property exists.
+     * If the value parameter is given, then it will only return true
+     * if the value also exists for that property.
+     *
+     * By providing a value parameter you can use this function to check
+     * to see if a triple exists in the graph.
+     *
+     * @param  mixed   $resource The resource to check
+     * @param  string  $property The name of the property (e.g. foaf:name)
+     * @param  mixed   $value    An optional value of the property
+     * @return boolean           True if value the property exists.
+     */
+    public function hasProperty($resource, $property, $value = null)
+    {
+        $this->checkResourceParam($resource);
+        $this->checkSinglePropertyParam($property, $inverse);
+        $this->checkValueParam($value);
+
+        // Use the reverse index if it is an inverse property
+        if ($inverse) {
+            $index = &$this->revIndex;
+        } else {
+            $index = &$this->index;
+        }
+
+        if (isset($index[$resource][$property])) {
+            if (is_null($value)) {
+                return true;
+            } else {
+                foreach ($index[$resource][$property] as $v) {
+                    if ($v == $value) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /** Serialise the graph into RDF
+     *
+     * The $format parameter can be an EasyRdf_Format object, a
+     * format name, a mime type or a file extension.
+     *
+     * Example:
+     *   $turtle = $graph->serialise('turtle');
+     *
+     * @param  mixed $format  The format to serialise to
+     * @param  array $options Serialiser-specific options, for fine-tuning the output
+     * @return mixed  The serialised graph
+     */
+    public function serialise($format, array $options = array())
+    {
+        if (!$format instanceof EasyRdf_Format) {
+            $format = EasyRdf_Format::getFormat($format);
+        }
+        $serialiser = $format->newSerialiser();
+        return $serialiser->serialise($this, $format->getName(), $options);
+    }
+
+    /** Return a human readable view of all the resources in the graph
+     *
+     * This method is intended to be a debugging aid and will
+     * return a pretty-print view of all the resources and their
+     * properties.
+     *
+     * @param  string  $format  Either 'html' or 'text'
+     * @return string
+     */
+    public function dump($format = 'html')
+    {
+        $result = '';
+        if ($format == 'html') {
+            $result .= "<div style='font-family:arial; font-weight: bold; padding:0.5em; ".
+                   "color: black; background-color:lightgrey;border:dashed 1px grey;'>".
+                   "Graph: ". $this->uri . "</div>\n";
+        } else {
+            $result .= "Graph: ". $this->uri . "\n";
+        }
+
+        foreach ($this->index as $resource => $properties) {
+            $result .= $this->dumpResource($resource, $format);
+        }
+        return $result;
+    }
+
+    /** Return a human readable view of a resource and its properties
+     *
+     * This method is intended to be a debugging aid and will
+     * print a resource and its properties.
+     *
+     * @param  mixed    $resource  The resource to dump
+     * @param  string   $format    Either 'html' or 'text'
+     * @return string
+     */
+    public function dumpResource($resource, $format = 'html')
+    {
+        $this->checkResourceParam($resource, true);
+
+        if (isset($this->index[$resource])) {
+            $properties = $this->index[$resource];
+        } else {
+            return '';
+        }
+
+        $plist = array();
+        foreach ($properties as $property => $values) {
+            $olist = array();
+            foreach ($values as $value) {
+                if ($value['type'] == 'literal') {
+                    $olist []= EasyRdf_Utils::dumpLiteralValue($value, $format, 'black');
+                } else {
+                    $olist []= EasyRdf_Utils::dumpResourceValue($value['value'], $format, 'blue');
+                }
+            }
+
+            $pstr = EasyRdf_Namespace::shorten($property);
+            if ($pstr == null) {
+                $pstr = $property;
+            }
+            if ($format == 'html') {
+                $plist []= "<span style='font-size:130%'>&rarr;</span> ".
+                           "<span style='text-decoration:none;color:green'>".
+                           htmlentities($pstr) . "</span> ".
+                           "<span style='font-size:130%'>&rarr;</span> ".
+                           join(", ", $olist);
+            } else {
+                $plist []= "  -> $pstr -> " . join(", ", $olist);
+            }
+        }
+
+        if ($format == 'html') {
+            return "<div id='".htmlentities($resource, ENT_QUOTES)."' " .
+                   "style='font-family:arial; padding:0.5em; ".
+                   "background-color:lightgrey;border:dashed 1px grey;'>\n".
+                   "<div>".EasyRdf_Utils::dumpResourceValue($resource, $format, 'blue')." ".
+                   "<span style='font-size: 0.8em'>(".
+                   $this->classForResource($resource).")</span></div>\n".
+                   "<div style='padding-left: 3em'>\n".
+                   "<div>".join("</div>\n<div>", $plist)."</div>".
+                   "</div></div>\n";
+        } else {
+            return $resource." (".$this->classForResource($resource).")\n" .
+                   join("\n", $plist) . "\n\n";
+        }
+    }
+
+    /** Get the resource type of the graph
+     *
+     * The type will be a shortened URI as a string.
+     * If the graph has multiple types then the type returned
+     * may be arbitrary.
+     * This method will return null if the resource has no type.
+     *
+     * @return string A type assocated with the resource (e.g. foaf:Document)
+     */
+    public function type($resource = null)
+    {
+        $type = $this->typeAsResource($resource);
+
+        if ($type) {
+            return EasyRdf_Namespace::shorten($type);
+        }
+
+        return null;
+    }
+
+    /** Get the resource type of the graph as a EasyRdf_Resource
+     *
+     * If the graph has multiple types then the type returned
+     * may be arbitrary.
+     * This method will return null if the resource has no type.
+     *
+     * @return object EasyRdf_Resource  A type assocated with the resource
+     */
+    public function typeAsResource($resource = null)
+    {
+        $this->checkResourceParam($resource, true);
+
+        if ($resource) {
+            return $this->get($resource, 'rdf:type', 'resource');
+        }
+
+        return null;
+    }
+
+    /** Get a list of types for a resource
+     *
+     * The types will each be a shortened URI as a string.
+     * This method will return an empty array if the resource has no types.
+     *
+     * If $resource is null, then it will get the types for the URI of the graph.
+     *
+     * @return array All types assocated with the resource (e.g. foaf:Person)
+     */
+    public function types($resource = null)
+    {
+        $resources = $this->typesAsResources($resource);
+
+        $types = array();
+        foreach ($resources as $type) {
+            $types[] = EasyRdf_Namespace::shorten($type);
+        }
+
+        return $types;
+    }
+
+    /**
+     * Get the resource types of the graph as a EasyRdf_Resource
+     *
+     * @return EasyRdf_Resource[]
+     */
+    public function typesAsResources($resource = null)
+    {
+        $this->checkResourceParam($resource, true);
+
+        if ($resource) {
+            return $this->all($resource, 'rdf:type', 'resource');
+        }
+
+        return array();
+    }
+
+    /** Check if a resource is of the specified type
+     *
+     * @param  string  $resource The resource to check the type of
+     * @param  string  $type     The type to check (e.g. foaf:Person)
+     * @return boolean           True if resource is of specified type
+     */
+    public function isA($resource, $type)
+    {
+        $this->checkResourceParam($resource, true);
+
+        $type = EasyRdf_Namespace::expand($type);
+        foreach ($this->all($resource, 'rdf:type', 'resource') as $t) {
+            if ($t->getUri() == $type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Add one or more rdf:type properties to a resource
+     *
+     * @param  string  $resource The resource to add the type to
+     * @param  string  $types    One or more types to add (e.g. foaf:Person)
+     * @return integer           The number of types added
+     */
+    public function addType($resource, $types)
+    {
+        $this->checkResourceParam($resource, true);
+
+        if (!is_array($types)) {
+            $types = array($types);
+        }
+
+        $count = 0;
+        foreach ($types as $type) {
+            $type = EasyRdf_Namespace::expand($type);
+            $count += $this->add($resource, 'rdf:type', array('type' => 'uri', 'value' => $type));
+        }
+
+        return $count;
+    }
+
+    /** Change the rdf:type property for a resource
+     *
+     * Note that if the resource object has already previously
+     * been created, then the PHP class of the resource will not change.
+     *
+     * @param  string  $resource The resource to change the type of
+     * @param  string  $type     The new type (e.g. foaf:Person)
+     * @return integer           The number of types added
+     */
+    public function setType($resource, $type)
+    {
+        $this->checkResourceParam($resource, true);
+
+        $this->delete($resource, 'rdf:type');
+        return $this->addType($resource, $type);
+    }
+
+    /** Get a human readable label for a resource
+     *
+     * This method will check a number of properties for a resource
+     * (in the order: skos:prefLabel, rdfs:label, foaf:name, dc:title)
+     * and return an approriate first that is available. If no label
+     * is available then it will return null.
+     *
+     * @return string A label for the resource.
+     */
+    public function label($resource = null, $lang = null)
+    {
+        $this->checkResourceParam($resource, true);
+
+        if ($resource) {
+            return $this->get(
+                $resource,
+                'skos:prefLabel|rdfs:label|foaf:name|rss:title|dc:title|dc11:title',
+                'literal',
+                $lang
+            );
+        } else {
+            return null;
+        }
+    }
+
+    /** Get the primary topic of the graph
+     *
+     * @return EasyRdf_Resource The primary topic of the document.
+     */
+    public function primaryTopic($resource = null)
+    {
+        $this->checkResourceParam($resource, true);
+
+        if ($resource) {
+            return $this->get(
+                $resource,
+                'foaf:primaryTopic|^foaf:isPrimaryTopicOf',
+                'resource'
+            );
+        } else {
+            return null;
+        }
+    }
+
+    /** Returns the graph as a RDF/PHP associative array
+     *
+     * @return array The contents of the graph as an array.
+     */
+    public function toRdfPhp()
+    {
+        return $this->index;
+    }
+
+    /** Calculates the number of triples in the graph
+     *
+     * @return integer The number of triples in the graph.
+     */
+    public function countTriples()
+    {
+        $count = 0;
+        foreach ($this->index as $resource) {
+            foreach ($resource as $property => $values) {
+                $count += count($values);
+            }
+        }
+        return $count;
+    }
+
+    /** Magic method to return URI of resource when casted to string
+     *
+     * @return string The URI of the resource
+     */
+    public function __toString()
+    {
+        return $this->uri == null ? '' : $this->uri;
+    }
+
+    /** Magic method to get a property of the graph
+     *
+     * Note that only properties in the default namespace can be accessed in this way.
+     *
+     * Example:
+     *   $value = $graph->title;
+     *
+     * @see EasyRdf_Namespace::setDefault()
+     * @param  string $name The name of the property
+     * @return string       A single value for the named property
+     */
+    public function __get($name)
+    {
+        return $this->get($this->uri, $name);
+    }
+
+    /** Magic method to set the value for a property of the graph
+     *
+     * Note that only properties in the default namespace can be accessed in this way.
+     *
+     * Example:
+     *   $graph->title = 'Title';
+     *
+     * @see EasyRdf_Namespace::setDefault()
+     * @param  string $name The name of the property
+     * @param  string $value The value for the property
+     */
+    public function __set($name, $value)
+    {
+        return $this->set($this->uri, $name, $value);
+    }
+
+    /** Magic method to check if a property exists
+     *
+     * Note that only properties in the default namespace can be accessed in this way.
+     *
+     * Example:
+     *   if (isset($graph->title)) { blah(); }
+     *
+     * @see EasyRdf_Namespace::setDefault()
+     * @param string $name The name of the property
+     */
+    public function __isset($name)
+    {
+        return $this->hasProperty($this->uri, $name);
+    }
+
+    /** Magic method to delete a property of the graph
+     *
+     * Note that only properties in the default namespace can be accessed in this way.
+     *
+     * Example:
+     *   unset($graph->title);
+     *
+     * @see EasyRdf_Namespace::setDefault()
+     * @param string $name The name of the property
+     */
+    public function __unset($name)
+    {
+        return $this->delete($this->uri, $name);
+    }
+}