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