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

dg / texy / 16790351274

06 Aug 2025 10:42PM UTC coverage: 92.746% (+0.005%) from 92.741%
16790351274

push

github

dg
HtmlElement: removed toHtml() & toText()

18 of 19 new or added lines in 5 files covered. (94.74%)

136 existing lines in 23 files now uncovered.

2391 of 2578 relevant lines covered (92.75%)

0.93 hits per line

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

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

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

8
declare(strict_types=1);
9

10
namespace Texy;
11

12
use function array_flip, explode, is_array, preg_match, settype, str_replace, str_starts_with, strlen, strpos, strtolower, substr, trim;
13

14

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

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

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

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

42
        /** @var array<string, int>  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
        public function __construct(?string $s = null)
1✔
53
        {
54
                $this->setProperties($s);
1✔
55
        }
1✔
56

57

58
        public function setProperties(?string $s): void
1✔
59
        {
60
                $p = 0;
1✔
61
                $len = $s ? strlen($s) : 0;
1✔
62

63
                while ($p < $len) {
1✔
64
                        $ch = $s[$p];
1✔
65

66
                        if ($ch === '(') { // title
1✔
67
                                $m = Regexp::match($s, '~(?: \\\\\) | [^)\n] )++\)~', offset: $p);
1✔
68
                                $this->title = Helpers::unescapeHtml(str_replace('\)', ')', trim(substr($m[0], 1, -1))));
1✔
69
                                $p += strlen($m[0]);
1✔
70

71
                        } elseif ($ch === '{') { // style & attributes
1✔
72
                                $a = strpos($s, '}', $p) + 1;
1✔
73
                                $this->parseStyle(substr($s, $p + 1, $a - $p - 2));
1✔
74
                                $p = $a;
1✔
75

76
                        } elseif ($ch === '[') { // classes & ID
1✔
77
                                $a = strpos($s, ']', $p) + 1;
1✔
78
                                $this->parseClasses(str_replace('#', ' #', substr($s, $p + 1, $a - $p - 2)));
1✔
79
                                $p = $a;
1✔
80

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

85
                        } elseif (substr($s, $p, 2) === '<>') {
1✔
86
                                $this->hAlign = 'center';
1✔
87
                                $p += 2;
1✔
88

89
                        } elseif ($val = ['=' => 'justify', '>' => 'right', '<' => 'left'][$ch] ?? null) {
1✔
90
                                $this->hAlign = $val;
1✔
91
                                $p++;
1✔
92
                        } else {
93
                                break;
1✔
94
                        }
95
                }
96
        }
1✔
97

98

99
        /**
100
         * Decorates HtmlElement element.
101
         */
102
        public function decorate(Texy $texy, HtmlElement $el): HtmlElement
1✔
103
        {
104
                $this->decorateAttrs($texy, $el->attrs, $el->getName());
1✔
105
                $el->validateAttrs($texy->getDTD());
1✔
106
                $this->decorateClasses($texy, $el->attrs);
1✔
107
                $this->decorateStyles($texy, $el->attrs);
1✔
108
                $this->decorateAligns($texy, $el->attrs);
1✔
109
                return $el;
1✔
110
        }
111

112

113
        private function decorateAttrs(Texy $texy, array &$attrs, string $name): void
1✔
114
        {
115
                if (!$this->attrs) {
1✔
116
                } elseif ($texy->allowedTags === $texy::ALL) {
1✔
UNCOV
117
                        $attrs = $this->attrs;
×
118

119
                } elseif (is_array($texy->allowedTags)) {
1✔
120
                        $tmp = $texy->allowedTags[$name] ?? [];
1✔
121

122
                        if ($tmp === $texy::ALL) {
1✔
123
                                $attrs = $this->attrs;
1✔
124

125
                        } elseif (is_array($tmp)) {
×
126
                                $attrs = array_flip($tmp);
×
127
                                foreach ($this->attrs as $key => $value) {
×
UNCOV
128
                                        if (isset($attrs[$key])) {
×
UNCOV
129
                                                $attrs[$key] = $value;
×
130
                                        }
131
                                }
132
                        }
133
                }
134

135
                if ($this->title !== null) {
1✔
136
                        $attrs['title'] = $texy->typographyModule->postLine($this->title);
1✔
137
                }
138
        }
1✔
139

140

141
        private function decorateClasses(Texy $texy, array &$attrs): void
