Math extension:

*coding style tweaks
*documentation
*check for MediaWiki environment in the main setup file
*add extension credits
*add i18n file (only English at the moment); messages from core MessagesEn.php

This is a follow-up to Brion's r85706.
This commit is contained in:
Jack Phoenix 2011-04-09 15:13:22 +00:00
parent d690d96e24
commit e0b832c637
4 changed files with 176 additions and 74 deletions

View file

@ -5,7 +5,7 @@
* (c) 2002-2011 Tomasz Wegrzanowski, Brion Vibber, and other MediaWiki contributors * (c) 2002-2011 Tomasz Wegrzanowski, Brion Vibber, and other MediaWiki contributors
* GPLv2 license; info in main package. * GPLv2 license; info in main package.
* *
* Contain everything related to <math> </math> parsing * Contains everything related to <math> </math> parsing
* @file * @file
* @ingroup Parser * @ingroup Parser
*/ */
@ -27,7 +27,7 @@ class MathRenderer {
var $mathml = ''; var $mathml = '';
var $conservativeness = 0; var $conservativeness = 0;
function __construct( $tex, $params=array() ) { function __construct( $tex, $params = array() ) {
$this->tex = $tex; $this->tex = $tex;
$this->params = $params; $this->params = $params;
} }
@ -80,46 +80,46 @@ class MathRenderer {
$contents = wfShellExec( $cmd ); $contents = wfShellExec( $cmd );
wfDebug( "TeX output:\n $contents\n---\n" ); wfDebug( "TeX output:\n $contents\n---\n" );
if (strlen($contents) == 0) { if ( strlen( $contents ) == 0 ) {
return $this->_error( 'math_unknown_error' ); return $this->_error( 'math_unknown_error' );
} }
$retval = substr ($contents, 0, 1); $retval = substr( $contents, 0, 1 );
$errmsg = ''; $errmsg = '';
if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) { if ( ( $retval == 'C' ) || ( $retval == 'M' ) || ( $retval == 'L' ) ) {
if ($retval == 'C') { if ( $retval == 'C' ) {
$this->conservativeness = 2; $this->conservativeness = 2;
} else if ($retval == 'M') { } elseif ( $retval == 'M' ) {
$this->conservativeness = 1; $this->conservativeness = 1;
} else { } else {
$this->conservativeness = 0; $this->conservativeness = 0;
} }
$outdata = substr ($contents, 33); $outdata = substr( $contents, 33 );
$i = strpos($outdata, "\000"); $i = strpos( $outdata, "\000" );
$this->html = substr($outdata, 0, $i); $this->html = substr( $outdata, 0, $i );
$this->mathml = substr($outdata, $i+1); $this->mathml = substr( $outdata, $i + 1 );
} else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) { } elseif ( ( $retval == 'c' ) || ( $retval == 'm' ) || ( $retval == 'l' ) ) {
$this->html = substr ($contents, 33); $this->html = substr( $contents, 33 );
if ($retval == 'c') { if ( $retval == 'c' ) {
$this->conservativeness = 2; $this->conservativeness = 2;
} else if ($retval == 'm') { } elseif ( $retval == 'm' ) {
$this->conservativeness = 1; $this->conservativeness = 1;
} else { } else {
$this->conservativeness = 0; $this->conservativeness = 0;
} }
$this->mathml = null; $this->mathml = null;
} else if ($retval == 'X') { } elseif ( $retval == 'X' ) {
$this->html = null; $this->html = null;
$this->mathml = substr ($contents, 33); $this->mathml = substr( $contents, 33 );
$this->conservativeness = 0; $this->conservativeness = 0;
} else if ($retval == '+') { } elseif ( $retval == '+' ) {
$this->html = null; $this->html = null;
$this->mathml = null; $this->mathml = null;
$this->conservativeness = 0; $this->conservativeness = 0;
} else { } else {
$errbit = htmlspecialchars( substr($contents, 1) ); $errbit = htmlspecialchars( substr( $contents, 1 ) );
switch( $retval ) { switch( $retval ) {
case 'E': case 'E':
$errmsg = $this->_error( 'math_lexing_error', $errbit ); $errmsg = $this->_error( 'math_lexing_error', $errbit );
@ -136,16 +136,16 @@ class MathRenderer {
} }
if ( !$errmsg ) { if ( !$errmsg ) {
$this->hash = substr ($contents, 1, 32); $this->hash = substr( $contents, 1, 32 );
} }
wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) ); wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
if ( $errmsg ) { if ( $errmsg ) {
return $errmsg; return $errmsg;
} }
if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) { if ( !preg_match( "/^[a-f0-9]{32}$/", $this->hash ) ) {
return $this->_error( 'math_unknown_error' ); return $this->_error( 'math_unknown_error' );
} }
@ -175,19 +175,22 @@ class MathRenderer {
# Now save it back to the DB: # Now save it back to the DB:
if ( !wfReadOnly() ) { if ( !wfReadOnly() ) {
$outmd5_sql = pack('H32', $this->hash); $outmd5_sql = pack( 'H32', $this->hash );
$md5_sql = pack('H32', $this->md5); # Binary packed, not hex $md5_sql = pack( 'H32', $this->md5 ); # Binary packed, not hex
$dbw = wfGetDB( DB_MASTER ); $dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'math', array( 'math_inputhash' ), $dbw->replace(
array( 'math',
'math_inputhash' => $dbw->encodeBlob($md5_sql), array( 'math_inputhash' ),
'math_outputhash' => $dbw->encodeBlob($outmd5_sql), array(
'math_html_conservativeness' => $this->conservativeness, 'math_inputhash' => $dbw->encodeBlob( $md5_sql ),
'math_html' => $this->html, 'math_outputhash' => $dbw->encodeBlob( $outmd5_sql ),
'math_mathml' => $this->mathml, 'math_html_conservativeness' => $this->conservativeness,
), __METHOD__ 'math_html' => $this->html,
'math_mathml' => $this->mathml,
),
__METHOD__
); );
} }
@ -204,7 +207,7 @@ class MathRenderer {
} }
function _error( $msg, $append = '' ) { function _error( $msg, $append = '' ) {
$mf = htmlspecialchars( wfMsg( 'math_failure' ) ); $mf = htmlspecialchars( wfMsg( 'math_failure' ) );
$errmsg = htmlspecialchars( wfMsg( $msg ) ); $errmsg = htmlspecialchars( wfMsg( $msg ) );
$source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) ); $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n"; return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
@ -215,16 +218,22 @@ class MathRenderer {
$this->md5 = md5( $this->tex ); $this->md5 = md5( $this->tex );
$dbr = wfGetDB( DB_SLAVE ); $dbr = wfGetDB( DB_SLAVE );
$rpage = $dbr->selectRow( 'math', $rpage = $dbr->selectRow(
array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ), 'math',
array( 'math_inputhash' => $dbr->encodeBlob(pack("H32", $this->md5))), # Binary packed, not hex array(
'math_outputhash', 'math_html_conservativeness', 'math_html',
'math_mathml'
),
array(
'math_inputhash' => $dbr->encodeBlob( pack( "H32", $this->md5 ) ) # Binary packed, not hex
),
__METHOD__ __METHOD__
); );
if( $rpage !== false ) { if( $rpage !== false ) {
# Tailing 0x20s can get dropped by the database, add it back on if necessary: # Tailing 0x20s can get dropped by the database, add it back on if necessary:
$xhash = unpack( 'H32md5', $dbr->decodeBlob($rpage->math_outputhash) . " " ); $xhash = unpack( 'H32md5', $dbr->decodeBlob( $rpage->math_outputhash ) . " " );
$this->hash = $xhash ['md5']; $this->hash = $xhash['md5'];
$this->conservativeness = $rpage->math_html_conservativeness; $this->conservativeness = $rpage->math_html_conservativeness;
$this->html = $rpage->math_html; $this->html = $rpage->math_html;
@ -261,11 +270,11 @@ class MathRenderer {
} elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) { } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
return false; return false;
} }
if ( function_exists( "link" ) ) { if ( function_exists( 'link' ) ) {
return link ( $wgMathDirectory . "/{$this->hash}.png", return link( $wgMathDirectory . "/{$this->hash}.png",
$hashpath . "/{$this->hash}.png" ); $hashpath . "/{$this->hash}.png" );
} else { } else {
return rename ( $wgMathDirectory . "/{$this->hash}.png", return rename( $wgMathDirectory . "/{$this->hash}.png",
$hashpath . "/{$this->hash}.png" ); $hashpath . "/{$this->hash}.png" );
} }
} }
@ -286,9 +295,11 @@ class MathRenderer {
array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ), array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ),
$this->mathml ); $this->mathml );
} }
if (($this->mode == MW_MATH_PNG) || ($this->html == '') || if ( ( $this->mode == MW_MATH_PNG ) || ( $this->html == '' ) ||
(($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) || ( ( $this->mode == MW_MATH_SIMPLE ) && ( $this->conservativeness != 2 ) ) ||
(($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) { ( ( $this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML ) && ( $this->conservativeness == 0 ) )
)
{
return $this->_linkToMathImage(); return $this->_linkToMathImage();
} else { } else {
return Xml::tags( 'span', return Xml::tags( 'span',
@ -296,11 +307,12 @@ class MathRenderer {
array( 'class' => 'texhtml', array( 'class' => 'texhtml',
'dir' => 'ltr' 'dir' => 'ltr'
) ), ) ),
$this->html ); $this->html
);
} }
} }
function _attribs( $tag, $defaults=array(), $overrides=array() ) { function _attribs( $tag, $defaults = array(), $overrides = array() ) {
$attribs = Sanitizer::validateTagAttributes( $this->params, $tag ); $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
$attribs = Sanitizer::mergeAttributes( $defaults, $attribs ); $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
$attribs = Sanitizer::mergeAttributes( $attribs, $overrides ); $attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
@ -315,9 +327,13 @@ class MathRenderer {
'img', 'img',
array( array(
'class' => 'tex', 'class' => 'tex',
'alt' => $this->tex ), 'alt' => $this->tex
),
array( array(
'src' => $url ) ) ); 'src' => $url
)
)
);
} }
function _mathImageUrl() { function _mathImageUrl() {
@ -328,21 +344,22 @@ class MathRenderer {
function _getHashPath() { function _getHashPath() {
global $wgMathDirectory; global $wgMathDirectory;
$path = $wgMathDirectory .'/' . $this->_getHashSubPath(); $path = $wgMathDirectory . '/' . $this->_getHashSubPath();
wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" ); wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
return $path; return $path;
} }
function _getHashSubPath() { function _getHashSubPath() {
return substr($this->hash, 0, 1) return substr( $this->hash, 0, 1)
.'/'. substr($this->hash, 1, 1) . '/' . substr( $this->hash, 1, 1 )
.'/'. substr($this->hash, 2, 1); . '/' . substr( $this->hash, 2, 1 );
} }
public static function renderMath( $tex, $params=array(), ParserOptions $parserOptions = null ) { public static function renderMath( $tex, $params = array(), ParserOptions $parserOptions = null ) {
$math = new MathRenderer( $tex, $params ); $math = new MathRenderer( $tex, $params );
if ( $parserOptions ) if ( $parserOptions ) {
$math->setOutputMode( $parserOptions->getMath() ); $math->setOutputMode( $parserOptions->getMath() );
}
return $math->render(); return $math->render();
} }
} }

View file

@ -7,30 +7,55 @@
*/ */
class MathHooks { class MathHooks {
/**
* Set up $wgMathPath and $wgMathDirectory globals if they're not already
* set.
*/
static function setup() { static function setup() {
global $wgMathPath, $wgMathDirectory; global $wgMathPath, $wgMathDirectory;
global $wgUploadPath, $wgUploadDirectory; global $wgUploadPath, $wgUploadDirectory;
if ( $wgMathPath === false ) $wgMathPath = "{$wgUploadPath}/math"; if ( $wgMathPath === false ) {
if ( $wgMathDirectory === false ) $wgMathDirectory = "{$wgUploadDirectory}/math"; $wgMathPath = "{$wgUploadPath}/math";
}
if ( $wgMathDirectory === false ) {
$wgMathDirectory = "{$wgUploadDirectory}/math";
}
} }
static function onParserFirstCallInit($parser) /**
{ * Register the <math> tag with the Parser.
*
* @param $parser Object: instance of Parser
* @return Boolean: true
*/
static function onParserFirstCallInit( $parser ) {
$parser->setHook( 'math', array( 'MathHooks', 'mathTagHook' ) ); $parser->setHook( 'math', array( 'MathHooks', 'mathTagHook' ) );
return true; return true;
} }
/** /**
* @param $content * Callback function for the <math> parser hook.
* @param $attributes *
* @param $content
* @param $attributes
* @param $parser Parser * @param $parser Parser
* @return * @return
*/ */
static function mathTagHook( $content, $attributes, $parser ) { static function mathTagHook( $content, $attributes, $parser ) {
global $wgContLang; global $wgContLang;
return $wgContLang->armourMath( MathRenderer::renderMath( $content, $attributes, $parser->getOptions() ) ); $renderedMath = MathRenderer::renderMath(
$content, $attributes, $parser->getOptions()
);
return $wgContLang->armourMath( $renderedMath );
} }
/**
* Add the new math rendering options to Special:Preferences.
*
* @param $user Object: current User object
* @param $defaultPreferences Object: Preferences object
* @return Boolean: true
*/
static function onGetPreferences( $user, &$defaultPreferences ) { static function onGetPreferences( $user, &$defaultPreferences ) {
global $wgLang; global $wgLang;
$defaultPreferences['math'] = array( $defaultPreferences['math'] = array(

38
Math.i18n.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* Internationalization file for the Math extension.
*
* @file
* @ingroup Extensions
*/
$messages = array();
/** English */
$messages['en'] = array(
// Edit toolbar stuff shown on ?action=edit (example text & tooltip)
'math_sample' => 'Insert formula here',
'math_tip' => 'Mathematical formula (LaTeX)',
// Header on Special:Preferences (or something)
'prefs-math' => 'Math',
// Math options
'mw_math_png' => 'Always render PNG',
'mw_math_simple' => 'HTML if very simple or else PNG',
'mw_math_html' => 'HTML if possible or else PNG',
'mw_math_source' => 'Leave it as TeX (for text browsers)',
'mw_math_modern' => 'Recommended for modern browsers',
'mw_math_mathml' => 'MathML if possible (experimental)',
// Math errors
'math_failure' => 'Failed to parse',
'math_unknown_error' => 'unknown error',
'math_unknown_function' => 'unknown function',
'math_lexing_error' => 'lexing error',
'math_syntax_error' => 'syntax error',
'math_image_error' => 'PNG conversion failed; check for correct installation of latex and dvipng (or dvips + gs + convert)',
'math_bad_tmpdir' => 'Cannot write to or create math temp directory',
'math_bad_output' => 'Cannot write to or create math output directory',
'math_notexvc' => 'Missing texvc executable; please see math/README to configure.',
);

View file

@ -2,21 +2,41 @@
/** /**
* MediaWiki math extension * MediaWiki math extension
* *
* (c) 2002-2011 various MediaWiki contributors * @file
* GPLv2 license; info in main package. * @ingroup Extensions
* @version 1.0
* @author Tomasz Wegrzanowski
* @author Brion Vibber
* @copyright © 2002-2011 various MediaWiki contributors
* @license GPLv2 license; info in main package.
* @link http://www.mediawiki.org/wiki/Extension:Math Documentation
* @see https://bugzilla.wikimedia.org/show_bug.cgi?id=14202
*/ */
if ( !defined( 'MEDIAWIKI' ) ) {
die( "This is not a valid entry point to MediaWiki.\n" );
}
// Extension credits that will show up on Special:Version
$wgExtensionCredits['parserhook'][] = array(
'name' => 'Math',
'version' => '1.0',
'author' => array( 'Tomasz Wegrzanowski', 'Brion Vibber', '...' ),
'description' => 'Render mathematical formulas between <code>&lt;math&gt;</code> ... <code>&lt;/math&gt;</code> tags',
'url' => 'http://www.mediawiki.org/wiki/Extension:Math',
);
/** For back-compat */ /** For back-compat */
$wgUseTeX = true; $wgUseTeX = true;
/** Location of the texvc binary */ /** Location of the texvc binary */
$wgTexvc = dirname( __FILE__ ) . '/math/texvc'; $wgTexvc = dirname( __FILE__ ) . '/math/texvc';
/** /**
* Texvc background color * Texvc background color
* use LaTeX color format as used in \special function * use LaTeX color format as used in \special function
* for transparent background use value 'Transparent' for alpha transparency or * for transparent background use value 'Transparent' for alpha transparency or
* 'transparent' for binary transparency. * 'transparent' for binary transparency.
*/ */
$wgTexvcBackgroundColor = 'transparent'; $wgTexvcBackgroundColor = 'transparent';
/** /**
@ -32,14 +52,13 @@ $wgTexvcBackgroundColor = 'transparent';
*/ */
$wgMathCheckFiles = true; $wgMathCheckFiles = true;
/** /**
* The URL path of the math directory. Defaults to "{$wgUploadPath}/math". * The URL path of the math directory. Defaults to "{$wgUploadPath}/math".
* *
* See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to * See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to
* set up mathematical formula display. * set up mathematical formula display.
*/ */
$wgMathPath = false; $wgMathPath = false;
/** /**
* The filesystem path of the math directory. * The filesystem path of the math directory.
@ -48,7 +67,7 @@ $wgMathPath = false;
* See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to * See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to
* set up mathematical formula display. * set up mathematical formula display.
*/ */
$wgMathDirectory = false; $wgMathDirectory = false;
////////// end of config settings. ////////// end of config settings.
@ -58,7 +77,10 @@ $wgExtensionFunctions[] = 'MathHooks::setup';
$wgHooks['ParserFirstCallInit'][] = 'MathHooks::onParserFirstCallInit'; $wgHooks['ParserFirstCallInit'][] = 'MathHooks::onParserFirstCallInit';
$wgHooks['GetPreferences'][] = 'MathHooks::onGetPreferences'; $wgHooks['GetPreferences'][] = 'MathHooks::onGetPreferences';
$wgAutoloadClasses['MathHooks'] = dirname( __FILE__ ) . '/Math.hooks.php'; $dir = dirname( __FILE__ ) . '/';
$wgAutoloadClasses['MathRenderer'] = dirname( __FILE__ ) . '/Math.body.php'; $wgAutoloadClasses['MathHooks'] = $dir . 'Math.hooks.php';
$wgAutoloadClasses['MathRenderer'] = $dir . 'Math.body.php';
$wgParserTestFiles[] = dirname( __FILE__ ) . "/mathParserTests.txt"; $wgExtensionMessagesFiles['Math'] = $dir . 'Math.i18n.php';
$wgParserTestFiles[] = $dir . 'mathParserTests.txt';