ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç thirdpartylibs.xml000064400000000431152266364460010346 0ustar00 AlgParser.pm WeBWorK 2.4.9+ GPL 2.0+ false db/renamedclasses.php000064400000001767152266364460010664 0ustar00. /** * Renamed classes for the filter_algebra plugin. * * @package filter_algebra * @copyright Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die; $renamedclasses = [ 'filter_algebra' => \filter_algebra\text_filter::class, ]; lang/en/filter_algebra.php000064400000002202152266364460011554 0ustar00. /** * Strings for component 'filter_algebra', language 'en', branch 'MOODLE_20_STABLE' * * @package filter_algebra * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['filtername'] = 'Algebra notation'; $string['algebraicexpression'] = 'Algebraic expression'; $string['privacy:metadata'] = 'The Algebra notation plugin does not store any personal data.'; version.php000064400000002255152266364460006764 0ustar00. /** * Version details * * @package filter * @subpackage algebra * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'filter_algebra'; // Full name of the plugin (used for diagnostics) filter.php000064400000002326152266364460006563 0ustar00. /** * File only retained to prevent fatal errors in code that tries to require/include this. * * @todo MDL-82708 delete this file as part of Moodle 6.0 development. * @deprecated This file is no longer required in Moodle 4.5+. * @package filter_algebra * @copyright Zbigniew Fiedorowicz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); debugging('This file is no longer required in Moodle 4.5+. Please do not include/require it.', DEBUG_DEVELOPER); algebradebug.php000064400000036571152266364460007713 0ustar00libdir.'/filelib.php'); require_once($CFG->dirroot.'/filter/tex/lib.php'); $action = optional_param('action', '', PARAM_ALPHANUM); $algebra = optional_param('algebra', '', PARAM_RAW); require_login(); require_capability('moodle/site:config', context_system::instance()); if ($action || $algebra) { require_sesskey(); } if ($algebra && $action) { $md5 = md5($algebra); if ($action == 'ShowDB' || $action == 'DeleteDB') { $texcache = $DB->get_record("cache_filters", array("filter"=>"algebra", "md5key"=>$md5)); } if ($action == 'ShowDB') { if ($texcache) { $output = "DB cache_filters entry for $algebra\n"; $output .= "id = $texcache->id\n"; $output .= "filter = $texcache->filter\n"; $output .= "version = $texcache->version\n"; $output .= "md5key = $texcache->md5key\n"; $output .= "rawtext = $texcache->rawtext\n"; $output .= "timemodified = $texcache->timemodified\n"; } else { $output = "DB cache_filters entry for $algebra not found\n"; } } if ($action == 'DeleteDB') { if ($texcache) { $output = "Deleting DB cache_filters entry for $algebra\n"; $result = $DB->delete_records("cache_filters", array("id"=>$texcache->id)); if ($result) { $result = 1; } else { $result = 0; } $output .= "Number of records deleted = $result\n"; } else { $output = "Could not delete DB cache_filters entry for $algebra\nbecause it could not be found.\n"; } } if ($action == 'TeXStage1') { $output = algebra2tex($algebra); } if ($action == 'TexStage2') { $output = algebra2tex($algebra); $output = refineTeX($output); } if ($action == 'ShowImage'|| $action == 'SlashArguments') { $output = algebra2tex($algebra); $output = refineTeX($output); if ($action == 'ShowImage') { tex2image($output, $md5); } else { slasharguments($output, $md5); } } else { outputText($output); } exit; } function algebra2tex($algebra) { global $CFG; $algebra = str_replace('<','<',$algebra); $algebra = str_replace('>','>',$algebra); $algebra = str_replace('<>','#',$algebra); $algebra = str_replace('<=','%',$algebra); $algebra = str_replace('>=','!',$algebra); $algebra = preg_replace('/([=><%!#] *)-/',"\$1 zeroplace -",$algebra); $algebra = str_replace('delta','zdelta',$algebra); $algebra = str_replace('beta','bita',$algebra); $algebra = str_replace('theta','thita',$algebra); $algebra = str_replace('zeta','zita',$algebra); $algebra = str_replace('eta','xeta',$algebra); $algebra = str_replace('epsilon','zepslon',$algebra); $algebra = str_replace('upsilon','zupslon',$algebra); $algebra = preg_replace('!\r\n?!',' ',$algebra); $algebra = escapeshellarg($algebra); if ( (PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows") ) { $cmd = "cd $CFG->dirroot\\filter\\algebra & algebra2tex.pl x/2"; $test = `$cmd`; if ($test != '\frac{x}{2}') { echo "There is a problem with either Perl or the script algebra2tex.pl
"; $ecmd = $cmd . " 2>&1"; echo `$ecmd` . "
\n"; echo "The shell command
$cmd
returned status = $status
\n"; $commandpath = "$CFG->dirroot\\filter\\algebra\\algebra2tex.pl"; if (file_exists($commandpath)) { echo "The file permissions of algebra2tex.pl are: " . decoct(fileperms($commandpath)) . "
"; } die; } $cmd = "cd $CFG->dirroot\\filter\\algebra & algebra2tex.pl $algebra"; } else { $cmd = "cd $CFG->dirroot/filter/algebra; ./algebra2tex.pl x/2"; $test = `$cmd`; if ($test != '\frac{x}{2}') { echo "There is a problem with either Perl or the script algebra2tex.pl
"; $ecmd = $cmd . " 2>&1"; echo `$ecmd` . "
\n"; echo "The shell command
$cmd
returned status = $status
\n"; $commandpath = "$CFG->dirroot/filter/algebra/algebra2tex.pl"; if (file_exists($commandpath)) { echo "The file permissions of algebra2tex.pl are: " . decoct(fileperms($commandpath)) . "
"; } die; } $cmd = "cd $CFG->dirroot/filter/algebra; ./algebra2tex.pl $algebra"; } $texexp = `$cmd`; return $texexp; } function refineTeX($texexp) { $texexp = str_replace('zeroplace','',$texexp); $texexp = str_replace('#','\not= ',$texexp); $texexp = str_replace('%','\leq ',$texexp); $texexp = str_replace('!','\geq ',$texexp); $texexp = str_replace('\left{','{',$texexp); $texexp = str_replace('\right}','}',$texexp); $texexp = str_replace('\fun',' ',$texexp); $texexp = str_replace('infty','\infty',$texexp); $texexp = str_replace('alpha','\alpha',$texexp); $texexp = str_replace('gamma','\gamma',$texexp); $texexp = str_replace('iota','\iota',$texexp); $texexp = str_replace('kappa','\kappa',$texexp); $texexp = str_replace('lambda','\lambda',$texexp); $texexp = str_replace('mu','\mu',$texexp); $texexp = str_replace('nu','\nu',$texexp); $texexp = str_replace('xi','\xi',$texexp); $texexp = str_replace('rho','\rho',$texexp); $texexp = str_replace('sigma','\sigma',$texexp); $texexp = str_replace('tau','\tau',$texexp); $texexp = str_replace('phi','\phi',$texexp); $texexp = str_replace('chi','\chi',$texexp); $texexp = str_replace('psi','\psi',$texexp); $texexp = str_replace('omega','\omega',$texexp); $texexp = str_replace('zdelta','\delta',$texexp); $texexp = str_replace('bita','\beta',$texexp); $texexp = str_replace('thita','\theta',$texexp); $texexp = str_replace('zita','\zeta',$texexp); $texexp = str_replace('xeta','\eta',$texexp); $texexp = str_replace('zepslon','\epsilon',$texexp); $texexp = str_replace('zupslon','\upsilon',$texexp); $texexp = str_replace('\mbox{logten}','\mbox{log}_{10}',$texexp); $texexp = str_replace('\mbox{acos}','\mbox{cos}^{-1}',$texexp); $texexp = str_replace('\mbox{asin}','\mbox{sin}^{-1}',$texexp); $texexp = str_replace('\mbox{atan}','\mbox{tan}^{-1}',$texexp); $texexp = str_replace('\mbox{asec}','\mbox{sec}^{-1}',$texexp); $texexp = str_replace('\mbox{acsc}','\mbox{csc}^{-1}',$texexp); $texexp = str_replace('\mbox{acot}','\mbox{cot}^{-1}',$texexp); $texexp = str_replace('\mbox{acosh}','\mbox{cosh}^{-1}',$texexp); $texexp = str_replace('\mbox{asinh}','\mbox{sinh}^{-1}',$texexp); $texexp = str_replace('\mbox{atanh}','\mbox{tanh}^{-1}',$texexp); $texexp = str_replace('\mbox{asech}','\mbox{sech}^{-1}',$texexp); $texexp = str_replace('\mbox{acsch}','\mbox{csch}^{-1}',$texexp); $texexp = str_replace('\mbox{acoth}','\mbox{coth}^{-1}',$texexp); $texexp = preg_replace('/\\\sqrt{(.+?),(.+?)}/s','\sqrt['. "\$2]{\$1}",$texexp); $texexp = preg_replace('/\\\mbox{abs}\\\left\((.+?)\\\right\)/s',"|\$1|",$texexp); $texexp = preg_replace('/\\\log\\\left\((.+?),(.+?)\\\right\)/s','\log_{'. "\$2}\\left(\$1\\right)",$texexp); $texexp = preg_replace('/(\\\cos|\\\sin|\\\tan|\\\sec|\\\csc|\\\cot)([h]*)\\\left\((.+?),(.+?)\\\right\)/s',"\$1\$2^{". "\$4}\\left(\$3\\right)",$texexp); $texexp = preg_replace('/\\\int\\\left\((.+?),(.+?),(.+?)\\\right\)/s','\int_'. "{\$2}^{\$3}\$1 ",$texexp); $texexp = preg_replace('/\\\int\\\left\((.+?d[a-z])\\\right\)/s','\int '. "\$1 ",$texexp); $texexp = preg_replace('/\\\lim\\\left\((.+?),(.+?),(.+?)\\\right\)/s','\lim_'. "{\$2\\to \$3}\$1 ",$texexp); return $texexp; } function outputText($texexp) { header("Content-type: text/html; charset=utf-8"); echo "
\n";
  if ($texexp) {
    $texexp = str_replace('<','<',$texexp);
    $texexp = str_replace('>','>',$texexp);
    $texexp = str_replace('"','"',$texexp);
    echo "$texexp\n\n";
  } else {
    echo "No text output available\n\n";
  }
  echo "
\n"; } function tex2image($texexp, $md5, $return=false) { global $CFG; if (!$texexp) { echo 'No tex expresion specified'; return; } $texexp = '\Large ' . $texexp; $image = $md5 . ".gif"; $filetype = 'image/gif'; if (!file_exists("$CFG->dataroot/filter/algebra")) { make_upload_directory("filter/algebra"); } $pathname = "$CFG->dataroot/filter/algebra/$image"; if (file_exists($pathname)) { unlink($pathname); } $commandpath = filter_tex_get_executable(true); $cmd = filter_tex_get_cmd($pathname, $texexp); system($cmd, $status); if ($return) { return $image; } if (file_exists($pathname)) { send_file($pathname, $image); } else { $ecmd = "$cmd 2>&1"; echo `$ecmd` . "
\n"; echo "The shell command
$cmd
returned status = $status
\n"; if ($status == 4) { echo "Status corresponds to illegal instruction
\n"; } else if ($status == 11) { echo "Status corresponds to bus error
\n"; } else if ($status == 22) { echo "Status corresponds to abnormal termination
\n"; } if (file_exists($commandpath)) { echo "File size of mimetex executable $commandpath is " . filesize($commandpath) . "
"; echo "The file permissions are: " . decoct(fileperms($commandpath)) . "
"; if (function_exists("md5_file")) { echo "The md5 checksum of the file is " . md5_file($commandpath) . "
"; } else { $handle = fopen($commandpath,"rb"); $contents = fread($handle,16384); fclose($handle); echo "The md5 checksum of the first 16384 bytes is " . md5($contents) . "
"; } } else { echo "mimetex executable $commandpath not found!
"; } echo "Image not found!"; } } function slasharguments($texexp, $md5) { global $CFG; $admin = $CFG->wwwroot.'/'.$CFG->admin.'/settings.php?section=http'; $image = tex2image($texexp,$md5,true); echo "

If the following image displays correctly, set your "; echo "Administration->Server->HTTP "; echo "setting for slasharguments to file.php/1/pic.jpg: "; echo "

\n"; echo "

Otherwise set it to file.php?file=/1/pic.jpg "; echo "It should display correctly as "; echo "

\n"; echo "

If neither equation image displays correctly, please seek "; echo "further help at moodle.org at the "; echo ""; echo "Mathematics Tools Forum

"; } ?> Algebra Filter Debugger

Please enter an algebraic expression without any surrounding @@ into the text box below. (Click here for help.)

  1. First click on this button to see the cache_filters database entry for this expression.
  2. If the database entry looks corrupt, click on this button to delete it:
  3. Now click on this button . A preliminary translation into TeX will appear in the box below.
  4. Next click on this button . A more refined translation into TeX will appear in the box below.
  5. Then click on this button to show a graphic image of the algebraic expression.
  6. Finally check your slash arguments setting




Debugging Help

First here is a brief overview on how the algebra filter works. It takes an algebra expression and first translates it into TeX. It first looks for the TeX translation in the Moodle database in the table cache_filters in the field rawtext. If not found, it passes the algebraic expression to the Perl script algebra2tex.pl, which also uses the Perl library AlgParser.pm. It then saves the TeX translation in the database for subsequent uses and passes the TeX to the mimetex executable to be converted to a gif image. Here are a few common things that can go wrong and some suggestions on how you might try to fix them.

  1. Something had gone wrong on a previous occasion when the filter tried to translate this expression. Then the database entry for that expression contains a bad TeX translation in the rawtext field (usually blank). You can fix this by clicking on "Delete DB Entry"
  2. The First Stage TeX Translation gives a "No text output available" message. If your server is running Windows, this may be due to the fact that you haven't installed Perl or didn't install it correctly. If your server is running some version of Unix (e.g. Linux), then this may be due to your Perl binary being installed in a nonstandard location. To fix this edit the first line of the algebra2tex.pl script. Another possible problem which may affect both Unix and Windows servers is that the web server doesn't have execute permission on the algebra2tex.pl script. In that case change permissions accordingly
  3. The Second Stage TeX Translation produces malformed TeX. This indicates a bug in the algebra filter. Post the original algebraic expression and the bad TeX translation in the Mathematics Tools forum in the Using Moodle course on moodle.org.
  4. The TeX to gif image conversion process does not work. If your server is running Unix, a likely cause is that the mimetex binary you are using is incompatible with your operating system. You can try compiling it from the C sources downloaded from http://www.forkosh.com/mimetex.zip. Lastly check the execute permissions on your mimetex binary, as outlined in item 2 above.
classes/privacy/provider.php000064400000002776152266364460012253 0ustar00. /** * Privacy Subsystem implementation for filter_algebra. * * @package filter_algebra * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace filter_algebra\privacy; defined('MOODLE_INTERNAL') || die(); /** * Privacy Subsystem for filter_algebra implementing null_provider. * * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } } classes/text_filter.php000064400000032320152266364460011261 0ustar00. namespace filter_algebra; use core\context\system as context_system; use core\output\actions\popup_action; use core\url; use stdClass; /** * Moodle - Filter for converting simple calculator-type algebraic expressions to cached gif images * * NOTE: This Moodle text filter converts algebraic expressions delimited * by either @@...@@ or by ... tags * first converts it to TeX using WeBWorK algebra parser Perl library * AlgParser.pm, part of the WeBWorK distribution obtained from * http://webhost.math.rochester.edu/downloadwebwork/ * then converts the TeX to gif images using * mimetex.cgi obtained from http://www.forkosh.com/mimetex.html authored by * John Forkosh john@forkosh.com. The mimetex.cgi ELF binary compiled for Linux i386 * as well as AlgParser.pm are included with this distribution. * Note that there may be patent restrictions on the production of gif images * in Canada and some parts of Western Europe and Japan until July 2004. * ------------------------------------------------------------------------- * You will then need to edit your moodle/config.php to invoke mathml_filter.php * ------------------------------------------------------------------------- * * @package filter_algebra * @subpackage algebra * @copyright 2004 Zbigniew Fiedorowicz fiedorow@math.ohio-state.edu * Originally based on code provided by Bruno Vernier bruno@vsbeducation.ca * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class text_filter extends \core_filters\text_filter { #[\Override] public function filter($text, array $options = []) { global $CFG, $DB; // Do a quick check using stripos to avoid unnecessary work. if (!preg_match('/ some algebraic input expression // or @@ some algebraic input expression @@. preg_match_all('/(.+?)<\/algebra>|@@(.+?)@@/is', $text, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $algebra = $matches[1][$i] . $matches[2][$i]; // Look for some common false positives, and skip processing them. if ($algebra == 'PLUGINFILE' || $algebra == 'DRAFTFILE') { // Raw pluginfile URL. continue; } if (preg_match('/^ -\d+(,\d+)? \+\d+(,\d+)? $/', $algebra)) { // Part of a unified diff. continue; } $algebra = str_replace('', '', $algebra); $algebra = str_replace('', '', $algebra); $algebra = str_replace('', '', $algebra); $algebra = str_replace('', '', $algebra); $align = "middle"; if (preg_match('/^align=bottom /', $algebra)) { $align = "text-bottom"; $algebra = preg_replace('/^align=bottom /', '', $algebra); } else if (preg_match('/^align=top /', $algebra)) { $align = "text-top"; $algebra = preg_replace('/^align=top /', '', $algebra); } $md5 = md5($algebra); $filename = $md5 . ".gif"; if (! $texcache = $DB->get_record("cache_filters", ["filter" => "algebra", "md5key" => $md5])) { $algebra = str_replace('<', '<', $algebra); $algebra = str_replace('>', '>', $algebra); $algebra = str_replace('<>', '#', $algebra); $algebra = str_replace('<=', '%', $algebra); $algebra = str_replace('>=', '!', $algebra); $algebra = preg_replace('/([=><%!#] *)-/', "\$1 zeroplace -", $algebra); $algebra = str_replace('delta', 'zdelta', $algebra); $algebra = str_replace('beta', 'bita', $algebra); $algebra = str_replace('theta', 'thita', $algebra); $algebra = str_replace('zeta', 'zita', $algebra); $algebra = str_replace('eta', 'xeta', $algebra); $algebra = str_replace('epsilon', 'zepslon', $algebra); $algebra = str_replace('upsilon', 'zupslon', $algebra); $algebra = preg_replace('!\r\n?!', ' ', $algebra); $algebra = escapeshellarg($algebra); if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) { $cmd = "cd $CFG->dirroot\\filter\\algebra & algebra2tex.pl $algebra"; } else { $cmd = "cd $CFG->dirroot/filter/algebra; ./algebra2tex.pl $algebra"; } $texexp = `$cmd`; if (preg_match('/parsehilight/', $texexp)) { $text = str_replace($matches[0][$i], "Syntax error: " . $texexp, $text); } else if ($texexp) { $texexp = str_replace('zeroplace', '', $texexp); $texexp = str_replace('#', '\not= ', $texexp); $texexp = str_replace('%', '\leq ', $texexp); $texexp = str_replace('!', '\geq ', $texexp); $texexp = str_replace('\left{', '{', $texexp); $texexp = str_replace('\right}', '}', $texexp); $texexp = str_replace('\fun', ' ', $texexp); $texexp = str_replace('infty', '\infty', $texexp); $texexp = str_replace('alpha', '\alpha', $texexp); $texexp = str_replace('gamma', '\gamma', $texexp); $texexp = str_replace('iota', '\iota', $texexp); $texexp = str_replace('kappa', '\kappa', $texexp); $texexp = str_replace('lambda', '\lambda', $texexp); $texexp = str_replace('mu', '\mu', $texexp); $texexp = str_replace('nu', '\nu', $texexp); $texexp = str_replace('xi', '\xi', $texexp); $texexp = str_replace('rho', '\rho', $texexp); $texexp = str_replace('sigma', '\sigma', $texexp); $texexp = str_replace('tau', '\tau', $texexp); $texexp = str_replace('phi', '\phi', $texexp); $texexp = str_replace('chi', '\chi', $texexp); $texexp = str_replace('psi', '\psi', $texexp); $texexp = str_replace('omega', '\omega', $texexp); $texexp = str_replace('zdelta', '\delta', $texexp); $texexp = str_replace('bita', '\beta', $texexp); $texexp = str_replace('thita', '\theta', $texexp); $texexp = str_replace('zita', '\zeta', $texexp); $texexp = str_replace('xeta', '\eta', $texexp); $texexp = str_replace('zepslon', '\epsilon', $texexp); $texexp = str_replace('zupslon', '\upsilon', $texexp); $texexp = str_replace('\mbox{logten}', '\mbox{log}_{10}', $texexp); $texexp = str_replace('\mbox{acos}', '\mbox{cos}^{-1}', $texexp); $texexp = str_replace('\mbox{asin}', '\mbox{sin}^{-1}', $texexp); $texexp = str_replace('\mbox{atan}', '\mbox{tan}^{-1}', $texexp); $texexp = str_replace('\mbox{asec}', '\mbox{sec}^{-1}', $texexp); $texexp = str_replace('\mbox{acsc}', '\mbox{csc}^{-1}', $texexp); $texexp = str_replace('\mbox{acot}', '\mbox{cot}^{-1}', $texexp); $texexp = str_replace('\mbox{acosh}', '\mbox{cosh}^{-1}', $texexp); $texexp = str_replace('\mbox{asinh}', '\mbox{sinh}^{-1}', $texexp); $texexp = str_replace('\mbox{atanh}', '\mbox{tanh}^{-1}', $texexp); $texexp = str_replace('\mbox{asech}', '\mbox{sech}^{-1}', $texexp); $texexp = str_replace('\mbox{acsch}', '\mbox{csch}^{-1}', $texexp); $texexp = str_replace('\mbox{acoth}', '\mbox{coth}^{-1}', $texexp); $texexp = preg_replace('/\\\sqrt{(.+?),(.+?)}/s', '\sqrt[' . "\$2]{\$1}", $texexp); $texexp = preg_replace('/\\\mbox{abs}\\\left\((.+?)\\\right\)/s', "|\$1|", $texexp); $texexp = preg_replace('/\\\log\\\left\((.+?),(.+?)\\\right\)/s', '\log_{' . "\$2}\\left(\$1\\right)", $texexp); $texexp = preg_replace( '/(\\\cos|\\\sin|\\\tan|\\\sec|\\\csc|\\\cot)([h]*)\\\left\((.+?),(.+?)\\\right\)/s', "\$1\$2^{" . "\$4}\\left(\$3\\right)", $texexp, ); $texexp = preg_replace('/\\\int\\\left\((.+?),(.+?),(.+?)\\\right\)/s', '\int_' . "{\$2}^{\$3}\$1 ", $texexp); $texexp = preg_replace('/\\\int\\\left\((.+?d[a-z])\\\right\)/s', '\int ' . "\$1 ", $texexp); $texexp = preg_replace('/\\\lim\\\left\((.+?),(.+?),(.+?)\\\right\)/s', '\lim_' . "{\$2\\to \$3}\$1 ", $texexp); // Remove a forbidden keyword. $texexp = str_replace('\mbox', '', $texexp); $texcache = new stdClass(); $texcache->filter = 'algebra'; $texcache->version = 1; $texcache->md5key = $md5; $texcache->rawtext = $texexp; $texcache->timemodified = time(); $DB->insert_record("cache_filters", $texcache, false); $text = str_replace($matches[0][$i], self::get_image_markup($filename, $texexp, 0, 0, $align), $text); } else { $text = str_replace($matches[0][$i], "Undetermined error: " . $matches[0][$i], $text); } } else { $text = str_replace($matches[0][$i], self::get_image_markup($filename, $texcache->rawtext), $text); } } return $text; } /** * Create image link. * * @param string $imagefile name of file * @param string $tex TeX notation (html entities already decoded) * @param int $height O means automatic * @param int $width O means automatic * @param string $align * @return string HTML markup */ protected function get_image_markup( string $imagefile, string $tex = "", int $height = 0, int $width = 0, string $align = 'middle', ): string { // Given the path to a picture file in a course, or a URL, // this function includes the picture in the page. global $CFG, $OUTPUT; $output = ""; $style = 'style="border:0px; vertical-align:' . $align . ';'; $title = ''; if ($tex) { $tex = html_entity_decode($tex, ENT_QUOTES, 'UTF-8'); $title = 'title="' . s($tex) . '"'; } if ($height) { $style .= " height:{$height}px;"; } if ($width) { $style .= " width:{$width}px;"; } $style .= '"'; $anchorcontents = ''; if ($imagefile) { $anchorcontents .= "\""slasharguments) { // Use this method if possible for better caching. $anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php/$imagefile"; } else { $anchorcontents .= "$CFG->wwwroot/filter/algebra/pix.php?file=$imagefile"; } $anchorcontents .= "\" $style />"; $imagefound = file_exists("$CFG->dataroot/filter/algebra/$imagefile"); if (!$imagefound && has_capability('moodle/site:config', context_system::instance())) { $link = '/filter/algebra/algebradebug.php'; $action = null; } else { $link = new url('/filter/tex/displaytex.php', ['texexp' => $tex]); // TODO: the popups do not work when text caching is enabled. $action = new popup_action('click', $link, 'popup', ['width' => 320, 'height' => 240]); } $output .= $OUTPUT->action_link($link, $anchorcontents, $action, ['title' => 'TeX']); } else { $output .= "Error: must pass URL or course"; } return $output; } } algebra2tex.pl000064400000000437152266364460007323 0ustar00#!/usr/bin/perl use lib '.'; use AlgParser; my $parser = new AlgParserWithImplicitExpand; my $ret; $ret = $parser -> parse($ARGV[0]); if ( ref($ret) ) { $parser -> tostring(); $parser -> normalize(); print $parser -> tolatex(); } else { print $parser->{htmlerror}; } pix.php000064400000004460152266364460006077 0ustar00libdir.'/filelib.php'); require_once($CFG->dirroot.'/filter/tex/lib.php'); $cmd = ''; // Initialise these variables $status = ''; $relativepath = get_file_argument(); $args = explode('/', trim($relativepath, '/')); if (count($args) == 1) { $image = $args[0]; $pathname = $CFG->dataroot.'/filter/algebra/'.$image; } else { throw new \moodle_exception('invalidarguments', 'error'); } if (!file_exists($pathname)) { $md5 = str_replace('.gif','',$image); if ($texcache = $DB->get_record('cache_filters', array('filter'=>'algebra', 'md5key'=>$md5))) { if (!file_exists($CFG->dataroot.'/filter/algebra')) { make_upload_directory('filter/algebra'); } $texexp = $texcache->rawtext; $texexp = str_replace('<','<',$texexp); $texexp = str_replace('>','>',$texexp); $texexp = preg_replace('!\r\n?!',' ',$texexp); $texexp = '\Large ' . $texexp; $cmd = filter_tex_get_cmd($pathname, $texexp); system($cmd, $status); } } if (file_exists($pathname)) { send_file($pathname, $image); } else { if (debugging()) { echo "The shell command
$cmd
returned status = $status
\n"; echo "Image not found!
"; echo "Please try the wwwroot/filter/algebra/algebradebug.php\">debugging script"; } else { echo "Image not found!
"; echo "Please try the wwwroot/filter/algebra/algebradebug.php\">debugging script
"; echo "Please turn on debug mode in site configuration to see more info here."; } } AlgParser.pm000064400000050002152266364460006775 0ustar00 ## Last modification: 8/3/00 by akp ## Originally written by Daniel Martin, Dept of Math, John Hopkins ## Additions and modifications were made by James Martino, Dept of Math, John Hopkins ## Additions and modifications were made by Arnold Pizer, Dept of Math, Univ of Rochester #use Data::Dumper; package AlgParser; use HTML::Entities; %close = (); sub new { my $package = shift; my (%ret); $ret{string} = ""; $ret{posarray} = []; $ret{parseerror} = ""; $ret{parseresult} = []; bless \%ret, $package; return \%ret; } sub inittokenizer { my($self, $string) = @_; $self->{string} =~ m/\G.*$/g; $self->{string} = undef; $self->{string} = $string; $self->{string} =~ m/\G.*$/g; $self->{string} =~ m/^/g; } $close{'{'} = '}'; $close{'['} = ']'; $close{'('} = ')'; $binoper3 = '(?:\\^|\\*\\*)'; $binoper2 = '[/*_,]'; $binoper1 = '[-+=><%!#]'; $openparen = '[{(\\[]'; $closeparen = '[})\\]]'; $varname = '[A-Za-z](?:_[0-9]+)?'; $specialvalue = '(?:e|pi|da|db|dc|de|df|dg|dh|di|dj|dk|dl|dm|dn|do|dp|dq|dr|ds|dt|du|dv|dw|dx|dy|dz|infty|alpha|bita|gamma|zita|thita|iota|kappa|lambda|mu|nu|xi|rho|sigma|tau|phi|chi|psi|omega|zepslon|zdelta|xeta|zupslon|zeroplace)'; $numberplain = '(?:\d+(?:\.\d*)?|\.\d+)'; $numberE = '(?:' . $numberplain . 'E[-+]?\d+)'; $number = '(?:' . $numberE . '|' . $numberplain . ')'; # # DPVC -- 2003/03/31 # added missing trig and inverse functions # #$trigfname = '(?:cosh|sinh|tanh|cot|(?:a(?:rc)?)?cos|(?:a(?:rc)?)?sin|' . # '(?:a(?:rc)?)?tan|sech?)'; $trigfname = '(?:(?:a(?:rc)?)?(?:sin|cos|tan|sec|csc|cot)h?)'; # # End DPVC # $otherfunc = '(?:exp|abs|logten|log|ln|sqrt|sgn|step|fact|int|lim|fun[a-zA-Z])'; $funcname = '(?:' . $otherfunc . '|' . $trigfname . ')'; $tokenregexp = "(?:($binoper3)|($binoper2)|($binoper1)|($openparen)|" . "($closeparen)|($funcname)|($specialvalue)|($varname)|" . "($numberE)|($number))"; sub nexttoken { my($self) = shift; $self->{string} =~ m/\G\s+/gc; my($p1) = pos($self->{string}) || 0; if(scalar($self->{string} =~ m/\G$tokenregexp/gc)) { push @{$self->{posarray}}, [$p1, pos($self->{string})]; if (defined($1)) {return ['binop3', $1];} if (defined($2)) {return ['binop2', $2];} if (defined($3)) {return ['binop1', $3];} if (defined($4)) {return ['openp', $4];} if (defined($5)) {return ['closep', $5];} if (defined($6)) {return ['func1', $6];} if (defined($7)) {return ['special', $7];} if (defined($8)) {return ['varname', $8];} if (defined($9)) {return ['numberE', $9];} if (defined($10)) {return ['number', $10];} } else { push @{$self->{posarray}}, [$p1, undef]; return undef; } } sub parse { my $self = shift; $self->{parseerror} = ""; $self->{posarray} = []; $self->{parseresult} = ['top', undef]; my (@backtrace) = (\$self->{parseresult}); my (@pushback) = (); my $currentref = \$self->{parseresult}->[1]; my $curenttok; my $sstring = shift; $self->inittokenizer($sstring); $currenttok = $self->nexttoken; if (!$currenttok) { if ($self->{string} =~ m/\G$/g) { return $self->error("empty"); } else { my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Illegal character at position $position", $mark); } } # so I can assume we got a token local $_; while ($currenttok) { $_ = $currenttok->[0]; /binop1/ && do { # check if we have a binary or unary operation here. if (defined(${$currentref})) { # binary - walk up the tree until we hit an open paren or the top while (${$currentref}->[0] !~ /^(openp|top)/) { $currentref = pop @backtrace; } my $index = ((${$currentref}->[0] eq 'top')?1:3); ${$currentref}->[$index] = ['binop1', $currenttok->[1], ${$currentref}->[$index], undef]; push @backtrace, $currentref; push @backtrace, \${$currentref}->[$index]; $currentref = \${$currentref}->[$index]->[3]; } else { # unary ${$currentref} = ['unop1', $currenttok->[1], undef]; push @backtrace, $currentref; $currentref = \${$currentref}->[2]; } }; /binop2/ && do { if (defined(${$currentref})) { # walk up the tree until an open paren, the top, binop1 or unop1 # I decide arbitrarily that -3*4 should be parsed as -(3*4) # instead of as (-3)*4. Not that it makes a difference. while (${$currentref}->[0] !~ /^(openp|top|binop1)/) { $currentref = pop @backtrace; } my $a = ${$currentref}->[0]; my $index = (($a eq 'top')?1:3); ${$currentref}->[$index] = ['binop2', $currenttok->[1], ${$currentref}->[$index], undef]; push @backtrace, $currentref; push @backtrace, \${$currentref}->[$index]; $currentref = \${$currentref}->[$index]->[3]; } else { # Error my($mark) = pop @{$self->{posarray}}; my $position =1+$mark->[0]; return $self->error("Didn't expect " . $currenttok->[1] . " at position $position" , $mark); } }; /binop3/ && do { if (defined(${$currentref})) { # walk up the tree until we need to stop # Note that the right-associated nature of ^ means we need to # stop walking backwards when we hit a ^ as well. while (${$currentref}->[0] !~ /^(openp|top|binop[123]|unop1)/) { $currentref = pop @backtrace; } my $a = ${$currentref}->[0]; my $index = ($a eq 'top')?1:($a eq 'unop1')?2:3; ${$currentref}->[$index] = ['binop3', $currenttok->[1], ${$currentref}->[$index], undef]; push @backtrace, $currentref; push @backtrace, \${$currentref}->[$index]; $currentref = \${$currentref}->[$index]->[3]; } else { # Error my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Didn't expect " . $currenttok->[1] . " at position $position", $mark); } }; /openp/ && do { if (defined(${$currentref})) { # we weren't expecting this - must be implicit # multiplication. push @pushback, $currenttok; $currenttok = ['binop2', 'implicit']; next; } else { my($me) = pop @{$self->{posarray}}; ${$currentref} = [$currenttok->[0], $currenttok->[1], $me, undef]; push @backtrace, $currentref; $currentref = \${$currentref}->[3]; } }; /func1/ && do { if (defined(${$currentref})) { # we weren't expecting this - must be implicit # multiplication. push @pushback, $currenttok; $currenttok = ['binop2', 'implicit']; next; } else { # just like a unary operator ${$currentref} = [$currenttok->[0], $currenttok->[1], undef]; push @backtrace, $currentref; $currentref = \${$currentref}->[2]; } }; /closep/ && do { if (defined(${$currentref})) { # walk up the tree until we need to stop while (${$currentref}->[0] !~ /^(openp|top)/) { $currentref = pop @backtrace; } my $a = ${$currentref}->[0]; if ($a eq 'top') { my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Unmatched close " . $currenttok->[1] . " at position $position", $mark); } elsif ($close{${$currentref}->[1]} ne $currenttok->[1]) { my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Mismatched parens at position $position" , ${$currentref}->[2], $mark); } else { ${$currentref}->[0] = 'closep'; ${$currentref}->[2] = pop @{${$currentref}}; } } else { # Error - something like (3+4*) my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Premature close " . $currenttok->[1] . " at position $position", $mark); } }; /special|varname|numberE?/ && do { if (defined(${$currentref})) { # we weren't expecting this - must be implicit # multiplication. push @pushback, $currenttok; $currenttok = ['binop2', 'implicit']; next; } else { ${$currentref} = [$currenttok->[0], $currenttok->[1]]; } }; if (@pushback) { $currenttok = pop @pushback; } else { $currenttok = $self->nexttoken; } } # ok, we stopped parsing. Now we need to see why. if ($self->{parseresult}->[0] eq 'top') { $self->{parseresult} = $self->arraytoexpr($self->{parseresult}->[1]); } else { return $self->error("Internal consistency error; not at top when done"); } if ($self->{string} =~ m/\G\s*$/g) { if (!defined(${$currentref})) { $self->{string} .= " "; return $self->error("I was expecting more at the end of the line", [length($self->{string})-1, length($self->{string})]); } else { # check that all the parens were closed while (@backtrace) { $currentref = pop @backtrace; if (${$currentref}->[0] eq 'openp') { my($mark) = ${$currentref}->[2]; my $position = 1+$mark->[0]; return $self->error("Unclosed parentheses beginning at position $position" , $mark); } } # Ok, we must really have parsed something return $self->{parseresult}; } } else { my($mark) = pop @{$self->{posarray}}; my $position = 1+$mark->[0]; return $self->error("Illegal character at position $position",$mark); } } sub arraytoexpr { my ($self) = shift; return Expr->fromarray(@_); } sub error { my($self, $errstr, @markers) = @_; # print STDERR Data::Dumper->Dump([\@markers], # ['$markers']); $self->{parseerror} = $errstr; my($htmledstring) = ''; my($str) = $self->{string}; # print STDERR Data::Dumper->Dump([$str], ['$str']); my($lastpos) = 0; $str =~ s/ /\240/g; while(@markers) { my($ref) = shift @markers; my($pos1) = $ref->[0]; my($pos2) = $ref->[1]; if (!defined($pos2)) {$pos2 = $pos1+1;} $htmledstring .= encode_entities(substr($str,$lastpos,$pos1-$lastpos)) . '' . encode_entities(substr($str,$pos1,$pos2-$pos1)) . ''; $lastpos = $pos2; } # print STDERR Data::Dumper->Dump([$str, $htmledstring, $lastpos], # ['$str', '$htmledstring', '$lastpos']); $htmledstring .= encode_entities(substr($str,$lastpos)); $htmledstring .= ''; # $self->{htmlerror} = '

