Render all math tags in parallel

Bug: T125543
Change-Id: Ia2febf2c0309e5de514445ad2aad58b7e5ce6837
This commit is contained in:
physikerwelt 2016-01-31 22:11:39 +01:00 committed by Mobrovac
parent 13c5ad956f
commit 9e605e09cd
7 changed files with 180 additions and 70 deletions

View File

@ -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 <math> 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

View File

@ -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();
}
/**

View File

@ -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;
}

View File

@ -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 );
}

View File

@ -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;
}
}

View File

@ -68,6 +68,9 @@
],
"WikibaseRepoDataTypes": [
"MathWikidataHook::onWikibaseRepoDataTypes"
],
"ParserBeforeTidy":[
"MathHooks::onParserBeforeTidy"
]
},
"config": {

View File

@ -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( '<mi>sin</mi>', $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 );