Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 73 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
DiffUtils | |
0.00% |
0 / 73 |
|
0.00% |
0 / 13 |
2450 | |
0.00% |
0 / 1 |
getDiffMark | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
hasDiffMarkers | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
hasDiffMark | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
30 | |||
hasInsertedDiffMark | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
maybeDeletedNode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
isDeletedBlockNode | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
directChildrenChanged | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onlySubtreeChanged | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
addDiffMark | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
42 | |||
setDiffMark | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
prependTypedMeta | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getAttributes | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
attribsEquals | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
90 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikimedia\Parsoid\Html2Wt; |
5 | |
6 | use stdClass; |
7 | use Wikimedia\Parsoid\Config\Env; |
8 | use Wikimedia\Parsoid\DOM\Comment; |
9 | use Wikimedia\Parsoid\DOM\Element; |
10 | use Wikimedia\Parsoid\DOM\Node; |
11 | use Wikimedia\Parsoid\DOM\Text; |
12 | use Wikimedia\Parsoid\Utils\DOMDataUtils; |
13 | use Wikimedia\Parsoid\Utils\DOMUtils; |
14 | |
15 | class 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 | } |