Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
TreeWalker
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 3
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 nextNode
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
90
 filterNode
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace MediaWiki\Extension\DiscussionTools;
4
5use DOMException;
6use Throwable;
7use Wikimedia\Parsoid\DOM\Node;
8
9/**
10 * Partial implementation of W3 DOM4 TreeWalker interface.
11 *
12 * See also:
13 * - https://dom.spec.whatwg.org/#interface-treewalker
14 *
15 * Ported from https://github.com/TRowbotham/PHPDOM (MIT)
16 */
17class TreeWalker {
18
19    public Node $root;
20    public int $whatToShow;
21    public Node $currentNode;
22    /** @var callable|null */
23    public $filter;
24    private bool $isActive = false;
25
26    /**
27     * See https://dom.spec.whatwg.org/#interface-treewalker
28     */
29    public function __construct(
30        Node $root,
31        int $whatToShow = NodeFilter::SHOW_ALL,
32        ?callable $filter = null
33    ) {
34        $this->currentNode = $root;
35        $this->filter = $filter;
36        $this->root = $root;
37        $this->whatToShow = $whatToShow;
38    }
39
40    /**
41     * See https://dom.spec.whatwg.org/#dom-treewalker-nextnode
42     *
43     * @return Node|null The current node
44     */
45    public function nextNode(): ?Node {
46        $node = $this->currentNode;
47        $result = NodeFilter::FILTER_ACCEPT;
48
49        while ( true ) {
50            while ( $result !== NodeFilter::FILTER_REJECT && $node->firstChild !== null ) {
51                $node = $node->firstChild;
52                $result = $this->filterNode( $node );
53                if ( $result === NodeFilter::FILTER_ACCEPT ) {
54                    $this->currentNode = $node;
55                    return $node;
56                }
57            }
58
59            $sibling = null;
60            $temp = $node;
61            while ( $temp !== null ) {
62                if ( $temp === $this->root ) {
63                    return null;
64                }
65
66                $sibling = $temp->nextSibling;
67
68                if ( $sibling !== null ) {
69                    $node = $sibling;
70
71                    break;
72                }
73
74                $temp = $temp->parentNode;
75            }
76
77            '@phan-var Node $node';
78            $result = $this->filterNode( $node );
79
80            if ( $result === NodeFilter::FILTER_ACCEPT ) {
81                $this->currentNode = $node;
82
83                return $node;
84            }
85
86        }
87    }
88
89    /**
90     * Filters a node.
91     *
92     * @internal
93     *
94     * @see https://dom.spec.whatwg.org/#concept-node-filter
95     *
96     * @param Node $node The node to check.
97     * @return int Returns one of NodeFilter's FILTER_* constants.
98     *     - NodeFilter::FILTER_ACCEPT
99     *     - NodeFilter::FILTER_REJECT
100     *     - NodeFilter::FILTER_SKIP
101     */
102    private function filterNode( Node $node ): int {
103        if ( $this->isActive ) {
104            throw new DOMException( 'InvalidStateError' );
105        }
106
107        // Let n be node’s nodeType attribute value minus 1.
108        $n = $node->nodeType - 1;
109
110        // If the nth bit (where 0 is the least significant bit) of whatToShow
111        // is not set, return FILTER_SKIP.
112        if ( !( ( 1 << $n ) & $this->whatToShow ) ) {
113            return NodeFilter::FILTER_SKIP;
114        }
115
116        // If filter is null, return FILTER_ACCEPT.
117        if ( !$this->filter ) {
118            return NodeFilter::FILTER_ACCEPT;
119        }
120
121        $this->isActive = true;
122
123        try {
124            // Let $result be the return value of call a user object's operation
125            // with traverser's filter, "acceptNode", and Node. If this throws
126            // an exception, then unset traverser's active flag and rethrow the
127            // exception.
128            $result = $this->filter instanceof NodeFilter
129                ? $this->filter->acceptNode( $node )
130                : ( $this->filter )( $node );
131        } catch ( Throwable $e ) {
132            $this->isActive = false;
133
134            throw $e;
135        }
136
137        $this->isActive = false;
138
139        return $result;
140    }
141}