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

dg / texy / 22262589061

21 Feb 2026 07:04PM UTC coverage: 92.991% (+1.8%) from 91.178%
22262589061

push

github

dg
LinkModule: deprecated label and modifiers in link definitions

3 of 3 new or added lines in 1 file covered. (100.0%)

126 existing lines in 22 files now uncovered.

2083 of 2240 relevant lines covered (92.99%)

0.93 hits per line

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

86.78
/src/Texy/Modifier.php
1
<?php declare(strict_types=1);
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
namespace Texy;
9

10
use function array_flip, explode, is_array, settype, str_replace, str_starts_with, strlen, strpos, strtolower, substr, trim;
11

12

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

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

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

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

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

49

50
        /**
51
         * Parses modifier string and returns new instance.
52
         */
53
        public static function parse(?string $s): self
1✔
54
        {
55
                $modifier = new self;
1✔
56
                if ($s !== null) {
1✔
57
                        $modifier->setProperties($s);
1✔
58
                }
59
                return $modifier;
1✔
60
        }
61

62

63
        /** @deprecated  use Modifier::parse() */
64
        public function setProperties(?string $s): void
1✔
65
        {
66
                $p = 0;
1✔
67
                $len = $s ? strlen($s) : 0;
1✔
68

69
                while ($p < $len) {
1✔
70
                        $ch = $s[$p];
1✔
71

72
                        if ($ch === '(') { // title
1✔
73
                                $m = Regexp::match($s, '~(?: \\\\\) | [^)\n] )++\)~', offset: $p);
1✔
74
                                if (isset($m[0])) {
1✔
75
                                        $this->title = Helpers::unescapeHtml(str_replace('\)', ')', trim(substr($m[0], 1, -1))));
1✔
76
                                        $p += strlen($m[0]);
1✔
77
                                }
78

79
                        } elseif ($ch === '{') { // style & attributes
1✔
80
                                $a = strpos($s, '}', $p) + 1;
1✔
81
                                $this->parseStyle(substr($s, $p + 1, $a - $p - 2));
1✔
82
                                $p = $a;
1✔
83

84
                        } elseif ($ch === '[') { // classes & ID
1✔
85
                                $a = strpos($s, ']', $p) + 1;
1✔
86
                                $this->parseClasses(str_replace('#', ' #', substr($s, $p + 1, $a - $p - 2)));
1✔
87
                                $p = $a;
1✔
88

89
                        } elseif ($val = ['^' => 'top', '-' => 'middle', '_' => 'bottom'][$ch] ?? null) { // alignment
1✔
UNCOV
90
                                $this->vAlign = $val;
×
UNCOV
91
                                $p++;
×
92

93
                        } elseif (substr($s, $p, 2) === '<>') {
1✔
94
                                $this->hAlign = 'center';
1✔
95
                                $p += 2;
1✔
96

97
                        } elseif ($val = ['=' => 'justify', '>' => 'right', '<' => 'left'][$ch] ?? null) {
1✔
98
                                $this->hAlign = $val;
1✔
99
                                $p++;
1✔
100
                        } else {
101
                                break;
1✔
102
                        }
103
                }
104
        }
1✔
105

106

107
        /**
108
         * Decorates HtmlElement element.
109
         */
110
        public function decorate(Texy $texy, HtmlElement $el): HtmlElement
1✔
111
        {
112
                $this->decorateAttrs($texy, $el->attrs, $el->getName() ?? '');
1✔
113
                $this->decorateClasses($texy, $el->attrs);
1✔
114
                $this->decorateStyles($texy, $el->attrs);
1✔
115
                $this->decorateAligns($texy, $el->attrs);
1✔
116
                return $el;
1✔
117
        }
118

119

120
        /** @param  array<string, mixed>  $attrs */
121
        private function decorateAttrs(Texy $texy, array &$attrs, string $name): void
1✔
122
        {
123
                if (!$this->attrs) {
1✔
124
                } elseif ($texy->allowedTags === $texy::ALL) {
1✔
UNCOV
125
                        $attrs = $this->attrs;
×
126

127
                } elseif (is_array($texy->allowedTags)) {
1✔
128
                        $tmp = $texy->allowedTags[$name] ?? [];
1✔
129

130
                        if ($tmp === $texy::ALL) {
1✔
131
                                $attrs = $this->attrs;
1✔
132

UNCOV
133
                        } elseif (is_array($tmp)) {
×
UNCOV
134
                                $attrs = array_flip($tmp);
×
UNCOV
135
                                foreach ($this->attrs as $key => $value) {
×
UNCOV
136
                                        if (isset($attrs[$key])) {
×
UNCOV
137
                                                $attrs[$key] = $value;
×
138
                                        }
139
                                }
140
                        }
141
                }
142

143
                if ($this->title !== null) {
1✔
144
                        $attrs['title'] = $texy->typographyModule->postLine($this->title);
1✔
145
                }
146
        }
