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

dg / texy / 21345344909

26 Jan 2026 03:32AM UTC coverage: 92.382% (-0.4%) from 92.744%
21345344909

push

github

dg
HtmlElement: removed toHtml() & toText()

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

149 existing lines in 21 files now uncovered.

2401 of 2599 relevant lines covered (92.38%)

0.92 hits per line

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

97.06
/src/Texy/Modules/ListModule.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\Modules;
11

12
use Texy;
13
use Texy\BlockParser;
14
use Texy\HtmlElement;
15
use Texy\Modifier;
16
use Texy\Patterns;
17
use Texy\Regexp;
18
use function implode, ord, preg_match, strlen;
19

20

21
/**
22
 * Processes ordered, unordered, and definition lists with nesting.
23
 */
24
final class ListModule extends Texy\Module
25
{
26
        /** @var array<string, array{string, int, string, 3?: string}> [regex, ordered?, list-style-type, next-regex?] */
27
        public array $bullets = [
28
                // first-rexexp ordered? list-style-type next-regexp
29
                '*' => ['\* [ \t]', 0, ''],
30
                '-' => ['[\x{2013}-] (?! [>-] )', 0, ''],
31
                '+' => ['\+ [ \t]', 0, ''],
32
                '1.' => ['1 \. [ \t]', /* not \d !*/ 1, '', '\d{1,3} \. [ \t]'],
33
                '1)' => ['\d{1,3} \) [ \t]', 1, ''],
34
                'I.' => ['I \. [ \t]', 1, 'upper-roman', '[IVX]{1,4} \. [ \t]'],
35
                'I)' => ['[IVX]+ \) [ \t]', 1, 'upper-roman'], // before A) !
36
                'a)' => ['[a-z] \) [ \t]', 1, 'lower-alpha'],
37
                'A)' => ['[A-Z] \) [ \t]', 1, 'upper-alpha'],
38
        ];
39

40

41
        public function __construct(Texy\Texy $texy)
1✔
42
        {
43
                $this->texy = $texy;
1✔
44

45
                $texy->addHandler('beforeParse', $this->beforeParse(...));
1✔
46
                $texy->allowed['list'] = true;
1✔
47
                $texy->allowed['list/definition'] = true;
1✔
48
        }
1✔
49

50

51
        private function beforeParse(): void
52
        {
53
                $RE = $REul = [];
1✔
54
                foreach ($this->bullets as $desc) {
1✔
55
                        $RE[] = $desc[0];
1✔
56
                        if (!$desc[1]) {
1✔
57
                                $REul[] = $desc[0];
1✔
58
                        }
59
                }
60

61
                $this->texy->registerBlockPattern(
1✔
62
                        $this->parseList(...),
1✔
63
                        '~^
64
                                (?:' . Patterns::MODIFIER_H . '\n)? # modifier (1)
65
                                (' . implode('|', $RE) . ')         # list marker (2)
1✔
66
                                [ \t]*+
67
                                \S .*                               # content
68
                        $~mU',
69
                        'list',
1✔
70
                );
71

72
                $this->texy->registerBlockPattern(
1✔
73
                        $this->parseDefList(...),
1✔
74
                        '~^
75
                                (?:' . Patterns::MODIFIER_H . '\n)?   # modifier (1)
76
                                ( \S .{0,2000} )                      # definition term (2)
77
                                : [ \t]*                              # colon separator
78
                                ' . Patterns::MODIFIER_H . '?         # modifier (3)
79
                                \n
80
                                ([ \t]++)                             # indentation (4)
81
                                (' . implode('|', $REul) . ')         # description marker (5)
1✔
82
                                [ \t]*+
83
                                \S .*                                 # content
84
                        $~mU',
85
                        'list/definition',
1✔
86
                );
87
        }
1✔
88

89

90
        /**
91
         * Callback for:.
92
         *
93
         * 1) .... .(title)[class]{style}>
94
         * 2) ....
95
         *   + ...
96
         *   + ...
97
         * 3) ....
98
         * @param  string[]  $matches
99
         */
100
        public function parseList(BlockParser $parser, array $matches): ?HtmlElement
1✔
101
        {
102
                [, $mMod, $mBullet] = $matches;
1✔
103
                // [1] => .(title)[class]{style}<>
104
                // [2] => bullet * + - 1) a) A) IV)
105

106
                $el = new HtmlElement;
1✔
107

108
                $bullet = $min = null;
1✔
109
                foreach ($this->bullets as $type => $desc) {
1✔
110
                        if (Regexp::match($mBullet, '~' . $desc[0] . '~A')) {
1✔
111
                                $bullet = $desc[3] ?? $desc[0];
1✔
112
                                $min = isset($desc[3]) ? 2 : 1;
1✔
113
                                $el->setName($desc[1] ? 'ol' : 'ul');
1✔
114
                                $el->attrs['style'] = (array) ($el->attrs['style'] ?? []);
1✔
115
                                $el->attrs['style']['list-style-type'] = $desc[2];
1✔
116
                                if ($desc[1]) { // ol
1✔
117
                                        if ($type[0] === '1' && (int) $mBullet > 1) {
1✔
UNCOV
118
                                                $el->attrs['start'] = (int) $mBullet;
×
119
                                        } elseif ($type[0] === 'a' && $mBullet[0] > 'a') {
1✔
UNCOV
120
                                                $el->attrs['start'] = ord($mBullet[0]) - 96;
×
121
                                        } elseif ($type[0] === 'A' && $mBullet[0] > 'A') {
1✔
UNCOV
122
                                                $el->attrs['start'] = ord($mBullet[0]) - 64;
×
123
                                        }
124
                                }
125

126
                                break;
1✔
127
                        }
128
                }
129
                assert($bullet !== null);
130

131
                $mod = new Modifier($mMod);
1✔
132
                $mod->decorate($this->texy, $el);
1✔
133

134
                $parser->moveBackward(1);
1✔
135

136
                while ($elItem = $this->parseItem($parser, $bullet, false, 'li')) {
1✔
137
                        $el->add($elItem);
1✔
138
                }
139

140
                if ($el->count() < $min) {
1✔
141
                        return null;
1✔
142
                }
143

144
                // event listener
145
                $this->texy->invokeHandlers('afterList', [$parser, $el, $mod]);
1✔
146

147
                return $el;
1✔
148
        }
149

150

151
        /**
152
         * Callback for:.
153
         *
154
         * Term: .(title)[class]{style}>
155
         * - description 1
156
         * - description 2
157
         * - description 3
158
         * @param  string[]  $matches
159
         */
160
        public function parseDefList(BlockParser $parser, array $matches): HtmlElement
1✔
161
        {
162
                [, $mMod, , , , $mBullet] = $matches;
1✔
163
                // [1] => .(title)[class]{style}<>
164
                // [2] => ...
165
                // [3] => .(title)[class]{style}<>
166
                // [4] => space
167
                // [5] => - * +
168

169
                $texy = $this->texy;
1✔
170

171
                $bullet = null;
1✔
172
                foreach ($this->bullets as $desc) {
1✔
173
                        if (Regexp::match($mBullet, '~' . $desc[0] . '~A')) {
1✔
174
                                $bullet = $desc[3] ?? $desc[0];
1✔
175
                                break;
1✔
176
                        }
177
                }
178
                assert($bullet !== null);
179

180
                $el = new HtmlElement('dl');
1✔
181
                $mod = new Modifier($mMod);
1✔
182
                $mod->decorate($texy, $el);
1✔
183
                $parser->moveBackward(2);
1✔
184

185
                $patternTerm = '~^
1✔
186
                        \n?
187
                        ( \S .* )                       # term content
188
                        : [ \t]*                        # colon separator
189
                        ' . Patterns::MODIFIER_H . '?
190
                $~mUA';
191

192
                while (true) {
1✔
193
                        if ($elItem = $this->parseItem($parser, $bullet, true, 'dd')) {
1✔
194
                                $el->add($elItem);
1✔
195
                                continue;
1✔
196
                        }
197

198
                        if ($parser->next($patternTerm, $matches)) {
1✔
199
                                [, $mContent, $mMod] = $matches;
1✔
200
                                // [1] => ...
201
                                // [2] => .(title)[class]{style}<>
202

203
                                $elItem = new HtmlElement('dt');
1✔
204
                                $modItem = new Modifier($mMod);
1✔
205
                                $modItem->decorate($texy, $elItem);
1✔
206

207
                                $elItem->inject($texy->parseLine($mContent));
1✔
208
                                $el->add($elItem);
1✔
209
                                continue;
1✔
210
                        }
211

212
                        break;
1✔
213
                }
214

215
                // event listener
216
                $texy->invokeHandlers('afterDefinitionList', [$parser, $el, $mod]);
1✔
217

218
                return $el;
1✔
219
        }
220

221

222
        /**
223
         * Callback for single list item.
224
         */
225
        private function parseItem(BlockParser $parser, string $bullet, bool $indented, string $tag): ?HtmlElement
1✔
226
        {
227
                $spacesBase = $indented ? ('[\ \t]{1,}') : '';
1✔
228
                $patternItem = "~^
1✔
229
                        \\n?
230
                        ($spacesBase)                            # base indentation
1✔
231
                        {$bullet}                                # bullet character
1✔
232
                        [ \\t]*
233
                        ( \\S .* )?                              # content
234
                        " . Patterns::MODIFIER_H . '?
1✔
235
                $~mAU';
236

237
                // first line with bullet
238
                $matches = null;
1✔
239
                if (!$parser->next($patternItem, $matches)) {
1✔
240
                        return null;
1✔
241
                }
242

243
                [, $mIndent, $mContent, $mMod] = $matches;
1✔
244
                // [1] => indent
245
                // [2] => ...
246
                // [3] => .(title)[class]{style}<>
247

248
                $elItem = new HtmlElement($tag);
1✔
249
                $mod = new Modifier($mMod);
1✔
250
                $mod->decorate($this->texy, $elItem);
1✔
251

252
                // next lines
253
                $spaces = '';
1✔
254
                $content = ' ' . $mContent; // trick
1✔
255
                while ($parser->next('~^
1✔
256
                        (\n*)
257
                        ' . Regexp::quote($mIndent) . '
1✔
258
                        ([ \t]{1,' . $spaces . '})
1✔
259
                        (.*)
260
                $~Am', $matches)) {
261
                        [, $mBlank, $mSpaces, $mContent] = $matches;
1✔
262
                        // [1] => blank line?
263
                        // [2] => spaces
264
                        // [3] => ...
265

266
                        if ($spaces === '') {
1✔
267
                                $spaces = strlen($mSpaces);
1✔
268
                        }
269

270
                        $content .= "\n" . $mBlank . $mContent;
1✔
271
                }
272

273
                // parse content
274
                $elItem->inject($this->texy->parseBlock($content, true));
1✔
275

276
                if (isset($elItem[0]) && $elItem[0] instanceof HtmlElement) {
1✔
277
                        $elItem[0]->setName(null);
1✔
278
                }
279

280
                return $elItem;
1✔
281
        }
282
}
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