Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
/*
6
 * This file is part of the league/commonmark package.
7
 *
8
 * (c) Colin O'Dell <colinodell@gmail.com>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
 
14
namespace League\CommonMark\Extension\CommonMark\Parser\Block;
15
 
16
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
17
use League\CommonMark\Extension\CommonMark\Node\Block\ListData;
18
use League\CommonMark\Parser\Block\BlockStart;
19
use League\CommonMark\Parser\Block\BlockStartParserInterface;
20
use League\CommonMark\Parser\Cursor;
21
use League\CommonMark\Parser\MarkdownParserStateInterface;
22
use League\CommonMark\Util\RegexHelper;
23
use League\Config\ConfigurationAwareInterface;
24
use League\Config\ConfigurationInterface;
25
 
26
final class ListBlockStartParser implements BlockStartParserInterface, ConfigurationAwareInterface
27
{
28
    /** @psalm-readonly-allow-private-mutation */
29
    private ?ConfigurationInterface $config = null;
30
 
31
    /** @psalm-readonly-allow-private-mutation */
32
    private ?string $listMarkerRegex = null;
33
 
34
    public function setConfiguration(ConfigurationInterface $configuration): void
35
    {
36
        $this->config = $configuration;
37
    }
38
 
39
    public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
40
    {
41
        if ($cursor->isIndented()) {
42
            return BlockStart::none();
43
        }
44
 
45
        $listData = $this->parseList($cursor, $parserState->getParagraphContent() !== null);
46
        if ($listData === null) {
47
            return BlockStart::none();
48
        }
49
 
50
        $listItemParser = new ListItemParser($listData);
51
 
52
        // prepend the list block if needed
53
        $matched = $parserState->getLastMatchedBlockParser();
54
        if (! ($matched instanceof ListBlockParser) || ! $listData->equals($matched->getBlock()->getListData())) {
55
            $listBlockParser = new ListBlockParser($listData);
56
            // We start out with assuming a list is tight. If we find a blank line, we set it to loose later.
57
            $listBlockParser->getBlock()->setTight(true);
58
 
59
            return BlockStart::of($listBlockParser, $listItemParser)->at($cursor);
60
        }
61
 
62
        return BlockStart::of($listItemParser)->at($cursor);
63
    }
64
 
65
    private function parseList(Cursor $cursor, bool $inParagraph): ?ListData
66
    {
67
        $indent = $cursor->getIndent();
68
 
69
        $tmpCursor = clone $cursor;
70
        $tmpCursor->advanceToNextNonSpaceOrTab();
71
        $rest = $tmpCursor->getRemainder();
72
 
73
        if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) {
74
            $data               = new ListData();
75
            $data->markerOffset = $indent;
76
            $data->type         = ListBlock::TYPE_BULLET;
77
            $data->delimiter    = null;
78
            $data->bulletChar   = $rest[0];
79
            $markerLength       = 1;
80
        } elseif (($matches = RegexHelper::matchFirst('/^(\d{1,9})([.)])/', $rest)) && (! $inParagraph || $matches[1] === '1')) {
81
            $data               = new ListData();
82
            $data->markerOffset = $indent;
83
            $data->type         = ListBlock::TYPE_ORDERED;
84
            $data->start        = (int) $matches[1];
85
            $data->delimiter    = $matches[2] === '.' ? ListBlock::DELIM_PERIOD : ListBlock::DELIM_PAREN;
86
            $data->bulletChar   = null;
87
            $markerLength       = \strlen($matches[0]);
88
        } else {
89
            return null;
90
        }
91
 
92
        // Make sure we have spaces after
93
        $nextChar = $tmpCursor->peek($markerLength);
94
        if (! ($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
95
            return null;
96
        }
97
 
98
        // If it interrupts paragraph, make sure first line isn't blank
99
        if ($inParagraph && ! RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) {
100
            return null;
101
        }
102
 
103
        $cursor->advanceToNextNonSpaceOrTab(); // to start of marker
104
        $cursor->advanceBy($markerLength, true); // to end of marker
105
        $data->padding = self::calculateListMarkerPadding($cursor, $markerLength);
106
 
107
        return $data;
108
    }
109
 
110
    private static function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
111
    {
112
        $start          = $cursor->saveState();
113
        $spacesStartCol = $cursor->getColumn();
114
 
115
        while ($cursor->getColumn() - $spacesStartCol < 5) {
116
            if (! $cursor->advanceBySpaceOrTab()) {
117
                break;
118
            }
119
        }
120
 
121
        $blankItem         = $cursor->peek() === null;
122
        $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
123
 
124
        if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
125
            $cursor->restoreState($start);
126
            $cursor->advanceBySpaceOrTab();
127
 
128
            return $markerLength + 1;
129
        }
130
 
131
        return $markerLength + $spacesAfterMarker;
132
    }
133
 
134
    private function generateListMarkerRegex(): string
135
    {
136
        // No configuration given - use the defaults
137
        if ($this->config === null) {
138
            return $this->listMarkerRegex = '/^[*+-]/';
139
        }
140
 
141
        $markers = $this->config->get('commonmark/unordered_list_markers');
142
        \assert(\is_array($markers));
143
 
144
        return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/';
145
    }
146
}