Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
69.30% |
79 / 114 |
|
30.00% |
3 / 10 |
CRAP | |
0.00% |
0 / 1 |
BaseMethods | |
69.30% |
79 / 114 |
|
30.00% |
3 / 10 |
126.27 | |
0.00% |
0 / 1 |
checkAndParse | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
7.39 | |||
checkAndParseOperator | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
7.39 | |||
parseOperatorDict | |
82.35% |
14 / 17 |
|
0.00% |
0 / 1 |
7.27 | |||
parseOperator | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
checkAndParseIdentifier | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
4.59 | |||
parseIdentifier | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
checkAndParseDelimiter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
7 | |||
checkAndParseMathCharacter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
checkAndParseColor | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
72 | |||
generateMMLError | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings; |
3 | |
4 | use ArgumentCountError; |
5 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants; |
6 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil; |
7 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror; |
8 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi; |
9 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
10 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; |
11 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace; |
12 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle; |
13 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext; |
14 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode; |
15 | use 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 | */ |
25 | class 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( "<" ); |
103 | case ">": |
104 | $mmlMo = new MMLmo(); |
105 | return $mmlMo->encapsulateRaw( ">" ); |
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 . "̸" ); |
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 | } |