Minor dependency updates
[yaffs-website] / vendor / easyrdf / easyrdf / lib / EasyRdf / Collection.php
1 <?php
2
3 /**
4  * EasyRdf
5  *
6  * LICENSE
7  *
8  * Copyright (c) 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) 2013 Nicholas J Humfrey
35  * @license    http://www.opensource.org/licenses/bsd-license.php
36  */
37
38 /**
39  * Sub-class of EasyRdf_Resource that represents an RDF collection (rdf:List)
40  *
41  * This class can be used to iterate through a collection of items.
42  *
43  * Note that items are numbered from 1 (not 0) for consistency with RDF Containers.
44  *
45  * @package    EasyRdf
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
49  */
50 class EasyRdf_Collection extends EasyRdf_Resource implements ArrayAccess, Countable, SeekableIterator
51 {
52     private $position;
53     private $current;
54
55     /** Create a new collection - do not use this directly
56      *
57      * @ignore
58      */
59     public function __construct($uri, $graph)
60     {
61         $this->position = 1;
62         $this->current = null;
63         parent::__construct($uri, $graph);
64     }
65
66     /** Seek to a specific position in the container
67      *
68      * The first item is postion 1
69      *
70      * @param  integer  $position     The position in the container to seek to
71      * @throws OutOfBoundsException
72      */
73     public function seek($position)
74     {
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;
80             } else {
81                 throw new OutOfBoundsException(
82                     "Unable to seek to position $position in the collection"
83                 );
84             }
85         } else {
86             throw new InvalidArgumentException(
87                 "Collection position must be a positive integer"
88             );
89         }
90     }
91
92     /** Rewind the iterator back to the start of the collection
93      *
94      */
95     public function rewind()
96     {
97         $this->position = 1;
98         $this->current = null;
99     }
100
101     /** Return the current item in the collection
102      *
103      * @return mixed The current item
104      */
105     public function current()
106     {
107         if ($this->position === 1) {
108             return $this->get('rdf:first');
109         } elseif ($this->current) {
110             return $this->current->get('rdf:first');
111         }
112     }
113
114     /** Return the key / current position in the collection
115      *
116      * Note: the first item is number 1
117      *
118      * @return int The current position
119      */
120     public function key()
121     {
122         return $this->position;
123     }
124
125     /** Move forward to next item in the collection
126      *
127      */
128     public function next()
129     {
130         if ($this->position === 1) {
131             $this->current = $this->get('rdf:rest');
132         } elseif ($this->current) {
133             $this->current = $this->current->get('rdf:rest');
134         }
135         $this->position++;
136     }
137
138     /** Checks if current position is valid
139      *
140      * @return bool True if the current position is valid
141      */
142     public function valid()
143     {
144         if ($this->position === 1 and $this->hasProperty('rdf:first')) {
145             return true;
146         } elseif ($this->current !== null and $this->current->hasProperty('rdf:first')) {
147             return true;
148         } else {
149             return false;
150         }
151     }
152
153     /** Get a node for a particular offset into the collection
154      *
155      * This function may not return the item you requested, if
156      * it does not exist. Please check the $postion parameter
157      * returned.
158      *
159      * If the offset is null, then the last node in the
160      * collection (before rdf:nil) will be returned.
161      *
162      * @param  integer $offset          The offset into the collection (or null)
163      * @return array   $node, $postion  The node object and postion of the node
164      */
165     public function getCollectionNode($offset)
166     {
167         $position = 1;
168         $node = $this;
169         $nil = $this->graph->resource('rdf:nil');
170         while (($rest = $node->get('rdf:rest')) and $rest !== $nil and (is_null($offset) or ($position < $offset))) {
171             $node = $rest;
172             $position++;
173         }
174         return array($node, $position);
175     }
176
177     /** Counts the number of items in the collection
178      *
179      * Note that this is an slow method - it is more efficient to use
180      * the iterator interface, if you can.
181      *
182      * @return integer The number of items in the collection
183      */
184     public function count()
185     {
186         // Find the end of the collection
187         list($node, $position) = $this->getCollectionNode(null);
188         if (!$node->hasProperty('rdf:first')) {
189             return 0;
190         } else {
191             return $position;
192         }
193     }
194
195     /** Append an item to the end of the collection
196      *
197      * @param  mixed $value      The value to append
198      * @return integer           The number of values appended (1 or 0)
199      */
200     public function append($value)
201     {
202         // Find the end of the collection
203         list($node, $position) = $this->getCollectionNode(null);
204         $rest = $node->get('rdf:rest');
205
206         if ($node === $this and is_null($rest)) {
207             $node->set('rdf:first', $value);
208             $node->addResource('rdf:rest', 'rdf:nil');
209         } else {
210             $new = $this->graph->newBnode();
211             $node->set('rdf:rest', $new);
212             $new->add('rdf:first', $value);
213             $new->addResource('rdf:rest', 'rdf:nil');
214         }
215
216         return 1;
217     }
218
219     /** Array Access: check if a position exists in collection using array syntax
220      *
221      * Example: isset($list[2])
222      */
223     public function offsetExists($offset)
224     {
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'));
228         } else {
229             throw new InvalidArgumentException(
230                 "Collection offset must be a positive integer"
231             );
232         }
233     }
234
235     /** Array Access: get an item at a specified position in collection using array syntax
236      *
237      * Example: $item = $list[2];
238      */
239     public function offsetGet($offset)
240     {
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');
245             }
246         } else {
247             throw new InvalidArgumentException(
248                 "Collection offset must be a positive integer"
249             );
250         }
251     }
252
253     /**
254      * Array Access: set an item at a positon in collection using array syntax
255      *
256      * Example: $list[2] = $item;
257      */
258     public function offsetSet($offset, $value)
259     {
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);
265
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');
271                 $node = $new;
272                 $position++;
273             }
274
275             // Terminate the list
276             if (!$node->hasProperty('rdf:rest')) {
277                 $node->addResource('rdf:rest', 'rdf:nil');
278             }
279
280             return $node->set('rdf:first', $value);
281         } else {
282             throw new InvalidArgumentException(
283                 "Collection offset must be a positive integer"
284             );
285         }
286     }
287
288     /**
289      * Array Access: delete an item at a specific postion using array syntax
290      *
291      * Example: unset($seq[2]);
292      */
293     public function offsetUnset($offset)
294     {
295         if (is_int($offset) and $offset > 0) {
296             list($node, $position) = $this->getCollectionNode($offset);
297         } else {
298             throw new InvalidArgumentException(
299                 "Collection offset must be a positive integer"
300             );
301         }
302
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');
314                 } else {
315                     // Just remove the value
316                     $node->delete('rdf:first');
317                     $node->delete('rdf:rest');
318                 }
319             } else {
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)) {
325                     $rest = $nil;
326                 }
327                 if ($previous) {
328                     $previous->set('rdf:rest', $rest);
329                 }
330             }
331         }
332     }
333 }