* [refac] Refactor the parsedown class.

This commit is contained in:
liugang
2025-10-27 09:08:21 +08:00
parent 7bf59d054d
commit faa95102d5
6 changed files with 8 additions and 3288 deletions
-20
View File
@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View File
+7 -585
View File
@@ -1,590 +1,12 @@
<?php
#
#
# Parsedown Extra Plugin
# https://github.com/taufik-nurrohman/parsedown-extra-plugin
#
# (c) Emanuil Rusev
# http://erusev.com
#
# (c) Taufik Nurrohman
# https://mecha-cms.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
require_once dirname(__FILE__, 2) . '/base/delegate/delegate.class.php';
require 'parsedownbase.php';
require 'parsedownextra.php';
class parsedown extends baseDelegate
{
protected static $className = 'Parsedown';
class parsedown extends parsedownextra {
const version = '1.3.6';
public $abbreviationData = array();
public $blockCodeAttributes = array();
public $blockCodeClassFormat = 'language-%s';
public $blockCodeHtml = null;
public $blockQuoteAttributes = array();
public $blockQuoteText = null;
public $codeAttributes = array();
public $codeAttributesOnParent = false;
public $codeHtml = null;
public $figureAttributes = array();
public $figuresEnabled = false;
public $footnoteAttributes = array();
public $footnoteBackLinkAttributes = array();
public $footnoteBackLinkHtml = null;
public $footnoteBackReferenceAttributes = array();
public $footnoteLinkAttributes = array();
public $footnoteLinkHtml = null;
public $footnoteReferenceAttributes = array();
public $headerAttributes = array();
public $headerText = null;
public $imageAttributes = array();
public $imageAttributesOnParent = false;
public $linkAttributes = array();
public $referenceData = array();
public $tableAttributes = array();
public $tableColumnAttributes = array();
public $voidElementSuffix = ' />';
protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)';
# Method aliases for every configuration property
public function __call($key, array $arguments = array()) {
$property = lcfirst(substr($key, 3));
if (strpos($key, 'set') === 0 && property_exists($this, $property)) {
$this->{$property} = $arguments[0];
return $this;
}
throw new Exception('Method ' . $key . ' does not exists.');
public function __construct()
{
$this->instance = new self::$className();
}
public function __construct() {
if (version_compare(parent::version, '0.8.0-beta-1') < 0) {
throw new Exception('ParsedownExtraPlugin requires a later version of Parsedown');
}
$this->BlockTypes['!'][] = 'Image';
parent::__construct();
}
protected function blockAbbreviation($Line) {
// Allow empty abbreviations
if (preg_match('/^\*\[(.+?)\]:[ ]*$/', $Line['text'], $matches)) {
$this->DefinitionData['Abbreviation'][$matches[1]] = null;
return array('hidden' => true);
}
return parent::blockAbbreviation($Line);
}
protected function blockCodeComplete($Block) {
$this->doSetAttributes($Block['element']['element'], $this->blockCodeAttributes);
$this->doSetContent($Block['element']['element'], $this->blockCodeHtml, true);
// Put code attributes on parent element
if ($this->codeAttributesOnParent) {
if ($this->codeAttributesOnParent === true) {
// $this->codeAttributesOnParent = array_keys($Block['element']['element']['attributes']);
$this->codeAttributesOnParent = array('class', 'id');
}
foreach ((array) $this->codeAttributesOnParent as $Name) {
if (isset($Block['element']['element']['attributes'][$Name])) {
$Block['element']['attributes'][$Name] = $Block['element']['element']['attributes'][$Name];
unset($Block['element']['element']['attributes'][$Name]);
}
}
}
$Block['element']['element']['rawHtml'] = $Block['element']['element']['text'];
$Block['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Block['element']['element']['text']);
return $Block;
}
protected function blockFencedCode($Line) {
// Re-enable the multiple class name feature
$Line['text'] = strtr(trim($Line['text']), array(
' ' => "\x1A",
'.' => "\x1A."
));
// Enable custom attribute syntax on code block
$Attributes = array();
if (strpos($Line['text'], '{') !== false && substr($Line['text'], -1) === '}') {
$Parts = explode('{', $Line['text'], 2);
$Attributes = $this->parseAttributeData(strtr(substr($Parts[1], 0, -1), "\x1A", ' '));
$Line['text'] = trim($Parts[0]);
}
if (!$Block = parent::blockFencedCode($Line)) {
return;
}
if ($Attributes) {
$Block['element']['element']['attributes'] = $Attributes;
} else if (isset($Block['element']['element']['attributes']['class'])) {
$Classes = explode("\x1A", strtr($Block['element']['element']['attributes']['class'], ' ', "\x1A"));
// `~~~ php` → `<pre><code class="language-php">`
// `~~~ php html` → `<pre><code class="language-php language-html">`
// `~~~ .php` → `<pre><code class="php">`
// `~~~ .php.html` → `<pre><code class="php html">`
// `~~~ .php html` → `<pre><code class="php language-html">`
// `~~~ {.php #foo}` → `<pre><code id="foo" class="php">`
$Results = [];
foreach ($Classes as $Class) {
if ($Class === "" || $Class === strtr($this->blockCodeClassFormat, array('%s' => ""))) {
continue;
}
if ($Class[0] === '.') {
$Results[] = substr($Class, 1);
} else if (preg_match('/^' . strtr(preg_quote($this->blockCodeClassFormat), array('%s' => '\S+')) . '$/', $Class)) {
$Results[] = $Class; // Do nothing!
} else {
$Results[] = sprintf($this->blockCodeClassFormat, $Class);
}
}
if ($Results = array_unique($Results)) {
$Block['element']['element']['attributes']['class'] = implode(' ', $Results);
} else {
unset($Block['element']['element']['attributes']['class']);
}
}
return $Block;
}
protected function blockFencedCodeComplete($Block) {
return $this->blockCodeComplete($Block);
}
protected function blockHeader($Line) {
if (!$Block = parent::blockHeader($Line)) {
return;
}
$Level = strspn($Line['text'], '#');
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockImage($Line) {
if (!$this->figuresEnabled) {
return;
}
// Match exactly an image syntax in a paragraph (with optional custom attributes, and optional hard break marker)
if (preg_match('/^\!\[[^\n]*?\](\[[^\n]*?\]|\([^\n]*?\))(\s*\{' . $this->regexAttribute . '+?\})?([ ]{2})?$/', $Line['text'])) {
$Block = array(
'description' => "",
'element' => array(
'name' => 'figure',
'attributes' => array(),
'elements' => array(
$this->inlineImage($Line)
)
)
);
$this->doSetAttributes($Block['element'], $this->figureAttributes);
return $Block;
}
return;
}
protected function blockImageComplete($Block) {
if (!empty($Block['description'])) {
$Description = $Block['description'];
$Block['element']['elements'][] = array(
'name' => 'figcaption',
'rawHtml' => $this->{strpos($Description, "\n\n") === false ? 'line' : 'text'}(trim($Description, "\n"))
);
// unset($Block['description']);
}
if ($this->imageAttributesOnParent) {
$Inline = $Block['element']['elements'][0];
if ($this->imageAttributesOnParent === true) {
$this->imageAttributesOnParent = array_keys($Inline['element']['attributes']);
}
foreach ((array) $this->imageAttributesOnParent as $Name) {
if (isset($Inline['element']['attributes'][$Name])) {
// Merge class names
if (
$Name === 'class' &&
isset($Block['element']['attributes'][$Name]) &&
isset($Inline['element']['attributes'][$Name])
) {
$Classes = array_merge(
explode(' ', $Block['element']['attributes'][$Name]),
explode(' ', $Inline['element']['attributes'][$Name])
);
sort($Classes);
$Block['element']['attributes']['class'] = implode(' ', array_unique(array_filter($Classes)));
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
continue;
}
$Block['element']['attributes'][$Name] = $Inline['element']['attributes'][$Name];
unset($Block['element']['elements'][0]['element']['attributes'][$Name]);
}
}
}
return $Block;
}
protected function blockImageContinue($Line, array $Block) {
if (isset($Block['complete'])) {
return;
}
if (isset($Block['interrupted'])) {
$Block['description'] .= "\n";
unset($Block['interrupted']);
}
if ($Line['indent'] === 0) {
$Block['complete'] = true;
return;
}
if ($Line['indent'] > 0 && $Line['indent'] < 4) {
$Block['description'] .= "\n" . $Line['text'];
return $Block;
}
return;
}
protected function blockQuoteComplete($Block) {
$this->doSetAttributes($Block['element'], $this->blockQuoteAttributes);
$this->doSetContent($Block['element'], $this->blockQuoteText, false, 'arguments');
return $Block;
}
protected function blockSetextHeader($Line, array $Block = null) {
if (!$Block = parent::blockSetextHeader($Line, $Block)) {
return;
}
$Level = $Line['text'][0] === '=' ? 1 : 2;
$this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
$this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
return $Block;
}
protected function blockTableContinue($Line, array $Block) {
if (!$Block = parent::blockTableContinue($Line, $Block)) {
return;
}
$Aligns = $Block['alignments'];
// `<thead>` or `<tbody>`
foreach ($Block['element']['elements'] as $Index0 => &$Element0) {
// `<tr>`
foreach ($Element0['elements'] as $Index1 => &$Element1) {
// `<th>` or `<td>`
foreach ($Element1['elements'] as $Index2 => &$Element2) {
$this->doSetAttributes($Element2, $this->tableColumnAttributes, array($Aligns[$Index2], $Index2, $Index1));
}
}
}
return $Block;
}
protected function blockTableComplete($Block) {
$this->doSetAttributes($Block['element'], $this->tableAttributes);
return $Block;
}
protected function buildFootnoteElement() {
$DefinitionData = $this->DefinitionData['Footnote'];
if (!$Footnotes = parent::buildFootnoteElement()) {
return;
}
$DefinitionKey = array_keys($DefinitionData);
$DefinitionData = array_values($DefinitionData);
$this->doSetAttributes($Footnotes, $this->footnoteAttributes);
foreach ($Footnotes['elements'][1]['elements'] as $Index0 => &$Element0) {
$Name = $DefinitionKey[$Index0];
$Count = $DefinitionData[$Index0]['count'];
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $Count);
$this->doSetAttributes($Element0, $this->footnoteBackReferenceAttributes, $Args);
foreach ($Element0['elements'] as $Index1 => &$Element1) {
if (!isset($Element1['elements'])) {
continue;
}
$Count = 0;
foreach ($Element1['elements'] as $Index2 => &$Element2) {
if (!isset($Element2['name']) || $Element2['name'] !== 'a') {
continue;
}
$Args[1] = ++$Count;
$this->doSetAttributes($Element2, $this->footnoteBackLinkAttributes, $Args);
$this->doSetContent($Element2, $this->footnoteBackLinkHtml, false, 'rawHtml');
}
}
}
return $Footnotes;
}
protected function doGetAttributes($Element) {
if (isset($Element['attributes'])) {
return (array) $Element['attributes'];
}
return array();
}
protected function doGetContent($Element) {
if (isset($Element['text'])) {
return $Element['text'];
}
if (isset($Element['rawHtml'])) {
return $Element['rawHtml'];
}
if (isset($Element['handler']['argument'])) {
return implode("\n", (array) $Element['handler']['argument']);
}
return null;
}
private function doSetLink($Excerpt, $Function) {
if (!$Inline = call_user_func('parent::' . $Function, $Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->linkAttributes, array($this->isLocal($Inline['element'], 'href')));
$this->doSetData($this->DefinitionData['Reference'], $this->referenceData);
return $Inline;
}
protected function doSetAttributes(&$Element, $From, $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Element['attributes'] = array_replace($Attributes, (array) call_user_func_array($From, $Args));
} else {
$Element['attributes'] = array_replace($Attributes, (array) $From);
}
}
protected function doSetContent(&$Element, $From, $Esc = false, $Mode = 'text', $Args = array()) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
if ($Esc) {
$Content = parent::escape($Content, true);
}
if (is_callable($From)) {
$Args = array_merge(array($Content, $Attributes, &$Element), $Args);
$Content = call_user_func_array($From, $Args);
} else if (!empty($From)) {
$Content = sprintf($From, $Content);
}
if ($Mode === 'arguments') {
$Element['handler']['argument'] = explode("\n", $Content);
} else if ($Mode === 'argument') {
$Element['handler']['argument'] = $Content;
} else {
$Element[$Mode] = $Content;
}
}
protected function doSetData(&$To, $From) {
$To = array_replace((array) $To, (array) $From);
}
protected function element(array $Element) {
if (!$Any = parent::element($Element)) {
return;
}
if (substr($Any, -3) === ' />') {
if (is_callable($this->voidElementSuffix)) {
$Attributes = $this->doGetAttributes($Element);
$Content = $this->doGetContent($Element);
$Suffix = call_user_func_array($this->voidElementSuffix, [$Content, $Attributes, &$Element]);
} else {
$Suffix = $this->voidElementSuffix;
}
$Any = substr_replace($Any, $Suffix, -3);
}
return $Any;
}
protected function inlineCode($Excerpt) {
if (!$Inline = parent::inlineCode($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->codeAttributes);
$this->doSetContent($Inline['element'], $this->codeHtml, true);
$Inline['element']['rawHtml'] = $Inline['element']['text'];
$Inline['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['text']);
return $Inline;
}
protected function inlineFootnoteMarker($Excerpt) {
if (!$Inline = parent::inlineFootnoteMarker($Excerpt)) {
return;
}
$Name = null;
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) {
$Name = $matches[1];
}
$Args = array(is_numeric($Name) ? (float) $Name : $Name, $this->DefinitionData['Footnote'][$Name]['count']);
$this->doSetAttributes($Inline['element'], $this->footnoteReferenceAttributes, $Args);
$this->doSetAttributes($Inline['element']['element'], $this->footnoteLinkAttributes, $Args);
$this->doSetContent($Inline['element']['element'], $this->footnoteLinkHtml, false, 'text', $Args);
$Inline['element']['element']['rawHtml'] = $Inline['element']['element']['text'];
$Inline['element']['element']['allowRawHtmlInSafeMode'] = true;
unset($Inline['element']['element']['text']);
return $Inline;
}
protected function inlineImage($Excerpt) {
if (!$Inline = parent::inlineImage($Excerpt)) {
return;
}
$this->doSetAttributes($Inline['element'], $this->imageAttributes, array($this->isLocal($Inline['element'], 'src')));
return $Inline;
}
protected function inlineLink($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineText($Text) {
$this->doSetData($this->DefinitionData['Abbreviation'], $this->abbreviationData);
return parent::inlineText($Text);
}
protected function inlineUrl($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function inlineUrlTag($Excerpt) {
return $this->doSetLink($Excerpt, __FUNCTION__);
}
protected function isLocal($Element, $Key) {
$Link = isset($Element['attributes'][$Key]) ? (string) $Element['attributes'][$Key] : null;
if (
// `<a href="">`
$Link === "" ||
// `<a href="../foo/bar">`
// `<a href="/foo/bar">`
// `<a href="?foo=bar">`
// `<a href="&foo=bar">`
// `<a href="#foo">`
strpos('./?&#', $Link[0]) !== false && strpos($Link, '//') !== 0 ||
// `<a href="data:text/html,asdf">`
strpos($Link, 'data:') === 0 ||
// `<a href="javascript:;">`
strpos($Link, 'javascript:') === 0 ||
// `<a href="mailto:as@df">`
strpos($Link, 'mailto:') === 0
) {
return true;
}
if (isset($_SERVER['HTTP_HOST'])) {
$Host = $_SERVER['HTTP_HOST'];
} else if (isset($_SERVER['SERVER_NAME'])) {
$Host = $_SERVER['SERVER_NAME'];
} else {
$Host = "";
}
// `<a href="//example.com">`
if (strpos($Link, '//') === 0 && strpos($Link, '//' . $Host) !== 0) {
return false;
}
if (
// `<a href="https://127.0.0.1">`
strpos($Link, 'https://' . $Host) === 0 ||
// `<a href="http://127.0.0.1">`
strpos($Link, 'http://' . $Host) === 0
) {
return true;
}
// `<a href="foo/bar">`
return strpos($Link, '://') === false;
}
protected function parseAttributeData($attributeString) {
// Allow compact attributes
$attributeString = strtr($attributeString, array(
'#' => ' #',
'.' => ' .'
));
if (strpos($attributeString, '="') !== false || strpos($attributeString, "='") !== false) {
$attributeString = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($matches) {
$value = strtr($matches[3], array(
' #' => '#',
' .' => '.',
' ' => "\x1A"
));
return $matches[1] . $matches[2] . $value . $matches[2];
}, $attributeString);
}
$Attributes = array();
foreach (explode(' ', $attributeString) as $v) {
if (!$v) {
continue;
}
// `{#foo}`
if ($v[0] === '#' && isset($v[1])) {
$Attributes['id'] = substr($v, 1);
// `{.foo}`
} else if ($v[0] === '.' && isset($v[1])) {
$Attributes['class'][] = substr($v, 1);
// ~
} else if (strpos($v, '=') !== false) {
$vv = explode('=', $v, 2);
// `{foo=}`
if ($vv[1] === "") {
if ($vv[0] === 'class') {
continue;
}
$Attributes[$vv[0]] = "";
// `{foo="bar baz"}`
// `{foo='bar baz'}`
} else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") {
$values = stripslashes(strtr(substr(substr($vv[1], 1), 0, -1), "\x1A", ' '));
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$values = explode(' ', $values);
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], $values);
} else {
$Attributes[$vv[0]] = $values;
}
// `{foo=bar}`
} else {
if ($vv[0] === 'class' && isset($Attributes[$vv[0]])) {
$Attributes[$vv[0]] = array_merge($Attributes[$vv[0]], [$vv[1]]);
} else {
$Attributes[$vv[0]] = $vv[1];
}
}
// `{foo}`
} else {
if ($v === 'class' && isset($Attributes[$v])) {
continue;
}
$Attributes[$v] = $v;
}
}
if (isset($Attributes['class'])) {
$Attributes['class'] = implode(' ', array_unique((array) $Attributes['class']));
}
return $Attributes;
}
}
File diff suppressed because it is too large Load Diff
-686
View File
@@ -1,686 +0,0 @@
<?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 parsedownbase
{
# ~
const version = '0.8.0';
# ~
function __construct()
{
if (version_compare(parent::version, '1.7.1') < 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)
{
$Elements = $this->textElements($text);
# convert to markup
$markup = $this->elements($Elements);
# trim line breaks
$markup = trim($markup, "\n");
# 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 $Block['type'] !== 'Paragraph')
{
return;
}
$Element = array(
'name' => 'dl',
'elements' => array(),
);
$terms = explode("\n", $Block['element']['handler']['argument']);
foreach ($terms as $term)
{
$Element['elements'] []= array(
'name' => 'dt',
'handler' => array(
'function' => 'lineElements',
'argument' => $term,
'destination' => 'elements'
),
);
}
$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']['function'] = 'textElements';
$Block['dd']['handler']['argument'] .= "\n\n";
$Block['dd']['handler']['destination'] = 'elements';
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['handler']['argument'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkup($Line)
{
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{
return;
}
$Block = array(
'name' => $matches[1],
'depth' => 0,
'element' => array(
'rawHtml' => $Line['text'],
'autobreak' => true,
),
);
$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['element']['rawHtml'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['rawHtml'] .= "\n".$Line['body'];
return $Block;
}
protected function blockMarkupComplete($Block)
{
if ( ! isset($Block['void']))
{
$Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 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),
'element' => 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 = $Link !== null ? 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;
}
#
# ~
#
private $currentAbreviation;
private $currentMeaning;
protected function insertAbreviation(array $Element)
{
if (isset($Element['text']))
{
$Element['elements'] = self::pregReplaceElements(
'/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
array(
array(
'name' => 'abbr',
'attributes' => array(
'title' => $this->currentMeaning,
),
'text' => $this->currentAbreviation,
)
),
$Element['text']
);
unset($Element['text']);
}
return $Element;
}
protected function inlineText($text)
{
$Inline = parent::inlineText($text);
if (isset($this->DefinitionData['Abbreviation']))
{
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{
$this->currentAbreviation = $abbreviation;
$this->currentMeaning = $meaning;
$Inline['element'] = $this->elementApplyRecursiveDepthFirst(
array($this, 'insertAbreviation'),
$Inline['element']
);
}
}
return $Inline;
}
#
# 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' => array(
'function' => 'lineElements',
'argument' => $text,
'destination' => 'elements'
),
);
if (isset($Block['interrupted']))
{
$Block['dd']['handler']['function'] = 'textElements';
unset($Block['interrupted']);
}
$Block['element']['elements'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'elements' => array(
array('name' => 'hr'),
array(
'name' => 'ol',
'elements' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{
if ( ! isset($DefinitionData['number']))
{
continue;
}
$text = $DefinitionData['text'];
$textElements = parent::textElements($text);
$numbers = range(1, $DefinitionData['count']);
$backLinkElements = array();
foreach ($numbers as $number)
{
$backLinkElements[] = array('text' => ' ');
$backLinkElements[] = array(
'name' => 'a',
'attributes' => array(
'href' => "#fnref$number:$definitionId",
'rev' => 'footnote',
'class' => 'footnote-backref',
),
'rawHtml' => '&#8617;',
'allowRawHtmlInSafeMode' => true,
'autobreak' => false,
);
}
unset($backLinkElements[0]);
$n = count($textElements) -1;
if ($textElements[$n]['name'] === 'p')
{
$backLinkElements = array_merge(
array(
array(
'rawHtml' => '&#160;',
'allowRawHtmlInSafeMode' => true,
),
),
$backLinkElements
);
unset($textElements[$n]['name']);
$textElements[$n] = array(
'name' => 'p',
'elements' => array_merge(
array($textElements[$n]),
$backLinkElements
),
);
}
else
{
$textElements[] = array(
'name' => 'p',
'elements' => $backLinkElements
);
}
$Element['elements'][1]['elements'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'elements' => array_merge(
$textElements
),
);
}
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\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]+[ ]*)';
}
+1 -5
View File
@@ -2341,11 +2341,7 @@ eof;
if(empty($markdown)) return false;
global $app;
$app->loadClass('parsedown');
$parsedown = new parsedown;
$parsedown->voidElementSuffix = '>'; // HTML5
$parsedown = $app->loadClass('parsedown');
return $parsedown->text($markdown);
}