Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.18% covered (danger)
18.18%
10 / 55
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathLaTeXML
18.52% covered (danger)
18.52%
10 / 54
37.50% covered (danger)
37.50%
3 / 8
173.34
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
 getHtmlOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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    /** @inheritDoc */
25    public function __construct( $tex = '', $params = [], $cache = null ) {
26        global $wgMathLaTeXMLUrl;
27        parent::__construct( $tex, $params, $cache );
28        $this->host = $wgMathLaTeXMLUrl;
29        $this->setMode( MathConfig::MODE_LATEXML );
30    }
31
32    /**
33     * Converts an array with LaTeXML settings to a URL encoded String.
34     * If the argument is a string the input will be returned.
35     * Thus the function has projector properties and can be applied a second time safely.
36     * @param string|array $array
37     * @return string
38     */
39    public function serializeSettings( $array ) {
40        if ( !is_array( $array ) ) {
41            return $array;
42        }
43
44        // removes the [1] [2]... for the unnamed subarrays since LaTeXML
45        // assigns multiple values to one key e.g.
46        // preload=amsmath.sty&preload=amsthm.sty&preload=amstext.sty
47        $cgi_string = wfArrayToCgi( $array );
48        $cgi_string = preg_replace( '|\%5B\d+\%5D|', '', $cgi_string );
49        $cgi_string = preg_replace( '|&\d+=|', '&', $cgi_string );
50
51        return $cgi_string;
52    }
53
54    /**
55     * Gets the settings for the LaTeXML daemon.
56     * @return string
57     */
58    public function getLaTeXMLSettings() {
59        global $wgMathDefaultLaTeXMLSetting;
60        return $this->LaTeXMLSettings ?: $wgMathDefaultLaTeXMLSetting;
61    }
62
63    /**
64     * Sets the settings for the LaTeXML daemon.
65     * The settings affect only the current instance of the class.
66     * For a list of possible settings see:
67     * http://dlmf.nist.gov/LaTeXML/manual/commands/latexmlpost.xhtml
68     * An empty value indicates to use the default settings.
69     * @param string|array $settings
70     */
71    public function setLaTeXMLSettings( $settings ) {
72        $this->LaTeXMLSettings = $settings;
73    }
74
75    /**
76     * Calculates the HTTP POST Data for the request. Depends on the settings
77     * and the input string only.
78     * @return string HTTP POST data
79     */
80    public function getPostData() {
81        $tex = $this->getTex();
82        if ( $this->getMathStyle() == 'inlineDisplaystyle' ) {
83            // In 'inlineDisplaystyle' the old
84            // texvc behavior is reproduced:
85            // The equation is rendered in displaystyle
86            // (texvc used $$ $tex $$ to render)
87            // but the equation is not centered.
88            $tex = '{\displaystyle ' . $tex . '}';
89        }
90        $texcmd = rawurlencode( $tex );
91        $settings = $this->serializeSettings( $this->getLaTeXMLSettings() );
92        $postData = $settings . '&tex=' . $texcmd;
93
94        // There is an API-inconsistency between different versions of the LaTeXML daemon
95        // some versions require the literal prefix other don't allow it.
96        if ( !str_contains( $this->host, '/convert' ) ) {
97            $postData = preg_replace( '/&tex=/', '&tex=literal:', $postData, 1 );
98        }
99
100        LoggerFactory::getInstance( 'Math' )->debug( 'Get post data: ' . $postData );
101        return $postData;
102    }
103
104    /**
105     * Does the actual web request to convert TeX to MathML.
106     * @return StatusValue
107     */
108    protected function doRender(): StatusValue {
109        if ( trim( $this->getTex() ) === '' ) {
110            LoggerFactory::getInstance( 'Math' )->warning(
111                'Rendering was requested, but no TeX string is specified.' );
112            return StatusValue::newFatal( 'math_empty_tex' );
113        }
114        $requestStatus = $this->makeRequest();
115        if ( $requestStatus->isGood() ) {
116            $jsonResult = json_decode( $requestStatus->getValue() );
117            if ( $jsonResult && json_last_error() === JSON_ERROR_NONE ) {
118                if ( $this->isValidMathML( $jsonResult->result ) ) {
119                    $this->setMathml( $jsonResult->result );
120                    // Avoid PHP 7.1 warning from passing $this by reference
121                    $renderer = $this;
122                    ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
123                        ->onMathRenderingResultRetrieved(
124                            $renderer, $jsonResult
125                        ); // Enables debugging of server results
126                    return StatusValue::newGood();
127                }
128
129                // Do not print bad mathml. It's probably too verbose and might
130                // mess up the browser output.
131                LoggerFactory::getInstance( 'Math' )
132                    ->warning( 'LaTeXML invalid MathML', [
133                        'post' => $this->getPostData(),
134                        'host' => $this->host,
135                        'result' => $requestStatus->getValue()
136                    ] );
137                return StatusValue::newFatal( 'math_invalidxml', $this->getModeName(), $this->host );
138            }
139            LoggerFactory::getInstance( 'Math' )
140                ->warning( 'LaTeXML invalid JSON', [
141                    'post' => $this->getPostData(),
142                    'host' => $this->host,
143                    'res' => $requestStatus->getValue()
144                ] );
145
146            return StatusValue::newFatal( $this->getError( 'math_invalidjson', $this->getModeName(), $this->host ) );
147        } else {
148            return $requestStatus;
149        }
150    }
151
152    public function getHtmlOutput( bool $svg = true ): string {
153        return parent::getHtmlOutput( false );
154    }
155
156    /**
157     * @return string
158     */
159    protected function getMathTableName() {
160        return 'mathlatexml';
161    }
162}
163
164class_alias( MathLaTeXML::class, 'MathLaTeXML' );