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