LaTeXMLSettings ) { return $this->LaTeXMLSettings; } else { return $wgMathDefaultLaTeXMLSetting; } } /** * Sets the settings for the LaTeXML daemon. * The settings affect only the current instance of the class. * For a list of possible settings see: * http://dlmf.nist.gov/LaTeXML/manual/commands/latexmlpost.xhtml * An empty value indicates to use the default settings. * @param string|array $settings */ public function setLaTeXMLSettings( $settings ) { $this->LaTeXMLSettings = $settings; } /* (non-PHPdoc) * @see MathRenderer::render() */ public function render( $forceReRendering = false ) { wfProfileIn( __METHOD__ ); if ( $forceReRendering ) { $this->setPurge( true ); } if ( $this->renderingRequired() ) { $res = $this->doRender( ); if ( ! $res ) { wfProfileOut( __METHOD__ ); return $this->getLastError(); } } $result = $this->getMathMLTag(); wfProfileOut( __METHOD__ ); return $result; } /** * Helper function to checks if the math tag must be rendered. * @return boolean */ private function renderingRequired() { if ( $this->isPurge() ) { wfDebugLog( "Math", "Rerendering was requested." ); return true; } else { $dbres = $this->readFromDatabase(); if ( $dbres ) { if ( self::isValidMathML( $this->getMathml() ) ) { wfDebugLog( "Math", "Valid entry found in database." ); return false; } else { wfDebugLog( "Math", "Malformatted entry found in database" ); return true; } } else { wfDebugLog( "Math", "No entry found in database." ); return true; } } } /** * Performs a HTTP Post request to the given host. * Uses $wgMathLaTeXMLTimeout as timeout. * Generates error messages on failure * @see Http::post() * * @global type $wgMathLaTeXMLTimeout * @param string $host * @param string $post the encoded post request * @param mixed $res the result * @param mixed $error the formatted error message or null * @param String $httpRequestClass class name of MWHttpRequest (needed for testing only) * @return boolean success */ public function makeRequest( $host, $post, &$res, &$error = '', $httpRequestClass = 'MWHttpRequest' ) { global $wgMathLaTeXMLTimeout; wfProfileIn( __METHOD__ ); $error = ''; $res = null; $options = array( 'method' => 'POST', 'postData' => $post, 'timeout' => $wgMathLaTeXMLTimeout ); $req = $httpRequestClass::factory( $host, $options ); $status = $req->execute(); if ( $status->isGood() ) { $res = $req->getContent(); wfProfileOut( __METHOD__ ); return true; } else { if ( $status->hasMessage( 'http-timed-out' ) ) { $error = $this->getError( 'math_latexml_timeout', $host ); $res = false; wfDebugLog( "Math", "\nLaTeXML Timeout:" . var_export( array( 'post' => $post, 'host' => $host , 'timeout' => $wgMathLaTeXMLTimeout ), true ) . "\n\n" ); } else { // for any other unkonwn http error $errormsg = $status->getHtml(); $error = $this->getError( 'math_latexml_invalidresponse', $host, $errormsg ); wfDebugLog( "Math", "\nLaTeXML NoResponse:" . var_export( array( 'post' => $post, 'host' => $host , 'errormsg' => $errormsg ), true ) . "\n\n" ); } wfProfileOut( __METHOD__ ); return false; } } /* (non-PHPdoc) * @see MathRenderer::writeCache() */ public function writeCache() { if ( $this->isChanged() ) { $this->writeToDatabase(); } } /** * Picks a LaTeXML daemon. * If more than one demon are availible one is chosen from the * $wgMathLaTeXMLUrl array. * @return string */ private static function pickHost() { global $wgMathLaTeXMLUrl; if ( is_array( $wgMathLaTeXMLUrl ) ) { $host = array_rand( $wgMathLaTeXMLUrl ); } else { $host = $wgMathLaTeXMLUrl; } wfDebugLog( "Math", "picking host " . $host ); return $host; } /** * Calculates the HTTP POST Data for the request. Depends on the settings * and the input string only. * @return string HTTP POST data */ public function getPostData() { $texcmd = urlencode( $this->tex ); $settings = $this->serializeSettings($this->getLaTeXMLSettings()); return $settings. '&tex=' . $texcmd; } /** * Does the actual web request to convert TeX to MathML. * @return boolean */ private function doRender( ) { wfProfileIn( __METHOD__ ); $host = self::pickHost(); $post = $this->getPostData(); $this->lastError = ''; if ( $this->makeRequest( $host, $post, $res, $this->lastError ) ) { $result = json_decode( $res ); if ( json_last_error() === JSON_ERROR_NONE ) { if ( self::isValidMathML( $result->result ) ) { $this->setMathml( $result->result ); wfProfileOut( __METHOD__ ); return true; } else { // Do not print bad mathml. It's probably too verbose and might // mess up the browser output. $this->lastError = $this->getError( 'math_latexml_invalidxml', $host ); wfDebugLog( "Math", "\nLaTeXML InvalidMathML:" . var_export( array( 'post' => $post, 'host' => $host , 'result' => $result ), true ) . "\n\n" ); wfProfileOut( __METHOD__ ); return false; } } else { $this->lastError = $this->getError( 'math_latexml_invalidjson', $host ); wfDebugLog( "Math", "\nLaTeXML InvalidJSON:" . var_export( array( 'post' => $post, 'host' => $host , 'res' => $res ), true ) . "\n\n" ); wfProfileOut( __METHOD__ ); return false; } } else { // Error message has already been set. wfProfileOut( __METHOD__ ); return false; } } /** * Checks if the input is valid MathML, * and if the root element has the name math * @param string $XML * @return boolean */ static public function isValidMathML( $XML ) { $out = false; // depends on https://gerrit.wikimedia.org/r/#/c/66365/ if ( ! is_callable( 'XmlTypeCheck::newFromString' ) ) { $msg = wfMessage( 'math_latexml_xmlversion' )->inContentLanguage()->escaped(); trigger_error( $msg, E_USER_NOTICE ); wfDebugLog( 'Math', $msg ); return true; } $xmlObject = new XmlTypeCheck( $XML, null, false ); if ( ! $xmlObject->wellFormed ) { wfDebugLog( "Math", "XML validation error:\n " . var_export( $XML, true ) . "\n" ); } else { $name = $xmlObject->getRootElement(); $name = str_replace( 'http://www.w3.org/1998/Math/MathML:', '', $name ); if ( $name == "math" or $name == "table" or $name == "div" ) { $out = true; } else { wfDebugLog( "Math", "got wrong root element " . $name ); } } return $out; } /** * Internal version of @link self::embedMathML * @return string * @return html element with rendered math */ private function getMathMLTag() { return self::embedMathML( $this->getMathml(), urldecode( $this->getTex() ) ); } /** * Embeds the MathML-XML element in a HTML span element with class tex * @param string $mml: the MathML string * @param string $tagId: optional tagID for references like (pagename#equation2) * @return html element with rendered math */ public static function embedMathML( $mml, $tagId = '', $attribs = false ) { $mml = str_replace( "\n", " ", $mml ); if ( ! $attribs ) { $attribs = array( 'class' => 'tex', 'dir' => 'ltr' ); if ( $tagId ) { $attribs['id'] = $tagId; } $attribs = Sanitizer::validateTagAttributes( $attribs, 'span' ); } return Xml::tags( 'span', $attribs, $mml ); } }