mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-22 01:55:56 -05:00
244 lines
6.1 KiB
PHP
244 lines
6.1 KiB
PHP
<?php
|
|
/**
|
|
* @file
|
|
* @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
|
|
*/
|
|
|
|
namespace Wikimedia\CSS\Objects;
|
|
|
|
use Wikimedia\CSS\Util;
|
|
|
|
/**
|
|
* Represent a list of CSS objects
|
|
*/
|
|
class CSSObjectList implements \Countable, \SeekableIterator, \ArrayAccess, CSSObject {
|
|
|
|
/** @var string The specific class of object contained */
|
|
protected static $objectType;
|
|
|
|
/** @var CSSObject[] The objects contained */
|
|
protected $objects;
|
|
|
|
/** @var int */
|
|
protected $offset = 0;
|
|
|
|
/**
|
|
* Additional validation for objects
|
|
* @param CSSObject[] $objects
|
|
*/
|
|
protected static function testObjects( array $objects ) {
|
|
}
|
|
|
|
/**
|
|
* @param CSSObject[] $objects
|
|
*/
|
|
public function __construct( array $objects = [] ) {
|
|
Util::assertAllInstanceOf( $objects, static::$objectType, static::class );
|
|
static::testObjects( $objects );
|
|
$this->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 );
|
|
}
|
|
}
|