Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.96% covered (warning)
86.96%
40 / 46
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
DomPFragment
86.96% covered (warning)
86.96%
40 / 46
58.33% covered (warning)
58.33%
7 / 12
18.72
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 newFromDocumentFragment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 castFromPFragment
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 isEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isValid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 markInvalid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 asDom
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 asHtmlString
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 concat
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
 toJsonArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 newFromJsonArray
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 jsonClassHintFor
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Fragments;
5
6use Wikimedia\Assert\Assert;
7use Wikimedia\Parsoid\Core\DomSourceRange;
8use Wikimedia\Parsoid\DOM\DocumentFragment;
9use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
10use Wikimedia\Parsoid\Utils\DOMCompat;
11use Wikimedia\Parsoid\Utils\DOMDataUtils;
12
13/**
14 * An atomic fragment represented as a (prepared and loaded) DOM tree.
15 */
16class DomPFragment extends PFragment {
17
18    public const TYPE_HINT = 'dom';
19
20    private ?DocumentFragment $value;
21
22    private function __construct( DocumentFragment $df, ?DomSourceRange $srcOffsets = null ) {
23        parent::__construct( $srcOffsets );
24        $this->value = $df;
25    }
26
27    /**
28     * Create a new DomPFragment from the given DocumentFragment and optional
29     * source string.
30     */
31    public static function newFromDocumentFragment(
32        DocumentFragment $df,
33        ?DomSourceRange $srcOffsets
34    ): DomPFragment {
35        return new self( $df, $srcOffsets );
36    }
37
38    /**
39     * Return a DomPFragment corresponding to the given PFragment.
40     * If the fragment is not already a DomPFragment, this will convert
41     * it to a DocumentFragment using PFragment::asDom().
42     */
43    public static function castFromPFragment(
44        ParsoidExtensionAPI $ext,
45        PFragment $fragment
46    ): DomPFragment {
47        if ( $fragment instanceof DomPFragment ) {
48            return $fragment;
49        }
50        return new self( $fragment->asDom( $ext ), $fragment->srcOffsets );
51    }
52
53    /** @inheritDoc */
54    public function isEmpty(): bool {
55        return !$this->value->hasChildNodes();
56    }
57
58    /**
59     * DomPFragments may become invalid when combined into other fragments.
60     */
61    public function isValid(): bool {
62        return $this->value !== null;
63    }
64
65    /**
66     * For ease of debugging, use ::markInvalid() to mark a DomPFragment
67     * that should not be (re)used.
68     */
69    public function markInvalid(): void {
70        $this->value = null;
71    }
72
73    /** @inheritDoc */
74    public function asDom( ParsoidExtensionAPI $ext, bool $release = false ): DocumentFragment {
75        Assert::invariant(
76            $ext->getTopLevelDoc() === $this->value->ownerDocument,
77            "All fragments should belong to the Parsoid top level doc"
78        );
79        $df = $this->value;
80        if ( $release ) {
81            $this->markInvalid();
82        } else {
83            // Return a clone so that callers can't mutate this fragment!
84            $df = DOMDataUtils::cloneDocumentFragment( $df );
85        }
86        return $df;
87    }
88
89    /** @inheritDoc */
90    public function asHtmlString( ParsoidExtensionAPI $ext ): string {
91        Assert::invariant(
92            $ext->getTopLevelDoc() === $this->value->ownerDocument,
93            "All fragments should belong to the Parsoid top level doc"
94        );
95        return $ext->domToHtml( $this->value, true, false );
96    }
97
98    /**
99     * Return a DomPFragment representing the concatenation of the
100     * given fragments, as (balanced) DOM forests.  The children of
101     * all fragments will be siblings in the result.
102     *
103     * If $release is true, all $fragments arguments may become invalid.
104     * Otherwise, fragment contents will be cloned as necessary to avoid
105     * fragment invalidation.
106     */
107    public static function concat( ParsoidExtensionAPI $ext, bool $release, PFragment ...$fragments ): self {
108        $result = $ext->getTopLevelDoc()->createDocumentFragment();
109        $isFirst = true;
110        $firstDSR = null;
111        $lastDSR = null;
112        foreach ( $fragments as $f ) {
113            if ( !$f->isEmpty() ) {
114                if ( $isFirst ) {
115                    $firstDSR = $f->getSrcOffsets();
116                    $isFirst = false;
117                }
118
119                // The return value of ::asDom() is going to be moved
120                // into $result, so this fragment may need to be released.
121                DOMCompat::append( $result, $f->asDom( $ext, $release ) );
122
123                $lastDSR = $f->getSrcOffsets();
124            }
125        }
126        return new self(
127            $result,
128            self::joinSourceRange( $firstDSR, $lastDSR )
129        );
130    }
131
132    // JsonCodecable implementation
133
134    /** @inheritDoc */
135    public function toJsonArray(): array {
136        return [
137            self::TYPE_HINT => $this->value,
138        ] + parent::toJsonArray();
139    }
140
141    /** @inheritDoc */
142    public static function newFromJsonArray( array $json ): self {
143        $v = $json[self::TYPE_HINT];
144        return new self( $v, $json['dsr'] ?? null );
145    }
146
147    /** @inheritDoc */
148    public static function jsonClassHintFor( string $keyName ) {
149        if ( $keyName === self::TYPE_HINT ) {
150            return DocumentFragment::class;
151        }
152        return parent::jsonClassHintFor( $keyName );
153    }
154}