Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.96% |
40 / 46 |
|
58.33% |
7 / 12 |
CRAP | |
0.00% |
0 / 1 |
DomPFragment | |
86.96% |
40 / 46 |
|
58.33% |
7 / 12 |
18.72 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
newFromDocumentFragment | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
castFromPFragment | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
isEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isValid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
markInvalid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
asDom | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
asHtmlString | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
concat | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
toJsonArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
newFromJsonArray | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
jsonClassHintFor | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikimedia\Parsoid\Fragments; |
5 | |
6 | use Wikimedia\Assert\Assert; |
7 | use Wikimedia\Parsoid\Core\DomSourceRange; |
8 | use Wikimedia\Parsoid\DOM\DocumentFragment; |
9 | use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI; |
10 | use Wikimedia\Parsoid\Utils\DOMCompat; |
11 | use Wikimedia\Parsoid\Utils\DOMDataUtils; |
12 | |
13 | /** |
14 | * An atomic fragment represented as a (prepared and loaded) DOM tree. |
15 | */ |
16 | class 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 | } |