' . "\n" . # '' . # encode_entities($errstr) . '
' . "\n" . # $htmledstring . "\n" . '

' . "\n"; $self->{htmlerror} = $htmledstring ; $self->{htmlerror} = 'empty' if $errstr eq 'empty'; $self->{error_msg} = $errstr; # warn $errstr . "\n"; return undef; } sub tostring { my ($self) = shift; return $self->{parseresult}->tostring(@_); } sub tolatex { my ($self) = shift; return $self->{parseresult}->tolatex(@_); } sub tolatexstring { return tolatex(@_);} sub exprtolatexstr { return exprtolatex(@_); } sub exprtolatex { my($expr) = shift; my($exprobj); if ((ref $expr) eq 'ARRAY') { $exprobj = Expr->new(@$expr); } else { $exprobj = $expr; } return $exprobj->tolatex(); } sub exprtostr { my($expr) = shift; my($exprobj); if ((ref $expr) eq 'ARRAY') { $exprobj = Expr->new(@$expr); } else { $exprobj = $expr; } return $exprobj->tostring(); } sub normalize { my ($self, $degree) = @_; $self->{parseresult} = $self->{parseresult}->normalize($degree); } sub normalize_expr { my($expr, $degree) = @_; my($exprobj); if ((ref $expr) eq 'ARRAY') { $exprobj = Expr->new(@$expr); } else { $exprobj = $expr; } return $exprobj->normalize($degree); } package AlgParserWithImplicitExpand; @ISA=qw(AlgParser); sub arraytoexpr { my ($self) = shift; my ($foo) = ExprWithImplicitExpand->fromarray(@_); # print STDERR Data::Dumper->Dump([$foo],['retval']); return $foo; } package Expr; sub new { my($class) = shift; my(@args) = @_; my($ret) = [@args]; return (bless $ret, $class); } sub head { my($self) = shift; return ($self->[0]); } sub normalize { #print STDERR "normalize\n"; #print STDERR Data::Dumper->Dump([@_]); my($self, $degree) = @_; my($class) = ref $self; $degree = $degree || 0; my($type, @args) = @$self; local $_; $_ = $type; my ($ret) = [$type, @args]; if(/closep/) { $ret = $args[1]->normalize($degree); } elsif (/unop1/) { $ret = $class->new($type, $args[0], $args[1]->normalize($degree)); } elsif (/binop/) { $ret = $class->new($type, $args[0], $args[1]->normalize($degree), $args[2]->normalize($degree)); } elsif (/func1/) { $args[0] =~ s/^arc/a/; $ret = $class->new($type, $args[0], $args[1]->normalize($degree)); } if ($degree < 0) {return $ret;} ($type, @args) = @$ret; $ret = $class->new($type, @args); $_ = $type; if (/binop1/ && ($args[2]->[0] =~ 'unop1')) { my($h1, $h2) = ($args[0], $args[2]->[1]); my($s1, $s2) = ($h1 eq '-', $h2 eq '-'); my($eventual) = ($s1==$s2); if ($eventual) { $ret = $class->new('binop1', '+', $args[1], $args[2]->[2] ); } else { $ret = $class->new('binop1', '-', $args[1], $args[2]->[2] ); } } elsif (/binop2/ && ($args[1]->[0] =~ 'unop1')) { $ret = $class->new('unop1', '-', $class->new($type, $args[0], $args[1]->[2], $args[2])->normalize($degree) ); } elsif (/binop[12]/ && ($args[2]->[0] eq $type) && ($args[0] =~ /[+*]/)) { # Remove frivolous right-association # For example, fix 3+(4-5) or 3*(4x) $ret = $class->new($type, $args[2]->[1], $class->new($type, $args[0], $args[1], $args[2]->[2])->normalize($degree), $args[2]->[3]); } elsif (/unop1/ && ($args[0] eq '+')) { $ret = $args[1]; } elsif (/unop1/ && ($args[1]->[0] =~ 'unop1')) { $ret = $args[1]->[2]; } if ($degree > 0) { } return $ret; } sub tostring { # print STDERR "Expr::tostring\n"; # print STDERR Data::Dumper->Dump([@_]); my($self) = shift; my($type, @args) = @$self; local $_; $_ = $type; /binop1/ && do { my ($p1, $p2) = ('',''); if ($args[2]->[0] eq 'binop1') {($p1,$p2)=qw{ ( ) };} return ($args[1]->tostring() . $args[0] . $p1 . $args[2]->tostring() . $p2); }; /unop1/ && do { my ($p1, $p2) = ('',''); if ($args[1]->[0] =~ /binop1/) {($p1,$p2)=qw{ ( ) };} return ($args[0] . $p1 . $args[1]->tostring() . $p2); }; /binop2/ && do { my ($p1, $p2, $p3, $p4)=('','','',''); if ($args[0] =~ /implicit/) {$args[0] = ' ';} if ($args[1]->[0] =~ /binop1/) {($p1,$p2)=qw{ ( ) };} # if ($args[2]->[0] =~ /binop[12]/) {($p3,$p4)=qw{ ( ) };} if ($args[2]->[0] =~ /binop[12]|unop1/) {($p3,$p4)=qw{ ( ) };} return ($p1 . $args[1]->tostring() . $p2 . $args[0] . $p3 . $args[2]->tostring() . $p4); }; /binop3/ && do { my ($p1, $p2, $p3, $p4)=('','','',''); # if ($args[1]->[0] =~ /binop[123]|numberE/) {($p1,$p2)=qw{ ( ) };} if ($args[1]->[0] =~ /binop[123]|unop1|numberE/) {($p1,$p2)=qw{ ( ) };} # if ($args[2]->[0] =~ /binop[12]|numberE/) {($p3,$p4)=qw{ ( ) };} if ($args[2]->[0] =~ /binop[12]|unop1|numberE/) {($p3,$p4)=qw{ ( ) };} return ($p1 . $args[1]->tostring() . $p2 . $args[0] . $p3 . $args[2]->tostring() . $p4); }; /func1/ && do { return ($args[0] . '(' . $args[1]->tostring() . ')'); }; /special|varname|numberE?/ && return $args[0]; /closep/ && do { my(%close) = %AlgParser::close; return ($args[0] . $args[1]->tostring() . $close{$args[0]}); }; } sub tolatex { my($self) = shift; my($type, @args) = @$self; local $_; $_ = $type; /binop1/ && do { my ($p1, $p2) = ('',''); if ($args[2]->[0] eq 'binop1') {($p1,$p2)=qw{ \left( \right) };} return ($args[1]->tolatex() . $args[0] . $p1 . $args[2]->tolatex() . $p2); }; /unop1/ && do { my ($p1, $p2) = ('',''); if ($args[1]->[0] =~ /binop1/) {($p1,$p2)=qw{ \left( \right) };} return ($args[0] . $p1 . $args[1]->tolatex() . $p2); }; /binop2/ && do { my ($p1, $p2, $p3, $p4) = ('','','',''); if ($args[0] =~ /implicit/) { if ( (($args[1]->head eq qq(number)) && ($args[2]->head eq qq(number))) || (($args[1]->head eq qq(binop2)) && ($args[1]->[2]->head eq qq(number))) ) { $args[0] = '\\,'; } else { $args[0] = ' '; } } if ($args[1]->[0] =~ /binop1|numberE/) {($p1,$p2)=qw{ \left( \right) };} # if ($args[2]->[0] =~ /binop[12]|numberE/) if ($args[2]->[0] =~ /binop[12]|numberE|unop1/) {($p3,$p4)=qw{ \left( \right) };} if ($args[0] eq '/'){ # return('\frac{' . $p1 . $args[1]->tolatex() . $p2 . '}'. # '{' . $p3 . $args[2]->tolatex() . $p4 . '}' ); return('\frac{' . $args[1]->tolatex() . '}'. '{' . $args[2]->tolatex() . '}' ); } else{ return ($p1 . $args[1]->tolatex() . $p2 . $args[0] . $p3 . $args[2]->tolatex() . $p4); } }; /binop3/ && do { my ($p1, $p2, $p3, $p4)=('','','',''); # if ($args[1]->[0] =~ /binop[123]|numberE/) {($p1,$p2)=qw{ \left( \right) };} if ($args[1]->[0] =~ /binop[123]|unop1|numberE/) {($p1,$p2)=qw{ \left( \right) };} # Not necessary in latex # if ($args[2]->[0] =~ /binop[12]/) {($p3,$p4)=qw{ \left( \right) };} return ($p1 . $args[1]->tolatex() . $p2 . "^{" . $p3 . $args[2]->tolatex() . $p4 . "}"); }; /func1/ && do { my($p1,$p2); if($args[0] eq "sqrt"){($p1,$p2)=qw{ \left{ \right} };} else {($p1,$p2)=qw{ \left( \right) };} # # DPVC -- 2003/03/31 # added missing trig functions # #$specialfunc = '(?:abs|logten|asin|acos|atan|sech|sgn|step|fact)'; $specialfunc = '(?:abs|logten|a(?:sin|cos|tan|sec|csc|cot)h?|sgn|step|fact)'; # # End DPVC # if ($args[0] =~ /$specialfunc/) { return ('\mbox{' . $args[0] .'}'. $p1 . $args[1]->tolatex() . $p2); } else { return ('\\' . $args[0] . $p1 . $args[1]->tolatex() . $p2); } }; /special/ && do { if ($args[0] eq 'pi') {return '\pi';} else {return $args[0];} }; /varname|(:?number$)/ && return $args[0]; /numberE/ && do { $args[0] =~ m/($AlgParser::numberplain)E([-+]?\d+)/; return ($1 . '\times 10^{' . $2 . '}'); }; /closep/ && do { my($backslash) = ''; my(%close) = %AlgParser::close; if ($args[0] eq '{') {$backslash = '\\';} #This is for editors to match: } return ('\left' . $backslash . $args[0] . $args[1]->tolatex() . '\right' . $backslash . $close{$args[0]}); }; } sub fromarray { my($class) = shift; my($expr) = shift; if ((ref $expr) ne qq{ARRAY}) { die "Program error; fromarray not passed an array ref."; } my($type, @args) = @$expr; foreach my $i (@args) { if (ref $i) { $i = $class->fromarray($i); } } return $class->new($type, @args); } package ExprWithImplicitExpand; @ISA=qw(Expr); sub tostring { # print STDERR "ExprWIE::tostring\n"; # print STDERR Data::Dumper->Dump([@_]); my ($self) = shift; my($type, @args) = @$self; if (($type eq qq(binop2)) && ($args[0] eq qq(implicit))) { my ($p1, $p2, $p3, $p4)=('','','',''); if ($args[1]->head =~ /binop1/) {($p1,$p2)=qw{ ( ) };} # if ($args[2]->head =~ /binop[12]/) {($p3,$p4)=qw{ ( ) };} if ($args[2]->head =~ /binop[12]|unop1/) {($p3,$p4)=qw{ ( ) };} return ($p1 . $args[1]->tostring() . $p2 . '*' . $p3 . $args[2]->tostring() . $p4); } else { return $self->SUPER::tostring(@_); } } tests/text_filter_test.php000064400000006203152266364460012026 0ustar00. /** * Unit test for the filter_algebra * * @package filter_algebra * @copyright 2012 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace filter_algebra; use core\context\system as context_system; /** * Unit tests for filter_algebra. * * Note that this only tests some of the filter logic. It does not actually test * the normal case of the filter working, because I cannot make it work on my * test server, and if it does not work here, it probably does not also work * for other people. A failing test will be irritating noise. * * @package filter_algebra * @copyright 2012 Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \filter_algebra\text_filter */ final class text_filter_test extends \basic_testcase { /** @var text_filter The filter to test */ protected text_filter $filter; protected function setUp(): void { parent::setUp(); $this->filter = new text_filter(context_system::instance(), []); } public function test_algebra_filter_no_algebra(): void { $this->assertEquals( '

Look no algebra!

', $this->filter->filter('

Look no algebra!

') ); } public function test_algebra_filter_pluginfile(): void { $this->assertEquals( '', $this->filter->filter('') ); } public function test_algebra_filter_draftfile(): void { $this->assertEquals( '', $this->filter->filter('') ); } public function test_algebra_filter_unified_diff(): void { $diff = ' diff -u -r1.1 Worksheet.php --- Worksheet.php 26 Sep 2003 04:18:02 -0000 1.1 +++ Worksheet.php 18 Nov 2009 03:58:50 -0000 @@ -1264,10 +1264,10 @@ } // Strip the = or @ sign at the beginning of the formula string - if (ereg("^=",$formula)) { + if (preg_match("/^=/",$formula)) { $formula = preg_replace("/(^=)/","",$formula); } - elseif(ereg("^@",$formula)) { + elseif(preg_match("/^@/",$formula)) { $formula = preg_replace("/(^@)/","",$formula); } else { '; $this->assertEquals( '
' . $diff . '
', $this->filter->filter('
' . $diff . '
') ); } }