Render all math tags in parallel
Bug: T125543 Change-Id: Ia2febf2c0309e5de514445ad2aad58b7e5ce6837
This commit is contained in:
parent
13c5ad956f
commit
9e605e09cd
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,9 @@
|
|||
],
|
||||
"WikibaseRepoDataTypes": [
|
||||
"MathWikidataHook::onWikibaseRepoDataTypes"
|
||||
],
|
||||
"ParserBeforeTidy":[
|
||||
"MathHooks::onParserBeforeTidy"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
|
|
|
@ -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 );
|
||||
|
|
Loading…
Reference in New Issue