Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
65 / 78
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
BaseMethods
83.33% covered (warning)
83.33%
65 / 78
33.33% covered (danger)
33.33%
3 / 9
55.80
0.00% covered (danger)
0.00%
0 / 1
 checkAndParse
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
8.35
 checkAndParseOperator
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 parseOperatorDict
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
7.39
 parseOperator
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 checkAndParseIdentifier
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
4.59
 parseIdentifier
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
5.68
 checkAndParseDelimiter
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 checkAndParseMathCharacter
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 generateMMLError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings;
3
4use ArgumentCountError;
5use Exception;
6use LogicException;
7use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants;
8use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil;
9use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLarray;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase;
11use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror;
12use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi;
13use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
14use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace;
15use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext;
16use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
17use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
18
19/**
20 * This contains the basic parsing methods for tex elements, which get invoked
21 * to check if there is a specific parsing function defined in the mappings
22 * and then forward to the parsing function.
23 *
24 * Much of this is WIP since there are many cases.
25 * @author Johannes Stegmüller
26 */
27class BaseMethods {
28
29    public static function checkAndParse( $input, $passedArgs, $operatorContent, TexNode $node ): MMLbase {
30        if ( !is_string( $input ) ) {
31            // just discard these elements, sometimes empty TexArray
32            return new MMLarray();
33        }
34
35        // Checking for a named parsing function
36
37        if ( $input === '\\ ' ) {
38            $resFct = [ 'macro', '\\text{ }' ];
39        } else {
40            $resFct = TexUtil::getInstance()->callback( trim( $input ) );
41        }
42        if ( $resFct == null ) {
43            return new MMLarray();
44        }
45        // If the function has been found, dynamically call the associated parsing function.
46        if ( is_string( $resFct ) ) {
47            $resFct = [ $resFct ];
48        }
49        if ( str_contains( $resFct[0], '::', ) ) {
50            throw new LogicException( "Callback to $resFct[0] should be treated in the respective class." );
51        }
52        try {
53            // Passing resolved function as param without first id
54            if ( count( $resFct ) > 1 ) {
55                $shifted = array_shift( $resFct );
56                return BaseParsing::{$shifted}( $node, $passedArgs, $operatorContent, $input, ...$resFct );
57            }
58            return BaseParsing::{$resFct[0]}( $node, $passedArgs, $operatorContent, $input );
59        } catch ( Exception ) {
60            return new MMLarray();
61        }
62    }
63
64    public function checkAndParseOperator( $input, $node, $passedArgs, $operatorContent,
65        $state, $prepareInput = true
66    ): MMLbase {
67        $resOperator = TexUtil::getInstance()->operator_rendering( trim( $input ) );
68        if ( $resOperator == null ) {
69            $resOperator = TexUtil::getInstance()->operator_infix( trim( $input ) );
70            if ( $resOperator ) {
71                if ( isset( $resOperator[1] ) ) {
72                    // custom parsing here
73                    return $this->parseOperatorDict( $node, $passedArgs, $operatorContent, $input, false );
74                }
75                // Atm just do simple parsing for elements in operator dictionary
76                return new MMLmo( '', $passedArgs, $input );
77            }
78        }
79
80        // If the macro has been found, dynamically call the associated parsing function.
81        if ( is_string( $resOperator ) ) {
82            $resOperator = [ $resOperator ];
83        }
84
85        if ( $resOperator == null ) {
86            return new MMLarray();
87        }
88        return $this->parseOperator( $node, $passedArgs, $operatorContent, $input, $state, ...$resOperator );
89    }
90
91    public function parseOperatorDict( $node, $passedArgs, $operatorContent, $input, $uc = null,
92        $attrs = []
93    ): MMLbase {
94        // Some custom parsing from operatorDict
95        switch ( $input ) {
96            // effectively, those operations are not tagged with stretchy=false
97            case ";":
98            case ",":
99            case "<":
100            case ">":
101                // this maybe just a default case, this is not rendered when it is the last in row
102                return new MMLmo( "", [], $input );
103            case "\\":
104                 // instead of carriage return, force whitespace here:
105                 // see: https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Math/+/961213
106                return new MMLmspace( "", [ "width" => "0.5em" ] );
107            case '/':
108                return new MMLmo( '', [ 'lspace' => '0', 'rspace' => '0' ], $input );
109        }
110        throw new LogicException( "$input is not a valid operator." );
111    }
112
113    public function parseOperator( $node, $passedArgs, $operatorContent, $name, $state, $uc = null,
114        $attrs = []
115    ): MMLbase {
116        // if($name == "equiv" || $name == "dotplus" || $name == "mp"  || $name == "pm"){
117        $attrs = array_merge( $passedArgs, $attrs ); // this is rather a workaround
118        if ( array_key_exists( "largeop", $attrs ) && $attrs['largeop'] == "" ) {
119            unset( $attrs['largeop'] );
120        }
121        if ( array_key_exists( "movesupsub", $attrs ) && $attrs['movesupsub'] == "1" ) {
122            unset( $attrs['movesupsub'] );
123        }
124        return new MMLmo( trim( $name ) === '\\colon' ? 'PUNCT' : '', $attrs, $uc );
125    }
126
127    public function checkAndParseIdentifier( $input, $node, $passedArgs, $operatorContent,
128         $prepareInput = true
129    ): MMLbase {
130        // @phan-suppress-next-line PhanCoalescingNeverUndefined
131        $resIdentifier = TexUtil::getInstance()->identifier( trim( $input ) ) ?? null;
132        // If the macro has been found, dynamically call the associated parsing function.
133        if ( is_string( $resIdentifier ) ) {
134            $resIdentifier = [ $resIdentifier ];
135        }
136
137        if ( $resIdentifier == null ) {
138            return new MMLarray();
139        }
140        try {
141            $resIdentifier[0] = MMLutil::uc2xNotation( $resIdentifier[0] );
142            return $this->parseIdentifier( $node, $passedArgs, $operatorContent, $input, ...$resIdentifier );
143        } catch ( ArgumentCountError ) {
144            return new MMLarray();
145        }
146    }
147
148    public function parseIdentifier( $node, $passedArgs, $operatorContent, $name, $uc = null, $attrs = [] ): MMLbase {
149        // tbd verify rule: Lowercase name ("operator" instead "Operator") seems to
150        // indicate additional italic mathvariant when bold already
151        if ( !ctype_upper( $name ) ) {
152            if ( isset( $passedArgs['mathvariant'] ) && $passedArgs['mathvariant'] === Variants::BOLD ) {
153                $passedArgs['mathvariant'] = $passedArgs['mathvariant'] . "-" . Variants::ITALIC;
154            }
155        }
156
157        $args = array_merge( $passedArgs, $attrs );
158
159        if ( array_key_exists( "texClass", $args ) ) {
160            $args["data-mjx-texclass"] = $args["texClass"];
161            unset( $args["texClass"] );
162        }
163
164        $state = [];
165        $uc = $node->changeUnicodeFontInput( $uc, $state, $args );
166
167        return new MMLmi( "", $args, $uc );
168    }
169
170    public function checkAndParseDelimiter( $input, $node, $passedArgs,
171        $operatorContent, $noargs = false, $texClass = "" ): MMLbase {
172        if ( $input === null ) {
173            return new MMLarray();
174        }
175        $input = trim( $input );
176
177        $resDelimiter = TexUtil::getInstance()->delimiter( $input ) ?? false;
178        if ( $resDelimiter === false || !is_string( $resDelimiter[0] ) ) {
179            return new MMLarray();
180        }
181
182        if ( isset( $resDelimiter[1] ) && is_array( $resDelimiter[1] ) && !$noargs ) {
183            $passedArgs = array_merge( $resDelimiter[1], $passedArgs );
184        }
185
186        return new MMLmo( $texClass, $passedArgs, $resDelimiter[0] );
187    }
188
189    public function checkAndParseMathCharacter( $input, $node, $passedArgs, $operatorContent,
190        $prepareInput = true
191    ): MMLbase {
192        $resChar = TexUtil::getInstance()->mathchar( trim( $input ) );
193        if ( $resChar == null ) {
194            return new MMLarray();
195        }
196        return new MMLmi( '', [ 'mathvariant' => Variants::NORMAL ], $resChar );
197    }
198
199    public static function generateMMLError( string $msg ): MMLmerror {
200        return new MMLmerror( "", [], new MMLmtext( "", [], $msg ) );
201    }
202}