1<?php
2
3/*
4 * This file is part of the league/commonmark package.
5 *
6 * (c) Colin O'Dell <colinodell@gmail.com>
7 * (c) 2015 Martin Hasoň <martin.hason@gmail.com>
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13declare(strict_types=1);
14
15namespace League\CommonMark\Extension\Attributes\Parser;
16
17use League\CommonMark\Extension\Attributes\Node\Attributes;
18use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
19use League\CommonMark\Node\Block\AbstractBlock;
20use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
21use League\CommonMark\Parser\Block\BlockContinue;
22use League\CommonMark\Parser\Block\BlockContinueParserInterface;
23use League\CommonMark\Parser\Cursor;
24
25final class AttributesBlockContinueParser extends AbstractBlockContinueParser
26{
27    private Attributes $block;
28
29    private AbstractBlock $container;
30
31    private bool $hasSubsequentLine = false;
32
33    /**
34     * @param array<string, mixed> $attributes The attributes identified by the block start parser
35     * @param AbstractBlock        $container  The node we were in when these attributes were discovered
36     */
37    public function __construct(array $attributes, AbstractBlock $container)
38    {
39        $this->block = new Attributes($attributes);
40
41        $this->container = $container;
42    }
43
44    public function getBlock(): AbstractBlock
45    {
46        return $this->block;
47    }
48
49    public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
50    {
51        $this->hasSubsequentLine = true;
52
53        $cursor->advanceToNextNonSpaceOrTab();
54
55        // Does this next line also have attributes?
56        $attributes = AttributesHelper::parseAttributes($cursor);
57        $cursor->advanceToNextNonSpaceOrTab();
58        if ($cursor->isAtEnd() && $attributes !== []) {
59            // It does! Merge them into what we parsed previously
60            $this->block->setAttributes(AttributesHelper::mergeAttributes(
61                $this->block->getAttributes(),
62                $attributes
63            ));
64
65            // Tell the core parser we've consumed everything
66            return BlockContinue::at($cursor);
67        }
68
69        // Okay, so there are no attributes on the next line
70        // If this next line is blank we know we can't target the next node, it must be a previous one
71        if ($cursor->isBlank()) {
72            $this->block->setTarget(Attributes::TARGET_PREVIOUS);
73        }
74
75        return BlockContinue::none();
76    }
77
78    public function closeBlock(): void
79    {
80        // Attributes appearing at the very end of the document won't have any last lines to check
81        // so we can make that determination here
82        if (! $this->hasSubsequentLine) {
83            $this->block->setTarget(Attributes::TARGET_PREVIOUS);
84        }
85
86        // We know this block must apply to the "previous" block, but that could be a sibling or parent,
87        // so we check the containing block to see which one it might be.
88        if ($this->block->getTarget() === Attributes::TARGET_PREVIOUS && $this->block->parent() === $this->container) {
89            $this->block->setTarget(Attributes::TARGET_PARENT);
90        }
91    }
92}
93