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

364 lines
9.8 KiB
PHP
Raw Normal View History

2013-11-09 01:40:00 +04:00
<?php
2018-04-17 16:44:38 +03:00
namespace Erusev\Parsedown;
2013-07-11 00:22:16 +04:00
2019-01-20 05:39:31 +03:00
use Erusev\Parsedown\AST\StateRenderable;
use Erusev\Parsedown\Components\Block;
use Erusev\Parsedown\Components\Blocks\BlockQuote;
use Erusev\Parsedown\Components\Blocks\Comment;
use Erusev\Parsedown\Components\Blocks\FencedCode;
use Erusev\Parsedown\Components\Blocks\Header;
use Erusev\Parsedown\Components\Blocks\IndentedCode;
use Erusev\Parsedown\Components\Blocks\Markup as BlockMarkup;
use Erusev\Parsedown\Components\Blocks\Paragraph;
use Erusev\Parsedown\Components\Blocks\Reference;
use Erusev\Parsedown\Components\Blocks\Rule;
use Erusev\Parsedown\Components\Blocks\SetextHeader;
use Erusev\Parsedown\Components\Blocks\Table;
use Erusev\Parsedown\Components\Blocks\TList;
use Erusev\Parsedown\Components\ContinuableBlock;
use Erusev\Parsedown\Components\Inline;
use Erusev\Parsedown\Components\Inlines\Code;
use Erusev\Parsedown\Components\Inlines\Email;
use Erusev\Parsedown\Components\Inlines\Emphasis;
use Erusev\Parsedown\Components\Inlines\EscapeSequence;
use Erusev\Parsedown\Components\Inlines\Image;
use Erusev\Parsedown\Components\Inlines\Link;
use Erusev\Parsedown\Components\Inlines\Markup as InlineMarkup;
use Erusev\Parsedown\Components\Inlines\PlainText;
use Erusev\Parsedown\Components\Inlines\SpecialCharacter;
use Erusev\Parsedown\Components\Inlines\Strikethrough;
use Erusev\Parsedown\Components\Inlines\Url;
use Erusev\Parsedown\Components\Inlines\UrlTag;
use Erusev\Parsedown\Components\StateUpdatingBlock;
use Erusev\Parsedown\Html\Renderable;
2019-01-20 05:39:31 +03:00
use Erusev\Parsedown\Html\Renderables\Invisible;
use Erusev\Parsedown\Html\Renderables\Text;
2018-12-05 17:07:55 +03:00
use Erusev\Parsedown\Parsing\Context;
2019-01-20 05:39:31 +03:00
use Erusev\Parsedown\Parsing\Excerpt;
2018-12-05 13:30:49 +03:00
use Erusev\Parsedown\Parsing\Line;
2018-12-05 17:07:55 +03:00
use Erusev\Parsedown\Parsing\Lines;
2018-12-05 13:30:49 +03:00
final class Parsedown
2013-07-11 00:22:16 +04:00
{
2014-04-17 11:59:35 +04:00
# ~
2013-11-09 01:40:00 +04:00
2018-04-17 16:44:38 +03:00
const version = '2.0.0-dev';
2015-01-19 18:11:13 +03:00
# ~
2019-01-20 05:44:34 +03:00
/** @var State */
private $State;
public function __construct(State $State = null)
{
$this->State = $State ?: new State;
}
/**
* @param string $text
* @return string
*/
2018-12-04 19:24:25 +03:00
public function text($text)
{
$InitialState = $this->State;
$StateRenderables = $this->lines(Lines::fromTextLines($text, 0));
$Renderables = $this->State->applyTo($StateRenderables);
$this->State = $InitialState;
$html = self::render($Renderables);
# trim line breaks
$html = \trim($html, "\n");
return $html;
}
2013-11-09 01:40:00 +04:00
2014-04-17 11:59:35 +04:00
#
2014-05-05 15:39:40 +04:00
# Lines
2014-04-17 11:59:35 +04:00
#
2019-01-20 05:44:34 +03:00
/** @var array<array-key, class-string<Block>[]> */
2018-12-04 19:24:25 +03:00
protected $BlockTypes = [
2019-01-20 05:44:34 +03:00
'#' => [Header::class],
'*' => [Rule::class, TList::class],
'+' => [TList::class],
'-' => [SetextHeader::class, Table::class, Rule::class, TList::class],
'0' => [TList::class],
'1' => [TList::class],
'2' => [TList::class],
'3' => [TList::class],
'4' => [TList::class],
'5' => [TList::class],
'6' => [TList::class],
'7' => [TList::class],
'8' => [TList::class],
'9' => [TList::class],
':' => [Table::class],
'<' => [Comment::class, BlockMarkup::class],
'=' => [SetextHeader::class],
'>' => [BlockQuote::class],
'[' => [Reference::class],
'_' => [Rule::class],
'`' => [FencedCode::class],
'|' => [Table::class],
'~' => [FencedCode::class],
2018-12-04 19:24:25 +03:00
];
2013-11-09 01:40:00 +04:00
2014-05-05 15:39:40 +04:00
# ~
2019-01-20 05:44:34 +03:00
/** @var class-string<Block>[] */
2018-12-04 19:24:25 +03:00
protected $unmarkedBlockTypes = [
2019-01-20 05:44:34 +03:00
IndentedCode::class,
2018-12-04 19:24:25 +03:00
];
2014-05-05 15:39:40 +04:00
#
# Blocks
#
2019-01-20 05:44:34 +03:00
/**
* @return StateRenderable[]
*/
public function lines(Lines $Lines)
2014-04-17 11:59:35 +04:00
{
return \array_map(
/** @return StateRenderable */
function (Block $Block) { return $Block->stateRenderable($this); },
$this->blocks($Lines)
);
}
/**
* @return Block[]
*/
public function blocks(Lines $Lines)
{
/** @var Block[] */
$Blocks = [];
2019-01-20 05:44:34 +03:00
/** @var Block|null */
$Block = null;
/** @var Block|null */
2014-04-17 11:59:35 +04:00
$CurrentBlock = null;
2013-11-09 01:40:00 +04:00
2018-12-05 17:07:55 +03:00
foreach ($Lines->contexts() as $Context) {
$Line = $Context->line();
2019-01-20 05:44:34 +03:00
if (
isset($CurrentBlock)
&& $CurrentBlock instanceof ContinuableBlock
&& ! $CurrentBlock instanceof Paragraph
) {
$Block = $CurrentBlock->advance($Context);
2018-12-04 19:24:25 +03:00
if (isset($Block)) {
2014-04-17 11:59:35 +04:00
$CurrentBlock = $Block;
2014-04-17 11:59:35 +04:00
continue;
}
}
2014-04-17 11:59:35 +04:00
# ~
2018-12-05 13:30:49 +03:00
$marker = $Line->text()[0];
2013-11-09 01:40:00 +04:00
2014-04-27 02:54:52 +04:00
# ~
$blockTypes = $this->unmarkedBlockTypes;
2018-12-04 19:24:25 +03:00
if (isset($this->BlockTypes[$marker])) {
foreach ($this->BlockTypes[$marker] as $blockType) {
$blockTypes []= $blockType;
2014-04-17 11:59:35 +04:00
}
}
2014-04-17 11:59:35 +04:00
#
# ~
2013-11-09 01:40:00 +04:00
2018-12-04 19:24:25 +03:00
foreach ($blockTypes as $blockType) {
2019-01-20 05:44:34 +03:00
$Block = $blockType::build($Context, $CurrentBlock, $this->State);
2013-11-09 01:40:00 +04:00
2018-12-04 19:24:25 +03:00
if (isset($Block)) {
2019-01-20 05:44:34 +03:00
if ($Block instanceof StateUpdatingBlock) {
$this->State = $this->State->mergingWith(
$Block->latestState()
);
}
2013-11-10 00:23:56 +04:00
2019-01-20 05:44:34 +03:00
if (isset($CurrentBlock) && ! $Block->acquiredPrevious()) {
$Blocks[] = $CurrentBlock;
}
2014-04-17 11:59:35 +04:00
$CurrentBlock = $Block;
2013-11-09 01:40:00 +04:00
2014-04-17 11:59:35 +04:00
continue 2;
}
}
2014-04-17 11:59:35 +04:00
# ~
2019-01-20 05:44:34 +03:00
if (isset($CurrentBlock) and $CurrentBlock instanceof Paragraph) {
$Block = $CurrentBlock->advance($Context);
}
2018-12-04 19:24:25 +03:00
if (isset($Block)) {
$CurrentBlock = $Block;
2018-12-04 19:24:25 +03:00
} else {
if (isset($CurrentBlock)) {
$Blocks[] = $CurrentBlock;
}
2014-04-17 11:59:35 +04:00
2019-01-20 05:44:34 +03:00
$CurrentBlock = Paragraph::build($Context);
2014-04-17 11:59:35 +04:00
}
}
# ~
2018-12-04 19:24:25 +03:00
if (isset($CurrentBlock)) {
$Blocks[] = $CurrentBlock;
2015-01-11 15:35:09 +03:00
}
2014-04-17 11:59:35 +04:00
# ~
return $Blocks;
}
2014-04-17 11:59:35 +04:00
#
# Inline Elements
2014-04-17 11:59:35 +04:00
#
2013-11-09 01:40:00 +04:00
2019-01-20 05:44:34 +03:00
/** @var array<array-key, class-string<Inline>[]> */
2018-12-04 19:24:25 +03:00
protected $InlineTypes = [
2019-01-20 05:44:34 +03:00
'!' => [Image::class],
'&' => [SpecialCharacter::class],
'*' => [Emphasis::class],
':' => [Url::class],
'<' => [UrlTag::class, Email::class, InlineMarkup::class],
'[' => [Link::class],
'_' => [Emphasis::class],
'`' => [Code::class],
'~' => [Strikethrough::class],
'\\' => [EscapeSequence::class],
2018-12-04 19:24:25 +03:00
];
2014-05-05 15:39:40 +04:00
# ~
2019-01-20 05:44:34 +03:00
/** @var string */
protected $inlineMarkerList = '!*_&[:<`~\\';
2014-05-05 15:39:40 +04:00
#
# ~
#
2019-01-20 05:44:34 +03:00
/**
* @param string $text
* @return StateRenderable[]
*/
public function line($text)
{
return \array_map(
/** @return StateRenderable */
function (Inline $Inline) { return $Inline->stateRenderable($this); },
$this->inlines($text)
);
}
/**
* @param string $text
* @return Inline[]
*/
public function inlines($text)
2018-03-21 05:18:34 +03:00
{
# standardize line breaks
2018-12-04 19:24:25 +03:00
$text = \str_replace(["\r\n", "\r"], "\n", $text);
/** @var Inline[] */
$Inlines = [];
2015-06-25 01:05:05 +03:00
# $excerpt is based on the first occurrence of a marker
2019-01-20 05:44:34 +03:00
for (
$Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($this->inlineMarkerList);
$Excerpt->text() !== '';
$Excerpt = $Excerpt->pushingOffsetTo($this->inlineMarkerList)
) {
$marker = \substr($Excerpt->text(), 0, 1);
2013-11-09 01:40:00 +04:00
2018-12-04 19:24:25 +03:00
foreach ($this->InlineTypes[$marker] as $inlineType) {
# check to see if the current inline type is nestable in the current context
2019-01-20 05:44:34 +03:00
$Inline = $inlineType::build($Excerpt, $this->State);
2014-01-20 11:26:25 +04:00
2018-12-04 19:24:25 +03:00
if (! isset($Inline)) {
2014-04-28 03:10:18 +04:00
continue;
}
2014-01-20 11:26:25 +04:00
2019-01-20 05:44:34 +03:00
$startPosition = $Inline->modifyStartPositionTo();
2015-06-25 01:05:05 +03:00
2019-01-20 05:44:34 +03:00
if (! isset($startPosition)) {
$startPosition = $Excerpt->offset();
}
2019-01-20 05:44:34 +03:00
# makes sure that the inline belongs to "our" marker
2015-06-25 01:05:05 +03:00
2019-01-20 05:44:34 +03:00
if ($startPosition > $Excerpt->offset()) {
continue;
}
2015-06-25 01:05:05 +03:00
# the text that comes before the inline
# compile the unmarked text
$Inlines[] = Plaintext::build($Excerpt->choppingUpToOffset($startPosition));
2014-01-21 00:19:23 +04:00
2015-06-25 01:05:05 +03:00
# compile the inline
$Inlines[] = $Inline;
2014-01-21 00:19:23 +04:00
2015-06-25 01:05:05 +03:00
# remove the examined text
2019-01-20 05:44:34 +03:00
/** @psalm-suppress LoopInvalidation */
$Excerpt = $Excerpt->choppingFromOffset($startPosition + $Inline->width());
2014-04-28 03:10:18 +04:00
continue 2;
2014-04-17 11:59:35 +04:00
}
2019-01-20 05:44:34 +03:00
if (! isset($startPosition)) {
$startPosition = $Excerpt->offset();
2018-04-08 22:37:36 +03:00
}
# the marker does not belong to an inline
$Inlines[] = Plaintext::build($Excerpt->choppingUpToOffset($startPosition + 1));
2019-01-20 05:44:34 +03:00
/** @psalm-suppress LoopInvalidation */
$Excerpt = $Excerpt->choppingFromOffset($startPosition + 1);
2015-01-16 04:18:07 +03:00
}
$Inlines[] = Plaintext::build($Excerpt->choppingFromOffset(0));
2013-11-09 01:40:00 +04:00
return $Inlines;
}
2014-01-18 17:10:24 +04:00
2018-03-19 01:37:40 +03:00
/**
* @param Renderable[] $Renderables
2019-01-20 05:44:34 +03:00
* @return string
2018-03-19 01:37:40 +03:00
*/
public static function render(array $Renderables)
{
2019-01-20 05:44:34 +03:00
return \array_reduce(
$Renderables,
2019-01-20 05:44:34 +03:00
/**
* @param string $html
* @return string
*/
function ($html, Renderable $Renderable) {
2019-01-20 05:44:34 +03:00
return (
$html
. ($Renderable instanceof Invisible ? '' : "\n")
. $Renderable->getHtml()
);
},
''
);
2014-02-21 04:22:31 +04:00
}
}