2014-06-03 08:15:53 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* MediaWiki math extension
|
|
|
|
*
|
2018-04-13 14:18:16 +00:00
|
|
|
* @copyright 2002-2015 various MediaWiki contributors
|
|
|
|
* @license GPL-2.0-or-later
|
2015-04-14 19:42:48 +00:00
|
|
|
*/
|
|
|
|
|
2021-04-07 22:22:05 +00:00
|
|
|
namespace MediaWiki\Extension\Math;
|
|
|
|
|
|
|
|
use Hooks;
|
|
|
|
use Html;
|
2015-04-14 19:42:48 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2019-07-19 20:46:20 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2019-11-18 08:51:05 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2021-04-07 22:22:05 +00:00
|
|
|
use SpecialPage;
|
2021-08-05 01:08:52 +00:00
|
|
|
use StatusValue;
|
2021-04-07 22:22:05 +00:00
|
|
|
use stdClass;
|
2023-03-02 21:19:24 +00:00
|
|
|
use Throwable;
|
2021-04-07 22:22:05 +00:00
|
|
|
use Title;
|
|
|
|
use Xml;
|
|
|
|
use XmlTypeCheck;
|
2015-04-14 19:42:48 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts LaTeX to MathML using the mathoid-server
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
|
|
|
class MathMathML extends MathRenderer {
|
|
|
|
|
2020-12-17 20:53:28 +00:00
|
|
|
/** @var string[] */
|
2016-04-12 20:53:25 +00:00
|
|
|
protected $defaultAllowedRootElements = [ 'math' ];
|
2020-12-17 20:53:28 +00:00
|
|
|
/** @var string[] */
|
2016-04-12 20:53:25 +00:00
|
|
|
protected $restbaseInputTypes = [ 'tex', 'inline-tex', 'chem' ];
|
2020-12-17 20:53:28 +00:00
|
|
|
/** @var string[] */
|
2022-11-18 22:50:15 +00:00
|
|
|
protected $restbaseRenderingModes = [ MathConfig::MODE_MATHML ];
|
2020-12-17 20:53:28 +00:00
|
|
|
/** @var string[] */
|
2018-04-07 19:03:56 +00:00
|
|
|
protected $allowedRootElements = [];
|
2021-08-02 22:29:04 +00:00
|
|
|
/** @var string */
|
|
|
|
protected $host;
|
2015-01-22 18:56:13 +00:00
|
|
|
|
2019-11-18 08:51:05 +00:00
|
|
|
/** @var LoggerInterface */
|
2022-06-21 09:23:05 +00:00
|
|
|
protected $logger;
|
2019-11-18 08:51:05 +00:00
|
|
|
|
2017-08-09 20:56:07 +00:00
|
|
|
/** @var bool if false MathML output is not validated */
|
2014-06-03 08:15:53 +00:00
|
|
|
private $XMLValidation = true;
|
2017-07-10 09:40:12 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string|bool
|
|
|
|
*/
|
2015-12-01 22:51:22 +00:00
|
|
|
private $svgPath = false;
|
2017-07-10 09:40:12 +00:00
|
|
|
|
2020-12-17 20:53:28 +00:00
|
|
|
/** @var string|false */
|
2018-05-18 17:08:51 +00:00
|
|
|
private $pngPath = false;
|
|
|
|
|
2019-11-04 18:41:47 +00:00
|
|
|
/** @var string|null */
|
2016-01-31 21:11:39 +00:00
|
|
|
private $mathoidStyle;
|
2014-06-10 16:49:20 +00:00
|
|
|
|
2016-04-12 20:53:25 +00:00
|
|
|
public function __construct( $tex = '', $params = [] ) {
|
2014-06-10 16:49:20 +00:00
|
|
|
global $wgMathMathMLUrl;
|
|
|
|
parent::__construct( $tex, $params );
|
2021-08-11 18:15:35 +00:00
|
|
|
$this->setMode( MathConfig::MODE_MATHML );
|
2021-08-02 22:29:04 +00:00
|
|
|
$this->host = $wgMathMathMLUrl;
|
2014-06-10 16:49:20 +00:00
|
|
|
if ( isset( $params['type'] ) ) {
|
2018-05-20 22:47:32 +00:00
|
|
|
$allowedTypes = [ 'pmml', 'ascii', 'chem' ];
|
|
|
|
if ( in_array( $params['type'], $allowedTypes ) ) {
|
|
|
|
$this->inputType = $params['type'];
|
|
|
|
}
|
2014-06-10 16:49:20 +00:00
|
|
|
if ( $params['type'] == 'pmml' ) {
|
|
|
|
$this->setMathml( '<math>' . $tex . '</math>' );
|
|
|
|
}
|
|
|
|
}
|
2016-01-29 16:50:32 +00:00
|
|
|
if ( !isset( $params['display'] ) && $this->getMathStyle() == 'inlineDisplaystyle' ) {
|
|
|
|
// default preserve the (broken) layout as it was
|
|
|
|
$this->tex = '{\\displaystyle ' . $tex . '}';
|
|
|
|
}
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger = LoggerFactory::getInstance( 'Math' );
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
2014-06-03 08:15:53 +00:00
|
|
|
|
2018-06-25 16:08:42 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function addTrackingCategories( $parser ) {
|
|
|
|
parent::addTrackingCategories( $parser );
|
|
|
|
if ( $this->hasWarnings() ) {
|
|
|
|
foreach ( $this->warnings as $warning ) {
|
2020-03-30 19:50:50 +00:00
|
|
|
if ( isset( $warning->type ) ) {
|
|
|
|
switch ( $warning->type ) {
|
|
|
|
case 'mhchem-deprecation':
|
|
|
|
$parser->addTrackingCategory( 'math-tracking-category-mhchem-deprecation' );
|
|
|
|
break;
|
|
|
|
case 'texvc-deprecation':
|
|
|
|
$parser->addTrackingCategory( 'math-tracking-category-texvc-deprecation' );
|
|
|
|
}
|
2018-06-25 16:08:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 18:41:47 +00:00
|
|
|
/**
|
2021-08-04 19:49:10 +00:00
|
|
|
* @param MathRenderer[] $renderers
|
2019-11-04 18:41:47 +00:00
|
|
|
*/
|
2021-08-04 19:49:10 +00:00
|
|
|
public static function batchEvaluate( array $renderers ) {
|
2017-08-16 05:50:44 +00:00
|
|
|
$rbis = [];
|
2022-09-29 13:54:47 +00:00
|
|
|
foreach ( $renderers as $renderer ) {
|
2017-08-16 05:50:44 +00:00
|
|
|
$rbi = new MathRestbaseInterface( $renderer->getTex(), $renderer->getInputType() );
|
|
|
|
$renderer->setRestbaseInterface( $rbi );
|
|
|
|
$rbis[] = $rbi;
|
|
|
|
}
|
|
|
|
MathRestbaseInterface::batchEvaluate( $rbis );
|
|
|
|
}
|
|
|
|
|
2014-06-03 08:15:53 +00:00
|
|
|
/**
|
|
|
|
* Gets the allowed root elements the rendered math tag might have.
|
|
|
|
*
|
2020-12-17 20:53:28 +00:00
|
|
|
* @return string[]
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
|
|
|
public function getAllowedRootElements() {
|
|
|
|
if ( $this->allowedRootElements ) {
|
|
|
|
return $this->allowedRootElements;
|
|
|
|
} else {
|
|
|
|
return $this->defaultAllowedRootElements;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the XML validation.
|
|
|
|
* If set to false the output of MathML is not validated.
|
2017-08-09 20:56:07 +00:00
|
|
|
* @param bool $validation
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
|
|
|
public function setXMLValidation( $validation = true ) {
|
|
|
|
$this->XMLValidation = $validation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the allowed root elements the rendered math tag might have.
|
|
|
|
* An empty value indicates to use the default settings.
|
2020-12-17 20:53:28 +00:00
|
|
|
* @param string[] $settings
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
2014-06-10 09:57:24 +00:00
|
|
|
public function setAllowedRootElements( $settings ) {
|
2014-06-03 08:15:53 +00:00
|
|
|
$this->allowedRootElements = $settings;
|
|
|
|
}
|
|
|
|
|
2022-06-21 09:23:05 +00:00
|
|
|
public function render() {
|
2016-05-10 13:45:26 +00:00
|
|
|
global $wgMathFullRestbaseURL;
|
|
|
|
try {
|
|
|
|
if ( in_array( $this->inputType, $this->restbaseInputTypes ) &&
|
2018-05-18 17:08:51 +00:00
|
|
|
in_array( $this->mode, $this->restbaseRenderingModes )
|
2016-05-10 13:45:26 +00:00
|
|
|
) {
|
|
|
|
if ( !$this->rbi ) {
|
|
|
|
$this->rbi =
|
|
|
|
new MathRestbaseInterface( $this->getTex(), $this->getInputType() );
|
2016-06-02 14:30:48 +00:00
|
|
|
$this->rbi->setPurge( $this->isPurge() );
|
2016-05-10 13:45:26 +00:00
|
|
|
}
|
|
|
|
$rbi = $this->rbi;
|
|
|
|
if ( $rbi->getSuccess() ) {
|
|
|
|
$this->mathml = $rbi->getMathML();
|
|
|
|
$this->mathoidStyle = $rbi->getMathoidStyle();
|
|
|
|
$this->svgPath = $rbi->getFullSvgUrl();
|
2018-05-18 17:08:51 +00:00
|
|
|
$this->pngPath = $rbi->getFullPngUrl();
|
2018-06-25 16:08:42 +00:00
|
|
|
$this->warnings = $rbi->getWarnings();
|
2016-05-10 13:45:26 +00:00
|
|
|
} elseif ( $this->lastError === '' ) {
|
|
|
|
$this->doCheck();
|
|
|
|
}
|
|
|
|
$this->changed = false;
|
|
|
|
return $rbi->getSuccess();
|
2015-12-01 22:51:22 +00:00
|
|
|
}
|
2016-05-10 13:45:26 +00:00
|
|
|
if ( $this->renderingRequired() ) {
|
2021-08-05 01:08:52 +00:00
|
|
|
$renderResult = $this->doRender();
|
|
|
|
if ( !$renderResult->isGood() ) {
|
|
|
|
// TODO: this is a hacky hack, lastError will not exist soon.
|
|
|
|
$renderError = $renderResult->getErrors()[0];
|
|
|
|
$this->lastError = $this->getError( $renderError['message'], ...$renderError['params'] );
|
|
|
|
}
|
|
|
|
return $renderResult->isGood();
|
2016-05-10 13:45:26 +00:00
|
|
|
}
|
|
|
|
return true;
|
2023-03-02 21:19:24 +00:00
|
|
|
} catch ( Throwable $e ) {
|
2016-05-10 13:45:26 +00:00
|
|
|
$this->lastError = $this->getError( 'math_mathoid_error',
|
|
|
|
$wgMathFullRestbaseURL, $e->getMessage() );
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger->error( $e->getMessage(), [ $e, $this ] );
|
2016-05-10 13:45:26 +00:00
|
|
|
return false;
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to checks if the math tag must be rendered.
|
2017-08-09 20:56:07 +00:00
|
|
|
* @return bool
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
|
|
|
private function renderingRequired() {
|
|
|
|
if ( $this->isPurge() ) {
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger->debug( 'Rerendering was requested.' );
|
2014-06-03 08:15:53 +00:00
|
|
|
return true;
|
2021-04-07 22:22:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$dbres = $this->isInDatabase();
|
|
|
|
if ( $dbres ) {
|
|
|
|
if ( $this->isValidMathML( $this->getMathml() ) ) {
|
|
|
|
$this->logger->debug( 'Valid MathML entry found in database.' );
|
|
|
|
if ( $this->getSvg( 'cached' ) ) {
|
|
|
|
$this->logger->debug( 'SVG-fallback found in database.' );
|
|
|
|
return false;
|
2014-06-03 08:15:53 +00:00
|
|
|
} else {
|
2021-04-07 22:22:05 +00:00
|
|
|
$this->logger->debug( 'SVG-fallback missing.' );
|
2014-06-03 08:15:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
2021-04-07 22:22:05 +00:00
|
|
|
$this->logger->debug( 'Malformatted entry found in database' );
|
2014-06-03 08:15:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
2021-04-07 22:22:05 +00:00
|
|
|
} else {
|
|
|
|
$this->logger->debug( 'No entry found in database.' );
|
|
|
|
return true;
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs a HTTP Post request to the given host.
|
|
|
|
* Uses $wgMathLaTeXMLTimeout as timeout.
|
|
|
|
*
|
2021-08-05 01:08:52 +00:00
|
|
|
* @return StatusValue result with response body as a value
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
2021-08-05 01:08:52 +00:00
|
|
|
public function makeRequest() {
|
2014-06-03 08:15:53 +00:00
|
|
|
// TODO: Change the timeout mechanism.
|
|
|
|
global $wgMathLaTeXMLTimeout;
|
2021-08-03 20:59:02 +00:00
|
|
|
$post = $this->getPostData();
|
2016-04-12 20:53:25 +00:00
|
|
|
$options = [ 'method' => 'POST', 'postData' => $post, 'timeout' => $wgMathLaTeXMLTimeout ];
|
2021-08-03 20:59:02 +00:00
|
|
|
$req = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $this->host, $options );
|
2014-06-03 08:15:53 +00:00
|
|
|
$status = $req->execute();
|
|
|
|
if ( $status->isGood() ) {
|
2021-08-05 01:08:52 +00:00
|
|
|
return StatusValue::newGood( $req->getContent() );
|
2014-06-03 08:15:53 +00:00
|
|
|
} else {
|
|
|
|
if ( $status->hasMessage( 'http-timed-out' ) ) {
|
2021-08-05 01:08:52 +00:00
|
|
|
$this->logger->warning( 'Math service request timeout', [
|
|
|
|
'post' => $post,
|
|
|
|
'host' => $this->host,
|
|
|
|
'timeout' => $wgMathLaTeXMLTimeout
|
|
|
|
] );
|
2021-08-12 03:14:17 +00:00
|
|
|
return StatusValue::newFatal( 'math_timeout', $this->getModeName(), $this->host );
|
2014-06-03 08:15:53 +00:00
|
|
|
} else {
|
2021-08-02 20:46:44 +00:00
|
|
|
$errormsg = $req->getContent();
|
2021-08-05 01:08:52 +00:00
|
|
|
$this->logger->warning( 'Math service request failed', [
|
|
|
|
'post' => $post,
|
|
|
|
'host' => $this->host,
|
|
|
|
'errormsg' => $errormsg
|
|
|
|
] );
|
|
|
|
return StatusValue::newFatal(
|
|
|
|
'math_invalidresponse',
|
2021-08-12 03:14:17 +00:00
|
|
|
$this->getModeName(),
|
2021-08-05 01:08:52 +00:00
|
|
|
$this->host,
|
|
|
|
$errormsg,
|
2021-08-12 03:14:17 +00:00
|
|
|
$this->getModeName()
|
2021-08-05 01:08:52 +00:00
|
|
|
);
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-10 16:49:20 +00:00
|
|
|
/**
|
|
|
|
* 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() {
|
|
|
|
$input = $this->getTex();
|
2015-03-16 19:51:16 +00:00
|
|
|
if ( $this->inputType == 'pmml' ||
|
2021-08-11 18:15:35 +00:00
|
|
|
$this->getMode() == MathConfig::MODE_LATEXML && $this->getMathml() ) {
|
2014-06-10 16:49:20 +00:00
|
|
|
$out = 'type=mml&q=' . rawurlencode( $this->getMathml() );
|
|
|
|
} elseif ( $this->inputType == 'ascii' ) {
|
|
|
|
$out = 'type=asciimath&q=' . rawurlencode( $input );
|
|
|
|
} else {
|
2021-02-02 21:51:17 +00:00
|
|
|
if ( $this->getMathStyle() === 'inlineDisplaystyle' ) {
|
|
|
|
// default preserve the (broken) layout as it was
|
|
|
|
$out = 'type=inline-TeX&q=' . rawurlencode( '{\\displaystyle ' . $input . '}' );
|
|
|
|
} elseif ( $this->getMathStyle() === 'inline' ) {
|
|
|
|
$out = 'type=inline-TeX&q=' . rawurlencode( $input );
|
|
|
|
} else {
|
|
|
|
$out = 'type=tex&q=' . rawurlencode( $input );
|
|
|
|
}
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger->debug( 'Get post data: ' . $out );
|
2014-06-10 16:49:20 +00:00
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Does the actual web request to convert TeX to MathML.
|
2021-08-05 01:08:52 +00:00
|
|
|
*
|
|
|
|
* @return StatusValue
|
2014-06-10 16:49:20 +00:00
|
|
|
*/
|
2021-08-05 01:08:52 +00:00
|
|
|
protected function doRender(): StatusValue {
|
2017-08-16 05:50:44 +00:00
|
|
|
if ( $this->isEmpty() ) {
|
2021-08-05 01:08:52 +00:00
|
|
|
$this->logger->debug( 'Rendering was requested, but no TeX string is specified.' );
|
|
|
|
return StatusValue::newFatal( 'math_empty_tex' );
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
2021-08-05 01:08:52 +00:00
|
|
|
$requestStatus = $this->makeRequest();
|
|
|
|
if ( $requestStatus->isGood() ) {
|
|
|
|
$jsonResult = json_decode( $requestStatus->getValue() );
|
2014-06-10 16:49:20 +00:00
|
|
|
if ( $jsonResult && json_last_error() === JSON_ERROR_NONE ) {
|
|
|
|
if ( $jsonResult->success ) {
|
2021-08-02 22:29:04 +00:00
|
|
|
return $this->processJsonResult( $jsonResult, $this->host );
|
2014-06-10 16:49:20 +00:00
|
|
|
} else {
|
2021-08-05 01:08:52 +00:00
|
|
|
$serviceLog = $jsonResult->log ?? wfMessage( 'math_unknown_error' )
|
|
|
|
->inContentLanguage()
|
|
|
|
->escaped();
|
|
|
|
$this->logger->warning( 'Mathoid conversion error', [
|
2021-08-03 20:59:02 +00:00
|
|
|
'post' => $this->getPostData(),
|
2021-08-02 22:29:04 +00:00
|
|
|
'host' => $this->host,
|
2021-08-05 01:08:52 +00:00
|
|
|
'result' => $requestStatus->getValue(),
|
|
|
|
'service_log' => $serviceLog
|
2021-08-03 20:59:02 +00:00
|
|
|
] );
|
2021-08-05 01:08:52 +00:00
|
|
|
return StatusValue::newFatal( 'math_mathoid_error', $this->host, $serviceLog );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->logger->error( 'MathML invalid JSON', [
|
|
|
|
'post' => $this->getPostData(),
|
|
|
|
'host' => $this->host,
|
|
|
|
'res' => $requestStatus->getValue(),
|
|
|
|
] );
|
|
|
|
return StatusValue::newFatal( 'math_invalidjson', $this->host );
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-08-05 01:08:52 +00:00
|
|
|
return $requestStatus;
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 08:15:53 +00:00
|
|
|
/**
|
|
|
|
* Checks if the input is valid MathML,
|
|
|
|
* and if the root element has the name math
|
|
|
|
* @param string $XML
|
2017-07-26 20:43:39 +00:00
|
|
|
* @return bool
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
|
|
|
public function isValidMathML( $XML ) {
|
|
|
|
$out = false;
|
|
|
|
if ( !$this->XMLValidation ) {
|
|
|
|
return true;
|
|
|
|
}
|
2015-02-20 00:22:46 +00:00
|
|
|
|
2014-06-03 08:15:53 +00:00
|
|
|
$xmlObject = new XmlTypeCheck( $XML, null, false );
|
|
|
|
if ( !$xmlObject->wellFormed ) {
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger->error(
|
2015-03-18 17:28:34 +00:00
|
|
|
'XML validation error: ' . var_export( $XML, true ) );
|
2014-06-03 08:15:53 +00:00
|
|
|
} else {
|
|
|
|
$name = $xmlObject->getRootElement();
|
|
|
|
$elementSplit = explode( ':', $name );
|
2018-05-20 22:47:32 +00:00
|
|
|
$localName = end( $elementSplit );
|
2015-09-21 16:14:01 +00:00
|
|
|
if ( in_array( $localName, $this->getAllowedRootElements() ) ) {
|
2014-06-03 08:15:53 +00:00
|
|
|
$out = true;
|
|
|
|
} else {
|
2019-11-18 08:51:05 +00:00
|
|
|
$this->logger->error( "Got wrong root element: $name" );
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
2014-06-10 16:49:20 +00:00
|
|
|
/**
|
2017-08-09 20:56:07 +00:00
|
|
|
* @param bool $noRender
|
2017-07-10 09:40:12 +00:00
|
|
|
* @return Title|string
|
2014-06-10 16:49:20 +00:00
|
|
|
*/
|
2014-10-11 14:13:04 +00:00
|
|
|
private function getFallbackImageUrl( $noRender = false ) {
|
2015-12-01 22:51:22 +00:00
|
|
|
if ( $this->svgPath ) {
|
|
|
|
return $this->svgPath;
|
|
|
|
}
|
2016-04-12 20:53:25 +00:00
|
|
|
return SpecialPage::getTitleFor( 'MathShowImage' )->getLocalURL( [
|
2014-06-10 16:49:20 +00:00
|
|
|
'hash' => $this->getMd5(),
|
2014-10-11 14:13:04 +00:00
|
|
|
'mode' => $this->getMode(),
|
2016-04-12 20:53:25 +00:00
|
|
|
'noRender' => $noRender
|
|
|
|
]
|
2014-06-10 16:49:20 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to correct the style information for a
|
|
|
|
* linked SVG image.
|
2017-08-09 20:56:07 +00:00
|
|
|
* @param string &$style current style information to be updated
|
2014-06-10 16:49:20 +00:00
|
|
|
*/
|
2016-04-13 12:30:48 +00:00
|
|
|
public function correctSvgStyle( &$style ) {
|
|
|
|
if ( preg_match( '/style="([^"]*)"/', $this->getSvg(), $styles ) ) {
|
2015-01-22 18:56:13 +00:00
|
|
|
$style .= ' ' . $styles[1]; // merge styles
|
2015-07-22 22:10:46 +00:00
|
|
|
if ( $this->getMathStyle() === 'display' ) {
|
2014-06-10 16:49:20 +00:00
|
|
|
// TODO: Improve style cleaning
|
2015-09-21 16:14:01 +00:00
|
|
|
$style = preg_replace(
|
|
|
|
'/margin\-(left|right)\:\s*\d+(\%|in|cm|mm|em|ex|pt|pc|px)\;/', '', $style
|
|
|
|
);
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
2016-04-13 12:30:48 +00:00
|
|
|
$style = trim( preg_replace( '/position:\s*absolute;\s*left:\s*0px;/', '', $style ),
|
2018-09-05 22:34:15 +00:00
|
|
|
"; \t\n\r\0\x0B" ) . '; ';
|
2016-04-13 12:30:48 +00:00
|
|
|
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
2014-07-09 11:32:28 +00:00
|
|
|
// TODO: Figure out if there is a way to construct
|
|
|
|
// a SVGReader from a string that represents the SVG
|
|
|
|
// content
|
2015-09-21 16:14:01 +00:00
|
|
|
if ( preg_match( "/height=\"(.*?)\"/", $this->getSvg(), $matches ) ) {
|
2015-01-22 18:56:13 +00:00
|
|
|
$style .= 'height: ' . $matches[1] . '; ';
|
2014-07-09 11:32:28 +00:00
|
|
|
}
|
|
|
|
if ( preg_match( "/width=\"(.*?)\"/", $this->getSvg(), $matches ) ) {
|
2015-01-22 18:56:13 +00:00
|
|
|
$style .= 'width: ' . $matches[1] . ';';
|
2014-07-09 11:32:28 +00:00
|
|
|
}
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-06-10 16:49:20 +00:00
|
|
|
* Gets img tag for math image
|
2017-08-09 20:56:07 +00:00
|
|
|
* @param bool $noRender if true no rendering will be performed
|
2015-09-21 16:14:01 +00:00
|
|
|
* if the image is not stored in the database
|
2020-12-17 20:53:28 +00:00
|
|
|
* @param false|string $classOverride if classOverride
|
2015-09-21 16:14:01 +00:00
|
|
|
* is false the class name will be calculated by getClassName
|
2014-06-10 16:49:20 +00:00
|
|
|
* @return string XML the image html tag
|
2014-06-03 08:15:53 +00:00
|
|
|
*/
|
2018-05-18 17:08:51 +00:00
|
|
|
protected function getFallbackImage( $noRender = false, $classOverride = false ) {
|
2016-06-02 10:12:32 +00:00
|
|
|
$attribs = [
|
2022-09-29 13:54:47 +00:00
|
|
|
'src' => $this->getFallbackImageUrl( $noRender ),
|
|
|
|
'class' => $classOverride === false ? $this->getClassName( true ) : $classOverride,
|
2016-06-02 10:12:32 +00:00
|
|
|
];
|
2019-02-06 23:08:22 +00:00
|
|
|
if ( !$this->mathoidStyle ) {
|
2016-04-13 12:30:48 +00:00
|
|
|
$this->correctSvgStyle( $this->mathoidStyle );
|
2016-01-31 21:11:39 +00:00
|
|
|
}
|
2015-09-21 16:14:01 +00:00
|
|
|
|
2018-04-23 13:34:35 +00:00
|
|
|
return Html::element( 'img', $this->getAttributes( 'span', $attribs, [
|
2016-06-02 10:12:32 +00:00
|
|
|
'aria-hidden' => 'true',
|
2016-06-06 16:03:24 +00:00
|
|
|
'style' => $this->mathoidStyle,
|
|
|
|
'alt' => $this->tex
|
2016-04-12 20:53:25 +00:00
|
|
|
] ) );
|
2014-06-03 08:15:53 +00:00
|
|
|
}
|
|
|
|
|
2021-02-18 13:22:49 +00:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
2014-06-03 08:15:53 +00:00
|
|
|
protected function getMathTableName() {
|
|
|
|
return 'mathoid';
|
|
|
|
}
|
2015-09-21 16:14:01 +00:00
|
|
|
|
2014-06-05 21:06:43 +00:00
|
|
|
/**
|
|
|
|
* Calculates the default class name for a math element
|
2017-08-09 20:56:07 +00:00
|
|
|
* @param bool $fallback
|
2014-06-05 21:06:43 +00:00
|
|
|
* @return string the class name
|
|
|
|
*/
|
2014-10-11 14:13:04 +00:00
|
|
|
private function getClassName( $fallback = false ) {
|
2015-01-22 18:56:13 +00:00
|
|
|
$class = 'mwe-math-';
|
2014-06-05 21:06:43 +00:00
|
|
|
if ( $fallback ) {
|
2014-10-12 09:26:57 +00:00
|
|
|
$class .= 'fallback-image-';
|
2014-06-05 21:06:43 +00:00
|
|
|
} else {
|
|
|
|
$class .= 'mathml-';
|
|
|
|
}
|
2015-07-22 22:10:46 +00:00
|
|
|
if ( $this->getMathStyle() == 'display' ) {
|
2014-06-05 21:06:43 +00:00
|
|
|
$class .= 'display';
|
|
|
|
} else {
|
|
|
|
$class .= 'inline';
|
|
|
|
}
|
2015-09-21 16:14:01 +00:00
|
|
|
if ( !$fallback ) {
|
2014-10-10 10:38:23 +00:00
|
|
|
$class .= ' mwe-math-mathml-a11y';
|
|
|
|
}
|
2016-12-24 13:04:07 +00:00
|
|
|
return $class;
|
2014-06-05 21:06:43 +00:00
|
|
|
}
|
2015-09-21 16:14:01 +00:00
|
|
|
|
2014-06-05 21:06:43 +00:00
|
|
|
/**
|
|
|
|
* @return string Html output that is embedded in the page
|
|
|
|
*/
|
|
|
|
public function getHtmlOutput() {
|
2019-07-19 20:46:20 +00:00
|
|
|
$config = MediaWikiServices::getInstance()->getMainConfig();
|
|
|
|
$enableLinks = $config->get( "MathEnableFormulaLinks" );
|
2015-07-22 22:10:46 +00:00
|
|
|
if ( $this->getMathStyle() == 'display' ) {
|
2014-06-05 21:06:43 +00:00
|
|
|
$element = 'div';
|
|
|
|
} else {
|
|
|
|
$element = 'span';
|
|
|
|
}
|
2017-01-11 18:43:10 +00:00
|
|
|
$attribs = [ 'class' => 'mwe-math-element' ];
|
2014-06-10 16:49:20 +00:00
|
|
|
if ( $this->getID() !== '' ) {
|
|
|
|
$attribs['id'] = $this->getID();
|
|
|
|
}
|
2019-07-19 20:46:20 +00:00
|
|
|
$hyperlink = null;
|
2018-11-05 17:29:56 +00:00
|
|
|
if ( isset( $this->params['qid'] ) && preg_match( '/Q\d+/', $this->params['qid'] ) ) {
|
|
|
|
$attribs['data-qid'] = $this->params['qid'];
|
2019-07-19 20:46:20 +00:00
|
|
|
// TODO: SpecialPage::getTitleFor uses the depcrated method Title::newFromTitleValue and
|
|
|
|
// declares a never thrown MWException. Once this SpecialPage is updated, update the next line.
|
|
|
|
$titleObj = Title::newFromLinkTarget( SpecialPage::getTitleValueFor( 'MathWikibase' ) );
|
|
|
|
$hyperlink = $titleObj->getLocalURL( [ 'qid' => $this->params['qid'] ] );
|
2018-11-05 17:29:56 +00:00
|
|
|
}
|
2016-02-08 17:35:49 +00:00
|
|
|
$output = Html::openElement( $element, $attribs );
|
2019-07-19 20:46:20 +00:00
|
|
|
if ( $hyperlink && $enableLinks ) {
|
|
|
|
$output .= Html::openElement( 'a', [ 'href' => $hyperlink, 'style' => 'color:inherit;' ] );
|
|
|
|
}
|
2014-06-05 21:06:43 +00:00
|
|
|
// MathML has to be wrapped into a div or span in order to be able to hide it.
|
2014-07-10 15:14:26 +00:00
|
|
|
// Remove displayStyle attributes set by the MathML converter
|
|
|
|
// (Beginning from Mathoid 0.2.5 block is the default layout.)
|
2015-09-21 16:14:01 +00:00
|
|
|
$mml = preg_replace(
|
|
|
|
'/(<math[^>]*)(display|mode)=["\'](inline|block)["\']/', '$1', $this->getMathml()
|
|
|
|
);
|
2015-07-22 22:10:46 +00:00
|
|
|
if ( $this->getMathStyle() == 'display' ) {
|
2014-06-05 21:06:43 +00:00
|
|
|
$mml = preg_replace( '/<math/', '<math display="block"', $mml );
|
|
|
|
}
|
2016-04-12 20:53:25 +00:00
|
|
|
$output .= Xml::tags( $element, [
|
2015-09-21 16:14:01 +00:00
|
|
|
'class' => $this->getClassName(), 'style' => 'display: none;'
|
2016-04-12 20:53:25 +00:00
|
|
|
], $mml );
|
2015-09-21 16:14:01 +00:00
|
|
|
$output .= $this->getFallbackImage();
|
2019-07-19 20:46:20 +00:00
|
|
|
if ( $hyperlink && $enableLinks ) {
|
|
|
|
$output .= Html::closeElement( 'a' );
|
|
|
|
}
|
2016-02-08 17:35:49 +00:00
|
|
|
$output .= Html::closeElement( $element );
|
2014-06-05 21:06:43 +00:00
|
|
|
return $output;
|
|
|
|
}
|
2014-06-10 16:49:20 +00:00
|
|
|
|
|
|
|
protected function dbOutArray() {
|
|
|
|
$out = parent::dbOutArray();
|
2015-09-21 16:14:01 +00:00
|
|
|
if ( $this->getMathTableName() == 'mathoid' ) {
|
2014-06-10 16:49:20 +00:00
|
|
|
$out['math_input'] = $out['math_inputtex'];
|
2015-09-21 16:14:01 +00:00
|
|
|
unset( $out['math_inputtex'] );
|
2021-02-02 21:51:17 +00:00
|
|
|
$out['math_png'] = $this->png;
|
2014-06-10 16:49:20 +00:00
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function dbInArray() {
|
|
|
|
$out = parent::dbInArray();
|
2015-09-21 16:14:01 +00:00
|
|
|
if ( $this->getMathTableName() == 'mathoid' ) {
|
2016-04-12 20:53:25 +00:00
|
|
|
$out = array_diff( $out, [ 'math_inputtex' ] );
|
2014-06-10 16:49:20 +00:00
|
|
|
$out[] = 'math_input';
|
|
|
|
}
|
|
|
|
return $out;
|
|
|
|
}
|
2014-07-25 20:09:34 +00:00
|
|
|
|
|
|
|
protected function initializeFromDatabaseRow( $rpage ) {
|
|
|
|
// mathoid allows different input formats
|
|
|
|
// therefore the column name math_inputtex was changed to math_input
|
2019-02-06 23:08:22 +00:00
|
|
|
if ( $this->getMathTableName() == 'mathoid' && !empty( $rpage->math_input ) ) {
|
2014-07-25 20:09:34 +00:00
|
|
|
$this->userInputTex = $rpage->math_input;
|
|
|
|
}
|
|
|
|
parent::initializeFromDatabaseRow( $rpage );
|
|
|
|
}
|
2015-01-24 14:53:24 +00:00
|
|
|
|
|
|
|
/**
|
2022-09-29 13:54:47 +00:00
|
|
|
* @param stdClass $jsonResult
|
2017-08-16 05:50:44 +00:00
|
|
|
* @param string $host name
|
2015-01-24 14:53:24 +00:00
|
|
|
*
|
2021-08-05 01:08:52 +00:00
|
|
|
* @return StatusValue
|
2015-01-24 14:53:24 +00:00
|
|
|
*/
|
2021-08-05 01:08:52 +00:00
|
|
|
protected function processJsonResult( $jsonResult, $host ): StatusValue {
|
2021-08-11 18:15:35 +00:00
|
|
|
if ( $this->getMode() == MathConfig::MODE_LATEXML || $this->inputType == 'pmml' ||
|
2015-01-24 14:53:24 +00:00
|
|
|
$this->isValidMathML( $jsonResult->mml )
|
|
|
|
) {
|
|
|
|
if ( isset( $jsonResult->svg ) ) {
|
|
|
|
$xmlObject = new XmlTypeCheck( $jsonResult->svg, null, false );
|
|
|
|
if ( !$xmlObject->wellFormed ) {
|
2021-08-05 01:08:52 +00:00
|
|
|
return StatusValue::newFatal( 'math_invalidxml', $host );
|
2015-01-24 14:53:24 +00:00
|
|
|
} else {
|
|
|
|
$this->setSvg( $jsonResult->svg );
|
|
|
|
}
|
|
|
|
} else {
|
2021-08-05 01:08:52 +00:00
|
|
|
$this->logger->error( 'Missing SVG property in JSON result.' );
|
2015-01-24 14:53:24 +00:00
|
|
|
}
|
2021-08-11 18:15:35 +00:00
|
|
|
if ( $this->getMode() != MathConfig::MODE_LATEXML && $this->inputType != 'pmml' ) {
|
2015-01-24 14:53:24 +00:00
|
|
|
$this->setMathml( $jsonResult->mml );
|
|
|
|
}
|
2017-02-01 03:54:07 +00:00
|
|
|
// Avoid PHP 7.1 warning from passing $this by reference
|
|
|
|
$renderer = $this;
|
2015-07-12 19:40:34 +00:00
|
|
|
Hooks::run( 'MathRenderingResultRetrieved',
|
2021-04-07 22:22:05 +00:00
|
|
|
[ &$renderer, &$jsonResult ]
|
|
|
|
); // Enables debugging of server results
|
2021-08-05 01:08:52 +00:00
|
|
|
return StatusValue::newGood(); // FIXME: empty?
|
2015-01-24 14:53:24 +00:00
|
|
|
} else {
|
2021-08-05 01:08:52 +00:00
|
|
|
return StatusValue::newFatal( 'math_unknown_error', $host );
|
2015-01-24 14:53:24 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-16 05:50:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function isEmpty() {
|
2021-08-05 01:08:52 +00:00
|
|
|
return $this->userInputTex === '';
|
2017-08-16 05:50:44 +00:00
|
|
|
}
|
2014-07-09 11:32:28 +00:00
|
|
|
}
|