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