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

dg / texy / 21501721037

30 Jan 2026 02:00AM UTC coverage: 91.159% (-1.3%) from 92.426%
21501721037

push

github

dg
wip

2681 of 2941 relevant lines covered (91.16%)

0.91 hits per line

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

94.92
/src/Texy/Modifier.php
1
<?php
2

3
/**
4
 * This file is part of the Texy! (https://texy.nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Texy;
11

12

13

14
/**
15
 * Modifier processor.
16
 *
17
 * Modifiers are texts like .(title)[class1 class2 #id]{color: red}>^
18
 *   .         starts with dot
19
 *   (...)     title or alt modifier
20
 *   [...]     classes or ID modifier
21
 *   {...}     inner style modifier
22
 *   < > <> =  horizontal align modifier
23
 *   ^ - _     vertical align modifier
24
 */
25
final class Modifier
26
{
27
        public ?string $id = null;
28

29
        /** @var array<string, true> of classes (as keys) */
30
        public array $classes = [];
31

32
        /** @var array<string, string> of CSS styles */
33
        public array $styles = [];
34

35
        /** @var array<string, string|string[]> of HTML element attributes */
36
        public array $attrs = [];
37
        public ?string $hAlign = null;
38
        public ?string $vAlign = null;
39
        public ?string $title = null;
40
        public ?Position $position = null;
41

42
        /** @var array<string, 1>  list of properties which are regarded as HTML element attributes */
43
        public static array $elAttrs = [
44
                'abbr' => 1, 'accesskey' => 1, 'alt' => 1, 'cite' => 1, 'colspan' => 1, 'contenteditable' => 1, 'crossorigin' => 1,
45
                'datetime' => 1, 'decoding' => 1, 'download' => 1, 'draggable' => 1, 'for' => 1, 'headers' => 1, 'hidden' => 1,
46
                'href' => 1, 'hreflang' => 1, 'id' => 1, 'itemid' => 1, 'itemprop' => 1, 'itemref' => 1, 'itemscope' => 1, 'itemtype' => 1,
47
                'lang' => 1, 'name' => 1, 'ping' => 1, 'referrerpolicy' => 1, 'rel' => 1, 'reversed' => 1, 'rowspan' => 1, 'scope' => 1,
48
                'slot' => 1, 'src' => 1, 'srcset' => 1, 'start' => 1, 'target' => 1, 'title' => 1, 'translate' => 1, 'type' => 1, 'value' => 1,
49
        ];
50

51

52
        /**
53
         * Parses modifier string and returns new instance.
54
         */
55
        public static function parse(?string $s, ?int $offset = null): ?self
1✔
56
        {
57
                if ($s === null) {
1✔
58
                        return null;
1✔
59
                }
60
                $modifier = new self;
1✔
61
                if ($offset !== null) {
1✔
62
                        $modifier->position = new Position($offset, strlen($s));
×
63
                }
64
                $modifier->setProperties($s);
1✔
65
                return $modifier;
1✔
66
        }
67

68

69
        /** @deprecated  use Modifier::parse() */
70
        public function setProperties(?string $s): void
1✔
71
        {
72
                $p = 0;
1✔
73
                $len = $s ? strlen($s) : 0;
1✔
74

75
                while ($p < $len) {
1✔
76
                        $ch = $s[$p];
1✔
77

78
                        if ($ch === '(') { // title
1✔
79
                                $m = Regexp::match($s, '~(?: \\\\\) | [^)\n] )++\)~', offset: $p);
1✔
80
                                if (isset($m[0])) {
1✔
81
                                        $this->title = Helpers::unescapeHtml(str_replace('\)', ')', trim(substr($m[0], 1, -1))));
1✔
82
                                        $p += strlen($m[0]);
1✔
83
                                }
84

85
                        } elseif ($ch === '{') { // style & attributes
1✔
86
                                $a = strpos($s, '}', $p) + 1;
1✔
87
                                $this->parseStyle(substr($s, $p + 1, $a - $p - 2));
1✔
88
                                $p = $a;
1✔
89

90
                        } elseif ($ch === '[') { // classes & ID
1✔
91
                                $a = strpos($s, ']', $p) + 1;
1✔
92
                                $this->parseClasses(str_replace('#', ' #', substr($s, $p + 1, $a - $p - 2)));
1✔
93
                                $p = $a;
1✔
94

95
                        } elseif ($val = ['^' => 'top', '-' => 'middle', '_' => 'bottom'][$ch] ?? null) { // alignment
1✔
96
                                $this->vAlign = $val;
×
97
                                $p++;
×
98

99
                        } elseif (substr($s, $p, 2) === '<>') {
1✔
100
                                $this->hAlign = 'center';
1✔
101
                                $p += 2;
1✔
102

103
                        } elseif ($val = ['=' => 'justify', '>' => 'right', '<' => 'left'][$ch] ?? null) {
1✔
104
                                $this->hAlign = $val;
1✔
105
                                $p++;
1✔
106
                        } else {
107
                                break;
1✔
108
                        }
109
                }
110
        }
1✔
111

112

113
        private function parseStyle(string $s): void
1✔
114
        {
115
                foreach (explode(';', $s) as $value) {
1✔
116
                        $pair = explode(':', $value, 2);
1✔
117
                        $prop = strtolower(trim($pair[0]));
1✔
118
                        if ($prop === '' || !isset($pair[1])) {
1✔
119
                                continue;
1✔
120
                        }
121

122
                        $value = trim($pair[1]);
1✔
123

124
                        if (
125
                                isset(self::$elAttrs[$prop])
1✔
126
                                || str_starts_with($prop, 'data-')
1✔
127
                                || str_starts_with($prop, 'aria-')
1✔
128
                        ) { // attribute
129
                                $this->attrs[$prop] = $value;
1✔
130
                        } elseif ($value !== '') { // style
1✔
131
                                $this->styles[$prop] = $value;
1✔
132
                        }
133
                }
134
        }
1✔
135

136

137
        private function parseClasses(string $s): void
1✔
138
        {
139
                foreach (explode(' ', $s) as $value) {
1✔
140
                        if ($value === '') {
1✔
141
                                continue;
1✔
142
                        } elseif ($value[0] === '#') {
1✔
143
                                $this->id = substr($value, 1);
1✔
144
                        } else {
145
                                $this->classes[$value] = true;
1✔
146
                        }
147
                }
148
        }
1✔
149
}
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

© 2026 Coveralls, Inc