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