diff --git a/filter/mathjaxloader/filter.php b/filter/mathjaxloader/filter.php index 50e568e45d2..0906e18fb49 100644 --- a/filter/mathjaxloader/filter.php +++ b/filter/mathjaxloader/filter.php @@ -128,37 +128,116 @@ class filter_mathjaxloader extends moodle_text_filter { $text = str_replace('\\]', '\\)', $text); } - $hasinline = strpos($text, '\\(') !== false && strpos($text, '\\)') !== false; - $hasdisplay = (strpos($text, '$$') !== false) || - (strpos($text, '\\[') !== false && strpos($text, '\\]') !== false); - $hasextra = false; - foreach ($extradelimiters as $extra) { if ($extra && strpos($text, $extra) !== false) { $hasextra = true; break; } } - if ($hasinline || $hasdisplay || $hasextra) { + + $hasdisplayorinline = false; + if ($hasextra) { + // If custom dilimeters are used, wrap whole text to prevent autolinking. + $text = '' . $text . ''; + } else { + // Wrap display and inline math environments in nolink spans. + // Do not wrap nested environments, i.e., if inline math is nested + // inside display math, only the outer display math is wrapped in + // a span. The span HTML inside a LaTex math environment would break + // MathJax. See MDL-61981. + list($text, $hasdisplayorinline) = $this->wrap_math_in_nolink($text); + } + + if ($hasdisplayorinline || $hasextra) { $PAGE->requires->yui_module('moodle-filter_mathjaxloader-loader', 'M.filter_mathjaxloader.typeset'); - if ($hasextra) { - // If custom dilimeters are used, wrap whole text to prevent autolinking. - $text = '' . $text . ''; - } else { - if ($hasinline) { - // If the default inline TeX delimiters \( \) are present, wrap each pair in nolink. - $text = preg_replace('/\\\\\\([\S\s]*?\\\\\\)/u', - '\0', $text); - } - if ($hasdisplay) { - // If default display TeX is used, wrap $$ $$ or \[ \] individually. - $text = preg_replace('/\$\$[\S\s]*?\$\$|\\\\\\[[\S\s]*?\\\\\\]/u', - '\0', $text); - } - } return '' . $text . ''; } return $text; } + + /** + * Find math environments in the $text and wrap them in no link spans + * (). If math environments are nested, only + * the outer environment is wrapped in the span. + * + * The recognized math environments are \[ \] and $$ $$ for display + * mathematics and \( \) for inline mathematics. + * + * @param string $text The text to filter. + * @return array An array containing the potentially modified text and + * a boolean that is true if any changes were made to the text. + */ + protected function wrap_math_in_nolink($text) { + $i = 1; + $len = strlen($text); + $displaystart = -1; + $displaybracket = false; + $displaydollar = false; + $inlinestart = -1; + $changesdone = false; + // Loop over the $text once. + while ($i < $len) { + if ($displaystart === -1) { + // No display math has started yet. + if ($text[$i - 1] === '\\' && $text[$i] === '[') { + // Display mode \[ begins. + $displaystart = $i - 1; + $displaybracket = true; + } else if ($text[$i - 1] === '$' && $text[$i] === '$') { + // Display mode $$ begins. + $displaystart = $i - 1; + $displaydollar = true; + } else if ($text[$i - 1] === '\\' && $text[$i] === '(') { + // Inline math \( begins, not nested inside display math. + $inlinestart = $i - 1; + } else if ($text[$i - 1] === '\\' && $text[$i] === ')' && $inlinestart > -1) { + // Inline math ends, not nested inside display math. + // Wrap the span around it. + $text = $this->insert_span($text, $inlinestart, $i); + + $inlinestart = -1; // Reset. + $i += 28; // The $text length changed due to the . + $len += 28; + $changesdone = true; + } + } else { + // Display math open. + if (($text[$i - 1] === '\\' && $text[$i] === ']' && $displaybracket) || + ($text[$i - 1] === '$' && $text[$i] === '$' && $displaydollar)) { + // Display math ends, wrap the span around it. + $text = $this->insert_span($text, $displaystart, $i); + + $displaystart = -1; // Reset. + $displaybracket = false; + $displaydollar = false; + $i += 28; // The $text length changed due to the . + $len += 28; + $changesdone = true; + } + } + + ++$i; + } + return array($text, $changesdone); + } + + /** + * Wrap a portion of the $text inside a no link span + * (). The whole text is then returned. + * + * @param string $text The text to modify. + * @param int $start The start index of the substring in $text that should + * be wrapped in the span. + * @param int $end The end index of the substring in $text that should be + * wrapped in the span. + * @return string The whole $text with the span inserted around + * the defined substring. + */ + protected function insert_span($text, $start, $end) { + return substr_replace($text, + ''. substr($text, $start, $end - $start + 1) .'', + $start, + $end - $start + 1); + } }