Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
65.22% |
165 / 253 |
|
40.00% |
14 / 35 |
CRAP | |
0.00% |
0 / 1 |
TexArray | |
65.22% |
165 / 253 |
|
40.00% |
14 / 35 |
914.33 | |
0.00% |
0 / 1 |
newCurly | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
checkForStyleArgs | |
63.64% |
7 / 11 |
|
0.00% |
0 / 1 |
7.73 | |||
checkForColor | |
50.00% |
5 / 10 |
|
0.00% |
0 / 1 |
8.12 | |||
checkForColorDefinition | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
checkForSideset | |
28.57% |
2 / 7 |
|
0.00% |
0 / 1 |
14.11 | |||
checkForLimits | |
84.21% |
16 / 19 |
|
0.00% |
0 / 1 |
15.89 | |||
checkForNot | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
checkForDerivatives | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
checkForNamedFctArgs | |
40.00% |
6 / 15 |
|
0.00% |
0 / 1 |
26.50 | |||
squashLiterals | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
renderMML | |
53.97% |
34 / 63 |
|
0.00% |
0 / 1 |
64.01 | |||
createMMLwithContext | |
23.08% |
3 / 13 |
|
0.00% |
0 / 1 |
37.13 | |||
addDerivativesContext | |
50.00% |
7 / 14 |
|
0.00% |
0 / 1 |
13.12 | |||
inCurlies | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
extractSubscripts | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
extractIdentifiers | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
10 | |||
getModIdent | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
push | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
pop | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
first | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
second | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
unshift | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
checkInput | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
render | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
isCurly | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setCurly | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getIterator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getArgs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetSet | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
offsetUnset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setRowSpecs | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRowSpecs | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace MediaWiki\Extension\Math\WikiTexVC\Nodes; |
6 | |
7 | use Generator; |
8 | use InvalidArgumentException; |
9 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMappings; |
10 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil; |
11 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil; |
12 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
13 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; |
14 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle; |
15 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup; |
16 | use MediaWiki\Extension\Math\WikiTexVC\TexUtil; |
17 | |
18 | /** |
19 | * |
20 | */ |
21 | class TexArray extends TexNode implements \ArrayAccess, \IteratorAggregate { |
22 | protected bool $curly = false; |
23 | private ?LengthSpec $rowSpecs = null; |
24 | |
25 | public static function newCurly( ...$args ) { |
26 | $node = new self( ...$args ); |
27 | $node->curly = true; |
28 | return $node; |
29 | } |
30 | |
31 | public function __construct( ...$args ) { |
32 | $nargs = []; |
33 | |
34 | foreach ( $args as &$arg ) { |
35 | if ( $arg !== null ) { |
36 | array_push( $nargs, $arg ); |
37 | } |
38 | } |
39 | |
40 | self::checkInput( $nargs ); |
41 | parent::__construct( ...$nargs ); |
42 | } |
43 | |
44 | public function checkForStyleArgs( $node ) { |
45 | if ( $node instanceof Literal ) { |
46 | $name = trim( $node->getArg() ); |
47 | switch ( $name ) { |
48 | case "\\displaystyle": |
49 | return [ "displaystyle" => "true", "scriptlevel" => "0" ]; |
50 | case "\\scriptstyle": |
51 | return [ "displaystyle" => "false", "scriptlevel" => "1" ]; |
52 | case "\\scriptscriptstyle": |
53 | return [ "displaystyle" => "false", "scriptlevel" => "2" ]; |
54 | case "\\textstyle": |
55 | return [ "displaystyle" => "false", "scriptlevel" => "0" ]; |
56 | } |
57 | } |
58 | return null; |
59 | } |
60 | |
61 | /** |
62 | * Checks if an TexNode of Literal contains color information (color, pagecolor) |
63 | * and returns info how to continue with the parsing. |
64 | * @param TexNode $node node to check if it contains color info |
65 | * @return array index 0: (bool) was color element found, index 1: (string) specified color |
66 | */ |
67 | public function checkForColor( TexNode $node ) { |
68 | if ( $node instanceof Literal ) { |
69 | $name = trim( $node->getArg() ); |
70 | if ( str_contains( $name, "\\color" ) ) { |
71 | $foundOperatorContent = MMLutil::initalParseLiteralExpression( $node->getArg() ); |
72 | if ( !$foundOperatorContent ) { |
73 | // discarding color elements which not specify color |
74 | return [ true, null ]; |
75 | } else { |
76 | return [ true, $foundOperatorContent[2][0] ]; |
77 | } |
78 | } elseif ( str_contains( $name, "\\pagecolor" ) ) { |
79 | return [ true, null ]; |
80 | } |
81 | } |
82 | return [ false, null ]; |
83 | } |
84 | |
85 | public function checkForColorDefinition( TexNode $node ): ?array { |
86 | if ( $node instanceof Literal ) { |
87 | $name = trim( $node->getArg() ); |
88 | if ( str_contains( $name, "\\definecolor" ) ) { |
89 | return MMLParsingUtil::parseDefineColorExpression( $node->getArg() ); |
90 | } |
91 | } |
92 | return null; |
93 | } |
94 | |
95 | /** |
96 | * Checks two sequential nodes in TexArray if they contain information on sideset expressions. |
97 | * @param TexNode $currentNode first node in array to check (for sideset expression) |
98 | * @param TexNode|null $nextNode second node in array to check (for succeeding operator) |
99 | * @return TexNode|null the succeeding operator for further Parsing or null if sideset not found or invalid |
100 | */ |
101 | public function checkForSideset( TexNode $currentNode, ?TexNode $nextNode ): ?TexNode { |
102 | if ( !( $currentNode instanceof Fun2nb && $currentNode->getFname() == "\\sideset" ) ) { |
103 | return null; |
104 | } |
105 | if ( $nextNode instanceof Literal ) { |
106 | return $nextNode; |
107 | } |
108 | if ( $nextNode instanceof FQ ) { |
109 | return $nextNode; |
110 | } |
111 | return null; |
112 | } |
113 | |
114 | public function checkForLimits( $currentNode, $nextNode ) { |
115 | // Preceding 'lim' in example: "\\lim_{x \\to 2}" |
116 | if ( ( $currentNode instanceof DQ || $currentNode instanceof FQ ) |
117 | && $currentNode->containsFunc( "\\lim" ) ) { |
118 | |
119 | if ( $currentNode->getBase() instanceof TexArray ) { |
120 | return [ $currentNode->getBase()->getArgs()[0], false ]; |
121 | } else { |
122 | return [ $currentNode->getBase(), false ]; |
123 | } |
124 | } |
125 | |
126 | /** Find cases which have preceding Literals with nullary_macro-type operators i.e.: |
127 | * "\iint\limits_D \, dx\,dy" |
128 | */ |
129 | $tu = TexUtil::getInstance(); |
130 | |
131 | // Check whether the current node is a possible preceding literal |
132 | if ( !( |
133 | // logically superfluous brackets were inserted to improve readability |
134 | ( $currentNode instanceof Literal && |
135 | // Check if the current node is a nullary macro such as \iint, \sum, \prod, etc. |
136 | ( $tu->nullary_macro( trim( $currentNode->getArg() ) ) |
137 | // or a limit operator |
138 | || ( trim( $currentNode->getArg() ) == "\\lim" ) ) ) || |
139 | // or the special case of \operatorname |
140 | ( $currentNode instanceof Fun1nb && $currentNode->getFname() == "\\operatorname" ) |
141 | ) ) { |
142 | return [ null, false ]; |
143 | } |
144 | |
145 | // Check whether the next node is a possible limits construct |
146 | if ( !( ( $nextNode instanceof DQ || $nextNode instanceof FQ ) |
147 | && $nextNode->getBase() instanceof Literal |
148 | && ( $nextNode->containsFunc( "\\limits" ) || $nextNode->containsFunc( "\\nolimits" ) ) |
149 | ) ) { |
150 | return [ null, false ]; |
151 | |
152 | } |
153 | return [ $currentNode, true ]; |
154 | } |
155 | |
156 | public function checkForNot( $currentNode ): bool { |
157 | if ( $currentNode instanceof Literal && trim( $currentNode->getArg() ) == "\\not" ) { |
158 | return true; |
159 | } |
160 | return false; |
161 | } |
162 | |
163 | public function checkForDerivatives( $iStart, $args ): int { |
164 | $ctr = 0; |
165 | for ( $i = $iStart, $count = count( $this->args ); $i < $count; $i++ ) { |
166 | $followUp = $args[$i]; |
167 | if ( $followUp instanceof Literal && $followUp->getArg() === "'" ) { |
168 | $ctr++; |
169 | } else { |
170 | break; |
171 | } |
172 | } |
173 | |
174 | return $ctr; |
175 | } |
176 | |
177 | public function checkForNamedFctArgs( $currentNode, $nextNode ) { |
178 | // Check if current node is named function |
179 | $hasNamedFct = false; |
180 | if ( $currentNode instanceof TexArray && count( $currentNode->args ) == 2 ) { |
181 | $tu = TexUtil::getInstance(); |
182 | $currentNodeContent = $currentNode[0]; |
183 | if ( $currentNodeContent instanceof Literal && |
184 | $tu->latex_function_names( $currentNodeContent->getArg() ) ) { |
185 | $hasNamedFct = true; |
186 | } |
187 | } elseif ( $currentNode instanceof Fun1nb && $currentNode->getFname() === '\\operatorname' ) { |
188 | $hasNamedFct = true; |
189 | } |
190 | |
191 | // Check if there is a valid argument as next parameter |
192 | $hasValidParameters = false; |
193 | if ( !$hasNamedFct ) { |
194 | return [ $hasNamedFct, $hasValidParameters ]; |
195 | } |
196 | |
197 | if ( $nextNode ) { |
198 | $hasValidParameters = true; |
199 | } |
200 | |
201 | return [ $hasNamedFct, $hasValidParameters ]; |
202 | } |
203 | |
204 | private function squashLiterals() { |
205 | $tmp = ''; |
206 | foreach ( $this->args as $arg ) { |
207 | if ( !( $arg instanceof Literal ) ) { |
208 | return; |
209 | } |
210 | // Don't squash if there is a macro in the literal |
211 | if ( preg_match( "/[\\\\]/", $arg->getArg() ) ) { |
212 | return; |
213 | } |
214 | $tmp .= $arg->getArg(); |
215 | } |
216 | $this->args = [ new Literal( $tmp ) ]; |
217 | $this->curly = false; |
218 | } |
219 | |
220 | public function renderMML( $arguments = [], $state = [] ) { |
221 | // Everything here is for parsing displaystyle, probably refactored to WikiTexVC grammar later |
222 | $fullRenderedArray = ""; |
223 | $mmlStyles = []; |
224 | $currentColor = null; |
225 | |
226 | if ( array_key_exists( 'squashLiterals', $state ) ) { |
227 | $this->squashLiterals(); |
228 | } |
229 | |
230 | for ( $i = 0, $count = count( $this->args ); $i < $count; $i++ ) { |
231 | $current = $this->args[$i]; |
232 | if ( isset( $this->args[$i + 1] ) ) { |
233 | $next = $this->args[$i + 1]; |
234 | } else { |
235 | $next = null; |
236 | } |
237 | // Check for sideset |
238 | $foundSideset = $this->checkForSideset( $current, $next ); |
239 | if ( $foundSideset ) { |
240 | $state["sideset"] = $foundSideset; |
241 | // Skipping the succeeding Literal |
242 | $i++; |
243 | } |
244 | |
245 | // Check for limits |
246 | $foundLimits = $this->checkForLimits( $current, $next ); |
247 | if ( $foundLimits[0] ) { |
248 | $state["limits"] = $foundLimits[0]; |
249 | if ( $foundLimits[1] ) { |
250 | continue; |
251 | } |
252 | } |
253 | |
254 | // Check for Not |
255 | $foundNot = $this->checkForNot( $current ); |
256 | if ( $foundNot ) { |
257 | $state["not"] = true; |
258 | continue; |
259 | } |
260 | |
261 | // Check for derivatives |
262 | $foundDeriv = $this->checkForDerivatives( $i + 1, $this->args ); |
263 | if ( $foundDeriv > 0 ) { |
264 | // skip the next indices which are derivative characters |
265 | $i += $foundDeriv; |
266 | $state["deriv"] = $foundDeriv; |
267 | } |
268 | |
269 | // Check if there is a new color definition and add it to state |
270 | $foundColorDef = $this->checkForColorDefinition( $current ); |
271 | if ( $foundColorDef ) { |
272 | $state["colorDefinitions"][$foundColorDef["name"]] = $foundColorDef; |
273 | continue; |
274 | } |
275 | // Pass preceding color info to state |
276 | $foundColor = $this->checkForColor( $current ); |
277 | if ( $foundColor[0] ) { |
278 | $currentColor = $foundColor[1]; |
279 | // Skipping the color element itself for rendering |
280 | continue; |
281 | } |
282 | $styleArguments = $this->checkForStyleArgs( $current ); |
283 | |
284 | $foundNamedFct = $this->checkForNamedFctArgs( $current, $next ); |
285 | if ( $foundNamedFct[0] ) { |
286 | $state["foundNamedFct"] = $foundNamedFct; |
287 | } |
288 | |
289 | if ( $styleArguments ) { |
290 | $state["styleargs"] = $styleArguments; |
291 | $mmlStyle = new MMLmstyle( "", $styleArguments ); |
292 | $fullRenderedArray .= $mmlStyle->getStart(); |
293 | if ( $next instanceof TexNode && $next->isCurly() ) { |
294 | // Wrap with style-tags when the next element is a Curly which determines start and end tag. |
295 | $fullRenderedArray .= $this->createMMLwithContext( $currentColor, $next, $state, $arguments ); |
296 | $fullRenderedArray .= $mmlStyle->getEnd(); |
297 | $mmlStyle = null; |
298 | unset( $state["styleargs"] ); |
299 | $i++; |
300 | } else { |
301 | // Start the style indicator in cases like \textstyle abc |
302 | $mmlStyles[] = $mmlStyle->getEnd(); |
303 | |
304 | } |
305 | } else { |
306 | $fullRenderedArray .= $this->createMMLwithContext( $currentColor, $current, $state, $arguments ); |
307 | } |
308 | |
309 | if ( array_key_exists( "not", $state ) ) { |
310 | unset( $state["not"] ); |
311 | } |
312 | if ( array_key_exists( "limits", $state ) ) { |
313 | unset( $state["limits"] ); |
314 | } |
315 | if ( array_key_exists( "deriv", $state ) ) { |
316 | unset( $state["deriv"] ); |
317 | } |
318 | } |
319 | |
320 | foreach ( array_reverse( $mmlStyles ) as $mmlStyleEnd ) { |
321 | $fullRenderedArray .= $mmlStyleEnd; |
322 | } |
323 | if ( $this->curly && $this->getLength() > 1 ) { |
324 | $mmlRow = new MMLmrow(); |
325 | return $mmlRow->encapsulateRaw( $fullRenderedArray ); |
326 | } |
327 | |
328 | return $fullRenderedArray; |
329 | } |
330 | |
331 | private function createMMLwithContext( $currentColor, $currentNode, $state, $arguments ) { |
332 | if ( $currentColor ) { |
333 | if ( array_key_exists( "colorDefinitions", $state ) |
334 | && is_array( $state["colorDefinitions"] ) |
335 | && array_key_exists( $currentColor, $state["colorDefinitions"] ?? [] ) |
336 | && is_array( $state["colorDefinitions"][$currentColor] ) |
337 | && array_key_exists( "hex", $state["colorDefinitions"][$currentColor] ) |
338 | ) { |
339 | $displayedColor = $state["colorDefinitions"][$currentColor]["hex"]; |
340 | |
341 | } else { |
342 | $resColor = BaseMappings::getColorByKey( $currentColor ); |
343 | $displayedColor = $resColor ? $resColor[0] : $currentColor; |
344 | } |
345 | $mmlStyleColor = new MMLmstyle( "", [ "mathcolor" => $displayedColor ] ); |
346 | $ret = $mmlStyleColor->encapsulateRaw( $currentNode->renderMML( $arguments, $state ) ); |
347 | } else { |
348 | $ret = $currentNode->renderMML( $arguments, $state ); |
349 | } |
350 | |
351 | return $this->addDerivativesContext( $state, $ret ); |
352 | } |
353 | |
354 | /** |
355 | * If derivative was recognized, add the corresponding derivative math operator |
356 | * to the mml and wrap with msup element. |
357 | * @param array $state state indicator which indicates derivative |
358 | * @param string $mml mathml input |
359 | * @return string mml with additional mml-elements for derivatives |
360 | */ |
361 | public function addDerivativesContext( $state, string $mml ): string { |
362 | if ( array_key_exists( "deriv", $state ) && $state["deriv"] > 0 ) { |
363 | $msup = new MMLmsup(); |
364 | $moDeriv = new MMLmo(); |
365 | |
366 | if ( $state["deriv"] == 1 ) { |
367 | $derInfo = "′"; |
368 | } elseif ( $state["deriv"] == 2 ) { |
369 | $derInfo = "″"; |
370 | } elseif ( $state["deriv"] == 3 ) { |
371 | $derInfo = "‴"; |
372 | } elseif ( $state["deriv"] == 4 ) { |
373 | $derInfo = "⁗"; |
374 | } else { |
375 | $derInfo = str_repeat( "′", $state["deriv"] ); |
376 | } |
377 | |
378 | $mml = $msup->encapsulateRaw( $mml . $moDeriv->encapsulateRaw( $derInfo ) ); |
379 | } |
380 | return $mml; |
381 | } |
382 | |
383 | public function inCurlies() { |
384 | if ( isset( $this->args[0] ) && count( $this->args ) == 1 ) { |
385 | return $this->args[0]->inCurlies(); |
386 | } else { |
387 | return '{' . parent::render() . '}'; |
388 | } |
389 | } |
390 | |
391 | public function extractSubscripts() { |
392 | $y = []; |
393 | |
394 | foreach ( $this->args as $x ) { |
395 | $y = array_merge( $y, $x->extractSubscripts() ); |
396 | } |
397 | if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) { |
398 | return implode( '', $y ); |
399 | } |
400 | return []; |
401 | } |
402 | |
403 | public function extractIdentifiers( $args = null ) { |
404 | if ( $args == null ) { |
405 | $args = $this->args; |
406 | } |
407 | $list = parent::extractIdentifiers( $args ); |
408 | $outpos = 0; |
409 | $offset = 0; |
410 | $int = 0; |
411 | |
412 | for ( $inpos = 0; $inpos < count( $list ); $inpos++ ) { |
413 | $outpos = $inpos - $offset; |
414 | switch ( $list[$inpos] ) { |
415 | case '\'': |
416 | $list[$outpos - 1] .= '\''; |
417 | $offset++; |
418 | break; |
419 | case '\\int': |
420 | $int++; |
421 | $offset++; |
422 | break; |
423 | case '\\mathrm{d}': |
424 | case 'd': |
425 | if ( $int ) { |
426 | $int--; |
427 | $offset++; |
428 | break; |
429 | } |
430 | // no break |
431 | default: |
432 | if ( isset( $list[0] ) ) { |
433 | $list[$outpos] = $list[$inpos]; |
434 | } |
435 | } |
436 | } |
437 | return array_slice( $list, 0, count( $list ) - $offset ); |
438 | } |
439 | |
440 | public function getModIdent() { |
441 | $y = []; |
442 | |
443 | foreach ( $this->args as $x ) { |
444 | $y = array_merge( $y, $x->getModIdent() ); |
445 | } |
446 | |
447 | if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) { |
448 | return implode( "", $y ); |
449 | } |
450 | return []; |
451 | } |
452 | |
453 | public function push( ...$elements ) { |
454 | self::checkInput( $elements ); |
455 | |
456 | array_push( $this->args, ...$elements ); |
457 | return $this; |
458 | } |
459 | |
460 | public function pop() { |
461 | array_splice( $this->args, 0, 1 ); |
462 | } |
463 | |
464 | /** |
465 | * @return TexNode|null first value |
466 | */ |
467 | public function first() { |
468 | return $this->args[0] ?? null; |
469 | } |
470 | |
471 | /** |
472 | * @return TexNode|null second value |
473 | */ |
474 | public function second() { |
475 | return $this->args[1] ?? null; |
476 | } |
477 | |
478 | public function unshift( ...$elements ): TexArray { |
479 | array_unshift( $this->args, ...$elements ); |
480 | return $this; |
481 | } |
482 | |
483 | /** |
484 | * @throws InvalidArgumentException if args not of correct type |
485 | * @param TexNode[] $args input args |
486 | * @return void |
487 | */ |
488 | private static function checkInput( $args ): void { |
489 | foreach ( $args as $arg ) { |
490 | if ( !( $arg instanceof TexNode ) ) { |
491 | throw new InvalidArgumentException( 'Wrong input type specified in input elements.' ); |
492 | } |
493 | } |
494 | } |
495 | |
496 | public function render() { |
497 | if ( $this->curly ) { |
498 | return $this->inCurlies(); |
499 | } |
500 | return parent::render(); |
501 | } |
502 | |
503 | public function isCurly(): bool { |
504 | return $this->curly; |
505 | } |
506 | |
507 | public function setCurly( $curly = true ): TexArray { |
508 | $this->curly = $curly; |
509 | return $this; |
510 | } |
511 | |
512 | /** |
513 | * @return Generator<TexNode> |
514 | */ |
515 | public function getIterator(): Generator { |
516 | yield from $this->args; |
517 | } |
518 | |
519 | /** |
520 | * @return TexNode[] |
521 | */ |
522 | public function getArgs(): array { |
523 | return parent::getArgs(); |
524 | } |
525 | |
526 | public function offsetExists( $offset ): bool { |
527 | return isset( $this->args[$offset] ); |
528 | } |
529 | |
530 | public function offsetGet( $offset ): ?TexNode { |
531 | return $this->args[$offset] ?? null; |
532 | } |
533 | |
534 | public function offsetSet( $offset, $value ): void { |
535 | if ( !( $value instanceof TexNode ) ) { |
536 | throw new InvalidArgumentException( 'TexArray elements must be of type TexNode.' ); |
537 | } |
538 | $this->args[$offset] = $value; |
539 | } |
540 | |
541 | public function offsetUnset( $offset ): void { |
542 | unset( $this->args[$offset] ); |
543 | } |
544 | |
545 | public function setRowSpecs( ?LengthSpec $r ) { |
546 | $this->rowSpecs = $r; |
547 | } |
548 | |
549 | public function getRowSpecs(): ?LengthSpec { |
550 | return $this->rowSpecs; |
551 | } |
552 | } |