Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
DiffDOMUtils
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 12
2070
0.00% covered (danger)
0.00%
0 / 1
 hasNChildren
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 isContentNode
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 firstNonSepChild
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 lastNonSepChild
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 previousNonSepSibling
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 nextNonSepSibling
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 numNonDeletedChildNodes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 firstNonDeletedChild
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 lastNonDeletedChild
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 nextNonDeletedSibling
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 previousNonDeletedSibling
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 nodeEssentiallyEmpty
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Utils;
5
6use Wikimedia\Parsoid\DOM\Comment;
7use Wikimedia\Parsoid\DOM\Element;
8use Wikimedia\Parsoid\DOM\Node;
9use Wikimedia\Parsoid\DOM\Text;
10use Wikimedia\Parsoid\Html2Wt\DiffUtils;
11
12/**
13 * Some diff marker aware DOM utils.
14 */
15class DiffDOMUtils {
16
17    /**
18     * Test the number of children this node has without using
19     * `DOMNode::$childNodes->count()`.  This walks the sibling list and so
20     * takes O(`nchildren`) time -- so `nchildren` is expected to be small
21     * (say: 0, 1, or 2).
22     *
23     * Skips all diff markers by default.
24     * @param Node $node
25     * @param int $nchildren
26     * @param bool $countDiffMarkers
27     * @return bool
28     */
29    public static function hasNChildren(
30        Node $node, int $nchildren, bool $countDiffMarkers = false
31    ): bool {
32        for ( $child = $node->firstChild; $child; $child = $child->nextSibling ) {
33            if ( !$countDiffMarkers && DiffUtils::isDiffMarker( $child ) ) {
34                continue;
35            }
36            if ( $nchildren <= 0 ) {
37                return false;
38            }
39            $nchildren -= 1;
40        }
41        return ( $nchildren === 0 );
42    }
43
44    /**
45     * Is a node a content node?
46     *
47     * @param ?Node $node
48     * @return bool
49     */
50    public static function isContentNode( ?Node $node ): bool {
51        return !( $node instanceof Comment ) &&
52            !DOMUtils::isIEW( $node ) &&
53            !DiffUtils::isDiffMarker( $node );
54    }
55
56    /**
57     * Get the first child element or non-IEW text node, ignoring
58     * whitespace-only text nodes, comments, and deleted nodes.
59     *
60     * @param Node $node
61     * @return Node|null
62     */
63    public static function firstNonSepChild( Node $node ): ?Node {
64        $child = $node->firstChild;
65        while ( $child && !self::isContentNode( $child ) ) {
66            $child = $child->nextSibling;
67        }
68        return $child;
69    }
70
71    /**
72     * Get the last child element or non-IEW text node, ignoring
73     * whitespace-only text nodes, comments, and deleted nodes.
74     */
75    public static function lastNonSepChild( Node $node, ?Node $sentinel = null ): ?Node {
76        $child = $node->lastChild;
77        while ( $child && $child !== $sentinel && !self::isContentNode( $child ) ) {
78            $child = $child->previousSibling;
79        }
80        return $child;
81    }
82
83    /**
84     * Get the previous non separator sibling node.
85     */
86    public static function previousNonSepSibling( Node $node, ?Node $sentinel = null ): ?Node {
87        $prev = $node->previousSibling;
88        while ( $prev && $prev !== $sentinel && !self::isContentNode( $prev ) ) {
89            $prev = $prev->previousSibling;
90        }
91        return $prev;
92    }
93
94    /**
95     * Get the next non separator sibling node.
96     *
97     * @param Node $node
98     * @return Node|null
99     */
100    public static function nextNonSepSibling( Node $node ): ?Node {
101        $next = $node->nextSibling;
102        while ( $next && !self::isContentNode( $next ) ) {
103            $next = $next->nextSibling;
104        }
105        return $next;
106    }
107
108    /**
109     * Return the numbler of non deleted child nodes.
110     *
111     * @param Node $node
112     * @return int
113     */
114    public static function numNonDeletedChildNodes( Node $node ): int {
115        $n = 0;
116        $child = $node->firstChild;
117        while ( $child ) {
118            if ( !DiffUtils::isDiffMarker( $child ) ) { // FIXME: This is ignoring both inserted/deleted
119                $n++;
120            }
121            $child = $child->nextSibling;
122        }
123        return $n;
124    }
125
126    /**
127     * Get the first non-deleted child of node.
128     *
129     * @param Node $node
130     * @return Node|null
131     */
132    public static function firstNonDeletedChild( Node $node ): ?Node {
133        $child = $node->firstChild;
134        // FIXME: This is ignoring both inserted/deleted
135        while ( $child && DiffUtils::isDiffMarker( $child ) ) {
136            $child = $child->nextSibling;
137        }
138        return $child;
139    }
140
141    /**
142     * Get the last non-deleted child of node.
143     *
144     * @param Node $node
145     * @return Node|null
146     */
147    public static function lastNonDeletedChild( Node $node ): ?Node {
148        $child = $node->lastChild;
149        // FIXME: This is ignoring both inserted/deleted
150        while ( $child && DiffUtils::isDiffMarker( $child ) ) {
151            $child = $child->previousSibling;
152        }
153        return $child;
154    }
155
156    /**
157     * Get the next non deleted sibling.
158     *
159     * @param Node $node
160     * @return Node|null
161     */
162    public static function nextNonDeletedSibling( Node $node ): ?Node {
163        $node = $node->nextSibling;
164        while ( $node && DiffUtils::isDiffMarker( $node ) ) { // FIXME: This is ignoring both inserted/deleted
165            $node = $node->nextSibling;
166        }
167        return $node;
168    }
169
170    /**
171     * Get the previous non deleted sibling.
172     *
173     * @param Node $node
174     * @return Node|null
175     */
176    public static function previousNonDeletedSibling( Node $node ): ?Node {
177        $node = $node->previousSibling;
178        while ( $node && DiffUtils::isDiffMarker( $node ) ) { // FIXME: This is ignoring both inserted/deleted
179            $node = $node->previousSibling;
180        }
181        return $node;
182    }
183
184    /**
185     * Does `node` contain nothing or just non-newline whitespace?
186     * `strict` adds the condition that all whitespace is forbidden.
187     *
188     * @param Node $node
189     * @param bool $strict
190     * @return bool
191     */
192    public static function nodeEssentiallyEmpty( Node $node, bool $strict = false ): bool {
193        $n = $node->firstChild;
194        while ( $n ) {
195            if ( $n instanceof Element && !DiffUtils::isDiffMarker( $n ) ) {
196                return false;
197            } elseif ( $n instanceof Text &&
198                ( $strict || !preg_match( '/^[ \t]*$/D', $n->nodeValue ) )
199            ) {
200                return false;
201            } elseif ( $n instanceof Comment ) {
202                return false;
203            }
204            $n = $n->nextSibling;
205        }
206        return true;
207    }
208
209}