objects = array_values( $objects ); } /** * Insert one or more objects into the list * @param CSSObject|CSSObject[]|CSSObjectList $objects An object to add, or an array of objects. * @param int $index Insert the objects at this index. If omitted, the * objects are added at the end. */ public function add( $objects, $index = null ) { if ( $objects instanceof static ) { $objects = $objects->objects; } elseif ( is_array( $objects ) ) { Util::assertAllInstanceOf( $objects, static::$objectType, static::class ); $objects = array_values( $objects ); static::testObjects( $objects ); } else { if ( !$objects instanceof static::$objectType ) { throw new \InvalidArgumentException( static::class . ' may only contain instances of ' . static::$objectType . '.' ); } $objects = [ $objects ]; static::testObjects( $objects ); } if ( $index === null ) { $index = count( $this->objects ); } elseif ( $index < 0 || $index > count( $this->objects ) ) { throw new \OutOfBoundsException( 'Index is out of range.' ); } array_splice( $this->objects, $index, 0, $objects ); if ( $this->offset > $index ) { $this->offset += count( $objects ); } } /** * Remove an object from the list * @param int $index * @return CSSObject The removed object */ public function remove( $index ) { if ( $index < 0 || $index >= count( $this->objects ) ) { throw new \OutOfBoundsException( 'Index is out of range.' ); } $ret = $this->objects[$index]; array_splice( $this->objects, $index, 1 ); // This works most sanely with foreach() and removing the current index if ( $this->offset >= $index ) { $this->offset--; } return $ret; } /** * Extract a slice of the list * @param int $offset * @param int|null $length * @return CSSObject[] The objects in the slice */ public function slice( $offset, $length = null ) { return array_slice( $this->objects, $offset, $length ); } /** * Clear the list */ public function clear() { $this->objects = []; $this->offset = 0; } // \Countable interface public function count() { return count( $this->objects ); } // \SeekableIterator interface public function seek( $offset ) { if ( $offset < 0 || $offset >= count( $this->objects ) ) { throw new \OutOfBoundsException( 'Offset is out of range.' ); } $this->offset = $offset; } public function current() { return isset( $this->objects[$this->offset] ) ? $this->objects[$this->offset] : null; } public function key() { return $this->offset; } public function next() { $this->offset++; } public function rewind() { $this->offset = 0; } public function valid() { return isset( $this->objects[$this->offset] ); } // \ArrayAccess interface public function offsetExists( $offset ) { return isset( $this->objects[$offset] ); } public function offsetGet( $offset ) { if ( !is_numeric( $offset ) || (float)(int)$offset !== (float)$offset ) { throw new \InvalidArgumentException( 'Offset must be an integer.' ); } if ( $offset < 0 || $offset > count( $this->objects ) ) { throw new \OutOfBoundsException( 'Offset is out of range.' ); } return $this->objects[$offset]; } public function offsetSet( $offset, $value ) { if ( !$value instanceof static::$objectType ) { throw new \InvalidArgumentException( static::class . ' may only contain instances of ' . static::$objectType . '.' ); } static::testObjects( [ $value ] ); if ( !is_numeric( $offset ) || (float)(int)$offset !== (float)$offset ) { throw new \InvalidArgumentException( 'Offset must be an integer.' ); } if ( $offset < 0 || $offset > count( $this->objects ) ) { throw new \OutOfBoundsException( 'Offset is out of range.' ); } $this->objects[$offset] = $value; } public function offsetUnset( $offset ) { if ( isset( $this->objects[$offset] ) && $offset !== count( $this->objects ) - 1 ) { throw new \OutOfBoundsException( 'Cannot leave holes in the list.' ); } unset( $this->objects[$offset] ); } // CSSObject interface public function getPosition() { $ret = null; foreach ( $this->objects as $obj ) { $pos = $obj->getPosition(); if ( $pos[0] >= 0 && ( !$ret || $pos[0] < $ret[0] || $pos[0] === $ret[0] && $pos[1] < $ret[1] ) ) { $ret = $pos; } } return $ret ?: [ -1, -1 ]; } /** * Return the tokens to use to separate list items * @param CSSObject $left * @param CSSObject|null $right * @return Token[] */ protected function getSeparator( CSSObject $left, CSSObject $right = null ) { return []; } /** * @param string $function Function to call, toTokenArray() or toComponentValueArray() */ private function toTokenOrCVArray( $function ) { $ret = []; $l = count( $this->objects ); for ( $i = 0; $i < $l; $i++ ) { // Manually looping and appending turns out to be noticably faster than array_merge. foreach ( $this->objects[$i]->$function() as $v ) { $ret[] = $v; } $sep = $this->getSeparator( $this->objects[$i], $i + 1 < $l ? $this->objects[$i + 1] : null ); foreach ( $sep as $v ) { $ret[] = $v; } } return $ret; } public function toTokenArray() { return $this->toTokenOrCVArray( __FUNCTION__ ); } public function toComponentValueArray() { return $this->toTokenOrCVArray( __FUNCTION__ ); } public function __toString() { return Util::stringify( $this ); } }