]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - vendor/wikimedia/css-sanitizer/src/Objects/CSSObjectList.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / wikimedia / css-sanitizer / src / Objects / CSSObjectList.php
1 <?php
2 /**
3  * @file
4  * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5  */
6
7 namespace Wikimedia\CSS\Objects;
8
9 use Wikimedia\CSS\Util;
10
11 /**
12  * Represent a list of CSS objects
13  */
14 class CSSObjectList implements \Countable, \SeekableIterator, \ArrayAccess, CSSObject {
15
16         /** @var string The specific class of object contained */
17         protected static $objectType;
18
19         /** @var CSSObject[] The objects contained */
20         protected $objects;
21
22         /** @var int */
23         protected $offset = 0;
24
25         /**
26          * Additional validation for objects
27          * @param CSSObject[] $objects
28          */
29         protected static function testObjects( array $objects ) {
30         }
31
32         /**
33          * @param CSSObject[] $objects
34          */
35         public function __construct( array $objects = [] ) {
36                 Util::assertAllInstanceOf( $objects, static::$objectType, static::class );
37                 static::testObjects( $objects );
38                 $this->objects = array_values( $objects );
39         }
40
41         /**
42          * Insert one or more objects into the list
43          * @param CSSObject|CSSObject[]|CSSObjectList $objects An object to add, or an array of objects.
44          * @param int $index Insert the objects at this index. If omitted, the
45          *  objects are added at the end.
46          */
47         public function add( $objects, $index = null ) {
48                 if ( $objects instanceof static ) {
49                         $objects = $objects->objects;
50                 } elseif ( is_array( $objects ) ) {
51                         Util::assertAllInstanceOf( $objects, static::$objectType, static::class );
52                         $objects = array_values( $objects );
53                         static::testObjects( $objects );
54                 } else {
55                         if ( !$objects instanceof static::$objectType ) {
56                                 throw new \InvalidArgumentException(
57                                         static::class . ' may only contain instances of ' . static::$objectType . '.'
58                                 );
59                         }
60                         $objects = [ $objects ];
61                         static::testObjects( $objects );
62                 }
63
64                 if ( $index === null ) {
65                         $index = count( $this->objects );
66                 } elseif ( $index < 0 || $index > count( $this->objects ) ) {
67                         throw new \OutOfBoundsException( 'Index is out of range.' );
68                 }
69
70                 array_splice( $this->objects, $index, 0, $objects );
71                 if ( $this->offset > $index ) {
72                         $this->offset += count( $objects );
73                 }
74         }
75
76         /**
77          * Remove an object from the list
78          * @param int $index
79          * @return CSSObject The removed object
80          */
81         public function remove( $index ) {
82                 if ( $index < 0 || $index >= count( $this->objects ) ) {
83                         throw new \OutOfBoundsException( 'Index is out of range.' );
84                 }
85                 $ret = $this->objects[$index];
86                 array_splice( $this->objects, $index, 1 );
87
88                 // This works most sanely with foreach() and removing the current index
89                 if ( $this->offset >= $index ) {
90                         $this->offset--;
91                 }
92
93                 return $ret;
94         }
95
96         /**
97          * Extract a slice of the list
98          * @param int $offset
99          * @param int|null $length
100          * @return CSSObject[] The objects in the slice
101          */
102         public function slice( $offset, $length = null ) {
103                 return array_slice( $this->objects, $offset, $length );
104         }
105
106         /**
107          * Clear the list
108          */
109         public function clear() {
110                 $this->objects = [];
111                 $this->offset = 0;
112         }
113
114         // \Countable interface
115
116         public function count() {
117                 return count( $this->objects );
118         }
119
120         // \SeekableIterator interface
121
122         public function seek( $offset ) {
123                 if ( $offset < 0 || $offset >= count( $this->objects ) ) {
124                         throw new \OutOfBoundsException( 'Offset is out of range.' );
125                 }
126                 $this->offset = $offset;
127         }
128
129         public function current() {
130                 return isset( $this->objects[$this->offset] ) ? $this->objects[$this->offset] : null;
131         }
132
133         public function key() {
134                 return $this->offset;
135         }
136
137         public function next() {
138                 $this->offset++;
139         }
140
141         public function rewind() {
142                 $this->offset = 0;
143         }
144
145         public function valid() {
146                 return isset( $this->objects[$this->offset] );
147         }
148
149         // \ArrayAccess interface
150
151         public function offsetExists( $offset ) {
152                 return isset( $this->objects[$offset] );
153         }
154
155         public function offsetGet( $offset ) {
156                 if ( !is_numeric( $offset ) || (float)(int)$offset !== (float)$offset ) {
157                         throw new \InvalidArgumentException( 'Offset must be an integer.' );
158                 }
159                 if ( $offset < 0 || $offset > count( $this->objects ) ) {
160                         throw new \OutOfBoundsException( 'Offset is out of range.' );
161                 }
162                 return $this->objects[$offset];
163         }
164
165         public function offsetSet( $offset, $value ) {
166                 if ( !$value instanceof static::$objectType ) {
167                         throw new \InvalidArgumentException(
168                                 static::class . ' may only contain instances of ' . static::$objectType . '.'
169                         );
170                 }
171                 static::testObjects( [ $value ] );
172                 if ( !is_numeric( $offset ) || (float)(int)$offset !== (float)$offset ) {
173                         throw new \InvalidArgumentException( 'Offset must be an integer.' );
174                 }
175                 if ( $offset < 0 || $offset > count( $this->objects ) ) {
176                         throw new \OutOfBoundsException( 'Offset is out of range.' );
177                 }
178                 $this->objects[$offset] = $value;
179         }
180
181         public function offsetUnset( $offset ) {
182                 if ( isset( $this->objects[$offset] ) && $offset !== count( $this->objects ) - 1 ) {
183                         throw new \OutOfBoundsException( 'Cannot leave holes in the list.' );
184                 }
185                 unset( $this->objects[$offset] );
186         }
187
188         // CSSObject interface
189
190         public function getPosition() {
191                 $ret = null;
192                 foreach ( $this->objects as $obj ) {
193                         $pos = $obj->getPosition();
194                         if ( $pos[0] >= 0 && (
195                                 !$ret || $pos[0] < $ret[0] || $pos[0] === $ret[0] && $pos[1] < $ret[1]
196                         ) ) {
197                                 $ret = $pos;
198                         }
199                 }
200                 return $ret ?: [ -1, -1 ];
201         }
202
203         /**
204          * Return the tokens to use to separate list items
205          * @param CSSObject $left
206          * @param CSSObject|null $right
207          * @return Token[]
208          */
209         protected function getSeparator( CSSObject $left, CSSObject $right = null ) {
210                 return [];
211         }
212
213         /**
214          * @param string $function Function to call, toTokenArray() or toComponentValueArray()
215          */
216         private function toTokenOrCVArray( $function ) {
217                 $ret = [];
218                 $l = count( $this->objects );
219                 for ( $i = 0; $i < $l; $i++ ) {
220                         // Manually looping and appending turns out to be noticably faster than array_merge.
221                         foreach ( $this->objects[$i]->$function() as $v ) {
222                                 $ret[] = $v;
223                         }
224                         $sep = $this->getSeparator( $this->objects[$i], $i + 1 < $l ? $this->objects[$i + 1] : null );
225                         foreach ( $sep as $v ) {
226                                 $ret[] = $v;
227                         }
228                 }
229                 return $ret;
230         }
231
232         public function toTokenArray() {
233                 return $this->toTokenOrCVArray( __FUNCTION__ );
234         }
235
236         public function toComponentValueArray() {
237                 return $this->toTokenOrCVArray( __FUNCTION__ );
238         }
239
240         public function __toString() {
241                 return Util::stringify( $this );
242         }
243 }