diff options
author | Fabio Bas <ctrlaltca@gmail.com> | 2014-02-12 15:39:31 +0100 |
---|---|---|
committer | Fabio Bas <ctrlaltca@gmail.com> | 2014-02-12 15:39:31 +0100 |
commit | 5abbbca5332a4e5f9b8e5fb24a45611df9b874e2 (patch) | |
tree | 6265e26949d69c8516834e2eaaf4487cf5b07e56 | |
parent | b3c782faadcbd47a723b5cd0b08559e5964f37cf (diff) |
Dropped Markdown for Parsedown
-rw-r--r-- | framework/3rdParty/Markdown/License.text | 34 | ||||
-rw-r--r-- | framework/3rdParty/Markdown/MarkdownParser.php | 1256 | ||||
-rw-r--r-- | framework/3rdParty/Parsedown/LICENSE.txt | 20 | ||||
-rwxr-xr-x | framework/3rdParty/Parsedown/Parsedown.php | 1135 | ||||
-rw-r--r-- | framework/3rdParty/readme.html | 8 | ||||
-rw-r--r-- | framework/Web/UI/WebControls/TMarkdown.php | 9 |
6 files changed, 1163 insertions, 1299 deletions
diff --git a/framework/3rdParty/Markdown/License.text b/framework/3rdParty/Markdown/License.text deleted file mode 100644 index ea6a6a1a..00000000 --- a/framework/3rdParty/Markdown/License.text +++ /dev/null @@ -1,34 +0,0 @@ -Copyright (c) 2004-2005, John Gruber -<http://daringfireball.net/> -All rights reserved. - -Copyright (c) 2004-2005, Michel Fortin -<http://www.michelf.com/> -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. diff --git a/framework/3rdParty/Markdown/MarkdownParser.php b/framework/3rdParty/Markdown/MarkdownParser.php deleted file mode 100644 index b581dcb5..00000000 --- a/framework/3rdParty/Markdown/MarkdownParser.php +++ /dev/null @@ -1,1256 +0,0 @@ -<?php - -# -# Markdown - A text-to-HTML conversion tool for web writers -# -# Copyright (c) 2004-2005 John Gruber -# <http://daringfireball.net/projects/markdown/> -# -# Copyright (c) 2004-2005 Michel Fortin - PHP Port -# <http://www.michelf.com/projects/php-markdown/> -# - -/** - * PHP5 version of the markdown parser. - * Usage: - * <code> - * $markdown = new MarkdownParser; - * echo $markdown->parse($text); - * </code> - */ -class MarkdownParser -{ - private static $md_nested_brackets; - private static $md_escape_table = array(); - private static $md_backslash_escape_table = array(); - private static $md_nested_brackets_depth = 6; - - protected $md_empty_element_suffix = " />"; # Change to ">" for HTML output - protected $md_tab_width = 4; - - private $md_list_level = 0; - private $md_urls = array(); - private $md_titles = array(); - private $md_html_blocks = array(); - - public function __construct() - { - if(is_null(self::$md_nested_brackets)) - $this->initialize(); - } - - private function initialize() - { - self::$md_nested_brackets = - str_repeat('(?>[^\[\]]+|\[', self::$md_nested_brackets_depth). - str_repeat('\])*', self::$md_nested_brackets_depth); - - self::$md_escape_table = array( - "\\" => md5("\\"), - "`" => md5("`"), - "*" => md5("*"), - "_" => md5("_"), - "{" => md5("{"), - "}" => md5("}"), - "[" => md5("["), - "]" => md5("]"), - "(" => md5("("), - ")" => md5(")"), - ">" => md5(">"), - "#" => md5("#"), - "+" => md5("+"), - "-" => md5("-"), - "." => md5("."), - "!" => md5("!") - ); - - # Table of hash values for escaped characters: - # Create an identical table but for escaped characters. - foreach (self::$md_escape_table as $key => $char) - self::$md_backslash_escape_table["\\$key"] = $char; - } - - public function parse($text) - { - # - # Main function. The order in which other subs are called here is - # essential. Link and image substitutions need to happen before - # _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a> - # and <img> tags get encoded. - # - # Clear the hashes. If we don't clear these, you get conflicts - # from other articles when generating a page which contains more than - # one article (e.g. an index page that shows the N most recent - # articles): - $this->md_urls = array(); - $this->md_titles = array(); - $this->md_html_blocks = array(); - - # Standardize line endings: - # DOS to Unix and Mac to Unix - $text = str_replace(array("\r\n", "\r"), "\n", $text); - - # Make sure $text ends with a couple of newlines: - $text .= "\n\n"; - - # Convert all tabs to spaces. - $text = $this->_Detab($text); - - # Strip any lines consisting only of spaces and tabs. - # This makes subsequent regexen easier to write, because we can - # match consecutive blank lines with /\n+/ instead of something - # contorted like /[ \t]*\n+/ . - $text = preg_replace('/^[ \t]+$/m', '', $text); - - # Turn block-level HTML blocks into hash entries - $text = $this->_HashHTMLBlocks($text); - - # Strip link definitions, store in hashes. - $text = $this->_StripLinkDefinitions($text); - - $text = $this->_RunBlockGamut($text); - - $text = $this->_UnescapeSpecialChars($text); - - return $text . "\n"; - } - - - private function _StripLinkDefinitions($text) { - # - # Strips link definitions from text, stores the URLs and titles in - # hash references. - # - $less_than_tab = $this->md_tab_width - 1; - - # Link defs are in the form: ^[id]: url "optional title" - $text = preg_replace_callback('{ - ^[ ]{0,'.$less_than_tab.'}\[(.+)\]: # id = $1 - [ \t]* - \n? # maybe *one* newline - [ \t]* - <?(\S+?)>? # url = $2 - [ \t]* - \n? # maybe one newline - [ \t]* - (?: - (?<=\s) # lookbehind for whitespace - ["(] - (.+?) # title = $3 - [")] - [ \t]* - )? # title is optional - (?:\n+|\Z) - }xm', - array($this,'_StripLinkDefinitions_callback'), - $text); - return $text; - } - - private function _StripLinkDefinitions_callback($matches) { - $link_id = strtolower($matches[1]); - $this->md_urls[$link_id] = $this->_EncodeAmpsAndAngles($matches[2]); - if (isset($matches[3])) - $this->md_titles[$link_id] = str_replace('"', '"', $matches[3]); - return ''; # String that will replace the block - } - - - private function _HashHTMLBlocks($text) { - $less_than_tab = $this->md_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 <p>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: - $block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. - 'script|noscript|form|fieldset|iframe|math|ins|del'; - $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. - 'script|noscript|form|fieldset|iframe|math'; - - # First, look for nested blocks, e.g.: - # <div> - # <div> - # tags for inner block must be indented. - # </div> - # </div> - # - # 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 `<div>` and stop at the first `</div>`. - $text = preg_replace_callback("{ - ( # save in $1 - ^ # start of line (with /m) - <($block_tags_a) # start tag = $2 - \\b # word break - (.*\\n)*? # any number of lines, minimally matching - </\\2> # the matching end tag - [ \\t]* # trailing spaces/tabs - (?=\\n+|\\Z) # followed by a newline or end of document - ) - }xm", - array($this,'_HashHTMLBlocks_callback'), - $text); - - # - # Now match more liberally, simply from `\n<tag>` to `</tag>\n` - # - $text = preg_replace_callback("{ - ( # save in $1 - ^ # start of line (with /m) - <($block_tags_b) # start tag = $2 - \\b # word break - (.*\\n)*? # any number of lines, minimally matching - .*</\\2> # the matching end tag - [ \\t]* # trailing spaces/tabs - (?=\\n+|\\Z) # followed by a newline or end of document - ) - }xm", - array($this,'_HashHTMLBlocks_callback'), - $text); - - # Special case just for <hr />. It was easier to make a special case than - # to make the other regex more complicated. - $text = preg_replace_callback('{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,'.$less_than_tab.'} - <(hr) # start tag = $2 - \b # word break - ([^<>])*? # - /?> # the matching end tag - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }x', - array($this,'_HashHTMLBlocks_callback'), - $text); - - # Special case for standalone HTML comments: - $text = preg_replace_callback('{ - (?: - (?<=\n\n) # Starting after a blank line - | # or - \A\n? # the beginning of the doc - ) - ( # save in $1 - [ ]{0,'.$less_than_tab.'} - (?s: - <! - (--.*?--\s*)+ - > - ) - [ \t]* - (?=\n{2,}|\Z) # followed by a blank line or end of document - ) - }x', - array($this,'_HashHTMLBlocks_callback'), - $text); - - return $text; - } - private function _HashHTMLBlocks_callback($matches) { - $text = $matches[1]; - $key = md5($text); - $this->md_html_blocks[$key] = $text; - return "\n\n$key\n\n"; # String that will replace the block - } - - - private function _RunBlockGamut($text) { - # - # These are all the transformations that form block-level - # tags like paragraphs, headers, and list items. - # - $text = $this->_DoHeaders($text); - - # Do Horizontal Rules: - $text = preg_replace( - array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}mx', - '{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}mx', - '{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}mx'), - "\n<hr{$this->md_empty_element_suffix}\n", - $text); - - $text = $this->_DoLists($text); - $text = $this->_DoCodeBlocks($text); - $text = $this->_DoBlockQuotes($text); - - # We already ran _HashHTMLBlocks() before, in Markdown(), but that - # was to escape raw HTML in the original Markdown source. This time, - # we're escaping the markup we've just created, so that we don't wrap - # <p> tags around block-level tags. - $text = $this->_HashHTMLBlocks($text); - $text = $this->_FormParagraphs($text); - - return $text; - } - - - private function _RunSpanGamut($text) { - # - # These are all the transformations that occur *within* block-level - # tags like paragraphs, headers, and list items. - # - - $text = $this->_DoCodeSpans($text); - - $text = $this->_EscapeSpecialChars($text); - - # Process anchor and image tags. Images must come first, - # because ![foo][f] looks like an anchor. - $text = $this->_DoImages($text); - $text = $this->_DoAnchors($text); - - # Make links out of things like `<http://example.com/>` - # Must come after _DoAnchors(), because you can use < and > - # delimiters in inline links like [this](<url>). - $text = $this->_DoAutoLinks($text); - $text = $this->_EncodeAmpsAndAngles($text); - $text = $this->_DoItalicsAndBold($text); - - # Do hard breaks: - $text = preg_replace('/ {2,}\n/', "<br{$this->md_empty_element_suffix}\n", $text); - - return $text; - } - - - private function _EscapeSpecialChars($text) { - $tokens = $this->_TokenizeHTML($text); - - $text = ''; # rebuild $text from the tokens - # $in_pre = 0; # Keep track of when we're inside <pre> or <code> tags. - # $tags_to_skip = "!<(/?)(?:pre|code|kbd|script|math)[\s>]!"; - - foreach ($tokens as $cur_token) { - if ($cur_token[0] == 'tag') { - # Within tags, encode * and _ so they don't conflict - # with their use in Markdown for italics and strong. - # We're replacing each such character with its - # corresponding MD5 checksum value; this is likely - # overkill, but it should prevent us from colliding - # with the escape values by accident. - $cur_token[1] = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $cur_token[1]); - $text .= $cur_token[1]; - } else { - $t = $cur_token[1]; - $t = $this->_EncodeBackslashEscapes($t); - $text .= $t; - } - } - return $text; - } - - - private function _DoAnchors($text) { - # - # Turn Markdown link shortcuts into XHTML <a> tags. - # - # - # First, handle reference-style links: [link text] [id] - # - $bracket = self::$md_nested_brackets; - $text = preg_replace_callback("{ - ( # wrap whole match in $1 - \\[ - ({$bracket}) # link text = $2 - \\] - - [ ]? # one optional space - (?:\\n[ ]*)? # one optional newline followed by spaces - - \\[ - (.*?) # id = $3 - \\] - ) - }xs", - array($this,'_DoAnchors_reference_callback'), $text); - - # - # Next, inline-style links: [link text](url "optional title") - # - $text = preg_replace_callback("{ - ( # wrap whole match in $1 - \\[ - ({$bracket}) # link text = $2 - \\] - \\( # literal paren - [ \\t]* - <?(.*?)>? # href = $3 - [ \\t]* - ( # $4 - (['\"]) # quote char = $5 - (.*?) # Title = $6 - \\5 # matching quote - )? # title is optional - \\) - ) - }xs", - array($this,'_DoAnchors_inline_callback'), $text); - - return $text; - } - private function _DoAnchors_reference_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $link_id = strtolower($matches[3]); - - if ($link_id == "") { - $link_id = strtolower($link_text); # for shortcut links like [this][]. - } - - if (isset($this->md_urls[$link_id])) { - $url = $this->md_urls[$link_id]; - # We've got to encode these to avoid conflicting with italics/bold. - $url = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $url); - $result = "<a href=\"$url\""; - if ( isset( $this->md_titles[$link_id] ) ) { - $title = $this->md_titles[$link_id]; - $title = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], - self::$md_escape_table['_']), $title); - $result .= " title=\"$title\""; - } - $result .= ">$link_text</a>"; - } - else { - $result = $whole_match; - } - return $result; - } - private function _DoAnchors_inline_callback($matches) { - $whole_match = $matches[1]; - $link_text = $matches[2]; - $url = $matches[3]; - $title =& $matches[6]; - - # We've got to encode these to avoid conflicting with italics/bold. - $url = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $url); - $result = "<a href=\"$url\""; - if (isset($title)) { - $title = str_replace('"', '"', $title); - $title = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $title); - $result .= " title=\"$title\""; - } - - $result .= ">$link_text</a>"; - - return $result; - } - - - private function _DoImages($text) { - # - # Turn Markdown image shortcuts into <img> tags. - # - # - # First, handle reference-style labeled images: ![alt text][id] - # - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.self::$md_nested_brackets.') # alt text = $2 - \] - - [ ]? # one optional space - (?:\n[ ]*)? # one optional newline followed by spaces - - \[ - (.*?) # id = $3 - \] - - ) - }xs', - array($this,'_DoImages_reference_callback'), $text); - - # - # Next, handle inline images: ![alt text](url "optional title") - # Don't forget: encode * and _ - - $text = preg_replace_callback('{ - ( # wrap whole match in $1 - !\[ - ('.self::$md_nested_brackets.') # alt text = $2 - \] - \( # literal paren - [ \t]* - <?(\S+?)>? # src url = $3 - [ \t]* - ( # $4 - ([\'"]) # quote char = $5 - (.*?) # title = $6 - \5 # matching quote - [ \t]* - )? # title is optional - \) - ) - }xs', - array($this,'_DoImages_inline_callback'), $text); - - return $text; - } - private 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][]. - } - - $alt_text = str_replace('"', '"', $alt_text); - if (isset($this->md_urls[$link_id])) { - $url = $this->md_urls[$link_id]; - # We've got to encode these to avoid conflicting with italics/bold. - $url = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $url); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($this->md_titles[$link_id])) { - $title = $this->md_titles[$link_id]; - $title = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], - self::$md_escape_table['_']), $title); - $result .= " title=\"$title\""; - } - $result .= $this->md_empty_element_suffix; - } - else { - # If there's no such link ID, leave intact: - $result = $whole_match; - } - - return $result; - } - private function _DoImages_inline_callback($matches) { - $whole_match = $matches[1]; - $alt_text = $matches[2]; - $url = $matches[3]; - $title = ''; - if (isset($matches[6])) { - $title = $matches[6]; - } - - $alt_text = str_replace('"', '"', $alt_text); - $title = str_replace('"', '"', $title); - # We've got to encode these to avoid conflicting with italics/bold. - $url = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $url); - $result = "<img src=\"$url\" alt=\"$alt_text\""; - if (isset($title)) { - $title = str_replace(array('*', '_'), - array(self::$md_escape_table['*'], self::$md_escape_table['_']), - $title); - $result .= " title=\"$title\""; # $title already quoted - } - $result .= $this->md_empty_element_suffix; - - return $result; - } - - - private function _DoHeaders($text) { - # Setext-style headers: - # Header 1 - # ======== - # - # Header 2 - # -------- - # - $text = preg_replace( - array('{ ^(.+)[ \t]*\n=+[ \t]*\n+ }emx', - '{ ^(.+)[ \t]*\n-+[ \t]*\n+ }emx'), - array("'<h1>'.\$this->_RunSpanGamut(\$this->_UnslashQuotes('\\1')).'</h1>\n\n'", - "'<h2>'.\$this->_RunSpanGamut(\$this->_UnslashQuotes('\\1')).'</h2>\n\n'"), - $text); - - # atx-style headers: - # # Header 1 - # ## Header 2 - # ## Header 2 with closing hashes ## - # ... - # ###### Header 6 - # - $text = preg_replace("{ - ^(\\#{1,6}) # $1 = string of #'s - [ \\t]* - (.+?) # $2 = Header text - [ \\t]* - \\#* # optional closing #'s (not counted) - \\n+ - }xme", - "'<h'.strlen('\\1').'>'.\$this->_RunSpanGamut(\$this->_UnslashQuotes('\\2')).'</h'.strlen('\\1').'>\n\n'", - $text); - - return $text; - } - - - private function _DoLists($text) { - # - # Form HTML ordered (numbered) and unordered (bulleted) lists. - # - $less_than_tab = $this->md_tab_width - 1; - - # Re-usable patterns to match list item bullets and number markers: - $marker_ul = '[*+-]'; - $marker_ol = '\d+[.]'; - $marker_any = "(?:$marker_ul|$marker_ol)"; - - $markers = array($marker_ul, $marker_ol); - - foreach ($markers as $marker) { - # Re-usable pattern to match any entirel ul or ol list: - $whole_list = ' - ( # $1 = whole list - ( # $2 - [ ]{0,'.$less_than_tab.'} - ('.$marker.') # $3 = first list item marker - [ \t]+ - ) - (?s:.+?) - ( # $4 - \z - | - \n{2,} - (?=\S) - (?! # Negative lookahead for another list item marker - [ \t]* - '.$marker.'[ \t]+ - ) - ) - ) - '; // mx - - # We use a different prefix before nested lists than top-level lists. - # See extended comment in _ProcessListItems(). - - if ($this->md_list_level) { - $text = preg_replace_callback('{ - ^ - '.$whole_list.' - }mx', - array($this,'_DoLists_callback_top'), $text); - } - else { - $text = preg_replace_callback('{ - (?:(?<=\n\n)|\A\n?) - '.$whole_list.' - }mx', - array($this,'_DoLists_callback_nested'), $text); - } - } - - return $text; - } - private function _DoLists_callback_top($matches) { - # Re-usable patterns to match list item bullets and number markers: - $marker_ul = '[*+-]'; - $marker_ol = '\d+[.]'; - $marker_any = "(?:$marker_ul|$marker_ol)"; - - $list = $matches[1]; - $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol"; - - $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol ); - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list = preg_replace("/\n{2,}/", "\n\n\n", $list); - $result = $this->_ProcessListItems($list, $marker_any); - - # Trim any trailing whitespace, to put the closing `</$list_type>` - # up on the preceding line, to get it past the current stupid - # HTML block parser. This is a hack to work around the terrible - # hack that is the HTML block parser. - $result = rtrim($result); - $result = "<$list_type>" . $result . "</$list_type>\n"; - return $result; - } - private function _DoLists_callback_nested($matches) { - # Re-usable patterns to match list item bullets and number markers: - $marker_ul = '[*+-]'; - $marker_ol = '\d+[.]'; - $marker_any = "(?:$marker_ul|$marker_ol)"; - - $list = $matches[1]; - $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol"; - - $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol ); - - # Turn double returns into triple returns, so that we can make a - # paragraph for the last item in a list, if necessary: - $list = preg_replace("/\n{2,}/", "\n\n\n", $list); - $result = $this->_ProcessListItems($list, $marker_any); - $result = "<$list_type>\n" . $result . "</$list_type>\n"; - return $result; - } - - - private function _ProcessListItems($list_str, $marker_any) { - # - # Process the contents of a single ordered or unordered list, splitting it - # into individual list items. - # - - # The $md_list_level 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->md_list_level++; - - # trim trailing blank lines: - $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); - - $list_str = preg_replace_callback('{ - (\n)? # leading line = $1 - (^[ \t]*) # leading whitespace = $2 - ('.$marker_any.') [ \t]+ # list marker = $3 - ((?s:.+?) # list item text = $4 - (\n{1,2})) - (?= \n* (\z | \2 ('.$marker_any.') [ \t]+)) - }xm', - array($this,'_ProcessListItems_callback'), $list_str); - - $this->md_list_level--; - return $list_str; - } - private function _ProcessListItems_callback($matches) { - $item = $matches[4]; - $leading_line =& $matches[1]; - $leading_space =& $matches[2]; - - if ($leading_line || preg_match('/\n{2,}/', $item)) { - $item = $this->_RunBlockGamut($this->_Outdent($item)); - } - else { - # Recursion for sub-lists: - $item = $this->_DoLists($this->_Outdent($item)); - $item = preg_replace('/\n+$/', '', $item); - $item = $this->_RunSpanGamut($item); - } - - return "<li>" . $item . "</li>\n"; - } - - - private function _DoCodeBlocks($text) { - # - # Process Markdown `<pre><code>` blocks. - # - $text = preg_replace_callback('{ - (?:\n\n|\A) - ( # $1 = the code block -- one or more lines, starting with a space/tab - (?: - (?:[ ]{'.$this->md_tab_width.'} | \t) # Lines must start with a tab or a tab-width of spaces - .*\n+ - )+ - ) - ((?=^[ ]{0,'.$this->md_tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc - }xm', - array($this,'_DoCodeBlocks_callback'), $text); - - return $text; - } - private function _DoCodeBlocks_callback($matches) { - $codeblock = $matches[1]; - - $codeblock = $this->_EncodeCode($this->_Outdent($codeblock)); - // $codeblock = _Detab($codeblock); - # trim leading newlines and trailing whitespace - $codeblock = preg_replace(array('/\A\n+/', '/\s+\z/'), '', $codeblock); - - $result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n"; - - return $result; - } - - - private function _DoCodeSpans($text) { - # - # * Backtick quotes are used for <code></code> spans. - # - # * You can use multiple backticks as the delimiters if you want to - # include literal backticks in the code span. So, this input: - # - # Just type ``foo `bar` baz`` at the prompt. - # - # Will translate to: - # - # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> - # - # There's no arbitrary limit to the number of backticks you - # can use as delimters. If you need three consecutive backticks - # in your code, use four for delimiters, etc. - # - # * You can use spaces to get literal backticks at the edges: - # - # ... type `` `bar` `` ... - # - # Turns to: - # - # ... type <code>`bar`</code> ... - # - $text = preg_replace_callback('@ - (?<!\\\) # Character before opening ` can\'t be a backslash - (`+) # $1 = Opening run of ` - (.+?) # $2 = The code block - (?<!`) - \1 # Matching closer - (?!`) - @xs', - array($this,'_DoCodeSpans_callback'), $text); - - return $text; - } - private function _DoCodeSpans_callback($matches) { - $c = $matches[2]; - $c = preg_replace('/^[ \t]*/', '', $c); # leading whitespace - $c = preg_replace('/[ \t]*$/', '', $c); # trailing whitespace - $c = $this->_EncodeCode($c); - return "<code>$c</code>"; - } - - - private function _EncodeCode($_) { - # - # Encode/escape certain characters inside Markdown code runs. - # The point is that in code, these characters are literals, - # and lose their special Markdown meanings. - # - # Encode all ampersands; HTML entities are not - # entities within a Markdown code span. - $_ = str_replace('&', '&', $_); - - # Do the angle bracket song and dance: - $_ = str_replace(array('<', '>'), - array('<', '>'), $_); - - # Now, escape characters that are magic in Markdown: - $_ = str_replace(array_keys(self::$md_escape_table), - array_values(self::$md_escape_table), $_); - - return $_; - } - - - private function _DoItalicsAndBold($text) { - # <strong> must go first: - $text = preg_replace('{ - ( # $1: Marker - (?<!\*\*) \*\* | # (not preceded by two chars of - (?<!__) __ # the same marker) - ) - (?=\S) # Not followed by whitespace - (?!\1) # or two others marker chars. - ( # $2: Content - (?: - [^*_]+? # Anthing not em markers. - | - # Balence any regular emphasis inside. - ([*_]) (?=\S) .+? (?<=\S) \3 # $3: em char (* or _) - | - (?! \1 ) . # Allow unbalenced * and _. - )+? - ) - (?<=\S) \1 # End mark not preceded by whitespace. - }sx', - '<strong>\2</strong>', $text); - # Then <em>: - $text = preg_replace( - '{ ( (?<!\*)\* | (?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S) \1 }sx', - '<em>\2</em>', $text); - - return $text; - } - - - private function _DoBlockQuotes($text) { - $text = preg_replace_callback('/ - ( # Wrap whole match in $1 - ( - ^[ \t]*>[ \t]? # ">" at the start of a line - .+\n # rest of the first line - (.+\n)* # subsequent consecutive lines - \n* # blanks - )+ - ) - /xm', - array($this,'_DoBlockQuotes_callback'), $text); - - return $text; - } - private function _DoBlockQuotes_callback($matches) { - $bq = $matches[1]; - # trim one level of quoting - trim whitespace-only lines - $bq = preg_replace(array('/^[ \t]*>[ \t]?/m', '/^[ \t]+$/m'), '', $bq); - $bq = $this->_RunBlockGamut($bq); # recurse - - $bq = preg_replace('/^/m', " ", $bq); - # These leading spaces screw with <pre> content, so we need to fix that: - $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', - array($this,'_DoBlockQuotes_callback2'), $bq); - - return "<blockquote>\n$bq\n</blockquote>\n\n"; - } - private function _DoBlockQuotes_callback2($matches) { - $pre = $matches[1]; - $pre = preg_replace('/^ /m', '', $pre); - return $pre; - } - - - private function _FormParagraphs($text) { - # - # Params: - # $text - string to process with html <p> tags - # - # Strip leading and trailing lines: - $text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text); - - $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - # - # Wrap <p> tags. - # - foreach ($grafs as $key => $value) { - if (!isset( $this->md_html_blocks[$value] )) { - $value = $this->_RunSpanGamut($value); - $value = preg_replace('/^([ \t]*)/', '<p>', $value); - $value .= "</p>"; - $grafs[$key] = $value; - } - } - - # - # Unhashify HTML blocks - # - foreach ($grafs as $key => $value) { - if (isset( $this->md_html_blocks[$value] )) { - $grafs[$key] = $this->md_html_blocks[$value]; - } - } - - return implode("\n\n", $grafs); - } - - - private function _EncodeAmpsAndAngles($text) { - # Smart processing for ampersands and angle brackets that need to be encoded. - - # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: - # http://bumppo.net/projects/amputator/ - $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', - '&', $text);; - - # Encode naked <'s - $text = preg_replace('{<(?![a-z/?\$!])}i', '<', $text); - - return $text; - } - - - private function _EncodeBackslashEscapes($text) { - # - # Parameter: String. - # Returns: The string, with after processing the following backslash - # escape sequences. - # - # Must process escaped backslashes first. - return str_replace(array_keys(self::$md_backslash_escape_table), - array_values(self::$md_backslash_escape_table), $text); - } - - - private function _DoAutoLinks($text) { - $text = preg_replace("!<((https?|ftp):[^'\">\\s]+)>!", - '<a href="\1">\1</a>', $text); - - # Email addresses: <address@domain.foo> - $text = preg_replace('{ - < - (?:mailto:)? - ( - [-.\w]+ - \@ - [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ - ) - > - }exi', - "\$this->_EncodeEmailAddress(\$this->_UnescapeSpecialChars(\$this->_UnslashQuotes('\\1')))", - $text); - - return $text; - } - - - private function _EncodeEmailAddress($addr) { - # - # Input: an email address, e.g. "foo@example.com" - # - # Output: the email address as a mailto link, with each character - # of the address encoded as either a decimal or hex entity, in - # the hopes of foiling most address harvesting spam bots. E.g.: - # - # <a href="mailto:foo@e - # xample.com">foo - # @example.com</a> - # - # Based by a filter by Matthew Wickline, posted to the BBEdit-Talk - # mailing list: <http://tinyurl.com/yu7ue> - # - $addr = "mailto:" . $addr; - $length = strlen($addr); - - # leave ':' alone (to spot mailto: later) - $addr = preg_replace_callback('/([^\:])/', - array($this,'_EncodeEmailAddress_callback'), $addr); - - $addr = "<a href=\"$addr\">$addr</a>"; - # strip the mailto: from the visible part - $addr = preg_replace('/">.+?:/', '">', $addr); - - return $addr; - } - private function _EncodeEmailAddress_callback($matches) { - $char = $matches[1]; - $r = rand(0, 100); - # roughly 10% raw, 45% hex, 45% dec - # '@' *must* be encoded. I insist. - if ($r > 90 && $char != '@') return $char; - if ($r < 45) return '&#x'.dechex(ord($char)).';'; - return '&#'.ord($char).';'; - } - - - private function _UnescapeSpecialChars($text) { - # - # Swap back in all the special characters we've hidden. - # - return str_replace(array_values(self::$md_escape_table), - array_keys(self::$md_escape_table), $text); - } - - - # _TokenizeHTML is shared between PHP Markdown and PHP SmartyPants. - # We only define it if it is not already defined. - - private function _TokenizeHTML($str) { - # - # Parameter: String containing HTML markup. - # Returns: An array of the tokens comprising the input - # string. Each token is either a tag (possibly with nested, - # tags contained therein, such as <a href="<MTFoo>">, or a - # run of text between tags. Each element of the array is a - # two-element array; the first is either 'tag' or 'text'; - # the second is the actual value. - # - # - # Regular expression derived from the _tokenize() subroutine in - # Brad Choate's MTRegex plugin. - # <http://www.bradchoate.com/past/mtregex.php> - # - $index = 0; - $tokens = array(); - - $match = '(?s:<!(?:--.*?--\s*)+>)|'. # comment - '(?s:<\?.*?\?>)|'. # processing instruction - # regular tags - '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)'; - - $parts = preg_split("{($match)}", $str, -1, PREG_SPLIT_DELIM_CAPTURE); - - foreach ($parts as $part) { - if (++$index % 2 && $part != '') - $tokens[] = array('text', $part); - else - $tokens[] = array('tag', $part); - } - - return $tokens; - } - - private function _Outdent($text) { - # - # Remove one level of line-leading tabs or spaces - # - return preg_replace("/^(\\t|[ ]{1,".$this->md_tab_width."})/m", "", $text); - } - - - private 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. - - $lines = explode("\n", $text); - $text = ""; - - foreach ($lines as $line) { - # Split in blocks. - $blocks = explode("\t", $line); - # Add each blocks to the line. - $line = $blocks[0]; - unset($blocks[0]); # Do not add first block twice. - foreach ($blocks as $block) { - # Calculate amount of space, insert spaces, insert block. - $amount = $this->md_tab_width - strlen($line) % $this->md_tab_width; - $line .= str_repeat(" ", $amount) . $block; - } - $text .= "$line\n"; - } - return $text; - } - - - private function _UnslashQuotes($text) { - # - # This function is useful to remove automaticaly slashed double quotes - # when using preg_replace and evaluating an expression. - # Parameter: String. - # Returns: The string with any slash-double-quote (\") sequence replaced - # by a single double quote. - # - return str_replace('\"', '"', $text); - } -} - -/* - -PHP Markdown -============ - -Description ------------ - -This is a PHP translation of the original Markdown formatter written in -Perl by John Gruber. - -Markdown is a text-to-HTML filter; it translates an easy-to-read / -easy-to-write structured text format into HTML. Markdown's text format -is most similar to that of plain text email, and supports features such -as headers, *emphasis*, code blocks, blockquotes, and links. - -Markdown's syntax is designed not as a generic markup language, but -specifically to serve as a front-end to (X)HTML. You can use span-level -HTML tags anywhere in a Markdown document, and you can use block level -HTML tags (like <div> and <table> as well). - -For more information about Markdown's syntax, see: - -<http://daringfireball.net/projects/markdown/> - - -Bugs ----- - -To file bug reports please send email to: - -<michel.fortin@michelf.com> - -Please include with your report: (1) the example input; (2) the output you -expected; (3) the output Markdown actually produced. - - -Version History ---------------- - -See the readme file for detailed release notes for this version. - -1.0.1c - 9 Dec 2005 - -1.0.1b - 6 Jun 2005 - -1.0.1a - 15 Apr 2005 - -1.0.1 - 16 Dec 2004 - -1.0 - 21 Aug 2004 - - -Author & Contributors ---------------------- - -Original Perl version by John Gruber -<http://daringfireball.net/> - -PHP port and other contributions by Michel Fortin -<http://www.michelf.com/> - - -Copyright and License ---------------------- - -Copyright (c) 2004-2005 Michel Fortin -<http://www.michelf.com/> -All rights reserved. - -Copyright (c) 2003-2004 John Gruber -<http://daringfireball.net/> -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name "Markdown" nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -This software is provided by the copyright holders and contributors "as -is" and any express or implied warranties, including, but not limited -to, the implied warranties of merchantability and fitness for a -particular purpose are disclaimed. In no event shall the copyright owner -or contributors be liable for any direct, indirect, incidental, special, -exemplary, or consequential damages (including, but not limited to, -procurement of substitute goods or services; loss of use, data, or -profits; or business interruption) however caused and on any theory of -liability, whether in contract, strict liability, or tort (including -negligence or otherwise) arising in any way out of the use of this -software, even if advised of the possibility of such damage. - -*/ diff --git a/framework/3rdParty/Parsedown/LICENSE.txt b/framework/3rdParty/Parsedown/LICENSE.txt new file mode 100644 index 00000000..baca86f5 --- /dev/null +++ b/framework/3rdParty/Parsedown/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Emanuil Rusev, erusev.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/framework/3rdParty/Parsedown/Parsedown.php b/framework/3rdParty/Parsedown/Parsedown.php new file mode 100755 index 00000000..ebc80974 --- /dev/null +++ b/framework/3rdParty/Parsedown/Parsedown.php @@ -0,0 +1,1135 @@ +<?php + +# +# +# Parsedown +# http://parsedown.org +# +# (c) Emanuil Rusev +# http://erusev.com +# +# For the full license information, view the LICENSE file that was distributed +# with this source code. +# +# + +class Parsedown +{ + # Multiton + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new Parsedown(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Setters + # + + # Enables GFM line breaks. + + function set_breaks_enabled($breaks_enabled) + { + $this->breaks_enabled = $breaks_enabled; + + return $this; + } + + private $breaks_enabled = false; + + # + # Synopsis + # + + # Markdown is intended to be easy-to-read by humans - those of us who read + # line by line, left to right, top to bottom. In order to take advantage of + # this, Parsedown tries to read in a similar way. It breaks texts into + # lines, it iterates through them and it looks at how they start and relate + # to each other. + + # + # Methods + # + + function parse($text) + { + # standardize line breaks + $text = str_replace("\r\n", "\n", $text); + $text = str_replace("\r", "\n", $text); + + # replace tabs with spaces + $text = str_replace("\t", ' ', $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # convert lines into html + $text = $this->parse_block_elements($lines); + + # remove trailing line breaks + $text = chop($text, "\n"); + + return $text; + } + + # + # Private + + private function parse_block_elements(array $lines, $context = '') + { + $blocks = array(); + + $block = array( + 'type' => '', + ); + + foreach ($lines as $line) + { + # context + + switch ($block['type']) + { + case 'fenced': + + if ( ! isset($block['closed'])) + { + if (preg_match('/^[ ]*'.$block['fence'][0].'{3,}[ ]*$/', $line)) + { + $block['closed'] = true; + } + else + { + if ($block['text'] !== '') + { + $block['text'] .= "\n"; + } + + $block['text'] .= $line; + } + + continue 2; + } + + break; + + case 'markup': + + if ( ! isset($block['closed'])) + { + if (strpos($line, $block['start']) !== false) # opening tag + { + $block['depth']++; + } + + if (strpos($line, $block['end']) !== false) # closing tag + { + if ($block['depth'] > 0) + { + $block['depth']--; + } + else + { + $block['closed'] = true; + } + } + + $block['text'] .= "\n".$line; + + continue 2; + } + + break; + } + + # ~ + + $indentation = 0; + + while(isset($line[$indentation]) and $line[$indentation] === ' ') + { + $indentation++; + } + + $outdented_line = $indentation > 0 ? ltrim($line) : $line; + + # blank + + if ($outdented_line === '') + { + $block['interrupted'] = true; + + continue; + } + + # context + + switch ($block['type']) + { + case 'quote': + + if ( ! isset($block['interrupted'])) + { + $line = preg_replace('/^[ ]*>[ ]?/', '', $line); + + $block['lines'] []= $line; + + continue 2; + } + + break; + + case 'li': + + if ($block['indentation'] === $indentation and preg_match('/^'.$block['marker'].'[ ]+(.*)/', $outdented_line, $matches)) + { + unset($block['last']); + + $blocks []= $block; + + $block['last'] = true; + $block['lines'] = array($matches[1]); + + unset($block['first']); + unset($block['interrupted']); + + continue 2; + } + + if ( ! isset($block['interrupted'])) + { + $line = preg_replace('/^[ ]{0,'.$block['baseline'].'}/', '', $line); + + $block['lines'] []= $line; + + continue 2; + } + elseif ($line[0] === ' ') + { + $block['lines'] []= ''; + + $line = preg_replace('/^[ ]{0,'.$block['baseline'].'}/', '', $line); + + $block['lines'] []= $line; + + unset($block['interrupted']); + + continue 2; + } + + break; + } + + # indentation sensitive types + + switch ($line[0]) + { + case ' ': + + # code + + if ($indentation >= 4) + { + $code_line = substr($line, 4); + + if ($block['type'] === 'code') + { + if (isset($block['interrupted'])) + { + $block['text'] .= "\n"; + + unset($block['interrupted']); + } + + $block['text'] .= "\n".$code_line; + } + else + { + $blocks []= $block; + + $block = array( + 'type' => 'code', + 'text' => $code_line, + ); + } + + continue 2; + } + + break; + + case '#': + + # atx heading (#) + + if (isset($line[1])) + { + $blocks []= $block; + + $level = 1; + + while (isset($line[$level]) and $line[$level] === '#') + { + $level++; + } + + $block = array( + 'type' => 'heading', + 'text' => trim($line, '# '), + 'level' => $level, + ); + + continue 2; + } + + break; + + case '-': + case '=': + + # setext heading (===) + + if ($block['type'] === 'paragraph' and isset($block['interrupted']) === false) + { + $chopped_line = chop($line); + + $i = 1; + + while (isset($chopped_line[$i])) + { + if ($chopped_line[$i] !== $line[0]) + { + break 2; + } + + $i++; + } + + $block['type'] = 'heading'; + + $block['level'] = $line[0] === '-' ? 2 : 1; + + continue 2; + } + + break; + } + + # indentation insensitive types + + switch ($outdented_line[0]) + { + case '<': + + $position = strpos($outdented_line, '>'); + + if ($position > 1) + { + $substring = substr($outdented_line, 1, $position - 1); + + $substring = chop($substring); + + if (substr($substring, -1) === '/') + { + $is_self_closing = true; + + $substring = substr($substring, 0, -1); + } + + $position = strpos($substring, ' '); + + if ($position) + { + $name = substr($substring, 0, $position); + } + else + { + $name = $substring; + } + + if ( ! ctype_alpha($name)) + { + break; + } + + if (in_array($name, self::$text_level_elements)) + { + break; + } + + $blocks []= $block; + + if (isset($is_self_closing)) + { + $block = array( + 'type' => 'self-closing tag', + 'text' => $outdented_line, + ); + + unset($is_self_closing); + + continue 2; + } + + $block = array( + 'type' => 'markup', + 'text' => $outdented_line, + 'start' => '<'.$name.'>', + 'end' => '</'.$name.'>', + 'depth' => 0, + ); + + if (strpos($outdented_line, $block['end'])) + { + $block['closed'] = true; + } + + continue 2; + } + + break; + + case '>': + + # quote + + if (preg_match('/^>[ ]?(.*)/', $outdented_line, $matches)) + { + $blocks []= $block; + + $block = array( + 'type' => 'quote', + 'lines' => array( + $matches[1], + ), + ); + + continue 2; + } + + break; + + case '[': + + # reference + + $position = strpos($outdented_line, ']:'); + + if ($position) + { + $reference = array(); + + $label = substr($outdented_line, 1, $position - 1); + $label = strtolower($label); + + $substring = substr($outdented_line, $position + 2); + $substring = trim($substring); + + if ($substring === '') + { + break; + } + + if ($substring[0] === '<') + { + $position = strpos($substring, '>'); + + if ($position === false) + { + break; + } + + $reference['»'] = substr($substring, 1, $position - 1); + + $substring = substr($substring, $position + 1); + } + else + { + $position = strpos($substring, ' '); + + if ($position === false) + { + $reference['»'] = $substring; + + $substring = false; + } + else + { + $reference['»'] = substr($substring, 0, $position); + + $substring = substr($substring, $position + 1); + } + } + + if ($substring !== false) + { + if ($substring[0] !== '"' and $substring[0] !== "'" and $substring[0] !== '(') + { + break; + } + + $last_char = substr($substring, -1); + + if ($last_char !== '"' and $last_char !== "'" and $last_char !== ')') + { + break; + } + + $reference['#'] = substr($substring, 1, -1); + } + + $this->reference_map[$label] = $reference; + + continue 2; + } + + break; + + case '`': + case '~': + + # fenced code block + + if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\S+)?[ ]*$/', $outdented_line, $matches)) + { + $blocks []= $block; + + $block = array( + 'type' => 'fenced', + 'text' => '', + 'fence' => $matches[1], + ); + + if (isset($matches[2])) + { + $block['language'] = $matches[2]; + } + + continue 2; + } + + break; + + case '*': + case '+': + case '-': + case '_': + + # hr + + if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $outdented_line)) + { + $blocks []= $block; + + $block = array( + 'type' => 'rule', + ); + + continue 2; + } + + # li + + if (preg_match('/^([*+-][ ]+)(.*)/', $outdented_line, $matches)) + { + $blocks []= $block; + + $baseline = $indentation + strlen($matches[1]); + + $block = array( + 'type' => 'li', + 'indentation' => $indentation, + 'baseline' => $baseline, + 'marker' => '[*+-]', + 'first' => true, + 'last' => true, + 'lines' => array(), + ); + + $block['lines'] []= preg_replace('/^[ ]{0,4}/', '', $matches[2]); + + continue 2; + } + } + + # li + + if ($outdented_line[0] <= '9' and preg_match('/^(\d+[.][ ]+)(.*)/', $outdented_line, $matches)) + { + $blocks []= $block; + + $baseline = $indentation + strlen($matches[1]); + + $block = array( + 'type' => 'li', + 'indentation' => $indentation, + 'baseline' => $baseline, + 'marker' => '\d+[.]', + 'first' => true, + 'last' => true, + 'ordered' => true, + 'lines' => array(), + ); + + $block['lines'] []= preg_replace('/^[ ]{0,4}/', '', $matches[2]); + + continue; + } + + # paragraph + + if ($block['type'] === 'paragraph') + { + if (isset($block['interrupted'])) + { + $blocks []= $block; + + $block['text'] = $line; + + unset($block['interrupted']); + } + else + { + if ($this->breaks_enabled) + { + $block['text'] .= ' '; + } + + $block['text'] .= "\n".$line; + } + } + else + { + $blocks []= $block; + + $block = array( + 'type' => 'paragraph', + 'text' => $line, + ); + } + } + + $blocks []= $block; + + unset($blocks[0]); + + # $blocks » HTML + + $markup = ''; + + foreach ($blocks as $block) + { + switch ($block['type']) + { + case 'paragraph': + + $text = $this->parse_span_elements($block['text']); + + if ($context === 'li' and $markup === '') + { + if (isset($block['interrupted'])) + { + $markup .= "\n".'<p>'.$text.'</p>'."\n"; + } + else + { + $markup .= $text; + + if (isset($blocks[2])) + { + $markup .= "\n"; + } + } + } + else + { + $markup .= '<p>'.$text.'</p>'."\n"; + } + + break; + + case 'quote': + + $text = $this->parse_block_elements($block['lines']); + + $markup .= '<blockquote>'."\n".$text.'</blockquote>'."\n"; + + break; + + case 'code': + + $text = htmlspecialchars($block['text'], ENT_NOQUOTES, 'UTF-8'); + + $markup .= '<pre><code>'.$text.'</code></pre>'."\n"; + + break; + + case 'fenced': + + $text = htmlspecialchars($block['text'], ENT_NOQUOTES, 'UTF-8'); + + $markup .= '<pre><code'; + + if (isset($block['language'])) + { + $markup .= ' class="language-'.$block['language'].'"'; + } + + $markup .= '>'.$text.'</code></pre>'."\n"; + + break; + + case 'heading': + + $text = $this->parse_span_elements($block['text']); + + $markup .= '<h'.$block['level'].'>'.$text.'</h'.$block['level'].'>'."\n"; + + break; + + case 'rule': + + $markup .= '<hr />'."\n"; + + break; + + case 'li': + + if (isset($block['first'])) + { + $type = isset($block['ordered']) ? 'ol' : 'ul'; + + $markup .= '<'.$type.'>'."\n"; + } + + if (isset($block['interrupted']) and ! isset($block['last'])) + { + $block['lines'] []= ''; + } + + $text = $this->parse_block_elements($block['lines'], 'li'); + + $markup .= '<li>'.$text.'</li>'."\n"; + + if (isset($block['last'])) + { + $type = isset($block['ordered']) ? 'ol' : 'ul'; + + $markup .= '</'.$type.'>'."\n"; + } + + break; + + case 'markup': + + $markup .= $block['text']."\n"; + + break; + + default: + + $markup .= $block['text']."\n"; + } + } + + return $markup; + } + + private function parse_span_elements($text, $markers = array(" \n", '![', '&', '*', '<', '[', '\\', '_', '`', 'http', '~~')) + { + if (isset($text[1]) === false or $markers === array()) + { + return $text; + } + + # ~ + + $markup = ''; + + while ($markers) + { + $closest_marker = null; + $closest_marker_index = 0; + $closest_marker_position = null; + + foreach ($markers as $index => $marker) + { + $marker_position = strpos($text, $marker); + + if ($marker_position === false) + { + unset($markers[$index]); + + continue; + } + + if ($closest_marker === null or $marker_position < $closest_marker_position) + { + $closest_marker = $marker; + $closest_marker_index = $index; + $closest_marker_position = $marker_position; + } + } + + # ~ + + if ($closest_marker === null or isset($text[$closest_marker_position + 1]) === false) + { + $markup .= $text; + + break; + } + else + { + $markup .= substr($text, 0, $closest_marker_position); + } + + $text = substr($text, $closest_marker_position); + + # ~ + + unset($markers[$closest_marker_index]); + + # ~ + + switch ($closest_marker) + { + case " \n": + + $markup .= '<br />'."\n"; + + $offset = 3; + + break; + + case '![': + case '[': + + if (strpos($text, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $text, $matches)) + { + $element = array( + '!' => $text[0] === '!', + 'a' => $matches[1], + ); + + $offset = strlen($matches[0]); + + if ($element['!']) + { + $offset++; + } + + $remaining_text = substr($text, $offset); + + if ($remaining_text[0] === '(' and preg_match('/\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $remaining_text, $matches)) + { + $element['»'] = $matches[1]; + + if (isset($matches[2])) + { + $element['#'] = $matches[2]; + } + + $offset += strlen($matches[0]); + } + elseif ($this->reference_map) + { + $reference = $element['a']; + + if (preg_match('/^\s*\[(.*?)\]/', $remaining_text, $matches)) + { + $reference = $matches[1] ? $matches[1] : $element['a']; + + $offset += strlen($matches[0]); + } + + $reference = strtolower($reference); + + if (isset($this->reference_map[$reference])) + { + $element['»'] = $this->reference_map[$reference]['»']; + + if (isset($this->reference_map[$reference]['#'])) + { + $element['#'] = $this->reference_map[$reference]['#']; + } + } + else + { + unset($element); + } + } + else + { + unset($element); + } + } + + if (isset($element)) + { + $element['»'] = str_replace('&', '&', $element['»']); + $element['»'] = str_replace('<', '<', $element['»']); + + if ($element['!']) + { + $markup .= '<img alt="'.$element['a'].'" src="'.$element['»'].'"'; + + if (isset($element['#'])) + { + $markup .= ' title="'.$element['#'].'"'; + } + + $markup .= ' />'; + } + else + { + $element['a'] = $this->parse_span_elements($element['a'], $markers); + + $markup .= '<a href="'.$element['»'].'"'; + + if (isset($element['#'])) + { + $markup .= ' title="'.$element['#'].'"'; + } + + $markup .= '>'.$element['a'].'</a>'; + } + + unset($element); + } + else + { + $markup .= $closest_marker; + + $offset = $closest_marker === '![' ? 2 : 1; + } + + break; + + case '&': + + if (preg_match('/^&#?\w+;/', $text, $matches)) + { + $markup .= $matches[0]; + + $offset = strlen($matches[0]); + } + else + { + $markup .= '&'; + + $offset = 1; + } + + break; + + case '*': + case '_': + + if ($text[1] === $closest_marker and preg_match(self::$strong_regex[$closest_marker], $text, $matches)) + { + $markers[] = $closest_marker; + $matches[1] = $this->parse_span_elements($matches[1], $markers); + + $markup .= '<strong>'.$matches[1].'</strong>'; + } + elseif (preg_match(self::$em_regex[$closest_marker], $text, $matches)) + { + $markers[] = $closest_marker; + $matches[1] = $this->parse_span_elements($matches[1], $markers); + + $markup .= '<em>'.$matches[1].'</em>'; + } + + if (isset($matches) and $matches) + { + $offset = strlen($matches[0]); + } + else + { + $markup .= $closest_marker; + + $offset = 1; + } + + break; + + case '<': + + if (strpos($text, '>') !== false) + { + if ($text[1] === 'h' and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $text, $matches)) + { + $element_url = $matches[1]; + $element_url = str_replace('&', '&', $element_url); + $element_url = str_replace('<', '<', $element_url); + + $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>'; + + $offset = strlen($matches[0]); + } + elseif (strpos($text, '@') > 1 and preg_match('/<(\S+?@\S+?)>/', $text, $matches)) + { + $markup .= '<a href="mailto:'.$matches[1].'">'.$matches[1].'</a>'; + + $offset = strlen($matches[0]); + } + elseif (preg_match('/^<\/?\w.*?>/', $text, $matches)) + { + $markup .= $matches[0]; + + $offset = strlen($matches[0]); + } + else + { + $markup .= '<'; + + $offset = 1; + } + } + else + { + $markup .= '<'; + + $offset = 1; + } + + break; + + case '\\': + + if (in_array($text[1], self::$special_characters)) + { + $markup .= $text[1]; + + $offset = 2; + } + else + { + $markup .= '\\'; + + $offset = 1; + } + + break; + + case '`': + + if (preg_match('/^(`+)[ ]*(.+?)[ ]*(?<!`)\1(?!`)/', $text, $matches)) + { + $element_text = $matches[2]; + $element_text = htmlspecialchars($element_text, ENT_NOQUOTES, 'UTF-8'); + + $markup .= '<code>'.$element_text.'</code>'; + + $offset = strlen($matches[0]); + } + else + { + $markup .= '`'; + + $offset = 1; + } + + break; + + case 'http': + + if (preg_match('/^https?:[\/]{2}[^\s]+\b\/*/ui', $text, $matches)) + { + $element_url = $matches[0]; + $element_url = str_replace('&', '&', $element_url); + $element_url = str_replace('<', '<', $element_url); + + $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>'; + + $offset = strlen($matches[0]); + } + else + { + $markup .= 'http'; + + $offset = 4; + } + + break; + + case '~~': + + if (preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $text, $matches)) + { + $matches[1] = $this->parse_span_elements($matches[1], $markers); + + $markup .= '<del>'.$matches[1].'</del>'; + + $offset = strlen($matches[0]); + } + else + { + $markup .= '~~'; + + $offset = 2; + } + + break; + } + + if (isset($offset)) + { + $text = substr($text, $offset); + } + + $markers[$closest_marker_index] = $closest_marker; + } + + return $markup; + } + + # + # Fields + # + + private $reference_map = array(); + + # + # Read-only + + private static $strong_regex = array( + '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + private static $em_regex = array( + '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + private static $special_characters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', + ); + + private static $text_level_elements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'sub', 'code', 'strike', 'marquee', + 'q', 'rt', 'sup', 'font', 'strong', + 's', 'tt', 'var', 'mark', + 'u', 'xm', 'wbr', 'nobr', + 'ruby', + 'span', + 'time', + ); +} diff --git a/framework/3rdParty/readme.html b/framework/3rdParty/readme.html index 5889a943..8e57dd4e 100644 --- a/framework/3rdParty/readme.html +++ b/framework/3rdParty/readme.html @@ -137,11 +137,11 @@ projects. </tr>
<tr>
- <td><a href="Markdown">Markdown</a></td>
- <td><a href="http://www.michelf.com/projects/php-markdown/">PHP Markdown</a></td>
- <td><a href="Markdown/License.text">BSD</a></td>
+ <td><a href="Parsedown">Parsedown</a></td>
+ <td><a href="https://github.com/erusev/parsedown">Markdown Parser for PHP</a></td>
+ <td><a href="Parsedown/LICENSE.txt">MIT</a></td>
<td>System.Web.UI.WebControls.TMarkdown</td>
- <td>PHP5 class implementation of the PHP Markdown.</td>
+ <td>PHP implementation of Markdown, GitHub Flavored.</td>
</tr>
<tr>
diff --git a/framework/Web/UI/WebControls/TMarkdown.php b/framework/Web/UI/WebControls/TMarkdown.php index 75ca71af..75c8ec1b 100644 --- a/framework/Web/UI/WebControls/TMarkdown.php +++ b/framework/Web/UI/WebControls/TMarkdown.php @@ -13,7 +13,7 @@ * Using TTextHighlighter and MarkdownParser classes */ Prado::using('System.Web.UI.WebControls.TTextHighlighter'); -Prado::using('System.3rdParty.Markdown.MarkdownParser'); +Prado::using('System.3rdParty.Parsedown.Parsedown'); /** * TMarkdown class @@ -29,7 +29,7 @@ Prado::using('System.3rdParty.Markdown.MarkdownParser'); * To use TMarkdown, simply enclose the content to be rendered within * the body of TMarkdown in a template. * - * See http://www.pradosoft.com/demos/quickstart/?page=Markdown for + * See https://daringfireball.net/projects/markdown/basics for * details on the Markdown syntax usage. * * TMarkdown also performs syntax highlighting for code blocks whose language @@ -51,10 +51,9 @@ class TMarkdown extends TTextHighlighter */ public function processText($text) { - $renderer = new MarkdownParser; - $result = $renderer->parse($text); + $result = Parsedown::instance()->parse($text); return preg_replace_callback( - '/<pre><code>\[\s*(\w+)\s*\]\n+((.|\n)*?)\s*<\\/code><\\/pre>/im', + '/<pre><code class="language-(\w+)">((.|\n)*?)<\\/code><\\/pre>/im', array($this, 'highlightCode'), $result); } |