Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
83.33% covered (warning)
83.33%
10 / 12
CRAP
90.38% covered (success)
90.38%
47 / 52
TokenList
0.00% covered (danger)
0.00%
0 / 1
83.33% covered (warning)
83.33%
10 / 12
21.39
90.38% covered (success)
90.38%
47 / 52
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getLength
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 contains
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 add
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 remove
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
 current
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 next
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 key
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 valid
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 rewind
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 lazyLoadClassList
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 saveClassList
0.00% covered (danger)
0.00%
0 / 1
3.47
62.50% covered (warning)
62.50%
5 / 8
<?php
declare( strict_types = 1 );
namespace Wikimedia\Parsoid\Utils\DOMCompat;
use Iterator;
use LogicException;
use Wikimedia\Parsoid\DOM\Element;
/**
 * Implements the parts of DOMTokenList interface which are used by Parsoid.
 * @note To improve performance, no effort is made to keep the TokenList in sync
 *   with the real class list if that is changed from elsewhere.
 * @see https://dom.spec.whatwg.org/#interface-domtokenlist
 */
class TokenList implements Iterator {
    /** @var Element The node whose classes are listed. */
    protected $node;
    /** @var string Copy of the attribute text, used for change detection. */
    private $attribute = false;
    // Testing element existence with a list is less painful than returning numeric keys
    // with a map, so let's go with that.
    /** @var string[] */
    private $classList;
    /**
     * @param Element $node The node whose classes are listed.
     */
    public function __construct( $node ) {
        $this->node = $node;
        $this->lazyLoadClassList();
    }
    /**
     * Return the number of CSS classes this element has.
     * @return int
     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-length
     */
    public function getLength(): int {
        $this->lazyLoadClassList();
        return count( $this->classList );
    }
    /**
     * Checks if the element has a given CSS class.
     * @param string $token
     * @return bool
     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-contains
     */
    public function contains( string $token ): bool {
        $this->lazyLoadClassList();
        return in_array( $token, $this->classList, true );
    }
    /**
     * Add CSS classes to the element.
     * @param string ...$tokens List of classes to add
     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-add
     */
    public function add( string ...$tokens ): void {
        $this->lazyLoadClassList();
        $changed = false;
        foreach ( $tokens as $token ) {
            if ( !in_array( $token, $this->classList, true ) ) {
                $changed = true;
                $this->classList[] = $token;
            }
        }
        if ( $changed ) {
            $this->saveClassList();
        }
    }
    /**
     * Remove CSS classes from the element.
     * @param string ...$tokens List of classes to remove
     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-remove
     */
    public function remove( string ...$tokens ): void {
        $this->lazyLoadClassList();
        $changed = false;
        foreach ( $tokens as $token ) {
            $index = array_search( $token, $this->classList, true );
            if ( $index !== false ) {
                array_splice( $this->classList, $index, 1 );
                $changed = true;
            }
        }
        if ( $changed ) {
            $this->saveClassList();
        }
    }
    /**
     * @return string
     */
    public function current() {
        $this->lazyLoadClassList();
        return current( $this->classList );
    }
    /**
     * @return void
     */
    public function next() {
        $this->lazyLoadClassList();
        next( $this->classList );
    }
    /**
     * @return int|null
     */
    public function key() {
        $this->lazyLoadClassList();
        return key( $this->classList );
    }
    /**
     * @return bool
     */
    public function valid() {
        $this->lazyLoadClassList();
        return key( $this->classList ) !== null;
    }
    /**
     * @return void
     */
    public function rewind() {
        $this->lazyLoadClassList();
        reset( $this->classList );
    }
    /**
     * Set the classList property based on the class attribute of the wrapped element.
     */
    private function lazyLoadClassList(): void {
        $attrib = $this->node->getAttribute( 'class' ) ?? '';
        if ( $attrib !== $this->attribute ) {
            $this->attribute = $attrib;
            $this->classList = preg_split( '/\s+/', $attrib, -1,
                PREG_SPLIT_NO_EMPTY );
        }
    }
    /**
     * Set the class attribute of the wrapped element based on the classList property.
     */
    private function saveClassList(): void {
        if ( $this->classList === null ) {
            throw new LogicException( 'no class list to set' );
        } elseif ( $this->classList === [] ) {
            $this->attribute = '';
            $this->node->removeAttribute( 'class' );
        } else {
            $this->attribute = implode( ' ', $this->classList );
            $this->node->setAttribute( 'class', $this->attribute );
        }
    }
}