1<?php
2
3declare(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
20namespace League\CommonMark\Delimiter\Processor;
21
22use League\CommonMark\Exception\InvalidArgumentException;
23
24final class DelimiterProcessorCollection implements DelimiterProcessorCollectionInterface
25{
26    /**
27     * @var array<string,DelimiterProcessorInterface>|DelimiterProcessorInterface[]
28     *
29     * @psalm-readonly-allow-private-mutation
30     */
31    private array $processorsByChar = [];
32
33    public function add(DelimiterProcessorInterface $processor): void
34    {
35        $opening = $processor->getOpeningCharacter();
36        $closing = $processor->getClosingCharacter();
37
38        if ($opening === $closing) {
39            $old = $this->processorsByChar[$opening] ?? null;
40            if ($old !== null && $old->getOpeningCharacter() === $old->getClosingCharacter()) {
41                $this->addStaggeredDelimiterProcessorForChar($opening, $old, $processor);
42            } else {
43                $this->addDelimiterProcessorForChar($opening, $processor);
44            }
45        } else {
46            $this->addDelimiterProcessorForChar($opening, $processor);
47            $this->addDelimiterProcessorForChar($closing, $processor);
48        }
49    }
50
51    public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface
52    {
53        return $this->processorsByChar[$char] ?? null;
54    }
55
56    /**
57     * @return string[]
58     */
59    public function getDelimiterCharacters(): array
60    {
61        return \array_keys($this->processorsByChar);
62    }
63
64    private function addDelimiterProcessorForChar(string $delimiterChar, DelimiterProcessorInterface $processor): void
65    {
66        if (isset($this->processorsByChar[$delimiterChar])) {
67            throw new InvalidArgumentException(\sprintf('Delim processor for character "%s" already exists', $processor->getOpeningCharacter()));
68        }
69
70        $this->processorsByChar[$delimiterChar] = $processor;
71    }
72
73    private function addStaggeredDelimiterProcessorForChar(string $opening, DelimiterProcessorInterface $old, DelimiterProcessorInterface $new): void
74    {
75        if ($old instanceof StaggeredDelimiterProcessor) {
76            $s = $old;
77        } else {
78            $s = new StaggeredDelimiterProcessor($opening, $old);
79        }
80
81        $s->add($new);
82        $this->processorsByChar[$opening] = $s;
83    }
84
85    public function count(): int
86    {
87        return \count($this->processorsByChar);
88    }
89}
90