Passed
Push — master ( 44266f...d1b9e8 )
by ReliQ
04:53
created

RuleSetGenerator::getRulesForElements()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 23
nc 5
nop 3
dl 0
loc 38
rs 9.2408
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ReliqArts\StyleImporter\CSS\Generator;
6
7
use Exception;
8
use ReliqArts\StyleImporter\CSS\Exception\RulesetGenerationFailed;
9
use ReliqArts\StyleImporter\CSS\Generator;
10
use ReliqArts\StyleImporter\CSS\Rule\Rule;
11
use ReliqArts\StyleImporter\CSS\Rules;
12
use ReliqArts\StyleImporter\CSS\Ruleset;
13
use ReliqArts\StyleImporter\CSS\Util\Sanitizer;
14
15
final class RuleSetGenerator implements Generator
16
{
17
    private const RULE_PATTERN_TEMPLATE = '(?<selector>[^}{,]*%s%s[\s:.,][^{,]*)[^{]*\{(?<properties>[^}]++)\}';
18
    private const DELIMITED_RULE_PATTERN_TEMPLATE = '%s' . self::RULE_PATTERN_TEMPLATE . '%s';
19
    private const PATTERN_DELIMITER = '/';
20
    private const PRE_TAG_CHARACTER_SET = '\s';
21
    private const PRE_CLASS_OR_ID_CHARACTER_SET = '[\s\w]';
22
    private const MATCH_GROUP_KEY_SELECTOR = 1;
23
    private const MATCH_GROUP_KEY_PROPERTIES = 2;
24
25
    /**
26
     * @param Context $context
27
     *
28
     * @throws RulesetGenerationFailed
29
     *
30
     * @return Ruleset
31
     */
32
    public function generate(Context $context): Ruleset
33
    {
34
        $htmlElements = $context->getHtmlElements();
35
36
        try {
37
            $rules = array_merge(
38
                $context->getImportRules(),
39
                $context->getFontFaceRules(),
40
                $this->getRulesForElements($htmlElements, $context->getSanitizedStyles())
41
            );
42
43
            foreach ($context->getMediaBlocks() as $mediaBlock) {
44
                $mediaBlockRules = $this->getRulesForElements(
45
                    $htmlElements,
46
                    $mediaBlock->getStyles(),
47
                    $mediaBlock->getMediaQuery()
48
                );
49
50
                if (!empty($mediaBlockRules)) {
51
                    array_push($rules, ...$mediaBlockRules);
52
                }
53
            }
54
55
            return new Rules($rules);
56
        } catch (Exception $exception) {
57
            throw new RulesetGenerationFailed(
58
                sprintf('Failed to generate rules. %s', $exception->getMessage()),
59
                $exception->getCode(),
60
                $exception
61
            );
62
        }
63
    }
64
65
    /**
66
     * @param string[] $htmlElements
67
     * @param string   $styles
68
     * @param string   $mediaQuery
69
     *
70
     * @return Rule[]
71
     */
72
    private function getRulesForElements(array $htmlElements, string $styles, string $mediaQuery = ''): array
73
    {
74
        $rules = [];
75
76
        foreach ($htmlElements as $element) {
77
            $sanitizedElement = Sanitizer::sanitizeElementName($element);
78
            $preElementCharacterSet = $this->getElementPreCharacterSet($element);
79
            $pattern = sprintf(
80
                self::DELIMITED_RULE_PATTERN_TEMPLATE,
81
                self::PATTERN_DELIMITER,
82
                $preElementCharacterSet,
83
                $sanitizedElement,
84
                self::PATTERN_DELIMITER
85
            );
86
87
            $matchCount = preg_match_all($pattern, $styles, $matches);
88
89
            if (empty($matchCount)) {
90
                continue;
91
            }
92
93
            $selectors = $matches[self::MATCH_GROUP_KEY_SELECTOR];
94
            $properties = $matches[self::MATCH_GROUP_KEY_PROPERTIES];
95
96
            foreach ($selectors as $index => $selector) {
97
                $rule = Rule::createNormalized(
98
                    $selector,
99
                    $properties[$index],
100
                    $mediaQuery
101
                );
102
103
                if (!in_array($rule, $rules, true)) {
104
                    $rules[] = $rule;
105
                }
106
            }
107
        }
108
109
        return array_unique($rules);
110
    }
111
112
    /**
113
     * @param string $element
114
     *
115
     * @return string
116
     */
117
    private function getElementPreCharacterSet(string $element): string
118
    {
119
        if (in_array($element[0], ['.', '#'], true)) {
120
            return self::PRE_CLASS_OR_ID_CHARACTER_SET;
121
        }
122
123
        return self::PRE_TAG_CHARACTER_SET;
124
    }
125
}
126