Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
DataParsoid
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 12
1332
0.00% covered (danger)
0.00%
0 / 1
 __clone
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 isEmpty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 getTemp
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTempFlag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 setTempFlag
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 toJsonArray
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 hint
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 defaultValue
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 flatten
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 embeddedDocumentFragments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jsonClassHintFor
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 newFromJsonArray
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\NodeData;
5
6use stdclass;
7use Wikimedia\JsonCodec\Hint;
8use Wikimedia\JsonCodec\JsonCodecable;
9use Wikimedia\JsonCodec\JsonCodecableTrait;
10use Wikimedia\Parsoid\Core\DomSourceRange;
11use Wikimedia\Parsoid\Core\SourceRange;
12use Wikimedia\Parsoid\DOM\DocumentFragment;
13use Wikimedia\Parsoid\Tokens\Token;
14use Wikimedia\Parsoid\Utils\DOMDataUtils;
15use Wikimedia\Parsoid\Utils\RichCodecable;
16use Wikimedia\Parsoid\Utils\Utils;
17
18/**
19 * Parsoid data for a DOM node. Managed by DOMDataUtils::get/setDataParsoid().
20 *
21 * To reduce memory usage, most the properties need to be undeclared, but we can
22 * use the property declarations below to satisfy phan and to provide type
23 * information to IDEs.
24 *
25 * TODO: Declaring common properties would be beneficial for memory usage, but
26 * changes the JSON serialized output and breaks tests.
27 *
28 * == Miscellaneous / General properties ==
29 *
30 * Used to emit original wikitext in some scenarios (entities, placeholder spans)
31 * Porting note: this can be '0', handle emptiness checks with care
32 * @property string|null $src
33 *
34 * Tag widths for all tokens.
35 * Temporarily present in data-parsoid, but not in final DOM output.
36 * @see ComputeDSR::computeNodeDSR()
37 * @property SourceRange|null $tsr
38 *
39 * Wikitext source ranges that generated this DOM node.
40 * In the form [ start-offset, end-offset ] or
41 * [ start-offset, end-offset, start-tag-width, end-tag-width ].
42 *
43 * Consider input wikitext: `abcdef ''foo'' something else`. Let us look at the `''foo''`
44 * part of the input. It generates `<i data-parsoid='{"dsr":[7,14,2,2]}'>foo</i>` . The dsr
45 * property of the data-parsoid attribute of this i-tag tells us the following. This HTML node
46 * maps to input wikitext substring 7..14. The opening tag <i> was 2 characters wide in wikitext
47 * and the closing tag </i> was also 2 characters wide in wikitext.
48 * @property DomSourceRange|null $dsr
49 *
50 * Denotes special syntax. Possible values:
51 *  - 'html' for html tags. Ex: `<div>foo</div>`
52 *  - 'row' for dt/dd that show on the same line. Ex: `;a:b` (but not `;a\n:b`)
53 *  - 'piped' for piped wikilinks with explicit content Ex: `[[Foo|bar]]` (but not `[[Foo]]`)
54 * - 'magiclink', 'url' - legacy, not used anymore
55 * @property string|null $stx
56 *
57 * Template parameter infos produced by TemplateHandler. After unserialization,
58 * the objects are not fully populated.
59 * @property list<list<ParamInfo>>|null $pi
60 *
61 * DocumentFragment content tunneled through for DOMFragment Token.
62 * @property DocumentFragment|null $html
63 *
64 * On mw:Entity spans this is set to the decoded entity value.
65 * @property string|null $srcContent
66 *
67 * An array of associative arrays describing image rendering options, attached
68 * to the image container (span or figure).
69 *   - ck: Canonical key for the image option.
70 *   - ak: Aliased key.
71 * @property array|null $optList
72 *
73 * Rendered attributes (shadow info). The key is the attribute name. The value
74 * is documented as "mixed" but seems to be coerced to string in
75 * Sanitizer::sanitizeTagAttrs().
76 * @property array|null $a Rendered attributes
77 *
78 * Source attributes (shadow info). The key is the attribute name. The value
79 * is documented as "mixed" but may possibly be a nullable string.
80 * @property array|null $sa Source attributes
81 *
82 * The number of extra dashes in the source of an hr
83 * @property int|null $extra_dashes
84 *
85 * The complete text of a double-underscore behavior switch
86 * @property string|null $magicSrc
87 *
88 * Whether the first argument of a parser function was split on a colon;
89 * contains the colon character used if so (Japanese may use a double-wide
90 * colon character).
91 * @property string|null $colon
92 *
93 * True if the input heading element had an id attribute, preventing automatic
94 * assignment of a new id attribute.
95 * @property bool|null $reusedId
96 *
97 * The link token associated with a redirect
98 * @property Token|null $linkTk
99 *
100 * This is set to "extlink" on auto URL (external hotlink) image links.
101 * @property string|null $type
102 *
103 * On a meta mw:Placeholder/StrippedTag, this is the name of the stripped tag.
104 * @property string|null $name
105 *
106 * This is set on image containers in which a template expands to multiple
107 * image parameters. It is converted to a typeof attribute later in the same
108 * function, so it's unclear why it needs to persist in data-parsoid.
109 * @property bool|null $uneditable
110 *
111 * == WrapTemplates ==
112 *
113 * The wikitext source which was not included in a template wrapper.
114 * @property string|null $unwrappedWT
115 *
116 * The token or DOM node name, optionally suffixed with the syntax name from
117 * $this->stx, of the first node within the encapsulated content.
118 * @property string|null $firstWikitextNode
119 *
120 * == Extensions ==
121 *
122 * Offsets of opening and closing tags for extension tags, in the form
123 * [ opening tag start , closing tag end, opening tag width, closing tag width ]
124 * Temporarily present in data-parsoid, but not in final DOM output.
125 * @property DomSourceRange|null $extTagOffsets
126 *
127 * The reference group. This is attached to the <ol> or its wrapper <div>,
128 * redundantly with the data-mw-group attribute on the <ol>. It is produced by
129 * the extension's sourceToDom() and consumed by wtPostprocess().
130 * @property string $group
131 *
132 * == Annotations ==
133 * This is used on annotation meta tags to indicate that the corresponding
134 * tag has been moved compared to it's initial location defined by wikitext.
135 * An annotation tag can be moved either as the result of fostering or as
136 * the result of annotation range extension to enclose a contiguous DOM
137 * forest.
138 * @property bool|null $wasMoved
139 *
140 * == HTML tags ==
141 *
142 * Are void tags self-closed? (Ex: `<br>` vs `<br />`)
143 * @property bool|null $selfClose
144 *
145 * Void tags that are not self-closed (Ex: `<br>`)
146 * @property bool|null $noClose
147 *
148 * Whether this start HTML tag has no corresponding wikitext and was auto-inserted by a token
149 * handler to generate well-formed html. Usually happens when a token handler fixes up misnesting.
150 * @property bool|null $autoInsertedStartToken
151 *
152 * Whether this end HTML tag has no corresponding wikitext and was auto-inserted by a token
153 * handler to generate well-formed html. Usually happens when a token handler fixes up misnesting.
154 * @property bool|null $autoInsertedEndToken
155 *
156 * Whether this start HTML tag has no corresponding wikitext and was auto-inserted to generate
157 * well-formed html. Usually happens when treebuilder fixes up badly nested HTML.
158 * @property bool|null $autoInsertedStart
159 *
160 * Whether this end HTML tag has no corresponding wikitext and was auto-inserted to generate
161 * well-formed html. Ex: `<tr>`, `<th>`, `<td>`, `<li>`, etc. that have no explicit closing
162 * markup. Or, html tags that aren't closed.
163 * @property bool|null $autoInsertedEnd
164 *
165 * Source tag name for HTML tags. Records case variations (`<div>` vs `<DiV>` vs `<DIV>`).
166 * @property string|null $srcTagName
167 *
168 * UnpackDomFragments sets this on misnested elements
169 * @property bool|null $misnested
170 *
171 * This is set by MarkFosteredContent to indicate fostered content and content
172 * wrappers.
173 * @property bool|null $fostered
174 *
175 * == Links ==
176 *
177 * Link trail source (Ex: the "l" in `[[Foo]]l`)
178 * Porting note: this can be '0', handle emptiness checks with care
179 * @property string|null $tail
180 *
181 * Link prefix source
182 * Porting note: this can be '0', handle emptiness checks with care
183 * @property string|null $prefix
184 *
185 * Did the link use interwiki syntax?
186 * Probably redundant with the rel=mw:WikiLink/Interwiki
187 * @property bool|null $isIW
188 *
189 * Source for first separator in a wikilink to account for variation
190 * Ex. [[Test{{!}}123]]
191 * @property string|null $firstPipeSrc
192 *
193 * == Tables ==
194 *
195 * Source for start-text separators in table wikitext.
196 * @property string|null $startTagSrc
197 *
198 * Source for end-text separators in table wikitext.
199 * @property string|null $endTagSrc
200 *
201 * Source for attribute-text separators in table wikitext.
202 * @property string|null $attrSepSrc
203 *
204 * 'row' for td/th cells that show up on the same line, null otherwise
205 * @property string|null $stx_v
206 *
207 * == Language variant data-parsoid properties ==
208 *
209 * @property array|null $flSp Spaces around flags, compressed with compressSpArray().
210 * @property array|null $tSp Spaces around texts, compressed with compressSpArray().
211 * @property array|null $fl Original flags, copied from VariantInfo::$original
212 *  on the token.
213 */
214#[\AllowDynamicProperties]
215class DataParsoid implements JsonCodecable, RichCodecable {
216    use JsonCodecableTrait;
217
218    /**
219     * Holds a number of transient properties in the wt->html pipeline to pass information between
220     * stages. Dropped before serialization.
221     */
222    public ?TempData $tmp;
223
224    /**
225     * Deeply clone this object
226     */
227    public function __clone() {
228        // Deep clone non-primitive properties
229
230        // 1. Properties which are lists of cloneable objects
231        foreach ( [ 'pi' ] as $prop ) {
232            if ( isset( $this->$prop ) ) {
233                $this->$prop = Utils::cloneArray( $this->$prop );
234            }
235        }
236
237        // 2. Properties which are cloneable objects
238        foreach ( [ 'tmp', 'linkTk', 'tsr', 'dsr', 'extTagOffsets', 'dmv' ] as $prop ) {
239            if ( isset( $this->$prop ) ) {
240                $this->$prop = clone $this->$prop;
241            }
242        }
243        // 3. Properties which are DocumentFragments
244        foreach ( [ 'html' ] as $field ) {
245            if ( isset( $this->$field ) ) {
246                $this->$field = DOMDataUtils::cloneDocumentFragment( $this->$field );
247            }
248        }
249    }
250
251    public function isEmpty(): bool {
252        // First two checks short-circuit for the common case (dsr for nodes & tsr for tokens)
253        return !isset( $this->dsr ) && !isset( $this->tsr ) && $this->toJsonArray() === [];
254    }
255
256    /**
257     * Get a lazy-initialized object to which temporary properties can be written.
258     * @return TempData
259     */
260    public function getTemp(): TempData {
261        // tmp can be unset despite being declared
262        $this->tmp ??= new TempData();
263        return $this->tmp;
264    }
265
266    /**
267     * Check whether a bit is set in $this->tmp->bits
268     *
269     * @param int $flag
270     * @return bool
271     */
272    public function getTempFlag( $flag ): bool {
273        return isset( $this->tmp ) && ( $this->tmp->bits & $flag );
274    }
275
276    /**
277     * Set a bit in $this->tmp->bits
278     *
279     * @param int $flag
280     * @param bool $value
281     */
282    public function setTempFlag( $flag, $value = true ): void {
283        if ( $value ) {
284            if ( !isset( $this->tmp ) ) {
285                $tmp = new TempData;
286                $tmp->bits = $flag;
287                $this->tmp = $tmp;
288            } else {
289                $this->tmp->bits |= $flag;
290            }
291        } elseif ( isset( $this->tmp ) ) {
292            $this->tmp->bits &= ~$flag;
293        }
294    }
295
296    /** @inheritDoc */
297    public function toJsonArray(): array {
298        static $clearNullsFrom = [
299            'dsr', 'tsr', 'extTagOffsets',
300        ];
301        $result = (array)$this;
302        unset( $result['tmp'] );
303        // Conciseness: don't include `null` values from certain properties.
304        foreach ( $clearNullsFrom as $prop ) {
305            if ( !isset( $result[$prop] ) ) {
306                unset( $result[$prop] );
307            }
308        }
309        return $result;
310    }
311
312    /** @return Hint<DataParsoid> */
313    public static function hint(): Hint {
314        static $hint = null;
315        if ( $hint === null ) {
316            $hint = Hint::build( self::class, Hint::ALLOW_OBJECT );
317        }
318        return $hint;
319    }
320
321    /** @inheritDoc */
322    public static function defaultValue(): ?self {
323        $dp = new DataParsoid;
324        // Mark data parsoid created as a default value.
325        $dp->setTempFlag( TempData::IS_NEW, true );
326        return $dp;
327    }
328
329    /** @inheritDoc */
330    public function flatten(): ?string {
331        return null;
332    }
333
334    /**
335     * @inheritDoc
336     * @suppress PhanEmptyYieldFrom this is deliberate
337     */
338    public function embeddedDocumentFragments(): \Iterator {
339        // There are internal DocumentFragments here, but they are transient
340        // internal data and should not be exposed.
341        yield from [];
342    }
343
344    /** @inheritDoc */
345    public static function jsonClassHintFor( string $keyname ) {
346        static $hints = null;
347        if ( $hints === null ) {
348            $dsr = DomSourceRange::hint();
349            $sr = SourceRange::hint();
350            $hints = [
351                'dsr' => $dsr,
352                'extTagOffsets' => $dsr,
353                'tsr' => $sr,
354                'pi' => Hint::build( ParamInfo::class, Hint::LIST, Hint::LIST ),
355                'linkTk' => Token::class,
356                'html' => DocumentFragment::class,
357                'dmv' => DataMwVariant::hint(),
358                'optList' => Hint::build( stdclass::class, Hint::LIST, Hint::LIST )
359            ];
360        }
361        return $hints[$keyname] ?? null;
362    }
363
364    /** @inheritDoc */
365    public static function newFromJsonArray( array $json ): DataParsoid {
366        $dp = new DataParsoid;
367        foreach ( $json as $key => $value ) {
368            switch ( $key ) {
369                case 'dsr':
370                case 'extTagOffsets':
371                case 'tsr':
372                    // For backward compatibility, leave these unset if null.
373                    if ( $value !== null ) {
374                        $dp->$key = $value;
375                    }
376                    break;
377                case 'tmp':
378                    // This isn't serialized, but we can deserialize it
379                    // for tests.
380                    $tmp = new TempData;
381                    foreach ( $value as $key2 => $value2 ) {
382                        $tmp->$key2 = $value2;
383                    }
384                    $dp->$key = $tmp;
385                    break;
386                default:
387                    $dp->$key = $value;
388                    break;
389            }
390        }
391        return $dp;
392    }
393}