Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.30% covered (warning)
69.30%
79 / 114
30.00% covered (danger)
30.00%
3 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseMethods
69.30% covered (warning)
69.30%
79 / 114
30.00% covered (danger)
30.00%
3 / 10
126.27
0.00% covered (danger)
0.00%
0 / 1
 checkAndParse
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
7.39
 checkAndParseOperator
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
7.39
 parseOperatorDict
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
7.27
 parseOperator
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 checkAndParseIdentifier
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 parseIdentifier
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 checkAndParseDelimiter
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 checkAndParseMathCharacter
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 checkAndParseColor
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
72
 generateMMLError
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings;
3
4use ArgumentCountError;
5use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants;
6use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil;
7use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror;
8use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi;
9use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
11use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace;
12use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle;
13use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext;
14use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
15use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
16
17/**
18 * This contains the basic parsing methods for tex elements, which get invoked
19 * to check if there is a specific parsing function defined in the mappings
20 * and then forward to the parsing function.
21 *
22 * Much of this is WIP since there are many cases.
23 * @author Johannes Stegmüller
24 */
25class BaseMethods {
26
27    public static function checkAndParse( $input, $passedArgs, $operatorContent, TexNode $node, $prepareInput = true ) {
28        if ( !is_string( $input ) ) {
29            // just discard these elements, sometimes empty TexArray
30            return null;
31        }
32
33        // Checking for a named parsing function
34
35        if ( $input === '\\ ' ) {
36            $resFct = [ 'macro', '\\text{ }' ];
37        } else {
38            $resFct = TexUtil::getInstance()->callback( trim( $input ) );
39        }
40        if ( $resFct == null ) {
41            return null;
42        }
43        // If the function has been found, dynamically call the associated parsing function.
44        if ( is_string( $resFct ) ) {
45            $resFct = [ $resFct ];
46        }
47        try {
48            // Passing resolved function as param without first id
49            if ( count( $resFct ) > 1 ) {
50                $shifted = array_shift( $resFct );
51                return BaseParsing::{$shifted}( $node, $passedArgs, $operatorContent, $input, ...$resFct );
52            } else {
53                return BaseParsing::{$resFct[0]}( $node, $passedArgs, $operatorContent, $input );
54            }
55        } catch ( \Exception $exception ) {
56            return null;
57        }
58    }
59
60    public function checkAndParseOperator( $input, $node, $passedArgs, $operatorContent,
61                                           $state, $prepareInput = true ) {
62            $resOperator = TexUtil::getInstance()->operator_rendering( trim( $input ) );
63        if ( $resOperator == null ) {
64            $resOperator = TexUtil::getInstance()->operator_infix( trim( $input ) );
65            if ( $resOperator ) {
66                if ( isset( $resOperator[1] ) ) {
67                    // custom parsing here
68                    return $this->parseOperatorDict( $node, $passedArgs, $operatorContent, $input, false );
69                }
70                // Atm just do simple parsing for elements in operator dictionary
71                $mmlMo = new MMLmo( '', $passedArgs );
72                return $mmlMo->encapsulateRaw( $input );
73            }
74        }
75
76        // If the macro has been found, dynamically call the associated parsing function.
77        if ( is_string( $resOperator ) ) {
78            $resOperator = [ $resOperator ];
79        }
80
81        if ( $resOperator == null ) {
82            return null;
83        }
84        try {
85            return $this->parseOperator( $node, $passedArgs, $operatorContent, $input, $state, ...$resOperator );
86
87        } catch ( ArgumentCountError $errArgcount ) {
88            return null;
89        }
90    }
91
92    public function parseOperatorDict( $node, $passedArgs, $operatorContent, $input, $uc = null, $attrs = [] ) {
93        // Some custom parsing from operatorDict
94        switch ( $input ) {
95            case ";":
96            case ",":
97                // this maybe just a default case, this is not rendered when it is the last in row
98                $mmlMo = new MMLmo();
99                return $mmlMo->encapsulate( $input );
100            case "<":
101                $mmlMo = new MMLmo();
102                return $mmlMo->encapsulateRaw( "&lt;" );
103            case ">":
104                $mmlMo = new MMLmo();
105                return $mmlMo->encapsulateRaw( "&gt;" );
106            case "\\":
107                 // instead of carriage return, force whitespace here:
108                 // see: https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Math/+/961213
109                $mspace = new MMLmspace( "", [ "width" => "0.5em" ] );
110                return $mspace->getEmpty();
111            case '/':
112                $mmlMo = new MMLmo( '', [ 'lspace' => '0', 'rspace' => '0' ] );
113                return $mmlMo->encapsulateRaw( $input );
114        }
115        return $input;
116    }
117
118    public function parseOperator( $node, $passedArgs, $operatorContent, $name, $state, $uc = null, $attrs = [] ) {
119        // if($name == "equiv" || $name == "dotplus" || $name == "mp"  || $name == "pm"){
120        $attrs = array_merge( $passedArgs, $attrs ); // this is rather a workaround
121        $mo = new MMLmo( "", $attrs );
122
123        if ( $state != null && array_key_exists( "not", $state ) && $state["not"] ) {
124            $text = $mo->encapsulateRaw( $uc . "&#x338;" );
125        } else {
126            $text = $mo->encapsulateRaw( $uc );
127        }
128
129        // Some attributes are nnot used which come from the mapping, tbd refactor this
130        $text = str_replace( " largeop=\"\"", "", $text );
131        $text = str_replace( "variantForm=\"True\"", "data-mjx-alternate=\"1\"", $text );
132        $text = str_replace( "variantForm=\"1\"", "data-mjx-alternate=\"1\"", $text );
133        $text = str_replace( " movesupsub=\"1\"", "", $text );
134        return str_replace( "texClass", "data-mjx-texclass", $text );
135    }
136
137    public function checkAndParseIdentifier( $input, $node, $passedArgs, $operatorContent, $prepareInput = true ) {
138        // @phan-suppress-next-line PhanCoalescingNeverUndefined
139        $resIdentifier = TexUtil::getInstance()->identifier( trim( $input ) ) ?? null;
140        // If the macro has been found, dynamically call the associated parsing function.
141        if ( is_string( $resIdentifier ) ) {
142            $resIdentifier = [ $resIdentifier ];
143        }
144
145        if ( $resIdentifier == null ) {
146            return null;
147        }
148        try {
149            $resIdentifier[0] = MMLutil::uc2xNotation( $resIdentifier[0] );
150            return $this->parseIdentifier( $node, $passedArgs, $operatorContent, $input, ...$resIdentifier );
151        } catch ( ArgumentCountError $errArgcount ) {
152            return null;
153        }
154    }
155
156    public function parseIdentifier( $node, $passedArgs, $operatorContent, $name, $uc = null, $attrs = [] ) {
157        // tbd verify rule: Lowercase name ("operator" instead "Operator") seems to
158        // indicate additional italic mathvariant when bold already
159        if ( !ctype_upper( $name ) ) {
160            if ( isset( $passedArgs["mathvariant"] ) && $passedArgs["mathvariant"] === 'bold' ) {
161                $passedArgs["mathvariant"] = $passedArgs["mathvariant"] . "-" . Variants::ITALIC;
162            }
163        }
164
165        $args = array_merge( $passedArgs, $attrs );
166        $mi = new MMLmi( "", $args );
167        $text = $mi->encapsulateRaw( $uc );
168        // TODO refactor just for test
169        $text = str_replace( "variantForm=\"True\"", "data-mjx-alternate=\"1\"", $text );
170        $text = str_replace( "variantForm=\"1\"", "data-mjx-alternate=\"1\"", $text );
171        return str_replace( "texClass", "data-mjx-texclass", $text );
172    }
173
174    public function checkAndParseDelimiter( $input, $node, $passedArgs,
175                                            $operatorContent, $noargs = false, $texClass = "" ) {
176        if ( $input === null ) {
177            return null;
178        }
179        $input = trim( $input );
180
181        $resDelimiter = TexUtil::getInstance()->delimiter( $input ) ?? false;
182        if ( $resDelimiter === false || !is_string( $resDelimiter[0] ) ) {
183            return null;
184        }
185
186        if ( isset( $resDelimiter[1] ) && is_array( $resDelimiter[1] ) && !$noargs ) {
187            $passedArgs = array_merge( $resDelimiter[1], $passedArgs );
188        }
189
190        $mo = new MMLmo( $texClass, $passedArgs );
191        return $mo->encapsulateRaw( $resDelimiter[0] );
192    }
193
194    public function checkAndParseMathCharacter( $input, $node, $passedArgs, $operatorContent, $prepareInput = true ) {
195        $resChar = TexUtil::getInstance()->mathchar( trim( $input ) );
196        if ( $resChar == null ) {
197            return null;
198        }
199
200        // Maybe move this to the mapping
201        $args = [ "mathvariant" => "normal" ];
202
203        $mi = new MMLmi( "", $args );
204        $enc = MMLutil::uc2xNotation( $resChar );
205        return $mi->encapsulateRaw( $enc );
206    }
207
208    public function checkAndParseColor( $input, $node, $passedArgs, $operatorContent, $prepareInput = true ) {
209        // tbd usually this encapsulates the succeeding box element
210        if ( $operatorContent == null ) {
211            return null;
212        }
213
214        if ( !( $input === 'color' || $input === 'pagecolor' ) ) {
215            return null;
216        }
217        $resColor = TexUtil::getInstance()->color( ucfirst( $operatorContent ) );
218        if ( $resColor == null ) {
219            return null;
220        }
221        if ( $input === 'color' ) {
222            $mstyle = new MMLmstyle( "", [ "mathcolor" => $resColor ] );
223            return $mstyle->encapsulate();
224        } else {
225            // Input is 'pagecolor'
226            $mtext = new MMLmtext( "", [ "mathcolor" => $resColor ] );
227            $mrow = new MMLmrow();
228            $mi = new MMLmi();
229            // Mj3 does this, probably not necessary
230            $innerRow = "";
231            foreach ( str_split( $operatorContent ) as $char ) {
232                $innerRow .= $mi->encapsulateRaw( $char );
233            }
234            if ( $innerRow !== "" ) {
235                return $mtext->encapsulate( "\\pagecolor" ) . $mrow->encapsulateRaw( $innerRow );
236            } else {
237                return $mtext->encapsulate( "\\pagecolor" );
238            }
239        }
240    }
241
242    public static function generateMMLError( string $msg ): string {
243        return ( new MMLmerror() )->encapsulateRaw(
244            ( new MMLmtext() )->encapsulate( $msg )
245        );
246    }
247}