PageRenderTime 38ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/mediawiki-integration/source/php/mediawiki/includes/Math.php

https://code.google.com/
PHP | 274 lines | 213 code | 39 blank | 22 comment | 79 complexity | 841d6fbc300be71a2107230f82a67cd8 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * Contain everything related to <math> </math> parsing
  4. * @package MediaWiki
  5. */
  6. /**
  7. * Takes LaTeX fragments, sends them to a helper program (texvc) for rendering
  8. * to rasterized PNG and HTML and MathML approximations. An appropriate
  9. * rendering form is picked and returned.
  10. *
  11. * by Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
  12. *
  13. * @package MediaWiki
  14. */
  15. class MathRenderer {
  16. var $mode = MW_MATH_MODERN;
  17. var $tex = '';
  18. var $inputhash = '';
  19. var $hash = '';
  20. var $html = '';
  21. var $mathml = '';
  22. var $conservativeness = 0;
  23. function MathRenderer( $tex ) {
  24. $this->tex = $tex;
  25. }
  26. function setOutputMode( $mode ) {
  27. $this->mode = $mode;
  28. }
  29. function render() {
  30. global $wgTmpDirectory, $wgInputEncoding;
  31. global $wgTexvc;
  32. $fname = 'MathRenderer::render';
  33. if( $this->mode == MW_MATH_SOURCE ) {
  34. # No need to render or parse anything more!
  35. return ('$ '.htmlspecialchars( $this->tex ).' $');
  36. }
  37. if( $this->tex == '' ) {
  38. return; # bug 8372
  39. }
  40. if( !$this->_recall() ) {
  41. # Ensure that the temp and output directories are available before continuing...
  42. if( !file_exists( $wgTmpDirectory ) ) {
  43. if( !@mkdir( $wgTmpDirectory ) ) {
  44. return $this->_error( 'math_bad_tmpdir' );
  45. }
  46. } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
  47. return $this->_error( 'math_bad_tmpdir' );
  48. }
  49. if( function_exists( 'is_executable' ) && !is_executable( $wgTexvc ) ) {
  50. return $this->_error( 'math_notexvc' );
  51. }
  52. $cmd = $wgTexvc . ' ' .
  53. escapeshellarg( $wgTmpDirectory ).' '.
  54. escapeshellarg( $wgTmpDirectory ).' '.
  55. escapeshellarg( $this->tex ).' '.
  56. escapeshellarg( $wgInputEncoding );
  57. if ( wfIsWindows() ) {
  58. # Invoke it within cygwin sh, because texvc expects sh features in its default shell
  59. $cmd = 'sh -c ' . wfEscapeShellArg( $cmd );
  60. }
  61. wfDebug( "TeX: $cmd\n" );
  62. $contents = `$cmd`;
  63. wfDebug( "TeX output:\n $contents\n---\n" );
  64. if (strlen($contents) == 0) {
  65. return $this->_error( 'math_unknown_error' );
  66. }
  67. $retval = substr ($contents, 0, 1);
  68. $errmsg = '';
  69. if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) {
  70. if ($retval == 'C') {
  71. $this->conservativeness = 2;
  72. } else if ($retval == 'M') {
  73. $this->conservativeness = 1;
  74. } else {
  75. $this->conservativeness = 0;
  76. }
  77. $outdata = substr ($contents, 33);
  78. $i = strpos($outdata, "\000");
  79. $this->html = substr($outdata, 0, $i);
  80. $this->mathml = substr($outdata, $i+1);
  81. } else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) {
  82. $this->html = substr ($contents, 33);
  83. if ($retval == 'c') {
  84. $this->conservativeness = 2;
  85. } else if ($retval == 'm') {
  86. $this->conservativeness = 1;
  87. } else {
  88. $this->conservativeness = 0;
  89. }
  90. $this->mathml = NULL;
  91. } else if ($retval == 'X') {
  92. $this->html = NULL;
  93. $this->mathml = substr ($contents, 33);
  94. $this->conservativeness = 0;
  95. } else if ($retval == '+') {
  96. $this->html = NULL;
  97. $this->mathml = NULL;
  98. $this->conservativeness = 0;
  99. } else {
  100. $errbit = htmlspecialchars( substr($contents, 1) );
  101. switch( $retval ) {
  102. case 'E': $errmsg = $this->_error( 'math_lexing_error', $errbit );
  103. case 'S': $errmsg = $this->_error( 'math_syntax_error', $errbit );
  104. case 'F': $errmsg = $this->_error( 'math_unknown_function', $errbit );
  105. default: $errmsg = $this->_error( 'math_unknown_error', $errbit );
  106. }
  107. }
  108. if ( !$errmsg ) {
  109. $this->hash = substr ($contents, 1, 32);
  110. }
  111. wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
  112. if ( $errmsg ) {
  113. return $errmsg;
  114. }
  115. if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
  116. return $this->_error( 'math_unknown_error' );
  117. }
  118. if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
  119. return $this->_error( 'math_image_error' );
  120. }
  121. $hashpath = $this->_getHashPath();
  122. if( !file_exists( $hashpath ) ) {
  123. if( !@wfMkdirParents( $hashpath, 0755 ) ) {
  124. return $this->_error( 'math_bad_output' );
  125. }
  126. } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
  127. return $this->_error( 'math_bad_output' );
  128. }
  129. if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
  130. return $this->_error( 'math_output_error' );
  131. }
  132. # Now save it back to the DB:
  133. if ( !wfReadOnly() ) {
  134. $outmd5_sql = pack('H32', $this->hash);
  135. $md5_sql = pack('H32', $this->md5); # Binary packed, not hex
  136. $dbw =& wfGetDB( DB_MASTER );
  137. $dbw->replace( 'math', array( 'math_inputhash' ),
  138. array(
  139. 'math_inputhash' => $md5_sql,
  140. 'math_outputhash' => $outmd5_sql,
  141. 'math_html_conservativeness' => $this->conservativeness,
  142. 'math_html' => $this->html,
  143. 'math_mathml' => $this->mathml,
  144. ), $fname, array( 'IGNORE' )
  145. );
  146. }
  147. }
  148. return $this->_doRender();
  149. }
  150. function _error( $msg, $append = '' ) {
  151. $mf = htmlspecialchars( wfMsg( 'math_failure' ) );
  152. $errmsg = htmlspecialchars( wfMsg( $msg ) );
  153. $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
  154. return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
  155. }
  156. function _recall() {
  157. global $wgMathDirectory;
  158. $fname = 'MathRenderer::_recall';
  159. $this->md5 = md5( $this->tex );
  160. $dbr =& wfGetDB( DB_SLAVE );
  161. $rpage = $dbr->selectRow( 'math',
  162. array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
  163. array( 'math_inputhash' => pack("H32", $this->md5)), # Binary packed, not hex
  164. $fname
  165. );
  166. if( $rpage !== false ) {
  167. # Tailing 0x20s can get dropped by the database, add it back on if necessary:
  168. $xhash = unpack( 'H32md5', $rpage->math_outputhash . " " );
  169. $this->hash = $xhash ['md5'];
  170. $this->conservativeness = $rpage->math_html_conservativeness;
  171. $this->html = $rpage->math_html;
  172. $this->mathml = $rpage->math_mathml;
  173. if( file_exists( $this->_getHashPath() . "/{$this->hash}.png" ) ) {
  174. return true;
  175. }
  176. if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
  177. $hashpath = $this->_getHashPath();
  178. if( !file_exists( $hashpath ) ) {
  179. if( !@wfMkdirParents( $hashpath, 0755 ) ) {
  180. return false;
  181. }
  182. } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
  183. return false;
  184. }
  185. if ( function_exists( "link" ) ) {
  186. return link ( $wgMathDirectory . "/{$this->hash}.png",
  187. $hashpath . "/{$this->hash}.png" );
  188. } else {
  189. return rename ( $wgMathDirectory . "/{$this->hash}.png",
  190. $hashpath . "/{$this->hash}.png" );
  191. }
  192. }
  193. }
  194. # Missing from the database and/or the render cache
  195. return false;
  196. }
  197. /**
  198. * Select among PNG, HTML, or MathML output depending on
  199. */
  200. function _doRender() {
  201. if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
  202. return "<math xmlns='http://www.w3.org/1998/Math/MathML'>{$this->mathml}</math>";
  203. }
  204. if (($this->mode == MW_MATH_PNG) || ($this->html == '') ||
  205. (($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) ||
  206. (($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) {
  207. return $this->_linkToMathImage();
  208. } else {
  209. return '<span class="texhtml">'.$this->html.'</span>';
  210. }
  211. }
  212. function _linkToMathImage() {
  213. global $wgMathPath;
  214. $url = htmlspecialchars( "$wgMathPath/" . substr($this->hash, 0, 1)
  215. .'/'. substr($this->hash, 1, 1) .'/'. substr($this->hash, 2, 1)
  216. . "/{$this->hash}.png" );
  217. $alt = trim(str_replace("\n", ' ', htmlspecialchars( $this->tex )));
  218. return "<img class='tex' src=\"$url\" alt=\"$alt\" />";
  219. }
  220. function _getHashPath() {
  221. global $wgMathDirectory;
  222. $path = $wgMathDirectory .'/'. substr($this->hash, 0, 1)
  223. .'/'. substr($this->hash, 1, 1)
  224. .'/'. substr($this->hash, 2, 1);
  225. wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
  226. return $path;
  227. }
  228. public static function renderMath( $tex ) {
  229. global $wgUser;
  230. $math = new MathRenderer( $tex );
  231. $math->setOutputMode( $wgUser->getOption('math'));
  232. return $math->render();
  233. }
  234. }
  235. ?>