From a95bc60c30840631596969be3a016da3c4d040ba Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 20 Jan 2019 02:27:48 +0000 Subject: [PATCH] Implement List --- src/Components/Blocks/TList.php | 286 ++++++++++++++++++++++++++++++++ src/Parsedown.php | 157 ------------------ 2 files changed, 286 insertions(+), 157 deletions(-) create mode 100644 src/Components/Blocks/TList.php diff --git a/src/Components/Blocks/TList.php b/src/Components/Blocks/TList.php new file mode 100644 index 0000000..6001cf8 --- /dev/null +++ b/src/Components/Blocks/TList.php @@ -0,0 +1,286 @@ +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 + */ + 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 + ) + ); + } + ); + } +} diff --git a/src/Parsedown.php b/src/Parsedown.php index 1f3dc4f..236370a 100644 --- a/src/Parsedown.php +++ b/src/Parsedown.php @@ -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