diff --git a/packages/guides-markdown/resources/config/guides-markdown.php b/packages/guides-markdown/resources/config/guides-markdown.php index 2026e2739..1acc881cf 100644 --- a/packages/guides-markdown/resources/config/guides-markdown.php +++ b/packages/guides-markdown/resources/config/guides-markdown.php @@ -3,7 +3,23 @@ declare(strict_types=1); use phpDocumentor\Guides\Markdown\MarkupLanguageParser; +use phpDocumentor\Guides\Markdown\Parsers\BlockQuoteParser; +use phpDocumentor\Guides\Markdown\Parsers\CodeBlockParser; +use phpDocumentor\Guides\Markdown\Parsers\HeaderParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\EmphasisParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\InlineCodeParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\InlineImageParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\LinkParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\PlainTextParser; +use phpDocumentor\Guides\Markdown\Parsers\InlineParsers\StrongParser; +use phpDocumentor\Guides\Markdown\Parsers\ListBlockParser; +use phpDocumentor\Guides\Markdown\Parsers\ListItemParser; +use phpDocumentor\Guides\Markdown\Parsers\ParagraphParser; +use phpDocumentor\Guides\Markdown\Parsers\SeparatorParser; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\String\Slugger\AsciiSlugger; + +use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator; return static function (ContainerConfigurator $container): void { $container->services() @@ -11,6 +27,50 @@ ->autowire() ->autoconfigure() + ->set(AsciiSlugger::class) + + ->set(HeaderParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->set(BlockQuoteParser::class) + ->arg('$subParsers', tagged_iterator('phpdoc.guides.markdown.parser.subParser')) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->tag('phpdoc.guides.markdown.parser.subParser') + ->set(ListBlockParser::class) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->tag('phpdoc.guides.markdown.parser.subParser') + ->set(ListItemParser::class) + ->arg('$subParsers', tagged_iterator('phpdoc.guides.markdown.parser.subParser')) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->set(ParagraphParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->tag('phpdoc.guides.markdown.parser.subParser') + ->set(SeparatorParser::class) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->tag('phpdoc.guides.markdown.parser.subParser') + ->set(CodeBlockParser::class) + ->tag('phpdoc.guides.markdown.parser.blockParser') + ->tag('phpdoc.guides.markdown.parser.subParser') + + ->set(EmphasisParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(LinkParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(PlainTextParser::class) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(StrongParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(InlineCodeParser::class) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(InlineImageParser::class) + ->arg('$inlineParsers', tagged_iterator('phpdoc.guides.markdown.parser.inlineParser')) + ->tag('phpdoc.guides.markdown.parser.inlineParser') + ->set(MarkupLanguageParser::class) + ->arg('$parsers', tagged_iterator('phpdoc.guides.markdown.parser.blockParser')) ->tag('phpdoc.guides.parser.markupLanguageParser'); }; diff --git a/packages/guides-markdown/src/Markdown/MarkupLanguageParser.php b/packages/guides-markdown/src/Markdown/MarkupLanguageParser.php index ad80bcd90..395e21663 100644 --- a/packages/guides-markdown/src/Markdown/MarkupLanguageParser.php +++ b/packages/guides-markdown/src/Markdown/MarkupLanguageParser.php @@ -6,34 +6,18 @@ use League\CommonMark\Environment\Environment as CommonMarkEnvironment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; -use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; -use League\CommonMark\Extension\CommonMark\Node\Block\Heading; -use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; -use League\CommonMark\Extension\CommonMark\Node\Inline\Code; -use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Node\Block\Document; -use League\CommonMark\Node\Inline\Text; use League\CommonMark\Node\NodeWalker; use League\CommonMark\Parser\MarkdownParser; -use phpDocumentor\Guides\Markdown\Parsers\ListBlock; -use phpDocumentor\Guides\Markdown\Parsers\Paragraph; -use phpDocumentor\Guides\Markdown\Parsers\ThematicBreak; use phpDocumentor\Guides\MarkupLanguageParser as MarkupLanguageParserInterface; -use phpDocumentor\Guides\Nodes\AnchorNode; -use phpDocumentor\Guides\Nodes\CodeNode; use phpDocumentor\Guides\Nodes\DocumentNode; -use phpDocumentor\Guides\Nodes\InlineCompoundNode; -use phpDocumentor\Guides\Nodes\ListNode; use phpDocumentor\Guides\Nodes\Node; -use phpDocumentor\Guides\Nodes\ParagraphNode; -use phpDocumentor\Guides\Nodes\RawNode; -use phpDocumentor\Guides\Nodes\SpanNode; -use phpDocumentor\Guides\Nodes\TitleNode; use phpDocumentor\Guides\ParserContext; +use Psr\Log\LoggerInterface; use RuntimeException; -use Symfony\Component\String\Slugger\AsciiSlugger; use function md5; +use function sprintf; use function strtolower; final class MarkupLanguageParser implements MarkupLanguageParserInterface @@ -42,23 +26,16 @@ final class MarkupLanguageParser implements MarkupLanguageParserInterface private ParserContext|null $parserContext = null; - /** @var ParserInterface[] */ - private readonly array $parsers; - private DocumentNode|null $document = null; - private readonly AsciiSlugger $idGenerator; - public function __construct() - { + /** @param iterable> $parsers */ + public function __construct( + private readonly LoggerInterface $logger, + private readonly iterable $parsers, + ) { $cmEnvironment = new CommonMarkEnvironment(['html_input' => 'strip']); $cmEnvironment->addExtension(new CommonMarkCoreExtension()); $this->markdownParser = new MarkdownParser($cmEnvironment); - $this->idGenerator = new AsciiSlugger(); - $this->parsers = [ - new Paragraph(), - new ListBlock(), - new ThematicBreak(), - ]; } public function supports(string $inputFormat): bool @@ -81,92 +58,30 @@ private function parseDocument(NodeWalker $walker, string $hash): DocumentNode $this->document = $document; while ($event = $walker->next()) { - $node = $event->getNode(); - - foreach ($this->parsers as $parser) { - if (!$parser->supports($event)) { - continue; - } + $commonMarkNode = $event->getNode(); - $document->addChildNode($parser->parse($this, $walker)); - } - - // ignore all Entering events; these are only used to switch to another context and context switching - // is defined above if ($event->isEntering()) { - continue; - } - - if ($node instanceof Document) { - return $document; - } - - if ($node instanceof Heading) { - $content = $node->firstChild(); - if ($content instanceof Text === false) { - continue; + // Use entering events for context switching + foreach ($this->parsers as $parser) { + if ($parser->supports($event)) { + $document->addChildNode($parser->parse($this, $walker, $commonMarkNode)); + break; + } } - $title = new TitleNode( - InlineCompoundNode::getPlainTextInlineNode($content->getLiteral()), - $node->getLevel(), - $this->idGenerator->slug($content->getLiteral())->lower()->toString(), - ); - $document->addChildNode($title); - continue; - } - - if ($node instanceof Text) { - $spanNode = new SpanNode($node->getLiteral(), []); - $document->addChildNode($spanNode); - continue; - } - - if ($node instanceof Code) { - $spanNode = new CodeNode([$node->getLiteral()]); - $document->addChildNode($spanNode); - continue; - } - - if ($node instanceof Link) { - $spanNode = new AnchorNode($node->getUrl()); - $document->addChildNode($spanNode); - continue; - } - - if ($node instanceof FencedCode) { - $spanNode = new CodeNode([$node->getLiteral()]); - $document->addChildNode($spanNode); continue; } - if ($node instanceof HtmlBlock) { - $spanNode = new RawNode($node->getLiteral()); - $document->addChildNode($spanNode); - continue; + if ($commonMarkNode instanceof Document) { + return $document; } - echo 'DOCUMENT CONTEXT: I am ' - . 'leaving' - . ' a ' - . $node::class - . ' node' - . "\n"; + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'Document')); } return $document; } - public function parseParagraph(NodeWalker $walker): ParagraphNode - { - return (new Paragraph())->parse($this, $walker); - } - - public function parseListBlock(NodeWalker $walker): ListNode - { - return (new ListBlock())->parse($this, $walker); - } - public function getParserContext(): ParserContext { if ($this->parserContext === null) { diff --git a/packages/guides-markdown/src/Markdown/ParserInterface.php b/packages/guides-markdown/src/Markdown/ParserInterface.php index faaf90ae0..d80d84d2d 100644 --- a/packages/guides-markdown/src/Markdown/ParserInterface.php +++ b/packages/guides-markdown/src/Markdown/ParserInterface.php @@ -4,6 +4,7 @@ namespace phpDocumentor\Guides\Markdown; +use League\CommonMark\Node\Node as CommonMarkNode; use League\CommonMark\Node\NodeWalker; use League\CommonMark\Node\NodeWalkerEvent; use phpDocumentor\Guides\MarkupLanguageParser as GuidesParser; @@ -13,7 +14,7 @@ interface ParserInterface { /** @return TValue */ - public function parse(GuidesParser $parser, NodeWalker $walker): Node; + public function parse(GuidesParser $parser, NodeWalker $walker, CommonMarkNode $current): Node; public function supports(NodeWalkerEvent $event): bool; } diff --git a/packages/guides-markdown/src/Markdown/Parsers/AbstractBlock.php b/packages/guides-markdown/src/Markdown/Parsers/AbstractBlockParser.php similarity index 80% rename from packages/guides-markdown/src/Markdown/Parsers/AbstractBlock.php rename to packages/guides-markdown/src/Markdown/Parsers/AbstractBlockParser.php index b9ff82fc7..b22c39fce 100644 --- a/packages/guides-markdown/src/Markdown/Parsers/AbstractBlock.php +++ b/packages/guides-markdown/src/Markdown/Parsers/AbstractBlockParser.php @@ -11,6 +11,6 @@ * @template TValue as Node * @implements ParserInterface */ -abstract class AbstractBlock implements ParserInterface +abstract class AbstractBlockParser implements ParserInterface { } diff --git a/packages/guides-markdown/src/Markdown/Parsers/BlockQuoteParser.php b/packages/guides-markdown/src/Markdown/Parsers/BlockQuoteParser.php new file mode 100644 index 000000000..d659c08d9 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/BlockQuoteParser.php @@ -0,0 +1,62 @@ + */ +final class BlockQuoteParser extends AbstractBlockParser +{ + /** @param iterable> $subParsers */ + public function __construct( + private readonly iterable $subParsers, + private readonly LoggerInterface $logger, + ) { + } + + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): QuoteNode + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + foreach ($this->subParsers as $subParser) { + if ($subParser->supports($event)) { + $content[] = $subParser->parse($parser, $walker, $commonMarkNode); + break; + } + } + + continue; + } + + // leaving the heading node + if ($commonMarkNode instanceof BlockQuote) { + return new QuoteNode($content); + } + + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'BlockQuote')); + } + + throw new RuntimeException('Unexpected end of NodeWalker'); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof BlockQuote; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/CodeBlockParser.php b/packages/guides-markdown/src/Markdown/Parsers/CodeBlockParser.php new file mode 100644 index 000000000..16806e2df --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/CodeBlockParser.php @@ -0,0 +1,37 @@ + */ +final class CodeBlockParser extends AbstractBlockParser +{ + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): CodeNode + { + assert($current instanceof IndentedCode || $current instanceof FencedCode); + $walker->next(); + $codeNode = new CodeNode(explode("\n", $current->getLiteral())); + if ($current instanceof FencedCode && $current->getInfo() !== null) { + $codeNode = $codeNode->withOptions(['caption' => $current->getInfo()]); + } + + return $codeNode; + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->getNode() instanceof IndentedCode || $event->getNode() instanceof FencedCode; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/HeaderParser.php b/packages/guides-markdown/src/Markdown/Parsers/HeaderParser.php new file mode 100644 index 000000000..c261b405a --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/HeaderParser.php @@ -0,0 +1,71 @@ + */ +final class HeaderParser extends AbstractBlockParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + private readonly iterable $inlineParsers, + private readonly LoggerInterface $logger, + private readonly AsciiSlugger $idGenerator, + ) { + } + + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): Node + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + foreach ($this->inlineParsers as $subParser) { + if ($subParser->supports($event)) { + $content[] = $subParser->parse($parser, $walker, $commonMarkNode); + break; + } + } + + continue; + } + + // leaving the heading node + if ($commonMarkNode instanceof Heading) { + return new TitleNode( + new InlineCompoundNode($content), + $commonMarkNode->getLevel(), + $this->idGenerator->slug($content[0]->toString() ?? '')->lower()->toString(), + ); + } + + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'Header')); + } + + throw new RuntimeException('Unexpected end of NodeWalker'); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof Heading; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineParser.php new file mode 100644 index 000000000..59422a426 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineParser.php @@ -0,0 +1,20 @@ + + */ +abstract class AbstractInlineParser implements ParserInterface +{ + abstract public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): InlineNode; +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineTextDecoratorParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineTextDecoratorParser.php new file mode 100644 index 000000000..30c426ebf --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/AbstractInlineTextDecoratorParser.php @@ -0,0 +1,80 @@ + + */ +abstract class AbstractInlineTextDecoratorParser extends AbstractInlineParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + private readonly iterable $inlineParsers, + private readonly LoggerInterface $logger, + ) { + } + + /** @return TValue */ + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): InlineNode + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + foreach ($this->inlineParsers as $subParser) { + if (!$subParser->supports($event)) { + continue; + } + + $content[] = $subParser->parse($parser, $walker, $commonMarkNode); + } + + continue; + } + + if ($this->supportsCommonMarkNode($commonMarkNode)) { + if (count($content) === 1 && $content[0] instanceof PlainTextInlineNode) { + return $this->createInlineNode($commonMarkNode, $content[0]->getValue()); + } + + $this->logger->warning(sprintf('%s CONTEXT: Content of emphasis could not be interpreted: %s', $this->getType(), var_export($content, true))); + + return $this->createInlineNode($commonMarkNode, null); + } + + $this->logger->warning(sprintf('%s context does not allow a %s node', $this->getType(), $commonMarkNode::class)); + } + + throw new RuntimeException(sprintf('Unexpected end of NodeWalker, %s context was not closed', $this->getType())); + } + + abstract protected function getType(): string; + + /** @return TValue */ + abstract protected function createInlineNode(CommonMarkNode $commonMarkNode, string|null $content): InlineNode; + + abstract protected function supportsCommonMarkNode(CommonMarkNode $commonMarkNode): bool; + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $this->supportsCommonMarkNode($event->getNode()); + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/EmphasisParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/EmphasisParser.php new file mode 100644 index 000000000..585647878 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/EmphasisParser.php @@ -0,0 +1,38 @@ + */ +final class EmphasisParser extends AbstractInlineTextDecoratorParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + iterable $inlineParsers, + LoggerInterface $logger, + ) { + parent::__construct($inlineParsers, $logger); + } + + protected function getType(): string + { + return 'Emphasis'; + } + + protected function createInlineNode(CommonMarkNode $commonMarkNode, string|null $content): InlineNode + { + return new EmphasisInlineNode($content ?? ''); + } + + protected function supportsCommonMarkNode(CommonMarkNode $commonMarkNode): bool + { + return $commonMarkNode instanceof Emphasis; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineCodeParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineCodeParser.php new file mode 100644 index 000000000..4fe6e372f --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineCodeParser.php @@ -0,0 +1,30 @@ + */ +final class InlineCodeParser extends AbstractInlineParser +{ + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): LiteralInlineNode + { + assert($current instanceof Code); + + return new LiteralInlineNode($current->getLiteral()); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->getNode() instanceof Code; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineImageParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineImageParser.php new file mode 100644 index 000000000..e8e304fc3 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/InlineImageParser.php @@ -0,0 +1,42 @@ + */ +final class InlineImageParser extends AbstractInlineTextDecoratorParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + iterable $inlineParsers, + LoggerInterface $logger, + ) { + parent::__construct($inlineParsers, $logger); + } + + protected function getType(): string + { + return 'Image'; + } + + protected function createInlineNode(CommonMarkNode $commonMarkNode, string|null $content): InlineNode + { + assert($commonMarkNode instanceof Image); + + return new ImageInlineNode($commonMarkNode->getUrl(), $content ?? ''); + } + + protected function supportsCommonMarkNode(CommonMarkNode $commonMarkNode): bool + { + return $commonMarkNode instanceof Image; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/LinkParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/LinkParser.php new file mode 100644 index 000000000..395243846 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/LinkParser.php @@ -0,0 +1,46 @@ + */ +final class LinkParser extends AbstractInlineTextDecoratorParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + iterable $inlineParsers, + LoggerInterface $logger, + ) { + parent::__construct($inlineParsers, $logger); + } + + protected function getType(): string + { + return 'Link'; + } + + protected function createInlineNode(CommonMarkNode $commonMarkNode, string|null $content): InlineNode + { + assert($commonMarkNode instanceof Link); + if (is_string($content)) { + return new HyperLinkNode($content, $commonMarkNode->getUrl()); + } + + return new HyperLinkNode($commonMarkNode->getUrl(), $commonMarkNode->getUrl()); + } + + protected function supportsCommonMarkNode(CommonMarkNode $commonMarkNode): bool + { + return $commonMarkNode instanceof Link; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/PlainTextParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/PlainTextParser.php new file mode 100644 index 000000000..4643a3b52 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/PlainTextParser.php @@ -0,0 +1,40 @@ + */ +final class PlainTextParser extends AbstractInlineParser +{ + public function __construct( + private readonly LoggerInterface $logger, + ) { + } + + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): PlainTextInlineNode + { + if (!$current instanceof Text) { + $this->logger->warning(sprintf('Expected plaintext, encountered a %s node', $current::class)); + + return new PlainTextInlineNode(''); + } + + return new PlainTextInlineNode($current->getLiteral()); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof Text; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/StrongParser.php b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/StrongParser.php new file mode 100644 index 000000000..617383f2c --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/InlineParsers/StrongParser.php @@ -0,0 +1,38 @@ + */ +final class StrongParser extends AbstractInlineTextDecoratorParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + iterable $inlineParsers, + LoggerInterface $logger, + ) { + parent::__construct($inlineParsers, $logger); + } + + protected function getType(): string + { + return 'StrongDecorator'; + } + + protected function createInlineNode(CommonMarkNode $commonMarkNode, string|null $content): InlineNode + { + return new StrongInlineNode($content ?? ''); + } + + protected function supportsCommonMarkNode(CommonMarkNode $commonMarkNode): bool + { + return $commonMarkNode instanceof Strong; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/ListBlock.php b/packages/guides-markdown/src/Markdown/Parsers/ListBlock.php deleted file mode 100644 index 6449783e2..000000000 --- a/packages/guides-markdown/src/Markdown/Parsers/ListBlock.php +++ /dev/null @@ -1,48 +0,0 @@ - */ -final class ListBlock extends AbstractBlock -{ - /** @return ListNode */ - public function parse(MarkupLanguageParser $parser, NodeWalker $walker): CompoundNode - { - $context = new ListNode([], false); - - while ($event = $walker->next()) { - $node = $event->getNode(); - - if ($event->isEntering()) { - continue; - } - - if ($node instanceof CommonMarkListBlock) { - return $context; - } - - echo 'LIST CONTEXT: I am ' - . 'leaving' - . ' a ' - . $node::class - . ' node' - . "\n"; - } - - return $context; - } - - public function supports(NodeWalkerEvent $event): bool - { - return $event->isEntering() && $event->getNode() instanceof CommonMarkListBlock; - } -} diff --git a/packages/guides-markdown/src/Markdown/Parsers/ListBlockParser.php b/packages/guides-markdown/src/Markdown/Parsers/ListBlockParser.php new file mode 100644 index 000000000..d02e4c3fe --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/ListBlockParser.php @@ -0,0 +1,56 @@ + */ +final class ListBlockParser extends AbstractBlockParser +{ + public function __construct( + private readonly ListItemParser $listItemParser, + private readonly LoggerInterface $logger, + ) { + } + + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): ListNode + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + if ($this->listItemParser->supports($event)) { + $content[] = $this->listItemParser->parse($parser, $walker, $commonMarkNode); + } + + continue; + } + + if ($commonMarkNode instanceof ListBlock) { + return new ListNode($content, $content[0]->isOrdered()); + } + + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'List')); + } + + throw new RuntimeException('Unexpected end of NodeWalker in list block'); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof ListBlock; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/ListItemParser.php b/packages/guides-markdown/src/Markdown/Parsers/ListItemParser.php new file mode 100644 index 000000000..e3b7f53e6 --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/ListItemParser.php @@ -0,0 +1,65 @@ + */ +final class ListItemParser extends AbstractBlockParser +{ + /** @param iterable> $subParsers */ + public function __construct( + private readonly iterable $subParsers, + private readonly LoggerInterface $logger, + ) { + } + + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): ListItemNode + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + foreach ($this->subParsers as $subParser) { + if ($subParser->supports($event)) { + $content[] = $subParser->parse($parser, $walker, $commonMarkNode); + break; + } + } + + continue; + } + + if ($commonMarkNode instanceof ListItem) { + $prefix = $commonMarkNode->getListData()->bulletChar ?? $commonMarkNode->getListData()->delimiter ?? ''; + $ordered = $commonMarkNode->getListData()->type === ListBlock::TYPE_ORDERED; + + return new ListItemNode($prefix, $ordered, $content); + } + + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'List')); + } + + throw new RuntimeException('Unexpected end of NodeWalker in list item'); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof ListItem; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/Paragraph.php b/packages/guides-markdown/src/Markdown/Parsers/Paragraph.php deleted file mode 100644 index 30cced2d8..000000000 --- a/packages/guides-markdown/src/Markdown/Parsers/Paragraph.php +++ /dev/null @@ -1,49 +0,0 @@ - */ -final class Paragraph extends AbstractBlock -{ - /** @return ParagraphNode */ - public function parse(MarkupLanguageParser $parser, NodeWalker $walker): CompoundNode - { - $context = new ParagraphNode([new SpanNode('', [])]); - - while ($event = $walker->next()) { - $node = $event->getNode(); - - if ($event->isEntering()) { - continue; - } - - if ($node instanceof CommonMarkParagraph) { - return $context; - } - - echo 'PARAGRAPH CONTEXT: I am ' - . 'leaving' - . ' a ' - . $node::class - . ' node' - . "\n"; - } - - return $context; - } - - public function supports(NodeWalkerEvent $event): bool - { - return $event->isEntering() && $event->getNode() instanceof CommonMarkParagraph; - } -} diff --git a/packages/guides-markdown/src/Markdown/Parsers/ParagraphParser.php b/packages/guides-markdown/src/Markdown/Parsers/ParagraphParser.php new file mode 100644 index 000000000..fa704d09f --- /dev/null +++ b/packages/guides-markdown/src/Markdown/Parsers/ParagraphParser.php @@ -0,0 +1,66 @@ + */ +final class ParagraphParser extends AbstractBlockParser +{ + /** @param iterable> $inlineParsers */ + public function __construct( + private readonly iterable $inlineParsers, + private readonly LoggerInterface $logger, + ) { + } + + /** @return ParagraphNode */ + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): CompoundNode + { + $content = []; + + while ($event = $walker->next()) { + $commonMarkNode = $event->getNode(); + + if ($event->isEntering()) { + foreach ($this->inlineParsers as $subParser) { + if (!$subParser->supports($event)) { + continue; + } + + $content[] = $subParser->parse($parser, $walker, $commonMarkNode); + } + + continue; + } + + if ($commonMarkNode instanceof CommonMarkParagraph) { + return new ParagraphNode([new InlineCompoundNode($content)]); + } + + $this->logger->warning(sprintf('"%s" node is not yet supported in context %s. ', $commonMarkNode::class, 'Paragraph')); + } + + throw new RuntimeException('Unexpected end of NodeWalker'); + } + + public function supports(NodeWalkerEvent $event): bool + { + return $event->isEntering() && $event->getNode() instanceof CommonMarkParagraph; + } +} diff --git a/packages/guides-markdown/src/Markdown/Parsers/ThematicBreak.php b/packages/guides-markdown/src/Markdown/Parsers/SeparatorParser.php similarity index 63% rename from packages/guides-markdown/src/Markdown/Parsers/ThematicBreak.php rename to packages/guides-markdown/src/Markdown/Parsers/SeparatorParser.php index a50424562..58017f17c 100644 --- a/packages/guides-markdown/src/Markdown/Parsers/ThematicBreak.php +++ b/packages/guides-markdown/src/Markdown/Parsers/SeparatorParser.php @@ -4,23 +4,25 @@ namespace phpDocumentor\Guides\Markdown\Parsers; -use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak as CommonMark; +use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak; +use League\CommonMark\Node\Node as CommonMarkNode; use League\CommonMark\Node\NodeWalker; use League\CommonMark\Node\NodeWalkerEvent; use phpDocumentor\Guides\MarkupLanguageParser; -use phpDocumentor\Guides\Nodes\CompoundNode; use phpDocumentor\Guides\Nodes\SeparatorNode; -/** @extends AbstractBlock */ -final class ThematicBreak extends AbstractBlock +/** @extends AbstractBlockParser */ +final class SeparatorParser extends AbstractBlockParser { - public function parse(MarkupLanguageParser $parser, NodeWalker $walker): CompoundNode + public function parse(MarkupLanguageParser $parser, NodeWalker $walker, CommonMarkNode $current): SeparatorNode { + $walker->next(); + return new SeparatorNode(1); } public function supports(NodeWalkerEvent $event): bool { - return !$event->isEntering() && $event->getNode() instanceof CommonMark; + return $event->getNode() instanceof ThematicBreak; } } diff --git a/packages/guides/resources/template/html/inline/image.html.twig b/packages/guides/resources/template/html/inline/image.html.twig new file mode 100644 index 000000000..0923a3eb3 --- /dev/null +++ b/packages/guides/resources/template/html/inline/image.html.twig @@ -0,0 +1,2 @@ +{% apply spaceless %}{{- node.altText -}} +{% endapply %} diff --git a/packages/guides/src/DependencyInjection/Compiler/NodeRendererPass.php b/packages/guides/src/DependencyInjection/Compiler/NodeRendererPass.php index 25e14bea2..706177d74 100644 --- a/packages/guides/src/DependencyInjection/Compiler/NodeRendererPass.php +++ b/packages/guides/src/DependencyInjection/Compiler/NodeRendererPass.php @@ -24,6 +24,7 @@ use phpDocumentor\Guides\Nodes\Inline\FootnoteInlineNode; use phpDocumentor\Guides\Nodes\Inline\GenericTextRoleInlineNode; use phpDocumentor\Guides\Nodes\Inline\HyperLinkNode; +use phpDocumentor\Guides\Nodes\Inline\ImageInlineNode; use phpDocumentor\Guides\Nodes\Inline\LiteralInlineNode; use phpDocumentor\Guides\Nodes\Inline\NewlineInlineNode; use phpDocumentor\Guides\Nodes\Inline\PlainTextInlineNode; @@ -85,6 +86,7 @@ final class NodeRendererPass implements CompilerPassInterface FootnoteNode::class => 'body/footnote.html.twig', AnnotationListNode::class => 'body/annotation-list.html.twig', // Inline + ImageInlineNode::class => 'inline/image.html.twig', InlineCompoundNode::class => 'inline/inline-node.html.twig', AbbreviationInlineNode::class => 'inline/textroles/abbreviation.html.twig', CitationInlineNode::class => 'inline/citation.html.twig', diff --git a/packages/guides/src/Handlers/ParseFileHandler.php b/packages/guides/src/Handlers/ParseFileHandler.php index f02c781f6..548f119b9 100644 --- a/packages/guides/src/Handlers/ParseFileHandler.php +++ b/packages/guides/src/Handlers/ParseFileHandler.php @@ -96,9 +96,10 @@ private function createDocument( $document = null; try { $document = $this->parser->parse($preParseDocumentEvent->getContents(), $extension)->withIsRoot($isRoot); - } catch (RuntimeException) { + } catch (RuntimeException $e) { $this->logger->error( sprintf('Unable to parse %s, input format was not recognized', $path), + ['error' => $e], ); } diff --git a/packages/guides/src/Nodes/DocumentNode.php b/packages/guides/src/Nodes/DocumentNode.php index e3860d7bb..1acead6b5 100644 --- a/packages/guides/src/Nodes/DocumentNode.php +++ b/packages/guides/src/Nodes/DocumentNode.php @@ -116,6 +116,10 @@ public function getTitle(): TitleNode|null if ($node instanceof SectionNode && $node->getTitle()->getLevel() === 1) { return $node->getTitle(); } + + if ($node instanceof TitleNode) { + return $node; + } } return null; diff --git a/packages/guides/src/Nodes/Inline/ImageInlineNode.php b/packages/guides/src/Nodes/Inline/ImageInlineNode.php new file mode 100644 index 000000000..b267d364f --- /dev/null +++ b/packages/guides/src/Nodes/Inline/ImageInlineNode.php @@ -0,0 +1,28 @@ +url; + } + + public function getAltText(): string + { + return $this->altText; + } +} diff --git a/tests/Integration/tests/markdown/blockquote-md/expected/index.html b/tests/Integration/tests/markdown/blockquote-md/expected/index.html new file mode 100644 index 000000000..c715e0dfb --- /dev/null +++ b/tests/Integration/tests/markdown/blockquote-md/expected/index.html @@ -0,0 +1,35 @@ + + + + Markdown Blockquotes - Project Title + + + +

Markdown Blockquotes

+ +

This is a blockquote.It can span multiple lines.

+ +

Blockquotes with Multiple Paragraphs

+ +

Dorothy followed her through many of the beautiful rooms in her castle.

The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.

+ +

Blockquotes with Other Elements

+ +

The quarterly results look great!

+ +
    +
  • Revenue was off the chart.

  • + +
  • Profits were higher than ever.

  • + +
+

Everything is going according to plan.

+ +

Blockquotes Best Practices

+ +

Try to put a blank line before...

+

This is a blockquote

+ +

...and after a blockquote.

+ + diff --git a/tests/Integration/tests/markdown/blockquote-md/input/guides.xml b/tests/Integration/tests/markdown/blockquote-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/blockquote-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/blockquote-md/input/index.md b/tests/Integration/tests/markdown/blockquote-md/input/index.md new file mode 100644 index 000000000..dcab9a85e --- /dev/null +++ b/tests/Integration/tests/markdown/blockquote-md/input/index.md @@ -0,0 +1,27 @@ +# Markdown Blockquotes + +> This is a blockquote. +> It can span multiple lines. + +## Blockquotes with Multiple Paragraphs + +> Dorothy followed her through many of the beautiful rooms in her castle. +> +> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood. + +## Blockquotes with Other Elements + +> The quarterly results look great! +> +> - Revenue was off the chart. +>- Profits were higher than ever. +> +> *Everything* is going according to **plan**. + +## Blockquotes Best Practices + +Try to put a blank line before... + +> This is a blockquote + +...and after a blockquote. diff --git a/tests/Integration/tests/markdown/code-md/expected/index.html b/tests/Integration/tests/markdown/code-md/expected/index.html new file mode 100644 index 000000000..bba8f8ee5 --- /dev/null +++ b/tests/Integration/tests/markdown/code-md/expected/index.html @@ -0,0 +1,33 @@ + + + + Markdown with Code Blocks - Project Title + + + +

Markdown with Code Blocks

+ +
<html>
+  <head>
+  </head>
+</html>
+
+

Fenced Code Blocks

+ +
{
+  "firstName": "John",
+  "lastName": "Smith",
+  "age": 25
+}
+
+

Fenced Code Block with caption

+ +
procedure startSwinging(swing, child)
+   while child.isComfortable()
+       swing.giveGentlePush()
+       waitForNextIteration()
+   end while
+end procedure
+
+ + diff --git a/tests/Integration/tests/markdown/code-md/input/guides.xml b/tests/Integration/tests/markdown/code-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/code-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/code-md/input/index.md b/tests/Integration/tests/markdown/code-md/input/index.md new file mode 100644 index 000000000..099159e36 --- /dev/null +++ b/tests/Integration/tests/markdown/code-md/input/index.md @@ -0,0 +1,26 @@ +# Markdown with Code Blocks + + + + + + +## Fenced Code Blocks + +``` +{ + "firstName": "John", + "lastName": "Smith", + "age": 25 +} +``` + +## Fenced Code Block with caption + +```pseudocode +procedure startSwinging(swing, child) + while child.isComfortable() + swing.giveGentlePush() + waitForNextIteration() + end while +end procedure \ No newline at end of file diff --git a/tests/Integration/tests/markdown/emphasis-md/expected/index.html b/tests/Integration/tests/markdown/emphasis-md/expected/index.html new file mode 100644 index 000000000..014e2c53c --- /dev/null +++ b/tests/Integration/tests/markdown/emphasis-md/expected/index.html @@ -0,0 +1,12 @@ + + + + Markdown with emphasis - Project Title + + + +

Markdown with emphasis

+ +

Italic or ItalicBold or Bold

+ + diff --git a/tests/Integration/tests/markdown/emphasis-md/input/guides.xml b/tests/Integration/tests/markdown/emphasis-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/emphasis-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/emphasis-md/input/index.md b/tests/Integration/tests/markdown/emphasis-md/input/index.md new file mode 100644 index 000000000..fc8f1bd97 --- /dev/null +++ b/tests/Integration/tests/markdown/emphasis-md/input/index.md @@ -0,0 +1,4 @@ +# Markdown with emphasis + +*Italic* or _Italic_ +**Bold** or __Bold__ diff --git a/tests/Integration/tests/markdown/image-md/expected/index.html b/tests/Integration/tests/markdown/image-md/expected/index.html new file mode 100644 index 000000000..6b4d3eb45 --- /dev/null +++ b/tests/Integration/tests/markdown/image-md/expected/index.html @@ -0,0 +1,12 @@ + + + + Markdown Image - Project Title + + + +

Markdown Image

+ +

Hero Illustrations

+ + diff --git a/tests/Integration/tests/markdown/image-md/input/guides.xml b/tests/Integration/tests/markdown/image-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/image-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/image-md/input/hero-illustration.svg b/tests/Integration/tests/markdown/image-md/input/hero-illustration.svg new file mode 100644 index 000000000..002f8feff --- /dev/null +++ b/tests/Integration/tests/markdown/image-md/input/hero-illustration.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Integration/tests/markdown/image-md/input/index.md b/tests/Integration/tests/markdown/image-md/input/index.md new file mode 100644 index 000000000..38f232b8a --- /dev/null +++ b/tests/Integration/tests/markdown/image-md/input/index.md @@ -0,0 +1,3 @@ +# Markdown Image + +![Hero Illustrations](hero-illustration.svg) diff --git a/tests/Integration/tests/markdown/index-md/expected/index.html b/tests/Integration/tests/markdown/index-md/expected/index.html index 419dbc308..6fbd4488d 100644 --- a/tests/Integration/tests/markdown/index-md/expected/index.html +++ b/tests/Integration/tests/markdown/index-md/expected/index.html @@ -1,16 +1,16 @@ - Project Title + Markdown Example - Project Title

Markdown Example

-

This is a Markdown document with some basic formatting.

+

This is a Markdown document with some basic formatting.

Headings

-

You can create headings using hash symbols.

- +

You can create headings using hash symbols.

+

This text is part of a paragraph under the "Headings" heading.

diff --git a/tests/Integration/tests/markdown/index-md/input/index.md b/tests/Integration/tests/markdown/index-md/input/index.md index 3e77e4d0a..184118784 100644 --- a/tests/Integration/tests/markdown/index-md/input/index.md +++ b/tests/Integration/tests/markdown/index-md/input/index.md @@ -5,3 +5,5 @@ This is a Markdown document with some basic formatting. ## Headings You can create headings using hash symbols. + +This text is part of a paragraph under the "Headings" heading. diff --git a/tests/Integration/tests/markdown/index-md/input/skip b/tests/Integration/tests/markdown/index-md/input/skip deleted file mode 100644 index 48708bf2c..000000000 --- a/tests/Integration/tests/markdown/index-md/input/skip +++ /dev/null @@ -1 +0,0 @@ -Paragraph text is missing in output, there is a warning about the document having no title even though it has a title \ No newline at end of file diff --git a/tests/Integration/tests/markdown/inline-code-md/expected/index.html b/tests/Integration/tests/markdown/inline-code-md/expected/index.html new file mode 100644 index 000000000..7bba725e2 --- /dev/null +++ b/tests/Integration/tests/markdown/inline-code-md/expected/index.html @@ -0,0 +1,13 @@ + + + + Markdown with inline Code - Project Title + + + +

Markdown with inline Code

+ +

Lorem Ipsum Dolor: $dolor...

+

Use `code` in your Markdown file.

+ + diff --git a/tests/Integration/tests/markdown/inline-code-md/input/guides.xml b/tests/Integration/tests/markdown/inline-code-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/inline-code-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/inline-code-md/input/index.md b/tests/Integration/tests/markdown/inline-code-md/input/index.md new file mode 100644 index 000000000..ee105de49 --- /dev/null +++ b/tests/Integration/tests/markdown/inline-code-md/input/index.md @@ -0,0 +1,5 @@ +# Markdown with inline Code + +Lorem Ipsum Dolor: `$dolor`... + +``Use `code` in your Markdown file.`` diff --git a/tests/Integration/tests/markdown/link-md/expected/index.html b/tests/Integration/tests/markdown/link-md/expected/index.html new file mode 100644 index 000000000..018f82157 --- /dev/null +++ b/tests/Integration/tests/markdown/link-md/expected/index.html @@ -0,0 +1,13 @@ + + + + Markdown with links - Project Title + + + +

Markdown with links

+ +

This is a Markdown document with some basic formatting.

+

See also: Link Text

+ + diff --git a/tests/Integration/tests/markdown/link-md/input/guides.xml b/tests/Integration/tests/markdown/link-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/link-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/link-md/input/index.md b/tests/Integration/tests/markdown/link-md/input/index.md new file mode 100644 index 000000000..319c6c118 --- /dev/null +++ b/tests/Integration/tests/markdown/link-md/input/index.md @@ -0,0 +1,5 @@ +# Markdown with links + +This is a Markdown document with some basic formatting. + +See also: [Link Text](https://www.example.com) \ No newline at end of file diff --git a/tests/Integration/tests/markdown/lists-md/expected/index.html b/tests/Integration/tests/markdown/lists-md/expected/index.html new file mode 100644 index 000000000..c2389178c --- /dev/null +++ b/tests/Integration/tests/markdown/lists-md/expected/index.html @@ -0,0 +1,53 @@ + + + + Markdown Lists - Project Title + + + +

Markdown Lists

+ +

Unordered

+ + + +
    +
  • Item 1

  • + +
  • Item 2

    + +
      +
    • Subitem A

    • + +
    • Subitem B

    • + +
    +
  • + +
  • Item 3

  • + +
+ +

Ordered

+ + + +
    +
  1. First item

  2. + +
  3. Second item

    + +
      +
    1. Subitem A

    2. + +
    3. Subitem B

    4. + +
    +
  4. + +
  5. Third item

  6. + +
+ + + diff --git a/tests/Integration/tests/markdown/lists-md/input/guides.xml b/tests/Integration/tests/markdown/lists-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/lists-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/lists-md/input/index.md b/tests/Integration/tests/markdown/lists-md/input/index.md new file mode 100644 index 000000000..89c9dcee7 --- /dev/null +++ b/tests/Integration/tests/markdown/lists-md/input/index.md @@ -0,0 +1,16 @@ +# Markdown Lists +## Unordered + +- Item 1 +- Item 2 + - Subitem A + - Subitem B +- Item 3 + +## Ordered + +1. First item +2. Second item + 1. Subitem A + 2. Subitem B +3. Third item diff --git a/tests/Integration/tests/markdown/readme-md/expected/index.html b/tests/Integration/tests/markdown/readme-md/expected/index.html new file mode 100644 index 000000000..f8c7e585d --- /dev/null +++ b/tests/Integration/tests/markdown/readme-md/expected/index.html @@ -0,0 +1,122 @@ + + + + Child's Swing Usage Guide - Project Title + + + +

Child's Swing Usage Guide

+ +

Welcome to the world of fun and laughter with our child's swing! Follow thesimple steps below to ensure a safe and enjoyable experience for your littleone.

+

Table of Contents

+ + + + + +
+ +

Installation

+ +

Swing Image

+ + +
    +
  1. Choose a Sturdy Location: Select a location with a sturdy beam or swingset structure capable of supporting the weight of the swing and the child.

  2. + +
  3. Securely Attach the Swing: Use strong and reliable ropesor chains to securely attach the swing to the chosen location. Double-checkthe knots or hardware to ensure they are tight and safe.

  4. + +
  5. Adjust the Height: Adjust the height of the swing to a level that iscomfortable for your child. Ensure that it is not too high or too low.

  6. + +
+ +

Usage

+ + + +
    +
  1. Place the Child in the Swing: Gently place your child in the swing seat,ensuring they are properly seated and secure.

  2. + +
  3. Hold onto the Swing: Provide support and hold onto the swing until yourchild is comfortably seated and ready to swing.

  4. + +
  5. Start Swinging: Give a gentle push to start the swinging motion. Observeyour child's comfort level and adjust the swinging speed accordingly.

    procedure startSwinging(swing, child)
    +    while child.isComfortable()
    +        swing.giveGentlePush()
    +        waitForNextIteration()
    +    end while
    +end procedure
    +
    +
  6. + +
  7. Supervise Always: Always supervise your child while they are using theswing. Ensure they are using it safely and within the recommended age and weightlimits.

  8. + +
+ +

"Swinging is not just a physical activity; it's a journey into the world ofimagination and joy."

+ +

Additional Tips:

+ + + +
    +
  • Bring your child's favorite toys to make swinging even more enjoyable.

  • + +
  • Consider creating a soft landing area with cushions or grass underneath the swing.

  • + +
+ +

Safety Guidelines

+ + + +
    +
  1. Check Hardware Regularly: Periodically inspect the ropes or chains, aswell as any hardware used to attach the swing. Replace any damaged or worn-outparts immediately.

  2. + +
  3. Set Age and Weight Limits: Follow the manufacturer's recommended age andweight limits for the swing. Do not exceed these limits to ensure safety.

  4. + +
  5. Use Proper Attire: Encourage your child to wear appropriate clothing,including closed-toe shoes, to prevent any injuries.

  6. + +
  7. No Rough Play: Instruct your child not to engage in rough play or attemptto stand up in the swing.

  8. + +
+ +

Maintenance

+ + + +
    +
  1. Clean the Swing: Wipe down the swing seat regularly to keep it clean andfree from dirt or debris.

  2. + +
  3. Inspect for Wear: Check the swing's components for signs of wear ordamage. Replace any worn-out parts promptly.

  4. + +
  5. Tighten Loose Hardware: Ensure that all nuts and bolts are securelytightened to maintain stability.

  6. + +
+ +

Troubleshooting

+ + + +
    +
  • Swing Doesn't Move Smoothly: Check for any knots or tangles in the ropesor chains. Untangle and ensure a smooth swinging motion.

  • + +
  • Unusual Sounds: If you hear any creaking or unusual sounds, inspect thehardware for any loose or damaged parts.

    If you hear screaming or crying, untangle the child.

  • + +
  • Uneven Swinging: Adjust the height of the swing to achieve a balanced andeven swinging motion.

  • + +
+ +

Remember, safety first! Enjoy your time on the swing with your little one.

+ + diff --git a/tests/Integration/tests/markdown/readme-md/input/guides.xml b/tests/Integration/tests/markdown/readme-md/input/guides.xml new file mode 100644 index 000000000..2ef460029 --- /dev/null +++ b/tests/Integration/tests/markdown/readme-md/input/guides.xml @@ -0,0 +1,8 @@ + + + + diff --git a/tests/Integration/tests/markdown/readme-md/input/index.md b/tests/Integration/tests/markdown/readme-md/input/index.md new file mode 100644 index 000000000..f6ad67e6a --- /dev/null +++ b/tests/Integration/tests/markdown/readme-md/input/index.md @@ -0,0 +1,99 @@ +# Child's Swing Usage Guide + +Welcome to the world of fun and laughter with our child's swing! Follow the +simple steps below to ensure a safe and enjoyable experience for your little +one. + +## Table of Contents +- [Installation](#installation) +- [Usage](#usage) +- [Safety Guidelines](#safety-guidelines) +- [Maintenance](#maintenance) +- [Troubleshooting](#troubleshooting) + +--- + +## Installation + +![Swing Image](https://upload.wikimedia.org/wikipedia/commons/a/a3/Forgiveness_5.gif) + +1. **Choose a Sturdy Location:** Select a location with a sturdy beam or swing + set structure capable of supporting the weight of the swing and the child. + +2. **Securely Attach the Swing:** Use strong and reliable [ropes](https://example.com/ropes) + or chains to securely attach the swing to the chosen location. Double-check + the knots or hardware to ensure they are tight and safe. + +3. **Adjust the Height:** Adjust the height of the swing to a level that is + comfortable for your child. Ensure that it is not too high or too low. + +## Usage + +1. **Place the Child in the Swing:** Gently place your child in the swing seat, + ensuring they are properly seated and secure. + +2. **Hold onto the Swing:** Provide support and hold onto the swing until your + child is comfortably seated and ready to swing. + +3. **Start Swinging:** Give a gentle push to start the swinging motion. Observe + your child's comfort level and adjust the swinging speed accordingly. + + ```pseudocode + procedure startSwinging(swing, child) + while child.isComfortable() + swing.giveGentlePush() + waitForNextIteration() + end while + end procedure + +4. **Supervise Always:** Always supervise your child while they are using the + swing. Ensure they are using it safely and within the recommended age and weight + limits. + +> "Swinging is not just a physical activity; it's a journey into the world of +> imagination and joy." + +### Additional Tips: +- Bring your child's favorite toys to make swinging even more enjoyable. +- Consider creating a soft landing area with cushions or grass underneath the swing. + +## Safety Guidelines + +1. **Check Hardware Regularly:** Periodically inspect the ropes or chains, as + well as any hardware used to attach the swing. Replace any damaged or worn-out + parts immediately. + +2. **Set Age and Weight Limits:** Follow the manufacturer's recommended age and + weight limits for the swing. Do not exceed these limits to ensure safety. + +3. **Use Proper Attire:** Encourage your child to wear appropriate clothing, + including closed-toe shoes, to prevent any injuries. + +4. **No Rough Play:** Instruct your child not to engage in rough play or attempt + to stand up in the swing. + +## Maintenance + +1. **Clean the Swing:** Wipe down the swing seat regularly to keep it clean and + free from dirt or debris. + +2. **Inspect for Wear:** Check the swing's components for signs of wear or + damage. Replace any worn-out parts promptly. + +3. **Tighten Loose Hardware:** Ensure that all nuts and bolts are securely + tightened to maintain stability. + +## Troubleshooting + +- **Swing Doesn't Move Smoothly:** Check for any knots or tangles in the ropes + or chains. Untangle and ensure a smooth swinging motion. + +- **Unusual Sounds:** If you hear any creaking or unusual sounds, inspect the + hardware for any loose or damaged parts. + + If you hear screaming or crying, untangle the child. + +- **Uneven Swinging:** Adjust the height of the swing to achieve a balanced and + even swinging motion. + +Remember, safety first! Enjoy your time on the swing with your little one.