Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
14.71% covered (danger)
14.71%
10 / 68
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathLaTeXML
14.93% covered (danger)
14.93%
10 / 67
33.33% covered (danger)
33.33%
3 / 9
320.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 serializeSettings
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getLaTeXMLSettings
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 setLaTeXMLSettings
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPostData
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 doRender
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
42
 calculateSvg
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getSvg
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
20
 getMathTableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\Math;
4
5use MediaWiki\Extension\Math\Hooks\HookRunner;
6use MediaWiki\Logger\LoggerFactory;
7use MediaWiki\MediaWikiServices;
8use StatusValue;
9
10/**
11 * Contains the driver function for the LaTeXML daemon
12 *
13 * @copyright 2012 Moritz Schubotz
14 * @license GPL-2.0-or-later
15 */
16class MathLaTeXML extends MathMathML {
17
18    /** @var string[] */
19    protected $defaultAllowedRootElements = [ 'math', 'div', 'table', 'query' ];
20
21    /** @var string settings for LaTeXML daemon */
22    private $LaTeXMLSettings = '';
23
24    public function __construct( $tex = '', $params = [], $cache = null ) {
25        global $wgMathLaTeXMLUrl;
26        parent::__construct( $tex, $params, $cache );
27        $this->host = $wgMathLaTeXMLUrl;
28        $this->setMode( MathConfig::MODE_LATEXML );
29    }
30
31    /**
32     * Converts an array with LaTeXML settings to a URL encoded String.
33     * If the argument is a string the input will be returned.
34     * Thus the function has projector properties and can be applied a second time safely.
35     * @param string|array $array
36     * @return string
37     */
38    public function serializeSettings( $array ) {
39        if ( !is_array( $array ) ) {
40            return $array;
41        }
42
43        // removes the [1] [2]... for the unnamed subarrays since LaTeXML
44        // assigns multiple values to one key e.g.
45        // preload=amsmath.sty&preload=amsthm.sty&preload=amstext.sty
46        $cgi_string = wfArrayToCgi( $array );
47        $cgi_string = preg_replace( '|\%5B\d+\%5D|', '', $cgi_string );
48        $cgi_string = preg_replace( '|&\d+=|', '&', $cgi_string );
49
50        return $cgi_string;
51    }
52
53    /**
54     * Gets the settings for the LaTeXML daemon.
55     * @return string
56     */
57    public function getLaTeXMLSettings() {
58        global $wgMathDefaultLaTeXMLSetting;
59        return $this->LaTeXMLSettings ?: $wgMathDefaultLaTeXMLSetting;
60    }
61
62    /**
63     * Sets the settings for the LaTeXML daemon.
64     * The settings affect only the current instance of the class.
65     * For a list of possible settings see:
66     * http://dlmf.nist.gov/LaTeXML/manual/commands/latexmlpost.xhtml
67     * An empty value indicates to use the default settings.
68     * @param string|array $settings
69     */
70    public function setLaTeXMLSettings( $settings ) {
71        $this->LaTeXMLSettings = $settings;
72    }
73
74    /**
75     * Calculates the HTTP POST Data for the request. Depends on the settings
76     * and the input string only.
77     * @return string HTTP POST data
78     */
79    public function getPostData() {
80        $tex = $this->getTex();
81        if ( $this->getMathStyle() == 'inlineDisplaystyle' ) {
82            // In 'inlineDisplaystyle' the old
83            // texvc behavior is reproduced:
84            // The equation is rendered in displaystyle
85            // (texvc used $$ $tex $$ to render)
86            // but the equation is not centered.
87            $tex = '{\displaystyle ' . $tex . '}';
88        }
89        $texcmd = rawurlencode( $tex );
90        $settings = $this->serializeSettings( $this->getLaTeXMLSettings() );
91        $postData = $settings . '&tex=' . $texcmd;
92
93        // There is an API-inconsistency between different versions of the LaTeXML daemon
94        // some versions require the literal prefix other don't allow it.
95        if ( !strpos( $this->host, '/convert' ) ) {
96            $postData = preg_replace( '/&tex=/', '&tex=literal:', $postData, 1 );
97        }
98
99        LoggerFactory::getInstance( 'Math' )->debug( 'Get post data: ' . $postData );
100        return $postData;
101    }
102
103    /**
104     * Does the actual web request to convert TeX to MathML.
105     * @return StatusValue
106     */
107    protected function doRender(): StatusValue {
108        if ( trim( $this->getTex() ) === '' ) {
109            LoggerFactory::getInstance( 'Math' )->warning(
110                'Rendering was requested, but no TeX string is specified.' );
111            return StatusValue::newFatal( 'math_empty_tex' );
112        }
113        $requestStatus = $this->makeRequest();
114        if ( $requestStatus->isGood() ) {
115            $jsonResult = json_decode( $requestStatus->getValue() );
116            if ( $jsonResult && json_last_error() === JSON_ERROR_NONE ) {
117                if ( $this->isValidMathML( $jsonResult->result ) ) {
118                    $this->setMathml( $jsonResult->result );
119                    // Avoid PHP 7.1 warning from passing $this by reference
120                    $renderer = $this;
121                    ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
122                        ->onMathRenderingResultRetrieved(
123                            $renderer, $jsonResult
124                        ); // Enables debugging of server results
125                    return StatusValue::newGood();
126                }
127
128                // Do not print bad mathml. It's probably too verbose and might
129                // mess up the browser output.
130                LoggerFactory::getInstance( 'Math' )
131                    ->warning( 'LaTeXML invalid MathML', [
132                        'post' => $this->getPostData(),
133                        'host' => $this->host,
134                        'result' => $requestStatus->getValue()
135                    ] );
136                return StatusValue::newFatal( 'math_invalidxml', $this->getModeName(), $this->host );
137            }
138            LoggerFactory::getInstance( 'Math' )
139                ->warning( 'LaTeXML invalid JSON', [
140                    'post' => $this->getPostData(),
141                    'host' => $this->host,
142                    'res' => $requestStatus->getValue()
143                ] );
144
145            return StatusValue::newFatal( $this->getError( 'math_invalidjson', $this->getModeName(), $this->host ) );
146        } else {
147            return $requestStatus;
148        }
149    }
150
151    /**
152     * Calculates the SVG image based on the MathML input
153     * No cache is used.
154     * @return bool
155     */
156    public function calculateSvg() {
157        $renderer = new MathMathML( $this->getTex() );
158        $renderer->setMathml( $this->getMathml() );
159        $renderer->setMode( MathConfig::MODE_LATEXML );
160        $renderer->setPurge();
161        $res = $renderer->render();
162        if ( $res == true ) {
163            $this->setSvg( $renderer->getSvg() );
164        } else {
165            $lastError = $renderer->getLastError();
166            LoggerFactory::getInstance( 'Math' )->error(
167                'Failed to convert LaTeXML-MathML to SVG:' . $lastError );
168        }
169        return $res;
170    }
171
172    /**
173     * Gets the SVG image
174     *
175     * @param string $render if set to 'render' (default) and no SVG image exists, the function
176     *                       tries to generate it on the fly.
177     *                       Otherwise, if set to 'cached', and there is no SVG in the database
178     *                       cache, an empty string is returned.
179     *
180     * @return string XML-Document of the rendered SVG
181     */
182    public function getSvg( $render = 'render' ) {
183        if ( $render == 'render' && ( $this->isPurge() || $this->svg == '' ) ) {
184            $this->calculateSvg();
185        }
186        return parent::getSvg( $render );
187    }
188
189    /**
190     * @return string
191     */
192    protected function getMathTableName() {
193        return 'mathlatexml';
194    }
195}
196
197class_alias( MathLaTeXML::class, 'MathLaTeXML' );