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