Completed
Push — master ( aeeeb2...74d969 )
by Colin
03:28
created

src/Block/Parser/ListParser.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9
 *  - (c) John MacFarlane
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace League\CommonMark\Block\Parser;
16
17
use League\CommonMark\Block\Element\ListBlock;
18
use League\CommonMark\Block\Element\ListData;
19
use League\CommonMark\Block\Element\ListItem;
20
use League\CommonMark\Block\Element\Paragraph;
21
use League\CommonMark\ContextInterface;
22
use League\CommonMark\Cursor;
23
use League\CommonMark\Util\RegexHelper;
24
25
class ListParser implements BlockParserInterface
26
{
27
    /**
28
     * @param ContextInterface $context
29
     * @param Cursor           $cursor
30
     *
31
     * @return bool
32
     */
33 1803
    public function parse(ContextInterface $context, Cursor $cursor): bool
34
    {
35 1803
        if ($cursor->isIndented() && !($context->getContainer() instanceof ListBlock)) {
36 168
            return false;
37
        }
38
39 1740
        $indent = $cursor->getIndent();
40 1740
        if ($indent >= 4) {
41 12
            return false;
42
        }
43
44 1740
        $tmpCursor = clone $cursor;
45 1740
        $tmpCursor->advanceToNextNonSpaceOrTab();
46 1740
        $rest = $tmpCursor->getRemainder();
47
48 1740
        if (\preg_match('/^[*+-]/', $rest) === 1) {
49 417
            $data = new ListData();
50 417
            $data->markerOffset = $indent;
51 417
            $data->type = ListBlock::TYPE_UNORDERED;
52 417
            $data->delimiter = null;
53 417
            $data->bulletChar = $rest[0];
54 417
            $markerLength = 1;
55 1548
        } elseif (($matches = RegexHelper::matchAll('/^(\d{1,9})([.)])/', $rest)) && (!($context->getContainer() instanceof Paragraph) || $matches[1] === '1')) {
56 87
            $data = new ListData();
57 87
            $data->markerOffset = $indent;
58 87
            $data->type = ListBlock::TYPE_ORDERED;
59 87
            $data->start = (int) $matches[1];
60 87
            $data->delimiter = $matches[2];
61 87
            $data->bulletChar = null;
62 87
            $markerLength = \strlen($matches[0]);
63
        } else {
64 1548
            return false;
65
        }
66
67
        // Make sure we have spaces after
68 483
        $nextChar = $tmpCursor->peek($markerLength);
69 483
        if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
70 234
            return false;
71
        }
72
73
        // If it interrupts paragraph, make sure first line isn't blank
74 252
        $container = $context->getContainer();
75 252
        if ($container instanceof Paragraph && !RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression \League\CommonMark\Util\..., $rest, $markerLength) of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
76 6
            return false;
77
        }
78
79
        // We've got a match! Advance offset and calculate padding
80 246
        $cursor->advanceToNextNonSpaceOrTab(); // to start of marker
81 246
        $cursor->advanceBy($markerLength, true); // to end of marker
82 246
        $data->padding = $this->calculateListMarkerPadding($cursor, $markerLength);
83
84
        // add the list if needed
85 246
        if (!($container instanceof ListBlock) || !$data->equals($container->getListData())) {
86 246
            $context->addBlock(new ListBlock($data));
87
        }
88
89
        // add the list item
90 246
        $context->addBlock(new ListItem($data));
91
92 246
        return true;
93
    }
94
95
    /**
96
     * @param Cursor $cursor
97
     * @param int    $markerLength
98
     *
99
     * @return int
100
     */
101 246
    private function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
102
    {
103 246
        $start = $cursor->saveState();
104 246
        $spacesStartCol = $cursor->getColumn();
105
106 246
        while ($cursor->getColumn() - $spacesStartCol < 5) {
107 246
            if (!$cursor->advanceBySpaceOrTab()) {
108 237
                break;
109
            }
110
        }
111
112 246
        $blankItem = $cursor->peek() === null;
113 246
        $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
114
115 246
        if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
116 78
            $cursor->restoreState($start);
117 78
            $cursor->advanceBySpaceOrTab();
118
119 78
            return $markerLength + 1;
120
        }
121
122 183
        return $markerLength + $spacesAfterMarker;
123
    }
124
}
125