diff --git a/third/Md/Markdown.inc.php b/third/Md/Markdown.inc.php index 8c28109..e2bd380 100644 --- a/third/Md/Markdown.inc.php +++ b/third/Md/Markdown.inc.php @@ -1,10 +1,10 @@ -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# +/** + * Markdown - A text-to-HTML conversion tool for web writers + * + * @package php-markdown + * @author Michel Fortin + * @copyright 2004-2019 Michel Fortin + * @copyright (Original Markdown) 2004-2006 John Gruber + */ + namespace Michelf; - -# -# Markdown Parser Class -# - +/** + * Markdown Parser Class + */ class Markdown implements MarkdownInterface { + /** + * Define the package version + * @var string + */ + const MARKDOWNLIB_VERSION = "1.9.0"; - ### Version ### - - const MARKDOWNLIB_VERSION = "1.6.0"; - - ### Simple Function Interface ### - + /** + * Simple function interface - Initialize the parser and return the result + * of its transform method. This will work fine for derived classes too. + * + * @api + * + * @param string $text + * @return string + */ public static function defaultTransform($text) { - # - # Initialize the parser and return the result of its transform method. - # This will work fine for derived classes too. - # - # Take parser class on which this function was called. + // Take parser class on which this function was called. $parser_class = \get_called_class(); - # try to take parser from the static parser list + // Try to take parser from the static parser list static $parser_list; $parser =& $parser_list[$parser_class]; - # create the parser it not already set - if (!$parser) + // Create the parser it not already set + if (!$parser) { $parser = new $parser_class; + } - # Transform text using parser. + // Transform text using parser. return $parser->transform($text); } - ### Configuration Variables ### + /** + * Configuration variables + */ - # Change to ">" for HTML output. + /** + * Change to ">" for HTML output. + * @var string + */ public $empty_element_suffix = " />"; + + /** + * The width of indentation of the output markup + * @var int + */ public $tab_width = 4; - - # Change to `true` to disallow markup or entities. - public $no_markup = false; + + /** + * Change to `true` to disallow markup or entities. + * @var boolean + */ + public $no_markup = false; public $no_entities = false; - - # Predefined urls and titles for reference links and images. - public $predef_urls = array(); + + + /** + * Change to `true` to enable line breaks on \n without two trailling spaces + * @var boolean + */ + public $hard_wrap = false; + + /** + * Predefined URLs and titles for reference links and images. + * @var array + */ + public $predef_urls = array(); public $predef_titles = array(); - # Optional filter function for URLs + /** + * Optional filter function for URLs + * @var callable|null + */ public $url_filter_func = null; - # Optional header id="" generation callback function. + /** + * Optional header id="" generation callback function. + * @var callable|null + */ public $header_id_func = null; - - # Optional function for converting code block content to HTML + + /** + * Optional function for converting code block content to HTML + * @var callable|null + */ public $code_block_content_func = null; - # Class attribute to toggle "enhanced ordered list" behaviour - # setting this to true will allow ordered lists to start from the index - # number that is defined first. For example: - # 2. List item two - # 3. List item three - # - # becomes - #
    - #
  1. List item two
  2. - #
  3. List item three
  4. - #
+ /** + * Optional function for converting code span content to HTML. + * @var callable|null + */ + public $code_span_content_func = null; + + /** + * Class attribute to toggle "enhanced ordered list" behaviour + * setting this to true will allow ordered lists to start from the index + * number that is defined first. + * + * For example: + * 2. List item two + * 3. List item three + * + * Becomes: + *
    + *
  1. List item two
  2. + *
  3. List item three
  4. + *
+ * + * @var bool + */ public $enhanced_ordered_list = false; - ### Parser Implementation ### + /** + * Parser implementation + */ - # Regex to match balanced [brackets]. - # Needed to insert a maximum bracked depth while converting to PHP. + /** + * Regex to match balanced [brackets]. + * Needed to insert a maximum bracked depth while converting to PHP. + * @var int + */ protected $nested_brackets_depth = 6; protected $nested_brackets_re; - + protected $nested_url_parenthesis_depth = 4; protected $nested_url_parenthesis_re; - # Table of hash values for escaped characters: + /** + * Table of hash values for escaped characters: + * @var string + */ protected $escape_chars = '\`*_{}[]()>#+-.!'; protected $escape_chars_re; - + /** + * Constructor function. Initialize appropriate member variables. + * @return void + */ public function __construct() { - # - # Constructor function. Initialize appropriate member variables. - # $this->_initDetab(); $this->prepareItalicsAndBold(); - - $this->nested_brackets_re = + + $this->nested_brackets_re = str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). str_repeat('\])*', $this->nested_brackets_depth); - - $this->nested_url_parenthesis_re = + + $this->nested_url_parenthesis_re = str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); - + $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; - - # Sort document, block, and span gamut in ascendent priority order. + + // Sort document, block, and span gamut in ascendent priority order. asort($this->document_gamut); asort($this->block_gamut); asort($this->span_gamut); } - # Internal hashes used during transformation. - protected $urls = array(); - protected $titles = array(); + /** + * Internal hashes used during transformation. + * @var array + */ + protected $urls = array(); + protected $titles = array(); protected $html_hashes = array(); - - # Status flag to avoid invalid nesting. + + /** + * Status flag to avoid invalid nesting. + * @var boolean + */ protected $in_anchor = false; - - + + /** + * Status flag to avoid invalid nesting. + * @var boolean + */ + protected $in_emphasis_processing = false; + + /** + * Called before the transformation process starts to setup parser states. + * @return void + */ protected function setup() { - # - # Called before the transformation process starts to setup parser - # states. - # - # Clear global hashes. - $this->urls = $this->predef_urls; - $this->titles = $this->predef_titles; + // Clear global hashes. + $this->urls = $this->predef_urls; + $this->titles = $this->predef_titles; $this->html_hashes = array(); - - $this->in_anchor = false; + $this->in_anchor = false; + $this->in_emphasis_processing = false; } - + + /** + * Called after the transformation process to clear any variable which may + * be taking up memory unnecessarly. + * @return void + */ protected function teardown() { - # - # Called after the transformation process to clear any variable - # which may be taking up memory unnecessarly. - # - $this->urls = array(); - $this->titles = array(); + $this->urls = array(); + $this->titles = array(); $this->html_hashes = array(); } - + /** + * Main function. Performs some preprocessing on the input text and pass + * it through the document gamut. + * + * @api + * + * @param string $text + * @return string + */ public function transform($text) { - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # $this->setup(); - + # Remove UTF-8 BOM and marker character in input, if present. $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); @@ -186,28 +255,33 @@ class Markdown implements MarkdownInterface { foreach ($this->document_gamut as $method => $priority) { $text = $this->$method($text); } - + $this->teardown(); return $text . "\n"; } - + + /** + * Define the document gamut + * @var array + */ protected $document_gamut = array( - # Strip link definitions, store in hashes. + // Strip link definitions, store in hashes. "stripLinkDefinitions" => 20, - "runBasicBlockGamut" => 30, - ); - + ); + /** + * Strips link definitions from text, stores the URLs and titles in + * hash references + * @param string $text + * @return string + */ protected function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # + $less_than_tab = $this->tab_width - 1; - # Link defs are in the form: ^[id]: url "optional title" + // Link defs are in the form: ^[id]: url "optional title" $text = preg_replace_callback('{ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 [ ]* @@ -231,43 +305,58 @@ class Markdown implements MarkdownInterface { (?:\n+|\Z) }xm', array($this, '_stripLinkDefinitions_callback'), - $text); + $text + ); return $text; } + + /** + * The callback to strip link definitions + * @param array $matches + * @return string + */ protected function _stripLinkDefinitions_callback($matches) { $link_id = strtolower($matches[1]); $url = $matches[2] == '' ? $matches[3] : $matches[2]; $this->urls[$link_id] = $url; $this->titles[$link_id] =& $matches[4]; - return ''; # String that will replace the block + return ''; // String that will replace the block } - + /** + * Hashify HTML blocks + * @param string $text + * @return string + */ protected function hashHTMLBlocks($text) { - if ($this->no_markup) return $text; + if ($this->no_markup) { + return $text; + } $less_than_tab = $this->tab_width - 1; - # Hashify HTML blocks: - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded: - # - # * List "a" is made of tags which can be both inline or block-level. - # These will be treated block-level when the start tag is alone on - # its line, otherwise they're not matched here and will be taken as - # inline later. - # * List "b" is made of tags which are always block-level; - # + /** + * Hashify HTML blocks: + * + * We only want to do this for block-level HTML tags, such as headers, + * lists, and tables. That's because we still want to wrap

s around + * "paragraphs" that are wrapped in non-block-level tags, such as + * anchors, phrase emphasis, and spans. The list of tags we're looking + * for is hard-coded: + * + * * List "a" is made of tags which can be both inline or block-level. + * These will be treated block-level when the start tag is alone on + * its line, otherwise they're not matched here and will be taken as + * inline later. + * * List "b" is made of tags which are always block-level; + */ $block_tags_a_re = 'ins|del'; $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 'script|noscript|style|form|fieldset|iframe|math|svg|'. 'article|section|nav|aside|hgroup|header|footer|'. 'figure'; - # Regular expression for the content of a block tag. + // Regular expression for the content of a block tag. $nested_tags_level = 4; $attr = ' (?> # optional tag attributes @@ -281,7 +370,7 @@ class Markdown implements MarkdownInterface { | \'[^\']*\' # text inside single quotes (tolerate ">") )* - )? + )? '; $content = str_repeat(' @@ -293,29 +382,32 @@ class Markdown implements MarkdownInterface { (?> /> | - >', $nested_tags_level). # end of opening tag - '.*?'. # last level nested tag content + >', $nested_tags_level). // end of opening tag + '.*?'. // last level nested tag content str_repeat(' # closing nested tag ) - | + | <(?!/\2\s*> # other tags with a different name ) )*', $nested_tags_level); $content2 = str_replace('\2', '\3', $content); - # First, look for nested blocks, e.g.: - #

