leilukin-tumbleblog/includes/lib/xenocrat/markdown/block/HeadlineTrait.php

155 lines
3.7 KiB
PHP

<?php
/**
* @copyright Copyright 2014 Carsten Brandt, 2024 Daniel Pimley
* @license https://github.com/xenocrat/chyrp-markdown/blob/master/LICENSE
* @link https://github.com/xenocrat/chyrp-markdown#readme
*/
namespace xenocrat\markdown\block;
/**
* Adds headline blocks.
*
* Make sure to reset anchor link counter on prepare():
*
* ```php
* protected function prepare()
* {
* $this->headlineAnchorLinks = [];
* }
* ```
*/
trait HeadlineTrait
{
/**
* @var bool - Generate an `id` attribute for headline anchors.
*/
public $headlineAnchors = false;
/**
* @var int[] - Incrementing counter of rendered anchor links.
*/
protected $headlineAnchorLinks = [];
/**
* Identify a line as a headline.
*/
protected function identifyHeadline($line, $lines, $current): bool
{
return (
// ATX headline.
preg_match('/^ {0,3}(#{1,6})([ \t]|$)/', $line)
// setext headline.
|| !empty($lines[$current + 1])
&& preg_match('/^ {0,3}(\-+|=+)\s*$/', $lines[$current + 1])
);
}
/**
* Consume lines for a headline.
*/
protected function consumeHeadline($lines, $current): array
{
if (
preg_match(
'/^ {0,3}(#{1,6})([ \t]|$)/',
$lines[$current],
$matches
)
) {
// ATX headline.
$line = ltrim($lines[$current], "# \t");
$line = preg_replace('/ +(#+ *)?$/', '', $line);
$block = [
'headline',
'content' => $this->parseInline($line),
'level' => strlen($matches[1]),
];
return [$block, $current];
} else {
// Setext headline.
$line = trim($lines[$current]);
$block = [
'headline',
'content' => $this->parseInline($line),
'level' => substr_count($lines[$current + 1], '=') ? 1 : 2,
];
return [$block, $current + 1];
}
}
/**
* Renders a headline.
*/
protected function renderHeadline($block): string
{
$tag = 'h' . $block['level'];
$id = '';
$content = $this->renderAbsy($block['content']);
if (
$this->headlineAnchors
&& class_exists('\\IntlChar')
&& function_exists('mb_str_split')
&& function_exists('mb_convert_case')
) {
$str = $this->unEscapeHtmlEntities(
strip_tags($content),
ENT_QUOTES | ENT_SUBSTITUTE
);
$exploded = mb_str_split($str, 1, 'UTF-8');
foreach ($exploded as $chr) {
$type = \IntlChar::charType($chr);
if (
$chr === ' ' || $chr === '-' || $chr === '_'
|| $type === \IntlChar::CHAR_CATEGORY_UPPERCASE_LETTER
|| $type === \IntlChar::CHAR_CATEGORY_LOWERCASE_LETTER
|| $type === \IntlChar::CHAR_CATEGORY_TITLECASE_LETTER
|| $type === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER
|| $type === \IntlChar::CHAR_CATEGORY_OTHER_LETTER
|| $type === \IntlChar::CHAR_CATEGORY_DECIMAL_DIGIT_NUMBER
|| $type === \IntlChar::CHAR_CATEGORY_LETTER_NUMBER
|| $type === \IntlChar::CHAR_CATEGORY_CONNECTOR_PUNCTUATION
|| $type === \IntlChar::CHAR_CATEGORY_NON_SPACING_MARK
|| $type === \IntlChar::CHAR_CATEGORY_ENCLOSING_MARK
|| $type === \IntlChar::CHAR_CATEGORY_COMBINING_SPACING_MARK
) {
$id .= ($chr === ' ') ?
'-' :
mb_convert_case($chr, MB_CASE_LOWER, 'UTF-8');
}
}
if ($id !== '') {
$prefix = $this->getContextId();
if ($prefix !== '') {
$prefix .= '-';
}
while (isset($this->headlineAnchorLinks[$id])) {
$id .= '-' . $this->headlineAnchorLinks[$id]++;
}
$this->headlineAnchorLinks[$id] = 1;
$id = ' id="'
. $prefix
. $this->escapeHtmlEntities(
$id,
ENT_COMPAT | ENT_SUBSTITUTE
)
. '"';
}
}
return "<{$tag}{$id}>{$content}</{$tag}>\n";
}
abstract protected function parseInline($text);
abstract protected function renderAbsy($absy);
abstract protected function escapeHtmlEntities($text, $flags = 0);
abstract protected function unEscapeHtmlEntities($text, $flags = 0);
}