Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
DomSourceRange
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 22
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 innerSubstr
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 innerStart
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 innerEnd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 innerLength
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 openSubstr
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 closeSubstr
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 openRange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 closeRange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 innerRange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 outerRange
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 stripTags
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 offset
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 hasValidTagWidths
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
20
 hasTrimmedWS
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 hasValidLeadingWS
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasValidTrailingWS
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fromTsr
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 fromSource
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 newFromJsonArray
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 toJsonArray
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 hint
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Core;
5
6use Wikimedia\Assert\Assert;
7use Wikimedia\JsonCodec\Hint;
8use Wikimedia\Parsoid\Utils\PHPUtils;
9
10/**
11 * Represents a DOM source range.  That is, for a given DOM tree, gives
12 * the source offset range in the original wikitext for this DOM tree,
13 * as well as the opening and closing tag widths if appropriate.
14 */
15class DomSourceRange extends SourceRange {
16    /**
17     * Opening tag width.
18     * @var ?int
19     */
20    public $openWidth;
21
22    /**
23     * Closing tag width.
24     * @var ?int
25     */
26    public $closeWidth;
27
28    /**
29     * Width of trimmed whitespace between opening tag & first child.
30     * Defaults to zero since for most nodes, there is no ws trimming.
31     * -1 indicates that this information is invalid and should not be used.
32     * @var int
33     */
34    public $leadingWS = 0;
35
36    /**
37     * Width of trimmed whitespace between last child & closing tag.
38     * Defaults to zero since for most nodes, there is no ws trimming.
39     * -1 indicates that this information is invalid and should not be used.
40     * @var int
41     */
42    public $trailingWS = 0;
43
44    /**
45     * Create a new DOM source offset range (DSR).
46     * @param ?int $start The starting index (UTF-8 byte count, inclusive)
47     * @param ?int $end The ending index (UTF-8 byte count, exclusive)
48     * @param ?int $openWidth The width of the open container tag
49     * @param ?int $closeWidth The width of the close container tag
50     * @param int $leadingWS The width of WS chars between opening tag & first child
51     * @param int $trailingWS The width of WS chars between last child & closing tag
52     */
53    public function __construct(
54        ?int $start, ?int $end, ?int $openWidth, ?int $closeWidth,
55        int $leadingWS = 0,
56        int $trailingWS = 0,
57        ?Source $source = null,
58    ) {
59        parent::__construct( $start, $end, $source );
60        $this->openWidth = $openWidth;
61        $this->closeWidth = $closeWidth;
62        $this->leadingWS = $leadingWS;
63        $this->trailingWS = $trailingWS;
64    }
65
66    /**
67     * Return the substring of the given string corresponding to the
68     * inner portion of this range (that is, not including the opening
69     * and closing tag widths).
70     * @param string|Source ...$str The source text string (optional)
71     *  The Source of this object (if non-null) is preferred over the given
72     *  argument.
73     * @return string
74     */
75    public function innerSubstr( string|Source ...$str ): string {
76        $str = $this->getSourceString( $str );
77        return PHPUtils::safeSubstr( $str, $this->innerStart(), $this->innerLength() );
78    }
79
80    /**
81     * Return the "inner start", that is, the start offset plus the open width.
82     * @return int
83     */
84    public function innerStart(): int {
85        return $this->start + ( $this->openWidth ?? 0 );
86    }
87
88    /**
89     * Return the "inner end", that is, the end offset minus the close width.
90     * @return int
91     */
92    public function innerEnd(): int {
93        return $this->end - ( $this->closeWidth ?? 0 );
94    }
95
96    /**
97     * Return the length of this source range, excluding the open and close
98     * tag widths.
99     * @return int
100     */
101    public function innerLength(): int {
102        return $this->innerEnd() - $this->innerStart();
103    }
104
105    /**
106     * Return the substring of the given string corresponding to the
107     * open portion of this range.
108     * @param string|Source ...$str The source text string (optional)
109     *  The Source of this object (if non-null) is preferred over the given
110     *  argument.
111     * @return string
112     */
113    public function openSubstr( string|Source ...$str ): string {
114        $str = $this->getSourceString( $str );
115        return PHPUtils::safeSubstr( $str, $this->start, $this->openWidth );
116    }
117
118    /**
119     * Return the substring of the given string corresponding to the
120     * close portion of this range.
121     * @param string|Source ...$str The source text string (optional)
122     *  The Source of this object (if non-null) is preferred over the given
123     *  argument.
124     * @return string
125     */
126    public function closeSubstr( string|Source ...$str ): string {
127        $str = $this->getSourceString( $str );
128        return PHPUtils::safeSubstr( $str, $this->innerEnd(), $this->closeWidth );
129    }
130
131    /**
132     * Return the source range corresponding to the open portion of this range.
133     * @return SourceRange
134     */
135    public function openRange(): SourceRange {
136        return new SourceRange( $this->start, $this->innerStart(), $this->source );
137    }
138
139    /**
140     * Return the source range corresponding to the close portion of this range.
141     * @return SourceRange
142     */
143    public function closeRange(): SourceRange {
144        return new SourceRange( $this->innerEnd(), $this->end, $this->source );
145    }
146
147    /**
148     * Return the source range corresponding to the inner portion of this range.
149     * @return SourceRange
150     */
151    public function innerRange(): SourceRange {
152        return new SourceRange( $this->innerStart(), $this->innerEnd(), $this->source );
153    }
154
155    /**
156     * Return the source range corresponding to the outer portion of this range.
157     * @return SourceRange
158     */
159    public function outerRange(): SourceRange {
160        return new SourceRange( $this->start, $this->end, $this->source );
161    }
162
163    /**
164     * Strip the tag open and close from the beginning and end of the
165     * provided string.  This is similar to `DomSourceRange::innerSubstr()`
166     * but we assume that the string before `$this->start` and after
167     * `$this->end` has already been removed. (That is, that the input
168     * is `$this->substr( $originalWikitextSource )`.)
169     *
170     * @param string $src The source text string from `$this->start`
171     *   (inclusive) to `$this->end` (exclusive).
172     * @return string
173     */
174    public function stripTags( string $src ): string {
175        Assert::invariant(
176            strlen( $src ) === $this->length(),
177            "Input string not the expected length"
178        );
179        return PHPUtils::safeSubstr(
180            $src,
181            $this->openWidth,
182            -$this->closeWidth
183        );
184    }
185
186    /**
187     * Return a new DOM source range shifted by $amount.
188     * @param int $amount The amount to shift by
189     * @return DomSourceRange
190     */
191    public function offset( int $amount ): DomSourceRange {
192        return new DomSourceRange(
193            $this->start + $amount,
194            $this->end + $amount,
195            $this->openWidth,
196            $this->closeWidth,
197            $this->leadingWS,
198            $this->trailingWS,
199            $this->source
200        );
201    }
202
203    /**
204     * @return bool True if the tag widths are valid.
205     */
206    public function hasValidTagWidths(): bool {
207        return $this->openWidth !== null && $this->closeWidth !== null &&
208            $this->openWidth >= 0 && $this->closeWidth >= 0;
209    }
210
211    /**
212     * Determine if this DSR records that whitespace was trimmed from
213     * this node.  Note that this doesn't mean that the amount trimmed
214     * is known; use ::hasValidLeadingWS() or ::hasValidTrimmedWS()
215     * to determine that.
216     * @return bool True if either leadingWS or trailingWS is non-zero.
217     */
218    public function hasTrimmedWS(): bool {
219        return $this->leadingWS !== 0 || $this->trailingWS !== 0;
220    }
221
222    /**
223     * @note In most cases you should check to see if this node
224     * ::hasTrimmedWS() *and* whether the amount is valid.
225     * @return bool if the amount of leading whitespace is known.
226     */
227    public function hasValidLeadingWS(): bool {
228        return $this->leadingWS !== -1;
229    }
230
231    /**
232     * @note In most cases you should check to see if this node
233     * ::hasTrimmedWS() *and* whether the amount is valid.
234     * @return bool if the amount of trailing whitespace is known.
235     */
236    public function hasValidTrailingWS(): bool {
237        return $this->trailingWS !== -1;
238    }
239
240    /**
241     * Convert a TSR to a DSR with zero-width container open/close tags.
242     * @param SourceRange $tsr
243     * @return DomSourceRange
244     */
245    public static function fromTsr( SourceRange $tsr ): DomSourceRange {
246        if ( $tsr instanceof DomSourceRange ) {
247            return $tsr;
248        }
249        return new DomSourceRange(
250            $tsr->start, $tsr->end, null, null, source: $tsr->source
251        );
252    }
253
254    /**
255     * Create a DomSourceRange spanning the given Source.
256     */
257    public static function fromSource( Source $source ): static {
258        return new DomSourceRange(
259            0, strlen( $source->getSrcText() ), null, null,
260            source: $source
261        );
262    }
263
264    /**
265     * Create a new DomSourceRange from an array of integers/null (such as
266     * created during JSON serialization).
267     * @param array<int|null> $json
268     * @return DomSourceRange
269     */
270    public static function newFromJsonArray( array $json ): DomSourceRange {
271        $dsr = $json;
272        $n = count( $dsr );
273        Assert::invariant( $n === 2 || $n === 4 || $n === 6, 'Not enough elements in DSR array' );
274        return new DomSourceRange(
275            $dsr[0], $dsr[1], $dsr[2] ?? null, $dsr[3] ?? null, $dsr[4] ?? 0, $dsr[5] ?? 0
276        );
277    }
278
279    /**
280     * @inheritDoc
281     */
282    public function toJsonArray(): array {
283        $a = [ $this->start, $this->end, $this->openWidth, $this->closeWidth ];
284        if ( $this->leadingWS !== 0 || $this->trailingWS !== 0 ) {
285            $a[] = $this->leadingWS;
286            $a[] = $this->trailingWS;
287        }
288        return $a;
289    }
290
291    /** JsonCodec serialization hint. */
292    public static function hint(): Hint {
293        return Hint::build( self::class, Hint::USE_SQUARE );
294    }
295}