Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.89% covered (warning)
88.89%
40 / 45
83.33% covered (warning)
83.33%
10 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
TokenList
88.89% covered (warning)
88.89%
40 / 45
83.33% covered (warning)
83.33%
10 / 12
21.60
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getLength
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 contains
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 remove
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 current
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 next
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 key
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 valid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 rewind
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 lazyLoadClassList
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 saveClassList
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
3.71
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Utils\DOMCompat;
5
6use Iterator;
7use LogicException;
8use Wikimedia\Parsoid\DOM\Element;
9use Wikimedia\Parsoid\Utils\DOMCompat;
10
11/**
12 * Implements the parts of DOMTokenList interface which are used by Parsoid.
13 * @note To improve performance, no effort is made to keep the TokenList in sync
14 *   with the real class list if that is changed from elsewhere.
15 * @see https://dom.spec.whatwg.org/#interface-domtokenlist
16 */
17class TokenList implements Iterator {
18
19    /** @var Element The node whose classes are listed. */
20    protected $node;
21
22    /** @var string|bool Copy of the attribute text, used for change detection. */
23    private $attribute = false;
24
25    // Testing element existence with a list is less painful than returning numeric keys
26    // with a map, so let's go with that.
27    /** @var string[] */
28    private $classList;
29
30    /**
31     * @param Element $node The node whose classes are listed.
32     */
33    public function __construct( $node ) {
34        $this->node = $node;
35        $this->lazyLoadClassList();
36    }
37
38    /**
39     * Return the number of CSS classes this element has.
40     * @return int
41     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-length
42     */
43    public function getLength(): int {
44        $this->lazyLoadClassList();
45        return count( $this->classList );
46    }
47
48    /**
49     * Checks if the element has a given CSS class.
50     * @param string $token
51     * @return bool
52     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-contains
53     */
54    public function contains( string $token ): bool {
55        $this->lazyLoadClassList();
56        return in_array( $token, $this->classList, true );
57    }
58
59    /**
60     * Add CSS classes to the element.
61     * @param string ...$tokens List of classes to add
62     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-add
63     */
64    public function add( string ...$tokens ): void {
65        $this->lazyLoadClassList();
66        $changed = false;
67        foreach ( $tokens as $token ) {
68            if ( !in_array( $token, $this->classList, true ) ) {
69                $changed = true;
70                $this->classList[] = $token;
71            }
72        }
73        if ( $changed ) {
74            $this->saveClassList();
75        }
76    }
77
78    /**
79     * Remove CSS classes from the element.
80     * @param string ...$tokens List of classes to remove
81     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-remove
82     */
83    public function remove( string ...$tokens ): void {
84        $this->lazyLoadClassList();
85        $changed = false;
86        foreach ( $tokens as $token ) {
87            $index = array_search( $token, $this->classList, true );
88            if ( $index !== false ) {
89                array_splice( $this->classList, $index, 1 );
90                $changed = true;
91            }
92        }
93        if ( $changed ) {
94            $this->saveClassList();
95        }
96    }
97
98    public function current(): string {
99        $this->lazyLoadClassList();
100        return current( $this->classList );
101    }
102
103    public function next(): void {
104        $this->lazyLoadClassList();
105        next( $this->classList );
106    }
107
108    public function key(): ?int {
109        $this->lazyLoadClassList();
110        return key( $this->classList );
111    }
112
113    public function valid(): bool {
114        $this->lazyLoadClassList();
115        return key( $this->classList ) !== null;
116    }
117
118    public function rewind(): void {
119        $this->lazyLoadClassList();
120        reset( $this->classList );
121    }
122
123    /**
124     * Set the classList property based on the class attribute of the wrapped element.
125     */
126    private function lazyLoadClassList(): void {
127        $attrib = DOMCompat::getAttribute( $this->node, 'class' ) ?? '';
128        if ( $attrib !== $this->attribute ) {
129            $this->attribute = $attrib;
130            $this->classList = preg_split( '/\s+/', $attrib, -1,
131                PREG_SPLIT_NO_EMPTY );
132        }
133    }
134
135    /**
136     * Set the class attribute of the wrapped element based on the classList property.
137     */
138    private function saveClassList(): void {
139        if ( $this->classList === null ) {
140            throw new LogicException( 'no class list to set' );
141        } elseif ( $this->classList === [] ) {
142            $this->attribute = '';
143            $this->node->removeAttribute( 'class' );
144        } else {
145            $this->attribute = implode( ' ', $this->classList );
146            $this->node->setAttribute( 'class', $this->attribute );
147        }
148    }
149
150}