School/Perlite/perlite/.src/PerliteParsedown.php
2025-09-01 20:50:07 +02:00

1159 lines
39 KiB
PHP

<?php
/*!
* Perlite v1.6.0 (https://github.com/secure-77/Perlite)
* Author: sec77 (https://secure77.de)
* Licensed under MIT (https://github.com/secure-77/Perlite/blob/main/LICENSE)
*/
namespace Perlite;
use Parsedown;
class PerliteParsedown extends Parsedown
{
public function __construct()
{
$this->BlockTypes['!'] = array('YouTube');
}
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);
# YAML front matter
$parsedYamlBlockText = "";
if ($lines[0] === '---') {
# search ending
$yamlBlockArray = array_slice($lines, 1, count($lines));
$endIndex = 0;
foreach ($yamlBlockArray as $line) {
$endIndex += 1;
if ($line === '---') {
break;
}
}
$yamlBlockArray = array_slice($lines, 0, $endIndex);
$yamlBlockText = implode("\n", $yamlBlockArray);
$lines = array_slice($lines, $endIndex + 1, count($lines));
$parsedYamlBlockText = $this->yamlFrontmatter($yamlBlockText);
}
# iterate through lines to identify blocks
$markup = $this->lines($lines);
# add front matter
$markup = $parsedYamlBlockText . $markup;
# trim line breaks
$markup = trim($markup, "\n");
return $markup;
}
protected function yamlFrontmatter($yaml)
{
if (!extension_loaded("yaml")) {
return "YAML front matter found but PHP YAML Parse extension is missing!<br>";
} else {
// var_dump($yaml);
$parsed = yaml_parse($yaml);
$yamlText = '
<div class="mod-header">
<div class="metadata-properties-heading">
<div class="collapse-indicator collapse-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon right-triangle"><path d="M3 8L12 17L21 8">
</path></svg>
</div>
<div class="metadata-properties-title">Properties</div>
</div>
<div class="metadata-container" tabindex="-1" data-property-count="1">
<div class="metadata-content">
<div class="metadata-properties">
';
# Parse Aliase if they are there
if (array_key_exists("aliases", $parsed)) {
$yamlText .= '
<div class="metadata-property" tabindex="0" data-property-key="tags" data-property-type="multitext">
<div class="metadata-property-key">
<span class="metadata-property-icon" aria-disabled="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-forward">
<polyline points="15 17 20 12 15 7"/>
<path d="M4 18v-2a4 4 0 0 1 4-4h12"/>
</svg>
</span>
<span class="metadata-text">aliases</span>
</div>
<div class="metadata-property-value">
<div class="multi-select-container">';
foreach ($parsed["aliases"] as $alias) {
$yamlText .= '<div class="multi-select-pill multi-select-pill-content">' . $alias . '</div>';
}
$yamlText .= '</div></div></div>';
}
# Parse Tags if they are there
if (isset($parsed["tags"])) {
$yamlText .= '
<div class="metadata-property" tabindex="0" data-property-key="tags" data-property-type="multitext">
<div class="metadata-property-key">
<span class="metadata-property-icon" aria-disabled="false">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-tags">
<path d="M9 5H2v7l6.29 6.29c.94.94 2.48.94 3.42 0l3.58-3.58c.94-.94.94-2.48 0-3.42L9 5Z"/>
<path d="M6 9.01V9"/>
<path d="m15 5 6.3 6.3a2.4 2.4 0 0 1 0 3.4L17 19"/>
</svg>
</span>
<span class="metadata-text">tags</span>
</div>
<div class="metadata-property-value">
<div class="multi-select-container">
';
foreach ($parsed["tags"] as $tag) {
$Block = array(
'element' => array(
'name' => 'div',
'text' => '#' . $tag,
'attributes' => array(
'class' => 'multi-select-pill multi-select-pill-content'
),
'handler' => 'line',
),
);
$yamlText .= $this->elements($Block);
}
$yamlText .= '</div></div></div>';
}
$yamlText .= '</div></div></div></div></div>';
return $yamlText;
}
}
#
# Callout (based on blockQuotes)
# See: https://help.obsidian.md/How+to/Use+callouts
# Callout Block
protected function blockQuote($Line)
{
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) {
$Block = array(
'element' => array(
'name' => 'blockquote',
'handler' => 'lines',
'text' => (array) $matches[1],
),
);
if (preg_match('/^>\s?\[\!(.*?)\](.*?)$/m', $Line['text'], $matches)) {
$type = strtolower($matches[1]);
$title = $matches[2];
$calloutTitle = $title ?: ucfirst($type);
# Handle collapsible callouts
$calloutclass = 'callout';
$calloutStyle = 'unset';
$collapsibleIcon = array(
'name' => 'div',
'text' => ''
);
$isCollapsed = '';
$needCollapseIcon = False;
$isCollapsedIcon = '';
$calloutTitleClass = 'callout-title-inner';
if (substr($calloutTitle, 0, 1) == '+') {
$calloutTitle = substr($calloutTitle, 1);
$calloutclass = 'callout is-collapsible';
$calloutTitleClass = 'callout-title-inner is-collapsible';
$calloutStyle = 'unset';
$needCollapseIcon = True;
}
if (substr($calloutTitle, 0, 1) == '-') {
$calloutTitle = substr($calloutTitle, 1);
$calloutclass = 'callout is-collapsible is-collapsed';
$calloutStyle = 'none';
$isCollapsed = 'is-collapsed-callout';
$isCollapsedIcon = 'is-collapsed';
$calloutTitleClass = 'callout-title-inner is-collapsed';
$needCollapseIcon = True;
}
if ($needCollapseIcon) {
$collapsibleIcon = array(
'name' => 'div',
'attributes' => array('class' => 'callout-fold ' . $isCollapsedIcon),
'elements' => array(
# svg
array(
'name' => 'svg',
'attributes' => array(
'xmlns' => 'http://www.w3.org/2000/svg',
'width' => '24',
'height' => '24',
'viewBox' => '0 0 24 24',
'fill' => 'none',
'stroke' => 'currentColor',
'stroke-width' => '2',
'stroke-linecap' => 'round',
'stroke-linejoin' => 'round',
'class' => 'svg-icon lucide-chevron-down',
),
# pathes and lines
'elements' => array(array('name' => '<path d="m6 9 6 6 6-6"/>')),
),
),
);
}
$Block = array(
'element' => array(
'name' => 'div',
'attributes' => array(
'data-callout' => $type,
'class' => $calloutclass
),
'elements' => array(
array(
'name' => 'div',
'attributes' => array('class' => 'callout-title'),
'elements' => array(
# callout icon
array(
'name' => 'div',
'attributes' => array('class' => 'callout-icon'),
'elements' => array(
# svg
array(
'name' => 'svg',
'attributes' => array(
'xmlns' => 'http://www.w3.org/2000/svg',
'width' => '24',
'height' => '24',
'viewBox' => '0 0 24 24',
'fill' => 'none',
'stroke' => 'currentColor',
'stroke-width' => '2',
'stroke-linecap' => 'round',
'stroke-linejoin' => 'round',
'class' => $this->getCalloutIcon($type)[0],
),
# pathes and lines
'elements' => $this->getCalloutIcon($type)[1]
),
),
),
# callout title
array(
'name' => 'div',
'attributes' => array('class' => $calloutTitleClass),
'text' => (array) $calloutTitle,
'handler' => 'lines',
),
# collapsible icon
$collapsibleIcon,
),
),
# callout content
array(
'name' => 'div',
'attributes' => array(
'class' => 'callout-content ' . $isCollapsed,
),
'handler' => 'lines',
),
)
),
);
}
}
return $Block;
}
# Callout Icons
protected function getCalloutIcon($callType)
{
// default = info
$class = 'svg-icon lucide-pencil';
$pathes = array(
array('name' => 'line x1="18" y1="2" x2="22" y2="6"'),
array('name' => 'path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"')
);
$callType = strtolower($callType);
switch ($callType) {
case 'abstract':
$class = 'svg-icon lucide-clipboard-list';
$pathes = array(
array('name' => 'rect x="8" y="2" width="8" height="4" rx="1" ry="1"'),
array('name' => 'path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"'),
array('name' => 'path d="M12 11h4"'),
array('name' => 'path d="M12 16h4"'),
array('name' => 'path d="M8 11h.01"'),
array('name' => 'path d="M8 16h.01"'),
);
break;
case 'info':
$class = 'svg-icon lucide-info';
$pathes = array(
array('name' => 'circle cx="12" cy="12" r="10"'),
array('name' => 'line x1="12" y1="16" x2="12" y2="12"'),
array('name' => 'line x1="12" y1="8" x2="12.01" y2="8"'),
);
break;
case 'todo':
$class = 'svg-icon lucide-check-circle-2';
$pathes = array(
array('name' => 'path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"'),
array('name' => 'path d="m9 12 2 2 4-4"'),
);
break;
case 'tip':
$class = 'svg-icon lucide-flame';
$pathes = array(
array('name' => 'path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"'),
);
break;
case 'success':
$class = 'svg-icon lucide-check';
$pathes = array(
array('name' => 'polyline points="20 6 9 17 4 12"'),
);
break;
case 'question':
$class = 'svg-icon lucide-help-circle';
$pathes = array(
array('name' => 'circle cx="12" cy="12" r="10"'),
array('name' => 'path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"'),
array('name' => 'line x1="12" y1="17" x2="12.01" y2="17"'),
);
break;
case 'warning':
$class = 'svg-icon lucide-alert-triangle';
$pathes = array(
array('name' => 'path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"'),
array('name' => 'line x1="12" y1="9" x2="12" y2="13"'),
array('name' => 'line x1="12" y1="17" x2="12.01" y2="17"'),
);
break;
case 'failure':
$class = 'svg-icon lucide-x';
$pathes = array(
array('name' => 'line x1="18" y1="6" x2="6" y2="18"'),
array('name' => 'line x1="6" y1="6" x2="18" y2="18"'),
);
break;
case 'danger':
$class = 'svg-icon lucide-zap';
$pathes = array(
array('name' => 'polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"'),
);
break;
case 'bug':
$class = 'svg-icon lucide-bug';
$pathes = array(
array('name' => 'rect x="8" y="6" width="8" height="14" rx="4"'),
array('name' => 'path d="m19 7-3 2"'),
array('name' => 'path d="m5 7 3 2"'),
array('name' => 'path d="m19 19-3-2"'),
array('name' => 'path d="m5 19 3-2"'),
array('name' => 'path d="M20 13h-4"'),
array('name' => 'path d="M4 13h4"'),
array('name' => 'path d="m10 4 1 2"'),
array('name' => 'path d="m14 4-1 2"'),
);
break;
case 'example':
$class = 'svg-icon lucide-list';
$pathes = array(
array('name' => 'line x1="8" y1="6" x2="21" y2="6"'),
array('name' => 'line x1="8" y1="12" x2="21" y2="12"'),
array('name' => 'line x1="8" y1="18" x2="21" y2="18"'),
array('name' => 'line x1="3" y1="6" x2="3.01" y2="6"'),
array('name' => 'line x1="3" y1="12" x2="3.01" y2="12"'),
array('name' => 'line x1="3" y1="18" x2="3.01" y2="18"'),
);
break;
case 'quote':
$class = 'svg-icon lucide-quote';
$pathes = array(
array('name' => 'path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"'),
array('name' => 'path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"'),
);
break;
}
return array($class, $pathes);
}
# Callout Block inner
protected function blockQuoteContinue($Line, array $Block)
{
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) {
if (isset($Block['interrupted'])) {
unset($Block['interrupted']);
}
$quoteContent = $matches[1];
if (isset($Block['element']['elements'])) {
$Block['element']['elements'][1]['text'][] = $quoteContent;
} else {
$Block['element']['text'][] = $quoteContent;
}
return $Block;
}
if (!isset($Block['interrupted'])) {
if (isset($Block['element']['elements'])) {
$Block['element']['elements'][1]['text'][] = $Line['text'];
} else {
$Block['element']['text'][] = $Line['text'];
}
return $Block;
}
}
# blockHeader seperated from Tags
protected function blockHeader($Line)
{
if (isset($Line['text'][1]) && ($Line['text'][1] === ' ' || $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;
}
}
protected function blockYouTube($Line)
{
if ( ! isset($Line['text'][1]) or $Line['text'][1] !== '[')
{
return;
}
$Line['text']= substr($Line['text'], 1);
$Link = $this->inlineLink($Line);
if ($Link === null)
{
return;
}
// See: https://stackoverflow.com/a/64320469
$yt = preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $Link['element']['attributes']['href'], $match);
if (! $yt)
{
return;
}
$youtubeId = $match[1];
$Block = array(
'element' => array(
'name' => 'iframe',
'text' => $Line['text'],
'handler' => 'line',
'attributes' => array(
'class' => 'external-embed mod-receives-events', 'sandbox' => 'allow-forms allow-presentation allow-same-origin allow-scripts allow-modals allow-popups',
'allow' => 'fullscreen',
'frameborder' => '0',
'src' => 'https://www.youtube.com/embed/'. $youtubeId,
),
),
);
return $Block;
}
# extend to obsidian tags
protected $inlineMarkerList = '!"*$_#&[:<>`~\\=';
protected $InlineTypes = array(
'"' => array('SpecialCharacter'),
'!' => array('Image'),
'&' => array('SpecialCharacter'),
'*' => array('Emphasis'),
':' => array('Url'),
'<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
'>' => array('SpecialCharacter'),
'[' => array('Link'),
'#' => array('Tag'),
'$' => array('Katex'),
'_' => array('Emphasis'),
'`' => array('Code'),
'~' => array('Strikethrough'),
'\\' => array('EscapeSequence'),
'=' => array('Highlight'),
);
# handle highlight code
protected function inlineHighlight($Excerpt)
{
$marker = $Excerpt['text'][1];
if (preg_match('/^==(.+?)==/s', $Excerpt['text'], $matches))
{
$content = $matches[1];
$Inline = array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'span',
'text' => $content,
'attributes' => array(
'class' => 'cm-highlight'
),
),
);
return $Inline;
}
}
# handle katex code
protected function inlineKatex($Excerpt)
{
$marker = $Excerpt['text'][0];
if (preg_match('/^(\\'.$marker.'+)[ ]*(.+?)[ ]*(?<!\\'.$marker.')\1(?!\\'.$marker.')/s', $Excerpt['text'], $matches))
{
$text = $matches[0];
$text = preg_replace("/[ ]*\n/", ' ', $text);
$name = 'katex';
if ($matches[1] === '$') {
$name = 'katex-inline';
}
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => $name,
'text' => $text,
),
);
}
}
# handle obsidian tags
protected function inlineTag($Excerpt)
{
if (!isset($Excerpt['text'][1]) or $Excerpt['text'][0] !== '#') {
return;
}
# ignore tags in links
$len = strlen($Excerpt['context']);
if ($len == 0) {
return;
}
if (substr(trim($Excerpt['context']), -1) === ']') {
return;
}
if (preg_match("/(^| )#[\w'-\/]+/ui", $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) {
$tag = $matches[0][0];
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
'text' => $tag,
'attributes' => array(
'href' => $tag,
'class' => 'tag'
),
),
);
return $Inline;
}
}
protected function blockList($Line)
{
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
if (preg_match('/(- \[(x| )\])(.*)/', $Line['text'], $matches)) {
$text = isset($matches[3]) ? $matches[3] : '';
$isActive = $matches[2];
$checked = '';
if ($isActive === 'x') {
$checked = 'checked';
}
$Block = array(
'element' => array(
'name' => 'div',
'elements' => array(
array(
'name' => 'div',
'attributes' => array(
'class' => 'HyperMD-list-line HyperMD-list-line-1 HyperMD-task-line cm-line',
'data-task' => $isActive,
),
),
array(
'name' => 'label',
'attributes' => array('class' => 'task-list-label'),
'elements' => array(
array(
'name' => 'input',
'attributes' => array(
'class' => 'task-list-item-checkbox',
'type' => 'checkbox',
'data-task' => $isActive,
$checked => '',
),
),
array(
'name' => 'label',
'attributes' => array('class' => 'cm-widgetBuffer'),
'text' => $text,
),
),
),
),
),
);
return $Block;
}
if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
{
$Block = array(
'indent' => $Line['indent'],
'pattern' => $pattern,
'element' => array(
'name' => $name,
'handler' => 'elements',
),
);
if($name === 'ol')
{
$listStart = stristr($matches[0], '.', true);
if($listStart !== '1')
{
$Block['element']['attributes'] = array('start' => $listStart);
}
}
$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 (preg_match('/(- \[(x| )\])(.*)/', $Line['text'], $matches)) {
$text = isset($matches[3]) ? $matches[3] : '';
$isActive = $matches[2];
$checked = '';
if ($isActive === 'x') {
$checked = 'checked';
}
$conBlock = array(
'name' => 'div',
'attributes' => array(
'class' => 'HyperMD-list-line HyperMD-list-line-1 HyperMD-task-line cm-line',
'data-task' => $isActive,
),
'elements' => array(
array(
'name' => 'label',
'attributes' => array('class' => 'task-list-label'),
'elements' => array(
array(
'name' => 'input',
'attributes' => array(
'class' => 'task-list-item-checkbox',
'type' => 'checkbox',
'data-task' => $isActive,
$checked => '',
),
),
array(
'name' => 'label',
'attributes' => array('class' => 'cm-widgetBuffer'),
'text' => $text,
),
),
),
)
);
$Block['element']['elements'][ ] = & $conBlock;
return $Block;
}
$Block['indent'] = isset($Block['indent']) ? $Block['indent'] : '0';
if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
{
if (isset($Block['interrupted']))
{
$Block['li']['text'] []= '';
$Block['loose'] = true;
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;
}
}
# handle external Urls
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)) {
$url = $matches[0][0];
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
'text' => $url,
'attributes' => array(
'href' => $url,
'class' => 'external-link perlite-external-link',
'target' => '_blank',
'rel' => 'noopener noreferrer',
),
),
);
return $Inline;
}
}
# handle external obsidian Urls
protected function inlineLink($Excerpt)
{
$Element = array(
'name' => 'a',
'handler' => 'line',
'nonNestables' => array('Url', 'Link'),
'text' => null,
'attributes' => array(
'href' => null,
'title' => null,
'class' => 'external-link perlite-external-link',
'target' => '_blank',
'rel' => 'noopener noreferrer',
),
);
$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('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $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'];
}
return array(
'extent' => $extent,
'element' => $Element,
);
}
# adjusted to support nested elements
protected function element(array $Element)
{
if ($this->safeMode) {
$Element = $this->sanitiseElement($Element);
}
$markup = '<' . $Element['name'];
if (isset($Element['attributes'])) {
foreach ($Element['attributes'] as $name => $value) {
if ($value === null) {
continue;
}
$markup .= ' ' . $name . '="' . self::escape($value) . '"';
}
}
$permitRawHtml = false;
# nested element handling
$closing = false;
if (isset($Element['elements'])) {
$markup .= '>';
$markup .= $this->elements($Element['elements']);
$closing = true;
} elseif (isset($Element['text'])) {
$text = $Element['text'];
} elseif (isset($Element['rawHtml'])) {
$text = $Element['rawHtml'];
$allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
$permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
}
if (isset($text)) {
$markup .= '>';
if (!isset($Element['nonNestables'])) {
$Element['nonNestables'] = array();
}
if (isset($Element['handler'])) {
$markup .= $this->{$Element['handler']}($text, $Element['nonNestables']);
} elseif (!$permitRawHtml) {
$markup .= self::escape($text, true);
} else {
$markup .= $text;
}
$markup .= '</' . $Element['name'] . '>';
} elseif ($closing) {
$markup .= '</' . $Element['name'] . '>';
} else {
$markup .= ' />';
}
return $markup;
}
# adjusted to handle interuppted quote blocks
protected 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 - strlen(utf8_decode($input)) % 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'])) {
if ($CurrentBlock['type'] === 'Quote') {
if (!isset($CurrentBlock['interrupted'])) {
$Block = $this->{'block' . $CurrentBlock['type'] . 'Continue'}($Line, $CurrentBlock);
if (isset($Block)) {
$CurrentBlock = $Block;
continue;
} else {
if ($this->isBlockCompletable($CurrentBlock['type'])) {
$CurrentBlock = $this->{'block' . $CurrentBlock['type'] . 'Complete'}($CurrentBlock);
}
}
}
} else {
$Block = $this->{'block' . $CurrentBlock['type'] . 'Continue'}($Line, $CurrentBlock);
if (isset($Block)) {
$CurrentBlock = $Block;
continue;
} else {
if ($this->isBlockCompletable($CurrentBlock['type'])) {
$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 ($this->isBlockContinuable($blockType)) {
$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 $this->isBlockCompletable($CurrentBlock['type'])) {
$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;
}
}