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