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