- #
- # tags for inner block must be indented. - #
- #
- # - # The outermost tags must start at the left margin for this to match, and - # the inner nested divs must be indented. - # We need to do this before the next, more liberal match, because the next - # match will start at the first `
` and stop at the first `
`. + /** + * First, look for nested blocks, e.g.: + *
+ *
+ * tags for inner block must be indented. + *
+ *
+ * + * The outermost tags must start at the left margin for this to match, + * and the inner nested divs must be indented. + * We need to do this before the next, more liberal match, because the + * next match will start at the first `
` and stop at the + * first `
`. + */ $text = preg_replace_callback('{(?> (?> (?<=\n) # Starting on its own line @@ -324,9 +416,9 @@ class Markdown implements MarkdownInterface { ) ( # save in $1 - # Match from `\n` to `\n`, handling nested tags + # Match from `\n` to `\n`, handling nested tags # in between. - + [ ]{0,'.$less_than_tab.'} <('.$block_tags_b_re.')# start tag = $2 '.$attr.'> # attributes followed by > and \n @@ -344,28 +436,28 @@ class Markdown implements MarkdownInterface { # the matching end tag [ ]* # trailing spaces/tabs (?=\n+|\Z) # followed by a newline or end of document - - | # Special case just for
. It was easier to make a special + + | # Special case just for
. It was easier to make a special # case than to make the other regex more complicated. - + [ ]{0,'.$less_than_tab.'} <(hr) # start tag = $2 '.$attr.' # attributes /?> # the matching end tag [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document - + | # Special case for standalone HTML comments: - + [ ]{0,'.$less_than_tab.'} (?s: ) [ ]* (?=\n{2,}|\Z) # followed by a blank line or end of document - + | # PHP and ASP-style processor instructions (hashBlock($text); return "\n\n$key\n\n"; } - - + + /** + * Called whenever a tag must be hashed when a function insert an atomic + * element in the text stream. Passing $text to through this function gives + * a unique text-token which will be reverted back when calling unhash. + * + * The $boundary argument specify what character should be used to surround + * the token. By convension, "B" is used for block elements that needs not + * to be wrapped into paragraph tags at the end, ":" is used for elements + * that are word separators and "X" is used in the general case. + * + * @param string $text + * @param string $boundary + * @return string + */ protected function hashPart($text, $boundary = 'X') { - # - # Called whenever a tag must be hashed when a function insert an atomic - # element in the text stream. Passing $text to through this function gives - # a unique text-token which will be reverted back when calling unhash. - # - # The $boundary argument specify what character should be used to surround - # the token. By convension, "B" is used for block elements that needs not - # to be wrapped into paragraph tags at the end, ":" is used for elements - # that are word separators and "X" is used in the general case. - # - # Swap back any tag hash found in $text so we do not have to `unhash` - # multiple times at the end. + // Swap back any tag hash found in $text so we do not have to `unhash` + // multiple times at the end. $text = $this->unhash($text); - - # Then hash the block. + + // Then hash the block. static $i = 0; $key = "$boundary\x1A" . ++$i . $boundary; $this->html_hashes[$key] = $text; - return $key; # String that will replace the tag. + return $key; // String that will replace the tag. } - + /** + * Shortcut function for hashPart with block-level boundaries. + * @param string $text + * @return string + */ protected function hashBlock($text) { - # - # Shortcut function for hashPart with block-level boundaries. - # return $this->hashPart($text, 'B'); } - + /** + * Define the block gamut - these are all the transformations that form + * block-level tags like paragraphs, headers, and list items. + * @var array + */ protected $block_gamut = array( - # - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - # "doHeaders" => 10, "doHorizontalRules" => 20, - "doLists" => 40, "doCodeBlocks" => 50, "doBlockQuotes" => 60, - ); + ); + /** + * Run block gamut tranformations. + * + * We need to escape raw HTML in Markdown source before doing anything + * else. This need to be done for each block, and not only at the + * begining in the Markdown function since hashed blocks can be part of + * list items and could have been indented. Indented blocks would have + * been seen as a code block in a previous pass of hashHTMLBlocks. + * + * @param string $text + * @return string + */ protected function runBlockGamut($text) { - # - # Run block gamut tranformations. - # - # We need to escape raw HTML in Markdown source before doing anything - # else. This need to be done for each block, and not only at the - # begining in the Markdown function since hashed blocks can be part of - # list items and could have been indented. Indented blocks would have - # been seen as a code block in a previous pass of hashHTMLBlocks. $text = $this->hashHTMLBlocks($text); - return $this->runBasicBlockGamut($text); } - + + /** + * Run block gamut tranformations, without hashing HTML blocks. This is + * useful when HTML blocks are known to be already hashed, like in the first + * whole-document pass. + * + * @param string $text + * @return string + */ protected function runBasicBlockGamut($text) { - # - # Run block gamut tranformations, without hashing HTML blocks. This is - # useful when HTML blocks are known to be already hashed, like in the first - # whole-document pass. - # + foreach ($this->block_gamut as $method => $priority) { $text = $this->$method($text); } - - # Finally form paragraph and restore hashed blocks. + + // Finally form paragraph and restore hashed blocks. $text = $this->formParagraphs($text); return $text; } - - + + /** + * Convert horizontal rules + * @param string $text + * @return string + */ protected function doHorizontalRules($text) { - # Do Horizontal Rules: return preg_replace( '{ ^[ ]{0,3} # Leading space @@ -477,67 +589,82 @@ class Markdown implements MarkdownInterface { [ ]* # Tailing spaces $ # End of line. }mx', - "\n".$this->hashBlock("empty_element_suffix")."\n", - $text); + "\n".$this->hashBlock("empty_element_suffix")."\n", + $text + ); } - + /** + * These are all the transformations that occur *within* block-level + * tags like paragraphs, headers, and list items. + * @var array + */ protected $span_gamut = array( - # - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - # - # Process character escapes, code spans, and inline HTML - # in one shot. + // Process character escapes, code spans, and inline HTML + // in one shot. "parseSpan" => -30, - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. "doImages" => 10, "doAnchors" => 20, - - # Make links out of things like `` - # Must come after doAnchors, because you can use < and > - # delimiters in inline links like [this](). + // Make links out of things like `` + // Must come after doAnchors, because you can use < and > + // delimiters in inline links like [this](). "doAutoLinks" => 30, "encodeAmpsAndAngles" => 40, - "doItalicsAndBold" => 50, "doHardBreaks" => 60, - ); + ); + /** + * Run span gamut transformations + * @param string $text + * @return string + */ protected function runSpanGamut($text) { - # - # Run span gamut tranformations. - # foreach ($this->span_gamut as $method => $priority) { $text = $this->$method($text); } return $text; } - - + + /** + * Do hard breaks + * @param string $text + * @return string + */ protected function doHardBreaks($text) { - # Do hard breaks: - return preg_replace_callback('/ {2,}\n/', - array($this, '_doHardBreaks_callback'), $text); + if ($this->hard_wrap) { + return preg_replace_callback('/ *\n/', + array($this, '_doHardBreaks_callback'), $text); + } else { + return preg_replace_callback('/ {2,}\n/', + array($this, '_doHardBreaks_callback'), $text); + } } + + /** + * Trigger part hashing for the hard break (callback method) + * @param array $matches + * @return string + */ protected function _doHardBreaks_callback($matches) { return $this->hashPart("empty_element_suffix\n"); } - + /** + * Turn Markdown link shortcuts into XHTML tags. + * @param string $text + * @return string + */ protected function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML tags. - # - if ($this->in_anchor) return $text; + if ($this->in_anchor) { + return $text; + } $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # + + // First, handle reference-style links: [link text] [id] $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ @@ -554,9 +681,7 @@ class Markdown implements MarkdownInterface { }xs', array($this, '_doAnchors_reference_callback'), $text); - # - # Next, inline-style links: [link text](url "optional title") - # + // Next, inline-style links: [link text](url "optional title") $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ @@ -581,11 +706,9 @@ class Markdown implements MarkdownInterface { }xs', array($this, '_doAnchors_inline_callback'), $text); - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link text][1] + // or [link text](/foo) $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ @@ -598,50 +721,61 @@ class Markdown implements MarkdownInterface { $this->in_anchor = false; return $text; } + + /** + * Callback method to parse referenced anchors + * @param string $matches + * @return string + */ protected function _doAnchors_reference_callback($matches) { $whole_match = $matches[1]; $link_text = $matches[2]; $link_id =& $matches[3]; if ($link_id == "") { - # for shortcut links like [this][] or [this]. + // for shortcut links like [this][] or [this]. $link_id = $link_text; } - - # lower-case and turn embedded newlines into spaces + + // lower-case and turn embedded newlines into spaces $link_id = strtolower($link_id); $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); if (isset($this->urls[$link_id])) { $url = $this->urls[$link_id]; $url = $this->encodeURLAttribute($url); - + $result = "titles[$link_id] ) ) { $title = $this->titles[$link_id]; $title = $this->encodeAttribute($title); $result .= " title=\"$title\""; } - + $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; $result = $this->hashPart($result); - } - else { + } else { $result = $whole_match; } return $result; } + + /** + * Callback method to parse inline anchors + * @param string $matches + * @return string + */ protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $url = $matches[3] === '' ? $matches[4] : $matches[3]; $title =& $matches[7]; - // if the URL was of the form it got caught by the HTML - // tag parser and hashed. Need to reverse the process before using the URL. + // If the URL was of the form it got caught by the HTML + // tag parser and hashed. Need to reverse the process before using + // the URL. $unhashed = $this->unhash($url); - if ($unhashed != $url) + if ($unhashed !== $url) $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = $this->encodeURLAttribute($url); @@ -651,21 +785,20 @@ class Markdown implements MarkdownInterface { $title = $this->encodeAttribute($title); $result .= " title=\"$title\""; } - + $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; return $this->hashPart($result); } - + /** + * Turn Markdown image shortcuts into tags. + * @param string $text + * @return string + */ protected function doImages($text) { - # - # Turn Markdown image shortcuts into tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # + // First, handle reference-style labeled images: ![alt text][id] $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ @@ -680,13 +813,11 @@ class Markdown implements MarkdownInterface { \] ) - }xs', + }xs', array($this, '_doImages_reference_callback'), $text); - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ @@ -714,13 +845,19 @@ class Markdown implements MarkdownInterface { return $text; } + + /** + * Callback to parse references image tags + * @param array $matches + * @return string + */ protected function _doImages_reference_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; $link_id = strtolower($matches[3]); if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. + $link_id = strtolower($alt_text); // for shortcut links like ![this][]. } $alt_text = $this->encodeAttribute($alt_text); @@ -734,14 +871,19 @@ class Markdown implements MarkdownInterface { } $result .= $this->empty_element_suffix; $result = $this->hashPart($result); - } - else { - # If there's no such link ID, leave intact: + } else { + // If there's no such link ID, leave intact: $result = $whole_match; } return $result; } + + /** + * Callback to parse inline image tags + * @param array $matches + * @return string + */ protected function _doImages_inline_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; @@ -753,32 +895,38 @@ class Markdown implements MarkdownInterface { $result = "\"$alt_text\"";encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted + $result .= " title=\"$title\""; // $title already quoted } $result .= $this->empty_element_suffix; return $this->hashPart($result); } - + /** + * Parse Markdown heading elements to HTML + * @param string $text + * @return string + */ protected function doHeaders($text) { - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # + /** + * Setext-style headers: + * Header 1 + * ======== + * + * Header 2 + * -------- + */ $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', array($this, '_doHeaders_callback_setext'), $text); - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # + /** + * atx-style headers: + * # Header 1 + * ## Header 2 + * ## Header 2 with closing hashes ## + * ... + * ###### Header 6 + */ $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s [ ]* @@ -792,22 +940,33 @@ class Markdown implements MarkdownInterface { return $text; } + /** + * Setext header parsing callback + * @param array $matches + * @return string + */ protected function _doHeaders_callback_setext($matches) { - # Terrible hack to check we haven't found an empty list item. - if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) + // Terrible hack to check we haven't found an empty list item. + if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) { return $matches[0]; - - $level = $matches[2]{0} == '=' ? 1 : 2; + } - # id attribute generation + $level = $matches[2][0] == '=' ? 1 : 2; + + // ID attribute generation $idAtt = $this->_generateIdFromHeaderValue($matches[1]); $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } - protected function _doHeaders_callback_atx($matches) { - # id attribute generation + /** + * ATX header parsing callback + * @param array $matches + * @return string + */ + protected function _doHeaders_callback_atx($matches) { + // ID attribute generation $idAtt = $this->_generateIdFromHeaderValue($matches[2]); $level = strlen($matches[1]); @@ -815,30 +974,37 @@ class Markdown implements MarkdownInterface { return "\n" . $this->hashBlock($block) . "\n\n"; } + /** + * If a header_id_func property is set, we can use it to automatically + * generate an id attribute. + * + * This method returns a string in the form id="foo", or an empty string + * otherwise. + * @param string $headerValue + * @return string + */ protected function _generateIdFromHeaderValue($headerValue) { - - # if a header_id_func property is set, we can use it to automatically - # generate an id attribute. - # - # This method returns a string in the form id="foo", or an empty string - # otherwise. if (!is_callable($this->header_id_func)) { return ""; } + $idValue = call_user_func($this->header_id_func, $headerValue); - if (!$idValue) return ""; + if (!$idValue) { + return ""; + } return ' id="' . $this->encodeAttribute($idValue) . '"'; - } + /** + * Form HTML ordered (numbered) and unordered (bulleted) lists. + * @param string $text + * @return string + */ protected function doLists($text) { - # - # Form HTML ordered (numbered) and unordered (bulleted) lists. - # $less_than_tab = $this->tab_width - 1; - # Re-usable patterns to match list item bullets and number markers: + // Re-usable patterns to match list item bullets and number markers: $marker_ul_re = '[*+-]'; $marker_ol_re = '\d+[\.]'; @@ -848,7 +1014,7 @@ class Markdown implements MarkdownInterface { ); foreach ($markers_relist as $marker_re => $other_marker_re) { - # Re-usable pattern to match any entirel ul or ol list: + // Re-usable pattern to match any entirel ul or ol list: $whole_list_re = ' ( # $1 = whole list ( # $2 @@ -875,18 +1041,17 @@ class Markdown implements MarkdownInterface { ) ) '; // mx - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - + + // We use a different prefix before nested lists than top-level lists. + //See extended comment in _ProcessListItems(). + if ($this->list_level) { $text = preg_replace_callback('{ ^ '.$whole_list_re.' }mx', array($this, '_doLists_callback'), $text); - } - else { + } else { $text = preg_replace_callback('{ (?:(?<=\n)\n|\A\n?) # Must eat the newline '.$whole_list_re.' @@ -897,8 +1062,14 @@ class Markdown implements MarkdownInterface { return $text; } + + /** + * List parsing callback + * @param array $matches + * @return string + */ protected function _doLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: + // Re-usable patterns to match list item bullets and number markers: $marker_ul_re = '[*+-]'; $marker_ol_re = '\d+[\.]'; $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; @@ -914,7 +1085,7 @@ class Markdown implements MarkdownInterface { $ol_start = 1; if ($this->enhanced_ordered_list) { - # Get the start number for ordered list. + // Get the start number for ordered list. if ($list_type == 'ol') { $ol_start_array = array(); $ol_start_check = preg_match("/$marker_ol_start_re/", $matches[4], $ol_start_array); @@ -932,37 +1103,45 @@ class Markdown implements MarkdownInterface { return "\n". $result ."\n\n"; } + /** + * Nesting tracker for list levels + * @var integer + */ protected $list_level = 0; + /** + * Process the contents of a single ordered or unordered list, splitting it + * into individual list items. + * @param string $list_str + * @param string $marker_any_re + * @return string + */ protected function processListItems($list_str, $marker_any_re) { - # - # Process the contents of a single ordered or unordered list, splitting it - # into individual list items. - # - # The $this->list_level global keeps track of when we're inside a list. - # Each time we enter a list, we increment it; when we leave a list, - # we decrement. If it's zero, we're not in a list anymore. - # - # We do this because when we're not inside a list, we want to treat - # something like this: - # - # I recommend upgrading to version - # 8. Oops, now this line is treated - # as a sub-list. - # - # As a single paragraph, despite the fact that the second line starts - # with a digit-period-space sequence. - # - # Whereas when we're inside a list (or sub-list), that line will be - # treated as the start of a sub-list. What a kludge, huh? This is - # an aspect of Markdown's syntax that's hard to parse perfectly - # without resorting to mind-reading. Perhaps the solution is to - # change the syntax rules such that sub-lists must start with a - # starting cardinal number; e.g. "1." or "a.". - + /** + * The $this->list_level global keeps track of when we're inside a list. + * Each time we enter a list, we increment it; when we leave a list, + * we decrement. If it's zero, we're not in a list anymore. + * + * We do this because when we're not inside a list, we want to treat + * something like this: + * + * I recommend upgrading to version + * 8. Oops, now this line is treated + * as a sub-list. + * + * As a single paragraph, despite the fact that the second line starts + * with a digit-period-space sequence. + * + * Whereas when we're inside a list (or sub-list), that line will be + * treated as the start of a sub-list. What a kludge, huh? This is + * an aspect of Markdown's syntax that's hard to parse perfectly + * without resorting to mind-reading. Perhaps the solution is to + * change the syntax rules such that sub-lists must start with a + * starting cardinal number; e.g. "1." or "a.". + */ $this->list_level++; - # trim trailing blank lines: + // Trim trailing blank lines: $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); $list_str = preg_replace_callback('{ @@ -980,6 +1159,12 @@ class Markdown implements MarkdownInterface { $this->list_level--; return $list_str; } + + /** + * List item parsing callback + * @param array $matches + * @return string + */ protected function _processListItems_callback($matches) { $item = $matches[4]; $leading_line =& $matches[1]; @@ -987,28 +1172,27 @@ class Markdown implements MarkdownInterface { $marker_space = $matches[3]; $tailing_blank_line =& $matches[5]; - if ($leading_line || $tailing_blank_line || + if ($leading_line || $tailing_blank_line || preg_match('/\n{2,}/', $item)) { - # Replace marker with the appropriate whitespace indentation + // Replace marker with the appropriate whitespace indentation $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; $item = $this->runBlockGamut($this->outdent($item)."\n"); - } - else { - # Recursion for sub-lists: + } else { + // Recursion for sub-lists: $item = $this->doLists($this->outdent($item)); - $item = preg_replace('/\n+$/', '', $item); - $item = $this->runSpanGamut($item); + $item = $this->formParagraphs($item, false); } return "
  • " . $item . "
  • \n"; } - + /** + * Process Markdown `
    ` blocks.
    +	 * @param  string $text
    +	 * @return string
    +	 */
     	protected function doCodeBlocks($text) {
    -	#
    -	#	Process Markdown `
    ` blocks.
    -	#
     		$text = preg_replace_callback('{
     				(?:\n\n|\A\n?)
     				(	            # $1 = the code block -- one or more lines, starting with a space/tab
    @@ -1023,11 +1207,17 @@ class Markdown implements MarkdownInterface {
     
     		return $text;
     	}
    +
    +	/**
    +	 * Code block parsing callback
    +	 * @param  array $matches
    +	 * @return string
    +	 */
     	protected function _doCodeBlocks_callback($matches) {
     		$codeblock = $matches[1];
     
     		$codeblock = $this->outdent($codeblock);
    -		if ($this->code_block_content_func) {
    +		if (is_callable($this->code_block_content_func)) {
     			$codeblock = call_user_func($this->code_block_content_func, $codeblock, "");
     		} else {
     			$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
    @@ -1037,96 +1227,126 @@ class Markdown implements MarkdownInterface {
     		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
     
     		$codeblock = "
    $codeblock\n
    "; - return "\n\n".$this->hashBlock($codeblock)."\n\n"; + return "\n\n" . $this->hashBlock($codeblock) . "\n\n"; } - + /** + * Create a code span markup for $code. Called from handleSpanToken. + * @param string $code + * @return string + */ protected function makeCodeSpan($code) { - # - # Create a code span markup for $code. Called from handleSpanToken. - # - $code = htmlspecialchars(trim($code), ENT_NOQUOTES); + if (is_callable($this->code_span_content_func)) { + $code = call_user_func($this->code_span_content_func, $code); + } else { + $code = htmlspecialchars(trim($code), ENT_NOQUOTES); + } return $this->hashPart("$code"); } - + /** + * Define the emphasis operators with their regex matches + * @var array + */ protected $em_relist = array( '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(?em_relist as $em => $em_re) { foreach ($this->strong_relist as $strong => $strong_re) { - # Construct list of allowed token expressions. + // Construct list of allowed token expressions. $token_relist = array(); if (isset($this->em_strong_relist["$em$strong"])) { $token_relist[] = $this->em_strong_relist["$em$strong"]; } $token_relist[] = $em_re; $token_relist[] = $strong_re; - - # Construct master expression from list. - $token_re = '{('. implode('|', $token_relist) .')}'; + + // Construct master expression from list. + $token_re = '{(' . implode('|', $token_relist) . ')}'; $this->em_strong_prepared_relist["$em$strong"] = $token_re; } } } - + + /** + * Convert Markdown italics (emphasis) and bold (strong) to HTML + * @param string $text + * @return string + */ protected function doItalicsAndBold($text) { + if ($this->in_emphasis_processing) { + return $text; // avoid reentrency + } + $this->in_emphasis_processing = true; + $token_stack = array(''); $text_stack = array(''); $em = ''; $strong = ''; $tree_char_em = false; - + while (1) { - # - # Get prepared regular expression for seraching emphasis tokens - # in current context. - # + // Get prepared regular expression for seraching emphasis tokens + // in current context. $token_re = $this->em_strong_prepared_relist["$em$strong"]; - - # - # Each loop iteration search for the next emphasis token. - # Each token is then passed to handleSpanToken. - # + + // 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. + // 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. + // Reached closing marker while inside a three-char emphasis. if ($token_len == 3) { - # Three-char closing marker, close em and strong. + // Three-char closing marker, close em and strong. array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); @@ -1135,21 +1355,21 @@ class Markdown implements MarkdownInterface { $em = ''; $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); + // 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 ? "strong" : "em"; $span = $text_stack[0]; $span = $this->runSpanGamut($span); $span = "<$tag>$span"; $text_stack[0] = $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong + $$tag = ''; // $$tag stands for $em or $strong } $tree_char_em = false; } else if ($token_len == 3) { if ($em) { - # Reached closing marker for both em and strong. - # Closing strong marker: + // 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 ? "strong" : "em"; @@ -1157,12 +1377,12 @@ class Markdown implements MarkdownInterface { $span = $this->runSpanGamut($span); $span = "<$tag>$span"; $text_stack[0] .= $this->hashPart($span); - $$tag = ''; # $$tag stands for $em or $strong + $$tag = ''; // $$tag stands for $em or $strong } } else { - # Reached opening three-char emphasis marker. Push on token - # stack; will be handled by the special condition above. - $em = $token{0}; + // Reached opening three-char emphasis marker. Push on token + // stack; will be handled by the special condition above. + $em = $token[0]; $strong = "$em$em"; array_unshift($token_stack, $token); array_unshift($text_stack, ''); @@ -1170,12 +1390,13 @@ class Markdown implements MarkdownInterface { } } else if ($token_len == 2) { if ($strong) { - # Unwind any dangling emphasis marker: + // 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); + $em = ''; } - # Closing strong marker: + // Closing strong marker: array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); @@ -1188,10 +1409,10 @@ class Markdown implements MarkdownInterface { $strong = $token; } } else { - # Here $token_len == 1 + // Here $token_len == 1 if ($em) { if (strlen($token_stack[0]) == 1) { - # Closing emphasis marker: + // Closing emphasis marker: array_shift($token_stack); $span = array_shift($text_stack); $span = $this->runSpanGamut($span); @@ -1208,10 +1429,15 @@ class Markdown implements MarkdownInterface { } } } + $this->in_emphasis_processing = false; return $text_stack[0]; } - + /** + * Parse Markdown blockquotes to HTML + * @param string $text + * @return string + */ protected function doBlockQuotes($text) { $text = preg_replace_callback('/ ( # Wrap whole match in $1 @@ -1227,51 +1453,64 @@ class Markdown implements MarkdownInterface { return $text; } + + /** + * Blockquote parsing callback + * @param array $matches + * @return string + */ protected function _doBlockQuotes_callback($matches) { $bq = $matches[1]; - # trim one level of quoting - trim whitespace-only lines + // trim one level of quoting - trim whitespace-only lines $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); - $bq = $this->runBlockGamut($bq); # recurse + $bq = $this->runBlockGamut($bq); // recurse $bq = preg_replace('/^/m', " ", $bq); - # These leading spaces cause problem with
     content, 
    -		# so we need to fix that:
    -		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', + // These leading spaces cause problem with
     content,
    +		// so we need to fix that:
    +		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', array($this, '_doBlockQuotes_callback2'), $bq); - return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; + return "\n" . $this->hashBlock("
    \n$bq\n
    ") . "\n\n"; } + + /** + * Blockquote parsing callback + * @param array $matches + * @return string + */ protected function _doBlockQuotes_callback2($matches) { $pre = $matches[1]; $pre = preg_replace('/^ /m', '', $pre); return $pre; } - - protected function formParagraphs($text) { - # - # Params: - # $text - string to process with html

    tags - # - # Strip leading and trailing lines: + /** + * Parse paragraphs + * + * @param string $text String to process in paragraphs + * @param boolean $wrap_in_p Whether paragraphs should be wrapped in

    tags + * @return string + */ + protected function formParagraphs($text, $wrap_in_p = true) { + // Strip leading and trailing lines: $text = preg_replace('/\A\n+|\n+\z/', '', $text); $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - # - # Wrap

    tags and unhashify HTML blocks - # + // Wrap

    tags and unhashify HTML blocks foreach ($grafs as $key => $value) { if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { - # Is a paragraph. + // Is a paragraph. $value = $this->runSpanGamut($value); - $value = preg_replace('/^([ ]*)/', "

    ", $value); - $value .= "

    "; + if ($wrap_in_p) { + $value = preg_replace('/^([ ]*)/', "

    ", $value); + $value .= "

    "; + } $grafs[$key] = $this->unhash($value); - } - else { - # Is a block. - # Modify elements of @grafs in-place... + } else { + // Is a block. + // Modify elements of @grafs in-place... $graf = $value; $block = $this->html_hashes[$graf]; $graf = $block; @@ -1296,11 +1535,11 @@ class Markdown implements MarkdownInterface { // { // list(, $div_open, , $div_content, $div_close) = $matches; // -// # We can't call Markdown(), because that resets the hash; -// # that initialization code should be pulled into its own sub, though. +// // We can't call Markdown(), because that resets the hash; +// // that initialization code should be pulled into its own sub, though. // $div_content = $this->hashHTMLBlocks($div_content); -// -// # Run document gamut methods on the content. +// +// // Run document gamut methods on the content. // foreach ($this->document_gamut as $method => $priority) { // $div_content = $this->$method($div_content); // } @@ -1317,71 +1556,78 @@ class Markdown implements MarkdownInterface { return implode("\n\n", $grafs); } - + /** + * Encode text for a double-quoted HTML attribute. This function + * is *not* suitable for attributes enclosed in single quotes. + * @param string $text + * @return string + */ protected function encodeAttribute($text) { - # - # Encode text for a double-quoted HTML attribute. This function - # is *not* suitable for attributes enclosed in single quotes. - # $text = $this->encodeAmpsAndAngles($text); $text = str_replace('"', '"', $text); return $text; } - + /** + * Encode text for a double-quoted HTML attribute containing a URL, + * applying the URL filter if set. Also generates the textual + * representation for the URL (removing mailto: or tel:) storing it in $text. + * This function is *not* suitable for attributes enclosed in single quotes. + * + * @param string $url + * @param string $text Passed by reference + * @return string URL + */ protected function encodeURLAttribute($url, &$text = null) { - # - # Encode text for a double-quoted HTML attribute containing a URL, - # applying the URL filter if set. Also generates the textual - # representation for the URL (removing mailto: or tel:) storing it in $text. - # This function is *not* suitable for attributes enclosed in single quotes. - # - if ($this->url_filter_func) + if (is_callable($this->url_filter_func)) { $url = call_user_func($this->url_filter_func, $url); + } - if (preg_match('{^mailto:}i', $url)) + if (preg_match('{^mailto:}i', $url)) { $url = $this->encodeEntityObfuscatedAttribute($url, $text, 7); - else if (preg_match('{^tel:}i', $url)) - { + } else if (preg_match('{^tel:}i', $url)) { $url = $this->encodeAttribute($url); $text = substr($url, 4); - } - else - { + } else { $url = $this->encodeAttribute($url); $text = $url; } return $url; } - - + + /** + * Smart processing for ampersands and angle brackets that need to + * be encoded. Valid character entities are left alone unless the + * no-entities mode is set. + * @param string $text + * @return string + */ protected function encodeAmpsAndAngles($text) { - # - # Smart processing for ampersands and angle brackets that need to - # be encoded. Valid character entities are left alone unless the - # no-entities mode is set. - # if ($this->no_entities) { $text = str_replace('&', '&', $text); } else { - # Ampersand-encoding based entirely on Nat Irons's Amputator - # MT plugin: - $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', + // Ampersand-encoding based entirely on Nat Irons's Amputator + // MT plugin: + $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', '&', $text); } - # Encode remaining <'s + // Encode remaining <'s $text = str_replace('<', '<', $text); return $text; } - + /** + * Parse Markdown automatic links to anchor HTML tags + * @param string $text + * @return string + */ protected function doAutoLinks($text) { $text = preg_replace_callback('{<((https?|ftp|dict|tel):[^\'">\s]+)>}i', array($this, '_doAutoLinks_url_callback'), $text); - # Email addresses: + // Email addresses: $text = preg_replace_callback('{ < (?:mailto:)? @@ -1404,11 +1650,23 @@ class Markdown implements MarkdownInterface { return $text; } + + /** + * Parse URL callback + * @param array $matches + * @return string + */ protected function _doAutoLinks_url_callback($matches) { $url = $this->encodeURLAttribute($matches[1], $text); $link = "$text"; return $this->hashPart($link); } + + /** + * Parse email address callback + * @param array $matches + * @return string + */ protected function _doAutoLinks_email_callback($matches) { $addr = $matches[1]; $url = $this->encodeURLAttribute("mailto:$addr", $text); @@ -1416,42 +1674,52 @@ class Markdown implements MarkdownInterface { return $this->hashPart($link); } - + /** + * Input: some text to obfuscate, e.g. "mailto:foo@example.com" + * + * Output: the same text but with most characters encoded as either a + * decimal or hex entity, in the hopes of foiling most address + * harvesting spam bots. E.g.: + * + * mailto:foo + * @example.co + * m + * + * Note: the additional output $tail is assigned the same value as the + * ouput, minus the number of characters specified by $head_length. + * + * Based by a filter by Matthew Wickline, posted to BBEdit-Talk. + * With some optimizations by Milian Wolff. Forced encoding of HTML + * attribute special characters by Allan Odgaard. + * + * @param string $text + * @param string $tail Passed by reference + * @param integer $head_length + * @return string + */ protected function encodeEntityObfuscatedAttribute($text, &$tail = null, $head_length = 0) { - # - # Input: some text to obfuscate, e.g. "mailto:foo@example.com" - # - # Output: the same text but with most characters encoded as either a - # decimal or hex entity, in the hopes of foiling most address - # harvesting spam bots. E.g.: - # - # mailto:foo - # @example.co - # m - # - # Note: the additional output $tail is assigned the same value as the - # ouput, minus the number of characters specified by $head_length. - # - # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - # With some optimizations by Milian Wolff. Forced encoding of HTML - # attribute special characters by Allan Odgaard. - # - if ($text == "") return $tail = ""; + if ($text == "") { + return $tail = ""; + } $chars = preg_split('/(? $char) { $ord = ord($char); - # Ignore non-ascii chars. + // Ignore non-ascii chars. if ($ord < 128) { - $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. - # roughly 10% raw, 45% hex, 45% dec - # '@' *must* be encoded. I insist. - # '"' and '>' have to be encoded inside the attribute - if ($r > 90 && strpos('@"&>', $char) === false) /* do nothing */; - else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; - else $chars[$key] = '&#'.$ord.';'; + $r = ($seed * (1 + $key)) % 100; // Pseudo-random function. + // roughly 10% raw, 45% hex, 45% dec + // '@' *must* be encoded. I insist. + // '"' and '>' have to be encoded inside the attribute + if ($r > 90 && strpos('@"&>', $char) === false) { + /* do nothing */ + } else if ($r < 45) { + $chars[$key] = '&#x'.dechex($ord).';'; + } else { + $chars[$key] = '&#'.$ord.';'; + } } } @@ -1461,14 +1729,15 @@ class Markdown implements MarkdownInterface { return $text; } - + /** + * Take the string $str and parse it into tokens, hashing embeded HTML, + * escaped characters and handling code spans. + * @param string $str + * @return string + */ protected function parseSpan($str) { - # - # Take the string $str and parse it into tokens, hashing embeded HTML, - # escaped characters and handling code spans. - # $output = ''; - + $span_re = '{ ( \\\\'.$this->escape_chars_re.' @@ -1496,121 +1765,145 @@ class Markdown implements MarkdownInterface { }xs'; while (1) { - # - # Each loop iteration seach for either the next tag, the next - # openning code span marker, or the next escaped character. - # Each token is then passed to handleSpanToken. - # + // Each loop iteration seach for either the next tag, the next + // openning code span marker, or the next escaped character. + // Each token is then passed to handleSpanToken. $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); - - # Create token from text preceding tag. + + // Create token from text preceding tag. if ($parts[0] != "") { $output .= $parts[0]; } - - # Check if we reach the end. + + // Check if we reach the end. if (isset($parts[1])) { $output .= $this->handleSpanToken($parts[1], $parts[2]); $str = $parts[2]; - } - else { + } else { break; } } - + return $output; } - - + + /** + * Handle $token provided by parseSpan by determining its nature and + * returning the corresponding value that should replace it. + * @param string $token + * @param string $str Passed by reference + * @return string + */ protected function handleSpanToken($token, &$str) { - # - # Handle $token provided by parseSpan by determining its nature and - # returning the corresponding value that should replace it. - # - switch ($token{0}) { + switch ($token[0]) { case "\\": - return $this->hashPart("&#". ord($token{1}). ";"); + return $this->hashPart("&#". ord($token[1]). ";"); case "`": - # Search for end marker in remaining text. - if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', + // Search for end marker in remaining text. + if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', $str, $matches)) { $str = $matches[2]; $codespan = $this->makeCodeSpan($matches[1]); return $this->hashPart($codespan); } - return $token; // return as text since no ending marker found. + return $token; // Return as text since no ending marker found. default: return $this->hashPart($token); } } - + /** + * Remove one level of line-leading tabs or spaces + * @param string $text + * @return string + */ protected function outdent($text) { - # - # Remove one level of line-leading tabs or spaces - # - return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); + return preg_replace('/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text); } - # String length function for detab. `_initDetab` will create a function to - # hanlde UTF-8 if the default function does not exist. + /** + * String length function for detab. `_initDetab` will create a function to + * handle UTF-8 if the default function does not exist. + * @var string + */ protected $utf8_strlen = 'mb_strlen'; - + + /** + * Replace tabs with the appropriate amount of spaces. + * + * For each line we separate the line in blocks delemited by tab characters. + * Then we reconstruct every line by adding the appropriate number of space + * between each blocks. + * + * @param string $text + * @return string + */ protected function detab($text) { - # - # Replace tabs with the appropriate amount of space. - # - # For each line we separate the line in blocks delemited by - # tab characters. Then we reconstruct every line by adding the - # appropriate number of space between each blocks. - $text = preg_replace_callback('/^.*\t.*$/m', array($this, '_detab_callback'), $text); return $text; } + + /** + * Replace tabs callback + * @param string $matches + * @return string + */ protected function _detab_callback($matches) { $line = $matches[0]; - $strlen = $this->utf8_strlen; # strlen function for UTF-8. - - # Split in blocks. + $strlen = $this->utf8_strlen; // strlen function for UTF-8. + + // Split in blocks. $blocks = explode("\t", $line); - # Add each blocks to the line. + // Add each blocks to the line. $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. + unset($blocks[0]); // Do not add first block twice. foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->tab_width - + // Calculate amount of space, insert spaces, insert block. + $amount = $this->tab_width - $strlen($line, 'UTF-8') % $this->tab_width; $line .= str_repeat(" ", $amount) . $block; } return $line; } + + /** + * Check for the availability of the function in the `utf8_strlen` property + * (initially `mb_strlen`). If the function is not available, create a + * function that will loosely count the number of UTF-8 characters with a + * regular expression. + * @return void + */ protected function _initDetab() { - # - # Check for the availability of the function in the `utf8_strlen` property - # (initially `mb_strlen`). If the function is not available, create a - # function that will loosely count the number of UTF-8 characters with a - # regular expression. - # - if (function_exists($this->utf8_strlen)) return; - $this->utf8_strlen = create_function('$text', 'return preg_match_all( - "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", - $text, $m);'); + + if (function_exists($this->utf8_strlen)) { + return; + } + + $this->utf8_strlen = function($text) { + return preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m); + }; } - + /** + * Swap back in all the tags hashed by _HashHTMLBlocks. + * @param string $text + * @return string + */ protected function unhash($text) { - # - # Swap back in all the tags hashed by _HashHTMLBlocks. - # - return preg_replace_callback('/(.)\x1A[0-9]+\1/', + return preg_replace_callback('/(.)\x1A[0-9]+\1/', array($this, '_unhash_callback'), $text); } + + /** + * Unhashing callback + * @param array $matches + * @return string + */ protected function _unhash_callback($matches) { return $this->html_hashes[$matches[0]]; } - } diff --git a/third/Md/MarkdownExtra.inc.php b/third/Md/MarkdownExtra.inc.php index e11b1ef..d09bd7a 100644 --- a/third/Md/MarkdownExtra.inc.php +++ b/third/Md/MarkdownExtra.inc.php @@ -1,10 +1,10 @@ -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# +/** + * Markdown Extra - A text-to-HTML conversion tool for web writers + * + * @package php-markdown + * @author Michel Fortin + * @copyright 2004-2019 Michel Fortin + * @copyright (Original Markdown) 2004-2006 John Gruber + */ + namespace Michelf; - -# -# Markdown Extra Parser Class -# - +/** + * Markdown Extra Parser Class + */ class MarkdownExtra extends \Michelf\Markdown { + /** + * Configuration variables + */ - ### Configuration Variables ### - - # Prefix for footnote ids. + /** + * Prefix for footnote ids. + * @var string + */ public $fn_id_prefix = ""; - - # Optional title attribute for footnote links and backlinks. + + /** + * Optional title attribute for footnote links. + * @var string + */ public $fn_link_title = ""; - public $fn_backlink_title = ""; - - # Optional class attribute for footnote links and backlinks. - public $fn_link_class = "footnote-ref"; + + /** + * Optional class attribute for footnote links and backlinks. + * @var string + */ + public $fn_link_class = "footnote-ref"; public $fn_backlink_class = "footnote-backref"; - # Content to be displayed within footnote backlinks. The default is '↩'; - # the U+FE0E on the end is a Unicode variant selector used to prevent iOS - # from displaying the arrow character as an emoji. + /** + * Content to be displayed within footnote backlinks. The default is '↩'; + * the U+FE0E on the end is a Unicode variant selector used to prevent iOS + * from displaying the arrow character as an emoji. + * Optionally use '^^' and '%%' to refer to the footnote number and + * reference number respectively. {@see parseFootnotePlaceholders()} + * @var string + */ public $fn_backlink_html = '↩︎'; - # Class name for table cell alignment (%% replaced left/center/right) - # For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center' - # If empty, the align attribute is used instead of a class name. + /** + * Optional title and aria-label attributes for footnote backlinks for + * added accessibility (to ensure backlink uniqueness). + * Use '^^' and '%%' to refer to the footnote number and reference number + * respectively. {@see parseFootnotePlaceholders()} + * @var string + */ + public $fn_backlink_title = ""; + public $fn_backlink_label = ""; + + /** + * Class name for table cell alignment (%% replaced left/center/right) + * For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center' + * If empty, the align attribute is used instead of a class name. + * @var string + */ public $table_align_class_tmpl = ''; - # Optional class prefix for fenced code block. + /** + * Optional class prefix for fenced code block. + * @var string + */ public $code_class_prefix = ""; - # Class attribute for code blocks goes on the `code` tag; - # setting this to true will put attributes on the `pre` tag instead. + + /** + * Class attribute for code blocks goes on the `code` tag; + * setting this to true will put attributes on the `pre` tag instead. + * @var boolean + */ public $code_attr_on_pre = false; - # Predefined abbreviations. + /** + * Predefined abbreviations. + * @var array + */ public $predef_abbr = array(); - ### Parser Implementation ### + /** + * Only convert atx-style headers if there's a space between the header and # + * @var boolean + */ + public $hashtag_protection = false; + /** + * Determines whether footnotes should be appended to the end of the document. + * If true, footnote html can be retrieved from $this->footnotes_assembled. + * @var boolean + */ + public $omit_footnotes = false; + + + /** + * After parsing, the HTML for the list of footnotes appears here. + * This is available only if $omit_footnotes == true. + * + * Note: when placing the content of `footnotes_assembled` on the page, + * consider adding the attribute `role="doc-endnotes"` to the `div` or + * `section` that will enclose the list of footnotes so they are + * reachable to accessibility tools the same way they would be with the + * default HTML output. + * @var null|string + */ + public $footnotes_assembled = null; + + /** + * Parser implementation + */ + + /** + * Constructor function. Initialize the parser object. + * @return void + */ public function __construct() { - # - # Constructor function. Initialize the parser object. - # - # Add extra escapable characters before parent constructor - # initialize the table. + // Add extra escapable characters before parent constructor + // initialize the table. $this->escape_chars .= ':|'; - - # Insert extra document, block, and span transformations. - # Parent constructor will do the sorting. + + // Insert extra document, block, and span transformations. + // Parent constructor will do the sorting. $this->document_gamut += array( "doFencedCodeBlocks" => 5, "stripFootnotes" => 15, "stripAbbreviations" => 25, "appendFootnotes" => 50, - ); + ); $this->block_gamut += array( "doFencedCodeBlocks" => 5, "doTables" => 15, "doDefLists" => 45, - ); + ); $this->span_gamut += array( "doFootnotes" => 5, "doAbbreviations" => 70, - ); - + ); + $this->enhanced_ordered_list = true; parent::__construct(); } - - - # Extra variables used during extra transformations. + + + /** + * Extra variables used during extra transformations. + * @var array + */ protected $footnotes = array(); protected $footnotes_ordered = array(); protected $footnotes_ref_count = array(); protected $footnotes_numbers = array(); protected $abbr_desciptions = array(); + /** @var string */ protected $abbr_word_re = ''; - - # Give the current footnote number. + + /** + * Give the current footnote number. + * @var integer + */ protected $footnote_counter = 1; - - + + /** + * Ref attribute for links + * @var array + */ + protected $ref_attr = array(); + + /** + * Setting up Extra-specific variables. + */ protected function setup() { - # - # Setting up Extra-specific variables. - # parent::setup(); - + $this->footnotes = array(); $this->footnotes_ordered = array(); $this->footnotes_ref_count = array(); @@ -109,7 +184,8 @@ class MarkdownExtra extends \Michelf\Markdown { $this->abbr_desciptions = array(); $this->abbr_word_re = ''; $this->footnote_counter = 1; - + $this->footnotes_assembled = null; + foreach ($this->predef_abbr as $abbr_word => $abbr_desc) { if ($this->abbr_word_re) $this->abbr_word_re .= '|'; @@ -117,52 +193,72 @@ class MarkdownExtra extends \Michelf\Markdown { $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); } } - + + /** + * Clearing Extra-specific variables. + */ protected function teardown() { - # - # Clearing Extra-specific variables. - # $this->footnotes = array(); $this->footnotes_ordered = array(); $this->footnotes_ref_count = array(); $this->footnotes_numbers = array(); $this->abbr_desciptions = array(); $this->abbr_word_re = ''; - + + if ( ! $this->omit_footnotes ) + $this->footnotes_assembled = null; + parent::teardown(); } - - - ### Extra Attribute Parser ### - # Expression to use to catch attributes (includes the braces) + + /** + * Extra attribute parser + */ + + /** + * Expression to use to catch attributes (includes the braces) + * @var string + */ protected $id_class_attr_catch_re = '\{((?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,})[ ]*\}'; - # Expression to use when parsing in a context when no capture is desired + + /** + * Expression to use when parsing in a context when no capture is desired + * @var string + */ protected $id_class_attr_nocatch_re = '\{(?>[ ]*[#.a-z][-_:a-zA-Z0-9=]+){1,}[ ]*\}'; + /** + * Parse attributes caught by the $this->id_class_attr_catch_re expression + * and return the HTML-formatted list of attributes. + * + * Currently supported attributes are .class and #id. + * + * In addition, this method also supports supplying a default Id value, + * which will be used to populate the id attribute in case it was not + * overridden. + * @param string $tag_name + * @param string $attr + * @param mixed $defaultIdValue + * @param array $classes + * @return string + */ protected function doExtraAttributes($tag_name, $attr, $defaultIdValue = null, $classes = array()) { - # - # Parse attributes caught by the $this->id_class_attr_catch_re expression - # and return the HTML-formatted list of attributes. - # - # Currently supported attributes are .class and #id. - # - # In addition, this method also supports supplying a default Id value, - # which will be used to populate the id attribute in case it was not - # overridden. - if (empty($attr) && !$defaultIdValue && empty($classes)) return ""; - - # Split on components + if (empty($attr) && !$defaultIdValue && empty($classes)) { + return ""; + } + + // Split on components preg_match_all('/[#.a-z][-_:a-zA-Z0-9=]+/', $attr, $matches); $elements = $matches[0]; - # handle classes and ids (only first id taken into account) + // Handle classes and IDs (only first ID taken into account) $attributes = array(); $id = false; foreach ($elements as $element) { - if ($element{0} == '.') { + if ($element[0] === '.') { $classes[] = substr($element, 1); - } else if ($element{0} == '#') { + } else if ($element[0] === '#') { if ($id === false) $id = substr($element, 1); } else if (strpos($element, '=') > 0) { $parts = explode('=', $element, 2); @@ -170,9 +266,11 @@ class MarkdownExtra extends \Michelf\Markdown { } } - if (!$id) $id = $defaultIdValue; + if ($id === false || $id === '') { + $id = $defaultIdValue; + } - # compose attributes as string + // Compose attributes as string $attr_str = ""; if (!empty($id)) { $attr_str .= ' id="'.$this->encodeAttribute($id) .'"'; @@ -186,15 +284,16 @@ class MarkdownExtra extends \Michelf\Markdown { return $attr_str; } - + /** + * Strips link definitions from text, stores the URLs and titles in + * hash references. + * @param string $text + * @return string + */ protected function stripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # $less_than_tab = $this->tab_width - 1; - # Link defs are in the form: ^[id]: url "optional title" + // Link defs are in the form: ^[id]: url "optional title" $text = preg_replace_callback('{ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 [ ]* @@ -222,109 +321,141 @@ class MarkdownExtra extends \Michelf\Markdown { $text); return $text; } + + /** + * Strip link definition callback + * @param array $matches + * @return string + */ protected function _stripLinkDefinitions_callback($matches) { $link_id = strtolower($matches[1]); $url = $matches[2] == '' ? $matches[3] : $matches[2]; $this->urls[$link_id] = $url; $this->titles[$link_id] =& $matches[4]; $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]); - return ''; # String that will replace the block + return ''; // String that will replace the block } - ### HTML Block Parser ### - - # Tags that are always treated as block tags: + /** + * HTML block parser + */ + + /** + * Tags that are always treated as block tags + * @var string + */ protected $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption|figure'; - - # Tags treated as block tags only if the opening tag is alone on its line: + + /** + * Tags treated as block tags only if the opening tag is alone on its line + * @var string + */ protected $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; - - # Tags where markdown="1" default to span mode: + + /** + * Tags where markdown="1" default to span mode: + * @var string + */ protected $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; - - # Tags which must not have their contents modified, no matter where - # they appear: + + /** + * Tags which must not have their contents modified, no matter where + * they appear + * @var string + */ protected $clean_tags_re = 'script|style|math|svg'; - - # Tags that do not need to be closed. + + /** + * Tags that do not need to be closed. + * @var string + */ protected $auto_close_tags_re = 'hr|img|param|source|track'; - + /** + * Hashify HTML Blocks and "clean tags". + * + * We only want to do this for block-level HTML tags, such as headers, + * lists, and tables. That's because we still want to wrap

    s around + * "paragraphs" that are wrapped in non-block-level tags, such as anchors, + * phrase emphasis, and spans. The list of tags we're looking for is + * hard-coded. + * + * This works by calling _HashHTMLBlocks_InMarkdown, which then calls + * _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" + * attribute is found within a tag, _HashHTMLBlocks_InHTML calls back + * _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. + * These two functions are calling each other. It's recursive! + * @param string $text + * @return string + */ protected function hashHTMLBlocks($text) { - # - # Hashify HTML Blocks and "clean tags". - # - # We only want to do this for block-level HTML tags, such as headers, - # lists, and tables. That's because we still want to wrap

    s around - # "paragraphs" that are wrapped in non-block-level tags, such as anchors, - # phrase emphasis, and spans. The list of tags we're looking for is - # hard-coded. - # - # This works by calling _HashHTMLBlocks_InMarkdown, which then calls - # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back - # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. - # These two functions are calling each other. It's recursive! - # - if ($this->no_markup) return $text; + if ($this->no_markup) { + return $text; + } - # - # Call the HTML-in-Markdown hasher. - # + // Call the HTML-in-Markdown hasher. list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text); - + return $text; } + + /** + * 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: + * + *

    + *
    + * Hello World. <-- Is this a Markdown code block or text? + *
    <-- Is this a Markdown code block or a real tag? + *
    + * + * 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 + * @param integer $indent + * @param string $enclosing_tag_re + * @param boolean $span + * @return array + */ protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0, $enclosing_tag_re = '', $span = false) { - # - # 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: - # - #
    - #
    - # Hello World. <-- Is this a Markdown code block or text? - #
    <-- Is this a Markdown code block or a real tag? - #
    - # - # 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 ) - # + if ($text === '') return array('', ''); - # Regex to check for the presense of newlines around a block tag. + // Regex to check for the presense of newlines around a block tag. $newline_before_re = '/(?:^\n?|\n\n)*$/'; - $newline_after_re = + $newline_after_re = '{ ^ # Start of text following the tag. (?>[ ]*)? # Optional comment. [ ]*\n # Must be followed by newline. }xs'; - - # Regex to match any tag. + + // Regex to match any tag. $block_tag_re = '{ ( # $2: Capture whole tag. # Tag name. - '.$this->block_tags_re.' | - '.$this->context_block_tags_re.' | - '.$this->clean_tags_re.' | - (?!\s)'.$enclosing_tag_re.' + ' . $this->block_tags_re . ' | + ' . $this->context_block_tags_re . ' | + ' . $this->clean_tags_re . ' | + (?!\s)'.$enclosing_tag_re . ' ) (?: (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name. @@ -341,25 +472,25 @@ class MarkdownExtra extends \Michelf\Markdown { <\?.*?\?> | <%.*?%> # Processing instruction | # CData Block - '. ( !$span ? ' # If not in span. + ' . ( !$span ? ' # If not in span. | # Indented code block (?: ^[ ]*\n | ^ | \n[ ]*\n ) - [ ]{'.($indent+4).'}[^\n]* \n + [ ]{' . ($indent + 4) . '}[^\n]* \n (?> - (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n + (?: [ ]{' . ($indent + 4) . '}[^\n]* | [ ]* ) \n )* | # Fenced code block marker (?<= ^ | \n ) - [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,}) + [ ]{0,' . ($indent + 3) . '}(?:~{3,}|`{3,}) [ ]* (?: \.?[-_:a-zA-Z0-9]+ )? # standalone class name [ ]* - (?: '.$this->id_class_attr_nocatch_re.' )? # extra attributes + (?: ' . $this->id_class_attr_nocatch_re . ' )? # extra attributes [ ]* (?= \n ) - ' : '' ). ' # End (if not is span). + ' : '' ) . ' # End (if not is span). | # Code span marker # Note, this regex needs to go after backtick fenced @@ -369,185 +500,172 @@ class MarkdownExtra extends \Michelf\Markdown { ) }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. - # + $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, + // 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 in Markdown span mode, add a empty-string span-level hash + // after each newline to prevent triggering any block element. if ($span) { $void = $this->hashPart("", ':'); - $newline = "$void\n"; + $newline = "\n$void"; $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. + + $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: Fenced code block marker. - # Note: need to recheck the whole tag to disambiguate backtick - # fences from code spans - # - if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+)?[ ]*(?:'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) { - # Fenced code block marker: find matching end marker. - $fence_indent = strlen($capture[1]); # use captured indent in re - $fence_re = $capture[2]; # use captured fence in re - if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text, - $matches)) + + $tag = $parts[1]; // Tag to handle. + $text = $parts[2]; // Remaining text after current tag. + + // Check for: Fenced code block marker. + // Note: need to recheck the whole tag to disambiguate backtick + // fences from code spans + if (preg_match('{^\n?([ ]{0,' . ($indent + 3) . '})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+)?[ ]*(?:' . $this->id_class_attr_nocatch_re . ')?[ ]*\n?$}', $tag, $capture)) { + // Fenced code block marker: find matching end marker. + $fence_indent = strlen($capture[1]); // use captured indent in re + $fence_re = $capture[2]; // use captured fence in re + if (preg_match('{^(?>.*\n)*?[ ]{' . ($fence_indent) . '}' . $fence_re . '[ ]*(?:\n|$)}', $text, + $matches)) { - # End marker found: pass text unchanged until marker. + // End marker found: pass text unchanged until marker. $parsed .= $tag . $matches[0]; $text = substr($text, strlen($matches[0])); } else { - # No end marker: just skip it. + // No end marker: just skip it. $parsed .= $tag; } } - # - # Check for: Indented code block. - # - else if ($tag{0} == "\n" || $tag{0} == " ") { - # Indented code block: pass it unchanged, will be handled - # later. + // Check for: Indented code block. + else if ($tag[0] === "\n" || $tag[0] === " ") { + // Indented code block: pass it unchanged, will be handled + // later. $parsed .= $tag; } - # - # Check for: Code span marker - # Note: need to check this after backtick fenced code blocks - # - else if ($tag{0} == "`") { - # Find corresponding end marker. + // Check for: Code span marker + // Note: need to check this after backtick fenced code blocks + else if ($tag[0] === "`") { + // Find corresponding end marker. $tag_re = preg_quote($tag); - if (preg_match('{^(?>.+?|\n(?!\n))*?(?.+?|\n(?!\n))*?(?block_tags_re.')\b}', $tag) || - ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && + // Check for: Opening Block level tag or + // Opening Context Block tag (like ins and del) + // used as a block tag (tag is alone on it's line). + else if (preg_match('{^<(?:' . $this->block_tags_re . ')\b}', $tag) || + ( preg_match('{^<(?:' . $this->context_block_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) = + // Need to parse tag and following text using the HTML parser. + list($block_text, $text) = $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true); - - # Make sure it stays outside of any paragraph by adding newlines. + + // Make sure it stays outside of any paragraph by adding newlines. $parsed .= "\n\n$block_text\n\n"; } - # - # Check for: Clean tag (like script, math) - # HTML Comments, processing instructions. - # - else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + // Check for: Clean tag (like script, math) + // HTML Comments, processing instructions. + else if (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) = + // Need to parse tag and following text using the HTML parser. + // (don't check for markdown attribute) + list($block_text, $text) = $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false); - + $parsed .= $block_text; } - # - # Check for: Tag with same name as enclosing tag. - # + // Check for: Tag with same name as enclosing tag. else if ($enclosing_tag_re !== '' && - # Same name as enclosing tag. - preg_match('{^= 0); - + return array($parsed, $text); } + + /** + * Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. + * + * * Calls $hash_method to convert any blocks. + * * Stops when the first opening tag closes. + * * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. + * (it is not inside clean tags) + * + * Returns an array of that form: ( processed text , remaining text ) + * @param string $text + * @param string $hash_method + * @param bool $md_attr Handle `markdown="1"` attribute + * @return array + */ protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { - # - # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. - # - # * Calls $hash_method to convert any blocks. - # * Stops when the first opening tag closes. - # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. - # (it is not inside clean tags) - # - # Returns an array of that form: ( processed text , remaining text ) - # if ($text === '') return array('', ''); - - # Regex to match `markdown` attribute inside of a tag. + + // Regex to match `markdown` attribute inside of a tag. $markdown_attr_re = ' { \s* # Eat whitespace before the `markdown` attribute markdown \s*=\s* (?> - (["\']) # $1: quote delimiter + (["\']) # $1: quote delimiter (.*?) # $2: attribute value - \1 # matching delimiter + \1 # matching delimiter | ([^\s>]*) # $3: unquoted attribute value ) () # $4: make $3 always defined (avoid warnings) }xs'; - - # Regex to match any tag. + + // Regex to match any tag. $tag_re = '{ ( # $2: Capture whole tag. # CData Block ) }xs'; - - $original_text = $text; # Save original text in case of faliure. - - $depth = 0; # Current depth inside the tag tree. - $block_text = ""; # Temporary text holder for current text. - $parsed = ""; # Parsed text that will be returned. - # - # Get the name of the starting tag. - # (This pattern makes $base_tag_name_re safe without quoting.) - # + $original_text = $text; // Save original text in case of faliure. + + $depth = 0; // Current depth inside the tag tree. + $block_text = ""; // Temporary text holder for current text. + $parsed = ""; // Parsed text that will be returned. + $base_tag_name_re = ''; + + // Get the name of the starting tag. + // (This pattern makes $base_tag_name_re safe without quoting.) if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) $base_tag_name_re = $matches[1]; - # - # Loop through every tag until we find the corresponding closing tag. - # + // Loop through every tag until we find the corresponding closing tag. 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. - # + // 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($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); - + if (count($parts) < 3) { - # - # End of $text reached with unbalenced tag(s). - # In that case, we return original text unchanged and pass the - # first character as filtered to prevent an infinite loop in the - # parent function. - # - return array($original_text{0}, substr($original_text, 1)); + // End of $text reached with unbalenced tag(s). + // In that case, we return original text unchanged and pass the + // first character as filtered to prevent an infinite loop in the + // parent function. + return array($original_text[0], substr($original_text, 1)); } - - $block_text .= $parts[0]; # Text before current tag. - $tag = $parts[1]; # Tag to handle. - $text = $parts[2]; # Remaining text after current tag. - - # - # Check for: Auto-close tag (like
    ) - # Comments and Processing Instructions. - # - if (preg_match('{^auto_close_tags_re.')\b}', $tag) || - $tag{1} == '!' || $tag{1} == '?') + + $block_text .= $parts[0]; // Text before current tag. + $tag = $parts[1]; // Tag to handle. + $text = $parts[2]; // Remaining text after current tag. + + // Check for: Auto-close tag (like
    ) + // Comments and Processing Instructions. + if (preg_match('{^auto_close_tags_re . ')\b}', $tag) || + $tag[1] === '!' || $tag[1] === '?') { - # Just add the tag to the block as if it was text. + // Just add the tag to the block as if it was text. $block_text .= $tag; } else { - # - # Increase/decrease nested tag count. Only do so if - # the tag's name match base tag's. - # - if (preg_match('{^mode = $attr_m[2] . $attr_m[3]; - $span_mode = $this->mode == 'span' || $this->mode != 'block' && - preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag); - - # Calculate indent before tag. + + // Check if text inside this tag must be parsed in span mode. + $mode = $attr_m[2] . $attr_m[3]; + $span_mode = $mode === 'span' || ($mode !== 'block' && + preg_match('{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag)); + + // Calculate indent before tag. if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) { $strlen = $this->utf8_strlen; $indent = $strlen($matches[1], 'UTF-8'); } else { $indent = 0; } - - # End preceding block with this tag. + + // End preceding block with this tag. $block_text .= $tag; $parsed .= $this->$hash_method($block_text); - - # Get enclosing tag name for the ParseMarkdown function. - # (This pattern makes $tag_name_re safe without quoting.) + + // Get enclosing tag name for the ParseMarkdown function. + // (This pattern makes $tag_name_re safe without quoting.) preg_match('/^<([\w:$]*)\b/', $tag, $matches); $tag_name_re = $matches[1]; - - # Parse the content using the HTML-in-Markdown parser. + + // Parse the content using the HTML-in-Markdown parser. list ($block_text, $text) - = $this->_hashHTMLBlocks_inMarkdown($text, $indent, + = $this->_hashHTMLBlocks_inMarkdown($text, $indent, $tag_name_re, $span_mode); - - # Outdent markdown text. + + // Outdent markdown text. if ($indent > 0) { - $block_text = preg_replace("/^[ ]{1,$indent}/m", "", + $block_text = preg_replace("/^[ ]{1,$indent}/m", "", $block_text); } - - # Append tag content to parsed text. - if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; - else $parsed .= "$block_text"; - - # Start over with a new block. + + // Append tag content to parsed text. + if (!$span_mode) { + $parsed .= "\n\n$block_text\n\n"; + } else { + $parsed .= (string) $block_text; + } + + // Start over with a new block. $block_text = ""; } else $block_text .= $tag; } - + } while ($depth > 0); - - # - # Hash last block text that wasn't processed inside the loop. - # + + // Hash last block text that wasn't processed inside the loop. $parsed .= $this->$hash_method($block_text); - + return array($parsed, $text); } - + /** + * Called whenever a tag must be hashed when a function inserts a "clean" tag + * in $text, it passes through this function and is automaticaly escaped, + * blocking invalid nested overlap. + * @param string $text + * @return string + */ protected function hashClean($text) { - # - # Called whenever a tag must be hashed when a function inserts a "clean" tag - # in $text, it passes through this function and is automaticaly escaped, - # blocking invalid nested overlap. - # return $this->hashPart($text, 'C'); } - + /** + * Turn Markdown link shortcuts into XHTML tags. + * @param string $text + * @return string + */ protected function doAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML tags. - # - if ($this->in_anchor) return $text; + if ($this->in_anchor) { + return $text; + } $this->in_anchor = true; - - # - # First, handle reference-style links: [link text] [id] - # + + // First, handle reference-style links: [link text] [id] $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ - ('.$this->nested_brackets_re.') # link text = $2 + (' . $this->nested_brackets_re . ') # link text = $2 \] [ ]? # one optional space @@ -729,20 +840,18 @@ class MarkdownExtra extends \Michelf\Markdown { }xs', array($this, '_doAnchors_reference_callback'), $text); - # - # Next, inline-style links: [link text](url "optional title") - # + // Next, inline-style links: [link text](url "optional title") $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ - ('.$this->nested_brackets_re.') # link text = $2 + (' . $this->nested_brackets_re . ') # link text = $2 \] \( # literal paren [ \n]* (?: <(.+?)> # href = $3 | - ('.$this->nested_url_parenthesis_re.') # href = $4 + (' . $this->nested_url_parenthesis_re . ') # href = $4 ) [ \n]* ( # $5 @@ -752,16 +861,14 @@ class MarkdownExtra extends \Michelf\Markdown { [ \n]* # ignore any spaces/tabs between closing quote and ) )? # title is optional \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes + (?:[ ]? ' . $this->id_class_attr_catch_re . ' )? # $8 = id/class attributes ) }xs', array($this, '_doAnchors_inline_callback'), $text); - # - # Last, handle reference-style shortcuts: [link text] - # These must come last in case you've also got [link text][1] - # or [link text](/foo) - # + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link text][1] + // or [link text](/foo) $text = preg_replace_callback('{ ( # wrap whole match in $1 \[ @@ -774,24 +881,30 @@ class MarkdownExtra extends \Michelf\Markdown { $this->in_anchor = false; return $text; } + + /** + * Callback for reference anchors + * @param array $matches + * @return string + */ protected function _doAnchors_reference_callback($matches) { $whole_match = $matches[1]; $link_text = $matches[2]; $link_id =& $matches[3]; if ($link_id == "") { - # for shortcut links like [this][] or [this]. + // for shortcut links like [this][] or [this]. $link_id = $link_text; } - - # lower-case and turn embedded newlines into spaces + + // lower-case and turn embedded newlines into spaces $link_id = strtolower($link_id); $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); if (isset($this->urls[$link_id])) { $url = $this->urls[$link_id]; $url = $this->encodeURLAttribute($url); - + $result = "titles[$link_id] ) ) { $title = $this->titles[$link_id]; @@ -800,7 +913,7 @@ class MarkdownExtra extends \Michelf\Markdown { } if (isset($this->ref_attr[$link_id])) $result .= $this->ref_attr[$link_id]; - + $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; $result = $this->hashPart($result); @@ -810,17 +923,22 @@ class MarkdownExtra extends \Michelf\Markdown { } return $result; } + + /** + * Callback for inline anchors + * @param array $matches + * @return string + */ protected function _doAnchors_inline_callback($matches) { - $whole_match = $matches[1]; $link_text = $this->runSpanGamut($matches[2]); - $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $url = $matches[3] === '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); // if the URL was of the form it got caught by the HTML // tag parser and hashed. Need to reverse the process before using the URL. $unhashed = $this->unhash($url); - if ($unhashed != $url) + if ($unhashed !== $url) $url = preg_replace('/^<(.*)>$/', '\1', $unhashed); $url = $this->encodeURLAttribute($url); @@ -831,25 +949,24 @@ class MarkdownExtra extends \Michelf\Markdown { $result .= " title=\"$title\""; } $result .= $attr; - + $link_text = $this->runSpanGamut($link_text); $result .= ">$link_text"; return $this->hashPart($result); } - + /** + * Turn Markdown image shortcuts into tags. + * @param string $text + * @return string + */ protected function doImages($text) { - # - # Turn Markdown image shortcuts into tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # + // First, handle reference-style labeled images: ![alt text][id] $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ - ('.$this->nested_brackets_re.') # alt text = $2 + (' . $this->nested_brackets_re . ') # alt text = $2 \] [ ]? # one optional space @@ -860,17 +977,15 @@ class MarkdownExtra extends \Michelf\Markdown { \] ) - }xs', + }xs', array($this, '_doImages_reference_callback'), $text); - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - # + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ $text = preg_replace_callback('{ ( # wrap whole match in $1 !\[ - ('.$this->nested_brackets_re.') # alt text = $2 + (' . $this->nested_brackets_re . ') # alt text = $2 \] \s? # One optional whitespace character \( # literal paren @@ -878,7 +993,7 @@ class MarkdownExtra extends \Michelf\Markdown { (?: <(\S*)> # src url = $3 | - ('.$this->nested_url_parenthesis_re.') # src url = $4 + (' . $this->nested_url_parenthesis_re . ') # src url = $4 ) [ \n]* ( # $5 @@ -888,20 +1003,26 @@ class MarkdownExtra extends \Michelf\Markdown { [ \n]* )? # title is optional \) - (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes + (?:[ ]? ' . $this->id_class_attr_catch_re . ' )? # $8 = id/class attributes ) }xs', array($this, '_doImages_inline_callback'), $text); return $text; } + + /** + * Callback for referenced images + * @param array $matches + * @return string + */ protected function _doImages_reference_callback($matches) { $whole_match = $matches[1]; $alt_text = $matches[2]; $link_id = strtolower($matches[3]); - if ($link_id == "") { - $link_id = strtolower($alt_text); # for shortcut links like ![this][]. + if ($link_id === "") { + $link_id = strtolower($alt_text); // for shortcut links like ![this][]. } $alt_text = $this->encodeAttribute($alt_text); @@ -913,22 +1034,28 @@ class MarkdownExtra extends \Michelf\Markdown { $title = $this->encodeAttribute($title); $result .= " title=\"$title\""; } - if (isset($this->ref_attr[$link_id])) + if (isset($this->ref_attr[$link_id])) { $result .= $this->ref_attr[$link_id]; + } $result .= $this->empty_element_suffix; $result = $this->hashPart($result); } else { - # If there's no such link ID, leave intact: + // If there's no such link ID, leave intact: $result = $whole_match; } return $result; } + + /** + * Callback for inline images + * @param array $matches + * @return string + */ protected function _doImages_inline_callback($matches) { - $whole_match = $matches[1]; $alt_text = $matches[2]; - $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $url = $matches[3] === '' ? $matches[4] : $matches[3]; $title =& $matches[7]; $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); @@ -937,7 +1064,7 @@ class MarkdownExtra extends \Michelf\Markdown { $result = "\"$alt_text\"";encodeAttribute($title); - $result .= " title=\"$title\""; # $title already quoted + $result .= " title=\"$title\""; // $title already quoted } $result .= $attr; $result .= $this->empty_element_suffix; @@ -945,40 +1072,41 @@ class MarkdownExtra extends \Michelf\Markdown { return $this->hashPart($result); } - + /** + * Process markdown headers. Redefined to add ID and class attribute support. + * @param string $text + * @return string + */ protected function doHeaders($text) { - # - # Redefined to add id and class attribute support. - # - # Setext-style headers: - # Header 1 {#header1} - # ======== - # - # Header 2 {#header2 .class1 .class2} - # -------- - # + // Setext-style headers: + // Header 1 {#header1} + // ======== + // + // Header 2 {#header2 .class1 .class2} + // -------- + // $text = preg_replace_callback( '{ (^.+?) # $1: Header text - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes + (?:[ ]+ ' . $this->id_class_attr_catch_re . ' )? # $3 = id/class attributes [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer }mx', array($this, '_doHeaders_callback_setext'), $text); - # atx-style headers: - # # Header 1 {#header1} - # ## Header 2 {#header2} - # ## Header 2 with closing hashes ## {#header3.class1.class2} - # ... - # ###### Header 6 {.class2} - # + // atx-style headers: + // # Header 1 {#header1} + // ## Header 2 {#header2} + // ## Header 2 with closing hashes ## {#header3.class1.class2} + // ... + // ###### Header 6 {.class2} + // $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s - [ ]* + [ ]'.($this->hashtag_protection ? '+' : '*').' (.+?) # $2 = Header text [ ]* \#* # optional closing #\'s (not counted) - (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes + (?:[ ]+ ' . $this->id_class_attr_catch_re . ' )? # $3 = id/class attributes [ ]* \n+ }xm', @@ -986,51 +1114,63 @@ class MarkdownExtra extends \Michelf\Markdown { return $text; } - protected function _doHeaders_callback_setext($matches) { - if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) - return $matches[0]; - $level = $matches[3]{0} == '=' ? 1 : 2; + /** + * Callback for setext headers + * @param array $matches + * @return string + */ + protected function _doHeaders_callback_setext($matches) { + if ($matches[3] === '-' && preg_match('{^- }', $matches[1])) { + return $matches[0]; + } + + $level = $matches[3][0] === '=' ? 1 : 2; $defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[1]) : null; $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2], $defaultId); - $block = "".$this->runSpanGamut($matches[1]).""; + $block = "" . $this->runSpanGamut($matches[1]) . ""; return "\n" . $this->hashBlock($block) . "\n\n"; } + + /** + * Callback for atx headers + * @param array $matches + * @return string + */ protected function _doHeaders_callback_atx($matches) { $level = strlen($matches[1]); $defaultId = is_callable($this->header_id_func) ? call_user_func($this->header_id_func, $matches[2]) : null; $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3], $defaultId); - $block = "".$this->runSpanGamut($matches[2]).""; + $block = "" . $this->runSpanGamut($matches[2]) . ""; return "\n" . $this->hashBlock($block) . "\n\n"; } - + /** + * Form HTML tables. + * @param string $text + * @return string + */ protected function doTables($text) { - # - # Form HTML tables. - # $less_than_tab = $this->tab_width - 1; - # - # Find tables with leading pipe. - # - # | Header 1 | Header 2 - # | -------- | -------- - # | Cell 1 | Cell 2 - # | Cell 3 | Cell 4 - # + // Find tables with leading pipe. + // + // | Header 1 | Header 2 + // | -------- | -------- + // | Cell 1 | Cell 2 + // | Cell 3 | Cell 4 $text = preg_replace_callback(' { ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. + [ ]{0,' . $less_than_tab . '} # Allowed whitespace. [|] # Optional leading pipe (present) (.+) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. + + [ ]{0,' . $less_than_tab . '} # Allowed whitespace. [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline - + ( # $3: Cells (?> [ ]* # Allowed whitespace. @@ -1040,24 +1180,22 @@ class MarkdownExtra extends \Michelf\Markdown { (?=\n|\Z) # Stop at final double newline. }xm', array($this, '_doTable_leadingPipe_callback'), $text); - - # - # Find tables without leading pipe. - # - # Header 1 | Header 2 - # -------- | -------- - # Cell 1 | Cell 2 - # Cell 3 | Cell 4 - # + + // Find tables without leading pipe. + // + // Header 1 | Header 2 + // -------- | -------- + // Cell 1 | Cell 2 + // Cell 3 | Cell 4 $text = preg_replace_callback(' { ^ # Start of a line - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. + [ ]{0,' . $less_than_tab . '} # Allowed whitespace. (\S.*[|].*) \n # $1: Header row (at least one pipe) - - [ ]{0,'.$less_than_tab.'} # Allowed whitespace. + + [ ]{0,' . $less_than_tab . '} # Allowed whitespace. ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline - + ( # $3: Cells (?> .* [|] .* \n # Row content @@ -1069,35 +1207,52 @@ class MarkdownExtra extends \Michelf\Markdown { return $text; } + + /** + * Callback for removing the leading pipe for each row + * @param array $matches + * @return string + */ protected function _doTable_leadingPipe_callback($matches) { $head = $matches[1]; $underline = $matches[2]; $content = $matches[3]; - - # Remove leading pipe for each row. + $content = preg_replace('/^ *[|]/m', '', $content); - + return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); } - protected function _doTable_makeAlignAttr($alignname) - { - if (empty($this->table_align_class_tmpl)) + + /** + * Make the align attribute in a table + * @param string $alignname + * @return string + */ + protected function _doTable_makeAlignAttr($alignname) { + if (empty($this->table_align_class_tmpl)) { return " align=\"$alignname\""; + } $classname = str_replace('%%', $alignname, $this->table_align_class_tmpl); return " class=\"$classname\""; } + + /** + * Calback for processing tables + * @param array $matches + * @return string + */ protected function _doTable_callback($matches) { $head = $matches[1]; $underline = $matches[2]; $content = $matches[3]; - # Remove any tailing pipes for each line. + // Remove any tailing pipes for each line. $head = preg_replace('/[|] *$/m', '', $head); $underline = preg_replace('/[|] *$/m', '', $underline); $content = preg_replace('/[|] *$/m', '', $content); - - # Reading alignement from header underline. + + // Reading alignement from header underline. $separators = preg_split('/ *[|] */', $underline); foreach ($separators as $n => $s) { if (preg_match('/^ *-+: *$/', $s)) @@ -1109,62 +1264,65 @@ class MarkdownExtra extends \Michelf\Markdown { else $attr[$n] = ''; } - - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. + + // Parsing span elements, including code spans, character escapes, + // and inline HTML tags, so that pipes inside those gets ignored. $head = $this->parseSpan($head); $headers = preg_split('/ *[|] */', $head); $col_count = count($headers); $attr = array_pad($attr, $col_count, ''); - - # Write column headers. + + // Write column headers. $text = "\n"; $text .= "\n"; $text .= "\n"; - foreach ($headers as $n => $header) - $text .= " ".$this->runSpanGamut(trim($header))."\n"; + foreach ($headers as $n => $header) { + $text .= " " . $this->runSpanGamut(trim($header)) . "\n"; + } $text .= "\n"; $text .= "\n"; - - # Split content by row. + + // Split content by row. $rows = explode("\n", trim($content, "\n")); - + $text .= "\n"; foreach ($rows as $row) { - # Parsing span elements, including code spans, character escapes, - # and inline HTML tags, so that pipes inside those gets ignored. + // Parsing span elements, including code spans, character escapes, + // and inline HTML tags, so that pipes inside those gets ignored. $row = $this->parseSpan($row); - - # Split row by cell. + + // Split row by cell. $row_cells = preg_split('/ *[|] */', $row, $col_count); $row_cells = array_pad($row_cells, $col_count, ''); - + $text .= "\n"; - foreach ($row_cells as $n => $cell) - $text .= " ".$this->runSpanGamut(trim($cell))."\n"; + foreach ($row_cells as $n => $cell) { + $text .= " " . $this->runSpanGamut(trim($cell)) . "\n"; + } $text .= "\n"; } $text .= "\n"; $text .= "
    "; - + return $this->hashBlock($text) . "\n"; } - + /** + * Form HTML definition lists. + * @param string $text + * @return string + */ protected function doDefLists($text) { - # - # Form HTML definition lists. - # $less_than_tab = $this->tab_width - 1; - # Re-usable pattern to match any entire dl list: + // Re-usable pattern to match any entire dl list: $whole_list_re = '(?> ( # $1 = whole list ( # $2 - [ ]{0,'.$less_than_tab.'} + [ ]{0,' . $less_than_tab . '} ((?>.*\S.*\n)+) # $3 = defined term \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) (?s:.+?) ( # $4 @@ -1173,13 +1331,13 @@ class MarkdownExtra extends \Michelf\Markdown { \n{2,} (?=\S) (?! # Negative lookahead for another term - [ ]{0,'.$less_than_tab.'} + [ ]{0,' . $less_than_tab . '} (?: \S.*\n )+? # defined term \n? - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) (?! # Negative lookahead for another definition - [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition + [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition ) ) ) @@ -1187,67 +1345,81 @@ class MarkdownExtra extends \Michelf\Markdown { $text = preg_replace_callback('{ (?>\A\n?|(?<=\n\n)) - '.$whole_list_re.' + ' . $whole_list_re . ' }mx', array($this, '_doDefLists_callback'), $text); return $text; } + + /** + * Callback for processing definition lists + * @param array $matches + * @return string + */ protected function _doDefLists_callback($matches) { - # Re-usable patterns to match list item bullets and number markers: + // Re-usable patterns to match list item bullets and number markers: $list = $matches[1]; - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: + + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: $result = trim($this->processDefListItems($list)); $result = "
    \n" . $result . "\n
    "; return $this->hashBlock($result) . "\n\n"; } - + /** + * Process the contents of a single definition list, splitting it + * into individual term and definition list items. + * @param string $list_str + * @return string + */ protected function processDefListItems($list_str) { - # - # Process the contents of a single definition list, splitting it - # into individual term and definition list items. - # + $less_than_tab = $this->tab_width - 1; - - # trim trailing blank lines: + + // Trim trailing blank lines: $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - # Process definition terms. + // Process definition terms. $list_str = preg_replace_callback('{ - (?>\A\n?|\n\n+) # leading line - ( # definition terms = $1 - [ ]{0,'.$less_than_tab.'} # leading whitespace - (?!\:[ ]|[ ]) # negative lookahead for a definition - # mark (colon) or more whitespace. - (?> \S.* \n)+? # actual term (not whitespace). - ) - (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed - # with a definition mark. + (?>\A\n?|\n\n+) # leading line + ( # definition terms = $1 + [ ]{0,' . $less_than_tab . '} # leading whitespace + (?!\:[ ]|[ ]) # negative lookahead for a definition + # mark (colon) or more whitespace. + (?> \S.* \n)+? # actual term (not whitespace). + ) + (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed + # with a definition mark. }xm', array($this, '_processDefListItems_callback_dt'), $list_str); - # Process actual definitions. + // Process actual definitions. $list_str = preg_replace_callback('{ - \n(\n+)? # leading line = $1 - ( # marker space = $2 - [ ]{0,'.$less_than_tab.'} # whitespace before colon - \:[ ]+ # definition mark (colon) + \n(\n+)? # leading line = $1 + ( # marker space = $2 + [ ]{0,' . $less_than_tab . '} # whitespace before colon + \:[ ]+ # definition mark (colon) ) - ((?s:.+?)) # definition text = $3 - (?= \n+ # stop at next definition mark, - (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} \:[ ] | + ((?s:.+?)) # definition text = $3 + (?= \n+ # stop at next definition mark, + (?: # next term or end of text + [ ]{0,' . $less_than_tab . '} \:[ ] |
    | \z - ) - ) + ) + ) }xm', array($this, '_processDefListItems_callback_dd'), $list_str); return $list_str; } + + /** + * Callback for
    elements in definition lists + * @param array $matches + * @return string + */ protected function _processDefListItems_callback_dt($matches) { $terms = explode("\n", trim($matches[1])); $text = ''; @@ -1257,13 +1429,19 @@ class MarkdownExtra extends \Michelf\Markdown { } return $text . "\n"; } + + /** + * Callback for
    elements in definition lists + * @param array $matches + * @return string + */ protected function _processDefListItems_callback_dd($matches) { $leading_line = $matches[1]; $marker_space = $matches[2]; $def = $matches[3]; if ($leading_line || preg_match('/\n{2,}/', $def)) { - # Replace marker with the appropriate whitespace indentation + // Replace marker with the appropriate whitespace indentation $def = str_repeat(' ', strlen($marker_space)) . $def; $def = $this->runBlockGamut($this->outdent($def . "\n\n")); $def = "\n". $def ."\n"; @@ -1276,17 +1454,18 @@ class MarkdownExtra extends \Michelf\Markdown { return "\n
    " . $def . "
    \n"; } - + /** + * Adding the fenced code block syntax to regular Markdown: + * + * ~~~ + * Code block + * ~~~ + * + * @param string $text + * @return string + */ protected function doFencedCodeBlocks($text) { - # - # Adding the fenced code block syntax to regular Markdown: - # - # ~~~ - # Code block - # ~~~ - # - $less_than_tab = $this->tab_width; - + $text = preg_replace_callback('{ (?:\n|\A) # 1: Opening marker @@ -1299,10 +1478,10 @@ class MarkdownExtra extends \Michelf\Markdown { )? [ ]* (?: - '.$this->id_class_attr_catch_re.' # 3: Extra attributes + ' . $this->id_class_attr_catch_re . ' # 3: Extra attributes )? [ ]* \n # Whitespace and newline following marker. - + # 4: Content ( (?> @@ -1310,7 +1489,7 @@ class MarkdownExtra extends \Michelf\Markdown { .*\n+ )+ ) - + # Closing marker. \1 [ ]* (?= \n ) }xm', @@ -1318,6 +1497,12 @@ class MarkdownExtra extends \Michelf\Markdown { return $text; } + + /** + * Callback to process fenced code blocks + * @param array $matches + * @return string + */ protected function _doFencedCodeBlocks_callback($matches) { $classname =& $matches[2]; $attrs =& $matches[3]; @@ -1333,204 +1518,256 @@ class MarkdownExtra extends \Michelf\Markdown { array($this, '_doFencedCodeBlocks_newlines'), $codeblock); $classes = array(); - if ($classname != "") { - if ($classname{0} == '.') + if ($classname !== "") { + if ($classname[0] === '.') { $classname = substr($classname, 1); - $classes[] = $this->code_class_prefix.$classname; + } + $classes[] = $this->code_class_prefix . $classname; } $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs, null, $classes); $pre_attr_str = $this->code_attr_on_pre ? $attr_str : ''; $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str; $codeblock = "$codeblock
    "; - + return "\n\n".$this->hashBlock($codeblock)."\n\n"; } + + /** + * Replace new lines in fenced code blocks + * @param array $matches + * @return string + */ protected function _doFencedCodeBlocks_newlines($matches) { - return str_repeat("empty_element_suffix", + return str_repeat("empty_element_suffix", strlen($matches[0])); } - - # - # Redefining emphasis markers so that emphasis by underscore does not - # work in the middle of a word. - # + /** + * Redefining emphasis markers so that emphasis by underscore does not + * work in the middle of a word. + * @var array + */ protected $em_relist = array( '' => '(?:(? '(? '(? '(?:(? '(? '(? '(?:(? '(? '(? tags - # - # Strip leading and trailing lines: + /** + * Parse text into paragraphs + * @param string $text String to process in paragraphs + * @param boolean $wrap_in_p Whether paragraphs should be wrapped in

    tags + * @return string HTML output + */ + protected function formParagraphs($text, $wrap_in_p = true) { + // Strip leading and trailing lines: $text = preg_replace('/\A\n+|\n+\z/', '', $text); - + $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - # - # Wrap

    tags and unhashify HTML blocks - # + // Wrap

    tags and unhashify HTML blocks foreach ($grafs as $key => $value) { $value = trim($this->runSpanGamut($value)); - - # Check if this should be enclosed in a paragraph. - # Clean tag hashes & block tag hashes are left alone. - $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); - + + // Check if this should be enclosed in a paragraph. + // Clean tag hashes & block tag hashes are left alone. + $is_p = $wrap_in_p && !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value); + if ($is_p) { $value = "

    $value

    "; } $grafs[$key] = $value; } - - # Join grafs in one text, then unhash HTML tags. + + // Join grafs in one text, then unhash HTML tags. $text = implode("\n\n", $grafs); - - # Finish by removing any tag hashes still present in $text. + + // Finish by removing any tag hashes still present in $text. $text = $this->unhash($text); - + return $text; } - - - ### Footnotes - + + + /** + * Footnotes - Strips link definitions from text, stores the URLs and + * titles in hash references. + * @param string $text + * @return string + */ protected function stripFootnotes($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # $less_than_tab = $this->tab_width - 1; - # Link defs are in the form: [^id]: url "optional title" + // Link defs are in the form: [^id]: url "optional title" $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1 + ^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?: # note_id = $1 [ ]* \n? # maybe *one* newline ( # text = $2 (no blank lines allowed) - (?: + (?: .+ # actual text | - \n # newlines but + \n # newlines but (?!\[.+?\][ ]?:\s)# negative lookahead for footnote or link definition marker. - (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed + (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed # by non-indented content )* - ) + ) }xm', array($this, '_stripFootnotes_callback'), $text); return $text; } + + /** + * Callback for stripping footnotes + * @param array $matches + * @return string + */ protected function _stripFootnotes_callback($matches) { $note_id = $this->fn_id_prefix . $matches[1]; $this->footnotes[$note_id] = $this->outdent($matches[2]); - return ''; # String that will replace the block + return ''; // String that will replace the block } - + /** + * Replace footnote references in $text [^id] with a special text-token + * which will be replaced by the actual footnote marker in appendFootnotes. + * @param string $text + * @return string + */ protected function doFootnotes($text) { - # - # Replace footnote references in $text [^id] with a special text-token - # which will be replaced by the actual footnote marker in appendFootnotes. - # if (!$this->in_anchor) { $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text); } return $text; } - + /** + * Append footnote list to text + * @param string $text + * @return string + */ protected function appendFootnotes($text) { - # - # Append footnote list to text. - # - $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', + $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', array($this, '_appendFootnotes_callback'), $text); - - if (!empty($this->footnotes_ordered)) { - $text .= "\n\n"; - $text .= "
    \n"; - $text .= "empty_element_suffix ."\n"; - $text .= "
      \n\n"; - $attr = ""; - if ($this->fn_backlink_class != "") { - $class = $this->fn_backlink_class; - $class = $this->encodeAttribute($class); - $attr .= " class=\"$class\""; + if ( ! empty( $this->footnotes_ordered ) ) { + $this->_doFootnotes(); + if ( ! $this->omit_footnotes ) { + $text .= "\n\n"; + $text .= "
      \n"; + $text .= "empty_element_suffix . "\n"; + $text .= $this->footnotes_assembled; + $text .= "
      "; } - if ($this->fn_backlink_title != "") { - $title = $this->fn_backlink_title; - $title = $this->encodeAttribute($title); - $attr .= " title=\"$title\""; - } - $backlink_text = $this->fn_backlink_html; - $num = 0; - - while (!empty($this->footnotes_ordered)) { - $footnote = reset($this->footnotes_ordered); - $note_id = key($this->footnotes_ordered); - unset($this->footnotes_ordered[$note_id]); - $ref_count = $this->footnotes_ref_count[$note_id]; - unset($this->footnotes_ref_count[$note_id]); - unset($this->footnotes[$note_id]); - - $footnote .= "\n"; # Need to append newline before parsing. - $footnote = $this->runBlockGamut("$footnote\n"); - $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', - array($this, '_appendFootnotes_callback'), $footnote); - - $attr = str_replace("%%", ++$num, $attr); - $note_id = $this->encodeAttribute($note_id); + } + return $text; + } - # Prepare backlink, multiple backlinks if multiple references - $backlink = "$backlink_text"; - for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { - $backlink .= " $backlink_text"; + + /** + * Generates the HTML for footnotes. Called by appendFootnotes, even if + * footnotes are not being appended. + * @return void + */ + protected function _doFootnotes() { + $attr = array(); + if ($this->fn_backlink_class !== "") { + $class = $this->fn_backlink_class; + $class = $this->encodeAttribute($class); + $attr['class'] = " class=\"$class\""; + } + $attr['role'] = " role=\"doc-backlink\""; + $num = 0; + + $text = "
        \n\n"; + while (!empty($this->footnotes_ordered)) { + $footnote = reset($this->footnotes_ordered); + $note_id = key($this->footnotes_ordered); + unset($this->footnotes_ordered[$note_id]); + $ref_count = $this->footnotes_ref_count[$note_id]; + unset($this->footnotes_ref_count[$note_id]); + unset($this->footnotes[$note_id]); + + $footnote .= "\n"; // Need to append newline before parsing. + $footnote = $this->runBlockGamut("$footnote\n"); + $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', + array($this, '_appendFootnotes_callback'), $footnote); + + $num++; + $note_id = $this->encodeAttribute($note_id); + + // Prepare backlink, multiple backlinks if multiple references + // Do not create empty backlinks if the html is blank + $backlink = ""; + if (!empty($this->fn_backlink_html)) { + for ($ref_num = 1; $ref_num <= $ref_count; ++$ref_num) { + if (!empty($this->fn_backlink_title)) { + $attr['title'] = ' title="' . $this->encodeAttribute($this->fn_backlink_title) . '"'; + } + if (!empty($this->fn_backlink_label)) { + $attr['label'] = ' aria-label="' . $this->encodeAttribute($this->fn_backlink_label) . '"'; + } + $parsed_attr = $this->parseFootnotePlaceholders( + implode('', $attr), + $num, + $ref_num + ); + $backlink_text = $this->parseFootnotePlaceholders( + $this->fn_backlink_html, + $num, + $ref_num + ); + $ref_count_mark = $ref_num > 1 ? $ref_num : ''; + $backlink .= " $backlink_text"; } - # Add backlink to last paragraph; create new paragraph if needed. + $backlink = trim($backlink); + } + + // Add backlink to last paragraph; create new paragraph if needed. + if (!empty($backlink)) { if (preg_match('{

        $}', $footnote)) { $footnote = substr($footnote, 0, -4) . " $backlink

        "; } else { $footnote .= "\n\n

        $backlink

        "; } - - $text .= "
      1. \n"; - $text .= $footnote . "\n"; - $text .= "
      2. \n\n"; } - - $text .= "
      \n"; - $text .= "
    "; + + $text .= "
  • \n"; + $text .= $footnote . "\n"; + $text .= "
  • \n\n"; } - return $text; + $text .= "\n"; + + $this->footnotes_assembled = $text; } + + /** + * Callback for appending footnotes + * @param array $matches + * @return string + */ protected function _appendFootnotes_callback($matches) { $node_id = $this->fn_id_prefix . $matches[1]; - - # Create footnote marker only if it has a corresponding footnote *and* - # the footnote hasn't been used by another marker. + + // Create footnote marker only if it has a corresponding footnote *and* + // the footnote hasn't been used by another marker. if (isset($this->footnotes[$node_id])) { $num =& $this->footnotes_numbers[$node_id]; if (!isset($num)) { - # Transfer footnote content to the ordered list and give it its - # number + // Transfer footnote content to the ordered list and give it its + // number $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; $this->footnotes_ref_count[$node_id] = 1; $num = $this->footnote_counter++; @@ -1540,86 +1777,117 @@ class MarkdownExtra extends \Michelf\Markdown { } $attr = ""; - if ($this->fn_link_class != "") { + if ($this->fn_link_class !== "") { $class = $this->fn_link_class; $class = $this->encodeAttribute($class); $attr .= " class=\"$class\""; } - if ($this->fn_link_title != "") { + if ($this->fn_link_title !== "") { $title = $this->fn_link_title; $title = $this->encodeAttribute($title); $attr .= " title=\"$title\""; } - + $attr .= " role=\"doc-noteref\""; + $attr = str_replace("%%", $num, $attr); $node_id = $this->encodeAttribute($node_id); - + return "". "$num". ""; } - - return "[^".$matches[1]."]"; + + return "[^" . $matches[1] . "]"; } - - - ### Abbreviations ### - + + /** + * Build footnote label by evaluating any placeholders. + * - ^^ footnote number + * - %% footnote reference number (Nth reference to footnote number) + * @param string $label + * @param int $footnote_number + * @param int $reference_number + * @return string + */ + protected function parseFootnotePlaceholders($label, $footnote_number, $reference_number) { + return str_replace( + array('^^', '%%'), + array($footnote_number, $reference_number), + $label + ); + } + + + /** + * Abbreviations - strips abbreviations from text, stores titles in hash + * references. + * @param string $text + * @return string + */ protected function stripAbbreviations($text) { - # - # Strips abbreviations from text, stores titles in hash references. - # $less_than_tab = $this->tab_width - 1; - # Link defs are in the form: [id]*: url "optional title" + // Link defs are in the form: [id]*: url "optional title" $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1 - (.*) # text = $2 (no blank lines allowed) + ^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?: # abbr_id = $1 + (.*) # text = $2 (no blank lines allowed) }xm', array($this, '_stripAbbreviations_callback'), $text); return $text; } + + /** + * Callback for stripping abbreviations + * @param array $matches + * @return string + */ protected function _stripAbbreviations_callback($matches) { $abbr_word = $matches[1]; $abbr_desc = $matches[2]; - if ($this->abbr_word_re) + if ($this->abbr_word_re) { $this->abbr_word_re .= '|'; + } $this->abbr_word_re .= preg_quote($abbr_word); $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); - return ''; # String that will replace the block + return ''; // String that will replace the block } - - + + /** + * Find defined abbreviations in text and wrap them in elements. + * @param string $text + * @return string + */ protected function doAbbreviations($text) { - # - # Find defined abbreviations in text and wrap them in elements. - # if ($this->abbr_word_re) { - // cannot use the /x modifier because abbr_word_re may + // cannot use the /x modifier because abbr_word_re may // contain significant spaces: - $text = preg_replace_callback('{'. - '(?abbr_word_re.')'. - '(?![\w\x1A])'. - '}', + $text = preg_replace_callback('{' . + '(?abbr_word_re . ')' . + '(?![\w\x1A])' . + '}', array($this, '_doAbbreviations_callback'), $text); } return $text; } + + /** + * Callback for processing abbreviations + * @param array $matches + * @return string + */ protected function _doAbbreviations_callback($matches) { $abbr = $matches[0]; if (isset($this->abbr_desciptions[$abbr])) { $desc = $this->abbr_desciptions[$abbr]; if (empty($desc)) { return $this->hashPart("$abbr"); - } else { - $desc = $this->encodeAttribute($desc); - return $this->hashPart("$abbr"); } - } else { - return $matches[0]; + $desc = $this->encodeAttribute($desc); + return $this->hashPart("$abbr"); } + return $matches[0]; } } diff --git a/third/Md/MarkdownInterface.inc.php b/third/Md/MarkdownInterface.inc.php index a023ed4..c4e9ac7 100644 --- a/third/Md/MarkdownInterface.inc.php +++ b/third/Md/MarkdownInterface.inc.php @@ -1,9 +1,9 @@ -# -# Original Markdown -# Copyright (c) 2004-2006 John Gruber -# -# +/** + * Markdown - A text-to-HTML conversion tool for web writers + * + * @package php-markdown + * @author Michel Fortin + * @copyright 2004-2019 Michel Fortin + * @copyright (Original Markdown) 2004-2006 John Gruber + */ + namespace Michelf; - -# -# Markdown Parser Interface -# - +/** + * Markdown Parser Interface + */ interface MarkdownInterface { + /** + * Initialize the parser and return the result of its transform method. + * This will work fine for derived classes too. + * + * @api + * + * @param string $text + * @return string + */ + public static function defaultTransform($text); - # - # Initialize the parser and return the result of its transform method. - # This will work fine for derived classes too. - # - public static function defaultTransform($text); - - # - # Main function. Performs some preprocessing on the input text - # and pass it through the document gamut. - # - public function transform($text); - + /** + * Main function. Performs some preprocessing on the input text + * and pass it through the document gamut. + * + * @api + * + * @param string $text + * @return string + */ + public function transform($text); }