2015-10-04 12:37:12 +00:00
< ? php
register_module ([
" name " => " Parsedown " ,
" version " => " 0.1 " ,
" author " => " Johnny Broadway, Emanuil Rusev & Starbeamrainbowlabs " ,
" description " => " An upgraded parser based on Emanuil Rusev's Parsedown Extra PHP library (https://github.com/erusev/parsedown-extra), which is licensed MIT. Also uses a modified Slimdown engine by Johnny Broadway in order to add support for internal links etc. Please be careful, as this module adds a _ton_ of weight to your installation. " ,
" id " => " parser-parsedown " ,
" optional " => true ,
" code " => function () {
$parsedown_extra = new ParsedownExtra ();
add_parser ( " parsedown " , function ( $source ) use ( $parsedown_extra ) {
2015-10-04 13:04:42 +00:00
$result = $parsedown_extra -> text ( $source );
$result = Parsedown_Slimdown_Extensions :: render ( $source );
return $result ;
2015-10-04 12:37:12 +00:00
});
}
]);
/**
* Slimdown - A very basic regex - based Markdown parser . Supports the
* following elements ( and can be extended via Slimdown :: add_rule ()) :
*
* - Headers
* - Links
* - Bold
* - Emphasis
* - Deletions
* - Quotes
* - Inline code
* - Blockquotes
* - Ordered / unordered lists
* - Horizontal rules
*
* Author : Johnny Broadway < johnny @ johnnybroadway . com >
* Website : https :// gist . github . com / jbroadway / 2836900
* License : MIT
*/
/**
* Modified by Starbeamrainbowlabs ( starbeamrainbowlabs )
*
* Changed bold to use single asterisks
* Changed italics to use single underscores
* Added one to add the heading levels ( no < h1 > tags allowed )
* Added wiki style internal link parsing
* Added wiki style internal link parsing with display text
* Added image support
*/
class Parsedown_Slimdown_Extensions {
public static $rules = array (
'/\r\n/' => " \n " , // new line normalisation
'/!\[(.*)\]\(([^\s]+)\s(\d+.+)\s(left|right)\)/' => '<img src="\2" alt="\1" style="max-width: \3; float: \4;" />' , // images with size
'/!\[(.*)\]\(([^\s]+)\s(\d+.+)\)/' => '<img src="\2" alt="\1" style="max-width: \3;" />' , // images with size
'/!\[(.*)\]\((.*)\)/' => '<img src="\2" alt="\1" />' , // basic images
'/\[\[([a-zA-Z0-9\_\- ]+)\|([a-zA-Z0-9\_\- ]+)\]\]/' => '<a href=\'index.php?page=\1\'>\2</a>' , //internal links with display text
'/\[\[([a-zA-Z0-9\_\- ]+)\]\]/' => '<a href=\'index.php?page=\1\'>\1</a>' , //internal links
);
/**
* Add a rule .
*/
public static function add_rule ( $regex , $replacement ) {
self :: $rules [ $regex ] = $replacement ;
}
/**
* Render some Markdown into HTML .
*/
public static function render ( $text ) {
foreach ( self :: $rules as $regex => $replacement ) {
if ( is_callable ( $replacement )) {
$text = preg_replace_callback ( $regex , $replacement , $text );
} else {
$text = preg_replace ( $regex , $replacement , $text );
}
}
return trim ( $text );
}
}
#
#
# Parsedown
# http://parsedown.org
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class Parsedown
{
# ~
const version = '1.5.4' ;
# ~
function text ( $text )
{
# make sure no definitions are set
$this -> DefinitionData = array ();
# standardize line breaks
$text = str_replace ( array ( " \r \n " , " \r " ), " \n " , $text );
# remove surrounding line breaks
$text = trim ( $text , " \n " );
# split text into lines
$lines = explode ( " \n " , $text );
# iterate through lines to identify blocks
$markup = $this -> lines ( $lines );
# trim line breaks
$markup = trim ( $markup , " \n " );
return $markup ;
}
#
# Setters
#
function setBreaksEnabled ( $breaksEnabled )
{
$this -> breaksEnabled = $breaksEnabled ;
return $this ;
}
protected $breaksEnabled ;
function setMarkupEscaped ( $markupEscaped )
{
$this -> markupEscaped = $markupEscaped ;
return $this ;
}
protected $markupEscaped ;
function setUrlsLinked ( $urlsLinked )
{
$this -> urlsLinked = $urlsLinked ;
return $this ;
}
protected $urlsLinked = true ;
#
# Lines
#
protected $BlockTypes = array (
'#' => array ( 'Header' ),
'*' => array ( 'Rule' , 'List' ),
'+' => array ( 'List' ),
'-' => array ( 'SetextHeader' , 'Table' , 'Rule' , 'List' ),
'0' => array ( 'List' ),
'1' => array ( 'List' ),
'2' => array ( 'List' ),
'3' => array ( 'List' ),
'4' => array ( 'List' ),
'5' => array ( 'List' ),
'6' => array ( 'List' ),
'7' => array ( 'List' ),
'8' => array ( 'List' ),
'9' => array ( 'List' ),
':' => array ( 'Table' ),
'<' => array ( 'Comment' , 'Markup' ),
'=' => array ( 'SetextHeader' ),
'>' => array ( 'Quote' ),
'[' => array ( 'Reference' ),
'_' => array ( 'Rule' ),
'`' => array ( 'FencedCode' ),
'|' => array ( 'Table' ),
'~' => array ( 'FencedCode' ),
);
# ~
protected $unmarkedBlockTypes = array (
'Code' ,
);
#
# Blocks
#
private function lines ( array $lines )
{
$CurrentBlock = null ;
foreach ( $lines as $line )
{
if ( chop ( $line ) === '' )
{
if ( isset ( $CurrentBlock ))
{
$CurrentBlock [ 'interrupted' ] = true ;
}
continue ;
}
if ( strpos ( $line , " \t " ) !== false )
{
$parts = explode ( " \t " , $line );
$line = $parts [ 0 ];
unset ( $parts [ 0 ]);
foreach ( $parts as $part )
{
$shortage = 4 - mb_strlen ( $line , 'utf-8' ) % 4 ;
$line .= str_repeat ( ' ' , $shortage );
$line .= $part ;
}
}
$indent = 0 ;
while ( isset ( $line [ $indent ]) and $line [ $indent ] === ' ' )
{
$indent ++ ;
}
$text = $indent > 0 ? substr ( $line , $indent ) : $line ;
# ~
$Line = array ( 'body' => $line , 'indent' => $indent , 'text' => $text );
# ~
if ( isset ( $CurrentBlock [ 'continuable' ]))
{
$Block = $this -> { 'block' . $CurrentBlock [ 'type' ] . 'Continue' }( $Line , $CurrentBlock );
if ( isset ( $Block ))
{
$CurrentBlock = $Block ;
continue ;
}
else
{
if ( method_exists ( $this , 'block' . $CurrentBlock [ 'type' ] . 'Complete' ))
{
$CurrentBlock = $this -> { 'block' . $CurrentBlock [ 'type' ] . 'Complete' }( $CurrentBlock );
}
}
}
# ~
$marker = $text [ 0 ];
# ~
$blockTypes = $this -> unmarkedBlockTypes ;
if ( isset ( $this -> BlockTypes [ $marker ]))
{
foreach ( $this -> BlockTypes [ $marker ] as $blockType )
{
$blockTypes [] = $blockType ;
}
}
#
# ~
foreach ( $blockTypes as $blockType )
{
$Block = $this -> { 'block' . $blockType }( $Line , $CurrentBlock );
if ( isset ( $Block ))
{
$Block [ 'type' ] = $blockType ;
if ( ! isset ( $Block [ 'identified' ]))
{
$Blocks [] = $CurrentBlock ;
$Block [ 'identified' ] = true ;
}
if ( method_exists ( $this , 'block' . $blockType . 'Continue' ))
{
$Block [ 'continuable' ] = true ;
}
$CurrentBlock = $Block ;
continue 2 ;
}
}
# ~
if ( isset ( $CurrentBlock ) and ! isset ( $CurrentBlock [ 'type' ]) and ! isset ( $CurrentBlock [ 'interrupted' ]))
{
$CurrentBlock [ 'element' ][ 'text' ] .= " \n " . $text ;
}
else
{
$Blocks [] = $CurrentBlock ;
$CurrentBlock = $this -> paragraph ( $Line );
$CurrentBlock [ 'identified' ] = true ;
}
}
# ~
if ( isset ( $CurrentBlock [ 'continuable' ]) and method_exists ( $this , 'block' . $CurrentBlock [ 'type' ] . 'Complete' ))
{
$CurrentBlock = $this -> { 'block' . $CurrentBlock [ 'type' ] . 'Complete' }( $CurrentBlock );
}
# ~
$Blocks [] = $CurrentBlock ;
unset ( $Blocks [ 0 ]);
# ~
$markup = '' ;
foreach ( $Blocks as $Block )
{
if ( isset ( $Block [ 'hidden' ]))
{
continue ;
}
$markup .= " \n " ;
$markup .= isset ( $Block [ 'markup' ]) ? $Block [ 'markup' ] : $this -> element ( $Block [ 'element' ]);
}
$markup .= " \n " ;
# ~
return $markup ;
}
#
# Code
protected function blockCode ( $Line , $Block = null )
{
if ( isset ( $Block ) and ! isset ( $Block [ 'type' ]) and ! isset ( $Block [ 'interrupted' ]))
{
return ;
}
if ( $Line [ 'indent' ] >= 4 )
{
$text = substr ( $Line [ 'body' ], 4 );
$Block = array (
'element' => array (
'name' => 'pre' ,
'handler' => 'element' ,
'text' => array (
'name' => 'code' ,
'text' => $text ,
),
),
);
return $Block ;
}
}
protected function blockCodeContinue ( $Line , $Block )
{
if ( $Line [ 'indent' ] >= 4 )
{
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'element' ][ 'text' ][ 'text' ] .= " \n " ;
unset ( $Block [ 'interrupted' ]);
}
$Block [ 'element' ][ 'text' ][ 'text' ] .= " \n " ;
$text = substr ( $Line [ 'body' ], 4 );
$Block [ 'element' ][ 'text' ][ 'text' ] .= $text ;
return $Block ;
}
}
protected function blockCodeComplete ( $Block )
{
$text = $Block [ 'element' ][ 'text' ][ 'text' ];
$text = htmlspecialchars ( $text , ENT_NOQUOTES , 'UTF-8' );
$Block [ 'element' ][ 'text' ][ 'text' ] = $text ;
return $Block ;
}
#
# Comment
protected function blockComment ( $Line )
{
if ( $this -> markupEscaped )
{
return ;
}
if ( isset ( $Line [ 'text' ][ 3 ]) and $Line [ 'text' ][ 3 ] === '-' and $Line [ 'text' ][ 2 ] === '-' and $Line [ 'text' ][ 1 ] === '!' )
{
$Block = array (
'markup' => $Line [ 'body' ],
);
if ( preg_match ( '/-->$/' , $Line [ 'text' ]))
{
$Block [ 'closed' ] = true ;
}
return $Block ;
}
}
protected function blockCommentContinue ( $Line , array $Block )
{
if ( isset ( $Block [ 'closed' ]))
{
return ;
}
$Block [ 'markup' ] .= " \n " . $Line [ 'body' ];
if ( preg_match ( '/-->$/' , $Line [ 'text' ]))
{
$Block [ 'closed' ] = true ;
}
return $Block ;
}
#
# Fenced Code
protected function blockFencedCode ( $Line )
{
if ( preg_match ( '/^[' . $Line [ 'text' ][ 0 ] . ']{3,}[ ]*([\w-]+)?[ ]*$/' , $Line [ 'text' ], $matches ))
{
$Element = array (
'name' => 'code' ,
'text' => '' ,
);
if ( isset ( $matches [ 1 ]))
{
$class = 'language-' . $matches [ 1 ];
$Element [ 'attributes' ] = array (
'class' => $class ,
);
}
$Block = array (
'char' => $Line [ 'text' ][ 0 ],
'element' => array (
'name' => 'pre' ,
'handler' => 'element' ,
'text' => $Element ,
),
);
return $Block ;
}
}
protected function blockFencedCodeContinue ( $Line , $Block )
{
if ( isset ( $Block [ 'complete' ]))
{
return ;
}
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'element' ][ 'text' ][ 'text' ] .= " \n " ;
unset ( $Block [ 'interrupted' ]);
}
if ( preg_match ( '/^' . $Block [ 'char' ] . '{3,}[ ]*$/' , $Line [ 'text' ]))
{
$Block [ 'element' ][ 'text' ][ 'text' ] = substr ( $Block [ 'element' ][ 'text' ][ 'text' ], 1 );
$Block [ 'complete' ] = true ;
return $Block ;
}
$Block [ 'element' ][ 'text' ][ 'text' ] .= " \n " . $Line [ 'body' ];;
return $Block ;
}
protected function blockFencedCodeComplete ( $Block )
{
$text = $Block [ 'element' ][ 'text' ][ 'text' ];
$text = htmlspecialchars ( $text , ENT_NOQUOTES , 'UTF-8' );
$Block [ 'element' ][ 'text' ][ 'text' ] = $text ;
return $Block ;
}
#
# Header
protected function blockHeader ( $Line )
{
if ( isset ( $Line [ 'text' ][ 1 ]))
{
$level = 1 ;
while ( isset ( $Line [ 'text' ][ $level ]) and $Line [ 'text' ][ $level ] === '#' )
{
$level ++ ;
}
if ( $level > 6 )
{
return ;
}
$text = trim ( $Line [ 'text' ], '# ' );
$Block = array (
'element' => array (
'name' => 'h' . min ( 6 , $level ),
'text' => $text ,
'handler' => 'line' ,
),
);
return $Block ;
}
}
#
# List
protected function blockList ( $Line )
{
list ( $name , $pattern ) = $Line [ 'text' ][ 0 ] <= '-' ? array ( 'ul' , '[*+-]' ) : array ( 'ol' , '[0-9]+[.]' );
if ( preg_match ( '/^(' . $pattern . '[ ]+)(.*)/' , $Line [ 'text' ], $matches ))
{
$Block = array (
'indent' => $Line [ 'indent' ],
'pattern' => $pattern ,
'element' => array (
'name' => $name ,
'handler' => 'elements' ,
),
);
$Block [ 'li' ] = array (
'name' => 'li' ,
'handler' => 'li' ,
'text' => array (
$matches [ 2 ],
),
);
$Block [ 'element' ][ 'text' ] [] = & $Block [ 'li' ];
return $Block ;
}
}
protected function blockListContinue ( $Line , array $Block )
{
if ( $Block [ 'indent' ] === $Line [ 'indent' ] and preg_match ( '/^' . $Block [ 'pattern' ] . '(?:[ ]+(.*)|$)/' , $Line [ 'text' ], $matches ))
{
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'li' ][ 'text' ] [] = '' ;
unset ( $Block [ 'interrupted' ]);
}
unset ( $Block [ 'li' ]);
$text = isset ( $matches [ 1 ]) ? $matches [ 1 ] : '' ;
$Block [ 'li' ] = array (
'name' => 'li' ,
'handler' => 'li' ,
'text' => array (
$text ,
),
);
$Block [ 'element' ][ 'text' ] [] = & $Block [ 'li' ];
return $Block ;
}
if ( $Line [ 'text' ][ 0 ] === '[' and $this -> blockReference ( $Line ))
{
return $Block ;
}
if ( ! isset ( $Block [ 'interrupted' ]))
{
$text = preg_replace ( '/^[ ]{0,4}/' , '' , $Line [ 'body' ]);
$Block [ 'li' ][ 'text' ] [] = $text ;
return $Block ;
}
if ( $Line [ 'indent' ] > 0 )
{
$Block [ 'li' ][ 'text' ] [] = '' ;
$text = preg_replace ( '/^[ ]{0,4}/' , '' , $Line [ 'body' ]);
$Block [ 'li' ][ 'text' ] [] = $text ;
unset ( $Block [ 'interrupted' ]);
return $Block ;
}
}
#
# Quote
protected function blockQuote ( $Line )
{
if ( preg_match ( '/^>[ ]?(.*)/' , $Line [ 'text' ], $matches ))
{
$Block = array (
'element' => array (
'name' => 'blockquote' ,
'handler' => 'lines' ,
'text' => ( array ) $matches [ 1 ],
),
);
return $Block ;
}
}
protected function blockQuoteContinue ( $Line , array $Block )
{
if ( $Line [ 'text' ][ 0 ] === '>' and preg_match ( '/^>[ ]?(.*)/' , $Line [ 'text' ], $matches ))
{
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'element' ][ 'text' ] [] = '' ;
unset ( $Block [ 'interrupted' ]);
}
$Block [ 'element' ][ 'text' ] [] = $matches [ 1 ];
return $Block ;
}
if ( ! isset ( $Block [ 'interrupted' ]))
{
$Block [ 'element' ][ 'text' ] [] = $Line [ 'text' ];
return $Block ;
}
}
#
# Rule
protected function blockRule ( $Line )
{
if ( preg_match ( '/^([' . $Line [ 'text' ][ 0 ] . '])([ ]*\1){2,}[ ]*$/' , $Line [ 'text' ]))
{
$Block = array (
'element' => array (
'name' => 'hr'
),
);
return $Block ;
}
}
#
# Setext
protected function blockSetextHeader ( $Line , array $Block = null )
{
if ( ! isset ( $Block ) or isset ( $Block [ 'type' ]) or isset ( $Block [ 'interrupted' ]))
{
return ;
}
if ( chop ( $Line [ 'text' ], $Line [ 'text' ][ 0 ]) === '' )
{
$Block [ 'element' ][ 'name' ] = $Line [ 'text' ][ 0 ] === '=' ? 'h1' : 'h2' ;
return $Block ;
}
}
#
# Markup
protected function blockMarkup ( $Line )
{
if ( $this -> markupEscaped )
{
return ;
}
if ( preg_match ( '/^<(\w*)(?:[ ]*' . $this -> regexHtmlAttribute . ')*[ ]*(\/)?>/' , $Line [ 'text' ], $matches ))
{
$element = strtolower ( $matches [ 1 ]);
if ( in_array ( $element , $this -> textLevelElements ))
{
return ;
}
$Block = array (
'name' => $matches [ 1 ],
'depth' => 0 ,
'markup' => $Line [ 'text' ],
);
$length = strlen ( $matches [ 0 ]);
$remainder = substr ( $Line [ 'text' ], $length );
if ( trim ( $remainder ) === '' )
{
if ( isset ( $matches [ 2 ]) or in_array ( $matches [ 1 ], $this -> voidElements ))
{
$Block [ 'closed' ] = true ;
$Block [ 'void' ] = true ;
}
}
else
{
if ( isset ( $matches [ 2 ]) or in_array ( $matches [ 1 ], $this -> voidElements ))
{
return ;
}
if ( preg_match ( '/<\/' . $matches [ 1 ] . '>[ ]*$/i' , $remainder ))
{
$Block [ 'closed' ] = true ;
}
}
return $Block ;
}
}
protected function blockMarkupContinue ( $Line , array $Block )
{
if ( isset ( $Block [ 'closed' ]))
{
return ;
}
if ( preg_match ( '/^<' . $Block [ 'name' ] . '(?:[ ]*' . $this -> regexHtmlAttribute . ')*[ ]*>/i' , $Line [ 'text' ])) # open
{
$Block [ 'depth' ] ++ ;
}
if ( preg_match ( '/(.*?)<\/' . $Block [ 'name' ] . '>[ ]*$/i' , $Line [ 'text' ], $matches )) # close
{
if ( $Block [ 'depth' ] > 0 )
{
$Block [ 'depth' ] -- ;
}
else
{
$Block [ 'closed' ] = true ;
}
}
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'markup' ] .= " \n " ;
unset ( $Block [ 'interrupted' ]);
}
$Block [ 'markup' ] .= " \n " . $Line [ 'body' ];
return $Block ;
}
#
# Reference
protected function blockReference ( $Line )
{
if ( preg_match ( '/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/' , $Line [ 'text' ], $matches ))
{
$id = strtolower ( $matches [ 1 ]);
$Data = array (
'url' => $matches [ 2 ],
'title' => null ,
);
if ( isset ( $matches [ 3 ]))
{
$Data [ 'title' ] = $matches [ 3 ];
}
$this -> DefinitionData [ 'Reference' ][ $id ] = $Data ;
$Block = array (
'hidden' => true ,
);
return $Block ;
}
}
#
# Table
protected function blockTable ( $Line , array $Block = null )
{
if ( ! isset ( $Block ) or isset ( $Block [ 'type' ]) or isset ( $Block [ 'interrupted' ]))
{
return ;
}
if ( strpos ( $Block [ 'element' ][ 'text' ], '|' ) !== false and chop ( $Line [ 'text' ], ' -:|' ) === '' )
{
$alignments = array ();
$divider = $Line [ 'text' ];
$divider = trim ( $divider );
$divider = trim ( $divider , '|' );
$dividerCells = explode ( '|' , $divider );
foreach ( $dividerCells as $dividerCell )
{
$dividerCell = trim ( $dividerCell );
if ( $dividerCell === '' )
{
continue ;
}
$alignment = null ;
if ( $dividerCell [ 0 ] === ':' )
{
$alignment = 'left' ;
}
if ( substr ( $dividerCell , - 1 ) === ':' )
{
$alignment = $alignment === 'left' ? 'center' : 'right' ;
}
$alignments [] = $alignment ;
}
# ~
$HeaderElements = array ();
$header = $Block [ 'element' ][ 'text' ];
$header = trim ( $header );
$header = trim ( $header , '|' );
$headerCells = explode ( '|' , $header );
foreach ( $headerCells as $index => $headerCell )
{
$headerCell = trim ( $headerCell );
$HeaderElement = array (
'name' => 'th' ,
'text' => $headerCell ,
'handler' => 'line' ,
);
if ( isset ( $alignments [ $index ]))
{
$alignment = $alignments [ $index ];
$HeaderElement [ 'attributes' ] = array (
'style' => 'text-align: ' . $alignment . ';' ,
);
}
$HeaderElements [] = $HeaderElement ;
}
# ~
$Block = array (
'alignments' => $alignments ,
'identified' => true ,
'element' => array (
'name' => 'table' ,
'handler' => 'elements' ,
),
);
$Block [ 'element' ][ 'text' ] [] = array (
'name' => 'thead' ,
'handler' => 'elements' ,
);
$Block [ 'element' ][ 'text' ] [] = array (
'name' => 'tbody' ,
'handler' => 'elements' ,
'text' => array (),
);
$Block [ 'element' ][ 'text' ][ 0 ][ 'text' ] [] = array (
'name' => 'tr' ,
'handler' => 'elements' ,
'text' => $HeaderElements ,
);
return $Block ;
}
}
protected function blockTableContinue ( $Line , array $Block )
{
if ( isset ( $Block [ 'interrupted' ]))
{
return ;
}
if ( $Line [ 'text' ][ 0 ] === '|' or strpos ( $Line [ 'text' ], '|' ))
{
$Elements = array ();
$row = $Line [ 'text' ];
$row = trim ( $row );
$row = trim ( $row , '|' );
preg_match_all ( '/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/' , $row , $matches );
foreach ( $matches [ 0 ] as $index => $cell )
{
$cell = trim ( $cell );
$Element = array (
'name' => 'td' ,
'handler' => 'line' ,
'text' => $cell ,
);
if ( isset ( $Block [ 'alignments' ][ $index ]))
{
$Element [ 'attributes' ] = array (
'style' => 'text-align: ' . $Block [ 'alignments' ][ $index ] . ';' ,
);
}
$Elements [] = $Element ;
}
$Element = array (
'name' => 'tr' ,
'handler' => 'elements' ,
'text' => $Elements ,
);
$Block [ 'element' ][ 'text' ][ 1 ][ 'text' ] [] = $Element ;
return $Block ;
}
}
#
# ~
#
protected function paragraph ( $Line )
{
$Block = array (
'element' => array (
'name' => 'p' ,
'text' => $Line [ 'text' ],
'handler' => 'line' ,
),
);
return $Block ;
}
#
# Inline Elements
#
protected $InlineTypes = array (
'"' => array ( 'SpecialCharacter' ),
'!' => array ( 'Image' ),
'&' => array ( 'SpecialCharacter' ),
'*' => array ( 'Emphasis' ),
':' => array ( 'Url' ),
'<' => array ( 'UrlTag' , 'EmailTag' , 'Markup' , 'SpecialCharacter' ),
'>' => array ( 'SpecialCharacter' ),
'[' => array ( 'Link' ),
'_' => array ( 'Emphasis' ),
'`' => array ( 'Code' ),
'~' => array ( 'Strikethrough' ),
'\\' => array ( 'EscapeSequence' ),
);
# ~
protected $inlineMarkerList = '!"*_&[:<>`~\\' ;
#
# ~
#
public function line ( $text )
{
$markup = '' ;
# $excerpt is based on the first occurrence of a marker
while ( $excerpt = strpbrk ( $text , $this -> inlineMarkerList ))
{
$marker = $excerpt [ 0 ];
$markerPosition = strpos ( $text , $marker );
$Excerpt = array ( 'text' => $excerpt , 'context' => $text );
foreach ( $this -> InlineTypes [ $marker ] as $inlineType )
{
$Inline = $this -> { 'inline' . $inlineType }( $Excerpt );
if ( ! isset ( $Inline ))
{
continue ;
}
# makes sure that the inline belongs to "our" marker
if ( isset ( $Inline [ 'position' ]) and $Inline [ 'position' ] > $markerPosition )
{
continue ;
}
# sets a default inline position
if ( ! isset ( $Inline [ 'position' ]))
{
$Inline [ 'position' ] = $markerPosition ;
}
# the text that comes before the inline
$unmarkedText = substr ( $text , 0 , $Inline [ 'position' ]);
# compile the unmarked text
$markup .= $this -> unmarkedText ( $unmarkedText );
# compile the inline
$markup .= isset ( $Inline [ 'markup' ]) ? $Inline [ 'markup' ] : $this -> element ( $Inline [ 'element' ]);
# remove the examined text
$text = substr ( $text , $Inline [ 'position' ] + $Inline [ 'extent' ]);
continue 2 ;
}
# the marker does not belong to an inline
$unmarkedText = substr ( $text , 0 , $markerPosition + 1 );
$markup .= $this -> unmarkedText ( $unmarkedText );
$text = substr ( $text , $markerPosition + 1 );
}
$markup .= $this -> unmarkedText ( $text );
return $markup ;
}
#
# ~
#
protected function inlineCode ( $Excerpt )
{
$marker = $Excerpt [ 'text' ][ 0 ];
if ( preg_match ( '/^(' . $marker . '+)[ ]*(.+?)[ ]*(?<!' . $marker . ')\1(?!' . $marker . ')/s' , $Excerpt [ 'text' ], $matches ))
{
$text = $matches [ 2 ];
$text = htmlspecialchars ( $text , ENT_NOQUOTES , 'UTF-8' );
$text = preg_replace ( " /[ ]* \n / " , ' ' , $text );
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => array (
'name' => 'code' ,
'text' => $text ,
),
);
}
}
protected function inlineEmailTag ( $Excerpt )
{
if ( strpos ( $Excerpt [ 'text' ], '>' ) !== false and preg_match ( '/^<((mailto:)?\S+?@\S+?)>/i' , $Excerpt [ 'text' ], $matches ))
{
$url = $matches [ 1 ];
if ( ! isset ( $matches [ 2 ]))
{
$url = 'mailto:' . $url ;
}
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => array (
'name' => 'a' ,
'text' => $matches [ 1 ],
'attributes' => array (
'href' => $url ,
),
),
);
}
}
protected function inlineEmphasis ( $Excerpt )
{
if ( ! isset ( $Excerpt [ 'text' ][ 1 ]))
{
return ;
}
$marker = $Excerpt [ 'text' ][ 0 ];
if ( $Excerpt [ 'text' ][ 1 ] === $marker and preg_match ( $this -> StrongRegex [ $marker ], $Excerpt [ 'text' ], $matches ))
{
$emphasis = 'strong' ;
}
elseif ( preg_match ( $this -> EmRegex [ $marker ], $Excerpt [ 'text' ], $matches ))
{
$emphasis = 'em' ;
}
else
{
return ;
}
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => array (
'name' => $emphasis ,
'handler' => 'line' ,
'text' => $matches [ 1 ],
),
);
}
protected function inlineEscapeSequence ( $Excerpt )
{
if ( isset ( $Excerpt [ 'text' ][ 1 ]) and in_array ( $Excerpt [ 'text' ][ 1 ], $this -> specialCharacters ))
{
return array (
'markup' => $Excerpt [ 'text' ][ 1 ],
'extent' => 2 ,
);
}
}
protected function inlineImage ( $Excerpt )
{
if ( ! isset ( $Excerpt [ 'text' ][ 1 ]) or $Excerpt [ 'text' ][ 1 ] !== '[' )
{
return ;
}
$Excerpt [ 'text' ] = substr ( $Excerpt [ 'text' ], 1 );
$Link = $this -> inlineLink ( $Excerpt );
if ( $Link === null )
{
return ;
}
$Inline = array (
'extent' => $Link [ 'extent' ] + 1 ,
'element' => array (
'name' => 'img' ,
'attributes' => array (
'src' => $Link [ 'element' ][ 'attributes' ][ 'href' ],
'alt' => $Link [ 'element' ][ 'text' ],
),
),
);
$Inline [ 'element' ][ 'attributes' ] += $Link [ 'element' ][ 'attributes' ];
unset ( $Inline [ 'element' ][ 'attributes' ][ 'href' ]);
return $Inline ;
}
protected function inlineLink ( $Excerpt )
{
$Element = array (
'name' => 'a' ,
'handler' => 'line' ,
'text' => null ,
'attributes' => array (
'href' => null ,
'title' => null ,
),
);
$extent = 0 ;
$remainder = $Excerpt [ 'text' ];
if ( preg_match ( '/\[((?:[^][]|(?R))*)\]/' , $remainder , $matches ))
{
$Element [ 'text' ] = $matches [ 1 ];
$extent += strlen ( $matches [ 0 ]);
$remainder = substr ( $remainder , $extent );
}
else
{
return ;
}
if ( preg_match ( '/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/' , $remainder , $matches ))
{
$Element [ 'attributes' ][ 'href' ] = $matches [ 1 ];
if ( isset ( $matches [ 2 ]))
{
$Element [ 'attributes' ][ 'title' ] = substr ( $matches [ 2 ], 1 , - 1 );
}
$extent += strlen ( $matches [ 0 ]);
}
else
{
if ( preg_match ( '/^\s*\[(.*?)\]/' , $remainder , $matches ))
{
$definition = strlen ( $matches [ 1 ]) ? $matches [ 1 ] : $Element [ 'text' ];
$definition = strtolower ( $definition );
$extent += strlen ( $matches [ 0 ]);
}
else
{
$definition = strtolower ( $Element [ 'text' ]);
}
if ( ! isset ( $this -> DefinitionData [ 'Reference' ][ $definition ]))
{
return ;
}
$Definition = $this -> DefinitionData [ 'Reference' ][ $definition ];
$Element [ 'attributes' ][ 'href' ] = $Definition [ 'url' ];
$Element [ 'attributes' ][ 'title' ] = $Definition [ 'title' ];
}
$Element [ 'attributes' ][ 'href' ] = str_replace ( array ( '&' , '<' ), array ( '&' , '<' ), $Element [ 'attributes' ][ 'href' ]);
return array (
'extent' => $extent ,
'element' => $Element ,
);
}
protected function inlineMarkup ( $Excerpt )
{
if ( $this -> markupEscaped or strpos ( $Excerpt [ 'text' ], '>' ) === false )
{
return ;
}
if ( $Excerpt [ 'text' ][ 1 ] === '/' and preg_match ( '/^<\/\w*[ ]*>/s' , $Excerpt [ 'text' ], $matches ))
{
return array (
'markup' => $matches [ 0 ],
'extent' => strlen ( $matches [ 0 ]),
);
}
if ( $Excerpt [ 'text' ][ 1 ] === '!' and preg_match ( '/^<!---?[^>-](?:-?[^-])*-->/s' , $Excerpt [ 'text' ], $matches ))
{
return array (
'markup' => $matches [ 0 ],
'extent' => strlen ( $matches [ 0 ]),
);
}
if ( $Excerpt [ 'text' ][ 1 ] !== ' ' and preg_match ( '/^<\w*(?:[ ]*' . $this -> regexHtmlAttribute . ')*[ ]*\/?>/s' , $Excerpt [ 'text' ], $matches ))
{
return array (
'markup' => $matches [ 0 ],
'extent' => strlen ( $matches [ 0 ]),
);
}
}
protected function inlineSpecialCharacter ( $Excerpt )
{
if ( $Excerpt [ 'text' ][ 0 ] === '&' and ! preg_match ( '/^&#?\w+;/' , $Excerpt [ 'text' ]))
{
return array (
'markup' => '&' ,
'extent' => 1 ,
);
}
$SpecialCharacter = array ( '>' => 'gt' , '<' => 'lt' , '"' => 'quot' );
if ( isset ( $SpecialCharacter [ $Excerpt [ 'text' ][ 0 ]]))
{
return array (
'markup' => '&' . $SpecialCharacter [ $Excerpt [ 'text' ][ 0 ]] . ';' ,
'extent' => 1 ,
);
}
}
protected function inlineStrikethrough ( $Excerpt )
{
if ( ! isset ( $Excerpt [ 'text' ][ 1 ]))
{
return ;
}
if ( $Excerpt [ 'text' ][ 1 ] === '~' and preg_match ( '/^~~(?=\S)(.+?)(?<=\S)~~/' , $Excerpt [ 'text' ], $matches ))
{
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => array (
'name' => 'del' ,
'text' => $matches [ 1 ],
'handler' => 'line' ,
),
);
}
}
protected function inlineUrl ( $Excerpt )
{
if ( $this -> urlsLinked !== true or ! isset ( $Excerpt [ 'text' ][ 2 ]) or $Excerpt [ 'text' ][ 2 ] !== '/' )
{
return ;
}
if ( preg_match ( '/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui' , $Excerpt [ 'context' ], $matches , PREG_OFFSET_CAPTURE ))
{
$Inline = array (
'extent' => strlen ( $matches [ 0 ][ 0 ]),
'position' => $matches [ 0 ][ 1 ],
'element' => array (
'name' => 'a' ,
'text' => $matches [ 0 ][ 0 ],
'attributes' => array (
'href' => $matches [ 0 ][ 0 ],
),
),
);
return $Inline ;
}
}
protected function inlineUrlTag ( $Excerpt )
{
if ( strpos ( $Excerpt [ 'text' ], '>' ) !== false and preg_match ( '/^<(\w+:\/{2}[^ >]+)>/i' , $Excerpt [ 'text' ], $matches ))
{
$url = str_replace ( array ( '&' , '<' ), array ( '&' , '<' ), $matches [ 1 ]);
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => array (
'name' => 'a' ,
'text' => $url ,
'attributes' => array (
'href' => $url ,
),
),
);
}
}
# ~
protected function unmarkedText ( $text )
{
if ( $this -> breaksEnabled )
{
$text = preg_replace ( '/[ ]*\n/' , " <br /> \n " , $text );
}
else
{
$text = preg_replace ( '/(?:[ ][ ]+|[ ]*\\\\)\n/' , " <br /> \n " , $text );
$text = str_replace ( " \n " , " \n " , $text );
}
return $text ;
}
#
# Handlers
#
protected function element ( array $Element )
{
$markup = '<' . $Element [ 'name' ];
if ( isset ( $Element [ 'attributes' ]))
{
foreach ( $Element [ 'attributes' ] as $name => $value )
{
if ( $value === null )
{
continue ;
}
$markup .= ' ' . $name . '="' . $value . '"' ;
}
}
if ( isset ( $Element [ 'text' ]))
{
$markup .= '>' ;
if ( isset ( $Element [ 'handler' ]))
{
$markup .= $this -> { $Element [ 'handler' ]}( $Element [ 'text' ]);
}
else
{
$markup .= $Element [ 'text' ];
}
$markup .= '</' . $Element [ 'name' ] . '>' ;
}
else
{
$markup .= ' />' ;
}
return $markup ;
}
protected function elements ( array $Elements )
{
$markup = '' ;
foreach ( $Elements as $Element )
{
$markup .= " \n " . $this -> element ( $Element );
}
$markup .= " \n " ;
return $markup ;
}
# ~
protected function li ( $lines )
{
$markup = $this -> lines ( $lines );
$trimmedMarkup = trim ( $markup );
if ( ! in_array ( '' , $lines ) and substr ( $trimmedMarkup , 0 , 3 ) === '<p>' )
{
$markup = $trimmedMarkup ;
$markup = substr ( $markup , 3 );
$position = strpos ( $markup , " </p> " );
$markup = substr_replace ( $markup , '' , $position , 4 );
}
return $markup ;
}
#
# Deprecated Methods
#
function parse ( $text )
{
$markup = $this -> text ( $text );
return $markup ;
}
#
# Static Methods
#
static function instance ( $name = 'default' )
{
if ( isset ( self :: $instances [ $name ]))
{
return self :: $instances [ $name ];
}
$instance = new static ();
self :: $instances [ $name ] = $instance ;
return $instance ;
}
private static $instances = array ();
#
# Fields
#
protected $DefinitionData ;
#
# Read-Only
protected $specialCharacters = array (
'\\' , '`' , '*' , '_' , '{' , '}' , '[' , ']' , '(' , ')' , '>' , '#' , '+' , '-' , '.' , '!' , '|' ,
);
protected $StrongRegex = array (
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s' ,
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us' ,
);
protected $EmRegex = array (
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s' ,
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us' ,
);
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?' ;
protected $voidElements = array (
'area' , 'base' , 'br' , 'col' , 'command' , 'embed' , 'hr' , 'img' , 'input' , 'link' , 'meta' , 'param' , 'source' ,
);
protected $textLevelElements = array (
'a' , 'br' , 'bdo' , 'abbr' , 'blink' , 'nextid' , 'acronym' , 'basefont' ,
'b' , 'em' , 'big' , 'cite' , 'small' , 'spacer' , 'listing' ,
'i' , 'rp' , 'del' , 'code' , 'strike' , 'marquee' ,
'q' , 'rt' , 'ins' , 'font' , 'strong' ,
's' , 'tt' , 'sub' , 'mark' ,
'u' , 'xm' , 'sup' , 'nobr' ,
'var' , 'ruby' ,
'wbr' , 'span' ,
'time' ,
);
}
#
#
# Parsedown Extra
# https://github.com/erusev/parsedown-extra
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class ParsedownExtra extends Parsedown
{
# ~
const version = '0.7.0' ;
# ~
function __construct ()
{
if ( parent :: version < '1.5.0' )
{
throw new Exception ( 'ParsedownExtra requires a later version of Parsedown' );
}
$this -> BlockTypes [ ':' ] [] = 'DefinitionList' ;
$this -> BlockTypes [ '*' ] [] = 'Abbreviation' ;
# identify footnote definitions before reference definitions
array_unshift ( $this -> BlockTypes [ '[' ], 'Footnote' );
# identify footnote markers before before links
array_unshift ( $this -> InlineTypes [ '[' ], 'FootnoteMarker' );
}
#
# ~
function text ( $text )
{
$markup = parent :: text ( $text );
# merge consecutive dl elements
$markup = preg_replace ( '/<\/dl>\s+<dl>\s+/' , '' , $markup );
# add footnotes
if ( isset ( $this -> DefinitionData [ 'Footnote' ]))
{
$Element = $this -> buildFootnoteElement ();
$markup .= " \n " . $this -> element ( $Element );
}
return $markup ;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation ( $Line )
{
if ( preg_match ( '/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/' , $Line [ 'text' ], $matches ))
{
$this -> DefinitionData [ 'Abbreviation' ][ $matches [ 1 ]] = $matches [ 2 ];
$Block = array (
'hidden' => true ,
);
return $Block ;
}
}
#
# Footnote
protected function blockFootnote ( $Line )
{
if ( preg_match ( '/^\[\^(.+?)\]:[ ]?(.*)$/' , $Line [ 'text' ], $matches ))
{
$Block = array (
'label' => $matches [ 1 ],
'text' => $matches [ 2 ],
'hidden' => true ,
);
return $Block ;
}
}
protected function blockFootnoteContinue ( $Line , $Block )
{
if ( $Line [ 'text' ][ 0 ] === '[' and preg_match ( '/^\[\^(.+?)\]:/' , $Line [ 'text' ]))
{
return ;
}
if ( isset ( $Block [ 'interrupted' ]))
{
if ( $Line [ 'indent' ] >= 4 )
{
$Block [ 'text' ] .= " \n \n " . $Line [ 'text' ];
return $Block ;
}
}
else
{
$Block [ 'text' ] .= " \n " . $Line [ 'text' ];
return $Block ;
}
}
protected function blockFootnoteComplete ( $Block )
{
$this -> DefinitionData [ 'Footnote' ][ $Block [ 'label' ]] = array (
'text' => $Block [ 'text' ],
'count' => null ,
'number' => null ,
);
return $Block ;
}
#
# Definition List
protected function blockDefinitionList ( $Line , $Block )
{
if ( ! isset ( $Block ) or isset ( $Block [ 'type' ]))
{
return ;
}
$Element = array (
'name' => 'dl' ,
'handler' => 'elements' ,
'text' => array (),
);
$terms = explode ( " \n " , $Block [ 'element' ][ 'text' ]);
foreach ( $terms as $term )
{
$Element [ 'text' ] [] = array (
'name' => 'dt' ,
'handler' => 'line' ,
'text' => $term ,
);
}
$Block [ 'element' ] = $Element ;
$Block = $this -> addDdElement ( $Line , $Block );
return $Block ;
}
protected function blockDefinitionListContinue ( $Line , array $Block )
{
if ( $Line [ 'text' ][ 0 ] === ':' )
{
$Block = $this -> addDdElement ( $Line , $Block );
return $Block ;
}
else
{
if ( isset ( $Block [ 'interrupted' ]) and $Line [ 'indent' ] === 0 )
{
return ;
}
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'dd' ][ 'handler' ] = 'text' ;
$Block [ 'dd' ][ 'text' ] .= " \n \n " ;
unset ( $Block [ 'interrupted' ]);
}
$text = substr ( $Line [ 'body' ], min ( $Line [ 'indent' ], 4 ));
$Block [ 'dd' ][ 'text' ] .= " \n " . $text ;
return $Block ;
}
}
#
# Header
protected function blockHeader ( $Line )
{
$Block = parent :: blockHeader ( $Line );
if ( preg_match ( '/[ #]*{(' . $this -> regexAttribute . '+)}[ ]*$/' , $Block [ 'element' ][ 'text' ], $matches , PREG_OFFSET_CAPTURE ))
{
$attributeString = $matches [ 1 ][ 0 ];
$Block [ 'element' ][ 'attributes' ] = $this -> parseAttributeData ( $attributeString );
$Block [ 'element' ][ 'text' ] = substr ( $Block [ 'element' ][ 'text' ], 0 , $matches [ 0 ][ 1 ]);
}
return $Block ;
}
#
# Markup
protected function blockMarkupComplete ( $Block )
{
if ( ! isset ( $Block [ 'void' ]))
{
$Block [ 'markup' ] = $this -> processTag ( $Block [ 'markup' ]);
}
return $Block ;
}
#
# Setext
protected function blockSetextHeader ( $Line , array $Block = null )
{
$Block = parent :: blockSetextHeader ( $Line , $Block );
if ( preg_match ( '/[ ]*{(' . $this -> regexAttribute . '+)}[ ]*$/' , $Block [ 'element' ][ 'text' ], $matches , PREG_OFFSET_CAPTURE ))
{
$attributeString = $matches [ 1 ][ 0 ];
$Block [ 'element' ][ 'attributes' ] = $this -> parseAttributeData ( $attributeString );
$Block [ 'element' ][ 'text' ] = substr ( $Block [ 'element' ][ 'text' ], 0 , $matches [ 0 ][ 1 ]);
}
return $Block ;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker ( $Excerpt )
{
if ( preg_match ( '/^\[\^(.+?)\]/' , $Excerpt [ 'text' ], $matches ))
{
$name = $matches [ 1 ];
if ( ! isset ( $this -> DefinitionData [ 'Footnote' ][ $name ]))
{
return ;
}
$this -> DefinitionData [ 'Footnote' ][ $name ][ 'count' ] ++ ;
if ( ! isset ( $this -> DefinitionData [ 'Footnote' ][ $name ][ 'number' ]))
{
$this -> DefinitionData [ 'Footnote' ][ $name ][ 'number' ] = ++ $this -> footnoteCount ; # » &
}
$Element = array (
'name' => 'sup' ,
'attributes' => array ( 'id' => 'fnref' . $this -> DefinitionData [ 'Footnote' ][ $name ][ 'count' ] . ':' . $name ),
'handler' => 'element' ,
'text' => array (
'name' => 'a' ,
'attributes' => array ( 'href' => '#fn:' . $name , 'class' => 'footnote-ref' ),
'text' => $this -> DefinitionData [ 'Footnote' ][ $name ][ 'number' ],
),
);
return array (
'extent' => strlen ( $matches [ 0 ]),
'element' => $Element ,
);
}
}
private $footnoteCount = 0 ;
#
# Link
protected function inlineLink ( $Excerpt )
{
$Link = parent :: inlineLink ( $Excerpt );
$remainder = substr ( $Excerpt [ 'text' ], $Link [ 'extent' ]);
if ( preg_match ( '/^[ ]*{(' . $this -> regexAttribute . '+)}/' , $remainder , $matches ))
{
$Link [ 'element' ][ 'attributes' ] += $this -> parseAttributeData ( $matches [ 1 ]);
$Link [ 'extent' ] += strlen ( $matches [ 0 ]);
}
return $Link ;
}
#
# ~
#
protected function unmarkedText ( $text )
{
$text = parent :: unmarkedText ( $text );
if ( isset ( $this -> DefinitionData [ 'Abbreviation' ]))
{
foreach ( $this -> DefinitionData [ 'Abbreviation' ] as $abbreviation => $meaning )
{
$pattern = '/\b' . preg_quote ( $abbreviation , '/' ) . '\b/' ;
$text = preg_replace ( $pattern , '<abbr title="' . $meaning . '">' . $abbreviation . '</abbr>' , $text );
}
}
return $text ;
}
#
# Util Methods
#
protected function addDdElement ( array $Line , array $Block )
{
$text = substr ( $Line [ 'text' ], 1 );
$text = trim ( $text );
unset ( $Block [ 'dd' ]);
$Block [ 'dd' ] = array (
'name' => 'dd' ,
'handler' => 'line' ,
'text' => $text ,
);
if ( isset ( $Block [ 'interrupted' ]))
{
$Block [ 'dd' ][ 'handler' ] = 'text' ;
unset ( $Block [ 'interrupted' ]);
}
$Block [ 'element' ][ 'text' ] [] = & $Block [ 'dd' ];
return $Block ;
}
protected function buildFootnoteElement ()
{
$Element = array (
'name' => 'div' ,
'attributes' => array ( 'class' => 'footnotes' ),
'handler' => 'elements' ,
'text' => array (
array (
'name' => 'hr' ,
),
array (
'name' => 'ol' ,
'handler' => 'elements' ,
'text' => array (),
),
),
);
uasort ( $this -> DefinitionData [ 'Footnote' ], 'self::sortFootnotes' );
foreach ( $this -> DefinitionData [ 'Footnote' ] as $definitionId => $DefinitionData )
{
if ( ! isset ( $DefinitionData [ 'number' ]))
{
continue ;
}
$text = $DefinitionData [ 'text' ];
$text = parent :: text ( $text );
$numbers = range ( 1 , $DefinitionData [ 'count' ]);
$backLinksMarkup = '' ;
foreach ( $numbers as $number )
{
$backLinksMarkup .= ' <a href="#fnref' . $number . ':' . $definitionId . '" rev="footnote" class="footnote-backref">↩</a>' ;
}
$backLinksMarkup = substr ( $backLinksMarkup , 1 );
if ( substr ( $text , - 4 ) === '</p>' )
{
$backLinksMarkup = ' ' . $backLinksMarkup ;
$text = substr_replace ( $text , $backLinksMarkup . '</p>' , - 4 );
}
else
{
$text .= " \n " . '<p>' . $backLinksMarkup . '</p>' ;
}
$Element [ 'text' ][ 1 ][ 'text' ] [] = array (
'name' => 'li' ,
'attributes' => array ( 'id' => 'fn:' . $definitionId ),
'text' => " \n " . $text . " \n " ,
);
}
return $Element ;
}
# ~
protected function parseAttributeData ( $attributeString )
{
$Data = array ();
$attributes = preg_split ( '/[ ]+/' , $attributeString , - 1 , PREG_SPLIT_NO_EMPTY );
foreach ( $attributes as $attribute )
{
if ( $attribute [ 0 ] === '#' )
{
$Data [ 'id' ] = substr ( $attribute , 1 );
}
else # "."
{
$classes [] = substr ( $attribute , 1 );
}
}
if ( isset ( $classes ))
{
$Data [ 'class' ] = implode ( ' ' , $classes );
}
return $Data ;
}
# ~
protected function processTag ( $elementMarkup ) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors ( true );
$DOMDocument = new DOMDocument ;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding ( $elementMarkup , 'HTML-ENTITIES' , 'UTF-8' );
# http://stackoverflow.com/q/4879946/200145
$DOMDocument -> loadHTML ( $elementMarkup );
$DOMDocument -> removeChild ( $DOMDocument -> doctype );
$DOMDocument -> replaceChild ( $DOMDocument -> firstChild -> firstChild -> firstChild , $DOMDocument -> firstChild );
$elementText = '' ;
if ( $DOMDocument -> documentElement -> getAttribute ( 'markdown' ) === '1' )
{
foreach ( $DOMDocument -> documentElement -> childNodes as $Node )
{
$elementText .= $DOMDocument -> saveHTML ( $Node );
}
$DOMDocument -> documentElement -> removeAttribute ( 'markdown' );
$elementText = " \n " . $this -> text ( $elementText ) . " \n " ;
}
else
{
foreach ( $DOMDocument -> documentElement -> childNodes as $Node )
{
$nodeMarkup = $DOMDocument -> saveHTML ( $Node );
if ( $Node instanceof DOMElement and ! in_array ( $Node -> nodeName , $this -> textLevelElements ))
{
$elementText .= $this -> processTag ( $nodeMarkup );
}
else
{
$elementText .= $nodeMarkup ;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument -> documentElement -> nodeValue = 'placeholder' ;
$markup = $DOMDocument -> saveHTML ( $DOMDocument -> documentElement );
$markup = str_replace ( 'placeholder' , $elementText , $markup );
return $markup ;
}
# ~
protected function sortFootnotes ( $A , $B ) # callback
{
return $A [ 'number' ] - $B [ 'number' ];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)' ;
}
?>