Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
NodeData
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 2
132
0.00% covered (danger)
0.00%
0 / 1
 __clone
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 cloneNodeData
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
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;
9
10// phpcs:disable MediaWiki.Commenting.PropertyDocumentation.ObjectTypeHintVar
11
12/**
13 * This object stores data associated with a single DOM node.
14 *
15 * Using undeclared properties reduces memory usage and CPU time if the
16 * property is null in more than about 75% of instances. There are typically
17 * a very large number of NodeData objects, so this optimisation is worthwhile.
18 *
19 * @property object|null $mw_variant
20 * @property int|null $storedId
21 */
22#[\AllowDynamicProperties]
23class NodeData {
24    /**
25     * The unserialized data-parsoid attribute
26     */
27    public ?DataParsoid $parsoid = null;
28
29    /**
30     * The unserialized data-mw attribute
31     */
32    public ?DataMw $mw = null;
33
34    public function __clone() {
35        // PHP performs a shallow clone then calls this method.
36        // Make a deep clone of every object-valued property.
37        // (Note that decoded 'rich attributes' are object-valued properties;
38        // undecoded rich attributes and hints are not, but they are immutable
39        // and thus don't need to be deep-cloned.)
40        foreach ( get_object_vars( $this ) as $k => $v ) {
41            if ( is_object( $v ) ) {
42                $this->$k = clone $v;
43            }
44        }
45    }
46
47    /**
48     * Deep clone this object
49     * If $stripSealedFragments is true, sealed DOMFragment included in expanded attributes are deleted in the
50     * clone.
51     * @param bool $stripSealedFragments
52     * @return self
53     */
54    public function cloneNodeData( bool $stripSealedFragments = false ): self {
55        $nd = clone $this;
56
57        if ( $this->mw === null || !$stripSealedFragments ) {
58            return $nd;
59        }
60
61        // Avoid cloning sealed DOMFragments that may occur in expanded attributes
62        foreach ( $nd->mw->attribs ?? [] as $attr ) {
63            // Look for DOMFragments in both key and value of DataMwAttrib
64            foreach ( [ 'key', 'value' ] as $part ) {
65                if (
66                    isset( $attr->$part['html'] ) &&
67                    str_contains( $attr->$part['html'], 'mw:DOMFragment/sealed' )
68                ) {
69                    $doc = DOMUtils::parseHTML( $attr->$part['html'] );
70                    DOMUtils::visitDOM( $doc, static function ( Node $node ) {
71                        if (
72                            DOMUtils::matchTypeOf( $node, '#^mw:DOMFragment/sealed/\w+$#D' )
73                        ) {
74                            DOMCompat::getParentElement( $node )->removeChild( $node );
75                        }
76                    } );
77                    $attr->$part['html'] = DOMCompat::getInnerHTML( DOMCompat::getBody( $doc ) );
78                }
79            }
80        }
81
82        return $nd;
83    }
84}