Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.48% covered (success)
90.48%
38 / 42
83.33% covered (warning)
83.33%
10 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
TokenList
90.48% covered (success)
90.48%
38 / 42
83.33% covered (warning)
83.33%
10 / 12
20.35
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
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
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
2.26
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Utils\DOMCompat;
5
6use Iterator;
7use Wikimedia\Parsoid\DOM\Element;
8use Wikimedia\Parsoid\Utils\DOMCompat;
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    /** Copy of the attribute text, used for change detection. */
22    private string $attribute = '';
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 array $classList = [];
28
29    /**
30     * @param Element $node The node whose classes are listed.
31     */
32    public function __construct( $node ) {
33        $this->node = $node;
34    }
35
36    /**
37     * Return the number of CSS classes this element has.
38     * @return int
39     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-length
40     */
41    public function getLength(): int {
42        $this->lazyLoadClassList();
43        return count( $this->classList );
44    }
45
46    /**
47     * Checks if the element has a given CSS class.
48     * @param string $token
49     * @return bool
50     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-contains
51     */
52    public function contains( string $token ): bool {
53        $this->lazyLoadClassList();
54        return in_array( $token, $this->classList, true );
55    }
56
57    /**
58     * Add CSS classes to the element.
59     * @param string ...$tokens List of classes to add
60     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-add
61     */
62    public function add( string ...$tokens ): void {
63        $this->lazyLoadClassList();
64        $changed = false;
65        foreach ( $tokens as $token ) {
66            if ( !in_array( $token, $this->classList, true ) ) {
67                $changed = true;
68                $this->classList[] = $token;
69            }
70        }
71        if ( $changed ) {
72            $this->saveClassList();
73        }
74    }
75
76    /**
77     * Remove CSS classes from the element.
78     * @param string ...$tokens List of classes to remove
79     * @see https://dom.spec.whatwg.org/#dom-domtokenlist-remove
80     */
81    public function remove( string ...$tokens ): void {
82        $this->lazyLoadClassList();
83        $changed = false;
84        foreach ( $tokens as $token ) {
85            $index = array_search( $token, $this->classList, true );
86            if ( $index !== false ) {
87                array_splice( $this->classList, $index, 1 );
88                $changed = true;
89            }
90        }
91        if ( $changed ) {
92            $this->saveClassList();
93        }
94    }
95
96    public function current(): string {
97        $this->lazyLoadClassList();
98        return current( $this->classList );
99    }
100
101    public function next(): void {
102        $this->lazyLoadClassList();
103        next( $this->classList );
104    }
105
106    public function key(): ?int {
107        $this->lazyLoadClassList();
108        return key( $this->classList );
109    }
110
111    public function valid(): bool {
112        $this->lazyLoadClassList();
113        return key( $this->classList ) !== null;
114    }
115
116    public function rewind(): void {
117        $this->lazyLoadClassList();
118        reset( $this->classList );
119    }
120
121    /**
122     * Set the classList property based on the class attribute of the wrapped element.
123     */
124    private function lazyLoadClassList(): void {
125        $attrib = DOMCompat::getAttribute( $this->node, 'class' ) ?? '';
126        if ( $attrib !== $this->attribute ) {
127            $this->attribute = $attrib;
128            $this->classList = preg_split( '/\s+/', $attrib, -1,
129                PREG_SPLIT_NO_EMPTY );
130        }
131    }
132
133    /**
134     * Set the class attribute of the wrapped element based on the classList property.
135     */
136    private function saveClassList(): void {
137        if ( !$this->classList ) {
138            $this->attribute = '';
139            $this->node->removeAttribute( 'class' );
140        } else {
141            $this->attribute = implode( ' ', $this->classList );
142            $this->node->setAttribute( 'class', $this->attribute );
143        }
144    }
145
146}