1✔
147

148

149
        /** @param  array<string, mixed>  $attrs */
150
        private function decorateClasses(Texy $texy, array &$attrs): void
1✔
151
        {
152
                if ($this->classes || $this->id !== null) {
1✔
153
                        [$allowedClasses] = $texy->getAllowedProps();
1✔
154
                        settype($attrs['class'], 'array');
1✔
155
                        if ($allowedClasses === $texy::ALL) {
1✔
156
                                foreach ($this->classes as $value => $foo) {
1✔
157
                                        $attrs['class'][] = $value;
1✔
158
                                }
159

160
                                $attrs['id'] = $this->id;
1✔
161
                        } elseif (is_array($allowedClasses)) {
1✔
162
                                foreach ($this->classes as $value => $foo) {
1✔
163
                                        if (isset($allowedClasses[$value])) {
1✔
164
                                                $attrs['class'][] = $value;
1✔
165
                                        }
166
                                }
167

168
                                if (isset($allowedClasses['#' . $this->id])) {
1✔
169
                                        $attrs['id'] = $this->id;
1✔
170
                                }
171
                        }
172
                }
173
        }
1✔
174

175

176
        /** @param  array<string, mixed>  $attrs */
177
        private function decorateStyles(Texy $texy, array &$attrs): void
1✔
178
        {
179
                if ($this->styles) {
1✔
180
                        [, $allowedStyles] = $texy->getAllowedProps();
1✔
181
                        settype($attrs['style'], 'array');
1✔
182
                        if ($allowedStyles === $texy::ALL) {
1✔
183
                                foreach ($this->styles as $prop => $value) {
1✔
184
                                        $attrs['style'][$prop] = $value;
1✔
185
                                }
186
                        } elseif (is_array($allowedStyles)) {
1✔
187
                                foreach ($this->styles as $prop => $value) {
1✔
188
                                        if (isset($allowedStyles[$prop])) {
1✔
189
                                                $attrs['style'][$prop] = $value;
1✔
190
                                        }
191
                                }
192
                        }
193
                }
194
        }
1✔
195

196

197
        /** @param  array<string, mixed>  $attrs */
198
        private function decorateAligns(Texy $texy, array &$attrs): void
1✔
199
        {
200
                if ($this->hAlign) {
1✔
201
                        $class = $texy->alignClasses[$this->hAlign] ?? null;
1✔
202
                        if ($class) {
1✔
UNCOV
203
                                settype($attrs['class'], 'array');
×
UNCOV
204
                                $attrs['class'][] = $class;
×
205
                        } else {
206
                                settype($attrs['style'], 'array');
1✔
207
                                $attrs['style']['text-align'] = $this->hAlign;
1✔
208
                        }
209
                }
210

211
                if ($this->vAlign) {
1✔
UNCOV
212
                        $class = $texy->alignClasses[$this->vAlign] ?? null;
×
UNCOV
213
                        if ($class) {
×
UNCOV
214
                                settype($attrs['class'], 'array');
×
UNCOV
215
                                $attrs['class'][] = $class;
×
216
                        } else {
UNCOV
217
                                settype($attrs['style'], 'array');
×
UNCOV
218
                                $attrs['style']['vertical-align'] = $this->vAlign;
×
219
                        }
220
                }
221
        }
1✔
222

223

224
        private function parseStyle(string $s): void
1✔
225
        {
226
                foreach (explode(';', $s) as $value) {
1✔
227
                        $pair = explode(':', $value, 2);
1✔
228
                        $prop = strtolower(trim($pair[0]));
1✔
229
                        if ($prop === '' || !isset($pair[1])) {
1✔
230
                                continue;
1✔
231
                        }
232

233
                        $value = trim($pair[1]);
1✔
234

235
                        if (
236
                                isset(self::$elAttrs[$prop])
1✔
237
                                || str_starts_with($prop, 'data-')
1✔
238
                                || str_starts_with($prop, 'aria-')
1✔
239
                        ) { // attribute
240
                                $this->attrs[$prop] = $value;
1✔
241
                        } elseif ($value !== '') { // style
1✔
242
                                $this->styles[$prop] = $value;
1✔
243
                        }
244
                }
245
        }
1✔
246

247

248
        private function parseClasses(string $s): void
1✔
249
        {
250
                foreach (explode(' ', $s) as $value) {
1✔
251
                        if ($value === '') {
1✔
252
                                continue;
1✔
253
                        } elseif ($value[0] === '#') {
1✔
254
                                $this->id = substr($value, 1);
1✔
255
                        } else {
256
                                $this->classes[$value] = true;
1✔
257
                        }
258
                }
259
        }
1✔
260
}
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