1✔
142
        {
143
                if ($this->classes || $this->id !== null) {
1✔
144
                        [$allowedClasses] = $texy->getAllowedProps();
1✔
145
                        settype($attrs['class'], 'array');
1✔
146
                        if ($allowedClasses === $texy::ALL) {
1✔
147
                                foreach ($this->classes as $value => $foo) {
1✔
148
                                        $attrs['class'][] = $value;
1✔
149
                                }
150

151
                                $attrs['id'] = $this->id;
1✔
152
                        } elseif (is_array($allowedClasses)) {
1✔
153
                                foreach ($this->classes as $value => $foo) {
1✔
154
                                        if (isset($allowedClasses[$value])) {
1✔
155
                                                $attrs['class'][] = $value;
1✔
156
                                        }
157
                                }
158

159
                                if (isset($allowedClasses['#' . $this->id])) {
1✔
160
                                        $attrs['id'] = $this->id;
1✔
161
                                }
162
                        }
163
                }
164
        }
1✔
165

166

167
        private function decorateStyles(Texy $texy, array &$attrs): void
1✔
168
        {
169
                if ($this->styles) {
1✔
170
                        [, $allowedStyles] = $texy->getAllowedProps();
1✔
171
                        settype($attrs['style'], 'array');
1✔
172
                        if ($allowedStyles === $texy::ALL) {
1✔
173
                                foreach ($this->styles as $prop => $value) {
1✔
174
                                        $attrs['style'][$prop] = $value;
1✔
175
                                }
176
                        } elseif (is_array($allowedStyles)) {
1✔
177
                                foreach ($this->styles as $prop => $value) {
1✔
178
                                        if (isset($allowedStyles[$prop])) {
1✔
179
                                                $attrs['style'][$prop] = $value;
1✔
180
                                        }
181
                                }
182
                        }
183
                }
184
        }
1✔
185

186

187
        private function decorateAligns(Texy $texy, array &$attrs): void
1✔
188
        {
189
                if ($this->hAlign) {
1✔
190
                        $class = $texy->alignClasses[$this->hAlign] ?? null;
1✔
191
                        if ($class) {
1✔
UNCOV
192
                                settype($attrs['class'], 'array');
×
UNCOV
193
                                $attrs['class'][] = $class;
×
194
                        } else {
195
                                settype($attrs['style'], 'array');
1✔
196
                                $attrs['style']['text-align'] = $this->hAlign;
1✔
197
                        }
198
                }
199

200
                if ($this->vAlign) {
1✔
201
                        $class = $texy->alignClasses[$this->vAlign] ?? null;
×
202
                        if ($class) {
×
UNCOV
203
                                settype($attrs['class'], 'array');
×
204
                                $attrs['class'][] = $class;
×
205
                        } else {
UNCOV
206
                                settype($attrs['style'], 'array');
×
UNCOV
207
                                $attrs['style']['vertical-align'] = $this->vAlign;
×
208
                        }
209
                }
210
        }
1✔
211

212

213
        private function parseStyle(string $s): void
1✔
214
        {
215
                foreach (explode(';', $s) as $value) {
1✔
216
                        $pair = explode(':', $value, 2);
1✔
217
                        $prop = strtolower(trim($pair[0]));
1✔
218
                        if ($prop === '' || !isset($pair[1])) {
1✔
219
                                continue;
1✔
220
                        }
221

222
                        $value = trim($pair[1]);
1✔
223

224
                        if (
225
                                isset(self::$elAttrs[$prop])
1✔
226
                                || str_starts_with($prop, 'data-')
1✔
227
                                || str_starts_with($prop, 'aria-')
1✔
228
                        ) { // attribute
229
                                $this->attrs[$prop] = $value;
1✔
230
                        } elseif ($value !== '') { // style
1✔
231
                                $this->styles[$prop] = $value;
1✔
232
                        }
233
                }
234
        }
1✔
235

236

237
        private function parseClasses(string $s): void
1✔
238
        {
239
                foreach (explode(' ', $s) as $value) {
1✔
240
                        if ($value === '') {
1✔
241
                                continue;
1✔
242
                        } elseif ($value[0] === '#') {
1✔
243
                                $this->id = substr($value, 1);
1✔
244
                        } else {
245
                                $this->classes[$value] = true;
1✔
246
                        }
247
                }
248
        }
1✔
249
}
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