Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
73.33% |
55 / 75 |
|
62.50% |
10 / 16 |
CRAP | |
0.00% |
0 / 1 |
TexNode | |
73.33% |
55 / 75 |
|
62.50% |
10 / 16 |
94.53 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
parseToMML | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getArgs | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
render | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
renderMML | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
renderChildMML | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isEmpty | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
6 | |||
getLength | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
inCurlies | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
extractIdentifiers | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
containsFunc | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
extractSubscripts | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getModIdent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
texContainsFunc | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
9 | |||
match | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
9 | |||
isCurly | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace MediaWiki\Extension\Math\WikiTexVC\Nodes; |
6 | |
7 | use InvalidArgumentException; |
8 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMethods; |
9 | |
10 | class 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 | } |