Class declared in MODPATH/userguide/vendor/markdown/markdown.php on line 1669.
$abbr_desciptionslink to thisarray(0)
$abbr_word_relink to thisstring(0) ""
$auto_close_tags_relink to thisstring(6) "hr|img"
$block_gamutlink to thisarray(5) ( "doHeaders" => integer 10 "doHorizontalRules" => integer 20 "doLists" => integer 40 "doCodeBlocks" => integer 50 "doBlockQuotes" => integer 60 )
$block_tags_relink to thisstring(81) "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend"
$clean_tags_relink to thisstring(11) "script|math"
$contain_span_tags_relink to thisstring(38) "p|h[1-6]|li|dd|dt|td|th|legend|address"
$context_block_tags_relink to thisstring(28) "script|noscript|math|ins|del"
$document_gamutlink to thisarray(2) ( "stripLinkDefinitions" => integer 20 "runBasicBlockGamut" => integer 30 )
$em_relistlink to thisarray(3) ( "" => string(61) "(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S)(?![.,:;]\s)" "*" => string(22) "(?<=\S)(?<!\*)\*(?!\*)" "_" => string(30) "(?<=\S)(?<!_)_(?![a-zA-Z0-9_])" )
$em_strong_prepared_relistlink to thisNULL
$em_strong_relistlink to thisarray(3) ( "" => string(67) "(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S)(?![.,:;]\s)" "***" => string(26) "(?<=\S)(?<!\*)\*\*\*(?!\*)" "___" => string(32) "(?<=\S)(?<!_)___(?![a-zA-Z0-9_])" )
$empty_element_suffixlink to thisstring(3) " />"
$escape_charslink to thisstring(16) "\`*_{}[]()>#+-.!"
$escape_chars_relink to thisNULL
$fn_backlink_classlink to thisstring(0) ""
$fn_backlink_titlelink to thisstring(0) ""
$fn_id_prefixlink to thisstring(0) ""
$fn_link_classlink to thisstring(0) ""
$fn_link_titlelink to thisstring(0) ""
$footnote_counterlink to thisinteger 1
$footnoteslink to thisarray(0)
$footnotes_orderedlink to thisarray(0)
$html_hasheslink to thisarray(0)
$in_anchorlink to thisbool FALSE
$list_levellink to thisinteger 0
$nested_brackets_depthlink to thisinteger 6
$nested_brackets_relink to thisNULL
$nested_url_parenthesis_depthlink to thisinteger 4
$nested_url_parenthesis_relink to thisNULL
$no_entitieslink to thisbool FALSE
$no_markuplink to thisbool FALSE
$predef_abbrlink to thisarray(0)
$predef_titleslink to thisarray(0)
$predef_urlslink to thisarray(0)
$span_gamutlink to thisarray(7) ( "parseSpan" => integer -30 "doImages" => integer 10 "doAnchors" => integer 20 "doAutoLinks" => integer 30 "encodeAmpsAndAngles" => integer 40 "doItalicsAndBold" => integer 50 "doHardBreaks" => integer 60 )
$strong_relistlink to thisarray(3) ( "" => string(64) "(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S)(?![.,:;]\s)" "**" => string(24) "(?<=\S)(?<!\*)\*\*(?!\*)" "__" => string(31) "(?<=\S)(?<!_)__(?![a-zA-Z0-9_])" )
$tab_widthlink to thisinteger 4
$titleslink to thisarray(0)
$urlslink to thisarray(0)
$utf8_strlenlink to thisstring(9) "mb_strlen"
function
__construct()
{
#
# Constructor
function
. Initialize the parser object.
#
Add extra escapable characters before parent constructor
# initialize the table.
$this
->escape_chars .=
':|'
;
# Insert extra document, block,
and
span transformations.
# Parent constructor will
do
the sorting.
$this
->document_gamut +=
array
(
"doFencedCodeBlocks"
=> 5,
"stripFootnotes"
=> 15,
"stripAbbreviations"
=> 25,
"appendFootnotes"
=> 50,
);
$this
->block_gamut +=
array
(
"doFencedCodeBlocks"
=> 5,
"doTables"
=> 15,
"doDefLists"
=> 45,
);
$this
->span_gamut +=
array
(
"doFootnotes"
=> 5,
"doAbbreviations"
=> 70,
);
parent::__construct();
}
function
_appendFootnotes_callback(
$matches
)
{
$node_id
=
$this
->fn_id_prefix .
$matches
[1];
# Create footnote marker only
if
it has a corresponding footnote *
and
*
# the footnote hasn't been used by another marker.
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
->footnote_counter++;
$attr
=
" rel=\"footnote\""
;
if
(
$this
->fn_link_class !=
""
) {
$class
=
$this
->fn_link_class;
$class
=
$this
->encodeAttribute(
$class
);
$attr
.=
" class=\"$class\""
;
}
if
(
$this
->fn_link_title !=
""
) {
$title
=
$this
->fn_link_title;
$title
=
$this
->encodeAttribute(
$title
);
$attr
.=
" title=\"$title\""
;
}
$attr
=
str_replace
(
"%%"
,
$num
,
$attr
);
$node_id
=
$this
->encodeAttribute(
$node_id
);
return
"<sup id=\"fnref:$node_id\">"
.
"<a href=\"#fn:$node_id\"$attr>$num</a>"
.
"</sup>"
;
}
return
"[^"
.
$matches
[1] .
"]"
;
}
function
_doAbbreviations_callback(
$matches
)
{
$abbr
=
$matches
[0];
if
(isset(
$this
->abbr_desciptions[
$abbr
])) {
$desc
=
$this
->abbr_desciptions[
$abbr
];
if
(
empty
(
$desc
)) {
return
$this
->hashPart(
"<abbr>$abbr</abbr>"
);
}
else
{
$desc
=
$this
->encodeAttribute(
$desc
);
return
$this
->hashPart(
"<abbr title=\"$desc\">$abbr</abbr>"
);
}
}
else
{
return
$matches
[0];
}
}
function
_doDefLists_callback(
$matches
)
{
# Re-usable patterns to match list item bullets
and
number markers:
$list
=
$matches
[1];
# Turn double returns into triple returns, so that we can make a
# paragraph
for
the last item in a list,
if
necessary:
$result
= trim(
$this
->processDefListItems(
$list
));
$result
=
"<dl>\n"
.
$result
.
"\n</dl>"
;
return
$this
->hashBlock(
$result
) .
"\n\n"
;
}
function
_doFencedCodeBlocks_callback(
$matches
)
{
$codeblock
=
$matches
[2];
$codeblock
= htmlspecialchars(
$codeblock
, ENT_NOQUOTES);
$codeblock
= preg_replace_callback(
'/^\n+/'
,
array
(&
$this
,
'_doFencedCodeBlocks_newlines'
),
$codeblock
);
$codeblock
=
"<pre><code>$codeblock</code></pre>"
;
return
"\n\n"
.
$this
->hashBlock(
$codeblock
) .
"\n\n"
;
}
function
_doFencedCodeBlocks_newlines(
$matches
)
{
return
str_repeat
(
"<br$this->empty_element_suffix"
,
strlen
(
$matches
[0]));
}
function
_doHeaders_attr(
$attr
)
{
if
(
empty
(
$attr
))
return
""
;
return
" id=\"$attr\""
;
}
function
_doHeaders_callback_atx(
$matches
)
{
$level
=
strlen
(
$matches
[1]);
$attr
=
$this
->_doHeaders_attr(
$id
= &
$matches
[3]);
$block
=
"<h$level$attr>"
.
$this
->runSpanGamut(
$matches
[2]) .
"</h$level>"
;
return
"\n"
.
$this
->hashBlock(
$block
) .
"\n\n"
;
}
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]);
$block
=
"<h$level$attr>"
.
$this
->runSpanGamut(
$matches
[1]) .
"</h$level>"
;
return
"\n"
.
$this
->hashBlock(
$block
) .
"\n\n"
;
}
function
_doTable_callback(
$matches
)
{
$head
=
$matches
[1];
$underline
=
$matches
[2];
$content
=
$matches
[3];
# Remove any tailing pipes
for
each line.
$head
= preg_replace(
'/[|] *$/m'
,
''
,
$head
);
$underline
= preg_replace(
'/[|] *$/m'
,
''
,
$underline
);
$content
= preg_replace(
'/[|] *$/m'
,
''
,
$content
);
# Reading alignement from header underline.
$separators
= preg_split(
'/ *[|] */'
,
$underline
);
foreach
(
$separators
as
$n
=>
$s
) {
if
(preg_match(
'/^ *-+: *$/'
,
$s
))
$attr
[
$n
] =
' align="right"'
;
else
if
(preg_match(
'/^ *:-+: *$/'
,
$s
))
$attr
[
$n
] =
' align="center"'
;
else
if
(preg_match(
'/^ *:-+ *$/'
,
$s
))
$attr
[
$n
] =
' align="left"'
;
else
$attr
[
$n
] =
''
;
}
# Parsing span elements, including code spans, character escapes,
#
and
inline HTML tags, so that pipes inside those gets ignored.
$head
=
$this
->parseSpan(
$head
);
$headers
= preg_split(
'/ *[|] */'
,
$head
);
$col_count
=
count
(
$headers
);
# Write column headers.
$text
=
"<table>\n"
;
$text
.=
"<thead>\n"
;
$text
.=
"<tr>\n"
;
foreach
(
$headers
as
$n
=>
$header
)
$text
.=
" <th$attr[$n]>"
.
$this
->runSpanGamut(trim(
$header
)) .
"</th>\n"
;
$text
.=
"</tr>\n"
;
$text
.=
"</thead>\n"
;
# Split content by row.
$rows
=
explode
(
"\n"
, trim(
$content
,
"\n"
));
$text
.=
"<tbody>\n"
;
foreach
(
$rows
as
$row
) {
# Parsing span elements, including code spans, character escapes,
#
and
inline HTML tags, so that pipes inside those gets ignored.
$row
=
$this
->parseSpan(
$row
);
# Split row by cell.
$row_cells
= preg_split(
'/ *[|] */'
,
$row
,
$col_count
);
$row_cells
=
array_pad
(
$row_cells
,
$col_count
,
''
);
$text
.=
"<tr>\n"
;
foreach
(
$row_cells
as
$n
=>
$cell
)
$text
.=
" <td$attr[$n]>"
.
$this
->runSpanGamut(trim(
$cell
)) .
"</td>\n"
;
$text
.=
"</tr>\n"
;
}
$text
.=
"</tbody>\n"
;
$text
.=
"</table>"
;
return
$this
->hashBlock(
$text
) .
"\n"
;
}
function
_doTable_leadingPipe_callback(
$matches
)
{
$head
=
$matches
[1];
$underline
=
$matches
[2];
$content
=
$matches
[3];
# Remove leading pipe
for
each row.
$content
= preg_replace(
'/^ *[|]/m'
,
''
,
$content
);
return
$this
->_doTable_callback(
array
(
$matches
[0],
$head
,
$underline
,
$content
));
}
function
_hashHTMLBlocks_inHTML(
$text
,
$hash_method
,
$md_attr
)
{
#
# Parse HTML, calling _HashHTMLBlocks_InMarkdown
for
block tags.
#
Calls
$hash_method
to convert any blocks.
# * Stops when the first opening tag closes.
# *
$md_attr
indicate
if
the
use
of the `markdown=
"1"
` attribute is allowed.
# (it is not inside clean tags)
#
eturns an
array
of that form: ( processed text , remaining text )
#
(
$text
===
''
)
return
array
(
''
,
''
);
# Regex to match `markdown` attribute inside of a tag.
$markdown_attr_re
= '
\s* # Eat whitespace before the `markdown` attribute
markdown
\s*=\s*
(?>
(["\']) #
$1
: quote delimiter
(.*?) #
$2
: attribute value
\1 # matching delimiter
|
([^\s>]*) #
$3
: unquoted attribute value
)
() #
$4
: make
$3
always defined (avoid warnings)
xs';
# Regex to match any tag.
$tag_re
= '{
( #
$2
: Capture hole tag.
</? # Any opening
or
closing tag.
[\w:$]+ # Tag name.
(?:
(?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
(?>
".*?"
| # Double quotes (can contain `>`)
\'.*?\' | # Single quotes (can contain `>`)
.+? # Anything but quotes
and
`>`.
)*?
)?
> #
End
of tag.
|
<!-- .*? --> # HTML Comment
|
<\?.*?\?> | <%.*?%> # Processing instruction
|
<!\[CDATA\[.*?\]\]> # CData Block
)
xs';
$original_text
=
$text
; # Save original text in
case
of faliure.
$depth
= 0; # Current depth inside the tag tree.
$block_text
=
""
; # Temporary text holder
for
current text.
$parsed
=
""
; # Parsed text that will be returned.
#
Get the name of the starting tag.
# (This pattern makes
$base_tag_name_re
safe without quoting.)
#
(preg_match(
'/^<([\w:$]*)\b/'
,
$text
,
$matches
))
$base_tag_name_re
=
$matches
[1];
#
# Loop through every tag until we find the corresponding closing tag.
#
{
#
# Split the text using the first
$tag_match
pattern found.
# Text before pattern will be first in the
array
, text after
# pattern will be at the
end
,
and
between will be any catches made
# by the pattern.
#
parts = preg_split(
$tag_re
,
$text
, 2, PREG_SPLIT_DELIM_CAPTURE);
if
(
count
(
$parts
) < 3) {
#
#
End
of
$text
reached with unbalenced tag(s).
# In that
case
, we
return
original text unchanged
and
pass the
# first character
as
filtered to prevent an infinite loop in the
# parent
function
.
#
return
array
(
$original_text
{0},
substr
(
$original_text
, 1));
}
$block_text
.=
$parts
[0]; # Text before current tag.
$tag
=
$parts
[1]; # Tag to handle.
$text
=
$parts
[2]; # Remaining text after current tag.
#
Check
for
: Auto-close tag (like <hr/>)
# Comments
and
Processing Instructions.
#
f (preg_match(
'{^</?(?:'
.
$this
->auto_close_tags_re .
')\b}'
,
$tag
) ||
$tag
{1} ==
'!'
||
$tag
{1} ==
'?'
) {
# Just add the tag to the block
as
if
it was text.
$block_text
.=
$tag
;
}
else
{
#
# Increase/decrease nested tag
count
. Only
do
so
if
# the tag
's name match base tag'
s.
#
if
(preg_match(
'{^</?'
.
$base_tag_name_re
.
'\b}'
,
$tag
)) {
if
(
$tag
{1} ==
'/'
)
$depth
--;
else
if
(
$tag
{
strlen
(
$tag
) - 2} !=
'/'
)
$depth
++;
}
#
# Check
for
`markdown=
"1"
` attribute
and
handle it.
#
if
(
$md_attr
&&
preg_match(
$markdown_attr_re
,
$tag
,
$attr_m
) &&
preg_match(
'/^1|block|span$/'
,
$attr_m
[2] .
$attr_m
[3])) {
# Remove `markdown` attribute from opening tag.
$tag
= preg_replace(
$markdown_attr_re
,
''
,
$tag
);
# Check
if
text inside this tag must be parsed in span mode.
$this
->mode =
$attr_m
[2] .
$attr_m
[3];
$span_mode
=
$this
->mode ==
'span'
||
$this
->mode !=
'block'
&&
preg_match(
'{^<(?:'
.
$this
->contain_span_tags_re .
')\b}'
,
$tag
);
# Calculate indent before tag.
if
(preg_match(
'/(?:^|\n)( *?)(?! ).*?$/'
,
$block_text
,
$matches
)) {
$strlen
=
$this
->utf8_strlen;
$indent
=
$strlen
(
$matches
[1],
'UTF-8'
);
}
else
{
$indent
= 0;
}
#
End
preceding block with this tag.
$block_text
.=
$tag
;
$parsed
.=
$this
->
$hash_method
(
$block_text
);
# Get enclosing tag name
for
the ParseMarkdown
function
.
# (This pattern makes
$tag_name_re
safe without quoting.)
preg_match(
'/^<([\w:$]*)\b/'
,
$tag
,
$matches
);
$tag_name_re
=
$matches
[1];
# Parse the content using the HTML-in-Markdown parser.
list (
$block_text
,
$text
) =
$this
->_hashHTMLBlocks_inMarkdown(
$text
,
$indent
,
$tag_name_re
,
$span_mode
);
# Outdent markdown text.
if
(
$indent
> 0) {
$block_text
= preg_replace(
"/^[ ]{1,$indent}/m"
,
""
,
$block_text
);
}
# Append tag content to parsed text.
if
(!
$span_mode
)
$parsed
.=
"\n\n$block_text\n\n"
;
else
$parsed
.=
"$block_text"
;
# Start over a
new
block.
$block_text
=
""
;
}
else
$block_text
.=
$tag
;
}
}
while
(
$depth
> 0);
#
# Hash last block text that wasn't processed inside the loop.
#
arsed .=
$this
->
$hash_method
(
$block_text
);
return
array
(
$parsed
,
$text
);
}
function
_hashHTMLBlocks_inMarkdown(
$text
,
$indent
= 0,
$enclosing_tag_re
=
''
,
$span
= false)
{
#
# Parse markdown text, calling _HashHTMLBlocks_InHTML
for
block tags.
#
$indent
is the number of space to be ignored when checking
for
code
# blocks. This is important because
if
we don't take the indent into
# account, something like this (which looks right) won't work
as
expected:
#
<div>
# <div markdown=
"1"
>
# Hello World. <-- Is this a Markdown code block
or
text?
# </div> <-- Is this a Markdown code block
or
a real tag?
# <div>
#
If you don
't like this, just don'
t indent the tag on which
# you apply the markdown=
"1"
attribute.
#
If
$enclosing_tag_re
is not
empty
, stops at the first unmatched closing
# tag with that name. Nested tags supported.
#
If
$span
is true, text inside must treated
as
span. So any double
# newline will be replaced by a single newline so that it does not create
# paragraphs.
#
eturns an
array
of that form: ( processed text , remaining text )
#
(
$text
===
''
)
return
array
(
''
,
''
);
# Regex to check
for
the presense of newlines around a block tag.
$newline_before_re
=
'/(?:^\n?|\n\n)*$/'
;
$newline_after_re
= '{
^ # Start of text following the tag.
(?>[ ]*<!--.*?-->)? # Optional comment.
[ ]*\n # Must be followed by newline.
xs';
# Regex to match any tag.
$block_tag_re
= '{
( #
$2
: Capture hole tag.
</? # Any opening
or
closing tag.
(?> # Tag name.
' . $this->block_tags_re . '
|
' . $this->context_block_tags_re . '
|
' . $this->clean_tags_re . '
|
(?!\s)
' . $enclosing_tag_re . '
)
(?:
(?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
(?>
".*?"
| # Double quotes (can contain `>`)
\'.*?\' | # Single quotes (can contain `>`)
.+? # Anything but quotes
and
`>`.
)*?
)?
> #
End
of tag.
|
<!-- .*? --> # HTML Comment
|
<\?.*?\?> | <%.*?%> # Processing instruction
|
<!\[CDATA\[.*?\]\]> # CData Block
|
# Code span marker
`+
' . (!$span ? '
# If not in span.
|
# Indented code block
(?> ^[ ]*\n? | \n[ ]*\n )
[ ]{
' . ($indent + 4) . '
}[^\n]* \n
(?>
(?: [ ]{
' . ($indent + 4) . '
}[^\n]* | [ ]* ) \n
)*
|
# Fenced code block marker
(?> ^ | \n )
[ ]{
' . ($indent) . '
}~~~+[ ]*\n
' : '
' ) . '
#
End
(
if
not is span).
)
xs';
$depth
= 0; # Current depth inside the tag tree.
$parsed
=
""
; # Parsed text that will be returned.
#
Loop through every tag until we find the closing tag of the parent
#
or
loop until reaching the
end
of text
if
no parent tag specified.
#
{
#
# Split the text using the first
$tag_match
pattern found.
# Text before pattern will be first in the
array
, text after
# pattern will be at the
end
,
and
between will be any catches made
# by the pattern.
#
parts = preg_split(
$block_tag_re
,
$text
, 2, PREG_SPLIT_DELIM_CAPTURE);
# If in Markdown span mode, add a
empty
-string span-level hash
# after each newline to prevent triggering any block element.
if
(
$span
) {
$void
=
$this
->hashPart(
""
,
':'
);
$newline
=
"$void\n"
;
$parts
[0] =
$void
.
str_replace
(
"\n"
,
$newline
,
$parts
[0]) .
$void
;
}
$parsed
.=
$parts
[0]; # Text before current tag.
# If
end
of
$text
has been reached. Stop loop.
if
(
count
(
$parts
) < 3) {
$text
=
""
;
break
;
}
$tag
=
$parts
[1]; # Tag to handle.
$text
=
$parts
[2]; # Remaining text after current tag.
$tag_re
= preg_quote(
$tag
); # For
use
in a regular expression.
#
Check
for
: Code span marker
#
f (
$tag
{0} ==
"`"
) {
# Find corresponding
end
marker.
$tag_re
= preg_quote(
$tag
);
if
(preg_match(
'{^(?>.+?|\n(?!\n))*?(?<!`)'
.
$tag_re
.
'(?!`)}'
,
$text
,
$matches
)) {
#
End
marker found: pass text unchanged until marker.
$parsed
.=
$tag
.
$matches
[0];
$text
=
substr
(
$text
,
strlen
(
$matches
[0]));
}
else
{
# Unmatched marker: just skip it.
$parsed
.=
$tag
;
}
}
#
# Check
for
: Indented code block
or
fenced code block marker.
#
lse
if
(
$tag
{0} ==
"\n"
||
$tag
{0} ==
"~"
) {
if
(
$tag
{1} ==
"\n"
||
$tag
{1} ==
" "
) {
# Indented code block: pass it unchanged, will be handled
# later.
$parsed
.=
$tag
;
}
else
{
# Fenced code block marker: find matching
end
marker.
$tag_re
= preg_quote(trim(
$tag
));
if
(preg_match(
'{^(?>.*\n)+?'
.
$tag_re
.
' *\n}'
,
$text
,
$matches
)) {
#
End
marker found: pass text unchanged until marker.
$parsed
.=
$tag
.
$matches
[0];
$text
=
substr
(
$text
,
strlen
(
$matches
[0]));
}
else
{
# No
end
marker: just skip it.
$parsed
.=
$tag
;
}
}
}
#
# Check
for
: Opening Block level tag
or
# Opening Context Block tag (like ins
and
del)
# used
as
a block tag (tag is alone on it's line).
#
lse
if
(preg_match(
'{^<(?:'
.
$this
->block_tags_re .
')\b}'
,
$tag
) ||
( preg_match(
'{^<(?:'
.
$this
->context_block_tags_re .
')\b}'
,
$tag
) &&
preg_match(
$newline_before_re
,
$parsed
) &&
preg_match(
$newline_after_re
,
$text
) )
) {
# Need to parse tag
and
following text using the HTML parser.
list(
$block_text
,
$text
) =
$this
->_hashHTMLBlocks_inHTML(
$tag
.
$text
,
"hashBlock"
, true);
# Make sure it stays outside of any paragraph by adding newlines.
$parsed
.=
"\n\n$block_text\n\n"
;
}
#
# Check
for
: Clean tag (like script, math)
# HTML Comments, processing instructions.
#
lse
if
(preg_match(
'{^<(?:'
.
$this
->clean_tags_re .
')\b}'
,
$tag
) ||
$tag
{1} ==
'!'
||
$tag
{1} ==
'?'
) {
# Need to parse tag
and
following text using the HTML parser.
# (don't check
for
markdown attribute)
list(
$block_text
,
$text
) =
$this
->_hashHTMLBlocks_inHTML(
$tag
.
$text
,
"hashClean"
, false);
$parsed
.=
$block_text
;
}
#
# Check
for
: Tag with same name
as
enclosing tag.
#
lse
if
(
$enclosing_tag_re
!==
''
&&
# Same name
as
enclosing tag.
preg_match(
'{^</?(?:'
.
$enclosing_tag_re
.
')\b}'
,
$tag
)) {
#
# Increase/decrease nested tag
count
.
#
if
(
$tag
{1} ==
'/'
)
$depth
--;
else
if
(
$tag
{
strlen
(
$tag
) - 2} !=
'/'
)
$depth
++;
if
(
$depth
< 0) {
#
# Going out of parent element. Clean up
and
break
so we
#
return
to the calling
function
.
#
$text
=
$tag
.
$text
;
break
;
}
$parsed
.=
$tag
;
}
else
{
$parsed
.=
$tag
;
}
}
while
(
$depth
>= 0);
return
array
(
$parsed
,
$text
);
}
function
_processDefListItems_callback_dd(
$matches
)
{
$leading_line
=
$matches
[1];
$marker_space
=
$matches
[2];
$def
=
$matches
[3];
if
(
$leading_line
|| preg_match(
'/\n{2,}/'
,
$def
)) {
# Replace marker with the appropriate whitespace indentation
$def
=
str_repeat
(
' '
,
strlen
(
$marker_space
)) .
$def
;
$def
=
$this
->runBlockGamut(
$this
->outdent(
$def
.
"\n\n"
));
$def
=
"\n"
.
$def
.
"\n"
;
}
else
{
$def
= rtrim(
$def
);
$def
=
$this
->runSpanGamut(
$this
->outdent(
$def
));
}
return
"\n<dd>"
.
$def
.
"</dd>\n"
;
}
function
_processDefListItems_callback_dt(
$matches
)
{
$terms
=
explode
(
"\n"
, trim(
$matches
[1]));
$text
=
''
;
foreach
(
$terms
as
$term
) {
$term
=
$this
->runSpanGamut(trim(
$term
));
$text
.=
"\n<dt>"
.
$term
.
"</dt>"
;
}
return
$text
.
"\n"
;
}
function
_stripAbbreviations_callback(
$matches
)
{
$abbr_word
=
$matches
[1];
$abbr_desc
=
$matches
[2];
if
(
$this
->abbr_word_re)
$this
->abbr_word_re .=
'|'
;
$this
->abbr_word_re .= preg_quote(
$abbr_word
);
$this
->abbr_desciptions[
$abbr_word
] = trim(
$abbr_desc
);
return
''
; # String that will replace the block
}
function
_stripFootnotes_callback(
$matches
)
{
$note_id
=
$this
->fn_id_prefix .
$matches
[1];
$this
->footnotes[
$note_id
] =
$this
->outdent(
$matches
[2]);
return
''
; # String that will replace the block
}
function
appendFootnotes(
$text
)
{
#
# Append footnote list to text.
#
ext = preg_replace_callback(
'{F\x1Afn:(.*?)\x1A:}'
,
array
(&
$this
,
'_appendFootnotes_callback'
),
$text
);
if
(!
empty
(
$this
->footnotes_ordered)) {
$text
.=
"\n\n"
;
$text
.=
"<div class=\"footnotes\">\n"
;
$text
.=
"<hr"
. MARKDOWN_EMPTY_ELEMENT_SUFFIX .
"\n"
;
$text
.=
"<ol>\n\n"
;
$attr
=
" rev=\"footnote\""
;
if
(
$this
->fn_backlink_class !=
""
) {
$class
=
$this
->fn_backlink_class;
$class
=
$this
->encodeAttribute(
$class
);
$attr
.=
" class=\"$class\""
;
}
if
(
$this
->fn_backlink_title !=
""
) {
$title
=
$this
->fn_backlink_title;
$title
=
$this
->encodeAttribute(
$title
);
$attr
.=
" title=\"$title\""
;
}
$num
= 0;
while
(!
empty
(
$this
->footnotes_ordered)) {
$footnote
= reset(
$this
->footnotes_ordered);
$note_id
= key(
$this
->footnotes_ordered);
unset(
$this
->footnotes_ordered[
$note_id
]);
$footnote
.=
"\n"
; # Need to append newline before parsing.
$footnote
=
$this
->runBlockGamut(
"$footnote\n"
);
$footnote
= preg_replace_callback(
'{F\x1Afn:(.*?)\x1A:}'
,
array
(&
$this
,
'_appendFootnotes_callback'
),
$footnote
);
$attr
=
str_replace
(
"%%"
, ++
$num
,
$attr
);
$note_id
=
$this
->encodeAttribute(
$note_id
);
# Add backlink to last paragraph; create
new
paragraph
if
needed.
$backlink
=
"<a href=\"#fnref:$note_id\"$attr>↩</a>"
;
if
(preg_match(
'{</p>$}'
,
$footnote
)) {
$footnote
=
substr
(
$footnote
, 0, -4) .
" $backlink</p>"
;
}
else
{
$footnote
.=
"\n\n<p>$backlink</p>"
;
}
$text
.=
"<li id=\"fn:$note_id\">\n"
;
$text
.=
$footnote
.
"\n"
;
$text
.=
"</li>\n\n"
;
}
$text
.=
"</ol>\n"
;
$text
.=
"</div>"
;
}
return
$text
;
}
function
doAbbreviations(
$text
)
{
#
# Find defined abbreviations in text
and
wrap them in <abbr> elements.
#
(
$this
->abbr_word_re) {
// cannot use the /x modifier because abbr_word_re may
// contain significant spaces:
$text
= preg_replace_callback(
'{'
.
'(?<![\w\x1A])'
.
'(?:'
.
$this
->abbr_word_re .
')'
.
'(?![\w\x1A])'
.
'}'
,
array
(&
$this
,
'_doAbbreviations_callback'
),
$text
);
}
return
$text
;
}
function
doDefLists(
$text
)
{
#
# Form HTML definition lists.
#
ess_than_tab =
$this
->tab_width - 1;
# Re-usable pattern to match any entire dl list:
$whole_list_re
= '(?>
#
$1
= whole list
( #
$2
[ ]{0,
' . $less_than_tab . '
}
((?>.*\S.*\n)+) #
$3
= defined term
\n?
[ ]{0,
' . $less_than_tab . '
}:[ ]+ # colon starting definition
)
(?s:.+?)
( #
$4
\z
|
\n{2,}
(?=\S)
(?! # Negative lookahead
for
another term
[ ]{0,
' . $less_than_tab . '
}
(?: \S.*\n )+? # defined term
\n?
[ ]{0,
' . $less_than_tab . '
}:[ ]+ # colon starting definition
)
(?! # Negative lookahead
for
another definition
[ ]{0,
' . $less_than_tab . '
}:[ ]+ # colon starting definition
)
)
;
// mx
$text
= preg_replace_callback('{
(?>\A\n?|(?<=\n\n))
' . $whole_list_re . '
mx
', array(&$this, '
_doDefLists_callback'),
$text
);
return
$text
;
}
function
doFencedCodeBlocks(
$text
)
{
#
# Adding the fenced code block syntax to regular Markdown:
#
~~
# Code block
# ~~~
#
ess_than_tab =
$this
->tab_width;
$text
= preg_replace_callback('{
(?:\n|\A)
# 1: Opening marker
(
~{3,} # Marker: three tilde
or
more.
)
[ ]* \n # Whitespace
and
newline following marker.
# 2: Content
(
(?>
(?!\1 [ ]* \n) # Not a closing marker.
.*\n+
)+
)
# Closing marker.
\1 [ ]* \n
xm
', array(&$this, '
_doFencedCodeBlocks_callback'),
$text
);
return
$text
;
}
function
doFootnotes(
$text
)
{
#
# Replace footnote references in
$text
[^id] with a special text-token
# which will be replaced by the actual footnote marker in appendFootnotes.
#
(!
$this
->in_anchor) {
$text
= preg_replace(
'{\[\^(.+?)\]}'
,
"F\x1Afn:\\1\x1A:"
,
$text
);
}
return
$text
;
}
function
doHeaders(
$text
)
{
#
# Redefined to add id attribute support.
#
Setext-style headers:
# Header 1 {#header1}
# ========
#
# Header 2 {#header2}
# --------
#
ext = preg_replace_callback(
'{
(^.+?) #
$1
: Header text
(?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? #
$2
: Id attribute
[ ]*\n(=+|-+)[ ]*\n+ #
$3
: Header footer
mx
', array(&$this, '
_doHeaders_callback_setext'),
$text
);
# atx-style headers:
# # Header 1 {#header1}
# ## Header 2 {#header2}
# ## Header 2 with closing hashes ## {#header3}
# ...
# ###### Header 6 {#header2}
#
ext = preg_replace_callback('{
^(\#{1,6}) #
$1
= string of #\'s
[ ]*
(.+?) #
$2
= Header text
[ ]*
\#* # optional closing #\'s (not counted)
(?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
[ ]*
\n+
xm
', array(&$this, '
_doHeaders_callback_atx'),
$text
);
return
$text
;
}
function
doTables(
$text
)
{
#
# Form HTML tables.
#
ess_than_tab =
$this
->tab_width - 1;
#
# Find tables with leading pipe.
#
| Header 1 | Header 2
# | -------- | --------
# | Cell 1 | Cell 2
# | Cell 3 | Cell 4
#
ext = preg_replace_callback('
^ # Start of a line
[ ]{0,
' . $less_than_tab . '
} # Allowed whitespace.
[|] # Optional leading pipe (present)
(.+) \n #
$1
: Header row (at least one pipe)
[ ]{0,
' . $less_than_tab . '
} # Allowed whitespace.
[|] ([ ]*[-:]+[-| :]*) \n #
$2
: Header underline
( #
$3
: Cells
(?>
[ ]* # Allowed whitespace.
[|] .* \n # Row content.
)*
)
(?=\n|\Z) # Stop at
final
double newline.
xm
', array(&$this, '
_doTable_leadingPipe_callback'),
$text
);
#
# Find tables without leading pipe.
#
Header 1 | Header 2
# -------- | --------
# Cell 1 | Cell 2
# Cell 3 | Cell 4
#
ext = preg_replace_callback('
^ # Start of a line
[ ]{0,
' . $less_than_tab . '
} # Allowed whitespace.
(\S.*[|].*) \n #
$1
: Header row (at least one pipe)
[ ]{0,
' . $less_than_tab . '
} # Allowed whitespace.
([-:]+[ ]*[|][-| :]*) \n #
$2
: Header underline
( #
$3
: Cells
(?>
.* [|] .* \n # Row content
)*
)
(?=\n|\Z) # Stop at
final
double newline.
xm
', array(&$this, '
_DoTable_callback'),
$text
);
return
$text
;
}
function
formParagraphs(
$text
)
{
#
# Params:
#
$text
- string to process with html <p> tags
#
Strip leading
and
trailing lines:
$text
= preg_replace(
'/\A\n+|\n+\z/'
,
''
,
$text
);
$grafs
= preg_split(
'/\n{2,}/'
,
$text
, -1, PREG_SPLIT_NO_EMPTY);
#
# Wrap <p> tags
and
unhashify HTML blocks
#
reach (
$grafs
as
$key
=>
$value
) {
$value
= trim(
$this
->runSpanGamut(
$value
));
# Check
if
this should be enclosed in a paragraph.
# Clean tag hashes & block tag hashes are left alone.
$is_p
= !preg_match(
'/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/'
,
$value
);
if
(
$is_p
) {
$value
=
"<p>$value</p>"
;
}
$grafs
[
$key
] =
$value
;
}
# Join grafs in one text, then unhash HTML tags.
$text
= implode(
"\n\n"
,
$grafs
);
# Finish by removing any tag hashes still present in
$text
.
$text
=
$this
->unhash(
$text
);
return
$text
;
}
function
hashClean(
$text
)
{
#
# Called whenever a tag must be hashed when a
function
insert a
"clean"
tag
# in
$text
, it pass through this
function
and
is automaticaly escaped,
# blocking invalid nested overlap.
#
turn
$this
->hashPart(
$text
,
'C'
);
}
function
hashHTMLBlocks(
$text
)
{
#
# Hashify HTML Blocks
and
"clean tags"
.
#
e 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.
#
his 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
# _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
# These two functions are calling each other. It's recursive!
#
Call the HTML-in-Markdown hasher.
#
st(
$text
, ) =
$this
->_hashHTMLBlocks_inMarkdown(
$text
);
return
$text
;
}
function
processDefListItems(
$list_str
)
{
#
# Process the contents of a single definition list, splitting it
# into individual term
and
definition list items.
#
ess_than_tab =
$this
->tab_width - 1;
# trim trailing blank lines:
$list_str
= preg_replace(
"/\n{2,}\\z/"
,
"\n"
,
$list_str
);
# Process definition terms.
$list_str
= preg_replace_callback('{
?>\A\n?|\n\n+) # leading line
# definition terms =
$1
[ ]{0,
' . $less_than_tab . '
} # leading whitespace
(?![:][ ]|[ ]) # negative lookahead
for
a definition
# mark (colon)
or
more whitespace.
(?> \S.* \n)+? # actual term (not whitespace).
?=\n?[ ]{0,3}:[ ]) # lookahead
for
following line feed
# with a definition mark.
xm
', array(&$this, '
_processDefListItems_callback_dt'),
$list_str
);
# Process actual definitions.
$list_str
= preg_replace_callback('{
n(\n+)? # leading line =
$1
# marker space =
$2
[ ]{0,
' . $less_than_tab . '
} # whitespace before colon
[:][ ]+ # definition mark (colon)
(?s:.+?)) # definition text =
$3
?= \n+ # stop at next definition mark,
(?: # next term
or
end
of text
[ ]{0,
' . $less_than_tab . '
} [:][ ] |
<dt> | \z
)
xm
', array(&$this, '
_processDefListItems_callback_dd'),
$list_str
);
return
$list_str
;
}
function
setup()
{
#
# Setting up Extra-specific variables.
#
rent::setup();
$this
->footnotes =
array
();
$this
->footnotes_ordered =
array
();
$this
->abbr_desciptions =
array
();
$this
->abbr_word_re =
''
;
$this
->footnote_counter = 1;
foreach
(
$this
->predef_abbr
as
$abbr_word
=>
$abbr_desc
) {
if
(
$this
->abbr_word_re)
$this
->abbr_word_re .=
'|'
;
$this
->abbr_word_re .= preg_quote(
$abbr_word
);
$this
->abbr_desciptions[
$abbr_word
] = trim(
$abbr_desc
);
}
}
function
stripAbbreviations(
$text
)
{
#
# Strips abbreviations from text, stores titles in hash references.
#
ess_than_tab =
$this
->tab_width - 1;
# Link defs are in the form: [id]*: url
"optional title"
$text
= preg_replace_callback('{
[ ]{0,
' . $less_than_tab . '
}\*\[(.+?)\][ ]?: # abbr_id =
$1
.*) # text =
$2
(no blank lines allowed)
xm
', array(&$this, '
_stripAbbreviations_callback'),
$text
);
return
$text
;
}
function
stripFootnotes(
$text
)
{
#
# Strips link definitions from text, stores the URLs
and
titles in
# hash references.
#
ess_than_tab =
$this
->tab_width - 1;
# Link defs are in the form: [^id]: url
"optional title"
$text
= preg_replace_callback('{
[ ]{0,
' . $less_than_tab . '
}\[\^(.+?)\][ ]?: # note_id =
$1
[ ]*
\n? # maybe *one* newline
# text =
$2
(no blank lines allowed)
(?:
.+ # actual text
|
\n # newlines but
(?!\[\^.+?\]:\s)# negative lookahead
for
footnote marker.
(?!\n+[ ]{0,3}\S)# ensure line is not blank
and
followed
# by non-indented content
)*
xm
', array(&$this, '
_stripFootnotes_callback'),
$text
);
return
$text
;
}
function
teardown()
{
#
# Clearing Extra-specific variables.
#
his->footnotes =
array
();
$this
->footnotes_ordered =
array
();
$this
->abbr_desciptions =
array
();
$this
->abbr_word_re =
''
;
parent::teardown();
}
function
_detab_callback(
$matches
)
{
$line
=
$matches
[0];
$strlen
=
$this
->utf8_strlen; #
strlen
function
for
UTF-8.
# 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
->tab_width -
$strlen
(
$line
,
'UTF-8'
) %
$this
->tab_width;
$line
.=
str_repeat
(
" "
,
$amount
) .
$block
;
}
return
$line
;
}
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];
$url
=
$this
->encodeAttribute(
$url
);
$result
=
"<a href=\"$url\""
;
if
(isset(
$title
)) {
$title
=
$this
->encodeAttribute(
$title
);
$result
.=
" title=\"$title\""
;
}
$link_text
=
$this
->runSpanGamut(
$link_text
);
$result
.=
">$link_text</a>"
;
return
$this
->hashPart(
$result
);
}
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
=
"<a href=\"$url\""
;
if
(isset(
$this
->titles[
$link_id
])) {
$title
=
$this
->titles[
$link_id
];
$title
=
$this
->encodeAttribute(
$title
);
$result
.=
" title=\"$title\""
;
}
$link_text
=
$this
->runSpanGamut(
$link_text
);
$result
.=
">$link_text</a>"
;
$result
=
$this
->hashPart(
$result
);
}
else
{
$result
=
$whole_match
;
}
return
$result
;
}
function
_doAutoLinks_email_callback(
$matches
)
{
$address
=
$matches
[1];
$link
=
$this
->encodeEmailAddress(
$address
);
return
$this
->hashPart(
$link
);
}
function
_doAutoLinks_url_callback(
$matches
)
{
$url
=
$this
->encodeAttribute(
$matches
[1]);
$link
=
"<a href=\"$url\">$url</a>"
;
return
$this
->hashPart(
$link
);
}
function
_doBlockQuotes_callback(
$matches
)
{
$bq
=
$matches
[1];
# trim one level of quoting - trim whitespace-only lines
$bq
= preg_replace(
'/^[ ]*>[ ]?|^[ ]+$/m'
,
''
,
$bq
);
$bq
=
$this
->runBlockGamut(
$bq
); # recurse
$bq
= preg_replace(
'/^/m'
,
" "
,
$bq
);
# These leading spaces cause problem with <pre> content,
# so we need to fix that:
$bq
= preg_replace_callback(
'{(\s*<pre>.+?</pre>)}sx'
,
array
(&
$this
,
'_DoBlockQuotes_callback2'
),
$bq
);
return
"\n"
.
$this
->hashBlock(
"<blockquote>\n$bq\n</blockquote>"
) .
"\n\n"
;
}
function
_doBlockQuotes_callback2(
$matches
)
{
$pre
=
$matches
[1];
$pre
= preg_replace(
'/^ /m'
,
''
,
$pre
);
return
$pre
;
}
function
_doCodeBlocks_callback(
$matches
)
{
$codeblock
=
$matches
[1];
$codeblock
=
$this
->outdent(
$codeblock
);
$codeblock
= htmlspecialchars(
$codeblock
, ENT_NOQUOTES);
# trim leading newlines
and
trailing newlines
$codeblock
= preg_replace(
'/\A\n+|\n+\z/'
,
''
,
$codeblock
);
$codeblock
=
"<pre><code>$codeblock\n</code></pre>"
;
return
"\n\n"
.
$this
->hashBlock(
$codeblock
) .
"\n\n"
;
}
function
_doHardBreaks_callback(
$matches
)
{
return
$this
->hashPart(
"<br$this->empty_element_suffix\n"
);
}
function
_doImages_inline_callback(
$matches
)
{
$whole_match
=
$matches
[1];
$alt_text
=
$matches
[2];
$url
=
$matches
[3] ==
''
?
$matches
[4] :
$matches
[3];
$title
= &
$matches
[7];
$alt_text
=
$this
->encodeAttribute(
$alt_text
);
$url
=
$this
->encodeAttribute(
$url
);
$result
=
"<img src=\"$url\" alt=\"$alt_text\""
;
if
(isset(
$title
)) {
$title
=
$this
->encodeAttribute(
$title
);
$result
.=
" title=\"$title\""
; #
$title
already quoted
}
$result
.=
$this
->empty_element_suffix;
return
$this
->hashPart(
$result
);
}
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
=
"<img src=\"$url\" alt=\"$alt_text\""
;
if
(isset(
$this
->titles[
$link_id
])) {
$title
=
$this
->titles[
$link_id
];
$title
=
$this
->encodeAttribute(
$title
);
$result
.=
" title=\"$title\""
;
}
$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
_doLists_callback(
$matches
)
{
# Re-usable patterns to match list item bullets
and
number markers:
$marker_ul_re
=
'[*+-]'
;
$marker_ol_re
=
'\d+[.]'
;
$marker_any_re
=
"(?:$marker_ul_re|$marker_ol_re)"
;
$list
=
$matches
[1];
$list_type
= preg_match(
"/$marker_ul_re/"
,
$matches
[3]) ?
"ul"
:
"ol"
;
$marker_any_re
= (
$list_type
==
"ul"
?
$marker_ul_re
:
$marker_ol_re
);
$list
.=
"\n"
;
$result
=
$this
->processListItems(
$list
,
$marker_any_re
);
$result
=
$this
->hashBlock(
"<$list_type>\n"
.
$result
.
"</$list_type>"
);
return
"\n"
.
$result
.
"\n\n"
;
}
function
_hashHTMLBlocks_callback(
$matches
)
{
$text
=
$matches
[1];
$key
=
$this
->hashBlock(
$text
);
return
"\n\n$key\n\n"
;
}
function
_initDetab()
{
#
# Check
for
the availability of the
function
in the `utf8_strlen` property
# (initially `mb_strlen`). If the
function
is not available, create a
#
function
that will loosely
count
the number of UTF-8 characters with a
# regular expression.
#
(function_exists(
$this
->utf8_strlen))
return
;
$this
->utf8_strlen = create_function(
'$text'
, '
return
preg_match_all(
/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
text,
$m
);');
}
function
_processListItems_callback(
$matches
)
{
$item
=
$matches
[4];
$leading_line
= &
$matches
[1];
$leading_space
= &
$matches
[2];
$marker_space
=
$matches
[3];
$tailing_blank_line
= &
$matches
[5];
if
(
$leading_line
||
$tailing_blank_line
||
preg_match(
'/\n{2,}/'
,
$item
)) {
# Replace marker with the appropriate whitespace indentation
$item
=
$leading_space
.
str_repeat
(
' '
,
strlen
(
$marker_space
)) .
$item
;
$item
=
$this
->runBlockGamut(
$this
->outdent(
$item
) .
"\n"
);
}
else
{
# Recursion
for
sub-lists:
$item
=
$this
->doLists(
$this
->outdent(
$item
));
$item
= preg_replace(
'/\n+$/'
,
''
,
$item
);
$item
=
$this
->runSpanGamut(
$item
);
}
return
"<li>"
.
$item
.
"</li>\n"
;
}
function
_stripLinkDefinitions_callback(
$matches
)
{
$link_id
=
strtolower
(
$matches
[1]);
$this
->urls[
$link_id
] =
$matches
[2];
$this
->titles[
$link_id
] = &
$matches
[3];
return
''
; # String that will replace the block
}
function
_unhash_callback(
$matches
)
{
return
$this
->html_hashes[
$matches
[0]];
}
function
detab(
$text
)
{
#
# Replace tabs with the appropriate amount of space.
#
For each line we separate the line in blocks delemited by
# tab characters. Then we reconstruct every line by adding the
# appropriate number of space between each blocks.
$text
= preg_replace_callback(
'/^.*\t.*$/m'
,
array
(&
$this
,
'_detab_callback'
),
$text
);
return
$text
;
}
function
doAnchors(
$text
)
{
#
# Turn Markdown link shortcuts into XHTML <a> tags.
#
(
$this
->in_anchor)
return
$text
;
$this
->in_anchor = true;
#
# First, handle reference-style links: [link text] [id]
#
ext = 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"
)
#
ext = preg_replace_callback('{
# wrap whole match in
$1
\[
(
' . $this->nested_brackets_re . '
) # link text =
$2
\]
\( # literal paren
[ ]*
(?:
<(\S*)> # href =
$3
|
(
' . $this->nested_url_parenthesis_re . '
) # href =
$4
)
[ ]*
( #
$5
([\'"]) # quote char =
$6
(.*?) # Title =
$7
\6 # matching quote
[ ]* # ignore any spaces/tabs between closing quote
and
)
)? # title is optional
\)
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 test][1]
#
or
[link test](/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
doAutoLinks(
$text
)
{
$text
= preg_replace_callback(
'{<((https?|ftp|dict):[^\'">\s]+)>}i'
,
array
(&
$this
,
'_doAutoLinks_url_callback'
),
$text
);
# Email addresses: <address@domain.foo>
$text
= preg_replace_callback('{
?:mailto:)?
[-.\w\x80-\xFF]+
\@
[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
xi
', array(&$this, '
_doAutoLinks_email_callback'),
$text
);
return
$text
;
}
function
doBlockQuotes(
$text
)
{
$text
= preg_replace_callback('/
( # Wrap whole match in
$1
(?>
^[ ]*>[ ]? #
">"
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
;
}
function
doCodeBlocks(
$text
)
{
#
# Process Markdown `<pre><code>` blocks.
#
ext = preg_replace_callback('{
(?:\n\n|\A\n?)
( #
$1
= the code block -- one
or
more lines, starting with a space/tab
(?>
[ ]{
' . $this->tab_width . '
} # Lines must start with a tab
or
a tab-width of spaces
.*\n+
)+
)
((?=^[ ]{0,
' . $this->tab_width . '
}\S)|\Z) # Lookahead
for
non-space at line-start,
or
end
of doc
xm
', array(&$this, '
_doCodeBlocks_callback'),
$text
);
return
$text
;
}
function
doHardBreaks(
$text
)
{
# Do hard breaks:
return
preg_replace_callback(
'/ {2,}\n/'
,
array
(&
$this
,
'_doHardBreaks_callback'
),
$text
);
}
function
doHorizontalRules(
$text
)
{
# Do Horizontal Rules:
return
preg_replace(
'{
^[ ]{0,3} # Leading space
([-*_]) #
$1
: First marker
(?> # Repeated marker group
[ ]{0,2} # Zero, one,
or
two spaces.
\1 # Marker character
){2,} # Group repeated at least twice
[ ]* # Tailing spaces
$ #
End
of line.
mx',
"\n"
.
$this
->hashBlock(
"<hr$this->empty_element_suffix"
) .
"\n"
,
$text
);
}
function
doImages(
$text
)
{
#
# Turn Markdown image shortcuts into <img> tags.
#
First, handle reference-style labeled images: ![alt text][id]
#
ext = 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
_
#
ext = preg_replace_callback('{
# wrap whole match in
$1
!\[
(
' . $this->nested_brackets_re . '
) # alt text =
$2
\]
\s? # One optional whitespace character
\( # literal paren
[ ]*
(?:
<(\S*)> # src url =
$3
|
(
' . $this->nested_url_parenthesis_re . '
) # src url =
$4
)
[ ]*
( #
$5
([\'"]) # quote char =
$6
(.*?) # title =
$7
\6 # matching quote
[ ]*
)? # title is optional
\)
xs
', array(&$this, '
_doImages_inline_callback'),
$text
);
return
$text
;
}
function
doItalicsAndBold(
$text
)
{
$token_stack
=
array
(
''
);
$text_stack
=
array
(
''
);
$em
=
''
;
$strong
=
''
;
$tree_char_em
= false;
while
(1) {
#
# Get prepared regular expression
for
seraching emphasis tokens
# in current context.
#
token_re =
$this
->em_strong_prepared_relist[
"$em$strong"
];
#
# Each loop iteration seach
for
the next emphasis token.
# Each token is then passed to handleSpanToken.
#
parts = preg_split(
$token_re
,
$text
, 2, PREG_SPLIT_DELIM_CAPTURE);
$text_stack
[0] .=
$parts
[0];
$token
= &
$parts
[1];
$text
= &
$parts
[2];
if
(
empty
(
$token
)) {
# Reached
end
of text span:
empty
stack without emitting.
# any more emphasis.
while
(
$token_stack
[0]) {
$text_stack
[1] .=
array_shift
(
$token_stack
);
$text_stack
[0] .=
array_shift
(
$text_stack
);
}
break
;
}
$token_len
=
strlen
(
$token
);
if
(
$tree_char_em
) {
# Reached closing marker
while
inside a three-char emphasis.
if
(
$token_len
== 3) {
# Three-char closing marker, close em
and
strong.
array_shift
(
$token_stack
);
$span
=
array_shift
(
$text_stack
);
$span
=
$this
->runSpanGamut(
$span
);
$span
=
"<strong><em>$span</em></strong>"
;
$text_stack
[0] .=
$this
->hashPart(
$span
);
$em
=
''
;
$strong
=
''
;
}
else
{
# Other closing marker: close one em
or
strong
and
# change current token state to match the other
$token_stack
[0] =
str_repeat
(
$token
{0}, 3 -
$token_len
);
$tag
=
$token_len
== 2 ?
"strong"
:
"em"
;
$span
=
$text_stack
[0];
$span
=
$this
->runSpanGamut(
$span
);
$span
=
"<$tag>$span</$tag>"
;
$text_stack
[0] =
$this
->hashPart(
$span
);
$
$tag
=
''
; # $
$tag
stands
for
$em
or
$strong
}
$tree_char_em
= false;
}
else
if
(
$token_len
== 3) {
if
(
$em
) {
# Reached closing marker
for
both em
and
strong.
# Closing strong marker:
for
(
$i
= 0;
$i
< 2; ++
$i
) {
$shifted_token
=
array_shift
(
$token_stack
);
$tag
=
strlen
(
$shifted_token
) == 2 ?
"strong"
:
"em"
;
$span
=
array_shift
(
$text_stack
);
$span
=
$this
->runSpanGamut(
$span
);
$span
=
"<$tag>$span</$tag>"
;
$text_stack
[0] .=
$this
->hashPart(
$span
);
$
$tag
=
''
; # $
$tag
stands
for
$em
or
$strong
}
}
else
{
# Reached opening three-char emphasis marker. Push on token
# stack; will be handled by the special condition above.
$em
=
$token
{0};
$strong
=
"$em$em"
;
array_unshift
(
$token_stack
,
$token
);
array_unshift
(
$text_stack
,
''
);
$tree_char_em
= true;
}
}
else
if
(
$token_len
== 2) {
if
(
$strong
) {
# Unwind any dangling emphasis marker:
if
(
strlen
(
$token_stack
[0]) == 1) {
$text_stack
[1] .=
array_shift
(
$token_stack
);
$text_stack
[0] .=
array_shift
(
$text_stack
);
}
# Closing strong marker:
array_shift
(
$token_stack
);
$span
=
array_shift
(
$text_stack
);
$span
=
$this
->runSpanGamut(
$span
);
$span
=
"<strong>$span</strong>"
;
$text_stack
[0] .=
$this
->hashPart(
$span
);
$strong
=
''
;
}
else
{
array_unshift
(
$token_stack
,
$token
);
array_unshift
(
$text_stack
,
''
);
$strong
=
$token
;
}
}
else
{
# Here
$token_len
== 1
if
(
$em
) {
if
(
strlen
(
$token_stack
[0]) == 1) {
# Closing emphasis marker:
array_shift
(
$token_stack
);
$span
=
array_shift
(
$text_stack
);
$span
=
$this
->runSpanGamut(
$span
);
$span
=
"<em>$span</em>"
;
$text_stack
[0] .=
$this
->hashPart(
$span
);
$em
=
''
;
}
else
{
$text_stack
[0] .=
$token
;
}
}
else
{
array_unshift
(
$token_stack
,
$token
);
array_unshift
(
$text_stack
,
''
);
$em
=
$token
;
}
}
}
return
$text_stack
[0];
}
function
doLists(
$text
)
{
#
# Form HTML ordered (numbered)
and
unordered (bulleted) lists.
#
ess_than_tab =
$this
->tab_width - 1;
# Re-usable patterns to match list item bullets
and
number markers:
$marker_ul_re
=
'[*+-]'
;
$marker_ol_re
=
'\d+[.]'
;
$marker_any_re
=
"(?:$marker_ul_re|$marker_ol_re)"
;
$markers_relist
=
array
(
$marker_ul_re
,
$marker_ol_re
);
foreach
(
$markers_relist
as
$marker_re
) {
# Re-usable pattern to match any entirel ul
or
ol list:
$whole_list_re
= '
( #
$1
= whole list
( #
$2
[ ]{0,
' . $less_than_tab . '
}
(
' . $marker_re . '
) #
$3
= first list item marker
[ ]+
)
(?s:.+?)
( #
$4
\z
|
\n{2,}
(?=\S)
(?! # Negative lookahead
for
another list item marker
[ ]*
' . $marker_re . '
[ ]+
)
)
)
;
// mx
# We
use
a different prefix before nested lists than top-level lists.
# See extended comment in _ProcessListItems().
if
(
$this
->list_level) {
$text
= preg_replace_callback('{
^
' . $whole_list_re . '
}mx
', array(&$this, '
_doLists_callback'),
$text
);
}
else
{
$text
= preg_replace_callback('{
(?:(?<=\n)\n|\A\n?) # Must eat the newline
' . $whole_list_re . '
}mx
', array(&$this, '
_doLists_callback'),
$text
);
}
}
return
$text
;
}
function
encodeAmpsAndAngles(
$text
)
{
#
# Smart processing
for
ampersands
and
angle brackets that need to
# be encoded. Valid character entities are left alone unless the
# no-entities mode is set.
#
(
$this
->no_entities) {
$text
=
str_replace
(
'&'
,
'&'
,
$text
);
}
else
{
# 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 remaining <'s
$text
=
str_replace
(
'<'
,
'<'
,
$text
);
return
$text
;
}
function
encodeAttribute(
$text
)
{
#
# Encode text
for
a double-quoted HTML attribute. This
function
# is *not* suitable
for
attributes enclosed in single quotes.
#
ext =
$this
->encodeAmpsAndAngles(
$text
);
$text
=
str_replace
(
'"'
,
'"'
,
$text
);
return
$text
;
}
function
encodeEmailAddress(
$addr
)
{
#
# Input: an email address, e.g.
"foo@example.com"
#
utput: 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.:
#
<p><a href="mailto:foo
# @example.co
# m">foo@exampl
# e.com</a></p>
#
ased by a filter by Matthew Wickline, posted to BBEdit-Talk.
# With some optimizations by Milian Wolff.
#
ddr =
"mailto:"
.
$addr
;
$chars
= preg_split(
'/(?<!^)(?!$)/'
,
$addr
);
$seed
= (int)
abs
(crc32(
$addr
) /
strlen
(
$addr
)); # Deterministic seed.
foreach
(
$chars
as
$key
=>
$char
) {
$ord
= ord(
$char
);
# Ignore non-ascii chars.
if
(
$ord
< 128) {
$r
= (
$seed
* (1 +
$key
)) % 100; # Pseudo-random
function
.
# roughly 10% raw, 45% hex, 45% dec
#
'@'
*must* be encoded. I insist.
if
(
$r
> 90 &&
$char
!=
'@'
)
/* do nothing */
;
else
if
(
$r
< 45)
$chars
[
$key
] =
'&#x'
.
dechex
(
$ord
) .
';'
;
else
$chars
[
$key
] =
'&#'
.
$ord
.
';'
;
}
}
$addr
= implode(
''
,
$chars
);
$text
= implode(
''
,
array_slice
(
$chars
, 7)); # text without `mailto:`
$addr
=
"<a href=\"$addr\">$text</a>"
;
return
$addr
;
}
function
handleSpanToken(
$token
, &
$str
)
{
#
# Handle
$token
provided by parseSpan by determining its nature
and
# returning the corresponding value that should replace it.
#
itch (
$token
{0}) {
case
"\\"
:
return
$this
->hashPart(
"&#"
. ord(
$token
{1}) .
";"
);
case
"`"
:
# Search
for
end
marker in remaining text.
if
(preg_match(
'/^(.*?[^`])'
. preg_quote(
$token
) .
'(?!`)(.*)$/sm'
,
$str
,
$matches
)) {
$str
=
$matches
[2];
$codespan
=
$this
->makeCodeSpan(
$matches
[1]);
return
$this
->hashPart(
$codespan
);
}
return
$token
;
// return as text since no ending marker found.
default
:
return
$this
->hashPart(
$token
);
}
}
function
hashBlock(
$text
)
{
#
# Shortcut
function
for
hashPart with block-level boundaries.
#
turn
$this
->hashPart(
$text
,
'B'
);
}
function
hashPart(
$text
,
$boundary
=
'X'
)
{
#
# Called whenever a tag must be hashed when a
function
insert an atomic
# element in the text stream. Passing
$text
to through this
function
gives
# a unique text-token which will be reverted back when calling unhash.
#
he
$boundary
argument specify what character should be used to surround
# the token. By convension,
"B"
is used
for
block elements that needs not
# to be wrapped into paragraph tags at the
end
,
":"
is used
for
elements
# that are word separators
and
"X"
is used in the general
case
.
#
Swap back any tag hash found in
$text
so we
do
not have to `unhash`
# multiple times at the
end
.
$text
=
$this
->unhash(
$text
);
# Then hash the block.
static
$i
= 0;
$key
=
"$boundary\x1A"
. ++
$i
.
$boundary
;
$this
->html_hashes[
$key
] =
$text
;
return
$key
; # String that will replace the tag.
}
function
makeCodeSpan(
$code
)
{
#
# Create a code span markup
for
$code
. Called from handleSpanToken.
#
ode = htmlspecialchars(trim(
$code
), ENT_NOQUOTES);
return
$this
->hashPart(
"<code>$code</code>"
);
}
function
outdent(
$text
)
{
#
# Remove one level of line-leading tabs
or
spaces
#
turn preg_replace(
'/^(\t|[ ]{1,'
.
$this
->tab_width .
'})/m'
,
''
,
$text
);
}
function
parseSpan(
$str
)
{
#
# Take the string
$str
and
parse it into tokens, hashing embeded HTML,
# escaped characters
and
handling code spans.
#
utput =
''
;
$span_re
= '{
(
\\\\
' . $this->escape_chars_re . '
|
(?<![`\\\\])
`+ # code span marker
. (
$this
->no_markup ?
''
: '
|
<!-- .*? --> # comment
|
<\?.*?\?> | <%.*?%> # processing instruction
|
<[/!$]?[-a-zA-Z0-9:]+ # regular tags
(?>
\s
(?>[^
"\'>]+|"
[^
"]*"
|\'[^\']*\')*
)?
>
) . '
)
}xs';
while
(1) {
#
# Each loop iteration seach
for
either the next tag, the next
# openning code span marker,
or
the next escaped character.
# Each token is then passed to handleSpanToken.
#
parts = preg_split(
$span_re
,
$str
, 2, PREG_SPLIT_DELIM_CAPTURE);
# Create token from text preceding tag.
if
(
$parts
[0] !=
""
) {
$output
.=
$parts
[0];
}
# Check
if
we reach the
end
.
if
(isset(
$parts
[1])) {
$output
.=
$this
->handleSpanToken(
$parts
[1],
$parts
[2]);
$str
=
$parts
[2];
}
else
{
break
;
}
}
return
$output
;
}
function
prepareItalicsAndBold()
{
#
# Prepare regular expressions
for
seraching emphasis tokens in any
# context.
#
reach (
$this
->em_relist
as
$em
=>
$em_re
) {
foreach
(
$this
->strong_relist
as
$strong
=>
$strong_re
) {
# Construct list of allowed token expressions.
$token_relist
=
array
();
if
(isset(
$this
->em_strong_relist[
"$em$strong"
])) {
$token_relist
[] =
$this
->em_strong_relist[
"$em$strong"
];
}
$token_relist
[] =
$em_re
;
$token_relist
[] =
$strong_re
;
# Construct master expression from list.
$token_re
=
'{('
. implode(
'|'
,
$token_relist
) .
')}'
;
$this
->em_strong_prepared_relist[
"$em$strong"
] =
$token_re
;
}
}
}
function
processListItems(
$list_str
,
$marker_any_re
)
{
#
# Process the contents of a single ordered
or
unordered list, splitting it
# into individual list items.
#
The
$this
->list_level
global
keeps track of when we're inside a list.
# Each time we enter a list, we increment it; when we leave a list,
# we decrement. If it
's zero, we'
re not in a list anymore.
#
We
do
this because when we're not inside a list, we want to treat
# something like this:
#
I recommend upgrading to version
# 8. Oops, now this line is treated
#
as
a sub-list.
#
As a single paragraph, despite the fact that the second line starts
# with a digit-period-space sequence.
#
Whereas when we're inside a list (
or
sub-list), that line will be
# treated
as
the start of a sub-list. What a kludge, huh? This is
# an aspect of Markdown
's syntax that'
s hard to parse perfectly
# without resorting to mind-reading. Perhaps the solution is to
# change the syntax rules such that sub-lists must start with a
# starting cardinal number; e.g.
"1."
or
"a."
.
$this
->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
^[ ]*) # leading whitespace =
$2
' . $marker_any_re . '
# list marker
and
space =
$3
(?:[ ]+|(?=\n)) # space only required
if
item is not
empty
(?s:.*?)) # list item text =
$4
?:(\n+(?=\n))|\n) # tailing blank line =
$5
?= \n* (\z | \2 (
' . $marker_any_re . '
) (?:[ ]+|(?=\n))))
xm
', array(&$this, '
_processListItems_callback'),
$list_str
);
$this
->list_level--;
return
$list_str
;
}
function
runBasicBlockGamut(
$text
)
{
#
# Run block gamut tranformations, without hashing HTML blocks. This is
# useful when HTML blocks are known to be already hashed, like in the first
# whole-document pass.
#
reach (
$this
->block_gamut
as
$method
=>
$priority
) {
$text
=
$this
->
$method
(
$text
);
}
# Finally form paragraph
and
restore hashed blocks.
$text
=
$this
->formParagraphs(
$text
);
return
$text
;
}
function
runBlockGamut(
$text
)
{
#
# Run block gamut tranformations.
#
We need to escape raw HTML in Markdown source before doing anything
#
else
. This need to be done
for
each block,
and
not only at the
# begining in the Markdown
function
since hashed blocks can be part of
# list items
and
could have been indented. Indented blocks would have
# been seen
as
a code block in a previous pass of hashHTMLBlocks.
$text
=
$this
->hashHTMLBlocks(
$text
);
return
$this
->runBasicBlockGamut(
$text
);
}
function
runSpanGamut(
$text
)
{
#
# Run span gamut tranformations.
#
reach (
$this
->span_gamut
as
$method
=>
$priority
) {
$text
=
$this
->
$method
(
$text
);
}
return
$text
;
}
function
stripLinkDefinitions(
$text
)
{
#
# Strips link definitions from text, stores the URLs
and
titles in
# hash references.
#
ess_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
[ ]*
<?(\S+?)>? # url =
$2
[ ]*
\n? # maybe one newline
[ ]*
(?:
(?<=\s) # lookbehind
for
whitespace
["(]
(.*?) # title =
$3
[")]
[ ]*
)? # title is optional
(?:\n+|\Z)
xm
', array(&$this, '
_stripLinkDefinitions_callback'),
$text
);
return
$text
;
}
function
transform(
$text
)
{
#
# Main
function
. Performs some preprocessing on the input text
#
and
pass it through the document gamut.
#
his->setup();
# Remove UTF-8 BOM
and
marker character in input,
if
present.
$text
= preg_replace(
'{^\xEF\xBB\xBF|\x1A}'
,
''
,
$text
);
# Standardize line endings:
# DOS to Unix
and
Mac to Unix
$text
= preg_replace(
'{\r\n?}'
,
"\n"
,
$text
);
# Make sure
$text
ends with a couple of newlines:
$text
.=
"\n\n"
;
# Convert all tabs to spaces.
$text
=
$this
->detab(
$text
);
# Turn block-level HTML blocks into hash entries
$text
=
$this
->hashHTMLBlocks(
$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 /[ ]*\n+/ .
$text
= preg_replace(
'/^[ ]+$/m'
,
''
,
$text
);
# Run document gamut methods.
foreach
(
$this
->document_gamut
as
$method
=>
$priority
) {
$text
=
$this
->
$method
(
$text
);
}
$this
->teardown();
return
$text
.
"\n"
;
}
function
unhash(
$text
)
{
#
# Swap back in all the tags hashed by _HashHTMLBlocks.
#
turn preg_replace_callback(
'/(.)\x1A[0-9]+\1/'
,
array
(&
$this
,
'_unhash_callback'
),
$text
);
}