372 lines
8.6 KiB
PHP
372 lines
8.6 KiB
PHP
|
<?php
|
||
|
|
||
|
#
|
||
|
#
|
||
|
# 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
|
||
|
{
|
||
|
#
|
||
|
# ~
|
||
|
|
||
|
function __construct()
|
||
|
{
|
||
|
$this->BlockTypes[':'] []= 'DefinitionList';
|
||
|
|
||
|
$this->DefinitionTypes['*'] []= 'Abbreviation';
|
||
|
|
||
|
# identify footnote definitions before reference definitions
|
||
|
array_unshift($this->DefinitionTypes['['], 'Footnote');
|
||
|
|
||
|
# identify footnote markers before before links
|
||
|
array_unshift($this->SpanTypes['['], '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->Definitions['Footnote']))
|
||
|
{
|
||
|
$Element = $this->buildFootnoteElement();
|
||
|
|
||
|
$markup .= "\n" . $this->element($Element);
|
||
|
}
|
||
|
|
||
|
return $markup;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Blocks
|
||
|
#
|
||
|
|
||
|
#
|
||
|
# Atx
|
||
|
|
||
|
protected function identifyAtx($Line)
|
||
|
{
|
||
|
$Block = parent::identifyAtx($Line);
|
||
|
|
||
|
if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
|
||
|
{
|
||
|
$attributeString = $matches[1][0];
|
||
|
|
||
|
$Block['element']['attributes'] = $this->parseAttributes($attributeString);
|
||
|
|
||
|
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
|
||
|
}
|
||
|
|
||
|
return $Block;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Definition List
|
||
|
|
||
|
protected function identifyDefinitionList($Line, $Block)
|
||
|
{
|
||
|
if (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,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
$Element['text'] []= array(
|
||
|
'name' => 'dd',
|
||
|
'handler' => 'line',
|
||
|
'text' => ltrim($Line['text'], ' :'),
|
||
|
);
|
||
|
|
||
|
$Block['element'] = $Element;
|
||
|
|
||
|
return $Block;
|
||
|
}
|
||
|
|
||
|
protected function addToDefinitionList($Line, array $Block)
|
||
|
{
|
||
|
if ($Line['text'][0] === ':')
|
||
|
{
|
||
|
$Block['element']['text'] []= array(
|
||
|
'name' => 'dd',
|
||
|
'handler' => 'line',
|
||
|
'text' => ltrim($Line['text'], ' :'),
|
||
|
);
|
||
|
|
||
|
return $Block;
|
||
|
}
|
||
|
|
||
|
if ( ! isset($Block['interrupted']))
|
||
|
{
|
||
|
$Element = array_pop($Block['element']['text']);
|
||
|
|
||
|
$Element['text'] .= "\n" . chop($Line['text']);
|
||
|
|
||
|
$Block['element']['text'] []= $Element;
|
||
|
|
||
|
return $Block;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Setext
|
||
|
|
||
|
protected function identifySetext($Line, array $Block = null)
|
||
|
{
|
||
|
$Block = parent::identifySetext($Line, $Block);
|
||
|
|
||
|
if (preg_match('/[ ]*'.$this->attributesPattern.'[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
|
||
|
{
|
||
|
$attributeString = $matches[1][0];
|
||
|
|
||
|
$Block['element']['attributes'] = $this->parseAttributes($attributeString);
|
||
|
|
||
|
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
|
||
|
}
|
||
|
|
||
|
return $Block;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Definitions
|
||
|
#
|
||
|
|
||
|
#
|
||
|
# Abbreviation
|
||
|
|
||
|
protected function identifyAbbreviation($Line)
|
||
|
{
|
||
|
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
|
||
|
{
|
||
|
$Abbreviation = array(
|
||
|
'id' => $matches[1],
|
||
|
'data' => $matches[2],
|
||
|
);
|
||
|
|
||
|
return $Abbreviation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Footnote
|
||
|
|
||
|
protected function identifyFootnote($Line)
|
||
|
{
|
||
|
if (preg_match('/^\[\^(.+?)\]:[ ]?(.+)$/', $Line['text'], $matches))
|
||
|
{
|
||
|
$Footnote = array(
|
||
|
'id' => $matches[1],
|
||
|
'data' => array(
|
||
|
'text' => $matches[2],
|
||
|
'count' => null,
|
||
|
'number' => null,
|
||
|
),
|
||
|
);
|
||
|
|
||
|
return $Footnote;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Spans
|
||
|
#
|
||
|
|
||
|
#
|
||
|
# Footnote Marker
|
||
|
|
||
|
protected function identifyFootnoteMarker($Excerpt)
|
||
|
{
|
||
|
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
|
||
|
{
|
||
|
$name = $matches[1];
|
||
|
|
||
|
if ( ! isset($this->Definitions['Footnote'][$name]))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->Definitions['Footnote'][$name]['count'] ++;
|
||
|
|
||
|
if ( ! isset($this->Definitions['Footnote'][$name]['number']))
|
||
|
{
|
||
|
$this->Definitions['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
|
||
|
}
|
||
|
|
||
|
$Element = array(
|
||
|
'name' => 'sup',
|
||
|
'attributes' => array('id' => 'fnref'.$this->Definitions['Footnote'][$name]['count'].':'.$name),
|
||
|
'handler' => 'element',
|
||
|
'text' => array(
|
||
|
'name' => 'a',
|
||
|
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
|
||
|
'text' => $this->Definitions['Footnote'][$name]['number'],
|
||
|
),
|
||
|
);
|
||
|
|
||
|
return array(
|
||
|
'extent' => strlen($matches[0]),
|
||
|
'element' => $Element,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private $footnoteCount = 0;
|
||
|
|
||
|
#
|
||
|
# Link
|
||
|
|
||
|
protected function identifyLink($Excerpt)
|
||
|
{
|
||
|
$Span = parent::identifyLink($Excerpt);
|
||
|
|
||
|
$remainder = substr($Excerpt['text'], $Span['extent']);
|
||
|
|
||
|
if (preg_match('/^[ ]*'.$this->attributesPattern.'/', $remainder, $matches))
|
||
|
{
|
||
|
$Span['element']['attributes'] += $this->parseAttributes($matches[1]);
|
||
|
|
||
|
$Span['extent'] += strlen($matches[0]);
|
||
|
}
|
||
|
|
||
|
return $Span;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# ~
|
||
|
|
||
|
protected function readPlainText($text)
|
||
|
{
|
||
|
$text = parent::readPlainText($text);
|
||
|
|
||
|
if (isset($this->Definitions['Abbreviation']))
|
||
|
{
|
||
|
foreach ($this->Definitions['Abbreviation'] as $abbreviation => $phrase)
|
||
|
{
|
||
|
$text = str_replace($abbreviation, '<abbr title="'.$phrase.'">'.$abbreviation.'</abbr>', $text);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# ~
|
||
|
#
|
||
|
|
||
|
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(),
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
|
||
|
usort($this->Definitions['Footnote'], function($A, $B) {
|
||
|
return $A['number'] - $B['number'];
|
||
|
});
|
||
|
|
||
|
foreach ($this->Definitions['Footnote'] as $name => $Data)
|
||
|
{
|
||
|
if ( ! isset($Data['number']))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$text = $Data['text'];
|
||
|
|
||
|
foreach (range(1, $Data['count']) as $number)
|
||
|
{
|
||
|
$text .= ' <a href="#fnref'.$number.':'.$name.'" rev="footnote" class="footnote-backref">↩</a>';
|
||
|
}
|
||
|
|
||
|
$Element['text'][1]['text'] []= array(
|
||
|
'name' => 'li',
|
||
|
'attributes' => array('id' => 'fn:'.$name),
|
||
|
'handler' => 'elements',
|
||
|
'text' => array(
|
||
|
array(
|
||
|
'name' => 'p',
|
||
|
'text' => $text,
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $Element;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Private
|
||
|
#
|
||
|
|
||
|
private function parseAttributes($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;
|
||
|
}
|
||
|
|
||
|
private $attributesPattern = '{((?:[#.][-\w]+[ ]*)+)}';
|
||
|
}
|