Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.43% covered (warning)
71.43%
55 / 77
58.82% covered (warning)
58.82%
10 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
TexNode
71.43% covered (warning)
71.43%
55 / 77
58.82% covered (warning)
58.82%
10 / 17
108.31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 parseToMML
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getArgs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 toMMLTree
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 processChildMML
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 renderMML
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isEmpty
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 getLength
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 inCurlies
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 extractIdentifiers
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 containsFunc
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 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%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 texContainsFunc
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
9
 match
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 isCurly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare( strict_types = 1 );
4
5namespace MediaWiki\Extension\Math\WikiTexVC\Nodes;
6
7use InvalidArgumentException;
8use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMethods;
9use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLarray;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase;
11
12class TexNode {
13
14    /** @var list<TexNode|string> */
15    protected $args;
16
17    /**
18     * Creates a TexNode
19     * @param TexNode|string ...$args arguments for this node
20     */
21    public function __construct( ...$args ) {
22        foreach ( $args as $arg ) {
23            if ( !( $arg instanceof TexNode || is_string( $arg ) ) ) {
24                throw new InvalidArgumentException( 'Wrong input type specified in args.' );
25            }
26        }
27        $this->args = $args;
28    }
29
30    /**
31     * @param string $input
32     * @param array $passedArgs
33     * @param mixed|null $operatorContent
34     * @return MMLbase|string|null
35     */
36    protected function parseToMML( $input, $passedArgs, $operatorContent ) {
37        $parsed = BaseMethods::checkAndParse( $input, $passedArgs, $operatorContent, $this );
38        if ( $parsed ) {
39            return $parsed;
40        }
41        $name = strtoupper( self::class );
42
43        return BaseMethods::generateMMLError( "Not implemented $name for $input" );
44    }
45
46    /**
47     * @return TexNode[]|string[]
48     */
49    public function getArgs(): array {
50        return $this->args;
51    }
52
53    /**
54     * @return string
55     */
56    public function render() {
57        $out = '';
58        foreach ( $this->args as $child ) {
59            $out .= $child instanceof self ? $child->render() : $child;
60        }
61        return $out;
62    }
63
64    /**
65     * @param array $arguments
66     * @param array &$state
67     * @return MMLbase|string|null
68     */
69    public function toMMLTree( array $arguments = [], array &$state = [] ) {
70        return new MMLarray( ...array_map(
71            fn ( $child ) => $this->processChildMML( $child, $arguments, $state ),
72            $this->args
73        ) );
74    }
75
76    /**
77     * @param mixed $child
78     * @param array $arguments
79     * @param array &$state
80     * @return MMLbase|null
81     */
82    private function processChildMML( $child, array $arguments, array &$state ): ?MMLbase {
83        if ( $child instanceof self ) {
84            return $child->toMMLTree( $arguments, $state );
85        }
86        return null;
87    }
88
89    /**
90     * @param array $arguments
91     * @param array &$state
92     * @return string
93     */
94    public function renderMML( $arguments = [], &$state = [] ) {
95        return (string)$this->toMMLTree( $arguments, $state );
96    }
97
98    /**
99     * @return bool
100     */
101    public function isEmpty() {
102        foreach ( $this->args ?? [] as $arg ) {
103            if ( $arg instanceof TexNode && !$arg->isEmpty() ) {
104                return false;
105            }
106            if ( is_string( $arg ) && $arg !== '' ) {
107                return false;
108            }
109        }
110        return true;
111    }
112
113    public function getLength(): int {
114        return count( $this->args ?? [] );
115    }
116
117    /**
118     * Wraps the rendered result in curly brackets.
119     * @return string rendered result in curlies.
120     */
121    public function inCurlies() {
122        return '{' . $this->render() . '}';
123    }
124
125    /**
126     * @param self[]|string[]|null $args
127     * @return string[]
128     */
129    public function extractIdentifiers( $args = null ) {
130        $output = [];
131
132        foreach ( $args ?? $this->args as $value ) {
133            if ( $value instanceof self ) {
134                $output = array_merge( $output, $value->extractIdentifiers() );
135            } else {
136                $output[] = $value;
137            }
138        }
139
140        return $output;
141    }
142
143    /**
144     * @param string|array $target
145     * @param self[]|string[]|null $args
146     * @return bool
147     */
148    public function containsFunc( $target, $args = null ) {
149        foreach ( $args ?? $this->args as $value ) {
150            if ( $value instanceof self ) {
151                $ret = $value->containsFunc( $target );
152            } else {
153                $ret = self::texContainsFunc( $target, $value );
154            }
155            if ( $ret ) {
156                // Do not check the other items, if some function has been found already.
157                return true;
158            }
159        }
160
161        return false;
162    }
163
164    /**
165     * @return string|array
166     */
167    public function extractSubscripts() {
168        return [];
169    }
170
171    /**
172     * @return string|array
173     */
174    public function getModIdent() {
175        return [];
176    }
177
178    /**
179     * strings can contain function references only in a few specific
180     * forms, which we test for here.
181     *
182     * @param string|array $target
183     * @param string $t Tex to be checked
184     * @return string|bool rendered LaTeX string or false if not found.
185     */
186    public static function texContainsFunc( $target, string $t ) {
187        // protect against using random strings as keys in target
188        if ( !$t || $t[0] !== '\\' ) {
189            return false;
190        }
191
192        // may have trailing '(', '[', '\\{' or " "
193        $t = preg_replace( '/(\(|\[|\\\\{| )$/', '', $t );
194
195        // special case #1: \\operatorname {someword}
196        $m = preg_match( '/^\\\\operatorname \{([^\\\\]*)}$/', $t );
197        if ( $m == 1 ) {
198            return self::match( $target, '\\operatorname' );
199        }
200
201        // special case #2: \\mbox{\\somefunc}
202        $matches = [];
203        $m = preg_match( '/^\\\\mbox\{(\\\\.*)}$/', $t, $matches );
204        if ( $m == 1 ) {
205            return self::match( $target, '\\mbox' ) ?: self::match( $target, $matches[1] );
206        }
207
208        // special case #3: \\color, \\pagecolor, \\definecolor
209        $matches = [];
210        $m = preg_match( '/^(\\\\(?:page|define)?color) /', $t, $matches );
211        if ( $m == 1 ) {
212            return self::match( $target, $matches[1] );
213        }
214
215        // special case #4: \\mathbb, \\mathrm
216        $matches = [];
217        $m = preg_match( '/^(\\\\math..) \{(\\\\.*)}$/', $t, $matches );
218        if ( $m == 1 ) {
219            return self::match( $target, $matches[1] ) ?: self::match( $target, $matches[2] );
220        }
221
222        return self::match( $target, $t );
223    }
224
225    /**
226     * Matches a string against a string, array, or set target.
227     * @param string|array $target
228     * @param string $str
229     * @return bool|string matching value or false
230     */
231    public static function match( $target, string $str ) {
232        if ( is_string( $target ) ) {
233            return $target === $str ? $str : false;
234        }
235
236        foreach ( $target as $key => $value ) {
237            // In javascript both types are used to comparison in match functionality
238            if ( is_string( $key ) ) {
239                if ( $key === $str ) {
240                    return $str;
241                }
242            } elseif ( is_array( $value ) ) {
243                if ( self::match( $value, $str ) !== false ) {
244                    return $str;
245                }
246            } elseif ( $value === $str ) {
247                return $str;
248            }
249        }
250
251        return false;
252    }
253
254    public function isCurly(): bool {
255        return false;
256    }
257
258}