Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 146
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMathDebug
0.00% covered (danger)
0.00%
0 / 146
0.00% covered (danger)
0.00%
0 / 12
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHeaders
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 displayButtons
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 compareParser
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
30
 testParser
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 generateParserTests
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 generateLaTeXMLOutput
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 render
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getMathTagsFromPage
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getTexvcTex
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Extension\Math\MathLaTeXML;
4use MediaWiki\Extension\Math\MathMathML;
5use MediaWiki\Extension\Math\MathRenderer;
6use MediaWiki\Logger\LoggerFactory;
7use Wikimedia\Diff\Diff;
8use Wikimedia\Diff\TableDiffFormatter;
9
10class SpecialMathDebug extends SpecialPage {
11
12    function __construct() {
13        parent::__construct( 'MathDebug' );
14    }
15
16    /**
17     * Sets headers - this should be called from the execute() method of all derived classes!
18     */
19    function setHeaders() {
20        $out = $this->getOutput();
21        $out->setArticleRelated( false );
22        $out->setRobotPolicy( "noindex,nofollow" );
23        $out->setPageTitle( $this->getDescription() );
24    }
25
26    /** @inheritDoc */
27    function execute( $par ) {
28        $offset = $this->getRequest()->getVal( 'offset', 0 );
29        $length = $this->getRequest()->getVal( 'length', 10 );
30        $page = $this->getRequest()->getVal( 'page', 'Testpage' );
31        $action = $this->getRequest()->getVal( 'action', 'show' );
32        $purge = $this->getRequest()->getVal( 'purge', '' );
33        if ( !$this->userCanExecute( $this->getUser() ) ) {
34            $this->displayRestrictionError();
35        } else {
36            if ( $action != 'generateParserTests' ) {
37                $this->setHeaders();
38                $this->displayButtons( $offset, $length, $page, $action, $purge );
39            }
40            switch ( $action ) {
41                case 'parserTest':
42                    $this->generateLaTeXMLOutput( $offset, $length, $page );
43                    break;
44                case 'parserDiff':
45                    $this->compareParser( $offset, $length, $page );
46                    break;
47                case 'generateParserTests':
48                    $this->generateParserTests( $offset, $length, $page );
49                    break;
50                default:
51                    $this->testParser( $offset, $length, $page, $purge === 'checked' );
52            }
53        }
54    }
55
56    function displayButtons(
57        $offset = 0, $length = 10, $page = 'Testpage', $action = 'show', $purge = ''
58    ) {
59        $out = $this->getOutput();
60        // TODO check if addHTML has to be sanitized
61        $out->addHTML( '<form method=\'get\'>'
62            . '<input value="Show :" type="submit">'
63            . ' <input name="length" size="3" value="'
64            . $length
65            . '" class="textfield"  onfocus="this.select()" type="text">'
66            . ' test(s) starting from test # <input name="offset" size="6" value="'
67            . ( $offset + $length )
68            . '" class="textfield" onfocus="this.select()" type="text"> for page'
69            . ' <input name="page" size="12" value="'
70            . $page
71            . '" class="textfield" onfocus="this.select()" type="text">'
72            . ' <input name="action" size="12" value="'
73            . $action
74            . '" class="textfield" onfocus="this.select()" type="text">'
75            . ' purge <input type="checkbox" name="purge" value="checked"'
76            . $purge
77            . '></form>'
78        );
79    }
80
81    public function compareParser( $offset = 0, $length = 10, $page = 'Testpage' ) {
82        // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionConfigUsage
83        global $wgMathLaTeXMLUrl;
84        $out = $this->getOutput();
85        if ( !$this->getConfig()->get( 'MathUseLaTeXML' ) ) {
86            $out->addWikiTextAsInterface( "MahtML support must be enabled." );
87            return false;
88        }
89        $parserA = $this->getRequest()->getVal( 'parserA', 'http://latexml.mathweb.org/convert' );
90        $parserB = $this->getRequest()->getVal( 'parserB', 'http://latexml-test.instance-proxy.wmflabs.org/' );
91        $formulae = self::getMathTagsFromPage( $page );
92        $i = 0;
93        $str_out = '';
94        $renderer = new MathLaTeXML();
95        $renderer->setPurge();
96        $diffFormatter = new TableDiffFormatter();
97        if ( count( $formulae ) ) {
98            foreach ( array_slice( $formulae, $offset, $length, true ) as $key => $formula ) {
99                $out->addWikiTextAsInterface( "=== Test #" . ( $offset + $i++ ) . "$key === " );
100                $renderer->setTex( $formula );
101                $wgMathLaTeXMLUrl = $parserA;
102                $stringA = $renderer->render( true );
103                $wgMathLaTeXMLUrl = $parserB;
104                $stringB = $renderer->render( true );
105                $diff = new Diff( [ $stringA ], [ $stringB ] );
106                if ( $diff->isEmpty() ) {
107                    $out->addWikiTextAsInterface( 'Output is identical' );
108                } else {
109                    $out->addWikiTextAsInterface( 'Request A <source lang="bash"> curl -d \'' .
110                        $renderer->getPostData() . '\' ' . $parserA . '</source>' );
111                    $out->addWikiTextAsInterface( 'Request B <source lang="bash"> curl -d \'' .
112                        $renderer->getPostData() . '\' ' . $parserB . '</source>' );
113                    $out->addWikiTextAsInterface(
114                        'Diff: <source lang="diff">' . $diffFormatter->format( $diff ) . '</source>'
115                    );
116                    $out->addWikiTextAsInterface( 'XML Element based:' );
117                    $XMLA = explode( '>', $stringA );
118                    $XMLB = explode( '>', $stringB );
119                    $diff = new Diff( $XMLA, $XMLB );
120                    $out->addWikiTextAsInterface(
121                        '<source lang="diff">' . $diffFormatter->format( $diff ) . '</source>'
122                    );
123                }
124                $i++;
125            }
126        } else {
127            $str_out = "No math elements found";
128        }
129        $out->addWikiTextAsInterface( $str_out );
130        return true;
131    }
132
133    public function testParser( $offset = 0, $length = 10, $page = 'Testpage', $purge = true ) {
134        $out = $this->getOutput();
135        $i = 0;
136        foreach (
137            array_slice( self::getMathTagsFromPage( $page ), $offset, $length, true ) as $key => $t
138        ) {
139            $out->addWikiTextAsInterface( "=== Test #" . ( $offset + $i++ ) . "$key === " );
140            $out->addHTML( self::render( $t, 'source', $purge ) );
141            $out->addWikiTextAsInterface(
142                'Texvc`s TeX output:<source lang="latex">' . $this->getTexvcTex( $t ) . '</source>'
143            );
144            if ( in_array( 'latexml', $this->getConfig()->get( 'MathMathValidModes' ) ) ) {
145                $out->addHTML( self::render( $t, 'latexml', $purge ) );
146            }
147        }
148    }
149
150    /**
151     * Generates test cases for texvcjs
152     *
153     * @param int $offset
154     * @param int $length
155     * @param string $page
156     * @param bool $purge
157     * @return bool
158     */
159    public function generateParserTests(
160        $offset = 0, $length = 10, $page = 'Testpage', $purge = true
161    ) {
162        $res = $this->getRequest()->response();
163        $res->header( 'Content-Type: application/json' );
164        $res->header( 'Content-Disposition: attachment;filename=ParserTest.json' );
165
166        $out = $this->getOutput();
167        $out->setArticleBodyOnly( true );
168        $parserTests = [];
169        foreach (
170            array_slice( self::getMathTagsFromPage( $page ), $offset, $length, true ) as $key => $input
171        ) {
172            $m = new MathMathML( $input );
173            $m->checkTeX();
174            $parserTests[] = [ 'id' => $key, 'input' => (string)$input, 'texvcjs' => $m->getTex() ];
175        }
176        $out->addHTML( json_encode( $parserTests ) );
177        return true;
178    }
179
180    function generateLaTeXMLOutput( $offset = 0, $length = 10, $page = 'Testpage' ) {
181        $out = $this->getOutput();
182        if ( !$this->getConfig()->get( 'MathUseLaTeXML' ) ) {
183            $out->addWikiTextAsInterface( "MahtML support must be enabled." );
184            return false;
185        }
186
187        $formulae = self::getMathTagsFromPage( $page );
188        $i = 0;
189        $renderer = new MathLaTeXML();
190        $renderer->setPurge();
191        $tstring = '';
192        if ( count( $formulae ) ) {
193            foreach ( array_slice( $formulae, $offset, $length, true ) as $key => $formula ) {
194                $tstring .= "\n!! test\n Test #" . ( $offset + $i++ ) . "$key \n!! input"
195                    . "\n<math>$formula</math>\n!! result\n";
196                $renderer->setTex( $formula );
197                $tstring .= $renderer->render( true );
198                $tstring .= "\n!! end\n";
199            }
200        } else {
201            $tstring = "No math elements found";
202        }
203        $out->addWikiTextAsInterface( '<source>' . $tstring . '<\source>' );
204        return true;
205    }
206
207    private static function render( $t, $mode, $purge = true ) {
208        $modeInt = (int)substr( $mode, 0, 1 );
209        $renderer = MathRenderer::getRenderer( $t, [], $modeInt );
210        $renderer->setPurge( $purge );
211        $renderer->render();
212        $fragment = $renderer->getHtmlOutput();
213        $res = $mode . ':' . $fragment;
214        LoggerFactory::getInstance( 'MathSearch' )->warning( 'rendered:' . $res . ' in mode ' . $mode );
215        return $res . '<br/>';
216    }
217
218    private static function getMathTagsFromPage( $titleString = 'Testpage' ) {
219        $title = Title::newFromText( $titleString );
220        if ( $title->exists() ) {
221            $idGenerator = MathIdGenerator::newFromTitle( $title );
222            $tags = $idGenerator->getMathTags();
223            $keys = $idGenerator->formatIds( $tags );
224            return array_combine( $keys, array_column( $tags, MathIdGenerator::CONTENT_POS ) );
225        } else {
226            return [];
227        }
228    }
229
230    private function getTexvcTex( $tex ) {
231        $renderer = MathRenderer::getRenderer( $tex, [], 'source' );
232        $renderer->checkTeX();
233        return $renderer->getTex();
234    }
235
236    protected function getGroupName() {
237        return 'mathsearch';
238    }
239}