Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
22.78% |
36 / 158 |
|
20.00% |
3 / 15 |
CRAP | |
0.00% |
0 / 1 |
MMLParsingUtil | |
22.78% |
36 / 158 |
|
20.00% |
3 / 15 |
2010.07 | |
0.00% |
0 / 1 |
renderApplyFunction | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFontArgs | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
272 | |||
parseDefineColorExpression | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
4 | |||
createNot | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
mapToDoubleStruckUnicode | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
mapToCaligraphicUnicode | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
mapToFrakturUnicode | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
7 | |||
addToChr | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
matchAlphanumeric | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getIntentContent | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getIntentParams | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getIntentArgs | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
forgeIntentToTopElement | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
addAttributesToMML | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
182 | |||
forgeIntentToSpecificElement | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util; |
4 | |
5 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Tag; |
6 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\TexClass; |
7 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants; |
8 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
9 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded; |
10 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; |
11 | use 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 | */ |
18 | class MMLParsingUtil { |
19 | public static function renderApplyFunction() { |
20 | $mo = new MMLmo(); |
21 | return $mo->encapsulateRaw( "⁡" ); |
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( "⧸" ) ) ); |
110 | } |
111 | |
112 | public static function mapToDoubleStruckUnicode( $inputString ) { |
113 | $map = [ |
114 | '0' => '𝟘', '1' => '𝟙', '2' => '𝟚', '3' => '𝟛', '4' => '𝟜', |
115 | '5' => '𝟝', '6' => '𝟞', '7' => '𝟟', '8' => '𝟠', '9' => '𝟡', |
116 | 'A' => '𝔸', 'B' => '𝔹', 'C' => 'ℂ', 'D' => '𝔻', 'E' => '𝔼', |
117 | 'F' => '𝔽', 'G' => '𝔾', 'H' => 'ℍ', 'I' => '𝕀', 'J' => '𝕁', |
118 | 'K' => '𝕂', 'L' => '𝕃', 'M' => '𝕄', 'N' => 'ℕ', 'O' => '𝕆', |
119 | 'P' => 'ℙ', 'Q' => 'ℚ', 'R' => 'ℝ', 'S' => '𝕊', 'T' => '𝕋', |
120 | 'U' => '𝕌', 'V' => '𝕍', 'W' => '𝕎', 'X' => '𝕏', 'Y' => '𝕐', |
121 | 'Z' => 'ℤ', 'a' => '𝕒', 'b' => '𝕓', 'c' => '𝕔', 'd' => '𝕕', |
122 | 'e' => '𝕖', 'f' => '𝕗', 'g' => '𝕘', 'h' => '𝕙', 'i' => '𝕚', |
123 | 'j' => '𝕛', 'k' => '𝕜', 'l' => '𝕝', 'm' => '𝕞', 'n' => '𝕟', |
124 | 'o' => '𝕠', 'p' => '𝕡', 'q' => '𝕢', 'r' => '𝕣', 's' => '𝕤', |
125 | 't' => '𝕥', 'u' => '𝕦', 'v' => '𝕧', 'w' => '𝕨', 'x' => '𝕩', |
126 | 'y' => '𝕪', 'z' => '𝕫' |
127 | ]; |
128 | |
129 | return self::matchAlphanumeric( $inputString, $map ); |
130 | } |
131 | |
132 | public static function mapToCaligraphicUnicode( $inputString ) { |
133 | $map = [ |
134 | '0' => '𝟎', '1' => '𝟏', '2' => '𝟐', '3' => '𝟑', '4' => '𝟒', |
135 | '5' => '𝟓', '6' => '𝟔', '7' => '𝟕', '8' => '𝟖', '9' => '𝟗', |
136 | 'A' => '𝒜', 'B' => 'ℬ', 'C' => '𝒞', 'D' => '𝒟', 'E' => 'ℰ', |
137 | 'F' => 'ℱ', 'G' => '𝒢', 'H' => 'ℋ', 'I' => 'ℐ', 'J' => '𝒥', |
138 | 'K' => '𝒦', 'L' => 'ℒ', 'M' => 'ℳ', 'N' => '𝒩', 'O' => '𝒪', |
139 | 'P' => '𝒫', 'Q' => '𝒬', 'R' => 'ℛ', 'S' => '𝒮', 'T' => '𝒯', |
140 | 'U' => '𝒰', 'V' => '𝒱', 'W' => '𝒲', 'X' => '𝒳', 'Y' => '𝒴', |
141 | 'Z' => '𝒵', 'a' => '𝒶', 'b' => '𝒷', 'c' => '𝒸', 'd' => '𝒹', |
142 | 'e' => 'ℯ', 'f' => '𝒻', 'g' => 'ℊ', 'h' => '𝒽', 'i' => '𝒾', |
143 | 'j' => '𝒿', 'k' => '𝓀', 'l' => '𝓁', 'm' => '𝓂', 'n' => '𝓃', |
144 | 'o' => 'ℴ', 'p' => '𝓅', 'q' => '𝓆', 'r' => '𝓇', 's' => '𝓈', |
145 | 't' => '𝓉', 'u' => '𝓊', 'v' => '𝓋', 'w' => '𝓌', 'x' => '𝓍', |
146 | 'y' => '𝓎', 'z' => '𝓏' |
147 | ]; |
148 | |
149 | return self::matchAlphanumeric( $inputString, $map ); |
150 | } |
151 | |
152 | public static function mapToFrakturUnicode( $inputString ): string { |
153 | $res = ''; |
154 | $specialCases = [ 'C' => 'ℭ', |
155 | 'H' => 'ℌ', |
156 | 'I' => 'ℑ', |
157 | 'R' => 'ℜ', |
158 | 'Z' => 'ℤ' ]; |
159 | foreach ( mb_str_split( $inputString ) as $chr ) { |
160 | // see https://www.w3.org/TR/mathml-core/#fraktur-mappings |
161 | if ( isset( $specialCases[$chr] ) ) { |
162 | $res .= $specialCases[$chr]; |
163 | continue; |
164 | } |
165 | if ( $chr >= 'A' && $chr <= 'Z' ) { |
166 | $code = self::addToChr( $chr, '1D4C3' ); |
167 | $res .= '&#x' . $code . ';'; |
168 | } elseif ( $chr >= 'a' && $chr <= 'z' ) { |
169 | $code = self::addToChr( $chr, '1D4BD' ); |
170 | $res .= '&#x' . $code . ';'; |
171 | } else { |
172 | $res .= $chr; |
173 | } |
174 | } |
175 | return $res; |
176 | } |
177 | |
178 | private static function addToChr( $chr, $base ): string { |
179 | return strtoupper( dechex( mb_ord( $chr ) + hexdec( $base ) ) ); |
180 | } |
181 | |
182 | public static function matchAlphanumeric( $inputString, $map ) { |
183 | // Replace each character in the input string with its caligraphic Unicode equivalent |
184 | return preg_replace_callback( '/[A-Za-z0-9]/u', static function ( $matches ) use ( $map ) { |
185 | return $map[$matches[0]] ?? $matches[0]; |
186 | }, $inputString ); |
187 | } |
188 | |
189 | public static function getIntentContent( ?string $input ) { |
190 | if ( !$input ) { |
191 | return null; |
192 | } |
193 | $matchesInt = []; |
194 | $matchInt = preg_match( "/intent=[\'\"](.*)[\'\"]/", $input, $matchesInt ); |
195 | if ( $matchInt && count( $matchesInt ) >= 2 ) { |
196 | return $matchesInt[1]; |
197 | } |
198 | return null; |
199 | } |
200 | |
201 | public static function getIntentParams( ?string $intentContent ) { |
202 | if ( !$intentContent ) { |
203 | return null; |
204 | } |
205 | $matchesParams = []; |
206 | // tbd eventually not only alphanumerical chars valid in intent params |
207 | $matchParams = preg_match_all( "/\\\$([a-zA-Z]+)/", $intentContent, $matchesParams ); |
208 | if ( $matchParams && count( $matchesParams ) >= 2 ) { |
209 | return $matchesParams[1]; |
210 | } |
211 | return null; |
212 | } |
213 | |
214 | public static function getIntentArgs( ?string $input ) { |
215 | if ( !$input ) { |
216 | return null; |
217 | } |
218 | $matchesArgs = []; |
219 | $matchArg = preg_match( "/arg\s*=\s*[\'\"](.*?)[\'\"]/", $input, $matchesArgs ); |
220 | if ( $matchArg && count( $matchesArgs ) >= 2 ) { |
221 | return $matchesArgs[1]; |
222 | } |
223 | return null; |
224 | } |
225 | |
226 | /** |
227 | * Converts a rendered MathML string to a XML tree and adds the attributes from input |
228 | * to the top-level element.Valid attributes for adding are "arg" and "intent. |
229 | * It overwrites pre-existing attributes in the top-level element. |
230 | * TBD: currently contains a hacky way to remove xml header in the output string |
231 | * example:" <msup intent="_($op,_of,$arg)">" intent attributes comes from input variables |
232 | * @param string $renderedMML defines input MathML string |
233 | * @param array $intentContentAtr defines attributes to add |
234 | * @return string MML with added attributes |
235 | */ |
236 | public static function forgeIntentToTopElement( string $renderedMML, $intentContentAtr ) { |
237 | if ( !$intentContentAtr || !$renderedMML ) { |
238 | return $renderedMML; |
239 | } |
240 | |
241 | return self::addAttributesToMML( $renderedMML, $intentContentAtr, "" ); |
242 | } |
243 | |
244 | /** |
245 | * Add parameters from aattributes to the MML string |
246 | * @param string $renderedMML defines input MathML string |
247 | * @param array $intentContentAtr defines attributes to add |
248 | * @param string $elementTag element tag when using foundNodes |
249 | * @param bool $useFoundNodes use found nodes |
250 | * @return string MML with added attributes |
251 | */ |
252 | public static function addAttributesToMML( $renderedMML, $intentContentAtr, $elementTag, $useFoundNodes = false ) { |
253 | $xml = simplexml_load_string( $renderedMML ); |
254 | if ( !$xml ) { |
255 | return ""; |
256 | } |
257 | if ( $useFoundNodes ) { |
258 | $foundNodes = $xml->xpath( $elementTag ); |
259 | if ( !( $foundNodes !== null && count( $foundNodes ) >= 1 ) ) { |
260 | return $renderedMML; |
261 | } |
262 | } |
263 | |
264 | if ( isset( $intentContentAtr["intent"] ) ) { |
265 | if ( isset( $xml["intent"] ) ) { |
266 | $xml["intent"] = $intentContentAtr["intent"]; |
267 | } elseif ( $intentContentAtr["intent"] != null && is_string( $intentContentAtr["intent"] ) ) { |
268 | $xml->addAttribute( "intent", $intentContentAtr["intent"] ); |
269 | } |
270 | } |
271 | if ( isset( $intentContentAtr["arg"] ) ) { |
272 | if ( isset( $xml["arg"] ) ) { |
273 | $xml["arg"] = $intentContentAtr["arg"]; |
274 | } elseif ( $intentContentAtr["arg"] != null && is_string( $intentContentAtr["arg"] ) ) { |
275 | $xml->addAttribute( "arg", $intentContentAtr["arg"] ); |
276 | } |
277 | } |
278 | |
279 | $hackyXML = str_replace( "<?xml version=\"1.0\"?>", "", $xml->asXML() ); |
280 | return str_replace( "\n", "", $hackyXML ); |
281 | } |
282 | |
283 | public static function forgeIntentToSpecificElement( string $renderedMML, $intentContentAtr, string $elementTag ) { |
284 | if ( !$intentContentAtr || !$renderedMML || !$elementTag ) { |
285 | return $renderedMML; |
286 | } |
287 | return self::addAttributesToMML( $elementTag, $intentContentAtr, $elementTag, true ); |
288 | } |
289 | |
290 | } |