8 * Copyright (c) 2013 Nicholas J Humfrey. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution.
17 * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
18 * promote products derived from this software without specific prior
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
34 * @copyright Copyright (c) 2013 Nicholas J Humfrey
35 * @license http://www.opensource.org/licenses/bsd-license.php
39 * Sub-class of EasyRdf_Resource that represents an RDF collection (rdf:List)
41 * This class can be used to iterate through a collection of items.
43 * Note that items are numbered from 1 (not 0) for consistency with RDF Containers.
46 * @link http://www.w3.org/TR/xmlschema-2/#date
47 * @copyright Copyright (c) 2013 Nicholas J Humfrey
48 * @license http://www.opensource.org/licenses/bsd-license.php
50 class EasyRdf_Collection extends EasyRdf_Resource implements ArrayAccess, Countable, SeekableIterator
55 /** Create a new collection - do not use this directly
59 public function __construct($uri, $graph)
62 $this->current = null;
63 parent::__construct($uri, $graph);
66 /** Seek to a specific position in the container
68 * The first item is postion 1
70 * @param integer $position The position in the container to seek to
71 * @throws OutOfBoundsException
73 public function seek($position)
75 if (is_int($position) and $position > 0) {
76 list($node, $actual) = $this->getCollectionNode($position);
77 if ($actual === $position) {
78 $this->position = $actual;
79 $this->current = $node;
81 throw new OutOfBoundsException(
82 "Unable to seek to position $position in the collection"
86 throw new InvalidArgumentException(
87 "Collection position must be a positive integer"
92 /** Rewind the iterator back to the start of the collection
95 public function rewind()
98 $this->current = null;
101 /** Return the current item in the collection
103 * @return mixed The current item
105 public function current()
107 if ($this->position === 1) {
108 return $this->get('rdf:first');
109 } elseif ($this->current) {
110 return $this->current->get('rdf:first');
114 /** Return the key / current position in the collection
116 * Note: the first item is number 1
118 * @return int The current position
120 public function key()
122 return $this->position;
125 /** Move forward to next item in the collection
128 public function next()
130 if ($this->position === 1) {
131 $this->current = $this->get('rdf:rest');
132 } elseif ($this->current) {
133 $this->current = $this->current->get('rdf:rest');
138 /** Checks if current position is valid
140 * @return bool True if the current position is valid
142 public function valid()
144 if ($this->position === 1 and $this->hasProperty('rdf:first')) {
146 } elseif ($this->current !== null and $this->current->hasProperty('rdf:first')) {
153 /** Get a node for a particular offset into the collection
155 * This function may not return the item you requested, if
156 * it does not exist. Please check the $postion parameter
159 * If the offset is null, then the last node in the
160 * collection (before rdf:nil) will be returned.
162 * @param integer $offset The offset into the collection (or null)
163 * @return array $node, $postion The node object and postion of the node
165 public function getCollectionNode($offset)
169 $nil = $this->graph->resource('rdf:nil');
170 while (($rest = $node->get('rdf:rest')) and $rest !== $nil and (is_null($offset) or ($position < $offset))) {
174 return array($node, $position);
177 /** Counts the number of items in the collection
179 * Note that this is an slow method - it is more efficient to use
180 * the iterator interface, if you can.
182 * @return integer The number of items in the collection
184 public function count()
186 // Find the end of the collection
187 list($node, $position) = $this->getCollectionNode(null);
188 if (!$node->hasProperty('rdf:first')) {
195 /** Append an item to the end of the collection
197 * @param mixed $value The value to append
198 * @return integer The number of values appended (1 or 0)
200 public function append($value)
202 // Find the end of the collection
203 list($node, $position) = $this->getCollectionNode(null);
204 $rest = $node->get('rdf:rest');
206 if ($node === $this and is_null($rest)) {
207 $node->set('rdf:first', $value);
208 $node->addResource('rdf:rest', 'rdf:nil');
210 $new = $this->graph->newBnode();
211 $node->set('rdf:rest', $new);
212 $new->add('rdf:first', $value);
213 $new->addResource('rdf:rest', 'rdf:nil');
219 /** Array Access: check if a position exists in collection using array syntax
221 * Example: isset($list[2])
223 public function offsetExists($offset)
225 if (is_int($offset) and $offset > 0) {
226 list($node, $position) = $this->getCollectionNode($offset);
227 return ($node and $position === $offset and $node->hasProperty('rdf:first'));
229 throw new InvalidArgumentException(
230 "Collection offset must be a positive integer"
235 /** Array Access: get an item at a specified position in collection using array syntax
237 * Example: $item = $list[2];
239 public function offsetGet($offset)
241 if (is_int($offset) and $offset > 0) {
242 list($node, $position) = $this->getCollectionNode($offset);
243 if ($node and $position === $offset) {
244 return $node->get('rdf:first');
247 throw new InvalidArgumentException(
248 "Collection offset must be a positive integer"
254 * Array Access: set an item at a positon in collection using array syntax
256 * Example: $list[2] = $item;
258 public function offsetSet($offset, $value)
260 if (is_null($offset)) {
261 // No offset - append to end of collection
262 $this->append($value);
263 } elseif (is_int($offset) and $offset > 0) {
264 list($node, $position) = $this->getCollectionNode($offset);
266 // Create nodes, if they are missing
267 while ($position < $offset) {
268 $new = $this->graph->newBnode();
269 $node->set('rdf:rest', $new);
270 $new->addResource('rdf:rest', 'rdf:nil');
275 // Terminate the list
276 if (!$node->hasProperty('rdf:rest')) {
277 $node->addResource('rdf:rest', 'rdf:nil');
280 return $node->set('rdf:first', $value);
282 throw new InvalidArgumentException(
283 "Collection offset must be a positive integer"
289 * Array Access: delete an item at a specific postion using array syntax
291 * Example: unset($seq[2]);
293 public function offsetUnset($offset)
295 if (is_int($offset) and $offset > 0) {
296 list($node, $position) = $this->getCollectionNode($offset);
298 throw new InvalidArgumentException(
299 "Collection offset must be a positive integer"
303 // Does the item exist?
304 if ($node and $position === $offset) {
305 $nil = $this->graph->resource('rdf:nil');
306 if ($position === 1) {
307 $rest = $node->get('rdf:rest');
308 if ($rest and $rest !== $nil) {
309 // Move second value, so we can keep the head of list
310 $node->set('rdf:first', $rest->get('rdf:first'));
311 $node->set('rdf:rest', $rest->get('rdf:rest'));
312 $rest->delete('rdf:first');
313 $rest->delete('rdf:rest');
315 // Just remove the value
316 $node->delete('rdf:first');
317 $node->delete('rdf:rest');
320 // Remove the value and re-link the list
321 $node->delete('rdf:first');
322 $rest = $node->get('rdf:rest');
323 $previous = $node->get('^rdf:rest');
324 if (is_null($rest)) {
328 $previous->set('rdf:rest', $rest);