From ba81d0b3623e9593b8919c7df57f3eff006be294 Mon Sep 17 00:00:00 2001 From: Jamie Snape Date: Wed, 18 Jun 2014 13:23:19 -0400 Subject: [PATCH] Update Markdown parser to 1.2.8 --- library/Markdown/License.text | 4 +- .../Markdown/PHP Markdown Extra Readme.text | 185 ++++-- library/Markdown/markdown.php | 609 +++++++++++++++--- 3 files changed, 653 insertions(+), 145 deletions(-) diff --git a/library/Markdown/License.text b/library/Markdown/License.text index 4d6bf8b63..e0685e0fd 100644 --- a/library/Markdown/License.text +++ b/library/Markdown/License.text @@ -1,6 +1,6 @@ PHP Markdown & Extra -Copyright (c) 2004-2009 Michel Fortin - +Copyright (c) 2004-2013 Michel Fortin + All rights reserved. Based on Markdown diff --git a/library/Markdown/PHP Markdown Extra Readme.text b/library/Markdown/PHP Markdown Extra Readme.text index e69857692..bb0b0f129 100644 --- a/library/Markdown/PHP Markdown Extra Readme.text +++ b/library/Markdown/PHP Markdown Extra Readme.text @@ -1,10 +1,10 @@ PHP Markdown Extra ================== -Version 1.2.5 - Sun 8 Jan 2012 +Version 1.2.8 - 29 Nov 2013 -by Michel Fortin - +by Michel Fortin + based on Markdown by John Gruber @@ -25,9 +25,9 @@ tool, written in Perl, that converts the plain text markup to HTML. PHP Markdown is a port to PHP of the original Markdown program by John Gruber. -PHP Markdown can work as a plug-in for WordPress and bBlog, as a -modifier for the Smarty templating engine, or as a remplacement for -textile formatting in any software that support textile. +PHP Markdown can work as a plug-in for WordPress, as a modifier for +the Smarty templating engine, or as a replacement for Textile +formatting in any software that supports Textile. Full documentation of Markdown's syntax is available on John's Markdown page: @@ -38,6 +38,10 @@ Installation and Requirement PHP Markdown requires PHP version 4.0.5 or later. +Before PHP 5.3.7, pcre.backtrack_limit defaults to 100 000, which is too small +in many situations. You might need to set it to higher values. Later PHP +releases defaults to 1 000 000, which is usually fine. + ### WordPress ### @@ -45,7 +49,7 @@ PHP Markdown works with [WordPress][wp], version 1.2 or later. [wp]: http://wordpress.org/ -1. To use PHP Markdown with WordPress, place the "makrdown.php" file +1. To use PHP Markdown with WordPress, place the "markdown.php" file in the "plugins" folder. This folder is located inside "wp-content" at the root of your site: @@ -54,56 +58,39 @@ PHP Markdown works with [WordPress][wp], version 1.2 or later. 2. Activate the plugin with the administrative interface of WordPress. In the "Plugins" section you will now find Markdown. To activate the plugin, click on the "Activate" button on the - same line than Markdown. Your entries will now be formatted by + same line as Markdown. Your entries will now be formatted by PHP Markdown. 3. To post Markdown content, you'll first have to disable the - "visual" editor in the User section of WordPress. + "visual" editor in the User section of WordPress. You can configure PHP Markdown to not apply to the comments on your WordPress weblog. See the "Configuration" section below. It is not possible at this time to apply a different set of -filters to different entries. All your entries will be formated by +filters to different entries. All your entries will be formatted by PHP Markdown. This is a limitation of WordPress. If your old entries are written in HTML (as opposed to another formatting syntax, like Textile), they'll probably stay fine after installing Markdown. -### bBlog ### - -PHP Markdown also works with [bBlog][bb]. - - [bb]: http://www.bblog.com/ - -To use PHP Markdown with bBlog, rename "markdown.php" to -"modifier.markdown.php" and place the file in the "bBlog_plugins" -folder. This folder is located inside the "bblog" directory of -your site, like this: - - (site home)/bblog/bBlog_plugins/modifier.markdown.php - -Select "Markdown" as the "Entry Modifier" when you post a new -entry. This setting will only apply to the entry you are editing. - - ### Replacing Textile in TextPattern ### [TextPattern][tp] use [Textile][tx] to format your text. You can replace Textile by Markdown in TextPattern without having to change -any code by using the *Texitle Compatibility Mode*. This may work +any code by using the *Textile Compatibility Mode*. This may work with other software that expect Textile too. [tx]: http://www.textism.com/tools/textile/ [tp]: http://www.textpattern.com/ 1. Rename the "markdown.php" file to "classTextile.php". This will - make PHP Markdown behave as if it was the actual Textile parser. + make PHP Markdown behave as if it was the actual Textile parser. 2. Replace the "classTextile.php" file TextPattern installed in your - web directory. It can be found in the "lib" directory: + web directory. It can be found in the "lib" directory: - (site home)/textpattern/lib/ + (site home)/textpattern/lib/ Contrary to Textile, Markdown does not convert quotes to curly ones and does not convert multiple hyphens (`--` and `---`) into en- and @@ -113,7 +100,7 @@ can solve this problem by installing the "smartypants.php" file from Compatibility Mode function will use SmartyPants automatically without further modification. - [psp]: http://michelf.com/projects/php-smartypants/ + [psp]: http://michelf.ca/projects/php-smartypants/ ### In Your Own Programs ### @@ -195,15 +182,121 @@ Bugs ---- To file bug reports please send email to: - + Please include with your report: (1) the example input; (2) the output you expected; (3) the output PHP Markdown actually produced. +If you have a problem where Markdown gives you an empty result, first check +that the backtrack limit is not too low by running `php --info | grep pcre`. +See Installation and Requirement above for details. + Version History --------------- +Extra 1.2.8: + +* Added backtick fenced code blocks, originally from Github-flavored Markdown. + + +1.0.2 + +* Added support for the `tel:` URL scheme in automatic links. + + + + It gets converted to this (note the `tel:` prefix becomes invisible): + + +1-111-111-1111 + + +Extra 1.2.7 (11 Apr 2013): + +* Added optional class and id attributes to images and links using the same + syntax as for headers: + + [link](url){#id .class} + ![img](url){#id .class} + + It work too for reference-style links and images. In this case you need + to put those attributes at the reference definition: + + [link][linkref] or [linkref] + ![img][linkref] + + [linkref]: url "optional title" {#id .class} + +* Fixed a PHP notice message triggered when some table column separator + markers are missing on the separator line below column headers. + + +1.0.1q (11 Apr 2013): + +* Fixed a small mistake that could cause the parser to retain an invalid + state related to parsing links across multiple runs. This was never + observed (that I know of), but it's still worth fixing. + + +Extra 1.2.6 (13 Jan 2013): + +* Headers can now have a class attribute. You can add a class inside the + extra attribute block which can optionally be put after a header: + + ### Header ### {#id .class1 .class2} + + Spaces between components in the brace is optional. + +* Fenced code blocks can also have a class and an id attribute. If you only + need to apply a class (typically to indicate the language of a code + snippet), you can write it like this: + + ~~~ html + bold + ~~~ + + or like this: + + ~~~ .html + bold + ~~~ + + There is a new configuration option `MARKDOWN_CODE_CLASS_PREFIX` you can + use if you need to append a prefix to the class name. + + You might also opt to use an extra attribute block just like for headers: + + ~~~ {.html #id .codeclass} + bold + ~~~ + + Note that class names added this way are not affected by the + MARKDOWN_CODE_CLASS_PREFIX. + + A code block creates a `pre` HTML element containing a `code` element. + The `code` HTML element is the one that receives the attribute. If for + some reason you need attributes to be applied to the enclosing `pre` + element instead, you can set the MARKDOWN_CODE_ATTR_ON_PRE configuration + variable to true. + +* Fixed an issue were consecutive fenced code blocks containing HTML-like + code would confuse the parser. + +* Multiple references to the same footnote are now allowed. + +* Fixed an issue where no_markup mode was ineffective. + + +1.0.1p (13 Jan 2013): + +* Fixed an issue where some XML-style empty tags (such as `
`) were not + recognized correctly as such when inserted into Markdown-formatted text. + +* The following HTML 5 elements are treated as block elements when at the + root of an HTML block: `article`, `section`, `nav`, `aside`, `hgroup`, + `header`, `footer`, and `figure`. `svg` too. + + 1.0.1o (8 Jan 2012): * Silenced a new warning introduced around PHP 5.3 complaining about @@ -224,11 +317,11 @@ Extra 1.2.5 (8 Jan 2012): * Enabled reference-style shortcut links. Now you can write reference-style links with less brakets: - + This is [my website]. - + [my website]: http://example.com/ - + This was added in the 1.0.2 betas, but commented out in the 1.0.1 branch, waiting for the feature to be officialized. [But half of the other Markdown implementations are supporting this syntax][half], so it makes sense for @@ -238,7 +331,7 @@ Extra 1.2.5 (8 Jan 2012): * Now accepting many valid email addresses in autolinks that were previously rejected, such as: - + <"abc@def"@example.com> @@ -247,7 +340,7 @@ Extra 1.2.5 (8 Jan 2012): * Now accepting spaces in URLs for inline and reference-style links. Such URLs need to be surrounded by angle brakets. For instance: - + [link text]( "optional title") [link text][ref] @@ -354,27 +447,27 @@ Extra 1.2 (11 May 2008): * Fix for code blocks as first element of a list item. Previously, this didn't create any code block for item 2: - + * Item 1 (regular paragraph) - + * Item 2 (code block) * A code block starting on the second line of a document wasn't seen as a code block. This has been fixed. - + * Added programatically-settable parser properties `predef_urls` and `predef_titles` for predefined URLs and titles for reference-style links. To use this, your PHP code must call the parser this way: - + $parser = new Markdwon_Parser; $parser->predef_urls = array('linkref' => 'http://example.com'); $html = $parser->transform($text); - + You can then use the URL as a normal link reference: [my link][linkref] [my link][linkRef] - + Reference names in the parser properties *must* be lowercase. Reference names in the Markdown source may have any case. @@ -764,8 +857,8 @@ Copyright and License --------------------- PHP Markdown & Extra -Copyright (c) 2004-2009 Michel Fortin - +Copyright (c) 2004-2013 Michel Fortin + All rights reserved. Based on Markdown diff --git a/library/Markdown/markdown.php b/library/Markdown/markdown.php index f548fc26c..0edba0f34 100644 --- a/library/Markdown/markdown.php +++ b/library/Markdown/markdown.php @@ -2,18 +2,18 @@ # # Markdown Extra - A text-to-HTML conversion tool for web writers # -# PHP Markdown & Extra -# Copyright (c) 2004-2012 Michel Fortin -# +# PHP Markdown & Extra +# Copyright (c) 2004-2013 Michel Fortin +# # -# Original Markdown +# Original Markdown # Copyright (c) 2004-2006 John Gruber # # -define( 'MARKDOWN_VERSION', "1.0.1o" ); # Sun 8 Jan 2012 -define( 'MARKDOWNEXTRA_VERSION', "1.2.5" ); # Sun 8 Jan 2012 +define( 'MARKDOWN_VERSION', "1.0.2" ); # 29 Nov 2013 +define( 'MARKDOWNEXTRA_VERSION', "1.2.8" ); # 29 Nov 2013 # @@ -34,6 +34,13 @@ @define( 'MARKDOWN_FN_LINK_CLASS', "" ); @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); +# Optional class prefix for fenced code block. +@define( 'MARKDOWN_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. +@define( 'MARKDOWN_CODE_ATTR_ON_PRE', false ); + # # WordPress settings: @@ -69,16 +76,17 @@ function Markdown($text) { /* Plugin Name: Markdown Extra -Plugin URI: http://michelf.com/projects/php-markdown/ -Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More... -Version: 1.2.5 +Plugin Name: Markdown +Plugin URI: http://michelf.ca/projects/php-markdown/ +Description: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More... +Version: 1.2.8 Author: Michel Fortin -Author URI: http://michelf.com/ +Author URI: http://michelf.ca/ */ if (isset($wp_version)) { # More details about how it works here: - # + # # Post content and excerpts # - Remove WordPress paragraph generator. @@ -171,7 +179,7 @@ function identify_modifier_markdown() { 'authors' => 'Michel Fortin and John Gruber', 'licence' => 'GPL', 'version' => MARKDOWNEXTRA_VERSION, - 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...', + 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...', ); } @@ -214,17 +222,7 @@ function blockLite($text) { return $text; } class Markdown_Parser { - # Regex to match balanced [brackets]. - # Needed to insert a maximum bracked depth while converting to PHP. - var $nested_brackets_depth = 6; - var $nested_brackets_re; - - var $nested_url_parenthesis_depth = 4; - var $nested_url_parenthesis_re; - - # Table of hash values for escaped characters: - var $escape_chars = '\`*_{}[]()>#+-.!'; - var $escape_chars_re; + ### Configuration Variables ### # Change to ">" for HTML output. var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; @@ -239,6 +237,21 @@ class Markdown_Parser { var $predef_titles = array(); + ### Parser Implementation ### + + # Regex to match balanced [brackets]. + # Needed to insert a maximum bracked depth while converting to PHP. + var $nested_brackets_depth = 6; + var $nested_brackets_re; + + var $nested_url_parenthesis_depth = 4; + var $nested_url_parenthesis_re; + + # Table of hash values for escaped characters: + var $escape_chars = '\`*_{}[]()>#+-.!'; + var $escape_chars_re; + + function Markdown_Parser() { # # Constructor function. Initialize appropriate member variables. @@ -282,7 +295,7 @@ function setup() { $this->titles = $this->predef_titles; $this->html_hashes = array(); - $in_anchor = false; + $this->in_anchor = false; } function teardown() { @@ -406,7 +419,9 @@ function hashHTMLBlocks($text) { # $block_tags_a_re = 'ins|del'; $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. - 'script|noscript|form|fieldset|iframe|math'; + 'script|noscript|form|fieldset|iframe|math|svg|'. + 'article|section|nav|aside|hgroup|header|footer|'. + 'figure'; # Regular expression for the content of a block tag. $nested_tags_level = 4; @@ -1464,9 +1479,16 @@ function doAutoLinks($text) { > }xi', array(&$this, '_doAutoLinks_email_callback'), $text); + $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array(&$this, '_doAutoLinks_tel_callback'), $text); return $text; } + function _doAutoLinks_tel_callback($matches) { + $url = $this->encodeAttribute($matches[1]); + $tel = $this->encodeAttribute($matches[2]); + $link = "$tel"; + return $this->hashPart($link); + } function _doAutoLinks_url_callback($matches) { $url = $this->encodeAttribute($matches[1]); $link = "$url"; @@ -1539,12 +1561,16 @@ function parseSpan($str) { | <\?.*?\?> | <%.*?%> # processing instruction | - <[/!$]?[-a-zA-Z0-9:_]+ # regular tags + <[!$]?[-a-zA-Z0-9:_]+ # regular tags (?> \s (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* )? > + | + <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag + | + # closing tag ').' ) }xs'; @@ -1676,6 +1702,8 @@ function _unhash_callback($matches) { class MarkdownExtra_Parser extends Markdown_Parser { + ### Configuration Variables ### + # Prefix for footnote ids. var $fn_id_prefix = ""; @@ -1686,11 +1714,19 @@ class MarkdownExtra_Parser extends Markdown_Parser { # Optional class attribute for footnote links and backlinks. var $fn_link_class = MARKDOWN_FN_LINK_CLASS; var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; + + # Optional class prefix for fenced code block. + var $code_class_prefix = MARKDOWN_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. + var $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE; # Predefined abbreviations. var $predef_abbr = array(); + ### Parser Implementation ### + function MarkdownExtra_Parser() { # # Constructor function. Initialize the parser object. @@ -1724,6 +1760,8 @@ function MarkdownExtra_Parser() { # Extra variables used during extra transformations. var $footnotes = array(); var $footnotes_ordered = array(); + var $footnotes_ref_count = array(); + var $footnotes_numbers = array(); var $abbr_desciptions = array(); var $abbr_word_re = ''; @@ -1739,6 +1777,8 @@ function setup() { $this->footnotes = array(); $this->footnotes_ordered = array(); + $this->footnotes_ref_count = array(); + $this->footnotes_numbers = array(); $this->abbr_desciptions = array(); $this->abbr_word_re = ''; $this->footnote_counter = 1; @@ -1757,6 +1797,8 @@ function teardown() { # $this->footnotes = array(); $this->footnotes_ordered = array(); + $this->footnotes_ref_count = array(); + $this->footnotes_numbers = array(); $this->abbr_desciptions = array(); $this->abbr_word_re = ''; @@ -1764,23 +1806,111 @@ function teardown() { } + ### Extra Attribute Parser ### + + # Expression to use to catch attributes (includes the braces) + var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}'; + # Expression to use when parsing in a context when no capture is desired + var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}'; + + function doExtraAttributes($tag_name, $attr) { + # + # 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. + # + if (empty($attr)) return ""; + + # Split on components + preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches); + $elements = $matches[0]; + + # handle classes and ids (only first id taken into account) + $classes = array(); + $id = false; + foreach ($elements as $element) { + if ($element{0} == '.') { + $classes[] = substr($element, 1); + } else if ($element{0} == '#') { + if ($id === false) $id = substr($element, 1); + } + } + + # compose attributes as string + $attr_str = ""; + if (!empty($id)) { + $attr_str .= ' id="'.$id.'"'; + } + if (!empty($classes)) { + $attr_str .= ' class="'.implode(" ", $classes).'"'; + } + return $attr_str; + } + + + 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" + $text = preg_replace_callback('{ + ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + (?: + <(.+?)> # url = $2 + | + (\S+?) # url = $3 + ) + [ ]* + \n? # maybe one newline + [ ]* + (?: + (?<=\s) # lookbehind for whitespace + ["(] + (.*?) # title = $4 + [")] + [ ]* + )? # title is optional + (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr + (?:\n+|\Z) + }xm', + array(&$this, '_stripLinkDefinitions_callback'), + $text); + return $text; + } + 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 + } + + ### HTML Block Parser ### # Tags that are always treated as block tags: - var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend'; - - # Tags treated as block tags only if the opening tag is alone on it's line: - var $context_block_tags_re = 'script|noscript|math|ins|del'; + var $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'; + + # Tags treated as block tags only if the opening tag is alone on its line: + var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video'; # Tags where markdown="1" default to span mode: var $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: - var $clean_tags_re = 'script|math'; + var $clean_tags_re = 'script|math|svg'; # Tags that do not need to be closed. - var $auto_close_tags_re = 'hr|img'; + var $auto_close_tags_re = 'hr|img|param|source|track'; function hashHTMLBlocks($text) { @@ -1795,10 +1925,12 @@ function hashHTMLBlocks($text) { # # This works by calling _HashHTMLBlocks_InMarkdown, which then calls # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back + # 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; + # # Call the HTML-in-Markdown hasher. # @@ -1848,7 +1980,7 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, # Regex to match any tag. $block_tag_re = '{ - ( # $2: Capture hole tag. + ( # $2: Capture whole tag. # Tag name. '.$this->block_tags_re.' | @@ -1871,9 +2003,6 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, <\?.*?\?> | <%.*?%> # Processing instruction | # CData Block - | - # Code span marker - `+ '. ( !$span ? ' # If not in span. | # Indented code block @@ -1884,9 +2013,23 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, )* | # Fenced code block marker - (?> ^ | \n ) - [ ]{0,'.($indent).'}~~~+[ ]*\n + (?<= ^ | \n ) + [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,}) + [ ]* + (?: + \.?[-_:a-zA-Z0-9]+ # standalone class name + | + '.$this->id_class_attr_nocatch_re.' # extra attributes + )? + [ ]* + (?= \n ) ' : '' ). ' # End (if not is span). + | + # Code span marker + # Note, this regex needs to go after backtick fenced + # code blocks but it should also be kept outside of the + # "if not in span" condition adding backticks to the parser + `+ ) }xs'; @@ -1928,31 +2071,16 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, $text = $parts[2]; # Remaining text after current tag. $tag_re = preg_quote($tag); # For use in a regular expression. - # - # Check for: Code span marker - # - if ($tag{0} == "`") { - # Find corresponding end marker. - $tag_re = preg_quote($tag); - if (preg_match('{^(?>.+?|\n(?!\n))*?(?id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) { # Fenced code block marker: find matching end marker. - $tag_re = preg_quote(trim($tag)); - if (preg_match('{^(?>.*\n)+?[ ]{0,'.($indent).'}'.$tag_re.'[ ]*\n}', $text, + $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. @@ -1973,6 +2101,25 @@ function _hashHTMLBlocks_inMarkdown($text, $indent = 0, $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. + $tag_re = preg_quote($tag); + if (preg_match('{^(?>.+?|\n(?!\n))*?(?hashPart($text, 'C'); } + function doAnchors($text) { + # + # Turn Markdown link shortcuts into XHTML tags. + # + if ($this->in_anchor) return $text; + $this->in_anchor = true; + + # + # First, handle reference-style links: [link text] [id] + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + \[ + ('.$this->nested_brackets_re.') # 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 + \[ + ('.$this->nested_brackets_re.') # link text = $2 + \] + \( # literal paren + [ \n]* + (?: + <(.+?)> # href = $3 + | + ('.$this->nested_url_parenthesis_re.') # href = $4 + ) + [ \n]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # Title = $7 + \6 # matching quote + [ \n]* # ignore any spaces/tabs between closing quote and ) + )? # title is optional + \) + (?:[ ]? '.$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) + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + \[ + ([^\[\]]+) # link text = $2; can\'t contain [ or ] + \] + ) + }xs', + array(&$this, '_doAnchors_reference_callback'), $text); + + $this->in_anchor = false; + return $text; + } + 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]. + $link_id = $link_text; + } + + # 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->encodeAttribute($url); + + $result = "titles[$link_id] ) ) { + $title = $this->titles[$link_id]; + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + 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); + } + else { + $result = $whole_match; + } + return $result; + } + function _doAnchors_inline_callback($matches) { + $whole_match = $matches[1]; + $link_text = $this->runSpanGamut($matches[2]); + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; + $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]); + + + $url = $this->encodeAttribute($url); + + $result = "encodeAttribute($title); + $result .= " title=\"$title\""; + } + $result .= $attr; + + $link_text = $this->runSpanGamut($link_text); + $result .= ">$link_text"; + + return $this->hashPart($result); + } + + + function doImages($text) { + # + # Turn Markdown image shortcuts into tags. + # + # + # 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 + \] + + [ ]? # 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 + !\[ + ('.$this->nested_brackets_re.') # alt text = $2 + \] + \s? # One optional whitespace character + \( # literal paren + [ \n]* + (?: + <(\S*)> # src url = $3 + | + ('.$this->nested_url_parenthesis_re.') # src url = $4 + ) + [ \n]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # title = $7 + \6 # matching quote + [ \n]* + )? # title is optional + \) + (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes + ) + }xs', + array(&$this, '_doImages_inline_callback'), $text); + + return $text; + } + 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 = $this->encodeAttribute($alt_text); + if (isset($this->urls[$link_id])) { + $url = $this->encodeAttribute($this->urls[$link_id]); + $result = "\"$alt_text\"";titles[$link_id])) { + $title = $this->titles[$link_id]; + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + 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: + $result = $whole_match; + } + + return $result; + } + function _doImages_inline_callback($matches) { + $whole_match = $matches[1]; + $alt_text = $matches[2]; + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; + $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]); + + $alt_text = $this->encodeAttribute($alt_text); + $url = $this->encodeAttribute($url); + $result = "\"$alt_text\"";encodeAttribute($title); + $result .= " title=\"$title\""; # $title already quoted + } + $result .= $attr; + $result .= $this->empty_element_suffix; + + return $this->hashPart($result); + } + + function doHeaders($text) { # - # Redefined to add id attribute support. + # Redefined to add id and class attribute support. # # Setext-style headers: # Header 1 {#header1} # ======== # - # Header 2 {#header2} + # Header 2 {#header2 .class1 .class2} # -------- # $text = preg_replace_callback( '{ (^.+?) # $1: Header text - (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute + (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer }mx', array(&$this, '_doHeaders_callback_setext'), $text); @@ -2242,9 +2627,9 @@ function doHeaders($text) { # atx-style headers: # # Header 1 {#header1} # ## Header 2 {#header2} - # ## Header 2 with closing hashes ## {#header3} + # ## Header 2 with closing hashes ## {#header3.class1.class2} # ... - # ###### Header 6 {#header2} + # ###### Header 6 {.class2} # $text = preg_replace_callback('{ ^(\#{1,6}) # $1 = string of #\'s @@ -2252,7 +2637,7 @@ function doHeaders($text) { (.+?) # $2 = Header text [ ]* \#* # optional closing #\'s (not counted) - (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute + (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes [ ]* \n+ }xm', @@ -2260,21 +2645,17 @@ function doHeaders($text) { return $text; } - function _doHeaders_attr($attr) { - if (empty($attr)) return ""; - return " id=\"$attr\""; - } function _doHeaders_callback_setext($matches) { if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) return $matches[0]; $level = $matches[3]{0} == '=' ? 1 : 2; - $attr = $this->_doHeaders_attr($id =& $matches[2]); + $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]); $block = "".$this->runSpanGamut($matches[1]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } function _doHeaders_callback_atx($matches) { $level = strlen($matches[1]); - $attr = $this->_doHeaders_attr($id =& $matches[3]); + $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]); $block = "".$this->runSpanGamut($matches[2]).""; return "\n" . $this->hashBlock($block) . "\n\n"; } @@ -2375,6 +2756,7 @@ function _doTable_callback($matches) { $head = $this->parseSpan($head); $headers = preg_split('/ *[|] */', $head); $col_count = count($headers); + $attr = array_pad($attr, $col_count, ''); # Write column headers. $text = "\n"; @@ -2479,7 +2861,7 @@ function processDefListItems($list_str) { (?>\A\n?|\n\n+) # leading line ( # definition terms = $1 [ ]{0,'.$less_than_tab.'} # leading whitespace - (?![:][ ]|[ ]) # negative lookahead for a definition + (?!\:[ ]|[ ]) # negative lookahead for a definition # mark (colon) or more whitespace. (?> \S.* \n)+? # actual term (not whitespace). ) @@ -2493,12 +2875,12 @@ function processDefListItems($list_str) { \n(\n+)? # leading line = $1 ( # marker space = $2 [ ]{0,'.$less_than_tab.'} # whitespace before colon - [:][ ]+ # definition mark (colon) + \:[ ]+ # definition mark (colon) ) ((?s:.+?)) # definition text = $3 (?= \n+ # stop at next definition mark, (?: # next term or end of text - [ ]{0,'.$less_than_tab.'} [:][ ] | + [ ]{0,'.$less_than_tab.'} \:[ ] |
| \z ) ) @@ -2550,11 +2932,17 @@ function doFencedCodeBlocks($text) { (?:\n|\A) # 1: Opening marker ( - ~{3,} # Marker: three tilde or more. + (?:~{3,}|`{3,}) # 3 or more tildes/backticks. ) + [ ]* + (?: + \.?([-_:a-zA-Z0-9]+) # 2: standalone class name + | + '.$this->id_class_attr_catch_re.' # 3: Extra attributes + )? [ ]* \n # Whitespace and newline following marker. - # 2: Content + # 4: Content ( (?> (?!\1 [ ]* \n) # Not a closing marker. @@ -2563,18 +2951,31 @@ function doFencedCodeBlocks($text) { ) # Closing marker. - \1 [ ]* \n + \1 [ ]* (?= \n ) }xm', array(&$this, '_doFencedCodeBlocks_callback'), $text); return $text; } function _doFencedCodeBlocks_callback($matches) { - $codeblock = $matches[2]; + $classname =& $matches[2]; + $attrs =& $matches[3]; + $codeblock = $matches[4]; $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES); $codeblock = preg_replace_callback('/^\n+/', array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock); - $codeblock = "
$codeblock
"; + + if ($classname != "") { + if ($classname{0} == '.') + $classname = substr($classname, 1); + $attr_str = ' class="'.$this->code_class_prefix.$classname.'"'; + } else { + $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs); + } + $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"; } function _doFencedCodeBlocks_newlines($matches) { @@ -2718,6 +3119,9 @@ function appendFootnotes($text) { $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"); @@ -2726,9 +3130,13 @@ function appendFootnotes($text) { $attr = str_replace("%%", ++$num, $attr); $note_id = $this->encodeAttribute($note_id); - - # Add backlink to last paragraph; create new paragraph if needed. + + # Prepare backlink, multiple backlinks if multiple references $backlink = ""; + for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) { + $backlink .= " "; + } + # Add backlink to last paragraph; create new paragraph if needed. if (preg_match('{

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

"; } else { @@ -2751,11 +3159,18 @@ function _appendFootnotes_callback($matches) { # 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])) { - # Transfert footnote content to the ordered list. - $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; - unset($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 + $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id]; + $this->footnotes_ref_count[$node_id] = 1; + $num = $this->footnote_counter++; + $ref_count_mark = ''; + } else { + $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1; + } - $num = $this->footnote_counter++; $attr = " rel=\"footnote\""; if ($this->fn_link_class != "") { $class = $this->fn_link_class; @@ -2772,7 +3187,7 @@ function _appendFootnotes_callback($matches) { $node_id = $this->encodeAttribute($node_id); return - "". + "". "$num". ""; } @@ -2858,7 +3273,7 @@ function _doAbbreviations_callback($matches) { 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 +is mostly 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 @@ -2876,7 +3291,7 @@ function _doAbbreviations_callback($matches) { To file bug reports please send email to: - + Please include with your report: (1) the example input; (2) the output you expected; (3) the output Markdown actually produced. @@ -2891,9 +3306,9 @@ function _doAbbreviations_callback($matches) { Copyright and License --------------------- -PHP Markdown & Extra -Copyright (c) 2004-2009 Michel Fortin - +PHP Markdown & Extra +Copyright (c) 2004-2013 Michel Fortin + All rights reserved. Based on Markdown @@ -2929,4 +3344,4 @@ function _doAbbreviations_callback($matches) { software, even if advised of the possibility of such damage. */ -?> \ No newline at end of file +?>