diff --git a/composer.json b/composer.json index 2287a44..17488fe 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "cache/void-adapter": "^1.1", "jakeasmith/http_build_url": "^1", "jbbcode/jbbcode": "^1.4", + "league/commonmark": "^2.0", "youthweb/urllinker": "^1.4" }, "require-dev": { diff --git a/src/Extension/BBCode/BBCodeExtension.php b/src/Extension/BBCode/BBCodeExtension.php new file mode 100644 index 0000000..91e5e86 --- /dev/null +++ b/src/Extension/BBCode/BBCodeExtension.php @@ -0,0 +1,51 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Extension\BBCode; + +use League\CommonMark\Environment\EnvironmentBuilderInterface; +use League\CommonMark\Extension\ExtensionInterface; +use League\CommonMark\Node as CoreNode; +use League\CommonMark\Parser as CoreParser; +use League\CommonMark\Renderer as CoreRenderer; +use Youthweb\BBCodeParser\Extension\BBCode\Delimiter\Processor\EmphasisDelimiterProcessor; + +/** + * BBCodeExtension + */ +final class BBCodeExtension implements ExtensionInterface +{ + public function register(EnvironmentBuilderInterface $environment): void + { + $environment + ->addBlockStartParser(new Parser\Block\BoldBlockStartParser(), 40) + + ->addInlineParser(new CoreParser\Inline\NewlineParser(), 200) + + ->addRenderer(CoreNode\Block\Document::class, new CoreRenderer\Block\DocumentRenderer(), 0) + ->addRenderer(Node\Block\BoldBlock::class, new Renderer\Block\BoldBlockRenderer(), 0) + ->addRenderer(CoreNode\Block\Paragraph::class, new CoreRenderer\Block\ParagraphRenderer(), 0) + + ->addRenderer(CoreNode\Inline\Text::class, new CoreRenderer\Inline\TextRenderer(), 0) + ; + } +} diff --git a/src/Extension/BBCode/Node/Block/BoldBlock.php b/src/Extension/BBCode/Node/Block/BoldBlock.php new file mode 100644 index 0000000..69715eb --- /dev/null +++ b/src/Extension/BBCode/Node/Block/BoldBlock.php @@ -0,0 +1,42 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Extension\BBCode\Node\Block; + +use League\CommonMark\Node\Block\AbstractBlock; +use League\CommonMark\Node\RawMarkupContainerInterface; +use League\CommonMark\Node\StringContainerInterface; + +final class BoldBlock extends AbstractBlock implements RawMarkupContainerInterface, StringContainerInterface +{ + private string $literal = ''; + + public function getLiteral(): string + { + return $this->literal; + } + + public function setLiteral(string $literal): void + { + $this->literal = $literal; + } +} diff --git a/src/Extension/BBCode/Parser/Block/BoldBlockParser.php b/src/Extension/BBCode/Parser/Block/BoldBlockParser.php new file mode 100644 index 0000000..3711b75 --- /dev/null +++ b/src/Extension/BBCode/Parser/Block/BoldBlockParser.php @@ -0,0 +1,93 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Extension\BBCode\Parser\Block; + +use League\CommonMark\Parser\Block\AbstractBlockContinueParser; +use League\CommonMark\Parser\Block\BlockContinue; +use League\CommonMark\Parser\Block\BlockContinueParserInterface; +use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; +use League\CommonMark\Parser\Cursor; +use League\CommonMark\Parser\InlineParserEngineInterface; +use League\CommonMark\Util\RegexHelper; +use Youthweb\BBCodeParser\Extension\BBCode\Node\Block\BoldBlock; + +final class BoldBlockParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface +{ + private BoldBlock $block; + + private string $content = ''; + + private bool $finished = false; + + public function __construct() + { + $this->block = new BoldBlock(); + } + + public function getBlock(): BoldBlock + { + return $this->block; + } + + public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue + { + if ($cursor->getNextNonSpaceCharacter() !== '[') { + return BlockContinue::at($cursor); + } + + $tmpCursor = clone $cursor; + $tmpCursor->advanceToNextNonSpaceOrTab(); + $line = $tmpCursor->getSubstring(0, 4); + + if ($line === '[/b]') { + return BlockStart::none(); + } + + return BlockContinue::at($cursor); + } + + public function addLine(string $line): void + { + if ($this->content !== '') { + $this->content .= "\n"; + } + + $line = substr($line, 0, -4); + + $this->content .= $line; + } + + public function closeBlock(): void + { + $this->block->setLiteral($this->content); + $this->content = ''; + } + + /** + * Parse any inlines inside of the current block + */ + public function parseInlines(InlineParserEngineInterface $inlineParser): void + { + // #TODO + } +} diff --git a/src/Extension/BBCode/Parser/Block/BoldBlockStartParser.php b/src/Extension/BBCode/Parser/Block/BoldBlockStartParser.php new file mode 100644 index 0000000..507bc34 --- /dev/null +++ b/src/Extension/BBCode/Parser/Block/BoldBlockStartParser.php @@ -0,0 +1,51 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Extension\BBCode\Parser\Block; + +use League\CommonMark\Parser\Block\BlockStart; +use League\CommonMark\Parser\Block\BlockStartParserInterface; +use League\CommonMark\Parser\Cursor; +use League\CommonMark\Parser\MarkdownParserStateInterface; +use League\CommonMark\Util\RegexHelper; + +final class BoldBlockStartParser implements BlockStartParserInterface +{ + public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart + { + if ($cursor->isIndented() || $cursor->getNextNonSpaceCharacter() !== '[') { + return BlockStart::none(); + } + + $tmpCursor = clone $cursor; + $tmpCursor->advanceToNextNonSpaceOrTab(); + $line = $tmpCursor->getSubstring(0, 3); + + if ($line !== '[b]') { + return BlockStart::none(); + } + + $cursor->advanceBy(3); + + return BlockStart::of(new BoldBlockParser())->at($cursor); + } +} diff --git a/src/Extension/BBCode/Renderer/Block/BoldBlockRenderer.php b/src/Extension/BBCode/Renderer/Block/BoldBlockRenderer.php new file mode 100644 index 0000000..750dc00 --- /dev/null +++ b/src/Extension/BBCode/Renderer/Block/BoldBlockRenderer.php @@ -0,0 +1,73 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Extension\BBCode\Renderer\Block; + +use League\CommonMark\Node\Node; +use League\CommonMark\Renderer\ChildNodeRendererInterface; +use League\CommonMark\Renderer\NodeRendererInterface; +use League\CommonMark\Util\HtmlElement; +use League\CommonMark\Xml\XmlNodeRendererInterface; +use League\Config\ConfigurationAwareInterface; +use League\Config\ConfigurationInterface; +use Youthweb\BBCodeParser\Extension\BBCode\Node\Block\BoldBlock; + +final class BoldBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface +{ + private ConfigurationInterface $config; + + /** + * @param BoldBlock $node + * + * {@inheritDoc} + */ + public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement + { + BoldBlock::assertInstanceOf($node); + + $innerHtml = $childRenderer->renderNodes($node->children()); + + if ($innerHtml === '') { + $innerHtml = $node->getLiteral(); + } + + return new HtmlElement('b', [], $innerHtml); + } + + public function setConfiguration(ConfigurationInterface $configuration): void + { + $this->config = $configuration; + } + + public function getXmlTagName(Node $node): string + { + return 'strong'; + } + + /** + * {@inheritDoc} + */ + public function getXmlAttributes(Node $node): array + { + return []; + } +} diff --git a/src/Parser/CommonMarkParser.php b/src/Parser/CommonMarkParser.php new file mode 100644 index 0000000..a0563e9 --- /dev/null +++ b/src/Parser/CommonMarkParser.php @@ -0,0 +1,81 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Parser; + +use Youthweb\BBCodeParser\Extension\BBCode\BBCodeExtension; +use League\CommonMark\Environment\Environment; +use League\CommonMark\Parser\MarkdownParser; +use League\CommonMark\Renderer\HtmlRenderer; + +/** + * CommonMarkParser + */ +final class CommonMarkParser +{ + /** + * Create the Parser + */ + public static function create(): CommonMarkParser + { + return new self(); + } + + private MarkdownParser $markdownParser; + + private HtmlRenderer $htmlRenderer; + + /** + * Create the Parser + */ + private function __construct() + { + $config = [ + 'html_input' => 'escape', + 'allow_unsafe_links' => false, + 'max_nesting_level' => 5, + 'renderer' => [ + 'block_separator' => \PHP_EOL, + 'inner_separator' => \PHP_EOL, + 'soft_break' => \PHP_EOL, + ], + ]; + + $environment = new Environment($config); + $environment->addExtension(new BBCodeExtension()); + + $this->markdownParser = new MarkdownParser($environment); + $this->htmlRenderer = new HtmlRenderer($environment); + } + + /** + * parse $test with BBCode to Html + */ + public function parseBbcodeToHtml(string $text): string + { + $documentAST = $this->markdownParser->parse($text); + + $content = $this->htmlRenderer->renderDocument($documentAST); + + return trim($content->getContent()); + } +} diff --git a/tests/Integration/Parser/CommonMarkParserTest.php b/tests/Integration/Parser/CommonMarkParserTest.php new file mode 100644 index 0000000..9e9bc0c --- /dev/null +++ b/tests/Integration/Parser/CommonMarkParserTest.php @@ -0,0 +1,58 @@ + + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace Youthweb\BBCodeParser\Tests\Integration\Parser; + +use PHPUnit\Framework\TestCase; +use Youthweb\BBCodeParser\Parser\CommonMarkParser; + +class CommonMarkParserTest extends TestCase +{ + /** + * @dataProvider provideBbcodeAndHtml + */ + public function testParseBbcodeToHtml(string $text, string $expected) + { + $parser = CommonMarkParser::create(); + + $this->assertSame($expected, $parser->parseBbcodeToHtml($text)); + } + + public function provideBbcodeAndHtml() + { + return [ + [ + 'Hello World!', + '

Hello World!

', + ], + [ + 'Hello World! ', + '

Hello World! </div>

', + ], + [ + 'Hello World! ', + '

Hello World! <img src="javascript:alert(\'XSS\')">

', + ], + [ + '[b]Hello World![/b]', + '

Hello World!

', + ], + ]; + } +}