diff --git a/Math.hooks.php b/Math.hooks.php index bd2d07b..3a76d8a 100644 --- a/Math.hooks.php +++ b/Math.hooks.php @@ -9,6 +9,7 @@ use MediaWiki\Logger\LoggerFactory; class MathHooks { + private static $tags = array(); const MATHCACHEKEY = 'math='; public static function mathConstantToString( $value, array $defs, $prefix, $default ) { @@ -179,22 +180,40 @@ class MathHooks { * @return array */ static function mathTagHook( $content, $attributes, $parser ) { - + static $n = 1; if ( trim( $content ) === '' ) { // bug 8372 return ''; } + $marker = Parser::MARKER_PREFIX . + '-postMath-' . sprintf( '%08X', $n ++ ) . + Parser::MARKER_SUFFIX; $mode = self::mathModeToString( $parser->getUser()->getOption( 'math' ) ); // Indicate that this page uses math. // This affects the page caching behavior. - if ( is_callable( 'ParserOptions::getMath' ) ) { - $parser->getOptions()->getMath(); - } else { - $parser->getOptions()->optionUsed( 'math' ); - } - + $parser->getOptions()->optionUsed( 'math' ); $renderer = MathRenderer::getRenderer( $content, $attributes, $mode ); + self::$tags[$marker] = array( $renderer, $parser ); + $parser->getOutput()->addModuleStyles( array( 'ext.math.styles' ) ); + if ( $mode == 'mathml' ) { + $parser->getOutput()->addModuleStyles( array( 'ext.math.desktop.styles' ) ); + $parser->getOutput()->addModules( array( 'ext.math.scripts' ) ); + } + return $marker; + + } + + /** + * Callback function for the parser hook. + * + * @param Parser $parser + * @param MathRenderer $renderer + * @return array + * @throws FatalError + * @throws MWException + */ + private static function mathPostTagHook( $renderer, $parser ) { $checkResult = $renderer->checkTeX(); if ( $checkResult !== true ) { @@ -212,15 +231,11 @@ class MathHooks { } Hooks::run( 'MathFormulaPostRender', array( $parser, &$renderer, &$renderedMath ) );// Enables indexing of math formula - $parser->getOutput()->addModuleStyles( array( 'ext.math.styles' ) ); - if ( $mode == 'mathml' ) { - $parser->getOutput()->addModuleStyles( array( 'ext.math.desktop.styles' ) ); - $parser->getOutput()->addModules( array( 'ext.math.scripts' ) ); - } + // Writes cache if rendering was successful $renderer->writeCache(); - return array( $renderedMath, "markerType" => 'nowiki' ); + return $renderedMath; } /** @@ -353,6 +368,29 @@ class MathHooks { return true; } + /** + * @param Parser $parser + * @param $text + * @return bool + */ + public static function onParserBeforeTidy( &$parser, &$text ) { + $rbis = array(); + foreach ( self::$tags as $key => $tag ){ + /** @var MathRenderer $renderer */ + $renderer = $tag[0]; + $rbi = new MathRestbaseInterface( $renderer->getTex(), $renderer->getInputType() ); + $renderer->setRestbaseInterface( $rbi ); + $rbis[] = $rbi; + } + MathRestbaseInterface::batchEvaluate( $rbis ); + foreach ( self::$tags as $key => $tag ){ + $value = call_user_func_array( array( "MathHooks","mathPostTagHook" ), $tag ); + $text = str_replace( $key, $value, $text ); + } + // This hook might be called multiple times. However one the tags are rendered the job is done. + self::$tags = array(); + return true; + } /** * * @global type $wgOut diff --git a/MathInputCheckRestbase.php b/MathInputCheckRestbase.php index 8d21b63..d73d8d5 100644 --- a/MathInputCheckRestbase.php +++ b/MathInputCheckRestbase.php @@ -17,10 +17,16 @@ class MathInputCheckRestbase extends MathInputCheck { * (performs no checking) * @param string $tex the TeX input string to be checked * @param string $type + * @param MathRestbaseInterface $ref */ - public function __construct( $tex = '', $type = 'tex' ) { + public function __construct( $tex = '', $type = 'tex', &$ref = null ) { parent::__construct( $tex ); - $this->restbaseInterface = new MathRestbaseInterface( $tex, $type ); + if ( $ref ) { + $this->restbaseInterface = $ref; + } else { + $this->restbaseInterface = new MathRestbaseInterface( $tex, $type ); + $ref = $this->restbaseInterface; + } } /** @@ -56,7 +62,7 @@ class MathInputCheckRestbase extends MathInputCheck { * @return boolean */ public function isValid() { - return $this->restbaseInterface->checkTeX(); + return $this->restbaseInterface->getSuccess(); } /** diff --git a/MathMathML.php b/MathMathML.php index 27acbf0..f669503 100644 --- a/MathMathML.php +++ b/MathMathML.php @@ -21,6 +21,7 @@ class MathMathML extends MathRenderer { /** @var boolean if false MathML output is not validated */ private $XMLValidation = true; private $svgPath = false; + private $mathoidStyle; public function __construct( $tex = '', $params = array() ) { global $wgMathMathMLUrl; @@ -79,14 +80,13 @@ class MathMathML extends MathRenderer { */ public function render( $forceReRendering = false ) { if ( in_array( $this->inputType, $this->restbaseInputTypes ) && $this->mode == 'mathml' ) { - $rbi = $this->rbi; - if ( !$rbi ){ - $rbi = new MathRestbaseInterface( $this->getTex(), $this->getInputType() ); - $rbi->checkTeX(); + if ( !$this->rbi ){ + $this->rbi = new MathRestbaseInterface( $this->getTex(), $this->getInputType() ); } + $rbi = $this->rbi; if ( $rbi->getSuccess() ) { $this->mathml = $rbi->getMathML(); - $this->svg = $rbi->getSvg(); + $this->mathoidStyle = $rbi->getMathoidStyle(); $this->svgPath = $rbi->getFullSvgUrl(); } $this->changed = false; @@ -378,11 +378,13 @@ class MathMathML extends MathRenderer { } else { $class = $classOverride; } - + if ( ! $this->mathoidStyle ) { + $this->correctSvgStyle( $this->getSvg(), $this->mathoidStyle ); + } // TODO: move the common styles to the global stylesheet! $style = 'background-image: url(\''. $url . - '\'); background-repeat: no-repeat; background-size: 100% 100%;'; - $this->correctSvgStyle( $this->getSvg(), $style ); + '\'); background-repeat: no-repeat; background-size: 100% 100%; '. + $this->mathoidStyle; if ( $class ) { $attribs['class'] = $class; } diff --git a/MathRenderer.php b/MathRenderer.php index 4da750d..8781b86 100644 --- a/MathRenderer.php +++ b/MathRenderer.php @@ -380,6 +380,13 @@ abstract class MathRenderer { return $out; } + /** + * @param MathRestbaseInterface $param + */ + public function setRestbaseInterface( $param ) { + $this->rbi = $param; + } + /** * Returns sanitized attributes * @@ -587,12 +594,11 @@ abstract class MathRenderer { return true; } } - $checker = new MathInputCheckRestbase( $this->tex, $this->getInputType() ); + $checker = new MathInputCheckRestbase( $this->tex, $this->getInputType(), $this->rbi ); try { if ( $checker->isValid() ) { $this->setTex( $checker->getValidTex() ); $this->texSecure = true; - $this->rbi = $checker->getRbi(); return true; } } catch ( MWException $e ) { @@ -655,6 +661,9 @@ abstract class MathRenderer { */ public function getSvg( /** @noinspection PhpUnusedParameterInspection */ $render = 'render' ) { // Spaces will prevent the image from being displayed correctly in the browser + if ( !$this->svg && $this->rbi ){ + $this->svg = $this->rbi->getSvg(); + } return trim( $this->svg ); } diff --git a/MathRestbaseInterface.php b/MathRestbaseInterface.php index 2e24e22..2273c2b 100644 --- a/MathRestbaseInterface.php +++ b/MathRestbaseInterface.php @@ -16,6 +16,8 @@ class MathRestbaseInterface { private $success; private $identifiers; private $error; + private $mathoidStyle; + private $mml; /** * MathRestbaseInterface constructor. @@ -32,7 +34,10 @@ class MathRestbaseInterface { * @throws MWException */ public function getMathML() { - return $this->getContent( 'mml' ); + if ( !$this->mml ){ + $this->mml = $this->getContent( 'mml' ); + } + return $this->mml; } private function getContent( $type ) { @@ -44,6 +49,9 @@ class MathRestbaseInterface { $serviceClient = $this->getServiceClient(); $response = $serviceClient->run( $request ); if ( $response['code'] === 200 ) { + if ( array_key_exists( 'x-mathoid-style', $response['headers'] ) ) { + $this->mathoidStyle = $response['headers']['x-mathoid-style']; + } return $response['body']; } $this->log()->error( 'Restbase math server problem:', array( @@ -64,27 +72,9 @@ class MathRestbaseInterface { } public function checkTeX() { - $postData = array( - 'type' => $this->type, - 'q' => $this->tex - ); - $requestResult = $this->makeRestbaseCheckRequest( $postData, $res ); - $json = json_decode( $res ); - if ( $requestResult ) { - $this->success = $json->success; - $this->checkedTex = $json->checked; - $this->identifiers = $json->identifiers; - return true; - } else { - if ( isset( $json->detail ) && isset( $json->detail->success ) ) { - $this->success = $json->detail->success; - $this->error = $json->detail; - } else { - $this->success = false; - $this->setErrorMessage( 'Math extension cannot connect to Restbase.' ); - } - return false; - } + $request = $this->getCheckRequest(); + $requestResult = $this->executeRestbaseCheckRequest( $request ); + return $this->evaluateRestbaseCheckResponse( $requestResult ); } /** @@ -92,33 +82,47 @@ class MathRestbaseInterface { * Generates error messages on failure * @see Http::post() * - * @param string $post the encoded post request - * @param mixed $res the result + * @param array $request the request object * @return bool success */ - private function makeRestbaseCheckRequest( $post, &$res ) { + private function executeRestbaseCheckRequest( $request ) { $res = null; - $request = array( - 'method' => 'POST', - 'body' => $post - ); $serviceClient = $this->getServiceClient(); - $request['url'] = $this->getUrl( "media/math/check/{$this->type}" ); - $response = $serviceClient->run( $request ); - if ( $response['code'] === 200 ) { - $res = $response['body']; - $headers = $response['headers']; - $this->hash = $headers['x-resource-location']; - return true; - } else { - $res = $response['body']; + $response = $serviceClient->run( $request ); + if ( $response['code'] !== 200 ) { $this->log()->info( 'Tex check failed:', array( - 'post' => $post, - 'error' => $response['error'], - 'url' => $request['url'] + 'post' => $request['body'], + 'error' => $response['error'], + 'url' => $request['url'] ) ); - return false; } + return $response; + + } + + /** + * @param array $rbis array of MathRestbaseInterface instances + */ + public static function batchEvaluate( $rbis ) { + if ( count( $rbis ) == 0 ){ + return; + } + $requests = array(); + /** @var MathRestbaseInterface $first */ + $first = $rbis[0]; + $serviceClient = $first->getServiceClient(); + foreach ( $rbis as $rbi ) { + /** @var MathRestbaseInterface $rbi */ + $requests[] = $rbi->getCheckRequest(); + } + $results = $serviceClient->runMulti( $requests ); + $i = 0; + foreach ( $results as $response ) { + /** @var MathRestbaseInterface $rbi */ + $rbi = $rbis[$i ++]; + $rbi->evaluateRestbaseCheckResponse( $response ); + } + } private function getServiceClient() { @@ -258,6 +262,9 @@ class MathRestbaseInterface { * @return boolean */ public function getSuccess() { + if ( $this->success === null ) { + $this->checkTeX(); + } return $this->success; } @@ -293,4 +300,51 @@ class MathRestbaseInterface { $this->error = (object)array( 'error' => (object)array( 'message' => $msg ) ); } + /** + * @return array + * @throws MWException + */ + public function getCheckRequest() { + $request = array( + 'method' => 'POST', + 'body' => array( + 'type' => $this->type, + 'q' => $this->tex + ), + 'url' => $this->getUrl( "media/math/check/{$this->type}" ) + ); + return $request; + } + + /** + * @param $response + * @return bool + */ + public function evaluateRestbaseCheckResponse( $response ) { + $json = json_decode( $response['body'] ); + if ( $response['code'] === 200 ) { + $headers = $response['headers']; + $this->hash = $headers['x-resource-location']; + $this->success = $json->success; + $this->checkedTex = $json->checked; + $this->identifiers = $json->identifiers; + return true; + } else { + if ( isset( $json->detail ) && isset( $json->detail->success ) ) { + $this->success = $json->detail->success; + $this->error = $json->detail; + } else { + $this->success = false; + $this->setErrorMessage( 'Math extension cannot connect to Restbase.' ); + } + return false; + } + } + + /** + * @return mixed + */ + public function getMathoidStyle() { + return $this->mathoidStyle; + } } diff --git a/extension.json b/extension.json index edc8732..9db5772 100644 --- a/extension.json +++ b/extension.json @@ -68,6 +68,9 @@ ], "WikibaseRepoDataTypes": [ "MathWikidataHook::onWikibaseRepoDataTypes" + ], + "ParserBeforeTidy":[ + "MathHooks::onParserBeforeTidy" ] }, "config": { diff --git a/tests/MathRestBaseInterfaceTest.php b/tests/MathRestBaseInterfaceTest.php index 5bb9d0f..45d7311 100644 --- a/tests/MathRestBaseInterfaceTest.php +++ b/tests/MathRestBaseInterfaceTest.php @@ -38,7 +38,6 @@ class MathRestbaseInterfaceTest extends MediaWikiTestCase { public function testSuccess() { $input = '\\sin x^2'; $rbi = new MathRestbaseInterface( $input ); - $this->assertTrue( $rbi->checkTeX(), "Assuming that $input is valid input." ); $this->assertTrue( $rbi->getSuccess(), "Assuming that $input is valid input." ); $this->assertEquals( '\\sin x^{2}', $rbi->getCheckedTex() ); $this->assertContains( 'sin', $rbi->getMathML() ); @@ -52,7 +51,6 @@ class MathRestbaseInterfaceTest extends MediaWikiTestCase { public function testFail() { $input = '\\sin\\newcommand'; $rbi = new MathRestbaseInterface( $input ); - $this->assertFalse( $rbi->checkTeX(), "Assuming that $input is invalid input." ); $this->assertFalse( $rbi->getSuccess(), "Assuming that $input is invalid input." ); $this->assertEquals( '', $rbi->getCheckedTex() ); $this->assertEquals( 'Illegal TeX function', $rbi->getError()->error->message );