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
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
11
 *  - (c) John MacFarlane
12
 *
13
 * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
14
 *  - (c) Atlassian Pty Ltd
15
 *
16
 * For the full copyright and license information, please view the LICENSE
17
 * file that was distributed with this source code.
18
 */
19
 
20
namespace League\CommonMark\Delimiter;
21
 
22
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
23
use League\CommonMark\Node\Inline\AdjacentTextMerger;
24
 
25
final class DelimiterStack
26
{
27
    /** @psalm-readonly-allow-private-mutation */
28
    private ?DelimiterInterface $top = null;
29
 
30
    public function push(DelimiterInterface $newDelimiter): void
31
    {
32
        $newDelimiter->setPrevious($this->top);
33
 
34
        if ($this->top !== null) {
35
            $this->top->setNext($newDelimiter);
36
        }
37
 
38
        $this->top = $newDelimiter;
39
    }
40
 
41
    private function findEarliest(?DelimiterInterface $stackBottom = null): ?DelimiterInterface
42
    {
43
        $delimiter = $this->top;
44
        while ($delimiter !== null && $delimiter->getPrevious() !== $stackBottom) {
45
            $delimiter = $delimiter->getPrevious();
46
        }
47
 
48
        return $delimiter;
49
    }
50
 
51
    public function removeDelimiter(DelimiterInterface $delimiter): void
52
    {
53
        if ($delimiter->getPrevious() !== null) {
54
            /** @psalm-suppress PossiblyNullReference */
55
            $delimiter->getPrevious()->setNext($delimiter->getNext());
56
        }
57
 
58
        if ($delimiter->getNext() === null) {
59
            // top of stack
60
            $this->top = $delimiter->getPrevious();
61
        } else {
62
            /** @psalm-suppress PossiblyNullReference */
63
            $delimiter->getNext()->setPrevious($delimiter->getPrevious());
64
        }
65
    }
66
 
67
    private function removeDelimiterAndNode(DelimiterInterface $delimiter): void
68
    {
69
        $delimiter->getInlineNode()->detach();
70
        $this->removeDelimiter($delimiter);
71
    }
72
 
73
    private function removeDelimitersBetween(DelimiterInterface $opener, DelimiterInterface $closer): void
74
    {
75
        $delimiter = $closer->getPrevious();
76
        while ($delimiter !== null && $delimiter !== $opener) {
77
            $previous = $delimiter->getPrevious();
78
            $this->removeDelimiter($delimiter);
79
            $delimiter = $previous;
80
        }
81
    }
82
 
83
    public function removeAll(?DelimiterInterface $stackBottom = null): void
84
    {
85
        while ($this->top && $this->top !== $stackBottom) {
86
            $this->removeDelimiter($this->top);
87
        }
88
    }
89
 
90
    public function removeEarlierMatches(string $character): void
91
    {
92
        $opener = $this->top;
93
        while ($opener !== null) {
94
            if ($opener->getChar() === $character) {
95
                $opener->setActive(false);
96
            }
97
 
98
            $opener = $opener->getPrevious();
99
        }
100
    }
101
 
102
    /**
103
     * @param string|string[] $characters
104
     */
105
    public function searchByCharacter($characters): ?DelimiterInterface
106
    {
107
        if (! \is_array($characters)) {
108
            $characters = [$characters];
109
        }
110
 
111
        $opener = $this->top;
112
        while ($opener !== null) {
113
            if (\in_array($opener->getChar(), $characters, true)) {
114
                break;
115
            }
116
 
117
            $opener = $opener->getPrevious();
118
        }
119
 
120
        return $opener;
121
    }
122
 
123
    public function processDelimiters(?DelimiterInterface $stackBottom, DelimiterProcessorCollection $processors): void
124
    {
125
        $openersBottom = [];
126
 
127
        // Find first closer above stackBottom
128
        $closer = $this->findEarliest($stackBottom);
129
 
130
        // Move forward, looking for closers, and handling each
131
        while ($closer !== null) {
132
            $delimiterChar = $closer->getChar();
133
 
134
            $delimiterProcessor = $processors->getDelimiterProcessor($delimiterChar);
135
            if (! $closer->canClose() || $delimiterProcessor === null) {
136
                $closer = $closer->getNext();
137
                continue;
138
            }
139
 
140
            $openingDelimiterChar = $delimiterProcessor->getOpeningCharacter();
141
 
142
            $useDelims            = 0;
143
            $openerFound          = false;
144
            $potentialOpenerFound = false;
145
            $opener               = $closer->getPrevious();
146
            while ($opener !== null && $opener !== $stackBottom && $opener !== ($openersBottom[$delimiterChar] ?? null)) {
147
                if ($opener->canOpen() && $opener->getChar() === $openingDelimiterChar) {
148
                    $potentialOpenerFound = true;
149
                    $useDelims            = $delimiterProcessor->getDelimiterUse($opener, $closer);
150
                    if ($useDelims > 0) {
151
                        $openerFound = true;
152
                        break;
153
                    }
154
                }
155
 
156
                $opener = $opener->getPrevious();
157
            }
158
 
159
            if (! $openerFound) {
160
                if (! $potentialOpenerFound) {
161
                    // Only do this when we didn't even have a potential
162
                    // opener (one that matches the character and can open).
163
                    // If an opener was rejected because of the number of
164
                    // delimiters (e.g. because of the "multiple of 3"
165
                    // Set lower bound for future searches for openersrule),
166
                    // we want to consider it next time because the number
167
                    // of delimiters can change as we continue processing.
168
                    $openersBottom[$delimiterChar] = $closer->getPrevious();
169
                    if (! $closer->canOpen()) {
170
                        // We can remove a closer that can't be an opener,
171
                        // once we've seen there's no matching opener.
172
                        $this->removeDelimiter($closer);
173
                    }
174
                }
175
 
176
                $closer = $closer->getNext();
177
                continue;
178
            }
179
 
180
            \assert($opener !== null);
181
 
182
            $openerNode = $opener->getInlineNode();
183
            $closerNode = $closer->getInlineNode();
184
 
185
            // Remove number of used delimiters from stack and inline nodes.
186
            $opener->setLength($opener->getLength() - $useDelims);
187
            $closer->setLength($closer->getLength() - $useDelims);
188
 
189
            $openerNode->setLiteral(\substr($openerNode->getLiteral(), 0, -$useDelims));
190
            $closerNode->setLiteral(\substr($closerNode->getLiteral(), 0, -$useDelims));
191
 
192
            $this->removeDelimitersBetween($opener, $closer);
193
            // The delimiter processor can re-parent the nodes between opener and closer,
194
            // so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves.
195
            AdjacentTextMerger::mergeTextNodesBetweenExclusive($openerNode, $closerNode);
196
            $delimiterProcessor->process($openerNode, $closerNode, $useDelims);
197
 
198
            // No delimiter characters left to process, so we can remove delimiter and the now empty node.
199
            if ($opener->getLength() === 0) {
200
                $this->removeDelimiterAndNode($opener);
201
            }
202
 
203
            // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
204
            if ($closer->getLength() === 0) {
205
                $next = $closer->getNext();
206
                $this->removeDelimiterAndNode($closer);
207
                $closer = $next;
208
            }
209
        }
210
 
211
        // Remove all delimiters
212
        $this->removeAll($stackBottom);
213
    }
214
}