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\Paragraph;
|
|
|
|
use Erusev\Parsedown\Components\ContinuableBlock;
|
|
|
|
use Erusev\Parsedown\Components\Inline;
|
|
|
|
use Erusev\Parsedown\Components\Inlines\PlainText;
|
|
|
|
use Erusev\Parsedown\Components\StateUpdatingBlock;
|
2019-01-25 22:49:18 +03:00
|
|
|
use Erusev\Parsedown\Configurables\BlockTypes;
|
|
|
|
use Erusev\Parsedown\Configurables\InlineTypes;
|
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
|
|
|
{
|
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;
|
|
|
|
|
2019-01-25 21:52:49 +03:00
|
|
|
public function __construct(State $State = null)
|
|
|
|
{
|
|
|
|
$this->State = $State ?: new State;
|
|
|
|
|
2019-01-25 22:49:18 +03:00
|
|
|
// ensure we cache the initial value if these weren't explicitly set
|
|
|
|
$this->State = $this->State->mergingWith(new State([
|
|
|
|
$this->State->get(BlockTypes::class),
|
|
|
|
$this->State->get(InlineTypes::class),
|
|
|
|
]));
|
2019-01-25 21:52:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $text
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
2014-05-05 15:39:40 +04:00
|
|
|
|
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 */
|
2019-01-26 00:39:37 +03:00
|
|
|
function (Block $Block) { return $Block->stateRenderable(); },
|
2019-01-25 00:56:20 +03:00
|
|
|
$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
|
|
|
|
2018-12-05 13:30:49 +03:00
|
|
|
$marker = $Line->text()[0];
|
2013-11-09 01:40:00 +04:00
|
|
|
|
2019-01-25 22:49:18 +03:00
|
|
|
$potentialBlockTypes = \array_merge(
|
|
|
|
$this->State->get(BlockTypes::class)->unmarked(),
|
2019-01-25 23:19:18 +03:00
|
|
|
$this->State->get(BlockTypes::class)->markedBy($marker)
|
2019-01-25 22:49:18 +03:00
|
|
|
);
|
2014-01-31 04:19:18 +04:00
|
|
|
|
2019-01-25 22:49:18 +03:00
|
|
|
foreach ($potentialBlockTypes 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) {
|
2019-01-25 23:23:04 +03:00
|
|
|
$this->State = $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
|
|
|
|
2019-01-26 17:51:05 +03:00
|
|
|
if (isset($CurrentBlock) && $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
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-01-25 00:56:20 +03:00
|
|
|
return $Blocks;
|
2015-12-17 20:46:44 +03: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 */
|
2019-01-26 00:39:37 +03:00
|
|
|
function (Inline $Inline) { return $Inline->stateRenderable(); },
|
2019-01-22 22:04:45 +03:00
|
|
|
$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-25 22:49:18 +03:00
|
|
|
$InlineTypes = $this->State->get(InlineTypes::class);
|
|
|
|
$markerMask = $InlineTypes->markers();
|
|
|
|
|
2019-01-20 05:44:34 +03:00
|
|
|
for (
|
2019-01-25 22:49:18 +03:00
|
|
|
$Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($markerMask);
|
2019-01-20 05:44:34 +03:00
|
|
|
$Excerpt->text() !== '';
|
2019-01-25 22:49:18 +03:00
|
|
|
$Excerpt = $Excerpt->pushingOffsetTo($markerMask)
|
2019-01-20 05:44:34 +03:00
|
|
|
) {
|
2019-01-25 01:26:26 +03:00
|
|
|
$marker = \substr($Excerpt->text(), 0, 1);
|
2013-11-09 01:40:00 +04:00
|
|
|
|
2019-01-25 23:19:18 +03:00
|
|
|
foreach ($InlineTypes->markedBy($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
|
|
|
/** @psalm-suppress LoopInvalidation */
|
2019-01-25 22:02:30 +03:00
|
|
|
$Excerpt = $Excerpt->addingToOffset(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
|
|
|
}
|