1
0
mirror of https://github.com/erusev/parsedown.git synced 2023-08-10 21:13:06 +03:00

Implement List

This commit is contained in:
Aidan Woods 2019-01-20 02:27:48 +00:00
parent 07c2566042
commit a95bc60c30
No known key found for this signature in database
GPG Key ID: 9A6A8EFAA512BBB9
2 changed files with 286 additions and 157 deletions

View File

@ -0,0 +1,286 @@
<?php
namespace Erusev\Parsedown\Components\Blocks;
use Erusev\Parsedown\AST\Handler;
use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\ContinuableBlock;
use Erusev\Parsedown\Html\Renderables\Element;
use Erusev\Parsedown\Parsedown;
use Erusev\Parsedown\Parsing\Context;
use Erusev\Parsedown\Parsing\Lines;
use Erusev\Parsedown\State;
final class TList implements ContinuableBlock
{
use ContinuableBlockDefaultInterrupt, BlockAcquisition;
/** @var Lines[] */
private $Lis;
/** @var string|null */
private $listStart;
/** @var bool */
private $isLoose;
/** @var int */
private $indent;
/** @var 'ul'|'ol' */
private $type;
/** @var string */
private $marker;
/** @var string */
private $markerType;
/** @var string */
private $markerTypeRegex;
/**
* @param Lines[] $Lis
* @param string|null $listStart
* @param bool $isLoose
* @param int $indent
* @param 'ul'|'ol' $type
* @param string $marker
* @param string $markerType
* @param string $markerTypeRegex
*/
public function __construct(
$Lis,
$listStart,
$isLoose,
$indent,
$type,
$marker,
$markerType,
$markerTypeRegex
) {
$this->Lis = $Lis;
$this->listStart = $listStart;
$this->isLoose = $isLoose;
$this->indent = $indent;
$this->type = $type;
$this->marker = $marker;
$this->markerType = $markerType;
$this->markerTypeRegex = $markerTypeRegex;
}
/**
* @param Context $Context
* @param Block|null $Block
* @param State|null $State
* @return static|null
*/
public static function build(
Context $Context,
Block $Block = null,
State $State = null
) {
list($type, $pattern) = (
$Context->line()->text()[0] <= '-'
? ['ul', '[*+-]']
: ['ol', '[0-9]{1,9}+[.\)]']
);
if (\preg_match(
'/^('.$pattern.'([ ]++|$))(.*+)/',
$Context->line()->text(),
$matches
)) {
$contentIndent = \strlen($matches[2]);
if ($contentIndent >= 5) {
$contentIndent -= 1;
$matches[1] = \substr($matches[1], 0, -$contentIndent);
$matches[3] = \str_repeat(' ', $contentIndent) . $matches[3];
} elseif ($contentIndent === 0) {
$matches[1] .= ' ';
}
$text = $matches[3];
$markerWithoutWhitespace = \rtrim($matches[1], " \t");
$marker = $matches[1];
$indent = $Context->line()->indent();
$indentOffset = $Context->line()->indentOffset() + $Context->line()->indent() + \strlen($marker);
$markerType = (
$type === 'ul'
? $markerWithoutWhitespace
: \substr($markerWithoutWhitespace, -1)
);
$markerTypeRegex = \preg_quote($markerType, '/');
/** @var string|null */
$listStart = null;
if ($type === 'ol') {
/** @psalm-suppress PossiblyFalseArgument */
$listStart = \ltrim(\strstr($matches[1], $markerType, true), '0') ?: '0';
if (
$listStart !== '1'
and isset($Block)
and $Block instanceof Paragraph
and ! $Context->previousEmptyLines() > 0
) {
return null;
}
}
return new self(
[!empty($text) ? Lines::fromTextLines($text, $indentOffset) : Lines::none()],
$listStart,
false,
$indent,
$type,
$marker,
$markerType,
$markerTypeRegex
);
}
}
/**
* @param Context $Context
* @return self|null
*/
public function continue(Context $Context)
{
if ($this->interrupted and \end($this->Lis)->isEmpty()) {
return null;
}
$requiredIndent = $this->indent + \strlen($this->marker);
$isLoose = $this->isLoose;
$indent = $Context->line()->indent();
$Lis = $this->Lis;
if ($Context->line()->indent() < $requiredIndent
&& ((
$this->type === 'ol'
&& \preg_match('/^([0-9]++'.$this->markerTypeRegex.')(?:[ ]++(.*)|$)/', $Context->line()->text(), $matches)
) || (
$this->type === 'ul'
&& \preg_match('/^('.$this->markerTypeRegex.')(?:[ ]++(.*)|$)/', $Context->line()->text(), $matches)
))
) {
if ($Context->previousEmptyLines() > 0) {
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingBlankLines(1);
$isLoose = true;
}
$text = isset($matches[2]) ? $matches[2] : '';
$indentOffset = $Context->line()->indentOffset() + $Context->line()->indent() + \strlen($matches[1]);
$Lis[] = Lines::fromTextLines($text, $indentOffset);
return new self(
$Lis,
$this->listStart,
$isLoose,
$indent,
$this->type,
$this->marker,
$this->markerType,
$this->markerTypeRegex
);
} elseif ($Context->line()->indent() < $requiredIndent && self::build($Context) !== null) {
return null;
}
if ($Context->line()->indent() >= $requiredIndent) {
if ($Context->previousEmptyLines() > 0) {
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingBlankLines(1);
$isLoose = true;
}
$text = $Context->line()->ltrimBodyUpto($requiredIndent);
$indentOffset = $Context->line()->indentOffset() + $Context->line()->indent();
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingTextLines($text, $indentOffset);
return new self(
$Lis,
$this->listStart,
$isLoose,
$this->indent,
$this->type,
$this->marker,
$this->markerType,
$this->markerTypeRegex
);
}
if (! $Context->previousEmptyLines() > 0) {
$text = $Context->line()->ltrimBodyUpto($requiredIndent);
$indentOffset = $Context->line()->indentOffset() + $Context->line()->indent();
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingTextLines($text, $indentOffset);
return new self(
$Lis,
$this->listStart,
$isLoose,
$this->indent,
$this->type,
$this->marker,
$this->markerType,
$this->markerTypeRegex
);
}
return null;
}
/**
* @return Handler<Element>
*/
public function stateRenderable(Parsedown $Parsedown)
{
return new Handler(
/** @return Element */
function (State $State) use ($Parsedown) {
return new Element(
$this->type,
(
isset($this->listStart) && $this->listStart !== '1'
? ['start' => $this->listStart]
: []
),
\array_map(
/** @return Element */
function (Lines $Lines) use ($State, $Parsedown) {
if ($this->isLoose && $Lines->trailingBlankLines() === 0) {
$Lines = $Lines->appendingBlankLines(1);
}
$Renderables = $State->applyTo($Parsedown->lines($Lines));
if (! $Lines->containsBlankLines()
and isset($Renderables[0])
and $Renderables[0] instanceof Element
and $Renderables[0]->name() === 'p'
) {
$Contents = $Renderables[0]->contents();
unset($Renderables[0]);
$Renderables = \array_merge($Contents ?: [], $Renderables);
}
return new Element('li', [], $Renderables);
},
$this->Lis
)
);
}
);
}
}

