Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
83.33% |
65 / 78 |
|
33.33% |
3 / 9 |
CRAP | |
0.00% |
0 / 1 |
| BaseMethods | |
83.33% |
65 / 78 |
|
33.33% |
3 / 9 |
55.80 | |
0.00% |
0 / 1 |
| checkAndParse | |
82.35% |
14 / 17 |
|
0.00% |
0 / 1 |
8.35 | |||
| checkAndParseOperator | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
6.02 | |||
| parseOperatorDict | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
| parseOperator | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
6 | |||
| checkAndParseIdentifier | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
4.59 | |||
| parseIdentifier | |
70.00% |
7 / 10 |
|
0.00% |
0 / 1 |
5.68 | |||
| checkAndParseDelimiter | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
| checkAndParseMathCharacter | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| generateMMLError | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings; |
| 3 | |
| 4 | use ArgumentCountError; |
| 5 | use Exception; |
| 6 | use LogicException; |
| 7 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants; |
| 8 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil; |
| 9 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLarray; |
| 10 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase; |
| 11 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror; |
| 12 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi; |
| 13 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
| 14 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace; |
| 15 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext; |
| 16 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode; |
| 17 | use 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 | */ |
| 27 | class 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 | } |