Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.42% covered (warning)
74.42%
64 / 86
57.14% covered (warning)
57.14%
8 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Literal
74.42% covered (warning)
74.42%
64 / 86
57.14% covered (warning)
57.14%
8 / 14
62.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getStart
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getArgFromCurlies
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 changeUnicodeFontInput
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
2.75
 toMMLTree
70.21% covered (warning)
70.21%
33 / 47
0.00% covered (danger)
0.00%
0 / 1
28.54
 getArg
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setArg
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLiterals
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExtendedLiterals
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 extractIdentifiers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 extractSubscripts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getModIdent
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getLiteral
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 createVlineElement
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace MediaWiki\Extension\Math\WikiTexVC\Nodes;
6
7use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMethods;
8use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\MathVariant;
9use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmn;
11use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
12use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded;
13use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
14use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle;
15use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
16
17class 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}