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