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\Util;
16
17use League\CommonMark\Block\Element\AbstractBlock;
18use League\CommonMark\Cursor;
19use League\CommonMark\Inline\Element\AbstractInline;
20use League\CommonMark\Util\RegexHelper;
21
22/**
23 * @internal
24 */
25final class AttributesHelper
26{
27    /**
28     * @param Cursor $cursor
29     *
30     * @return array<string, mixed>
31     */
32    public static function parseAttributes(Cursor $cursor): array
33    {
34        $state = $cursor->saveState();
35        $cursor->advanceToNextNonSpaceOrNewline();
36        if ($cursor->getCharacter() !== '{') {
37            $cursor->restoreState($state);
38
39            return [];
40        }
41
42        $cursor->advanceBy(1);
43        if ($cursor->getCharacter() === ':') {
44            $cursor->advanceBy(1);
45        }
46
47        $attributes = [];
48        $regex = '/^\s*([.#][_a-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')(?<!})\s*/i';
49        while ($attribute = \trim((string) $cursor->match($regex))) {
50            if ($attribute[0] === '#') {
51                $attributes['id'] = \substr($attribute, 1);
52
53                continue;
54            }
55
56            if ($attribute[0] === '.') {
57                $attributes['class'][] = \substr($attribute, 1);
58
59                continue;
60            }
61
62            [$name, $value] = \explode('=', $attribute, 2);
63            $first = $value[0];
64            $last = \substr($value, -1);
65            if ((($first === '"' && $last === '"') || ($first === "'" && $last === "'")) && \strlen($value) > 1) {
66                $value = \substr($value, 1, -1);
67            }
68
69            if (\strtolower(\trim($name)) === 'class') {
70                foreach (\array_filter(\explode(' ', \trim($value))) as $class) {
71                    $attributes['class'][] = $class;
72                }
73            } else {
74                $attributes[trim($name)] = trim($value);
75            }
76        }
77
78        if ($cursor->match('/}/') === null) {
79            $cursor->restoreState($state);
80
81            return [];
82        }
83
84        if ($attributes === []) {
85            $cursor->restoreState($state);
86
87            return [];
88        }
89
90        if (isset($attributes['class'])) {
91            $attributes['class'] = \implode(' ', (array) $attributes['class']);
92        }
93
94        return $attributes;
95    }
96
97    /**
98     * @param AbstractBlock|AbstractInline|array<string, mixed> $attributes1
99     * @param AbstractBlock|AbstractInline|array<string, mixed> $attributes2
100     *
101     * @return array<string, mixed>
102     */
103    public static function mergeAttributes($attributes1, $attributes2): array
104    {
105        $attributes = [];
106        foreach ([$attributes1, $attributes2] as $arg) {
107            if ($arg instanceof AbstractBlock || $arg instanceof AbstractInline) {
108                $arg = $arg->data['attributes'] ?? [];
109            }
110
111            /** @var array<string, mixed> $arg */
112            $arg = (array) $arg;
113            if (isset($arg['class'])) {
114                foreach (\array_filter(\explode(' ', \trim($arg['class']))) as $class) {
115                    $attributes['class'][] = $class;
116                }
117
118                unset($arg['class']);
119            }
120
121            $attributes = \array_merge($attributes, $arg);
122        }
123
124        if (isset($attributes['class'])) {
125            $attributes['class'] = \implode(' ', $attributes['class']);
126        }
127
128        return $attributes;
129    }
130}
131