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