View File

@ -334,163 +334,6 @@ class Parsedown
return $Block;
}
#
# List
protected function blockList(Context $Context, array $CurrentBlock = null)
{
list($name, $pattern) = $Context->line()->text()[0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]{1,9}+[.\)]'];
if (\preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Context->line()->text(), $matches)) {
$contentIndent = \strlen($matches[2]);
if ($contentIndent >= 5) {
$contentIndent -= 1;
$matches[1] = \substr($matches[1], 0, -$contentIndent);
$matches[3] = \str_repeat(' ', $contentIndent) . $matches[3];
} elseif ($contentIndent === 0) {
$matches[1] .= ' ';
}
$markerWithoutWhitespace = \strstr($matches[1], ' ', true);
$Block = [
'indent' => $Context->line()->indent(),
'pattern' => $pattern,
'data' => [
'type' => $name,
'marker' => $matches[1],
'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : \substr($markerWithoutWhitespace, -1)),
],
'element' => [
'name' => $name,
'elements' => [],
],
];
$Block['data']['markerTypeRegex'] = \preg_quote($Block['data']['markerType'], '/');
if ($name === 'ol') {
$listStart = \ltrim(\strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
if ($listStart !== '1') {
if (
isset($CurrentBlock)
and $CurrentBlock['type'] === 'Paragraph'
and ! $Context->previousEmptyLines() > 0
) {
return;
}
$Block['element']['attributes'] = ['start' => $listStart];
}
}
$Block['li'] = [
'name' => 'li',
'handler' => [
'function' => 'li',
'argument' => !empty($matches[3]) ? Lines::fromTextLines($matches[3], 0) : Lines::none(),
'destination' => 'elements'
]
];
$Block['element']['elements'] []= & $Block['li'];
return $Block;
}
}
protected function blockListContinue(Context $Context, array $Block)
{
if ($Context->previousEmptyLines() > 0 and $Block['li']['handler']['argument']->isEmpty()) {
return null;
}
$requiredIndent = ($Block['indent'] + \strlen($Block['data']['marker']));
if ($Context->line()->indent() < $requiredIndent
and (
(
$Block['data']['type'] === 'ol'
and \preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Context->line()->text(), $matches)
) or (
$Block['data']['type'] === 'ul'
and \preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Context->line()->text(), $matches)
)
)
) {
if ($Context->previousEmptyLines() > 0) {
$Block['li']['handler']['argument'] = $Block['li']['handler']['argument']->appendingBlankLines(1);
$Block['loose'] = true;
unset($Block['interrupted']);
}
unset($Block['li']);
$text = isset($matches[1]) ? $matches[1] : '';
$Block['indent'] = $Context->line()->indent();
$Block['li'] = [
'name' => 'li',
'handler' => [
'function' => 'li',
'argument' => Lines::fromTextLines($text, 0),
'destination' => 'elements'
]
];
$Block['element']['elements'] []= & $Block['li'];
return $Block;
} elseif ($Context->line()->indent() < $requiredIndent and $this->blockList($Context)) {
return null;
}
if ($Context->line()->text()[0] === '[' and $this->blockReference($Context)) {
return $Block;
}
if ($Context->line()->indent() >= $requiredIndent) {
if ($Context->previousEmptyLines() > 0) {
$Block['li']['handler']['argument'] = $Block['li']['handler']['argument']->appendingBlankLines(1);
$Block['loose'] = true;
unset($Block['interrupted']);
}
$text = $Context->line()->ltrimBodyUpto($requiredIndent);
$Block['li']['handler']['argument'] = $Block['li']['handler']['argument']->appendingTextLines($text, 0);
return $Block;
}
if (! $Context->previousEmptyLines() > 0) {
$text = $Context->line()->ltrimBodyUpto($requiredIndent);
$Block['li']['handler']['argument'] = $Block['li']['handler']['argument']->appendingTextLines($text, 0);
return $Block;
}
}
protected function blockListComplete(array $Block)
{
if (isset($Block['loose'])) {
foreach ($Block['element']['elements'] as &$li) {
if ($li['handler']['argument']->trailingBlankLines() === 0) {
$li['handler']['argument'] = $li['handler']['argument']->appendingBlankLines(1);
}
}
}
return $Block;
}
#
# Rule