• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MyIntervals / emogrifier / 19388261011

15 Nov 2025 10:09AM UTC coverage: 96.518% (+0.02%) from 96.503%
19388261011

Pull #1498

github

web-flow
Merge fcf3ec0b1 into 0280c572e
Pull Request #1498: [CLEANUP] Tighten type annotations in `AbstractHtmlProcessorTest`

887 of 919 relevant lines covered (96.52%)

246.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/src/Utilities/DeclarationBlockParser.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Pelago\Emogrifier\Utilities;
6

7
use function Safe\preg_match;
8
use function Safe\preg_split;
9

10
/**
11
 * Provides a common method for parsing CSS declaration blocks.
12
 * These might be from actual CSS, or from the `style` attribute of an HTML DOM element.
13
 *
14
 * Caches results globally.
15
 *
16
 * @internal
17
 */
18
final class DeclarationBlockParser
19
{
20
    /**
21
     * @var array<non-empty-string, array<non-empty-string, string>>
22
     */
23
    private static $cache = [];
24

25
    /**
26
     * CSS custom properties (variables) have case-sensitive names, so their case must be preserved.
27
     * Standard CSS properties have case-insensitive names, which are converted to lowercase.
28
     *
29
     * @param non-empty-string $name
30
     *
31
     * @return non-empty-string
32
     */
33
    public function normalizePropertyName(string $name): string
35✔
34
    {
35
        if (\substr($name, 0, 2) === '--') {
35✔
36
            return $name;
8✔
37
        }
38

39
        return \strtolower($name);
27✔
40
    }
41

42
    /**
43
     * Parses a CSS declaration block into property name/value pairs.
44
     *
45
     * Example:
46
     *
47
     * The declaration block
48
     *
49
     * ```css
50
     *   color: #000; font-weight: bold;
51
     * ```
52
     *
53
     * will be parsed into the following array:
54
     *
55
     * ```php
56
     *   [
57
     *     'color' => '#000',
58
     *     'font-weight' => 'bold',
59
     *   ]
60
     * ```
61
     *
62
     * @param string $declarationBlock the CSS declarations block (without the curly braces)
63
     *
64
     * @return array<non-empty-string, string>
65
     *         the CSS declarations with the property names as array keys and the property values as array values
66
     *
67
     * @throws \UnexpectedValueException if an empty property name is encountered (which cannot happen)
68
     */
69
    public function parse(string $declarationBlock): array
46✔
70
    {
71
        $trimmedDeclarationBlock = \trim($declarationBlock, "; \n\r\t\v\x00");
46✔
72
        if ($trimmedDeclarationBlock === '') {
46✔
73
            return [];
4✔
74
        }
75

76
        if (isset(self::$cache[$trimmedDeclarationBlock])) {
42✔
77
            return self::$cache[$trimmedDeclarationBlock];
9✔
78
        }
79

80
        $declarations = preg_split('/;(?!base64|charset)/', $trimmedDeclarationBlock);
33✔
81
        /** @var list<string> $declarations */
82
        $properties = [];
33✔
83
        foreach ($declarations as $declaration) {
33✔
84
            $matches = [];
33✔
85
            if (preg_match(
33✔
86
                '/^(-?+[a-zA-Z_][a-zA-Z_0-9\\-]*+|--[a-zA-Z_0-9\\-]++)\\s*+:\\s*+(.++)$/s',
33✔
87
                \trim($declaration),
33✔
88
                $matches
33✔
89
            ) === 0) {
33✔
90
                continue;
7✔
91
            }
92

93
            \assert(\is_array($matches));
27✔
94
            $propertyName = $matches[1];
27✔
95
            if ($propertyName === '') {
27✔
96
                // This cannot happen since the regular expression matches one or more characters.
97
                throw new \UnexpectedValueException('An empty property name was encountered.', 1727046409);
×
98
            }
99
            $propertyValue = $matches[2];
27✔
100
            $properties[$this->normalizePropertyName($propertyName)] = $propertyValue;
27✔
101
        }
102
        self::$cache[$trimmedDeclarationBlock] = $properties;
33✔
103

104
        return $properties;
33✔
105
    }
106
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc