IconifyRuntime::configureOptions()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
dl 0
loc 26
ccs 21
cts 21
cp 1
rs 9.7
c 1
b 0
f 0
cc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of ocubom/twig-svg-extension
5
 *
6
 * © Oscar Cubo Medina <https://ocubom.github.io>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ocubom\Twig\Extension\Svg\Provider\Iconify;
13
14
use Ocubom\Twig\Extension\Svg\Exception\LoaderException;
15
use Ocubom\Twig\Extension\Svg\Loader\LoaderInterface;
16
use Ocubom\Twig\Extension\Svg\Util\DomUtil;
17
use Ocubom\Twig\Extension\Svg\Util\Html5Util;
18
use Psr\Log\LoggerInterface;
19
use Psr\Log\NullLogger;
20
use Symfony\Component\OptionsResolver\Options;
21
use Symfony\Component\OptionsResolver\OptionsResolver;
22
use Twig\Environment;
23
use Twig\Extension\RuntimeExtensionInterface;
24
25
use function BenTools\IterableFunctions\iterable_merge;
26
use function BenTools\IterableFunctions\iterable_to_array;
27
use function Ocubom\Twig\Extension\is_string;
28
29
class IconifyRuntime implements RuntimeExtensionInterface
30
{
31
    private LoaderInterface $loader;
32
    private LoggerInterface $logger;
33
    private array $options;
34
35 6
    public function __construct(LoaderInterface $loader, iterable $options = null, LoggerInterface $logger = null)
36
    {
37 6
        $this->loader = $loader;
38 6
        $this->logger = $logger ?? new NullLogger();
39
40 6
        $this->options = static::configureOptions()
41 6
            ->resolve(iterable_to_array($options ?? /* @scrutinizer ignore-type */ []));
42
    }
43
44 6
    public function replaceIcons(
45
        Environment $twig,
46
        string $html,
47
        array $options = []
48
    ): string {
49
        // Load HTML
50 6
        $doc = Html5Util::loadHtml($html);
51
52
        /** @var \DOMNode $node */
53 6
        foreach (iterable_to_array($this->queryIconify($doc, $options)) as $node) {
54 6
            if ($node instanceof \DOMElement) {
55
                try {
56 6
                    $ident = $node->hasAttribute('data-icon')
57 5
                        ? $node->getAttribute('data-icon')
58 2
                        : $node->getAttribute('icon');
59
60 6
                    if ($twig->isDebug()) {
61 5
                        DomUtil::createComment(DomUtil::toHtml($node), $node, true);
62
                    }
63
64 6
                    $icon = $this->loader->resolve(
65 6
                        $ident, // Resolve icon
66 6
                        iterable_to_array(iterable_merge(
67 6
                            DomUtil::getElementAttributes($node), // … and clone all its attributes as options
68 6
                            $options
69 6
                        ))
70 6
                    );
71
72
                    // Replace node
73 3
                    DomUtil::replaceNode($node, $icon->getElement());
74 3
                } catch (LoaderException $err) {
75 2
                    $this->logger->notice($err->getMessage(), ['exception' => $err]);
76
77 2
                    DomUtil::removeNode($node);
78
                }
79
            }
80
        }
81
82
        // Generate normalized HTML
83 5
        return Html5Util::toHtml($doc);
84
    }
85
86
    /**
87
     * @return iterable<\DOMElement>
88
     */
89 6
    private function queryIconify(\DOMDocument $doc, iterable $options = null)
90
    {
91 6
        $options = static::configureOptions()->resolve(iterable_to_array(iterable_merge(
92 6
            $this->options,
93 6
            $options ?? /* @scrutinizer ignore-type */ []
94 6
        )));
95
96
        // SVG Framework
97
        // <span class="iconify" data-icon="mdi:home"></span>
98
        // <span class="iconify-inline" data-icon="mdi:home"></span>
99 6
        if ($options['svg_framework']) {
100 5
            $query = implode(' | ', array_map(
101 5
                function (string $class): string {
102 5
                    return sprintf(
103 5
                        'descendant-or-self::*[@class and contains(concat(\' \', normalize-space(@class), \' \'), \' %s \')]',
104 5
                        $class
105 5
                    );
106 5
                },
107 5
                $options['svg_framework']
108 5
            ));
109
110 5
            foreach (DomUtil::query($query, $doc) as $node) {
111 5
                if ($node instanceof \DOMElement && $node->hasAttribute('data-icon')) {
112 5
                    yield $node;
113
                }
114
            }
115
        }
116
117
        // Web Component
118
        // <icon icon="mdi:home" />
119
        // <iconify-icon icon="mdi:home"></iconify-icon>
120 6
        if ($options['web_component']) {
121 5
            foreach ($options['web_component'] as $tag) {
122 5
                foreach ($doc->getElementsByTagName($tag) as $node) {
123 2
                    if ($node->hasAttribute('icon')) {
124 2
                        yield $node;
125
                    }
126
                }
127
            }
128
        }
129
    }
130
131
    /** @psalm-suppress MissingClosureParamType */
132 6
    protected static function configureOptions(OptionsResolver $resolver = null): OptionsResolver
133
    {
134 6
        $resolver = $resolver ?? new OptionsResolver();
135
136 6
        $normalizeStringArray = function (Options $options, $value): array {
137 6
            return array_filter(
138 6
                is_string($value) ? preg_split('@\s+@Uis', $value) : ($value ?? []),
139 6
                function (string $item): bool {
140 6
                    return !empty($item);
141 6
                }
142 6
            );
143 6
        };
144
145 6
        $resolver->define('svg_framework')
146 6
            ->default(['iconify', 'iconify-inline'])
147 6
            ->allowedTypes('null', 'string', 'string[]')
148 6
            ->normalize($normalizeStringArray)
149 6
            ->info('SVG Framework classes');
150
151 6
        $resolver->define('web_component')
152 6
            ->default(['icon', 'iconify-icon'])
153 6
            ->allowedTypes('null', 'string', 'string[]')
154 6
            ->normalize($normalizeStringArray)
155 6
            ->info('Web Component tags');
156
157 6
        return $resolver;
158
    }
159
}
160