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