PageRenderTime 26ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/Math/Math.body.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 365 lines | 288 code | 46 blank | 31 comment | 77 complexity | fac7c1996d05013f08cedae965612cf0 MD5 | raw file
  1. <?php
  2. /**
  3. * MediaWiki math extension
  4. *
  5. * (c) 2002-2011 Tomasz Wegrzanowski, Brion Vibber, and other MediaWiki contributors
  6. * GPLv2 license; info in main package.
  7. *
  8. * Contains everything related to <math> </math> parsing
  9. * @file
  10. * @ingroup Parser
  11. */
  12. /**
  13. * Takes LaTeX fragments, sends them to a helper program (texvc) for rendering
  14. * to rasterized PNG and HTML and MathML approximations. An appropriate
  15. * rendering form is picked and returned.
  16. *
  17. * @author Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
  18. * @ingroup Parser
  19. */
  20. class MathRenderer {
  21. var $mode = MW_MATH_MODERN;
  22. var $tex = '';
  23. var $inputhash = '';
  24. var $hash = '';
  25. var $html = '';
  26. var $mathml = '';
  27. var $conservativeness = 0;
  28. function __construct( $tex, $params = array() ) {
  29. $this->tex = $tex;
  30. $this->params = $params;
  31. }
  32. function setOutputMode( $mode ) {
  33. $this->mode = $mode;
  34. }
  35. function render() {
  36. global $wgTmpDirectory;
  37. global $wgTexvc, $wgMathCheckFiles, $wgTexvcBackgroundColor;
  38. if( $this->mode == MW_MATH_SOURCE ) {
  39. # No need to render or parse anything more!
  40. # New lines are replaced with spaces, which avoids confusing our parser (bugs 23190, 22818)
  41. return ('<span class="tex" dir="ltr">$ ' . str_replace( "\n", " ", htmlspecialchars( $this->tex ) ) . ' $</span>');
  42. }
  43. if( $this->tex == '' ) {
  44. return; # bug 8372
  45. }
  46. if( !$this->_recall() ) {
  47. if( $wgMathCheckFiles ) {
  48. # Ensure that the temp and output directories are available before continuing...
  49. if( !file_exists( $wgTmpDirectory ) ) {
  50. if( !wfMkdirParents( $wgTmpDirectory, null, __METHOD__ ) ) {
  51. return $this->_error( 'math_bad_tmpdir' );
  52. }
  53. } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
  54. return $this->_error( 'math_bad_tmpdir' );
  55. }
  56. }
  57. if( !is_executable( $wgTexvc ) ) {
  58. return $this->_error( 'math_notexvc' );
  59. }
  60. $cmd = $wgTexvc . ' ' .
  61. escapeshellarg( $wgTmpDirectory ).' '.
  62. escapeshellarg( $wgTmpDirectory ).' '.
  63. escapeshellarg( $this->tex ).' '.
  64. escapeshellarg( 'UTF-8' ).' '.
  65. escapeshellarg( $wgTexvcBackgroundColor );
  66. if ( wfIsWindows() ) {
  67. # Invoke it within cygwin sh, because texvc expects sh features in its default shell
  68. $cmd = 'sh -c ' . wfEscapeShellArg( $cmd );
  69. }
  70. wfDebug( "TeX: $cmd\n" );
  71. $contents = wfShellExec( $cmd );
  72. wfDebug( "TeX output:\n $contents\n---\n" );
  73. if ( strlen( $contents ) == 0 ) {
  74. return $this->_error( 'math_unknown_error' );
  75. }
  76. $retval = substr( $contents, 0, 1 );
  77. $errmsg = '';
  78. if ( ( $retval == 'C' ) || ( $retval == 'M' ) || ( $retval == 'L' ) ) {
  79. if ( $retval == 'C' ) {
  80. $this->conservativeness = 2;
  81. } elseif ( $retval == 'M' ) {
  82. $this->conservativeness = 1;
  83. } else {
  84. $this->conservativeness = 0;
  85. }
  86. $outdata = substr( $contents, 33 );
  87. $i = strpos( $outdata, "\000" );
  88. $this->html = substr( $outdata, 0, $i );
  89. $this->mathml = substr( $outdata, $i + 1 );
  90. } elseif ( ( $retval == 'c' ) || ( $retval == 'm' ) || ( $retval == 'l' ) ) {
  91. $this->html = substr( $contents, 33 );
  92. if ( $retval == 'c' ) {
  93. $this->conservativeness = 2;
  94. } elseif ( $retval == 'm' ) {
  95. $this->conservativeness = 1;
  96. } else {
  97. $this->conservativeness = 0;
  98. }
  99. $this->mathml = null;
  100. } elseif ( $retval == 'X' ) {
  101. $this->html = null;
  102. $this->mathml = substr( $contents, 33 );
  103. $this->conservativeness = 0;
  104. } elseif ( $retval == '+' ) {
  105. $this->html = null;
  106. $this->mathml = null;
  107. $this->conservativeness = 0;
  108. } else {
  109. $errbit = htmlspecialchars( substr( $contents, 1 ) );
  110. switch( $retval ) {
  111. case 'E':
  112. $errmsg = $this->_error( 'math_lexing_error', $errbit );
  113. break;
  114. case 'S':
  115. $errmsg = $this->_error( 'math_syntax_error', $errbit );
  116. break;
  117. case 'F':
  118. $errmsg = $this->_error( 'math_unknown_function', $errbit );
  119. break;
  120. default:
  121. $errmsg = $this->_error( 'math_unknown_error', $errbit );
  122. }
  123. }
  124. if ( !$errmsg ) {
  125. $this->hash = substr( $contents, 1, 32 );
  126. }
  127. wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
  128. if ( $errmsg ) {
  129. return $errmsg;
  130. }
  131. if ( !preg_match( "/^[a-f0-9]{32}$/", $this->hash ) ) {
  132. return $this->_error( 'math_unknown_error' );
  133. }
  134. if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
  135. return $this->_error( 'math_image_error' );
  136. }
  137. if( filesize( "$wgTmpDirectory/{$this->hash}.png" ) == 0 ) {
  138. return $this->_error( 'math_image_error' );
  139. }
  140. $hashpath = $this->_getHashPath();
  141. if( !file_exists( $hashpath ) ) {
  142. wfSuppressWarnings();
  143. $ret = wfMkdirParents( $hashpath, 0755, __METHOD__ );
  144. wfRestoreWarnings();
  145. if( !$ret ) {
  146. return $this->_error( 'math_bad_output' );
  147. }
  148. } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
  149. return $this->_error( 'math_bad_output' );
  150. }
  151. if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
  152. return $this->_error( 'math_output_error' );
  153. }
  154. # Now save it back to the DB:
  155. if ( !wfReadOnly() ) {
  156. $outmd5_sql = pack( 'H32', $this->hash );
  157. $md5_sql = pack( 'H32', $this->md5 ); # Binary packed, not hex
  158. $dbw = wfGetDB( DB_MASTER );
  159. $dbw->replace(
  160. 'math',
  161. array( 'math_inputhash' ),
  162. array(
  163. 'math_inputhash' => $dbw->encodeBlob( $md5_sql ),
  164. 'math_outputhash' => $dbw->encodeBlob( $outmd5_sql ),
  165. 'math_html_conservativeness' => $this->conservativeness,
  166. 'math_html' => $this->html,
  167. 'math_mathml' => $this->mathml,
  168. ),
  169. __METHOD__
  170. );
  171. }
  172. // If we're replacing an older version of the image, make sure it's current.
  173. global $wgUseSquid;
  174. if ( $wgUseSquid ) {
  175. $urls = array( $this->_mathImageUrl() );
  176. $u = new SquidUpdate( $urls );
  177. $u->doUpdate();
  178. }
  179. }
  180. return $this->_doRender();
  181. }
  182. function _error( $msg, $append = '' ) {
  183. $mf = htmlspecialchars( wfMsg( 'math_failure' ) );
  184. $errmsg = htmlspecialchars( wfMsg( $msg ) );
  185. $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
  186. return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
  187. }
  188. function _recall() {
  189. global $wgMathDirectory, $wgMathCheckFiles;
  190. $this->md5 = md5( $this->tex );
  191. $dbr = wfGetDB( DB_SLAVE );
  192. $rpage = $dbr->selectRow(
  193. 'math',
  194. array(
  195. 'math_outputhash', 'math_html_conservativeness', 'math_html',
  196. 'math_mathml'
  197. ),
  198. array(
  199. 'math_inputhash' => $dbr->encodeBlob( pack( "H32", $this->md5 ) ) # Binary packed, not hex
  200. ),
  201. __METHOD__
  202. );
  203. if( $rpage !== false ) {
  204. # Tailing 0x20s can get dropped by the database, add it back on if necessary:
  205. $xhash = unpack( 'H32md5', $dbr->decodeBlob( $rpage->math_outputhash ) . " " );
  206. $this->hash = $xhash['md5'];
  207. $this->conservativeness = $rpage->math_html_conservativeness;
  208. $this->html = $rpage->math_html;
  209. $this->mathml = $rpage->math_mathml;
  210. $filename = $this->_getHashPath() . "/{$this->hash}.png";
  211. if( !$wgMathCheckFiles ) {
  212. // Short-circuit the file existence & migration checks
  213. return true;
  214. }
  215. if( file_exists( $filename ) ) {
  216. if( filesize( $filename ) == 0 ) {
  217. // Some horrible error corrupted stuff :(
  218. wfSuppressWarnings();
  219. unlink( $filename );
  220. wfRestoreWarnings();
  221. } else {
  222. return true;
  223. }
  224. }
  225. if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
  226. $hashpath = $this->_getHashPath();
  227. if( !file_exists( $hashpath ) ) {
  228. wfSuppressWarnings();
  229. $ret = wfMkdirParents( $hashpath, 0755, __METHOD__ );
  230. wfRestoreWarnings();
  231. if( !$ret ) {
  232. return false;
  233. }
  234. } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
  235. return false;
  236. }
  237. if ( function_exists( 'link' ) ) {
  238. return link( $wgMathDirectory . "/{$this->hash}.png",
  239. $hashpath . "/{$this->hash}.png" );
  240. } else {
  241. return rename( $wgMathDirectory . "/{$this->hash}.png",
  242. $hashpath . "/{$this->hash}.png" );
  243. }
  244. }
  245. }
  246. # Missing from the database and/or the render cache
  247. return false;
  248. }
  249. /**
  250. * Select among PNG, HTML, or MathML output depending on
  251. */
  252. function _doRender() {
  253. if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
  254. return Xml::tags( 'math',
  255. $this->_attribs( 'math',
  256. array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ),
  257. $this->mathml );
  258. }
  259. if ( ( $this->mode == MW_MATH_PNG ) || ( $this->html == '' ) ||
  260. ( ( $this->mode == MW_MATH_SIMPLE ) && ( $this->conservativeness != 2 ) ) ||
  261. ( ( $this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML ) && ( $this->conservativeness == 0 ) )
  262. )
  263. {
  264. return $this->_linkToMathImage();
  265. } else {
  266. return Xml::tags( 'span',
  267. $this->_attribs( 'span',
  268. array( 'class' => 'texhtml',
  269. 'dir' => 'ltr'
  270. ) ),
  271. $this->html
  272. );
  273. }
  274. }
  275. function _attribs( $tag, $defaults = array(), $overrides = array() ) {
  276. $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
  277. $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
  278. $attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
  279. return $attribs;
  280. }
  281. function _linkToMathImage() {
  282. $url = $this->_mathImageUrl();
  283. return Xml::element( 'img',
  284. $this->_attribs(
  285. 'img',
  286. array(
  287. 'class' => 'tex',
  288. 'alt' => $this->tex
  289. ),
  290. array(
  291. 'src' => $url
  292. )
  293. )
  294. );
  295. }
  296. function _mathImageUrl() {
  297. global $wgMathPath;
  298. $dir = $this->_getHashSubPath();
  299. return "$wgMathPath/$dir/{$this->hash}.png";
  300. }
  301. function _getHashPath() {
  302. global $wgMathDirectory;
  303. $path = $wgMathDirectory . '/' . $this->_getHashSubPath();
  304. wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
  305. return $path;
  306. }
  307. function _getHashSubPath() {
  308. return substr( $this->hash, 0, 1)
  309. . '/' . substr( $this->hash, 1, 1 )
  310. . '/' . substr( $this->hash, 2, 1 );
  311. }
  312. public static function renderMath( $tex, $params = array(), ParserOptions $parserOptions = null ) {
  313. $math = new MathRenderer( $tex, $params );
  314. if ( $parserOptions ) {
  315. $math->setOutputMode( $parserOptions->getMath() );
  316. }
  317. return $math->render();
  318. }
  319. }