wet_textfilter_markdown

Created Diff never expires
59 removals
Words removed125
Total words2016
Words removed (%)6.20
993 lines
64 additions
Words added105
Total words1996
Words added (%)5.26
995 lines
<?php
<?php


namespace {
$plugin['version'] = '0.8';
$plugin['author'] = 'Robert Wetzlmayr';
$plugin['author_uri'] = 'http://wetzlmayr.com/';
$plugin['description'] = 'Markdown Extra textfilter';
$plugin['type'] = 4;

if (!defined('txpinterface'))
include_once('zem_tpl.php');

if (0) {
?>
# --- BEGIN PLUGIN HELP ---

h2. Markdown Extra textfilter for Textpattern 4.6+

Textfilters transform text entered into the article's bodies and excerpts into the final HTML.

Textpattern supports Textile and nl2br out of the box. This plugin extends Textpattern's capabilities by adding Markdown Extra to the filter set.

# --- END PLUGIN HELP ---
<?php
}
}

# --- BEGIN PLUGIN CODE ---

namespace Wet\Textfilter {
namespace Wet\Textfilter {


use erusev\ParsedownExtra;
use erusev\ParsedownExtra;


class Markdown extends \Textpattern\Textfilter\Base implements \Textpattern\Textfilter\TextfilterInterface
class Markdown extends \Textpattern\Textfilter\Base implements \Textpattern\Textfilter\TextfilterInterface
{
{
public $version;

function __construct()
function __construct()
{
{
global $plugin, $plugins_ver, $txp_current_plugin;
global $plugin, $plugins_ver, $txp_current_plugin;


parent::__construct('wet_markdown', gTxt('Markdown Extra'));
parent::__construct('wet_markdown', gTxt('Markdown Extra'));
$this->version = (isset($plugin)) ? $plugin['version'] : $plugins_ver[$txp_current_plugin];
$this->version = (isset($plugin)) ? $plugin['version'] : $plugins_ver[$txp_current_plugin];
}
}


function filter($thing, $options)
function filter($thing, $options)
{
{
parent::filter($thing, $options);
parent::filter($thing, $options);


$Parsedown = new ParsedownExtra();
$Parsedown = new ParsedownExtra();
return $Parsedown->text($thing);
return $Parsedown->text($thing);
}
}


function getHelp()
function getHelp()
{
{
return
return
n . '<ul class="plain-list">' .
n . '<ul class="plain-list">' .
n . '<li><a href="http://daringfireball.net/projects/markdown/basics">Markdown Basics</a></li>' .
n . '<li><a href="http://daringfireball.net/projects/markdown/basics">Markdown Basics</a></li>' .
n . '<li><a href="http://daringfireball.net/projects/markdown/syntax">Markdown Syntax</a></li>' .
n . '<li><a href="http://daringfireball.net/projects/markdown/syntax">Markdown Syntax</a></li>' .
n . '<li><a href="http://michelf.ca/projects/php-markdown/extra/">Markdown Extra</a></li>' .
n . '<li><a href="http://michelf.ca/projects/php-markdown/extra/">Markdown Extra</a></li>' .
n . '</ul>';
n . '</ul>';
}
}
}
}
}
}


namespace {
namespace {
if (txpinterface == 'admin') {
if (txpinterface == 'admin') {
new \Wet\Textfilter\Markdown();
new \Wet\Textfilter\Markdown();
}
}
}
}


