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