diff --git a/psalm.xml b/psalm.xml index 3b0a03d..3cb03e6 100644 --- a/psalm.xml +++ b/psalm.xml @@ -27,6 +27,7 @@ + diff --git a/src/Components/Inlines/HardBreak.php b/src/Components/Inlines/HardBreak.php new file mode 100644 index 0000000..b031469 --- /dev/null +++ b/src/Components/Inlines/HardBreak.php @@ -0,0 +1,91 @@ +width = $width; + $this->position = $position; + } + + /** + * @param Excerpt $Excerpt + * @param State $State + * @return static|null + */ + public static function build(Excerpt $Excerpt, State $State) + { + $context = $Excerpt->context(); + $offset = $Excerpt->offset(); + + if ($offset < 1) { + return null; + } + + if (\substr($context, $offset -1, 1) === '\\') { + $trimTrailingWhitespace = \rtrim(\substr($context, 0, $offset -1)); + $contentLen = \strlen($trimTrailingWhitespace); + + return new self($offset - $contentLen, $contentLen); + } + + if ($offset < 2) { + return null; + } + + if (\substr($context, $offset -2, 2) === ' ') { + $trimTrailingWhitespace = \rtrim(\substr($context, 0, $offset)); + $contentLen = \strlen($trimTrailingWhitespace); + + return new self($offset - $contentLen, $contentLen); + } + + return null; + } + + /** + * Return an integer to declare that the inline should be treated as if it + * started from that position in the excerpt given to static::build. + * Return null to use the excerpt offset value. + * @return int|null + * */ + public function modifyStartPositionTo() + { + return $this->position; + } + + /** + * @return Element + */ + public function stateRenderable(Parsedown $_) + { + return Element::selfClosing('br', []); + } + + /** + * @return Text + */ + public function bestPlaintext() + { + return new Text("\n"); + } +} diff --git a/src/Components/Inlines/PlainText.php b/src/Components/Inlines/PlainText.php index 79e7aa1..9d83216 100644 --- a/src/Components/Inlines/PlainText.php +++ b/src/Components/Inlines/PlainText.php @@ -2,11 +2,8 @@ namespace Erusev\Parsedown\Components\Inlines; -use Erusev\Parsedown\AST\Handler; use Erusev\Parsedown\AST\StateRenderable; use Erusev\Parsedown\Components\Inline; -use Erusev\Parsedown\Html\Renderables\Container; -use Erusev\Parsedown\Html\Renderables\Element; use Erusev\Parsedown\Html\Renderables\Text; use Erusev\Parsedown\Parsedown; use Erusev\Parsedown\Parsing\Excerpt; @@ -45,34 +42,11 @@ final class PlainText implements Inline } /** - * @return Handler + * @return Text */ public function stateRenderable(Parsedown $_) { - return new Handler( - /** @return Container */ - function (State $_) { - $Renderables = []; - $text = $this->text; - - $text = \preg_replace('/(?text); } /** diff --git a/src/Components/Inlines/SoftBreak.php b/src/Components/Inlines/SoftBreak.php new file mode 100644 index 0000000..45b6a20 --- /dev/null +++ b/src/Components/Inlines/SoftBreak.php @@ -0,0 +1,76 @@ +width = $width; + $this->position = $position; + } + + /** + * @param Excerpt $Excerpt + * @param State $State + * @return static|null + */ + public static function build(Excerpt $Excerpt, State $State) + { + $context = $Excerpt->context(); + $offset = $Excerpt->offset(); + + $trimTrailingWhitespaceBefore = \rtrim(\substr($context, 0, $offset)); + $trimLeadingWhitespaceAfter = \ltrim(\substr($context, $offset + 1)); + $contentLenBefore = \strlen($trimTrailingWhitespaceBefore); + $contentLenAfter = \strlen($trimLeadingWhitespaceAfter); + + $originalLen = \strlen($context); + $afterWidth = $originalLen - $offset - $contentLenAfter; + + return new self($offset + $afterWidth - $contentLenBefore, $contentLenBefore); + } + + /** + * Return an integer to declare that the inline should be treated as if it + * started from that position in the excerpt given to static::build. + * Return null to use the excerpt offset value. + * @return int|null + * */ + public function modifyStartPositionTo() + { + return $this->position; + } + + /** + * @return Text + */ + public function stateRenderable(Parsedown $_) + { + return new Text("\n"); + } + + /** + * @return Text + */ + public function bestPlaintext() + { + return new Text("\n"); + } +} diff --git a/src/Parsedown.php b/src/Parsedown.php index 275fa7d..84822a8 100644 --- a/src/Parsedown.php +++ b/src/Parsedown.php @@ -22,10 +22,12 @@ 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\HardBreak; 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\SoftBreak; use Erusev\Parsedown\Components\Inlines\SpecialCharacter; use Erusev\Parsedown\Components\Inlines\Strikethrough; use Erusev\Parsedown\Components\Inlines\Url; @@ -41,48 +43,13 @@ use Erusev\Parsedown\Parsing\Lines; final class Parsedown { - # ~ - const version = '2.0.0-dev'; - # ~ - /** @var State */ private $State; - public function __construct(State $State = null) - { - $this->State = $State ?: new State; - } - - /** - * @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; - } - - # - # Lines - # - /** @var array[]> */ - protected $BlockTypes = [ + private $BlockTypes = [ '#' => [Header::class], '*' => [Rule::class, TList::class], '+' => [TList::class], @@ -108,16 +75,57 @@ final class Parsedown '~' => [FencedCode::class], ]; - # ~ - /** @var class-string[] */ - protected $unmarkedBlockTypes = [ + private $unmarkedBlockTypes = [ IndentedCode::class, ]; - # - # Blocks - # + /** @var array[]> */ + private $InlineTypes = [ + '!' => [Image::class], + '*' => [Emphasis::class], + '_' => [Emphasis::class], + '&' => [SpecialCharacter::class], + '[' => [Link::class], + ':' => [Url::class], + '<' => [UrlTag::class, Email::class, InlineMarkup::class], + '`' => [Code::class], + '~' => [Strikethrough::class], + '\\' => [EscapeSequence::class], + "\n" => [HardBreak::class, SoftBreak::class], + ]; + + /** @var string */ + private $inlineMarkers; + + public function __construct(State $State = null) + { + $this->State = $State ?: new State; + + $this->inlineMarkers = \implode('', \array_keys($this->InlineTypes)); + } + + /** + * @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; + } /** * @return StateRenderable[] @@ -225,29 +233,6 @@ final class Parsedown return $Blocks; } - # - # Inline Elements - # - - /** @var array[]> */ - protected $InlineTypes = [ - '!' => [Image::class], - '&' => [SpecialCharacter::class], - '*' => [Emphasis::class], - ':' => [Url::class], - '<' => [UrlTag::class, Email::class, InlineMarkup::class], - '[' => [Link::class], - '_' => [Emphasis::class], - '`' => [Code::class], - '~' => [Strikethrough::class], - '\\' => [EscapeSequence::class], - ]; - - # ~ - - /** @var string */ - protected $inlineMarkerList = '!*_&[:<`~\\'; - # # ~ # @@ -280,9 +265,9 @@ final class Parsedown # $excerpt is based on the first occurrence of a marker for ( - $Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($this->inlineMarkerList); + $Excerpt = (new Excerpt($text, 0))->pushingOffsetTo($this->inlineMarkers); $Excerpt->text() !== ''; - $Excerpt = $Excerpt->pushingOffsetTo($this->inlineMarkerList) + $Excerpt = $Excerpt->pushingOffsetTo($this->inlineMarkers) ) { $marker = \substr($Excerpt->text(), 0, 1);