namespace erusev {
namespace erusev {


use \DOMDocument;
use \DOMDocument;


if (!class_exists(__NAMESPACE__.'\Parsedown')) {
if (!class_exists(__NAMESPACE__.'\Parsedown')) {


#
#
#
#
# Parsedown
# Parsedown
# http://parsedown.org
# http://parsedown.org
#
#
# (c) Emanuil Rusev
# (c) Emanuil Rusev
# http://erusev.com
# http://erusev.com
#
#
# For the full license information, view the LICENSE file that was distributed
# For the full license information, view the LICENSE file that was distributed
# with this source code.
# with this source code.
#
#
#
#


class Parsedown
class Parsedown
{
{
# ~
# ~


const version = '1.8.0-beta-7';
public const version = '1.8.0-beta-7';


# ~
# ~
# Explicitly define properties to avoid dynamic property creation
protected $breaksEnabled;
protected $markupEscaped;
protected $urlsLinked = true;
protected $safeMode;
protected $strictMode;
protected $DefinitionData = [];


function text($text)
function text($text)
{
{
$Elements = $this->textElements($text);
$Elements = $this->textElements($text);


# convert to markup
# convert to markup
$markup = $this->elements($Elements);
$markup = $this->elements($Elements);


# trim line breaks
# trim line breaks
$markup = trim($markup, "\n");
$markup = trim($markup, "\n");


return $markup;
return $markup;
}
}


protected function textElements($text)
protected function textElements($text)
{
{
# make sure no definitions are set
# make sure no definitions are set
$this->DefinitionData = array();
$this->DefinitionData = array();


# standardize line breaks
# standardize line breaks
$text = str_replace(array("\r\n", "\r"), "\n", $text);
$text = str_replace(array("\r\n", "\r"), "\n", $text);


# remove surrounding line breaks
# remove surrounding line breaks
$text = trim($text, "\n");
$text = trim($text, "\n");


# split text into lines
# split text into lines
$lines = explode("\n", $text);
$lines = explode("\n", $text);


# iterate through lines to identify blocks
# iterate through lines to identify blocks
return $this->linesElements($lines);
return $this->linesElements($lines);
}
}


#
#
# Setters
# Setters
#
#


function setBreaksEnabled($breaksEnabled)
function setBreaksEnabled($breaksEnabled)
{
{
$this->breaksEnabled = $breaksEnabled;
$this->breaksEnabled = $breaksEnabled;


return $this;
return $this;
}
}


protected $breaksEnabled;

function setMarkupEscaped($markupEscaped)
function setMarkupEscaped($markupEscaped)
{
{
$this->markupEscaped = $markupEscaped;
$this->markupEscaped = $markupEscaped;


return $this;
return $this;
}
}


protected $markupEscaped;

function setUrlsLinked($urlsLinked)
function setUrlsLinked($urlsLinked)
{
{
$this->urlsLinked = $urlsLinked;
$this->urlsLinked = $urlsLinked;


return $this;
return $this;
}
}


protected $urlsLinked = true;

function setSafeMode($safeMode)
function setSafeMode($safeMode)
{
{
$this->safeMode = (bool) $safeMode;
$this->safeMode = (bool) $safeMode;


return $this;
return $this;
}
}

protected $safeMode;


function setStrictMode($strictMode)
function setStrictMode($strictMode)
{
{
$this->strictMode = (bool) $strictMode;
$this->strictMode = (bool) $strictMode;


return $this;
return $this;
}
}

protected $strictMode;


protected $safeLinksWhitelist = array(
protected $safeLinksWhitelist = array(
'http://',
'http://',
'https://',
'https://',
'ftp://',
'ftp://',
'ftps://',
'ftps://',
'mailto:',
'mailto:',
'tel:',
'tel:',
'data:image/png;base64,',
'data:image/png;base64,',
'data:image/gif;base64,',
'data:image/gif;base64,',
'data:image/jpeg;base64,',
'data:image/jpeg;base64,',
'irc:',
'irc:',
'ircs:',
'ircs:',
'git:',
'git:',
'ssh:',
'ssh:',
'news:',
'news:',
'steam:',
'steam:',
);
);


#
#
# Lines
# Lines
#
#


protected $BlockTypes = array(
protected $BlockTypes = array(
'#' => array('Header'),
'#' => array('Header'),
'*' => array('Rule', 'List'),
'*' => array('Rule', 'List'),
'+' => array('List'),
'+' => array('List'),
'-' => array('SetextHeader', 'Table', 'Rule', 'List'),
'-' => array('SetextHeader', 'Table', 'Rule', 'List'),
'0' => array('List'),
'0' => array('List'),
'1' => array('List'),
'1' => array('List'),
'2' => array('List'),
'2' => array('List'),
'3' => array('List'),
'3' => array('List'),
'4' => array('List'),
'4' => array('List'),
'5' => array('List'),
'5' => array('List'),
'6' => array('List'),
'6' => array('List'),
'7' => array('List'),
'7' => array('List'),
'8' => array('List'),
'8' => array('List'),
'9' => array('List'),
'9' => array('List'),
':' => array('Table'),
':' => array('Table'),
'<' => array('Comment', 'Markup'),
'<' => array('Comment', 'Markup'),
'=' => array('SetextHeader'),
'=' => array('SetextHeader'),
'>' => array('Quote'),
'>' => array('Quote'),
'[' => array('Reference'),
'[' => array('Reference'),
'_' => array('Rule'),
'_' => array('Rule'),
'`' => array('FencedCode'),
'`' => array('FencedCode'),
'|' => array('Table'),
'|' => array('Table'),
'~' => array('FencedCode'),
'~' => array('FencedCode'),
);
);


# ~
# ~


protected $unmarkedBlockTypes = array(
protected $unmarkedBlockTypes = array(
'Code',
'Code',
);
);


#
#
# Blocks
# Blocks
#
#


protected function lines(array $lines)
protected function lines(array $lines)
{
{
return $this->elements($this->linesElements($lines));
return $this->elements($this->linesElements($lines));
}
}


protected function linesElements(array $lines)
protected function linesElements(array $lines)
{
{
$Elements = array();
$Elements = array();
$CurrentBlock = null;
$CurrentBlock = null;


foreach ($lines as $line)
foreach ($lines as $line)
{
{
if (chop($line) === '')
if (chop($line) === '')
{
{
if (isset($CurrentBlock))
if (isset($CurrentBlock))
{
{
$CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
$CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
? $CurrentBlock['interrupted'] + 1 : 1
? $CurrentBlock['interrupted'] + 1 : 1
);
);
}
}


continue;
continue;
}
}


while (($beforeTab = strstr($line, "\t", true)) !== false)
while (($beforeTab = strstr($line, "\t", true)) !== false)
{
{
$shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
$shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;


$line = $beforeTab
$line = $beforeTab
. str_repeat(' ', $shortage)
. str_repeat(' ', $shortage)
. substr($line, strlen($beforeTab) + 1)
. substr($line, strlen($beforeTab) + 1)
;
;
}
}


$indent = strspn($line, ' ');
$indent = strspn($line, ' ');


$text = $indent > 0 ? substr($line, $indent) : $line;
$text = $indent > 0 ? substr($line, $indent) : $line;


# ~
# ~


$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);


# ~
# ~


if (isset($CurrentBlock['continuable']))
if (isset($CurrentBlock['continuable']))
{
{
$methodName = 'block' . $CurrentBlock['type'] . 'Continue';
$methodName = 'block' . $CurrentBlock['type'] . 'Continue';
$Block = $this->$methodName($Line, $CurrentBlock);
$Block = $this->$methodName($Line, $CurrentBlock);


if (isset($Block))
if (isset($Block))
{
{
$CurrentBlock = $Block;
$CurrentBlock = $Block;


continue;
continue;
}
}
else
else
{
{
if ($this->isBlockCompletable($CurrentBlock['type']))
if ($this->isBlockCompletable($CurrentBlock['type']))
{
{
$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$CurrentBlock = $this->$methodName($CurrentBlock);
$CurrentBlock = $this->$methodName($CurrentBlock);
}
}
}
}
}
}


# ~
# ~


$marker = $text[0];
$marker = $text[0];


# ~
# ~


$blockTypes = $this->unmarkedBlockTypes;
$blockTypes = $this->unmarkedBlockTypes;


if (isset($this->BlockTypes[$marker]))
if (isset($this->BlockTypes[$marker]))
{
{
foreach ($this->BlockTypes[$marker] as $blockType)
foreach ($this->BlockTypes[$marker] as $blockType)
{
{
$blockTypes []= $blockType;
$blockTypes []= $blockType;
}
}
}
}


#
#
# ~
# ~


foreach ($blockTypes as $blockType)
foreach ($blockTypes as $blockType)
{
{
$Block = $this->{"block$blockType"}($Line, $CurrentBlock);
$Block = $this->{"block$blockType"}($Line, $CurrentBlock);


if (isset($Block))
if (isset($Block))
{
{
$Block['type'] = $blockType;
$Block['type'] = $blockType;


if ( ! isset($Block['identified']))
if ( ! isset($Block['identified']))
{
{
if (isset($CurrentBlock))
if (isset($CurrentBlock))
{
{
$Elements[] = $this->extractElement($CurrentBlock);
$Elements[] = $this->extractElement($CurrentBlock);
}
}


$Block['identified'] = true;
$Block['identified'] = true;
}
}


if ($this->isBlockContinuable($blockType))
if ($this->isBlockContinuable($blockType))
{
{
$Block['continuable'] = true;
$Block['continuable'] = true;
}
}


$CurrentBlock = $Block;
$CurrentBlock = $Block;


continue 2;
continue 2;
}
}
}
}


# ~
# ~


if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
{
{
$Block = $this->paragraphContinue($Line, $CurrentBlock);
$Block = $this->paragraphContinue($Line, $CurrentBlock);
}
}


if (isset($Block))
if (isset($Block))
{
{
$CurrentBlock = $Block;
$CurrentBlock = $Block;
}
}
else
else
{
{
if (isset($CurrentBlock))
if (isset($CurrentBlock))
{
{
$Elements[] = $this->extractElement($CurrentBlock);
$Elements[] = $this->extractElement($CurrentBlock);
}
}


$CurrentBlock = $this->paragraph($Line);
$CurrentBlock = $this->paragraph($Line);


$CurrentBlock['identified'] = true;
$CurrentBlock['identified'] = true;
}
}
}
}


# ~
# ~


if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{
{
$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$CurrentBlock = $this->$methodName($CurrentBlock);
$CurrentBlock = $this->$methodName($CurrentBlock);
}
}


# ~
# ~


if (isset($CurrentBlock))
if (isset($CurrentBlock))
{
{
$Elements[] = $this->extractElement($CurrentBlock);
$Elements[] = $this->extractElement($CurrentBlock);
}
}


# ~
# ~


return $Elements;
return $Elements;
}
}


protected function extractElement(array $Component)
protected function extractElement(array $Component)
{
{
if ( ! isset($Component['element']))
if ( ! isset($Component['element']))
{
{
if (isset($Component['markup']))
if (isset($Component['markup']))
{
{
$Component['element'] = array('rawHtml' => $Component['markup']);
$Component['element'] = array('rawHtml' => $Component['markup']);
}
}
elseif (isset($Component['hidden']))
elseif (isset($Component['hidden']))
{
{
$Component['element'] = array();
$Component['element'] = array();
}
}
}
}


return $Component['element'];
return $Component['element'];
}
}


protected function isBlockContinuable($Type)
protected function isBlockContinuable($Type)
{
{
return method_exists($this, 'block' . $Type . 'Continue');
return method_exists($this, 'block' . $Type . 'Continue');
}
}


protected function isBlockCompletable($Type)
protected function isBlockCompletable($Type)
{
{
return method_exists($this, 'block' . $Type . 'Complete');
return method_exists($this, 'block' . $Type . 'Complete');
}
}


#
#
# Code
# Code


protected function blockCode($Line, $Block = null)
protected function blockCode($Line, $Block = null)
{
{
if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
{
{
return;
return null;
}
}


if ($Line['indent'] >= 4)
if ($Line['indent'] >= 4)
{
{
$text = substr($Line['body'], 4);
$text = substr($Line['body'], 4);


$Block = array(
$Block = array(
'element' => array(
'element' => array(
'name' => 'pre',
'name' => 'pre',
'element' => array(
'element' => array(
'name' => 'code',
'name' => 'code',
'text' => $text,
'text' => $text,
),
),
),
),
);
);


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockCodeContinue($Line, $Block)
protected function blockCodeContinue($Line, $Block)
{
{
if ($Line['indent'] >= 4)
if ($Line['indent'] >= 4)
{
{
if (isset($Block['interrupted']))
if (isset($Block['interrupted']))
{
{
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);


unset($Block['interrupted']);
unset($Block['interrupted']);
}
}


$Block['element']['element']['text'] .= "\n";
$Block['element']['element']['text'] .= "\n";


$text = substr($Line['body'], 4);
$text = substr($Line['body'], 4);


$Block['element']['element']['text'] .= $text;
$Block['element']['element']['text'] .= $text;


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockCodeComplete($Block)
protected function blockCodeComplete($Block)
{
{
return $Block;
return $Block;
}
}


#
#
# Comment
# Comment


protected function blockComment($Line)
protected function blockComment($Line)
{
{
if ($this->markupEscaped or $this->safeMode)
if ($this->markupEscaped or $this->safeMode)
{
{
return;
return null;
}
}


if (strpos($Line['text'], '<!--') === 0)
if (strpos($Line['text'], '<!--') === 0)
{
{
$Block = array(
$Block = array(
'element' => array(
'element' => array(
'rawHtml' => $Line['body'],
'rawHtml' => $Line['body'],
'autobreak' => true,
'autobreak' => true,
),
),
);
);


if (strpos($Line['text'], '-->') !== false)
if (strpos($Line['text'], '-->') !== false)
{
{
$Block['closed'] = true;
$Block['closed'] = true;
}
}


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockCommentContinue($Line, array $Block)
protected function blockCommentContinue($Line, array $Block)
{
{
if (isset($Block['closed']))
if (isset($Block['closed']))
{
{
return;
return null;
}
}


$Block['element']['rawHtml'] .= "\n" . $Line['body'];
$Block['element']['rawHtml'] .= "\n" . $Line['body'];


if (strpos($Line['text'], '-->') !== false)
if (strpos($Line['text'], '-->') !== false)
{
{
$Block['closed'] = true;
$Block['closed'] = true;
}
}


return $Block;
return $Block;
}
}


#
#
# Fenced Code
# Fenced Code


protected function blockFencedCode($Line)
protected function blockFencedCode($Line)
{
{
$marker = $Line['text'][0];
$marker = $Line['text'][0];


$openerLength = strspn($Line['text'], $marker);
$openerLength = strspn($Line['text'], $marker);


if ($openerLength < 3)
if ($openerLength < 3)
{
{
return;
return null;
}
}


$infostring = trim(substr($Line['text'], $openerLength), "\t ");
$infostring = trim(substr($Line['text'], $openerLength), "\t ");


if (strpos($infostring, '`') !== false)
if (strpos($infostring, '`') !== false)
{
{
return;
return null;
}
}


$Element = array(
$Element = array(
'name' => 'code',
'name' => 'code',
'text' => '',
'text' => '',
);
);


if ($infostring !== '')
if ($infostring !== '')
{
{
/**
/**
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
* Every HTML element may have a class attribute specified.
* Every HTML element may have a class attribute specified.
* The attribute, if specified, must have a value that is a set
* The attribute, if specified, must have a value that is a set
* of space-separated tokens representing the various classes
* of space-separated tokens representing the various classes
* that the element belongs to.
* that the element belongs to.
* [...]
* [...]
* The space characters, for the purposes of this specification,
* The space characters, for the purposes of this specification,
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
* U+000D CARRIAGE RETURN (CR).
* U+000D CARRIAGE RETURN (CR).
*/
*/
$language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
$language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));


$Element['attributes'] = array('class' => "language-$language");
$Element['attributes'] = array('class' => "language-$language");
}
}


$Block = array(
$Block = array(
'char' => $marker,
'char' => $marker,
'openerLength' => $openerLength,
'openerLength' => $openerLength,
'element' => array(
'element' => array(
'name' => 'pre',
'name' => 'pre',
'element' => $Element,
'element' => $Element,
),
),
);
);


return $Block;
return $Block;
}
}


protected function blockFencedCodeContinue($Line, $Block)
protected function blockFencedCodeContinue($Line, $Block)
{
{
if (isset($Block['complete']))
if (isset($Block['complete']))
{
{
return;
return null;
}
}


if (isset($Block['interrupted']))
if (isset($Block['interrupted']))
{
{
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);


unset($Block['interrupted']);
unset($Block['interrupted']);
}
}


if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
and chop(substr($Line['text'], $len), ' ') === ''
and chop(substr($Line['text'], $len), ' ') === ''
) {
) {
$Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
$Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);


$Block['complete'] = true;
$Block['complete'] = true;


return $Block;
return $Block;
}
}


$Block['element']['element']['text'] .= "\n" . $Line['body'];
$Block['element']['element']['text'] .= "\n" . $Line['body'];


return $Block;
return $Block;
}
}


protected function blockFencedCodeComplete($Block)
protected function blockFencedCodeComplete($Block)
{
{
return $Block;
return $Block;
}
}


#
#
# Header
# Header


protected function blockHeader($Line)
protected function blockHeader($Line)
{
{
$level = strspn($Line['text'], '#');
$level = strspn($Line['text'], '#');


if ($level > 6)
if ($level > 6)
{
{
return;
return null;
}
}


$text = trim($Line['text'], '#');
$text = trim($Line['text'], '#');


if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
if ($this->strictMode && isset($text[0]) && $text[0] !== ' ')
{
{
return;
return null;
}
}


$text = trim($text, ' ');
$text = trim($text, ' ');


$Block = array(
$Block = array(
'element' => array(
'element' => array(
'name' => 'h' . $level,
'name' => 'h' . $level,
'handler' => array(
'handler' => array(
'function' => 'lineElements',
'function' => 'lineElements',
'argument' => $text,
'argument' => $text,
'destination' => 'elements',
'destination' => 'elements',
)
)
),
),
);
);


return $Block;
return $Block;
}
}


#
#
# List
# List


protected function blockList($Line, array $CurrentBlock = null)
protected function blockList($Line, ?array $CurrentBlock = null)
{
{
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');


if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
{
{
$contentIndent = strlen($matches[2]);
$contentIndent = strlen($matches[2]);


if ($contentIndent >= 5)
if ($contentIndent >= 5)
{
{
$contentIndent -= 1;
$contentIndent -= 1;
$matches[1] = substr($matches[1], 0, -$contentIndent);
$matches[1] = substr($matches[1], 0, -$contentIndent);
$matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
$matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
}
}
elseif ($contentIndent === 0)
elseif ($contentIndent === 0)
{
{
$matches[1] .= ' ';
$matches[1] .= ' ';
}
}


$markerWithoutWhitespace = strstr($matches[1], ' ', true);
$markerWithoutWhitespace = strstr($matches[1], ' ', true);


$Block = array(
$Block = array(
'indent' => $Line['indent'],
'indent' => $Line['indent'],
'pattern' => $pattern,
'pattern' => $pattern,
'data' => array(
'data' => array(
'type' => $name,
'type' => $name,
'marker' => $matches[1],
'marker' => $matches[1],
'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
),
),
'element' => array(
'element' => array(
'name' => $name,
'name' => $name,
'elements' => array(),
'elements' => array(),
),
),
);
);
$Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
$Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');


if ($name === 'ol')
if ($name === 'ol')
{
{
$listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
$listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';


if ($listStart !== '1')
if ($listStart !== '1')
{
{
if (
if (
isset($CurrentBlock)
isset($CurrentBlock)
and $CurrentBlock['type'] === 'Paragraph'
and $CurrentBlock['type'] === 'Paragraph'
and ! isset($CurrentBlock['interrupted'])
and ! isset($CurrentBlock['interrupted'])
) {
) {
return;
return null;
}
}


$Block['element']['attributes'] = array('start' => $listStart);
$Block['element']['attributes'] = array('start' => $listStart);
}
}
}
}


$Block['li'] = array(
$Block['li'] = array(
'name' => 'li',
'name' => 'li',
'handler' => array(
'handler' => array(
'function' => 'li',
'function' => 'li',
'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
'destination' => 'elements'
'destination' => 'elements'
)
)
);
);


$Block['element']['elements'] []= & $Block['li'];
$Block['element']['elements'] []= & $Block['li'];


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockListContinue($Line, array $Block)
protected function blockListContinue($Line, array $Block)
{
{
if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
{
{
return null;
return null;
}
}


$requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
$requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));


if ($Line['indent'] < $requiredIndent
if ($Line['indent'] < $requiredIndent
and (
and (
(
(
$Block['data']['type'] === 'ol'
$Block['data']['type'] === 'ol'
and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
) or (
) or (
$Block['data']['type'] === 'ul'
$Block['data']['type'] === 'ul'
and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
)
)
)
)
) {
) {
if (isset($Block['interrupted']))
if (isset($Block['interrupted']))
{
{
$Block['li']['handler']['argument'] []= '';
$Block['li']['handler']['argument'] []= '';


$Block['loose'] = true;
$Block['loose'] = true;


unset($Block['interrupted']);
unset($Block['interrupted']);
}
}


unset($Block['li']);
unset($Block['li']);


$text = isset($matches[1]) ? $matches[1] : '';
$text = isset($matches[1]) ? $matches[1] : '';


$Block['indent'] = $Line['indent'];
$Block['indent'] = $Line['indent'];


$Block['li'] = array(
$Block['li'] = array(
'name' => 'li',
'name' => 'li',
'handler' => array(
'handler' => array(
'function' => 'li',
'function' => 'li',
'argument' => array($text),
'argument' => array($text),
'destination' => 'elements'
'destination' => 'elements'
)
)
);
);


$Block['element']['elements'] []= & $Block['li'];
$Block['element']['elements'] []= & $Block['li'];


return $Block;
return $Block;
}
}
elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
{
{
return null;
return null;
}
}


if ($Line['text'][0] === '[' and $this->blockReference($Line))
if ($Line['text'][0] === '[' and $this->blockReference($Line))
{
{
return $Block;
return $Block;
}
}


if ($Line['indent'] >= $requiredIndent)
if ($Line['indent'] >= $requiredIndent)
{
{
if (isset($Block['interrupted']))
if (isset($Block['interrupted']))
{
{
$Block['li']['handler']['argument'] []= '';
$Block['li']['handler']['argument'] []= '';


$Block['loose'] = true;
$Block['loose'] = true;


unset($Block['interrupted']);
unset($Block['interrupted']);
}
}


$text = substr($Line['body'], $requiredIndent);
$text = substr($Line['body'], $requiredIndent);


$Block['li']['handler']['argument'] []= $text;
$Block['li']['handler']['argument'] []= $text;


return $Block;
return $Block;
}
}


if ( ! isset($Block['interrupted']))
if ( ! isset($Block['interrupted']))
{
{
$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);


$Block['li']['handler']['argument'] []= $text;
$Block['li']['handler']['argument'] []= $text;


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockListComplete(array $Block)
protected function blockListComplete(array $Block)
{
{
if (isset($Block['loose']))
if (isset($Block['loose']))
{
{
foreach ($Block['element']['elements'] as &$li)
foreach ($Block['element']['elements'] as &$li)
{
{
if (end($li['handler']['argument']) !== '')
if (end($li['handler']['argument']) !== '')
{
{
$li['handler']['argument'] []= '';
$li['handler']['argument'] []= '';
}
}
}
}
}
}


return $Block;
return $Block;
}
}


#
#
# Quote
# Quote


protected function blockQuote($Line)
protected function blockQuote($Line)
{
{
if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
{
{
$Block = array(
$Block = array(
'element' => array(
'element' => array(
'name' => 'blockquote',
'name' => 'blockquote',
'handler' => array(
'handler' => array(
'function' => 'linesElements',
'function' => 'linesElements',
'argument' => (array) $matches[1],
'argument' => (array) $matches[1],
'destination' => 'elements',
'destination' => 'elements',
)
)
),
),
);
);


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockQuoteContinue($Line, array $Block)
protected function blockQuoteContinue($Line, array $Block)
{
{
if (isset($Block['interrupted']))
if (isset($Block['interrupted']))
{
{
return;
return null;
}
}


if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
{
{
$Block['element']['handler']['argument'] []= $matches[1];
$Block['element']['handler']['argument'] []= $matches[1];


return $Block;
return $Block;
}
}


if ( ! isset($Block['interrupted']))
if ( ! isset($Block['interrupted']))
{
{
$Block['element']['handler']['argument'] []= $Line['text'];
$Block['element']['handler']['argument'] []= $Line['text'];


return $Block;
return $Block;
}
}
return null;
}
}


#
#
# Rule
# Rule


protected function blockRule($Line)
protected function blockRule($Line)
{
{
$marker = $Line['text'][0];
$marker = $Line['text'][0];


if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
{
{
$Block = array(
$Block = array(
'element' => array(
'element' => array(
'name' => 'hr',
'name' => 'hr',
),
),
);
);


return $Block;
return $Block;
}
}
return null;
}
}


#
#
# Setext
# Setext


protected function blockSetextHeader($Line, array $Block = null)
protected function blockSetextHeader($Line, ?array $Block = null)
{
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
{
{
return;
return null;
}
}


if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
{
{
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';


return $Block;
return $Block;
}
}
return null;
}
}


#
#
# Markup
# Markup


protected function blockMarkup($Line)
protected function blockMarkup($Line)
{
{
if ($this->markupEscaped or $this->safeMode)
if ($this->markupEscaped or $this->safeMode)
{
{
return;
return null;
}
}


if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
{
{
$element = strtolower($matches[1]);
$element = strtolower($matches[1]);


if (in_array($element, $this->textLevelElements))
if (in_array($element, $this->textLevelElements))
{
{
return;
return null;
}
}


$Block = array(
$Block = array(
'name' => $matches[1],
'name' => $matches[1],
'element' => array(
'element' => array(
'rawHtml' => $Line['text'],
'rawHtml' => $Line['text'],
'autobreak' => true,
'autobreak' => true,
),
),
);
);


return $Block;
return $Block;
}
}
return null;
}
}


protected function blockMarkupContinue($Line, array $Block)
protected function blockMarkupContinue($Line, array $Block)
{
{
if (isset($Block['closed']) or isset($Block['interrupted']))
if (isset($Block['closed']) or isset($Block['interrupted']))
{
{
return;
return null;
}
}


$Block['element']['rawHtml'] .= "\n" . $Line['body'];
$Block['element']['rawHtml'] .= "\n" . $Line['body'];


return $Block;
return $Block;
}
}


#
#
# Reference
# Reference


protected function blockReference($Line)
protected function blockReference($Line)
{
{
if (strpos($Line['text'], ']') !== false
if (strpos($Line['text'], ']') !== false
and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
) {
) {
$id = strtolower($matches[1]);
$id = strtolower($matches[1]);


$Data = array(
$Data = array(
'url' => $matches[2],
'url' => $matches[2],
'title' => isset($matches[3]) ? $matches[3] : null,
'title' => isset($matches[3]) ? $matches[3] : null,
);
);


$this->DefinitionData['Reference'][$id] = $Data;
$this->DefinitionData['Reference'][$id] = $Data;


$Block = array(
$Block = array(
'element' => array(),
'element' => array(),
);
);


return $Block;
return $Block;
}
}
return null;
}
}


#
#
# Table
# Table


protected function blockTable($Line, array $Block = null)
protected function blockTable($Line, ?array $Block = null)
{
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
{
{
return;
return null;
}
}


if (
if (
strpos($Block['element']['handler']['argument'], '|') === false
strpos($Block['element']['handler']['argument'], '|') === false
and strpos($Line['text'], '|') === false
and strpos($Line['text'], '|') === false
and strpos($Line['text'], ':') === false
and strpos($Line['text'], ':') === false
or strpos($Block['element']['handler']['argument'], "\n") !== false
or strpos($Block['element']['handler']['argument'], "\n") !== false
) {
) {
return;
return null;
}
}


if (chop($Line['text'], ' -:|') !== '')
if (chop($Line['text'], ' -:|') !== '')
{
{
return;
return null;
}
}


$alignments = array();
$alignments = array();


$divider = $Line['text'];

$divider = trim($divider);
$divider = trim($divider, '|');

$dividerCells = explode('|', $divider);

foreach (