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
1892
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
12
 previousNonSepSibling
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 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     * @param Node $node
76     * @return Node|null
77     */
78    public static function lastNonSepChild( Node $node ): ?Node {
79        $child = $node->lastChild;
80        while ( $child && !self::isContentNode( $child ) ) {
81            $child = $child->previousSibling;
82        }
83        return $child;
84    }
85
86    /**
87     * Get the previous non separator sibling node.
88     *
89     * @param Node $node
90     * @return Node|null
91     */
92    public static function previousNonSepSibling( Node $node ): ?Node {
93        $prev = $node->previousSibling;
94        while ( $prev && !self::isContentNode( $prev ) ) {
95            $prev = $prev->previousSibling;
96        }
97        return $prev;
98    }
99
100    /**
101     * Get the next non separator sibling node.
102     *
103     * @param Node $node
104     * @return Node|null
105     */
106    public static function nextNonSepSibling( Node $node ): ?Node {
107        $next = $node->nextSibling;
108        while ( $next && !self::isContentNode( $next ) ) {
109            $next = $next->nextSibling;
110        }
111        return $next;
112    }
113
114    /**
115     * Return the numbler of non deleted child nodes.
116     *
117     * @param Node $node
118     * @return int
119     */
120    public static function numNonDeletedChildNodes( Node $node ): int {
121        $n = 0;
122        $child = $node->firstChild;
123        while ( $child ) {
124            if ( !DiffUtils::isDiffMarker( $child ) ) { // FIXME: This is ignoring both inserted/deleted
125                $n++;
126            }
127            $child = $child->nextSibling;
128        }
129        return $n;
130    }
131
132    /**
133     * Get the first non-deleted child of node.
134     *
135     * @param Node $node
136     * @return Node|null
137     */
138    public static function firstNonDeletedChild( Node $node ): ?Node {
139        $child = $node->firstChild;
140        // FIXME: This is ignoring both inserted/deleted
141        while ( $child && DiffUtils::isDiffMarker( $child ) ) {
142            $child = $child->nextSibling;
143        }
144        return $child;
145    }
146
147    /**
148     * Get the last non-deleted child of node.
149     *
150     * @param Node $node
151     * @return Node|null
152     */
153    public static function lastNonDeletedChild( Node $node ): ?Node {
154        $child = $node->lastChild;
155        // FIXME: This is ignoring both inserted/deleted
156        while ( $child && DiffUtils::isDiffMarker( $child ) ) {
157            $child = $child->previousSibling;
158        }
159        return $child;
160    }
161
162    /**
163     * Get the next non deleted sibling.
164     *
165     * @param Node $node
166     * @return Node|null
167     */
168    public static function nextNonDeletedSibling( Node $node ): ?Node {
169        $node = $node->nextSibling;
170        while ( $node && DiffUtils::isDiffMarker( $node ) ) { // FIXME: This is ignoring both inserted/deleted
171            $node = $node->nextSibling;
172        }
173        return $node;
174    }
175
176    /**
177     * Get the previous non deleted sibling.
178     *
179     * @param Node $node
180     * @return Node|null
181     */
182    public static function previousNonDeletedSibling( Node $node ): ?Node {
183        $node = $node->previousSibling;
184        while ( $node && DiffUtils::isDiffMarker( $node ) ) { // FIXME: This is ignoring both inserted/deleted
185            $node = $node->previousSibling;
186        }
187        return $node;
188    }
189
190    /**
191     * Does `node` contain nothing or just non-newline whitespace?
192     * `strict` adds the condition that all whitespace is forbidden.
193     *
194     * @param Node $node
195     * @param bool $strict
196     * @return bool
197     */
198    public static function nodeEssentiallyEmpty( Node $node, bool $strict = false ): bool {
199        $n = $node->firstChild;
200        while ( $n ) {
201            if ( $n instanceof Element && !DiffUtils::isDiffMarker( $n ) ) {
202                return false;
203            } elseif ( $n instanceof Text &&
204                ( $strict || !preg_match( '/^[ \t]*$/D', $n->nodeValue ) )
205            ) {
206                return false;
207            } elseif ( $n instanceof Comment ) {
208                return false;
209            }
210            $n = $n->nextSibling;
211        }
212        return true;
213    }
214
215}