Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.58% covered (danger)
6.58%
5 / 76
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
DiffUtils
6.58% covered (danger)
6.58%
5 / 76
0.00% covered (danger)
0.00%
0 / 15
2088.33
0.00% covered (danger)
0.00%
0 / 1
 getDiffMark
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 hasDiffMarkers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 hasDiffMark
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
 hasInsertedDiffMark
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 maybeDeletedNode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isDeletedBlockNode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 directChildrenChanged
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onlySubtreeChanged
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 subtreeUnchanged
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addDiffMark
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 setDiffMark
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 prependTypedMeta
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getAttributes
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 attribsEquals
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
90
 isDiffMarker
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Html2Wt;
5
6use Wikimedia\Parsoid\Config\Env;
7use Wikimedia\Parsoid\DOM\Comment;
8use Wikimedia\Parsoid\DOM\Element;
9use Wikimedia\Parsoid\DOM\Node;
10use Wikimedia\Parsoid\DOM\Text;
11use Wikimedia\Parsoid\NodeData\DataParsoidDiff;
12use Wikimedia\Parsoid\Utils\DOMCompat;
13use Wikimedia\Parsoid\Utils\DOMDataUtils;
14use Wikimedia\Parsoid\Utils\DOMUtils;
15
16class DiffUtils {
17    /**
18     * Get a node's diff marker.
19     *
20     * @param Node $node
21     * @return ?DataParsoidDiff
22     */
23    public static function getDiffMark( Node $node ): ?DataParsoidDiff {
24        if ( !( $node instanceof Element ) ) {
25            return null;
26        }
27        return DOMDataUtils::getDataParsoidDiff( $node );
28    }
29
30    /**
31     * Check that the diff markers on the node exist.
32     *
33     * @param Node $node
34     * @return bool
35     */
36    public static function hasDiffMarkers( Node $node ): bool {
37        return self::getDiffMark( $node ) !== null || self::isDiffMarker( $node );
38    }
39
40    public static function hasDiffMark( Node $node, string $mark ): bool {
41        // For 'deletion' and 'insertion' markers on non-element nodes,
42        // a mw:DiffMarker meta is added
43        if ( $mark === DiffMarkers::DELETED || ( $mark === DiffMarkers::INSERTED && !( $node instanceof Element ) ) ) {
44            return self::isDiffMarker( $node->previousSibling, $mark );
45        } else {
46            $diffMarks = self::getDiffMark( $node );
47            return $diffMarks && $diffMarks->hasDiffMarker( $mark );
48        }
49    }
50
51    public static function hasInsertedDiffMark( Node $node ): bool {
52        return self::hasDiffMark( $node, DiffMarkers::INSERTED );
53    }
54
55    public static function maybeDeletedNode( ?Node $node ): bool {
56        return $node instanceof Element && self::isDiffMarker( $node, DiffMarkers::DELETED );
57    }
58
59    /**
60     * Is node a mw:DiffMarker node that represents a deleted block node?
61     * This annotation is added by the DOMDiff pass.
62     *
63     * @param ?Node $node
64     * @return bool
65     */
66    public static function isDeletedBlockNode( ?Node $node ): bool {
67        return $node instanceof Element && self::maybeDeletedNode( $node ) &&
68            $node->hasAttribute( 'data-is-block' );
69    }
70
71    public static function directChildrenChanged( Node $node ): bool {
72        return self::hasDiffMark( $node, DiffMarkers::CHILDREN_CHANGED );
73    }
74
75    public static function onlySubtreeChanged( Element $node ): bool {
76        $dmark = self::getDiffMark( $node );
77        if ( !$dmark ) {
78            return false;
79        }
80        return $dmark->hasOnlyDiffMarkers(
81            DiffMarkers::SUBTREE_CHANGED, DiffMarkers::CHILDREN_CHANGED
82        );
83    }
84
85    public static function subtreeUnchanged( Element $node ): bool {
86        $dmark = self::getDiffMark( $node );
87        if ( !$dmark ) {
88            return true;
89        }
90        return $dmark->hasOnlyDiffMarkers( DiffMarkers::MODIFIED_WRAPPER );
91    }
92
93    public static function addDiffMark( Node $node, Env $env, string $mark ): ?Element {
94        static $ignoreableNodeTypes = [ XML_DOCUMENT_NODE, XML_DOCUMENT_TYPE_NODE, XML_DOCUMENT_FRAG_NODE ];
95
96        if ( $mark === DiffMarkers::DELETED || $mark === DiffMarkers::MOVED ) {
97            return self::prependTypedMeta( $node, 'mw:DiffMarker/' . $mark );
98        } elseif ( $node instanceof Text || $node instanceof Comment ) {
99            if ( $mark !== DiffMarkers::INSERTED ) {
100                $env->log( 'error', 'BUG! CHANGE-marker for ', $node->nodeType, ' node is: ', $mark );
101            }
102            return self::prependTypedMeta( $node, 'mw:DiffMarker/' . $mark );
103        } elseif ( $node instanceof Element ) {
104            self::setDiffMark( $node, $mark );
105        } elseif ( !in_array( $node->nodeType, $ignoreableNodeTypes, true ) ) {
106            $env->log( 'error', 'Unhandled node type', $node->nodeType, 'in addDiffMark!' );
107        }
108
109        return null;
110    }
111
112    /**
113     * Set a diff marker on a node.
114     *
115     * @param Node $node
116     * @param string $change
117     */
118    private static function setDiffMark( Node $node, string $change ): void {
119        if ( !( $node instanceof Element ) ) {
120            return;
121        }
122        $dpd = DOMDataUtils::getDataParsoidDiffDefault( $node );
123        $dpd->addDiffMarker( $change );
124    }
125
126    /**
127     * Insert a meta element with the passed-in typeof attribute before a node.
128     *
129     * @param Node $node
130     * @param string $type
131     * @return Element
132     */
133    private static function prependTypedMeta( Node $node, string $type ): Element {
134        $meta = $node->ownerDocument->createElement( 'meta' );
135        DOMUtils::addTypeOf( $meta, $type );
136        $node->parentNode->insertBefore( $meta, $node );
137        return $meta;
138    }
139
140    private static function getAttributes( Element $node, array $ignoreableAttribs ): array {
141        $h = DOMUtils::attributes( $node );
142        foreach ( $h as $name => $value ) {
143            if ( in_array( $name, $ignoreableAttribs, true ) ) {
144                unset( $h[$name] );
145            }
146        }
147        // If there's no special attribute handler, we want a straight
148        // comparison of these.
149        if ( !in_array( 'data-parsoid', $ignoreableAttribs, true ) ) {
150            $h['data-parsoid'] = DOMDataUtils::getDataParsoid( $node );
151        }
152        if ( !in_array( 'data-mw', $ignoreableAttribs, true ) && DOMDataUtils::validDataMw( $node ) ) {
153            $h['data-mw'] = DOMDataUtils::getDataMw( $node );
154        }
155        return $h;
156    }
157
158    /**
159     * Attribute equality test.
160     *
161     * @param Element $nodeA
162     * @param Element $nodeB
163     * @param array $ignoreableAttribs
164     * @param array $specializedAttribHandlers
165     * @return bool
166     */
167    public static function attribsEquals(
168        Element $nodeA, Element $nodeB, array $ignoreableAttribs, array $specializedAttribHandlers
169    ): bool {
170        $hA = self::getAttributes( $nodeA, $ignoreableAttribs );
171        $hB = self::getAttributes( $nodeB, $ignoreableAttribs );
172
173        if ( count( $hA ) !== count( $hB ) ) {
174            return false;
175        }
176
177        $keysA = array_keys( $hA );
178        sort( $keysA );
179        $keysB = array_keys( $hB );
180        sort( $keysB );
181
182        foreach ( $keysA as $i => $k ) {
183            if ( $k !== $keysB[$i] ) {
184                return false;
185            }
186
187            $attribEquals = $specializedAttribHandlers[$k] ?? null;
188            if ( $attribEquals ) {
189                // Use a specialized compare function, if provided
190                if ( !$hA[$k] || !$hB[$k] || !$attribEquals( $nodeA, $hA[$k], $nodeB, $hB[$k] ) ) {
191                    return false;
192                }
193            } elseif ( $hA[$k] !== $hB[$k] ) {
194                return false;
195            }
196        }
197
198        return true;
199    }
200
201    /**
202     * Check a node to see whether it's a diff marker.
203     *
204     * @param ?Node $node
205     * @param ?string $mark
206     * @return bool
207     */
208    public static function isDiffMarker(
209        ?Node $node, ?string $mark = null
210    ): bool {
211        if ( !$node ) {
212            return false;
213        }
214
215        if ( $mark ) {
216            return DOMUtils::isMarkerMeta( $node, 'mw:DiffMarker/' . $mark );
217        } else {
218            return DOMCompat::nodeName( $node ) === 'meta' &&
219                DOMUtils::matchTypeOf( $node, '#^mw:DiffMarker/#' );
220        }
221    }
222}