Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
NodeData
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
0.00% covered (danger)
0.00%
0 / 1
 cloneNodeData
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\NodeData;
5
6use Wikimedia\Parsoid\DOM\Node;
7use Wikimedia\Parsoid\Utils\DOMCompat;
8use Wikimedia\Parsoid\Utils\DOMUtils;
9use Wikimedia\Parsoid\Utils\Utils;
10
11// phpcs:disable MediaWiki.Commenting.PropertyDocumentation.ObjectTypeHintVar
12
13/**
14 * This object stores data associated with a single DOM node.
15 *
16 * Using undeclared properties reduces memory usage and CPU time if the
17 * property is null in more than about 75% of instances. There are typically
18 * a very large number of NodeData objects, so this optimisation is worthwhile.
19 *
20 * @property object|null $parsoid_diff
21 * @property object|null $mw_variant
22 * @property int|null $storedId
23 * @property DataMwI18n|null $i18n
24 */
25#[\AllowDynamicProperties]
26class NodeData {
27    /**
28     * @var DataParsoid|null The unserialized data-parsoid attribute
29     */
30    public $parsoid;
31
32    /**
33     * @var object|null The unserialized data-mw attribute
34     */
35    public $mw;
36
37    /**
38     * Deep clone this object
39     * If $stripSealedFragments is true, sealed DOMFragment included in expanded attributes are deleted in the
40     * clone.
41     * @param bool $stripSealedFragments
42     * @return self
43     */
44    public function cloneNodeData( bool $stripSealedFragments = false ): self {
45        $cloneableData = get_object_vars( $this );
46        // Don't clone $this->parsoid because it has a custom clone method
47        unset( $cloneableData['parsoid'] );
48        // Don't clone storedId because it doesn't need it
49        unset( $cloneableData['storedId'] );
50        // Deep clone everything else
51        $cloneableData = Utils::clone( $cloneableData );
52        $nd = clone $this;
53        if ( $nd->parsoid ) {
54            $nd->parsoid = $nd->parsoid->clone();
55        }
56        // Avoid cloning sealed DOMFragments that may occur in expanded attributes
57        if ( $nd->mw && $stripSealedFragments && is_array( $nd->mw->attribs ) ) {
58            foreach ( $nd->mw->attribs as $attr ) {
59                foreach ( $attr as $v ) {
60                    if ( isset( $v->html ) && str_contains( $v->html, 'mw:DOMFragment/sealed' ) ) {
61                        $doc = DOMUtils::parseHTML( $v->html );
62                        DOMUtils::visitDOM( $doc, static function ( Node $node ) {
63                            if (
64                                DOMUtils::matchTypeOf( $node, '#^mw:DOMFragment/sealed/\w+$#D' )
65                            ) {
66                                DOMCompat::getParentElement( $node )->removeChild( $node );
67                            }
68                        } );
69                        $v->html = DOMCompat::getInnerHTML( DOMCompat::getBody( $doc ) );
70                    }
71                }
72            }
73        }
74        foreach ( $cloneableData as $key => $value ) {
75            $nd->$key = $value;
76        }
77        return $nd;
78    }
79}