Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.37% covered (warning)
72.37%
55 / 76
66.67% covered (warning)
66.67%
10 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
TexNode
72.37% covered (warning)
72.37%
55 / 76
66.67% covered (warning)
66.67%
10 / 15
99.65
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 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 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
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        if ( isset( $this->args[0] ) ) {
80            return count( $this->args );
81        } else {
82            return 0;
83        }
84    }
85
86    /**
87     * Wraps the rendered result in curly brackets.
88     * @return string rendered result in curlies.
89     */
90    public function inCurlies() {
91        return '{' . $this->render() . '}';
92    }
93
94    public function extractIdentifiers( $args = null ) {
95        $output = [];
96
97        foreach ( $args ?? $this->args as $value ) {
98            if ( $value instanceof self ) {
99                $output = array_merge( $output, $value->extractIdentifiers() );
100            } else {
101                $output[] = $value;
102            }
103        }
104
105        return $output;
106    }
107
108    public function containsFunc( $target, $args = null ) {
109        foreach ( $args ?? $this->args as $value ) {
110            if ( $value instanceof self ) {
111                $ret = $value->containsFunc( $target );
112            } else {
113                $ret = self::texContainsFunc( $target, $value );
114            }
115            if ( $ret ) {
116                // Do not check the other items, if some function has been found already.
117                return true;
118            }
119        }
120
121        return false;
122    }
123
124    public function extractSubscripts() {
125        return [];
126    }
127
128    public function getModIdent() {
129        return [];
130    }
131
132    /**
133     * strings can contain function references only in a few specific
134     * forms, which we test for here.
135     *
136     * @param string|array $target
137     * @param string $t Tex to be checked
138     * @return string|bool rendered LaTeX string or false if not found.
139     */
140    public static function texContainsFunc( $target, string $t ) {
141        // protect against using random strings as keys in target
142        if ( !$t || $t[0] !== '\\' ) {
143            return false;
144        }
145
146        // may have trailing '(', '[', '\\{' or " "
147        $t = preg_replace( '/(\(|\[|\\\\{| )$/', '', $t );
148
149        // special case #1: \\operatorname {someword}
150        $m = preg_match( '/^\\\\operatorname \{([^\\\\]*)}$/', $t );
151        if ( $m == 1 ) {
152            return self::match( $target, '\\operatorname' );
153        }
154
155        // special case #2: \\mbox{\\somefunc}
156        $matches = [];
157        $m = preg_match( '/^\\\\mbox\{(\\\\.*)}$/', $t, $matches );
158        if ( $m == 1 ) {
159            return self::match( $target, '\\mbox' ) ?: self::match( $target, $matches[1] );
160        }
161
162        // special case #3: \\color, \\pagecolor, \\definecolor
163        $matches = [];
164        $m = preg_match( '/^(\\\\(?:page|define)?color) /', $t, $matches );
165        if ( $m == 1 ) {
166            return self::match( $target, $matches[1] );
167        }
168
169        // special case #4: \\mathbb, \\mathrm
170        $matches = [];
171        $m = preg_match( '/^(\\\\math..) \{(\\\\.*)}$/', $t, $matches );
172        if ( $m == 1 ) {
173            return self::match( $target, $matches[1] ) ?: self::match( $target, $matches[2] );
174        }
175
176        return self::match( $target, $t );
177    }
178
179    /**
180     * Matches a string against a string, array, or set target.
181     * @param string|array $target
182     * @param string $str
183     * @return bool|string matching value or false
184     */
185    public static function match( $target, string $str ) {
186        if ( is_string( $target ) ) {
187            return $target === $str ? $str : false;
188        }
189
190        foreach ( $target as $key => $value ) {
191            // In javascript both types are used to comparison in match functionality
192            if ( is_string( $key ) ) {
193                if ( $key === $str ) {
194                    return $str;
195                }
196            } elseif ( is_array( $value ) ) {
197                if ( self::match( $value, $str ) !== false ) {
198                    return $str;
199                }
200            } elseif ( $value === $str ) {
201                return $str;
202            }
203        }
204
205        return false;
206    }
207}