Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.24% covered (warning)
74.24%
441 / 594
51.16% covered (warning)
51.16%
22 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseParsing
74.24% covered (warning)
74.24%
441 / 594
51.16% covered (warning)
51.16%
22 / 43
1233.54
0.00% covered (danger)
0.00%
0 / 1
 accent
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
8.02
 array
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
13.15
 alignAt
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
10
 boldsymbol
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 cancel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 menclose
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 cancelTo
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 chemCustom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 customLetters
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 cFrac
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 crLaTeX
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 dots
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 genFrac
78.05% covered (warning)
78.05%
32 / 41
0.00% covered (danger)
0.00%
0 / 1
14.79
 frac
50.00% covered (danger)
50.00%
8 / 16
0.00% covered (danger)
0.00%
0 / 1
8.12
 hline
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hskip
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
6.97
 handleOperatorName
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 matrix
100.00% covered (success)
100.00%
59 / 59
100.00% covered (success)
100.00%
1 / 1
25
 over
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
10
 oint
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
8.02
 overset
55.56% covered (warning)
55.56%
10 / 18
0.00% covered (danger)
0.00%
0 / 1
2.35
 phantom
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
 raiseLower
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 underset
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 underOver
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 mathFont
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 mathChoice
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
132
 makeBig
85.19% covered (warning)
85.19%
23 / 27
0.00% covered (danger)
0.00%
0 / 1
15.73
 machine
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFont
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sideset
90.00% covered (success)
90.00%
36 / 40
0.00% covered (danger)
0.00%
0 / 1
12.14
 spacer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 smash
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
72
 texAtom
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 intent
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
240
 hBox
67.65% covered (warning)
67.65%
23 / 34
0.00% covered (danger)
0.00%
0 / 1
22.62
 setStyle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 not
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 vbox
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 sqrt
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 tilde
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 xArrow
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
2
 getApplyFct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings;
