Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.23% covered (danger)
12.23%
17 / 139
7.69% covered (danger)
7.69%
1 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
MMLParsingUtil
12.23% covered (danger)
12.23%
17 / 139
7.69% covered (danger)
7.69%
1 / 13
2253.77
0.00% covered (danger)
0.00%
0 / 1
 renderApplyFunction
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getFontArgs
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
272
 parseDefineColorExpression
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 createNot
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mapToDoubleStruckUnicode
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 mapToCaligraphicUnicode
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 matchAlphanumeric
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getIntentContent
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getIntentParams
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getIntentArgs
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 forgeIntentToTopElement
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 addAttributesToMML
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
182
 forgeIntentToSpecificElement
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util;
4
5use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Tag;
6use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\TexClass;
7use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants;
8use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
9use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded;
10use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
11use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext;
12
13/**
14 * This class contains functionalities for MML-node
15 * parsing which can be extracted and are used
16 * for multiple functions.
17 */
18class MMLParsingUtil {
19    public static function renderApplyFunction() {
20        $mo = new MMLmo();
21        return $mo->encapsulateRaw( "&#x2061;" );
22    }
23
24    public static function getFontArgs( $name, $variant, $passedArgs ) {
25        $args = [];
26        switch ( $name ) {
27            case "cal":
28            case "mathcal":
29                $args = [ Tag::MJXVARIANT => "-tex-calligraphic", "mathvariant" => Variants::SCRIPT ];
30                break;
31            case "it":
32            case "mathit":
33                $args = [ Tag::MJXVARIANT => $variant, "mathvariant" => Variants::ITALIC ];
34                break;
35            case "bf":
36            case "mathbf":
37                $args = [ "mathvariant" => $variant ];
38                break;
39            // Sstatements from here come from other fct ok ? otherwise create second fct
40            case "textit":
41                $args = [ "mathvariant" => Variants::ITALIC ];
42                break;
43            case "textbf":
44                $args = [ "mathvariant" => Variants::BOLD ];
45                break;
46            case "textsf":
47                $args = [ "mathvariant" => Variants::SANSSERIF ];
48                break;
49            case "texttt":
50                $args = [ "mathvariant" => Variants::MONOSPACE ];
51                break;
52            case "textrm":
53                break;
54            case "emph":
55                // Toggle by passed args in emph
56                if ( isset( $passedArgs["mathvariant"] ) ) {
57                    if ( $passedArgs["mathvariant"] === Variants::ITALIC ) {
58                        $args = [ "mathvariant" => Variants::NORMAL ];
59                    }
60                } else {
61                    $args = [ "mathvariant" => Variants::ITALIC ];
62                }
63                break;
64            default:
65                $args = [ "mathvariant" => $variant ];
66
67        }
68        return $args;
69    }
70
71    /**
72     * Parses an expression that defines a color; this is usually an argument in Literal.
73     * Example expression is: "\definecolor {ultramarine}{rgb}{0,0.12549019607843,0.37647058823529}"
74     * @param string $input tex-string, which contains the expression
75     * @return array|null either an array which contains hex of parsed expression or null if not parsable
76     */
77    public static function parseDefineColorExpression( string $input ): ?array {
78        $returnObj = null;
79        $matches = [];
80        $matched = preg_match_all( '/\{(.*?)\}/', $input, $matches );
81        if ( !$matched ) {
82            return null;
83        }
84        $ctr = count( $matches[1] ?? [] );
85
86        if ( $ctr == 3 && $matches[1][1] === "rgb" ) {
87            $returnObj = [];
88            $rgbValues = explode( ",", $matches[1][2] );
89            $r  = round( floatval( $rgbValues[0] ) * 255 );
90            $g = round( floatval( $rgbValues[1] ) * 255 );
91            $b = round( floatval( $rgbValues[2] ) * 255 );
92            $color = sprintf( "#%02x%02x%02x", $r, $g, $b );
93            $returnObj["name"] = $matches[1][0];
94            $returnObj["type"] = "rgb";
95            $returnObj["hex"] = $color;
96        }
97
98        return $returnObj;
99    }
100
101    /**
102     * Creates a negation block in MathML, usually preceding the negated statement
103     * @return string negation block as MathML
104     */
105    public static function createNot() {
106        $mmlMrow = new MMLmrow( TexClass::REL );
107        $mpadded = new MMLmpadded( "", [ "width" => "0" ] );
108        $mtext = new MMLmtext();
109        return $mmlMrow->encapsulateRaw( $mpadded->encapsulateRaw( $mtext->encapsulateRaw( "&#x29F8;" ) ) );
110    }
111
112    public static function mapToDoubleStruckUnicode( $inputString ) {
113        $map = [
114            '0' => '&#x1D7D8;', '1' => '&#x1D7D9;', '2' => '&#x1D7DA;', '3' => '&#x1D7DB;', '4' => '&#x1D7DC;',
115            '5' => '&#x1D7DD;', '6' => '&#x1D7DE;', '7' => '&#x1D7DF;', '8' => '&#x1D7E0;', '9' => '&#x1D7E1;',
116            'A' => '&#x1D538;', 'B' => '&#x1D539;', 'C' => '&#x2102;', 'D' => '&#x1D53B;', 'E' => '&#x1D53C;',
117            'F' => '&#x1D53D;', 'G' => '&#x1D53E;', 'H' => '&#x210D;', 'I' => '&#x1D540;', 'J' => '&#x1D541;',
118            'K' => '&#x1D542;', 'L' => '&#x1D543;', 'M' => '&#x1D544;', 'N' => '&#x2115;', 'O' => '&#x1D546;',
119            'P' => '&#x2119;', 'Q' => '&#x211A;', 'R' => '&#x211D;', 'S' => '&#x1D54A;', 'T' => '&#x1D54B;',
120            'U' => '&#x1D54C;', 'V' => '&#x1D54D;', 'W' => '&#x1D54E;', 'X' => '&#x1D54F;', 'Y' => '&#x1D550;',
121            'Z' => '&#x2124;', 'a' => '&#x1D552;', 'b' => '&#x1D553;', 'c' => '&#x1D554;', 'd' => '&#x1D555;',
122            'e' => '&#x1D556;', 'f' => '&#x1D557;', 'g' => '&#x1D558;', 'h' => '&#x1D559;', 'i' => '&#x1D55A;',
123            'j' => '&#x1D55B;', 'k' => '&#x1D55C;', 'l' => '&#x1D55D;', 'm' => '&#x1D55E;', 'n' => '&#x1D55F;',
124            'o' => '&#x1D560;', 'p' => '&#x1D561;', 'q' => '&#x1D562;', 'r' => '&#x1D563;', 's' => '&#x1D564;',
125            't' => '&#x1D565;', 'u' => '&#x1D566;', 'v' => '&#x1D567;', 'w' => '&#x1D568;', 'x' => '&#x1D569;',
126            'y' => '&#x1D56A;', 'z' => '&#x1D56B;'
127        ];
128
129        return self::matchAlphanumeric( $inputString, $map );
130    }
131
132    public static function mapToCaligraphicUnicode( $inputString ) {
133        $map = [
134            '0' => '&#x1D7CE;', '1' => '&#x1D7CF;', '2' => '&#x1D7D0;', '3' => '&#x1D7D1;', '4' => '&#x1D7D2;',
135            '5' => '&#x1D7D3;', '6' => '&#x1D7D4;', '7' => '&#x1D7D5;', '8' => '&#x1D7D6;', '9' => '&#x1D7D7;',
136            'A' => '&#x1D49C;', 'B' => '&#x212C;', 'C' => '&#x1D49E;', 'D' => '&#x1D49F;', 'E' => '&#x2130;',
137            'F' => '&#x2131;', 'G' => '&#x1D4A2;', 'H' => '&#x210B;', 'I' => '&#x2110;', 'J' => '&#x1D4A5;',
138            'K' => '&#x1D4A6;', 'L' => '&#x2112;', 'M' => '&#x2133;', 'N' => '&#x1D4A9;', 'O' => '&#x1D4AA;',
139            'P' => '&#x1D4AB;', 'Q' => '&#x1D4AC;', 'R' => '&#x211B;', 'S' => '&#x1D4AE;', 'T' => '&#x1D4AF;',
140            'U' => '&#x1D4B0;', 'V' => '&#x1D4B1;', 'W' => '&#x1D4B2;', 'X' => '&#x1D4B3;', 'Y' => '&#x1D4B4;',
141            'Z' => '&#x1D4B5;', 'a' => '&#x1D4B6;', 'b' => '&#x1D4B7;', 'c' => '&#x1D4B8;', 'd' => '&#x1D4B9;',
142            'e' => '&#x212F;', 'f' => '&#x1D4BB;', 'g' => '&#x210A;', 'h' => '&#x1D4BD;', 'i' => '&#x1D4BE;',
143            'j' => '&#x1D4BF;', 'k' => '&#x1D4C0;', 'l' => '&#x1D4C1;', 'm' => '&#x1D4C2;', 'n' => '&#x1D4C3;',
144            'o' => '&#x2134;', 'p' => '&#x1D4C5;', 'q' => '&#x1D4C6;', 'r' => '&#x1D4C7;', 's' => '&#x1D4C8;',
145            't' => '&#x1D4C9;', 'u' => '&#x1D4CA;', 'v' => '&#x1D4CB;', 'w' => '&#x1D4CC;', 'x' => '&#x1D4CD;',
146            'y' => '&#x1D4CE;', 'z' => '&#x1D4CF;'
147        ];
148
149        return self::matchAlphanumeric( $inputString, $map );
150    }
151
152    public static function matchAlphanumeric( $inputString, $map ) {
153        // Replace each character in the input string with its caligraphic Unicode equivalent
154        return preg_replace_callback( '/[A-Za-z0-9]/u', static function ( $matches ) use ( $map ) {
155            return $map[$matches[0]] ?? $matches[0];
156        }, $inputString );
157    }
158
159    public static function getIntentContent( ?string $input ) {
160        if ( !$input ) {
161            return null;
162        }
163        $matchesInt = [];
164        $matchInt = preg_match( "/intent=[\'\"](.*)[\'\"]/", $input, $matchesInt );
165        if ( $matchInt && count( $matchesInt ) >= 2 ) {
166            return $matchesInt[1];
167        }
168        return null;
169    }
170
171    public static function getIntentParams( ?string $intentContent ) {
172        if ( !$intentContent ) {
173            return null;
174        }
175        $matchesParams = [];
176        // tbd eventually not only alphanumerical chars valid in intent params
177        $matchParams = preg_match_all( "/\\\$([a-zA-Z]+)/", $intentContent, $matchesParams );
178        if ( $matchParams && count( $matchesParams ) >= 2 ) {
179            return $matchesParams[1];
180        }
181        return null;
182    }
183
184    public static function getIntentArgs( ?string $input ) {
185        if ( !$input ) {
186            return null;
187        }
188        $matchesArgs = [];
189        $matchArg = preg_match( "/arg\s*=\s*[\'\"](.*?)[\'\"]/", $input, $matchesArgs );
190        if ( $matchArg && count( $matchesArgs ) >= 2 ) {
191            return $matchesArgs[1];
192        }
193        return null;
194    }
195
196    /**
197     * Converts a rendered MathML string to a XML tree and adds the attributes from input
198     * to the top-level element.Valid attributes for adding are "arg" and "intent.
199     * It overwrites pre-existing attributes in the top-level element.
200     * TBD: currently contains a hacky way to remove xml header in the output string
201     * example:" <msup intent="_($op,_of,$arg)">" intent attributes comes from input variables
202     * @param string $renderedMML defines input MathML string
203     * @param array $intentContentAtr defines attributes to add
204     * @return string MML with added attributes
205     */
206    public static function forgeIntentToTopElement( string $renderedMML, $intentContentAtr ) {
207        if ( !$intentContentAtr || !$renderedMML ) {
208            return $renderedMML;
209        }
210
211        return self::addAttributesToMML( $renderedMML, $intentContentAtr, "" );
212    }
213
214    /**
215     * Add parameters from aattributes to the MML string
216     * @param string $renderedMML defines input MathML string
217     * @param array $intentContentAtr defines attributes to add
218     * @param string $elementTag element tag when using foundNodes
219     * @param bool $useFoundNodes use found nodes
220     * @return string MML with added attributes
221     */
222    public static function addAttributesToMML( $renderedMML, $intentContentAtr, $elementTag, $useFoundNodes = false ) {
223        $xml = simplexml_load_string( $renderedMML );
224        if ( !$xml ) {
225            return "";
226        }
227        if ( $useFoundNodes ) {
228            $foundNodes = $xml->xpath( $elementTag );
229            if ( !( isset( $foundNodes ) && count( $foundNodes ) >= 1 ) ) {
230                return $renderedMML;
231            }
232        }
233
234        if ( isset( $intentContentAtr["intent"] ) ) {
235            if ( isset( $xml["intent"] ) ) {
236                $xml["intent"] = $intentContentAtr["intent"];
237            } elseif ( $intentContentAtr["intent"] != null && is_string( $intentContentAtr["intent"] ) ) {
238                $xml->addAttribute( "intent", $intentContentAtr["intent"] );
239            }
240        }
241        if ( isset( $intentContentAtr["arg"] ) ) {
242            if ( isset( $xml["arg"] ) ) {
243                $xml["arg"] = $intentContentAtr["arg"];
244            } elseif ( $intentContentAtr["arg"] != null && is_string( $intentContentAtr["arg"] ) ) {
245                $xml->addAttribute( "arg", $intentContentAtr["arg"] );
246            }
247        }
248
249        $hackyXML = str_replace( "<?xml version=\"1.0\"?>", "", $xml->asXML() );
250        return str_replace( "\n", "", $hackyXML );
251    }
252
253    public static function forgeIntentToSpecificElement( string $renderedMML, $intentContentAtr, string $elementTag ) {
254        if ( !$intentContentAtr || !$renderedMML || !$elementTag ) {
255            return $renderedMML;
256        }
257        return self::addAttributesToMML( $elementTag, $intentContentAtr, $elementTag, true );
258    }
259
260}