Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
74.42% |
64 / 86 |
|
57.14% |
8 / 14 |
CRAP | |
0.00% |
0 / 1 |
| Literal | |
74.42% |
64 / 86 |
|
57.14% |
8 / 14 |
62.17 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| getStart | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
| getArgFromCurlies | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| changeUnicodeFontInput | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
2.75 | |||
| toMMLTree | |
70.21% |
33 / 47 |
|
0.00% |
0 / 1 |
28.54 | |||
| getArg | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setArg | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getLiterals | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getExtendedLiterals | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| extractIdentifiers | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| extractSubscripts | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getModIdent | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getLiteral | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
| createVlineElement | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare( strict_types = 1 ); |
| 4 | |
| 5 | namespace MediaWiki\Extension\Math\WikiTexVC\Nodes; |
| 6 | |
| 7 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMethods; |
| 8 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\MathVariant; |
| 9 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi; |
| 10 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmn; |
| 11 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
| 12 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded; |
| 13 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; |
| 14 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle; |
| 15 | use MediaWiki\Extension\Math\WikiTexVC\TexUtil; |
| 16 | |
| 17 | class Literal extends TexNode { |
| 18 | private const CURLY_PATTERN = '/(?<start>[\\a-zA-Z\s]+)\{(?<arg>[^}]+)}/'; |
| 19 | |
| 20 | /** @var string */ |
| 21 | private $arg; |
| 22 | /** @var string[] */ |
| 23 | private $literals; |
| 24 | /** @var string[] */ |
| 25 | private $extendedLiterals; |
| 26 | |
| 27 | public function __construct( string $arg ) { |
| 28 | parent::__construct( $arg ); |
| 29 | $this->arg = $arg; |
| 30 | $this->literals = array_keys( TexUtil::getInstance()->getBaseElements()['is_literal'] ); |
| 31 | $this->extendedLiterals = $this->literals; |
| 32 | array_push( $this->extendedLiterals, '\\infty', '\\emptyset' ); |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Gets the arg of the literal or the part that is before |
| 37 | * a curly bracket if the expression contains one and matches |
| 38 | * {@link self::CURLY_PATTERN}. |
| 39 | * |
| 40 | * @return string |
| 41 | */ |
| 42 | private function getStart(): string { |
| 43 | if ( preg_match( self::CURLY_PATTERN, $this->arg, $matches ) ) { |
| 44 | return $matches['start']; |
| 45 | } |
| 46 | return $this->arg; |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * If the arg matches {@link self::CURLY_PATTERN}, return the |
| 51 | * inner content of the curlies. |
| 52 | * For example, for if the arg was a{b} this function returns b. |
| 53 | * |
| 54 | * @return string|null |
| 55 | */ |
| 56 | public function getArgFromCurlies(): ?string { |
| 57 | if ( preg_match( self::CURLY_PATTERN, $this->arg, $matches ) ) { |
| 58 | return $matches['arg']; |
| 59 | } |
| 60 | return null; |
| 61 | } |
| 62 | |
| 63 | public function changeUnicodeFontInput( string $input, array &$state, array &$arguments ): string { |
| 64 | $variant = MathVariant::removeMathVariantAttribute( $arguments ); |
| 65 | if ( $variant !== 'normal' ) { |
| 66 | // If the variant is normal, we do not need to change the input. |
| 67 | return MathVariant::translate( |
| 68 | $input, |
| 69 | $variant |
| 70 | ); |
| 71 | } |
| 72 | return $input; |
| 73 | } |
| 74 | |
| 75 | /** @inheritDoc */ |
| 76 | public function toMMLTree( $arguments = [], &$state = [] ) { |
| 77 | if ( $this->arg === " " ) { |
| 78 | // Fixes https://gerrit.wikimedia.org/r/c/mediawiki/extensions/Math/+/961711 |
| 79 | // And they creation of empty mo elements. |
| 80 | return null; |
| 81 | } |
| 82 | if ( isset( $state["intent-params"] ) ) { |
| 83 | foreach ( $state["intent-params"] as $intparam ) { |
| 84 | if ( $intparam == $this->arg ) { |
| 85 | $arguments["arg"] = $intparam; |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | if ( isset( $state["intent-params-expl"] ) ) { |
| 91 | $arguments["arg"] = $state["intent-params-expl"]; |
| 92 | } |
| 93 | |
| 94 | if ( is_numeric( $this->arg ) && empty( $state['inHBox'] ) ) { |
| 95 | if ( ( $arguments['mathvariant'] ?? '' ) === 'italic' ) { |
| 96 | // If the mathvariant italic does not exist for numbers |
| 97 | // https://github.com/w3c/mathml/issues/77#issuecomment-2993838911 |
| 98 | $arguments['style'] = trim( ( $arguments['style'] ?? '' ) . ' font-style: italic' ); |
| 99 | } |
| 100 | $content = $this->changeUnicodeFontInput( $this->arg, $state, $arguments ); |
| 101 | return new MMLmn( "", $arguments, $content ); |
| 102 | } |
| 103 | |
| 104 | // is important to split and find chars within curly and differentiate, see tc 459 |
| 105 | $input = $this->getStart(); |
| 106 | $operatorContent = $this->getArgFromCurlies(); |
| 107 | if ( $operatorContent !== null ) { |
| 108 | $operatorContent = [ 'foundOC' => $operatorContent ]; |
| 109 | } |
| 110 | |
| 111 | // This is rather a workaround: |
| 112 | // Sometimes literals from WikiTexVC contain complete \\operatorname {asd} hinted as bug tex-2-mml.json |
| 113 | if ( str_contains( $input, "\\operatorname" ) ) { |
| 114 | return new MMLmi( "", [], $operatorContent["foundOC"] ); |
| 115 | } |
| 116 | |
| 117 | $inputP = $input; |
| 118 | |
| 119 | // Sieve for Operators |
| 120 | $bm = new BaseMethods(); |
| 121 | $noStretchArgs = $arguments; |
| 122 | // Delimiters and operators should not be stretchy by default when used as literals |
| 123 | $noStretchArgs['stretchy'] ??= 'false'; |
| 124 | $ret = $bm->checkAndParseOperator( $inputP, $this, $noStretchArgs, $operatorContent, $state, false ); |
| 125 | if ( $ret ) { |
| 126 | return $ret; |
| 127 | } |
| 128 | // Sieve for mathchar07 chars |
| 129 | $bm = new BaseMethods(); |
| 130 | $ret = $bm->checkAndParseMathCharacter( $inputP, $this, $arguments, $operatorContent, false ); |
| 131 | if ( $ret ) { |
| 132 | return $ret; |
| 133 | } |
| 134 | |
| 135 | // Sieve for Identifiers |
| 136 | $ret = $bm->checkAndParseIdentifier( $inputP, $this, $arguments, $operatorContent, false ); |
| 137 | if ( $ret ) { |
| 138 | return $ret; |
| 139 | } |
| 140 | // Sieve for Delimiters |
| 141 | $ret = $bm->checkAndParseDelimiter( $input, $this, $noStretchArgs, $operatorContent ); |
| 142 | if ( $ret ) { |
| 143 | return $ret; |
| 144 | } |
| 145 | |
| 146 | // Sieve for Makros |
| 147 | $ret = BaseMethods::checkAndParse( $inputP, $arguments, |
| 148 | array_merge( $operatorContent ?? [], $state ?? [] ), |
| 149 | $this ); |
| 150 | if ( $ret ) { |
| 151 | return $ret; |
| 152 | } |
| 153 | |
| 154 | // Specific |
| 155 | if ( !( empty( $state['inMatrix'] ) ) && trim( $this->arg ) === '\vline' ) { |
| 156 | return $this->createVlineElement(); |
| 157 | } |
| 158 | |
| 159 | $content = $this->changeUnicodeFontInput( $input, $state, $arguments ); |
| 160 | if ( !( empty( $state['inHBox'] ) ) ) { |
| 161 | // No mi, if literal is from HBox |
| 162 | return $content; |
| 163 | } |
| 164 | // If falling through all sieves just creates an mi element |
| 165 | |
| 166 | return new MMLmi( "", $arguments, $content ); |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * @return string |
| 171 | */ |
| 172 | public function getArg(): string { |
| 173 | return $this->arg; |
| 174 | } |
| 175 | |
| 176 | public function setArg( string $arg ) { |
| 177 | $this->arg = $arg; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * @return int[]|string[] |
| 182 | */ |
| 183 | public function getLiterals(): array { |
| 184 | return $this->literals; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * @return int[]|string[] |
| 189 | */ |
| 190 | public function getExtendedLiterals(): array { |
| 191 | return $this->extendedLiterals; |
| 192 | } |
| 193 | |
| 194 | /** @inheritDoc */ |
| 195 | public function extractIdentifiers( $args = null ) { |
| 196 | return $this->getLiteral( $this->literals, '/^([a-zA-Z\']|\\\\int)$/' ); |
| 197 | } |
| 198 | |
| 199 | /** @inheritDoc */ |
| 200 | public function extractSubscripts() { |
| 201 | return $this->getLiteral( $this->extendedLiterals, '/^([0-9a-zA-Z+\',-])$/' ); |
| 202 | } |
| 203 | |
| 204 | /** @inheritDoc */ |
| 205 | public function getModIdent() { |
| 206 | if ( $this->arg === '\\ ' ) { |
| 207 | return [ '\\ ' ]; |
| 208 | } |
| 209 | return $this->getLiteral( $this->literals, '/^([0-9a-zA-Z\'])$/' ); |
| 210 | } |
| 211 | |
| 212 | private function getLiteral( array $lit, string $regexp ): array { |
| 213 | $s = trim( $this->arg ); |
| 214 | if ( preg_match( $regexp, $s ) == 1 ) { |
| 215 | return [ $s ]; |
| 216 | } elseif ( in_array( $s, $lit, true ) ) { |
| 217 | return [ $s ]; |
| 218 | } else { |
| 219 | return []; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * @return string |
| 225 | */ |
| 226 | public function createVlineElement(): string { |
| 227 | $mrow = new MMLmrow(); |
| 228 | $mpAdded = new MMLmpadded( "", [ "depth" => "0", "height" => "0" ] ); |
| 229 | $mStyle = new MMLmstyle( "", [ "mathsize" => "1.2em" ] ); |
| 230 | $mo = new MMLmo( "", [ "fence" => "false", "stretchy" => "false" ] ); |
| 231 | return $mrow->encapsulateRaw( $mpAdded->encapsulateRaw( |
| 232 | $mStyle->encapsulateRaw( $mo->encapsulateRaw( "|" ) ) ) ); |
| 233 | } |
| 234 | |
| 235 | } |