Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 217 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
SpecialLaTeXTranslator | |
0.00% |
0 / 217 |
|
0.00% |
0 / 15 |
1722 | |
0.00% |
0 / 1 |
log | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
30 | |||
processInput | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getTranslations | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
calculateTranslations | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
getDependencyGraphFromContext | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
calculateDependencyGraphFromContext | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
6 | |||
printSource | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
displayResults | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
20 | |||
printList | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
printColHeader | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
printColFooter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
displayTests | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | |
3 | use MediaWiki\Logger\LegacyLogger; |
4 | use MediaWiki\Logger\LoggerFactory; |
5 | use MediaWiki\MediaWikiServices; |
6 | use MediaWiki\Revision\RevisionRecord; |
7 | use MediaWiki\Revision\SlotRecord; |
8 | use Psr\Log\LogLevel; |
9 | |
10 | class SpecialLaTeXTranslator extends SpecialPage { |
11 | |
12 | private const VERSION = '1.0.0'; |
13 | |
14 | private $cache; |
15 | private $dgUrl; |
16 | private $compUrl; |
17 | private $httpFactory; |
18 | private $logger; |
19 | private $context; |
20 | private $tex; |
21 | /** @var bool */ |
22 | private $purge; |
23 | /** |
24 | * @var false|mixed|string |
25 | */ |
26 | private $dependencyGraph; |
27 | |
28 | private function log( $level, $message, array $context = [] ) { |
29 | $this->logger->log( $level, $message, $context ); |
30 | if ( $this->getConfig()->get( 'ShowDebug' ) ) { |
31 | $msg = LegacyLogger::interpolate( $message, $context ); |
32 | $this->getOutput()->addWikiTextAsContent( "Log $level:" . $msg ); |
33 | } |
34 | } |
35 | |
36 | function __construct() { |
37 | parent::__construct( 'LaTeXTranslator' ); |
38 | $mw = MediaWikiServices::getInstance(); |
39 | $this->cache = $mw->getMainWANObjectCache(); |
40 | // provisional Hack to get the URL |
41 | $provisionalUrl = $mw->getMainConfig()->get( 'MathSearchTranslationUrl' ); |
42 | $this->dgUrl = |
43 | preg_replace( '/translation/', 'generateAnnotatedDependencyGraph', $provisionalUrl ); |
44 | $this->compUrl = |
45 | preg_replace( '/translation/', 'generateTranslatedComputedMoi', $provisionalUrl ); |
46 | $this->httpFactory = $mw->getHttpRequestFactory(); |
47 | $this->logger = LoggerFactory::getInstance( 'MathSearch' ); |
48 | } |
49 | |
50 | /** |
51 | * Returns corresponding Mathematica translations of LaTeX functions |
52 | * @param string|null $par |
53 | */ |
54 | function execute( $par ) { |
55 | $pid = $this->getRequest()->getVal( 'pid' ); // Page ID |
56 | $eid = $this->getRequest()->getVal( 'eid' ); // Equation ID |
57 | $this->setHeaders(); |
58 | $output = $this->getOutput(); |
59 | $output->addWikiMsg( 'math-tex2nb-intro' ); |
60 | if ( $pid && $eid ) { |
61 | $revisionRecord = |
62 | MediaWikiServices::getInstance()->getRevisionLookup()->getRevisionById( $pid ); |
63 | $contentModel = |
64 | $revisionRecord->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel(); |
65 | if ( $contentModel !== CONTENT_MODEL_WIKITEXT ) { |
66 | throw new RuntimeException( "Only CONTENT_MODEL_WIKITEXT supported for translation." ); |
67 | } |
68 | |
69 | $content = $revisionRecord->getContent( SlotRecord::MAIN ); |
70 | if ( !$content instanceof TextContent ) { |
71 | throw new RuntimeException( "Translation supports only TextContent" ); |
72 | } |
73 | $this->context = $content->getText(); |
74 | $mo = MathObject::newFromRevisionText( $pid, $eid ); |
75 | $this->tex = $mo->getTex(); |
76 | $this->displayResults(); |
77 | } else { |
78 | $this->tex = '(z)_n = \frac{\Gamma(z+n)}{\Gamma(z)}'; |
79 | $this->context = 'The Gamma function |
80 | <math>\Gamma(z)</math> |
81 | and the Pochhammer symbol |
82 | <math>(a)_n</math> |
83 | are often used together.'; |
84 | } |
85 | $formDescriptor = [ |
86 | 'input' => [ |
87 | 'label-message' => 'math-tex2nb-input', |
88 | 'class' => 'HTMLTextField', |
89 | 'default' => $this->tex, |
90 | ], |
91 | 'wikitext' => [ |
92 | 'label-message' => 'math-tex2nb-wikitext', |
93 | 'class' => 'HTMLTextAreaField', |
94 | 'default' => $this->context, |
95 | ], |
96 | 'purge' => [ |
97 | 'label-message' => 'math-tex2nb-purge', |
98 | 'class' => 'HTMLCheckField', |
99 | ], |
100 | ]; |
101 | $htmlForm = new HTMLForm( $formDescriptor, $this->getContext() ); |
102 | $htmlForm->setSubmitText( 'Translate' ); |
103 | $htmlForm->setSubmitCallback( [ $this, 'processInput' ] ); |
104 | $htmlForm->setHeaderText( '<h2>' . $this->msg( 'math-tex2nb-header' )->escaped() . |
105 | '</h2>' ); |
106 | $htmlForm->show(); |
107 | } |
108 | |
109 | /** |
110 | * Processes the submitted Form input |
111 | * @param array $formData |
112 | * @return bool |
113 | */ |
114 | public function processInput( $formData ) { |
115 | $this->tex = $formData['input']; |
116 | $this->context = $formData['wikitext']; |
117 | $this->purge = $formData['purge']; |
118 | |
119 | return $this->displayResults(); |
120 | } |
121 | |
122 | /** |
123 | * @return false|mixed |
124 | */ |
125 | private function getTranslations(): string { |
126 | $hash = |
127 | $this->cache->makeGlobalKey( self::class, |
128 | sha1( self::VERSION . '-F-' . $this->dependencyGraph . $this->tex ) ); |
129 | if ( $this->purge ) { |
130 | $value = $this->calculateTranslations(); |
131 | $this->cache->set( $hash, $value ); |
132 | } |
133 | |
134 | return $this->cache->getWithSetCallback( $hash, WANObjectCache::TTL_INDEFINITE, |
135 | [ $this, 'calculateTranslations' ] ); |
136 | } |
137 | |
138 | /** |
139 | * @return string |
140 | */ |
141 | public function calculateTranslations(): string { |
142 | $this->log( LogLevel::INFO, "Cache miss. Calculate translation." ); |
143 | $q = rawurlencode( $this->tex ); |
144 | $url = "{$this->compUrl}?latex=$q"; |
145 | $options = [ |
146 | 'method' => 'POST', |
147 | 'postData' => $this->dependencyGraph, |
148 | ]; |
149 | $req = $this->httpFactory->create( $url, $options, __METHOD__ ); |
150 | $req->setHeader( 'Content-Type', 'application/json' ); |
151 | $req->execute(); |
152 | $statusCode = $req->getStatus(); |
153 | if ( $statusCode === 200 ) { |
154 | return $req->getContent(); |
155 | } |
156 | $e = new RuntimeException( 'Calculation endpoint failed. Error:' . $req->getContent() ); |
157 | $this->logger->error( 'Calculation "{url}" returned ' . |
158 | 'HTTP status code "{statusCode}" for post data "{postData}".: {exception}.', [ |
159 | 'url' => $url, |
160 | 'statusCode' => $statusCode, |
161 | 'postData' => $this->dependencyGraph, |
162 | 'exception' => $e, |
163 | ] ); |
164 | throw $e; |
165 | } |
166 | |
167 | private function getDependencyGraphFromContext(): string { |
168 | $hash = |
169 | $this->cache->makeGlobalKey( self::class, |
170 | sha1( self::VERSION . '-DG-' . $this->context ) ); |
171 | $this->log( LogLevel::DEBUG, "DG Hash is {hash}", [ 'hash' => $hash ] ); |
172 | if ( $this->purge ) { |
173 | $this->log( LogLevel::INFO, 'Cache purging requested' ); |
174 | $value = $this->calculateDependencyGraphFromContext(); |
175 | $this->cache->set( $hash, $value ); |
176 | } |
177 | |
178 | return $this->cache->getWithSetCallback( $hash, 31556952, |
179 | [ $this, 'calculateDependencyGraphFromContext' ] ); |
180 | } |
181 | |
182 | /** |
183 | * @return false|mixed |
184 | */ |
185 | public function calculateDependencyGraphFromContext(): string { |
186 | $this->log( LogLevel::INFO, "Cache miss. Calculate dependency graph." ); |
187 | $url = $this->dgUrl; |
188 | $q = rawurlencode( $this->context ); |
189 | $postData = "content=$q"; |
190 | $options = [ |
191 | 'method' => 'POST', |
192 | 'postData' => $postData, |
193 | 'timeout' => 240, |
194 | ]; |
195 | $req = $this->httpFactory->create( $url, $options, __METHOD__ ); |
196 | $req->execute(); |
197 | $statusCode = $req->getStatus(); |
198 | if ( $statusCode === 200 ) { |
199 | return $req->getContent(); |
200 | } |
201 | $e = new RuntimeException( 'Dependency graph endpoint failed.' ); |
202 | $this->log( LogLevel::ERROR, 'Dependency graph "{url}" returned ' . |
203 | 'HTTP status code "{statusCode}" for post data "{postData}": {exception}.', [ |
204 | 'url' => $url, |
205 | 'statusCode' => $statusCode, |
206 | 'postData' => $postData, |
207 | 'exception' => $e, |
208 | ] ); |
209 | throw $e; |
210 | } |
211 | |
212 | private function printSource( |
213 | $source, $description = "", $language = "text", $linestart = true, $collapsible = true |
214 | ) { |
215 | $inline = ' inline '; |
216 | $out = $this->getOutput(); |
217 | if ( $description ) { |
218 | $description .= ": "; |
219 | } |
220 | if ( $collapsible ) { |
221 | $this->printColHeader( $description ); |
222 | $description = ''; |
223 | $inline = ''; |
224 | } |
225 | $out->addWikiTextAsInterface( "$description<syntaxhighlight lang=\"$language\" $inline>" . |
226 | $source . |
227 | '</syntaxhighlight>', $linestart ); |
228 | if ( $collapsible ) { |
229 | $this->printColFooter(); |
230 | } |
231 | } |
232 | |
233 | /** |
234 | * @return bool |
235 | */ |
236 | public function displayResults(): bool { |
237 | $output = $this->getOutput(); |
238 | $output->addWikiMsg( 'math-tex2nb-latex' ); |
239 | $this->printSource( $this->tex, '', 'latex', false, false ); |
240 | $output->addWikiTextAsContent( "<math>$this->tex</math>", false ); |
241 | $output->addWikiMsg( 'math-tex2nb-mathematica' ); |
242 | try { |
243 | $this->dependencyGraph = $this->getDependencyGraphFromContext(); |
244 | $calulation = $this->getTranslations(); |
245 | } catch ( Exception $exception ) { |
246 | $expected_error = |
247 | 'The given context (dependency graph) did not contain sufficient information'; |
248 | if ( strpos( $exception->getText(), $expected_error ) !== false ) { |
249 | FormulaInfo::DisplayTranslations( $this->tex ); |
250 | |
251 | return false; |
252 | } else { |
253 | $output->addWikiTextAsContent( "Could not consider context: {$exception->getMessage()}" ); |
254 | FormulaInfo::DisplayTranslations( $this->tex ); |
255 | |
256 | return false; |
257 | } |
258 | } |
259 | $insights = json_decode( $calulation ); |
260 | $this->printSource( $insights->semanticFormula, "Semantic latex", 'latex', false, false ); |
261 | $output->addWikiTextAsContent( "Confidence: " . $insights->confidence, false ); |
262 | |
263 | foreach ( $insights->translations as $key => $value ) { |
264 | $output->addWikiTextAsContent( "=== $key ===" ); |
265 | $this->printSource( $value->translation, "Translation", '$key', false, false ); |
266 | $output->addWikiTextAsContent( "==== Information ====" ); |
267 | $info = $value->translationInformation; |
268 | $this->printList( $info->subEquations, "Sub Equations" ); |
269 | $this->printList( $info->freeVariables, "Free variables" ); |
270 | $this->printList( $info->constraints, "Constraints" ); |
271 | $this->printList( $info->tokenTranslations, "Symbol info" ); |
272 | |
273 | // $this->printList($value); |
274 | $output->addWikiTextAsContent( "==== Tests ====" ); |
275 | $output->addWikiTextAsContent( "===== Symbolic =====" ); |
276 | |
277 | $this->displayTests( $value->symbolicResults->testCalculationsGroup ); |
278 | $output->addWikiTextAsContent( "===== Numeric =====" ); |
279 | |
280 | $this->displayTests( $value->numericResults->testCalculationsGroups ); |
281 | } |
282 | |
283 | $output->addWikiTextAsContent( "=== Dependency Graph Information ===" ); |
284 | $mathprint = static function ( $x ) { |
285 | return "* <math>$x</math>"; |
286 | }; |
287 | $this->printList( $insights->includes, "Includes", $mathprint ); |
288 | $this->printList( $insights->isPartOf, "Is part of", $mathprint ); |
289 | $this->printList( $insights->definiens, 'Description', |
290 | static function ( $x ) { return "* {$x->definition}"; |
291 | } ); |
292 | $this->printSource( $calulation, 'Complete translation information', 'json' ); |
293 | return false; |
294 | } |
295 | |
296 | private function printList( $list, $description, $callable = false ): void { |
297 | if ( !$list || empty( $list ) ) { |
298 | return; |
299 | } |
300 | $output = $this->getOutput(); |
301 | $this->printColHeader( $description ); |
302 | foreach ( $list as $key => $value ) { |
303 | if ( $callable === false ) { |
304 | $callable = static function ( $x, $y ) { return "* $x"; |
305 | }; |
306 | } |
307 | $value = $callable( $value, $key ); |
308 | $output->addWikiTextAsContent( $value ); |
309 | } |
310 | $this->printColFooter(); |
311 | } |
312 | |
313 | protected function getGroupName() { |
314 | return 'mathsearch'; |
315 | } |
316 | |
317 | private function printColHeader( string $description ): void { |
318 | $out = $this->getOutput(); |
319 | $out->addHTML( '<div class="toccolours mw-collapsible mw-collapsed" style="text-align: left">' ); |
320 | $out->addWikiTextAsContent( $description ); |
321 | $out->addHTML( '<div class="mw-collapsible-content">' ); |
322 | } |
323 | |
324 | private function printColFooter(): void { |
325 | $this->getOutput()->addHTML( '</div></div>' ); |
326 | } |
327 | |
328 | private function displayTests( $group ) { |
329 | if ( !is_array( $group ) ) { |
330 | return; |
331 | } |
332 | foreach ( $group as $testGroup ) { |
333 | if ( !$testGroup->testExpression ) { |
334 | continue; |
335 | } |
336 | $this->printSource( $testGroup->testExpression, "Test expression", "text", |
337 | true, false ); |
338 | foreach ( $testGroup->testCalculations as $calculation ) { |
339 | $calcRes = json_encode( $calculation, JSON_PRETTY_PRINT ); |
340 | $description = $calculation->result; |
341 | if ( isset( $calculation->testExpression ) ) { |
342 | $description .= ": " . $calculation->testExpression; |
343 | } |
344 | if ( isset( $calculation->testValues ) ) { |
345 | $description .= ":"; |
346 | foreach ( $calculation->testValues as $k => $v ) { |
347 | $description .= " $k = $v, "; |
348 | } |
349 | } |
350 | $this->printSource( $calcRes, $description, 'json' ); |
351 | } |
352 | } |
353 | } |
354 | } |