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;
|
2019-01-21 21:45:12 +03:00
|
|
|
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
|
|
|
|
2019-01-20 05:39: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)
|
2018-04-06 20:10:41 +03:00
|
|
|
{
|
2019-01-21 21:45:12 +03:00
|
|
|
$InitialState = $this->State;
|
2018-04-06 20:10:41 +03:00
|
|
|
|
2019-01-21 21:45:12 +03:00
|
|
|
$StateRenderables = $this->lines(Lines::fromTextLines($text, 0));
|
2018-04-06 20:10:41 +03:00
|
|
|
|
2019-01-21 21:45:12 +03:00
|
|
|
$Renderables = $this->State->applyTo($StateRenderables);
|
2018-04-06 20:10:41 +03:00
|
|
|
|
2019-01-21 21:45:12 +03:00
|
|
|
$this->State = $InitialState;
|
2018-04-06 20:10:41 +03:00
|
|
|
|
2019-01-21 21:45:12 +03:00
|
|
|
$html = self::render($Renderables);
|
|
|
|
|
|
|
|
# trim line breaks
|
|
|
|
$html = \trim($html, "\n");
|
|
|
|
|
|
|
|
return $html;
|
2014-01-23 02:57:36 +04:00
|
|
|
}
|
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
|
|
|
#
|
2014-02-21 03:54:23 +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-02-21 03:54:23 +04:00
|
|
|
|
2014-05-05 15:39:40 +04:00
|
|
|
#
|
|
|
|
# Blocks
|
|
|
|
#
|
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
/**
|
|
|
|
* @return StateRenderable[]
|
|
|
|
*/
|
2019-01-21 21:19:43 +03:00
|
|
|
public function lines(Lines $Lines)
|
2014-04-17 11:59:35 +04:00
|
|
|
{
|
2019-01-25 00:56:20 +03: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();
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
if (
|
|
|
|
isset($CurrentBlock)
|
|
|
|
&& $CurrentBlock instanceof ContinuableBlock
|
|
|
|
&& ! $CurrentBlock instanceof Paragraph
|
|
|
|
) {
|
2019-01-20 18:14:57 +03:00
|
|
|
$Block = $CurrentBlock->advance($Context);
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2018-12-04 19:24:25 +03:00
|
|
|
if (isset($Block)) {
|
2014-04-17 11:59:35 +04:00
|
|
|
$CurrentBlock = $Block;
|
2014-01-31 05:03:52 +04:00
|
|
|
|
2014-04-17 11:59:35 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2014-04-17 11:59:35 +04:00
|
|
|
# ~
|
2014-01-31 04:19:18 +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) {
|
2016-10-13 20:25:43 +03:00
|
|
|
$blockTypes []= $blockType;
|
2014-04-17 11:59:35 +04:00
|
|
|
}
|
|
|
|
}
|
2014-01-31 04:19:18 +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()
|
|
|
|
);
|
2014-02-21 03:54:23 +04:00
|
|
|
}
|
2013-11-10 00:23:56 +04:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
if (isset($CurrentBlock) && ! $Block->acquiredPrevious()) {
|
2019-01-25 00:56:20 +03:00
|
|
|
$Blocks[] = $CurrentBlock;
|
2014-02-21 03:54:23 +04:00
|
|
|
}
|
2013-11-17 14:48:01 +04:00
|
|
|
|
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-02-21 03:54:23 +04:00
|
|
|
|
2014-04-17 11:59:35 +04:00
|
|
|
# ~
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
if (isset($CurrentBlock) and $CurrentBlock instanceof Paragraph) {
|
2019-01-20 18:14:57 +03:00
|
|
|
$Block = $CurrentBlock->advance($Context);
|
2018-04-09 17:12:17 +03:00
|
|
|
}
|
|
|
|
|
2018-12-04 19:24:25 +03:00
|
|
|
if (isset($Block)) {
|
2018-04-09 17:12:17 +03:00
|
|
|
$CurrentBlock = $Block;
|
2018-12-04 19:24:25 +03:00
|
|
|
} else {
|
|
|
|
if (isset($CurrentBlock)) {
|
2019-01-25 00:56:20 +03:00
|
|
|
$Blocks[] = $CurrentBlock;
|
2018-04-08 22:29:09 +03:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
}
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2014-04-25 00:52:42 +04:00
|
|
|
# ~
|
|
|
|
|
2018-12-04 19:24:25 +03:00
|
|
|
if (isset($CurrentBlock)) {
|
2019-01-25 00:56:20 +03:00
|
|
|
$Blocks[] = $CurrentBlock;
|
2015-01-11 15:35:09 +03:00
|
|
|
}
|
|
|
|
|
2014-04-17 11:59:35 +04:00
|
|
|
# ~
|
2014-02-21 03:54:23 +04:00
|
|
|
|
2019-01-25 00:56:20 +03:00
|
|
|
return $Blocks;
|
2015-12-17 20:46:44 +03:00
|
|
|
}
|
|
|
|
|
2014-04-17 11:59:35 +04:00
|
|
|
#
|
2015-01-11 03:16:36 +03: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
|
|
|
];
|
2013-09-20 02:12:40 +04:00
|
|
|
|
2014-05-05 15:39:40 +04:00
|
|
|
# ~
|
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
/** @var string */
|
2018-03-19 01:44:07 +03:00
|
|
|
protected $inlineMarkerList = '!*_&[:<`~\\';
|
2013-12-24 05:17:23 +04:00
|
|
|
|
2014-05-05 15:39:40 +04:00
|
|
|
#
|
|
|
|
# ~
|
|
|
|
#
|
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
/**
|
|
|
|
* @param string $text
|
|
|
|
* @return StateRenderable[]
|
|
|
|
*/
|
2019-01-21 21:44:19 +03:00
|
|
|
public function line($text)
|
2019-01-22 22:04:45 +03:00
|
|
|
{
|
|
|
|
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
|
|
|
{
|
2018-10-16 20:41:42 +03:00
|
|
|
# standardize line breaks
|
2018-12-04 19:24:25 +03:00
|
|
|
$text = \str_replace(["\r\n", "\r"], "\n", $text);
|
2018-10-16 20:41:42 +03:00
|
|
|
|
2019-01-22 22:04:45 +03:00
|
|
|
/** @var Inline[] */
|
|
|
|
$Inlines = [];
|
2018-04-08 22:38:21 +03:00
|
|
|
|
2015-06-25 01:05:05 +03:00
|
|
|
# $excerpt is based on the first occurrence of a marker
|
2013-09-20 02:12:40 +04:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
for (
|
|
|
|
$Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($this->inlineMarkerList);
|
|
|
|
$Excerpt->text() !== '';
|
|
|
|
$Excerpt = $Excerpt->pushingOffsetTo($this->inlineMarkerList)
|
|
|
|
) {
|
2019-01-25 01:26:26 +03:00
|
|
|
$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) {
|
2016-10-02 20:26:13 +03:00
|
|
|
# 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();
|
2015-01-19 18:05:10 +03:00
|
|
|
}
|
|
|
|
|
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-01-19 18:05:10 +03:00
|
|
|
}
|
2013-12-24 18:05:13 +04:00
|
|
|
|
2015-06-25 01:05:05 +03:00
|
|
|
# the text that comes before the inline
|
|
|
|
# compile the unmarked text
|
2019-01-22 22:04:45 +03:00
|
|
|
$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
|
2019-01-22 22:04:45 +03:00
|
|
|
$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());
|
2013-12-24 18:05:13 +04:00
|
|
|
|
2014-04-28 03:10:18 +04:00
|
|
|
continue 2;
|
2014-04-17 11:59:35 +04:00
|
|
|
}
|
2013-12-24 18:05:13 +04:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
if (! isset($startPosition)) {
|
|
|
|
$startPosition = $Excerpt->offset();
|
2018-04-08 22:37:36 +03:00
|
|
|
}
|
2018-03-28 22:59:56 +03:00
|
|
|
|
2019-01-20 05:44:20 +03:00
|
|
|
# the marker does not belong to an inline
|
2018-04-08 22:27:44 +03:00
|
|
|
|
2019-01-22 22:04:45 +03:00
|
|
|
$Inlines[] = Plaintext::build($Excerpt->choppingUpToOffset($startPosition + 1));
|
2018-03-19 01:36:30 +03:00
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
/** @psalm-suppress LoopInvalidation */
|
|
|
|
$Excerpt = $Excerpt->choppingFromOffset($startPosition + 1);
|
2015-01-16 04:18:07 +03:00
|
|
|
}
|
|
|
|
|
2019-01-22 22:04:45 +03:00
|
|
|
$Inlines[] = Plaintext::build($Excerpt->choppingFromOffset(0));
|
2013-11-09 01:40:00 +04:00
|
|
|
|
2019-01-22 22:04:45 +03:00
|
|
|
return $Inlines;
|
2014-01-23 02:57:36 +04:00
|
|
|
}
|
2014-01-18 17:10:24 +04:00
|
|
|
|
2018-03-19 01:37:40 +03:00
|
|
|
/**
|
2019-01-21 21:45:12 +03:00
|
|
|
* @param Renderable[] $Renderables
|
2019-01-20 05:44:34 +03:00
|
|
|
* @return string
|
2018-03-19 01:37:40 +03:00
|
|
|
*/
|
2019-01-21 21:45:12 +03:00
|
|
|
public static function render(array $Renderables)
|
2017-05-01 05:24:40 +03:00
|
|
|
{
|
2019-01-20 05:44:34 +03:00
|
|
|
return \array_reduce(
|
2019-01-21 21:45:12 +03:00
|
|
|
$Renderables,
|
2019-01-20 05:44:34 +03:00
|
|
|
/**
|
|
|
|
* @param string $html
|
|
|
|
* @return string
|
|
|
|
*/
|
2019-01-21 21:45:12 +03:00
|
|
|
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
|
|
|
}
|
2014-01-29 14:30:21 +04:00
|
|
|
}
|