/** * @param array $matches A set of results of the `transform` function * @return string */ protected function _email_callback($matches) { $address = $matches[1]; Kernel::addConfig('urls', $address); $block = Kernel::get('OutputFormatBag')->buildTag('link', $address, array('email' => $address)); return parent::hashPart($block); }
/** * Build each maths span * * @param string $texblock * @return string */ public function span($texblock) { $texblock = trim($texblock); $block = Kernel::get('OutputFormatBag')->buildTag('maths_span', $texblock, array()); return parent::hashPart($block); }
/** * Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. * * * $indent is the number of space to be ignored when checking for code * blocks. This is important because if we don't take the indent into * account, something like this (which looks right) won't work as expected: * * <div> * <div markdown="1"> * Hello World. <-- Is this a Markdown code block or text? * </div> <-- Is this a Markdown code block or a real tag? * <div> * * If you don't like this, just don't indent the tag on which * you apply the markdown="1" attribute. * * * If $enclosing_tag_re is not empty, stops at the first unmatched closing * tag with that name. Nested tags supported. * * * If $span is true, text inside must treated as span. So any double * newline will be replaced by a single newline so that it does not create * paragraphs. * * Returns an array of that form: ( processed text , remaining text ) * * @param string $text The text to be parsed * @param int $indent The indentation to use * @param string $enclosing_tag_re The closing tag to use * @param bool $span Are we in a span element (false by default) * @return array ( processed text , remaining text ) */ protected function _hashBlocks_inMarkdown($text, $indent = 0, $enclosing_tag_re = '', $span = false) { if ($text === '') { return array('', ''); } // Regex to check for the presense of newlines around a block tag. $newline_before_re = '/(?:^\\n?|\\n\\n)*$/'; $newline_after_re = '{ ^ # Start of text following the tag. (?>[ ]*<!--.*?-->)? # Optional comment. [ ]*\\n # Must be followed by newline. }xs'; // Regex to match any tag. $block_tag_re = '{ ( # $2: Capture hole tag. </? # Any opening or closing tag. (?> # Tag name. ' . $this->block_tags_re . ' | ' . $this->blocks_tags_re . ' | ' . $this->clean_tags_re . ' | (?!\\s)' . $enclosing_tag_re . ' ) (?: (?=[\\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. (?> ".*?" | # Double quotes (can contain `>`) \'.*?\' | # Single quotes (can contain `>`) .+? # Anything but quotes and `>`. )*? )? > # End of tag. | <!-- .*? --> # HTML Comment | <\\?.*?\\?> | <%.*?%> # Processing instruction | <!\\[CDATA\\[.*?\\]\\]> # CData Block | # Code span marker `+ ' . (!$span ? ' # If not in span. | # Indented code block (?: ^[ ]*\\n | ^ | \\n[ ]*\\n ) [ ]{' . ($indent + 4) . '}[^\\n]* \\n (?> (?: [ ]{' . ($indent + 4) . '}[^\\n]* | [ ]* ) \\n )* | # Fenced code block marker (?> ^ | \\n ) [ ]{0,' . $indent . '}~~~+[ ]*\\n ' : '') . ' # End (if not is span). ) }xs'; $depth = 0; // Current depth inside the tag tree. $parsed = ""; // Parsed text that will be returned. // Loop through every tag until we find the closing tag of the parent // or loop until reaching the end of text if no parent tag specified. do { // Split the text using the first $tag_match pattern found. // Text before pattern will be first in the array, text after // pattern will be at the end, and between will be any catches made // by the pattern. $parts = preg_split($block_tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); // If in Markdown span mode, add a empty-string span-level hash // after each newline to prevent triggering any block element. if ($span) { $void = parent::hashPart("", ':'); $newline = "{$void}\n"; $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void; } $parsed .= $parts[0]; // Text before current tag. // If end of $text has been reached. Stop loop. if (count($parts) < 3) { $text = ""; break; } $tag = $parts[1]; // Tag to handle. $text = $parts[2]; // Remaining text after current tag. $tag_re = preg_quote($tag); // For use in a regular expression. // Check for: Code span marker if ($tag[0] == "`") { // Find corresponding end marker. $tag_re = preg_quote($tag); // End marker found: pass text unchanged until marker. if (preg_match('{^(?>.+?|\\n(?!\\n))*?(?<!`)' . $tag_re . '(?!`)}', $text, $matches)) { $parsed .= $tag . $matches[0]; $text = substr($text, strlen($matches[0])); // Unmatched marker: just skip it. } else { $parsed .= $tag; } } elseif (preg_match('{^\\n?[ ]{0,' . ($indent + 3) . '}~}', $tag)) { // Fenced code block marker: find matching end marker. $tag_re = preg_quote(trim($tag)); // End marker found: pass text unchanged until marker. if (preg_match('{^(?>.*\\n)+?[ ]{0,' . $indent . '}' . $tag_re . '[ ]*\\n}', $text, $matches)) { $parsed .= $tag . $matches[0]; $text = substr($text, strlen($matches[0])); // No end marker: just skip it. } else { $parsed .= $tag; } } elseif ($tag[0] == "\n" || $tag[0] == " ") { // Indented code block: pass it unchanged, will be handled later. $parsed .= $tag; } elseif (preg_match('{^<(?:' . $this->block_tags_re . ')\\b}', $tag) || preg_match('{^<(?:' . $this->blocks_tags_re . ')\\b}', $tag) && preg_match($newline_before_re, $parsed) && preg_match($newline_after_re, $text)) { // Need to parse tag and following text using the HTML parser. list($block_text, $text) = self::_hashBlocks_inHTML($tag . $text, "hashBlock", true); // Make sure it stays outside of any paragraph by adding newlines. $parsed .= "\n\n{$block_text}\n\n"; } elseif (preg_match('{^<(?:' . $this->clean_tags_re . ')\\b}', $tag) || $tag[1] == '!' || $tag[1] == '?') { // Need to parse tag and following text using the HTML parser. // (don't check for markdown attribute) list($block_text, $text) = $this->_hashBlocks_inHTML($tag . $text, "hashClean", false); $parsed .= $block_text; } elseif ($enclosing_tag_re !== '' && preg_match('{^</?(?:' . $enclosing_tag_re . ')\\b}', $tag)) { // Increase/decrease nested tag count. if ($tag[1] == '/') { $depth--; } elseif ($tag[strlen($tag) - 2] != '/') { $depth++; } if ($depth < 0) { // Going out of parent element. Clean up and break so we // return to the calling function. $text = $tag . $text; break; } $parsed .= $tag; } else { $parsed .= $tag; } } while ($depth >= 0); return array($parsed, $text); }
/** * @param array $matches A set of results of the `transform` function * @return string */ protected function _inline_callback($matches) { $alt_text = $matches[2]; $url = $matches[3] == '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $attributes = array(); $attributes['alt'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $alt_text); $attributes['src'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $url); if (!empty($title)) { $attributes['title'] = Lexer::runGamut(GamutLoader::TOOL_ALIAS . ':EncodeAttribute', $title); } $block = Kernel::get('OutputFormatBag')->buildTag('image', null, $attributes); return parent::hashPart($block); }
/** * @param array $matches A set of results of the `transform()` function * @return string */ protected function _callback($matches) { return parent::hashPart(Kernel::get('OutputFormatBag')->buildTag('new_line') . "\n"); }
/** * Create a code span markup for $code. Called from handleSpanToken. * * @param string $code * @return string */ public function span($code) { $codeblock = Kernel::get('OutputFormatBag')->buildTag('code', Helper::escapeCodeContent(trim($code))); return parent::hashPart($codeblock); }
/** * Handle $token provided by parseSpan by determining its nature and * returning the corresponding value that should replace it. * * @param string $token * @param string $str * @return string */ public function handleSpanToken($token, &$str) { switch ($token[0]) { case "\\": if ($token[1] == "(") { $texend = strpos($str, '\\)'); if ($texend) { $eqn = substr($str, 0, $texend); $str = substr($str, $texend + 2); $texspan = Lexer::runGamut('filter:Maths:span', $eqn); return parent::hashPart($texspan); } else { return $str; } } else { return parent::hashPart("&#" . ord($token[1]) . ";"); } case "`": // Search for end marker in remaining text. if (preg_match('/^(.*?[^`])' . preg_quote($token) . '(?!`)(.*)$/sm', $str, $matches)) { $str = $matches[2]; $codespan = Lexer::runGamut('filter:CodeBlock:span', $matches[1], true); return parent::hashPart($codespan); } return $token; // return as text since no ending marker found. // return as text since no ending marker found. default: return parent::hashPart($token); } }
/** * @param string $text * @return string */ public function transform($text) { $token_stack = array(''); $text_stack = array(''); $italic = ''; $strong = ''; $tree_char_em = false; while (1) { // Get prepared regular expression for seraching emphasis tokens in current context. $token_re = self::$em_strong_prepared["{$italic}{$strong}"]; // Each loop iteration search for the next emphasis token. // Each token is then passed to handleSpanToken. $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); $text_stack[0] .= $parts[0]; $token =& $parts[1]; $text =& $parts[2]; if (empty($token)) { // Reached end of text span: empty stack without emitting any more emphasis. while ($token_stack[0]) { $text_stack[1] .= array_shift($token_stack); $text_stack[0] .= array_shift($text_stack); } break; } $token_len = strlen($token); if ($tree_char_em) { // Reached closing marker while inside a three-char emphasis. if ($token_len == 3) { // Three-char closing marker, close em and strong. array_shift($token_stack); $span = Lexer::runGamut('span_gamut', array_shift($text_stack)); $span = Kernel::get('OutputFormatBag')->buildTag('italic', $span); $span = Kernel::get('OutputFormatBag')->buildTag('bold', $span); $text_stack[0] .= parent::hashPart($span); $italic = ''; $strong = ''; } else { // Other closing marker: close one em or strong and // change current token state to match the other $token_stack[0] = str_repeat($token[0], 3 - $token_len); $tag = $token_len == 2 ? "bold" : "italic"; $span = Lexer::runGamut('span_gamut', $text_stack[0]); $span = Kernel::get('OutputFormatBag')->buildTag($tag, $span); $text_stack[0] = parent::hashPart($span); ${$tag} = ''; // $$tag stands for $italic or $strong } $tree_char_em = false; } elseif ($token_len == 3) { if ($italic) { // Reached closing marker for both em and strong. // Closing strong marker: for ($i = 0; $i < 2; ++$i) { $shifted_token = array_shift($token_stack); $tag = strlen($shifted_token) == 2 ? "bold" : "italic"; $span = Lexer::runGamut('span_gamut', array_shift($text_stack)); $span = Kernel::get('OutputFormatBag')->buildTag($tag, $span); $text_stack[0] .= parent::hashPart($span); ${$tag} = ''; // $$tag stands for $italic or $strong } } else { // Reached opening three-char emphasis marker. Push on token // stack; will be handled by the special condition above. $italic = $token[0]; $strong = "{$italic}{$italic}"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); $tree_char_em = true; } } elseif ($token_len == 2) { if ($strong) { // Unwind any dangling emphasis marker: if (strlen($token_stack[0]) == 1) { $text_stack[1] .= array_shift($token_stack); $text_stack[0] .= array_shift($text_stack); } // Closing strong marker: array_shift($token_stack); $span = Lexer::runGamut('span_gamut', array_shift($text_stack)); $span = Kernel::get('OutputFormatBag')->buildTag('bold', $span); $text_stack[0] .= parent::hashPart($span); $strong = ''; } else { array_unshift($token_stack, $token); array_unshift($text_stack, ''); $strong = $token; } } else { // Here $token_len == 1 if ($italic) { if (strlen($token_stack[0]) == 1) { // Closing emphasis marker: array_shift($token_stack); $span = Lexer::runGamut('span_gamut', array_shift($text_stack)); $span = Kernel::get('OutputFormatBag')->buildTag('italic', $span); $text_stack[0] .= parent::hashPart($span); $italic = ''; } else { $text_stack[0] .= $token; } } else { array_unshift($token_stack, $token); array_unshift($text_stack, ''); $italic = $token; } } } return $text_stack[0]; }