1
0

Update parsedown & parsedown-extra for php-7

This commit is contained in:
Mike Schwörer 2016-12-24 13:37:55 +01:00
parent f35c631ae5
commit d1b7b3e46d
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
2 changed files with 891 additions and 590 deletions

File diff suppressed because it is too large Load Diff

View File

@ -15,20 +15,27 @@
class ParsedownExtra extends Parsedown class ParsedownExtra extends Parsedown
{ {
# # ~
const version = '0.7.0';
# ~ # ~
function __construct() function __construct()
{ {
$this->BlockTypes[':'] []= 'DefinitionList'; if (parent::version < '1.5.0')
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->DefinitionTypes['*'] []= 'Abbreviation'; $this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions # identify footnote definitions before reference definitions
array_unshift($this->DefinitionTypes['['], 'Footnote'); array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links # identify footnote markers before before links
array_unshift($this->SpanTypes['['], 'FootnoteMarker'); array_unshift($this->InlineTypes['['], 'FootnoteMarker');
} }
# #
@ -44,7 +51,7 @@ class ParsedownExtra extends Parsedown
# add footnotes # add footnotes
if (isset($this->Definitions['Footnote'])) if (isset($this->DefinitionData['Footnote']))
{ {
$Element = $this->buildFootnoteElement(); $Element = $this->buildFootnoteElement();
@ -59,20 +66,70 @@ class ParsedownExtra extends Parsedown
# #
# #
# Atx # Abbreviation
protected function identifyAtx($Line) protected function blockAbbreviation($Line)
{ {
$Block = parent::identifyAtx($Line); if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{ {
$attributeString = $matches[1][0]; $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block['element']['attributes'] = $this->parseAttributes($attributeString); $Block = array(
'hidden' => true,
);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]); 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; return $Block;
} }
@ -80,9 +137,9 @@ class ParsedownExtra extends Parsedown
# #
# Definition List # Definition List
protected function identifyDefinitionList($Line, $Block) protected function blockDefinitionList($Line, $Block)
{ {
if (isset($Block['type'])) if ( ! isset($Block) or isset($Block['type']))
{ {
return; return;
} }
@ -104,54 +161,56 @@ class ParsedownExtra extends Parsedown
); );
} }
$Element['text'] []= array(
'name' => 'dd',
'handler' => 'line',
'text' => ltrim($Line['text'], ' :'),
);
$Block['element'] = $Element; $Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block; return $Block;
} }
protected function addToDefinitionList($Line, array $Block) protected function blockDefinitionListContinue($Line, array $Block)
{ {
if ($Line['text'][0] === ':') if ($Line['text'][0] === ':')
{ {
$Block['element']['text'] []= array( $Block = $this->addDdElement($Line, $Block);
'name' => 'dd',
'handler' => 'line',
'text' => ltrim($Line['text'], ' :'),
);
return $Block; return $Block;
} }
else
if ( ! isset($Block['interrupted']))
{ {
$Element = array_pop($Block['element']['text']); if (isset($Block['interrupted']) and $Line['indent'] === 0)
{
return;
}
$Element['text'] .= "\n" . chop($Line['text']); if (isset($Block['interrupted']))
{
$Block['dd']['handler'] = 'text';
$Block['dd']['text'] .= "\n\n";
$Block['element']['text'] []= $Element; unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['text'] .= "\n" . $text;
return $Block; return $Block;
} }
} }
# #
# Setext # Header
protected function identifySetext($Line, array $Block = null) protected function blockHeader($Line)
{ {
$Block = parent::identifySetext($Line, $Block); $Block = parent::blockHeader($Line);
if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE)) if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{ {
$attributeString = $matches[1][0]; $attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributes($attributeString); $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]); $Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
} }
@ -160,78 +219,70 @@ class ParsedownExtra extends Parsedown
} }
# #
# Definitions # Markup
#
# protected function blockMarkupComplete($Block)
# Abbreviation
protected function identifyAbbreviation($Line)
{ {
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches)) if ( ! isset($Block['void']))
{ {
$Abbreviation = array( $Block['markup'] = $this->processTag($Block['markup']);
'id' => $matches[1],
'data' => $matches[2],
);
return $Abbreviation;
} }
return $Block;
} }
# #
# Footnote # Setext
protected function identifyFootnote($Line) protected function blockSetextHeader($Line, array $Block = null)
{ {
if (preg_match('/^\[\^(.+?)\]:[ ]?(.+)$/', $Line['text'], $matches)) $Block = parent::blockSetextHeader($Line, $Block);
{
$Footnote = array(
'id' => $matches[1],
'data' => array(
'text' => $matches[2],
'count' => null,
'number' => null,
),
);
return $Footnote; 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;
} }
# #
# Spans # Inline Elements
# #
# #
# Footnote Marker # Footnote Marker
protected function identifyFootnoteMarker($Excerpt) protected function inlineFootnoteMarker($Excerpt)
{ {
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
{ {
$name = $matches[1]; $name = $matches[1];
if ( ! isset($this->Definitions['Footnote'][$name])) if ( ! isset($this->DefinitionData['Footnote'][$name]))
{ {
return; return;
} }
$this->Definitions['Footnote'][$name]['count'] ++; $this->DefinitionData['Footnote'][$name]['count'] ++;
if ( ! isset($this->Definitions['Footnote'][$name]['number'])) if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
{ {
$this->Definitions['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » & $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
} }
$Element = array( $Element = array(
'name' => 'sup', 'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->Definitions['Footnote'][$name]['count'].':'.$name), 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'handler' => 'element', 'handler' => 'element',
'text' => array( 'text' => array(
'name' => 'a', 'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'), 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->Definitions['Footnote'][$name]['number'], 'text' => $this->DefinitionData['Footnote'][$name]['number'],
), ),
); );
@ -247,34 +298,37 @@ class ParsedownExtra extends Parsedown
# #
# Link # Link
protected function identifyLink($Excerpt) protected function inlineLink($Excerpt)
{ {
$Span = parent::identifyLink($Excerpt); $Link = parent::inlineLink($Excerpt);
$remainder = substr($Excerpt['text'], $Span['extent']); $remainder = substr($Excerpt['text'], $Link['extent']);
if (preg_match('/^[ ]*'.$this->attributesPattern.'/', $remainder, $matches)) if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
{ {
$Span['element']['attributes'] += $this->parseAttributes($matches[1]); $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Span['extent'] += strlen($matches[0]); $Link['extent'] += strlen($matches[0]);
} }
return $Span; return $Link;
} }
# #
# ~ # ~
#
protected function readPlainText($text) protected function unmarkedText($text)
{ {
$text = parent::readPlainText($text); $text = parent::unmarkedText($text);
if (isset($this->Definitions['Abbreviation'])) if (isset($this->DefinitionData['Abbreviation']))
{ {
foreach ($this->Definitions['Abbreviation'] as $abbreviation => $phrase) foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{ {
$text = str_replace($abbreviation, '<abbr title="'.$phrase.'">'.$abbreviation.'</abbr>', $text); $pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
$text = preg_replace($pattern, '<abbr title="'.$meaning.'">'.$abbreviation.'</abbr>', $text);
} }
} }
@ -282,9 +336,34 @@ class ParsedownExtra extends Parsedown
} }
# #
# ~ # 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() protected function buildFootnoteElement()
{ {
$Element = array( $Element = array(
@ -303,45 +382,54 @@ class ParsedownExtra extends Parsedown
), ),
); );
usort($this->Definitions['Footnote'], function($A, $B) { uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
return $A['number'] - $B['number'];
});
foreach ($this->Definitions['Footnote'] as $name => $Data) foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{ {
if ( ! isset($Data['number'])) if ( ! isset($DefinitionData['number']))
{ {
continue; continue;
} }
$text = $Data['text']; $text = $DefinitionData['text'];
foreach (range(1, $Data['count']) as $number) $text = parent::text($text);
$numbers = range(1, $DefinitionData['count']);
$backLinksMarkup = '';
foreach ($numbers as $number)
{ {
$text .= '&#160;<a href="#fnref'.$number.':'.$name.'" rev="footnote" class="footnote-backref">&#8617;</a>'; $backLinksMarkup .= ' <a href="#fnref'.$number.':'.$definitionId.'" rev="footnote" class="footnote-backref">&#8617;</a>';
}
$backLinksMarkup = substr($backLinksMarkup, 1);
if (substr($text, - 4) === '</p>')
{
$backLinksMarkup = '&#160;'.$backLinksMarkup;
$text = substr_replace($text, $backLinksMarkup.'</p>', - 4);
}
else
{
$text .= "\n".'<p>'.$backLinksMarkup.'</p>';
} }
$Element['text'][1]['text'] []= array( $Element['text'][1]['text'] []= array(
'name' => 'li', 'name' => 'li',
'attributes' => array('id' => 'fn:'.$name), 'attributes' => array('id' => 'fn:'.$definitionId),
'handler' => 'elements', 'text' => "\n".$text."\n",
'text' => array(
array(
'name' => 'p',
'text' => $text,
),
),
); );
} }
return $Element; return $Element;
} }
# # ~
# Private
#
private function parseAttributes($attributeString) protected function parseAttributeData($attributeString)
{ {
$Data = array(); $Data = array();
@ -367,5 +455,72 @@ class ParsedownExtra extends Parsedown
return $Data; return $Data;
} }
private $attributesPattern = '{((?:[#.][-\w]+[ ]*)+)}'; # ~
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\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
} }