3
4use IntlChar;
5use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Tag;
6use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\TexClass;
7use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants;
8use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil;
9use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLarray;
11use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase;
12use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror;
13use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmfrac;
14use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi;
15use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmmultiscripts;
16use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
17use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmover;
18use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded;
19use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmphantom;
20use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmroot;
21use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
22use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace;
23use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsqrt;
24use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle;
25use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup;
26use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtable;
27use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtd;
28use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext;
29use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtr;
30use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunder;
31use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunderover;
32use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
33use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ;
34use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1;
35use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun2;
36use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun2sq;
37use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun4;
38use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
39use MediaWiki\Extension\Math\WikiTexVC\Nodes\Matrix;
40use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
41use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
42use MediaWiki\Extension\Math\WikiTexVC\Nodes\UQ;
43use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
44
45/**
46 * Parsing functions for specific recognized mappings.
47 * Usually the parsing functions are invoked from the BaseMethods classes.
48 */
49class BaseParsing {
50
51    public static function accent( $node, $passedArgs, $name, $operatorContent,
52                                   string $accent, ?bool $stretchy = null ): MMLbase {
53        // Currently this is own implementation from Fun1.php
54        // TODO The first if-clause is mathjax specific (and not necessary by generic parsers)
55        // and will most probably removed (just for running all tc atm)
56        if ( $accent == "00B4" || $accent == "0060" ) {
57            $attrs = [ Tag::SCRIPTTAG => "true" ];
58        } else {
59            if ( $stretchy === null ) {
60                $attrs = [];
61            } else {
62                $attrs = [ "stretchy" => $stretchy ? "true" : "false" ];
63            }
64        }
65        if ( trim( $operatorContent ) === '\\vec' ) {
66            $attrs['class'] = 'mwe-math-vec';
67        }
68
69        // Fetching entity from $accent key tbd
70        $entity = MMLutil::createEntity( $accent );
71        if ( !$entity ) {
72            $entity = $accent;
73        }
74        $inner = $node->getArg()->toMMLtree( $passedArgs );
75
76        return MMLmover::newSubtree(
77            !$inner->isEmpty() ? $inner : ( new MMLmrow() ),
78            new MMLmo( '', $attrs, $entity )
79        );
80    }
81
82    public static function array( $node, $passedArgs, $operatorContent, $name, $begin = null, $open = null,
83        $close = null, $align = null, $spacing = null,
84        $vspacing = null, $style = null, $raggedHeight = null
85    ): MMLbase {
86        $output = [];
87        if ( $open != null ) {
88            $resDelimiter = TexUtil::getInstance()->delimiter( trim( $open ) ) ?? false;
89            if ( $resDelimiter ) {
90                // $retDelim = $bm->checkAndParseDelimiter($open, $node,$passedArgs,true);
91                $output[] = ( new MMLmo( TexClass::OPEN, [], $resDelimiter[0] ) );
92            }
93        }
94        if ( $name == "Bmatrix" || $name == "bmatrix" || $name == "Vmatrix"
95            || $name == "vmatrix" || $name == "smallmatrix" || $name == "pmatrix" || $name == "matrix" ) {
96            // This is a workaround and might be improved mapping BMatrix to Matrix directly instead of array
97            return self::matrix( $node, $passedArgs, $operatorContent, $name,
98                $open, $close, null, null, null, null, true );
99
100        } else {
101            $output[] = new MMLmrow( TexClass::ORD, [], $node->getMainarg()->toMMLtree() );
102        }
103
104        if ( $close != null ) {
105            $resDelimiter = TexUtil::getInstance()->delimiter( trim( $close ) ) ?? false;
106            if ( $resDelimiter ) {
107                $output[] = new MMLmo( TexClass::CLOSE, [], $resDelimiter[0] );
108            }
109        }
110        return new MMLarray( ...$output );
111    }
112
113    public static function alignAt( Matrix $node, $passedArgs, $operatorContent, $name, $align = null,
114        $smth2 = null
115    ): MMLbase {
116        $mtable  = new MMLmtable( '', [ 'displaystyle' => 'true' ] );
117        $inner = [];
118        $align ??= $node->getAlign();
119        $rowNo = 0;
120        $rowCount = count( $node->getArgs() );
121        foreach ( $node as $tableRow ) {
122            $rowNo++;
123            $mtds = [];
124            $colNo = 0;
125            $isEmptyLine = true;
126            $attributes = [];
127            $rowSpecs = $tableRow->getRowSpecs();
128            if ( $rowSpecs ) {
129                $attributes['style'] = "padding-bottom: {$rowSpecs->getCssLength()};";
130            }
131            foreach ( $tableRow->getArgs() as $tableCell ) {
132                $class = '';
133                if ( in_array( $align[$colNo] ?? [], [ 'l', 'r' ] ) ) {
134                    // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
135                    $class .= ' mwe-math-columnalign-' . $align[$colNo];
136                }
137                $class = trim( $class );
138                $isEmptyLine = $isEmptyLine && $tableCell->isEmpty();
139                $mtds[] = new MMLmtd( "",
140                    $attributes + ( $class ? [ 'class' => $class ] : [] ),
141                    $tableCell->toMMLtree() );
142                $colNo++;
143            }
144            // empty trailing lines with only one empty cell should not be rendered
145            if ( $rowNo === $rowCount && $colNo === 1 && $isEmptyLine ) {
146                continue;
147            }
148            $inner[] = new MMLmtr( "", [], ...$mtds );
149        }
150        $mtable->addChild( ...$inner );
151        return $mtable;
152    }
153
154    public static function boldsymbol( $node, $passedArgs, $operatorContent, $name, $smth = null,
155        $smth2 = null
156    ): MMLbase {
157        $passedArgs = array_merge( $passedArgs, [ 'mathvariant' => Variants::BOLDITALIC ] );
158        return new MMLmrow( TexClass::ORD, [], $node->getArg()->toMMLtree( $passedArgs ) );
159    }
160
161    public static function cancel( Fun1 $node, $passedArgs, $operatorContent, $name, $notation = '' ): MMLbase {
162        return self::menclose( $node->getArg()->toMMLtree(), $notation );
163    }
164
165    private static function menclose( MMLbase $content, string $notation ): MMLbase {
166        $classes = [ 'menclose' ];
167        foreach ( explode( ' ', $notation ) as $element ) {
168            $classes[] = 'menclose-' . $element;
169        }
170
171        return new MMLmrow( '', [ 'class' => implode( ' ', $classes ) ],
172            new MMLmrow( '', [], $content ) );
173    }
174
175    public static function cancelTo( $node, $passedArgs, $operatorContent, $name, string $notation = '' ): MMLbase {
176        // Match MathJax's 0.1em upward adjustment for the cancelto annotation:
177        // https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/cancel/CancelConfiguration.ts
178        $mpAdded = new MMLmpadded( "", [ "depth" => "-.1em", "voffset" => ".1em",
179            "class" => "mwe-math-cancelto" ],
180            $node->getArg1()->toMMLtree() );
181        $menclose = self::menclose( $node->getArg2()->toMMLtree(), $notation );
182        return new MMLmrow( TexClass::ORD, [], MMLmsup::newSubtree( $menclose, $mpAdded ) );
183    }
184
185    public static function chemCustom( $node, $passedArgs, $operatorContent, $name, $translation = null ): MMLmerror {
186        return MMLmerror::newFromText( $translation ?: 'tbd chemCustom' );
187    }
188
189    public static function customLetters( $node, $passedArgs, $operatorContent, $name, $char,
190            $isOperator = false
191    ): MMLbase {
192        if ( $isOperator ) {
193            return new MMLmrow( TexClass::ORD, [], new MMLmo( "", [], $char ) );
194        }
195        $variant = $passedArgs['mathvariant'] ?? Variants::NORMAL;
196        return new MMLmrow( TexClass::ORD, [], new MMLmi( "", [ 'mathvariant' => $variant ], $char ) );
197    }
198
199    public static function cFrac( $node, $passedArgs, $operatorContent, $name ): MMLbase {
200        $mstyle1 = new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ],
201            new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree() ) );
202        $mstyle2 = new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ],
203            new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree() ) );
204        // See TexUtilMMLTest testcase 81
205        // (mml3 might be erronous here, but this element seems to be rendered correctly)
206        $whatIsThis = new MMLmrow( TexClass::ORD, [],
207            new MMLmpadded( "", [ "depth" => "3pt", "height" => "8.6pt", "width" => "0" ] ) );
208        $inner = new MMLmrow( TexClass::ORD, [], $whatIsThis, $mstyle2 );
209        $mfrac = MMLmfrac::newSubtree(
210            new MMLmrow( TexClass::ORD, [], $whatIsThis, $mstyle1 ), $inner );
211        return new MMLmrow( TexClass::ORD, [], $mfrac );
212    }
213
214    public static function crLaTeX( $node, $passedArgs, $operatorContent, $name ): MMLbase {
215        return new MMLmspace( "", [ "linebreak" => "newline" ] );
216    }
217
218    public static function dots( $node, $passedArgs, $operatorContent, $name, $smth = null, $smth2 = null ): MMLbase {
219        switch ( $operatorContent['dots'] ?? 'dotso' ) {
220            case 'dotsb':
221                return new MMLmo( "", $passedArgs, "&#x22EF;" );
222            case 'dotsi':
223                return new MMLarray( ( new Literal( '\\!' ) )->toMMLTree(),
224                    new MMLmo( "", $passedArgs, "&#x22EF;" ) );
225            case 'rightdelim':
226                return new MMLarray( new MMLmo( "", $passedArgs, "&#x2026;" ),
227                    ( new Literal( '\\,' ) )->toMMLTree() );
228            default:
229                return new MMLmo( "", $passedArgs, "&#x2026;" );
230        }
231    }
232
233    public static function genFrac( $node, $passedArgs, $operatorContent, $name,
234        $left = null, $right = null, $thick = null, $style = null
235    ): MMLbase {
236        // Actually this is in AMSMethods, consider refactoring  left, right, thick, style
237        $bm = new BaseMethods();
238        $ret = $bm->checkAndParseDelimiter( $name, $node, $passedArgs, $operatorContent, true );
239        if ( !$ret->isEmpty() ) {
240            // TBD
241            if ( $left == null ) {
242                $left = $ret;
243            }
244            if ( $right == null ) {
245                $right = $ret;
246            }
247            if ( $thick == null ) {
248                $thick = $ret;
249            }
250            if ( $style == null ) {
251                $style = trim( $ret );
252            }
253        }
254        $attrs = [];
255        $displayStyle = "false";
256        if ( in_array( $thick, [ 'thin', 'medium', 'thick', '0' ], true ) ) {
257            $attrs = array_merge( $attrs, [ "linethickness" => $thick ] );
258        }
259        if ( $style !== '' ) {
260            $styleDigit = intval( $style, 10 );
261            $styleAlpha = [ 'D', 'T', 'S', 'SS' ][$styleDigit];
262            if ( $styleAlpha == null ) {
263                return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], "Bad math style" ) );
264            }
265
266            if ( $styleAlpha === 'D' ) {
267                // MathJax fences \genfrac with \bigg in display style and \big otherwise
268                // swh:1:cnt:191254e9b9aedba501b4f734fb8f0b6cd9217050;lines=377-378
269                $displayStyle = "true";
270                $styleAttr = [ "minsize" => TexUtil::getInstance()->callback( '\\bigg' )[2] ];
271
272            } else {
273                $styleAttr = [ "minsize" => TexUtil::getInstance()->callback( '\\big' )[2] ];
274            }
275        } else {
276            // Inherit delimiter size and script level when no explicit style was requested.
277            $styleAttr = [];
278        }
279        $output = [];
280        if ( $left ) {
281            $mrowOpen = new MMLmrow( TexClass::OPEN, [], new MMLmo( "", $styleAttr, $left ) );
282            $output[] = $mrowOpen;
283        }
284        // TeX sets the parts in text style unless \genfrac asks for display style.
285        $state1 = [ 'styleargs' => [ 'displaystyle' => $displayStyle ] ];
286        $state2 = $state1;
287        $mrow1 = new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree( [], $state1 ) );
288        $mrow2 = new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree( [], $state2 ) );
289
290        $output[] = MMLmfrac::newSubtree( $mrow1, $mrow2, "", $attrs );
291        if ( $right ) {
292            $mrowClose = new MMLmrow( TexClass::CLOSE, [], new MMLmo( "", $styleAttr, $right ) );
293            $output[] = $mrowClose;
294        }
295        $output = new MMLmrow( TexClass::ORD, [], ...$output );
296        if ( $style !== '' ) {
297            $output = new MMLmstyle( "", [ "displaystyle" => $displayStyle, "scriptlevel" => "0" ], $output );
298        }
299
300        return new MMLmrow( TexClass::ORD, [], $output );
301    }
302
303    public static function frac( $node, $passedArgs, $operatorContent, $name ): MMLbase {
304        // TeX sets the parts of a fraction in text style.
305        $state = [ 'styleargs' => [ 'displaystyle' => 'false' ] ];
306        $state1 = $state;
307        $state2 = $state;
308        if ( $node instanceof Fun2 ) {
309            $inner = [ new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree( [], $state1 ) ),
310                new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree( [], $state2 ) ) ];
311        } elseif ( $node instanceof DQ ) {
312            $inner = [ new MMLmrow( TexClass::ORD, [], $node->getBase()->toMMLtree( [], $state1 ) ),
313                new MMLmrow( TexClass::ORD, [], $node->getDown()->toMMLtree( [], $state2 ) ) ];
314        } else {
315            $inner = [];
316            foreach ( $node->getArgs() as $arg ) {
317                $argState = $state;
318                $rendered = is_string( $arg ) ? $arg : $arg->toMMLtree( [], $argState );
319                $inner[] = new MMLmrow( TexClass::ORD, [], $rendered );
320            }
321        }
322        $mfrac = MMLmfrac::newSubtree( $inner[0], $inner[1] );
323        return new MMLmrow( TexClass::ORD, [], $mfrac );
324    }
325
326    public static function hline( $node, $passedArgs, $operatorContent, $name,
327            $smth1 = null, $smth2 = null, $smth3 = null, $smth4 = null
328    ): MMLbase {
329        // HLine is most probably not parsed this way, since only parsed in Matrix context
330        return new MMLmrow( "tbd", [], new MMLmtext( "", [], "HLINE TBD" ) );
331    }
332
333    public static function hskip( $node, $passedArgs, $operatorContent, $name ): MMLbase {
334        if ( $node->getArg()->isCurly() ) {
335            $unit = MMLutil::squashLitsToUnit( $node->getArg() );
336            if ( !$unit ) {
337                return new MMLarray();
338            }
339            $em = MMLutil::dimen2em( $unit );
340        } else {
341            // Prevent parsing in unmapped cases
342            return new MMLarray();
343        }
344        // Added kern j4t
345        if ( $name == "mskip" || $name == "mkern" || "kern" ) {
346            $args = [ "width" => $em ];
347        } else {
348            return new MMLarray();
349        }
350
351        return new MMLmspace( "", $args );
352    }
353
354    public static function handleOperatorName( $node, $passedArgs, $operatorContent, $name ): MMLbase {
355        // In example "\\operatorname{a}"
356        $applyFct = self::getApplyFct( $operatorContent );
357        $mmlNot = new MMLarray();
358        if ( isset( $operatorContent['not'] ) && $operatorContent['not'] ) {
359            $mmlNot = MMLParsingUtil::createNot();
360        }
361        $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::OP, 'mathvariant' => Variants::NORMAL ] );
362        $state = [ 'squashLiterals' => true ];
363        $inner = $node->getArg()->toMMLtree( $passedArgs, $state );
364        if ( $inner instanceof MMLarray && count( $inner->getChildren() ) == 1 ) {
365            $mi = $inner->getChildren()[0];
366            // this check needs to be made explicit for phan
367            if ( $mi instanceof MMLmi ) {
368                $inner = $mi;
369            }
370        }
371        return new MMLarray( $mmlNot, $inner, $applyFct );
372    }
373
374    public static function matrix( Matrix $node, $passedArgs, $operatorContent,
375        $name, $open = null, $close = null, $align = null, $spacing = null,
376        $vspacing = null, $style = null, $cases = null, $numbered = null
377    ): MMLbase {
378        $resInner = [];
379        $boarder = $node->getBoarder();
380        if ( !$align ) {
381            $align = $node->getAlign();
382        }
383        $rowNo = 0;
384        $lines = $node->getLines();
385        foreach ( $node as $row ) {
386            $innerInnter = [];
387            $colNo = 0;
388            $isEmptyLine = true;
389            $rowAttributes = [];
390            $rowSpecs = $row->getRowSpecs();
391            if ( $rowSpecs ) {
392                $rowAttributes['style'] = "padding-bottom: {$rowSpecs->getCssLength()};";
393            }
394            foreach ( $row  as $cell ) {
395                $usedArg = clone $cell;
396                if ( $usedArg instanceof TexArray &&
397                    $usedArg->getLength() >= 1
398                ) {
399                    $firstArg = $usedArg[0];
400                    if ( $firstArg instanceof Literal &&
401                        $firstArg->getArg() === '\\hline '
402                    ) {
403                        $usedArg->pop();
404                    }
405                }
406                $mtdAttributes = $rowAttributes;
407                $texclass = $lines[$rowNo] ? TexClass::TOP : '';
408                $texclass .= $lines[$rowNo + 1] ?? false ? ' ' . TexClass::BOTTOM : '';
409                $texclass .= $boarder[$colNo] ?? false ? ' ' . TexClass::LEFT : '';
410                $texclass .= $boarder[$colNo + 1 ] ?? false ? ' ' . TexClass::RIGHT : '';
411                if ( in_array( $align[$colNo] ?? [], [ 'l', 'r' ] ) ) {
412                    // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
413                    $texclass .= ' mwe-math-columnalign-' . $align[$colNo];
414                }
415                $texclass = trim( $texclass );
416                if ( $texclass ) {
417                    $mtdAttributes['class'] = $texclass;
418                }
419                // TeX sets each cell as a $...$ formula, which starts in text style.
420                // plain.tex \matrix: swh:1:cnt:0f363a96c0e1f93d830e97534b1fe07861fdae69;lines=1093-1096
421                // amsmath matrix: swh:1:cnt:d4a287b9531788cd614d524bdd53f38681a63902;lines=2525-2527
422                $state = [ 'inMatrix' => true, 'styleargs' => [ 'displaystyle' => 'false' ] ];
423                $isEmptyLine = $isEmptyLine && $usedArg->isEmpty();
424                $innerInnter[] = new MMLmtd( "", $mtdAttributes, $usedArg->toMMLtree( $passedArgs, $state ) );
425                $colNo++;
426            }
427            $rowNo++;
428            // empty trailing lines with only one empty cell should not be rendered
429            if ( $rowNo === count( $lines ) && $colNo === 1 && $isEmptyLine ) {
430                break;
431            }
432            $resInner[] = new MMLmtr( "", [], ...$innerInnter );
433        }
434        $mtable = new MMLmtable( '',
435        $name === 'smallmatrix' ?
436        [ 'class' => 'mwe-math-smallmatrix' ] : []
437        );
438        if ( $cases || ( $open != null && $close != null ) ) {
439            $bm = new BaseMethods();
440            $mmlMoOpen = $bm->checkAndParseDelimiter( $open, $node, [], [],
441                true, TexClass::OPEN );
442            if ( $mmlMoOpen->isEmpty() ) {
443                $mmlMoOpen = new MMLmo( TexClass::OPEN, [], $open ?? '' );
444            }
445
446            // MathJax gives an empty closing fence the width of a null delimiter.
447            $closeAtts = [ "data-mwe-fence" => "true", "stretchy" => "true", "symmetric" => "true" ];
448            $mmlMoClose = $bm->checkAndParseDelimiter( $close, $node, $closeAtts,
449                null, true, TexClass::CLOSE );
450            if ( $mmlMoClose->isEmpty() ) {
451                $mmlMoClose = ( new MMLmo( TexClass::CLOSE, $closeAtts, $close ?? '' ) );
452            }
453            $mtable->addChild( ...$resInner );
454            return new MMLmrow( TexClass::ORD, [], $mmlMoOpen, $mtable, $mmlMoClose );
455        }
456        $mtable->addChild( ...$resInner );
457        return $mtable;
458    }
459
460    public static function over( $node, $passedArgs, $operatorContent, $name, $id = null ): MMLbase {
461        $attributes = [];
462        $start = new MMLarray();
463        $tail = new MMLarray();
464        if ( trim( $name ) === "\\atop" ) {
465            $attributes = [ "linethickness" => "0" ];
466        } elseif ( trim( $name ) == "\\choose" ) {
467            // Let the operator dictionary stretch the parentheses to the contextual fraction height.
468            $start = new MMLmrow( TexClass::OPEN, [],
469                new MMLmo( "", [], "(" )
470            );
471            $tail = new MMLmrow( TexClass::CLOSE, [],
472                new MMLmo( "", [], ")" )
473            );
474            $attributes = [ "linethickness" => "0" ];
475        }
476        // TeX sets the parts of a fraction in text style.
477        $state = [ 'styleargs' => [ 'displaystyle' => 'false' ] ];
478        $state1 = $state;
479        $state2 = $state;
480        if ( $node instanceof Fun2 ) {
481            $mfrac = MMLmfrac::newSubtree( new MMLmrow( "", [], $node->getArg1()->toMMLtree( [], $state1 ) ),
482                new MMLmrow( "", [], $node->getArg2()->toMMLtree( [], $state2 ) ), "", $attributes );
483            if ( $start->isEmpty() ) {
484                return $mfrac;
485            }
486            return new MMLmrow( TexClass::ORD, [], $start, $mfrac, $tail );
487        }
488        $inner = [];
489        foreach ( $node->getArgs() as $arg ) {
490            if ( is_string( $arg ) && str_contains( $arg, $name ) ) {
491                continue;
492            }
493            $argState = $state;
494            $rendered = $arg instanceof TexNode ? $arg->toMMLtree( [], $argState ) : $arg;
495            $inner[] = new MMLmrow( "", [], $rendered );
496        }
497        $mfrac = MMLmfrac::newSubtree( $inner[0], $inner[1], "", $attributes );
498        if ( $start->isEmpty() ) {
499            return $mfrac;
500        }
501        return new MMLmrow( TexClass::ORD, [], $start, $mfrac, $tail );
502    }
503
504    public static function oint( $node, $passedArgs, $operatorContent,
505        $name, $uc = null, $attributes = null, $smth2 = null
506    ): MMLbase {
507        // This is a custom mapping not in js.
508        switch ( trim( $name ) ) {
509            case "\\oint":
510            case "\\P":
511                return new MMLmo( "", [], MMLutil::uc2xNotation( $uc ) );
512            case "\\oiint":
513            case "\\oiiint":
514            case "\\ointctrclockwise":
515            case "\\varointclockwise":
516                // FIXME: the trailing thin space has no known source
517                return new MMLmrow( TexClass::ORD, [],
518                    new MMLmstyle( "", [ "mathsize" => "2.07em" ],
519                        new MMLmtext( "", $attributes, MMLutil::uc2xNotation( $uc ) ),
520                        new MMLmspace( "", [ "width" => MMLutil::round2em( 3 / 18 ) ] )
521                    )
522                );
523            default:
524                return MMLmerror::newFromText( "not found in OintMethod" );
525        }
526    }
527
528    public static function overset( $node, $passedArgs, $operatorContent, $name, $id = null ): MMLbase {
529        if ( $node instanceof DQ ) {
530            return new MMLmrow( TexClass::ORD, [],
531                MMLmover::newSubtree( new MMLmrow(
532                    "",
533                    [],
534                    $node->getDown()->toMMLtree() ),
535                    $node->getDown()->toMMLtree()
536                )
537            );
538        }
539        return new MMLmrow( TexClass::ORD, [],
540            MMLmover::newSubtree(
541                new MMLmrow(
542                    "",
543                    [],
544                $node->getArg2()->toMMLtree() ),
545                $node->getArg1()->toMMLtree()
546            )
547        );
548    }
549
550    public static function phantom( $node, $passedArgs, $operatorContent,
551        $name, $vertical = null, $horizontal = null, $smh3 = null
552    ): MMLbase {
553        $attrs = [];
554        if ( $vertical ) {
555            $attrs = array_merge( $attrs, [ "width" => "0" ] );
556        }
557        if ( $horizontal ) {
558            $attrs = array_merge( $attrs, [ "depth" => "0", "height" => "0" ] );
559        }
560        return new MMLmrow(
561            TexClass::ORD,
562            [],
563            new MMLmrow(
564                TexClass::ORD,
565                [],
566                new MMLmpadded(
567                    "",
568                    $attrs,
569                    new MMLmphantom( "", [], $node->getArg()->toMMLtree() )
570                )
571            )
572        );
573    }
574
575    public static function raiseLower( $node, $passedArgs, $operatorContent, $name ): MMLbase {
576        if ( !$node instanceof Fun2 ) {
577            return new MMLarray();
578        }
579
580        $arg1 = $node->getArg1();
581        // the second check is to avoid a false positive for PhanTypeMismatchArgumentSuperType
582        if ( $arg1->isCurly() && $arg1 instanceof TexArray ) {
583            $unit = MMLutil::squashLitsToUnit( $arg1 );
584            if ( !$unit ) {
585                return new MMLarray();
586            }
587            $em = MMLutil::dimen2em( $unit );
588            if ( !$em ) {
589                return new MMLarray();
590            }
591        } else {
592            return new MMLarray();
593        }
594
595        if ( trim( $name ) === "\\raise" ) {
596            $args = [ "height" => MMLutil::addPreOperator( $em, "+" ),
597                "depth" => MMLutil::addPreOperator( $em, "-" ),
598                "voffset" => MMLutil::addPreOperator( $em, "+" ) ];
599        } elseif ( trim( $name ) === "\\lower" ) {
600            $args = [ "height" => MMLutil::addPreOperator( $em, "-" ),
601                "depth" => MMLutil::addPreOperator( $em, "+" ),
602                "voffset" => MMLutil::addPreOperator( $em, "-" ) ];
603        } else {
604            // incorrect name, should not happen, prevent erroneous mappings from getting rendered.
605            return new MMLarray();
606        }
607        return new MMLmrow( "", [], new MMLmpadded( "", $args, $node->getArg2()->toMMLtree() ) );
608    }
609
610    public static function underset( $node, $passedArgs, $operatorContent, $name, $smh = null ): MMLbase {
611        $inrow = $node->getArg2()->toMMLtree();
612        $arg1 = $node->getArg1()->toMMLtree();
613        if ( !$inrow->isEmpty() && !$arg1->isEmpty() ) {
614            return new MMLmrow( TexClass::ORD, [], MMLmunder::newSubtree( $inrow, $arg1 ) );
615        }
616
617        // If there are no two elements in munder, not render munder
618        return new MMLmrow( TexClass::ORD, [], $inrow, $arg1 );
619    }
620
621    public static function underOver( Fun1 $node, $passedArgs, $operatorContent,
622        $name, $operatorSymbol = null, $stack = null ): MMLbase {
623        // tbd verify if stack interpreted correctly ?
624        $texClass = $stack ? TexClass::OP : TexClass::ORD; // ORD or ""
625
626        $fname = $node->getFname();
627        if ( str_starts_with( $fname, '\\over' ) ) {
628            $movun = new MMLmover();
629        } elseif ( str_starts_with( $fname, '\\under' ) ) {
630            $movun = new MMLmunder();
631        } else {
632            // incorrect name, should not happen, prevent erroneous mappings from getting rendered.
633            return MMLmerror::newFromText(
634                'underOver rendering requires macro to start with either \\under or \\over.' );
635        }
636
637        $mo = new MMLmo( "", [], $operatorSymbol );
638        return new MMLmrow( $texClass, [], $movun::newSubtree( $node->getArg()->toMMLtree( $passedArgs ), $mo ) );
639    }
640
641    public static function mathFont( $node, $passedArgs, $operatorContent, $name, $mathvariant = null ): MMLbase {
642        $args = MMLParsingUtil::getFontArgs( $name, $mathvariant, $passedArgs );
643        $state = [];
644
645            return new MMLmrow( TexClass::ORD, [], $node->getArg()->toMMLtree( $args, $state ) );
646    }
647
648    public static function mathChoice( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase {
649        if ( !$node instanceof Fun4 ) {
650            return MMLmerror::newFromText( "Wrong node type in mathChoice" );
651        }
652
653        /**
654         * Parametrization for mathchoice:
655         * \mathchoice
656         * {<material for display style>}
657         * {<material for text style>}
658         * {<material for script style>}
659         * {<material for scriptscript style>}
660         */
661
662        if ( isset( $operatorContent["styleargs"] ) ) {
663            $styleArgs = $operatorContent["styleargs"];
664            $displayStyle = $styleArgs["displaystyle"] ?? "true";
665            $scriptLevel = $styleArgs["scriptlevel"] ?? "0";
666
667            if ( $displayStyle == "true" && $scriptLevel == "0" ) {
668                // This is displaystyle
669                return $node->getArg1()->toMMLtree( $passedArgs, $operatorContent );
670            } elseif ( $displayStyle == "false" && $scriptLevel == "0" ) {
671                // This is textstyle
672                return $node->getArg2()->toMMLtree( $passedArgs, $operatorContent );
673            } elseif ( $displayStyle == "false" && $scriptLevel == "1" ) {
674                // This is scriptstyle
675                return $node->getArg3()->toMMLtree( $passedArgs, $operatorContent );
676            } elseif ( $displayStyle == "false" && $scriptLevel == "2" ) {
677                // This is scriptscriptstyle
678                return $node->getArg4()->toMMLtree( $passedArgs, $operatorContent );
679            }
680        }
681        // By default render displaystyle
682        return $node->getArg1()->toMMLtree( $passedArgs, $operatorContent );
683    }
684
685    public static function makeBig( $node, $passedArgs, $operatorContent, $name, $texClass = null,
686        $size = null
687    ): MMLbase {
688        // $size is the kernel height of \big..\Bigg (8.5pt..17.5pt) scaled by MathJax's 1.2/.85
689        // swh:1:cnt:72f94735bba07ecbc4214f3cc409de0e137ff1ee;lines=1534-1541
690        // swh:1:cnt:4fcf2badb30fa89a86be332c1481b0daaf21d367;lines=47
691        $passedArgs = array_merge( $passedArgs, [ "maxsize" => $size, "minsize" => $size ] );
692        // Sieve arg if it is a delimiter (it seems args are not applied here
693        $bm = new BaseMethods();
694        $argcurrent = trim( $node->getArg() );
695        switch ( $argcurrent ) {
696            case "\\|":
697            case "\\vert":
698            case "|":
699            case "\\uparrow":
700            case "\\downarrow":
701            case "\\Uparrow":
702            case "\\Downarrow":
703            case "\\updownarrow":
704            case "/":
705            case "\\backslash":
706            case "\\Updownarrow":
707                $passedArgs = array_merge( $passedArgs, [ "stretchy" => "true", "symmetric" => "true" ] );
708                break;
709        }
710
711        if ( in_array( $name, [ "\\bigl", "\\Bigl", "\\biggl", "\\Biggl" ] ) ) {
712            $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::OPEN ] );
713        }
714
715        if ( in_array( $name, [ "\\bigr", "\\Bigr", "\\biggr", "\\Biggr" ] ) ) {
716            $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::CLOSE ] );
717        }
718
719        $ret = $bm->checkAndParseDelimiter( $node->getArg(), $node, $passedArgs, $operatorContent, true );
720        if ( !$ret->isEmpty() ) {
721            return $ret;
722        }
723
724        $argPrep = $node->getArg();
725        return new MMLmrow( TexClass::ORD, [],
726            new MMLmrow( $texClass, [], new MMLmo( "", $passedArgs, $argPrep ) )
727        );
728    }
729
730    public static function machine( $node, $passedArgs, $operatorContent, $name, $type = null ): MMLbase {
731        // this could also be shifted to MhChem.php renderMML for ce
732        // For parsing chem (ce) or ??? (pu)
733        return new MMLmrow( "", [], $node->getArg()->toMMLtree() );
734    }
735
736    public static function setFont( $node, $passedArgs, $operatorContent, $name, $variant = null ): MMLbase {
737        return self::mathFont( $node, $passedArgs, $operatorContent, $name, $variant );
738    }
739
740    public static function sideset( $node, $passedArgs, $operatorContent, $name ): MMLbase {
741        if ( !array_key_exists( "sideset", $operatorContent ) ) {
742            return MMLmerror::newFromText( "Error parsing sideset expression, no succeeding operator found" );
743        }
744
745        $op = $operatorContent["sideset"];
746        $state = [ 'sideset' => true ];
747        $in1 = $node->getArg1()->toMMLtree( [], $state );
748        $in2 = $node->getArg2()->toMMLtree( [], $state );
749
750        if ( $op instanceof FQ || $op instanceof DQ || $op instanceof UQ ) {
751            $bm = new BaseMethods();
752            if ( count( $op->getBase()->getArgs() ) == 1 ) {
753                $baseOperator = $op->getBase()->getArgs()[0];
754                if ( is_string( $baseOperator ) ) {
755                    $opParsed = $bm->checkAndParseOperator( $baseOperator,
756                        null, [ "largeop" => "true", "symmetric" => "true" ], [], null );
757                } else {
758                    $opParsed = $baseOperator->toMMLTree();
759                }
760                if ( $opParsed->isEmpty() ) {
761                    $opParsed = $op->getBase()->toMMLtree();
762                }
763            } else {
764                $opParsed = MMLmerror::newFromText( "Sideset operator parsing not implemented yet" );
765            }
766            $down = $op instanceof UQ ? new MMLmrow( "", [] ) : $op->getDown()->toMMLtree();
767            $up = $op instanceof DQ ? new MMLmrow( "", [] ) : $op->getUp()->toMMLtree();
768            return new MMLmrow(
769                TexClass::OP,
770                [],
771                MMLmunderover::newSubtree(
772                    new MMLmstyle( "", [ "displaystyle" => "true" ],
773                    MMLmmultiscripts::newSubtree( $opParsed, $in2, new MMLarray(), $in1 ) ),
774                    new MMLmrow( "", [], $down ),
775                    new MMLmrow( "", [], $up )
776                )
777            );
778        }
779
780        if ( $op instanceof Literal ) {
781            $bm = new BaseMethods();
782            $opParsed = $bm->checkAndParseOperator( $op->getArg(), null, [], [], null );
783            if ( $opParsed->isEmpty() ) {
784                $opParsed = $op->toMMLtree();
785            }
786        } else {
787            $opParsed = $op->toMMLtree();
788        }
789        return new MMLmrow( TexClass::OP, [],
790            MMLmmultiscripts::newSubtree( $opParsed, $in2, new MMLarray(), $in1, new MMLarray(),
791                "", [ Tag::ALIGN => "left" ]
792            )
793        );
794    }
795
796    public static function spacer( $node, $passedArgs, $operatorContent, $name, $withIn = null, $smth2 = null
797    ): MMLbase {
798        return new MMLmspace( "", [ "width" => MMLutil::round2em( $withIn ) ] );
799    }
800
801    public static function smash( $node, $passedArgs, $operatorContent, $name ): MMLbase {
802        $mpArgs = [];
803        $inner = new MMLarray();
804        if ( $node instanceof Fun2sq ) {
805            $arg1 = $node->getArg1();
806            $arg1i = "";
807            if ( $arg1->isCurly() ) {
808                $arg1i = $arg1->render();
809            }
810
811            if ( str_contains( $arg1i, "{b}" ) ) {
812                $mpArgs = [ "depth" => "0" ];
813            }
814            if ( str_contains( $arg1i, "{t}" ) ) {
815                $mpArgs = [ "height" => "0" ];
816            }
817            if ( str_contains( $arg1i, "{tb}" ) || str_contains( $arg1i, "{bt}" ) ) {
818                $mpArgs = [ "height" => "0", "depth" => "0" ];
819            }
820
821            $inner = $node->getArg2()->toMMLtree();
822        } elseif ( $node instanceof Fun1 ) {
823            // Implicitly assume "tb" as default mode
824            $mpArgs = [ "height" => "0", "depth" => "0" ];
825            $inner = $node->getArg()->toMMLtree();
826        }
827        return new MMLmrow( TexClass::ORD, [], new MMLmpadded( "", $mpArgs, $inner ) );
828    }
829
830    public static function texAtom( $node, $passedArgs, $operatorContent, $name, $texClass = null ): MMLbase {
831        switch ( $name ) {
832            case '\mathbin':
833                // no break
834            case '\mathop':
835                // no break
836            case '\mathrel':
837                $inner = $node->getArg()->toMMLtree();
838                return new MMLmrow( $texClass, [], $inner );
839            default:
840                $inner = $node->getArg()->toMMLtree();
841                return new MMLmrow( TexClass::ORD, [], new MMLmrow( $texClass, [], $inner ) );
842        }
843    }
844
845    public static function intent( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase {
846        if ( !$node instanceof Fun2 ) {
847            return new MMLarray();
848        }
849        // if there is intent annotation add intent to root element
850        // match args in row of subargs, unless an element has explicit annotations
851        // nested annotations ?
852        $arg1 = $node->getArg1();
853        $arg2 = $node->getArg2();
854        if ( !$arg2->isCurly() ) {
855            return new MMLarray();
856        }
857        // tbd refactor intent form and fiddle in mml or tree
858        $intentStr = MMLutil::squashLitsToUnitIntent( $arg2 );
859        $intentContent = MMLParsingUtil::getIntentContent( $intentStr );
860        $intentParams = MMLParsingUtil::getIntentParams( $intentContent );
861        // Sometimes the intent has additioargs = {array[3]} nal args in the same string
862        $intentArg = MMLParsingUtil::getIntentArgs( $intentStr );
863        if ( !$intentContent && !$intentParams && $intentArg !== null ) {
864            // explicit args annotation parsing in literal
865            // return $arg1->renderMML([],["intent-params-expl"=>$intentArg]);
866            // alternative just add the arg here
867            return $arg1->toMMLtree( [ "arg" => $intentArg ] );
868        }
869        $intentContentAtr = [ "intent" => $intentContent ];
870        if ( $intentArg !== null ) {
871            $intentContentAtr["arg"] = $intentArg;
872        }
873        // tbd refine intent params and operator content merging (does it overwrite ??)
874        $intentParamsState = $intentParams ? [ "intent-params" => $intentParams ] : $operatorContent;
875        // Here are some edge cases, they might go into renderMML in the related element
876        if ( str_contains( $intentContent ?? '', "matrix" ) ||
877            ( $arg1->isCurly() && $arg1->getArgs()[0] instanceof Matrix ) ) {
878            $element = $arg1->getArgs()[0];
879            $rendered = $element->toMMLtree( [], $intentParamsState );
880            return MMLParsingUtil::forgeIntentToSpecificElement( $rendered,
881                $intentContentAtr, "mtable" );
882        } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 2 ) {
883            // Create a surrounding element which holds the intents
884            return new MMLmrow( "", $intentContentAtr, $arg1->toMMLtree( [], $intentParamsState ) );
885        } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 1 ) {
886            // Forge the intent attribute to the top-level element after MML rendering
887            $element = $arg1->getArgs()[0];
888            $rendered = $element->toMMLtree( [], $intentParamsState );
889            return MMLParsingUtil::forgeIntentToTopElement( $rendered, $intentContentAtr );
890        } else {
891            // This is the default case
892            return $arg1->toMMLtree( $intentContentAtr, $intentParamsState );
893        }
894    }
895
896    public static function hBox( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase {
897        switch ( trim( $name ) ) {
898            case "\\mbox":
899                if ( isset( $operatorContent['foundOC'] ) ) {
900                    $op = $operatorContent['foundOC'];
901                    $macro = TexUtil::getInstance()->nullary_macro_in_mbox( $op ) ?
902                        /* tested in \MediaWiki\Extension\Math\Tests\WikiTexVC\TexUtilTest::testUnicodeDefined */
903                        [ TexUtil::getInstance()->unicode_char( $op ) ] :
904                        TexUtil::getInstance()->identifier( $op );
905                    $input = $macro[0] ?? $op;
906                    return new MMLmrow( TexClass::ORD, [], new MMLmo( "", [], MMLutil::uc2xNotation( $input ) ) );
907                } else {
908                    return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], "\mbox" ) );
909                }
910            case "\\hbox":
911                $inner = $node->getArg() instanceof TexNode ? $node->getArg()->toMMLtree() : $node->getArg();
912                return new MMLmrow( TexClass::ORD, [],
913                    new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ],
914                        new MMLmtext( "", [], $inner )
915                    )
916                );
917            case "\\text":
918                $inner = $node->getArg() instanceof TexNode ? $node->getArg()->toMMLtree() : $node->getArg();
919                return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], $inner ) );
920            case "\\textbf":
921                // no break
922            case "\\textit":
923                // no break
924            case "\\textrm":
925                // no break
926            case "\\textsf":
927                // no break
928            case "\\texttt":
929                $state = [ "inHBox" => true, 'squashLiterals' => true ];
930                $fontArgs = MMLParsingUtil::getFontArgs( $name, null, null );
931                $inner = $node->getArg()->isCurly() ? $node->getArg()->toMMLtree(
932                    $fontArgs, $state )
933                    : $node->getArg()->toMMLtree( $fontArgs );
934                if ( $inner instanceof MMLbase ) {
935                    $inner = $inner->getTextContent();
936                }
937                return new MMLmtext( "", $fontArgs, $inner ?? '' );
938
939        }
940
941        return MMLmerror::newFromText( "undefined hbox" );
942    }
943
944    public static function setStyle( $node, $passedArgs, $operatorContent, $name,
945        $smth = null, $smth1 = null, $smth2 = null
946    ): MMLmrow {
947        // Just discard setstyle since they are captured in TexArray now}
948        return new MMLmrow();
949    }
950
951    public static function not( $node, $passedArgs, $operatorContent, $name, $smth = null,
952        $smth1 = null, $smth2 = null
953    ): MMLbase {
954        // This is only tested for \not statement without follow-up parameters
955        if ( $node instanceof Literal ) {
956            return MMLParsingUtil::createNot();
957        }
958        return MMLmerror::newFromText( "TBD implement not" );
959    }
960
961    public static function vbox( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase {
962        // This is only example functionality for vbox("ab").
963        // TBD: it should be discussed if vbox is supported since it
964        // does not seem to be supported by mathjax
965        if ( is_string( $node->getArg() ) ) {
966            $arr1 = str_split( $node->getArg() );
967            $inner = [];
968            foreach ( $arr1 as $char ) {
969                $inner[] = new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], $char ) );
970            }
971            return MMLmover::newSubtree( $inner[0], $inner[1] );
972        }
973        return MMLmerror::newFromText( "no implemented vbox" );
974    }
975
976    public static function sqrt( $node, $passedArgs, $operatorContent, $name ): MMLbase {
977        // There is an additional argument for the root
978        if ( $node instanceof Fun2sq ) {
979            // In case of an empty curly add an mrow
980            $arg2Rendered = $node->getArg2()->toMMLtree( $passedArgs );
981            if ( $arg2Rendered->isEmpty() ) {
982                $arg2Rendered = new MMLmrow( TexClass::ORD, [] );
983            }
984            return new MMLmrow( TexClass::ORD, [],
985                MMLmroot::newSubtree(
986                    $arg2Rendered,
987                    new MMLmrow( TexClass::ORD, [],
988                        $node->getArg1()->toMMLtree( $passedArgs )
989                    )
990                )
991            );
992        }
993        // Currently this is own implementation from Fun1.php
994        return new MMLmrow( TexClass::ORD, [], // assuming that this is always encapsulated in mrow
995            new MMLmsqrt( "", [],
996                $node->getArg()->toMMLtree( $passedArgs )
997            )
998        );
999    }
1000
1001    public static function tilde( $node, $passedArgs, $operatorContent, $name ): MMLbase {
1002        return new MMLmspace( "", [ "width" => "0.5em" ] );
1003    }
1004
1005    public static function xArrow( $node, $passedArgs, $operatorContent, $name, $chr = null, $l = null,
1006                                   $r = null ): MMLbase {
1007        $defWidth = "+" . MMLutil::round2em( ( $l + $r ) / 18 );
1008        $defLspace = MMLutil::round2em( $l / 18 );
1009
1010        $char = IntlChar::chr( $chr );
1011
1012        // Core has no relative width; Chrome reads "+0.833em" as 0.833em.
1013        $mpaddedArgs = [ "height" => "-.2em", "lspace" => $defLspace, "voffset" => "-.2em",
1014            "data-mwe-width" => $defWidth ];
1015        $mspace = new MMLmspace( "", [ "depth" => ".25em" ] );
1016        if ( $node instanceof Fun2sq ) {
1017            return new MMLmrow( TexClass::ORD, [], MMLmunderover::newSubtree(
1018                new MMLmstyle( "", [ "scriptlevel" => "0" ],
1019                    new MMLmo( Texclass::REL, [], $char )
1020                ),
1021                new MMLmpadded( "", $mpaddedArgs,
1022                    new MMLmrow( TexClass::ORD, [],
1023                        $node->getArg1()->toMMLtree()
1024                    ),
1025                    $mspace
1026                ),
1027                new MMLmpadded( "", $mpaddedArgs,
1028                    $node->getArg2()->toMMLtree()
1029                )
1030            ) );
1031
1032        }
1033        return MMLmover::newSubtree(
1034            new MMLmstyle( "", [ "scriptlevel" => "0" ], new MMLmo( Texclass::REL, [], $char ) ),
1035            new MMLmpadded( "", $mpaddedArgs, $node->getArg()->toMMLtree(), $mspace )
1036        );
1037    }
1038
1039    public static function getApplyFct( array $operatorContent ): MMLbase {
1040        $applyFct = new MMLarray();
1041        if ( array_key_exists( "foundNamedFct", $operatorContent ) ) {
1042            $hasNamedFct = $operatorContent['foundNamedFct'][0];
1043            $hasValidParameters = $operatorContent["foundNamedFct"][1];
1044            if ( $hasNamedFct && $hasValidParameters ) {
1045                $applyFct = MMLParsingUtil::renderApplyFunction();
1046            }
1047        }
1048        return $applyFct;
1049    }
1050}