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