• 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

98.58
/src/Texy/Modules/PhraseModule.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\LineParser;
14
use Texy\Modifier;
15
use Texy\Patterns;
16
use function htmlspecialchars, str_replace, trim;
17
use const ENT_NOQUOTES;
18

19

20
/**
21
 * Processes inline text formatting (bold, italic, code, etc.).
22
 */
23
final class PhraseModule extends Texy\Module
24
{
25
        /** @var array<string, string> */
26
        public array $tags = [
27
                'phrase/strong' => 'strong', // or 'b'
28
                'phrase/em' => 'em', // or 'i'
29
                'phrase/em-alt' => 'em',
30
                'phrase/em-alt2' => 'em',
31
                'phrase/ins' => 'ins',
32
                'phrase/del' => 'del',
33
                'phrase/sup' => 'sup',
34
                'phrase/sup-alt' => 'sup',
35
                'phrase/sub' => 'sub',
36
                'phrase/sub-alt' => 'sub',
37
                'phrase/span' => 'a',
38
                'phrase/span-alt' => 'a',
39
                'phrase/acronym' => 'abbr',
40
                'phrase/acronym-alt' => 'abbr',
41
                'phrase/code' => 'code',
42
                'phrase/quote' => 'q',
43
                'phrase/quicklink' => 'a',
44
        ];
45

46
        public bool $linksAllowed = true;
47

48

49
        public function __construct(Texy\Texy $texy)
1✔
50
        {
51
                $this->texy = $texy;
1✔
52

53
                $texy->addHandler('phrase', $this->toElement(...));
1✔
54

55
                /*
56
                // UNIVERSAL
57
                $texy->registerLinePattern(
58
                        array($this, 'patternPhrase'),
59
                        '~((?>([*+/^_"\~`-])+?))(?!\s)(.*(?!\2).)'.Texy\Patterns::MODIFIER.'?(?<!\s)\1(?!\2)(?::('.Texy\Patterns::LINK_URL.'))??~Us',
60
                        'phrase/strong'
61
                );
62
                */
63

64
                // ***strong+emphasis***
65
                $texy->registerLinePattern(
1✔
66
                        $this->parsePhrase(...),
1✔
67
                        '~
68
                                (?<! [*\\\] )                     # not preceded by * or \
69
                                \*\*\*
70
                                (?! [\s*] )                       # not followed by space or *
71
                                ( (?: [^ *]++ | [ *] )+ )         # content (1)
72
                                ' . Patterns::MODIFIER . '?       # modifier (2)
73
                                (?<! [\s*\\\] )                   # not preceded by space, * or \
74
                                \*\*\*
75
                                (?! \* )                          # not followed by *
76
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
77
                        ~Us',
78
                        'phrase/strong+em',
1✔
79
                );
80

81
                // **strong**
82
                $texy->registerLinePattern(
1✔
83
                        $this->parsePhrase(...),
1✔
84
                        '~
85
                                (?<! [*\\\] )                     # not preceded by * or \
86
                                \*\*
87
                                (?! [\s*] )                       # not followed by space or *
88
                                ( (?: [^ *]++ | [ *] )+ )         # content (1)
89
                                ' . Patterns::MODIFIER . '?       # modifier (2)
90
                                (?<! [\s*\\\] )                   # not preceded by space, * or \
91
                                \*\*
92
                                (?! \* )                          # not followed by *
93
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
94
                        ~Us',
95
                        'phrase/strong',
1✔
96
                );
97

98
                // //emphasis//
99
                $texy->registerLinePattern(
1✔
100
                        $this->parsePhrase(...),
1✔
101
                        '~
102
                                (?<! [/:] )                       # not preceded by / or :
103
                                //
104
                                (?! [\s/] )                       # not followed by space or /
105
                                ( (?: [^ /]++ | [ /] )+ )         # content (1)
106
                                ' . Patterns::MODIFIER . '?       # modifier (2)
107
                                (?<! [\s/:] )                     # not preceded by space, / or :
108
                                //
109
                                (?! / )                           # not followed by /
110
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
111
                        ~Us',
112
                        'phrase/em',
1✔
113
                );
114

115
                // *emphasisAlt*
116
                $texy->registerLinePattern(
1✔
117
                        $this->parsePhrase(...),
1✔
118
                        '~
119
                                (?<! [*\\\] )                    # not preceded by * or \
120
                                \*
121
                                (?! [\s*] )                      # not followed by space or *
122
                                ( (?: [^\s*]++ | [*] )+ )        # content (1)
123
                                ' . Patterns::MODIFIER . '?      # modifier (2)
124
                                (?<! [\s*\\\] )                  # not preceded by space, * or \
125
                                \*
126
                                (?! \* )                         # not followed by *
127
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
128
                        ~Us',
129
                        'phrase/em-alt',
1✔
130
                );
131

132
                // *emphasisAlt2*
133
                $texy->registerLinePattern(
1✔
134
                        $this->parsePhrase(...),
1✔
135
                        '~
136
                                (?<! [^\s.,;:<>()"\'' . Patterns::MARK . '-] )  # must be preceded by these chars
137
                                \*
138
                                (?! [\s*] )                      # not followed by space or *
139
                                ( (?: [^ *]++ | [ *] )+ )        # content (1)
140
                                ' . Patterns::MODIFIER . '?      # modifier (2)
141
                                (?<! [\s*\\\] )                  # not preceded by space, * or \
142
                                \*
143
                                (?! [^\s.,;:<>()"?!\'-] )        # must be followed by these chars
144
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
145
                        ~Us',
146
                        'phrase/em-alt2',
1✔
147
                );
148

149
                // ++inserted++
150
                $texy->registerLinePattern(
1✔
151
                        $this->parsePhrase(...),
1✔
152
                        '~
153
                                (?<! \+ )                        # not preceded by +
154
                                \+\+
155
                                (?! [\s+] )                      # not followed by space or +
156
                                ( (?: [^\r\n +]++ | [ +] )+ )    # content (1)
157
                                ' . Patterns::MODIFIER . '?      # modifier (2)
1✔
158
                                (?<! [\s+] )                     # not preceded by space or +
159
                                \+\+
160
                                (?! \+ )                         # not followed by +
161
                        ~U',
162
                        'phrase/ins',
1✔
163
                );
164

165
                // --deleted--
166
                $texy->registerLinePattern(
1✔
167
                        $this->parsePhrase(...),
1✔
168
                        '~
169
                                (?<! [<-] )                      # not preceded by < or -
170
                                --
171
                                (?! [\s>-] )                     # not followed by space, > or -
172
                                ( (?: [^\r\n -]++ | [ -] )+ )    # content (1)
173
                                ' . Patterns::MODIFIER . '?      # modifier (2)
1✔
174
                                (?<! [\s<-] )                    # not preceded by space, < or -
175
                                --
176
                                (?! [>-] )                       # not followed by > or -
177
                        ~U',
178
                        'phrase/del',
1✔
179
                );
180

181
                // ^^superscript^^
182
                $texy->registerLinePattern(
1✔
183
                        $this->parsePhrase(...),
1✔
184
                        '~
185
                                (?<! \^ )                        # not preceded by ^
186
                                \^\^
187
                                (?! [\s^] )                      # not followed by space or ^
188
                                ( (?: [^\r\n ^]++ | [ ^] )+ )    # content (1)
189
                                ' . Patterns::MODIFIER . '?      # modifier (2)
1✔
190
                                (?<! [\s^] )                     # not preceded by space or ^
191
                                \^\^
192
                                (?! \^ )                         # not followed by ^
193
                        ~U',
194
                        'phrase/sup',
1✔
195
                );
196

197
                // m^2 alternative superscript
198
                $texy->registerLinePattern(
1✔
199
                        $this->parseSupSub(...),
1✔
200
                        '~
1✔
201
                                (?<= [a-z0-9] )                  # preceded by letter or number
202
                                \^
203
                                ( [n0-9+-]{1,4}? )               # 1-4 digits, n, + or - (1)
204
                                (?! [a-z0-9] )                   # not followed by letter or number
205
                        ~Ui',
206
                        'phrase/sup-alt',
1✔
207
                );
208

209
                // __subscript__
210
                $texy->registerLinePattern(
1✔
211
                        $this->parsePhrase(...),
1✔
212
                        '~
213
                                (?<! _ )                         # not preceded by _
214
                                __
215
                                (?! [\s_] )                      # not followed by space or _
216
                                ( (?: [^\r\n _]++ | [ _] )+ )    # content (1)
217
                                ' . Patterns::MODIFIER . '?      # modifier (2)
1✔
218
                                (?<! [\s_] )                     # not preceded by space or _
219
                                __
220
                                (?! _ )                          # not followed by _
221
                        ~U',
222
                        'phrase/sub',
1✔
223
                );
224

225
                // m_2 alternative subscript
226
                $texy->registerLinePattern(
1✔
227
                        $this->parseSupSub(...),
1✔
228
                        '~
1✔
229
                                (?<= [a-z] )                     # preceded by letter
230
                                _
231
                                ( [n0-9]{1,3} )                  # 1-3 digits or n (1)
232
                                (?! [a-z0-9] )                   # not followed by letter or number
233
                        ~Ui',
234
                        'phrase/sub-alt',
1✔
235
                );
236

237
                // "span"
238
                $texy->registerLinePattern(
1✔
239
                        $this->parsePhrase(...),
1✔
240
                        '~
241
                                (?<! " )                         # not preceded by "
242
                                "
243
                                (?! \s )                         # not followed by space
244
                                ( (?: [^\r "]++ | [ ] )+ )       # content (1)
245
                                ' . Patterns::MODIFIER . '?      # modifier (2)
246
                                (?<! \s )                        # not preceded by space
247
                                "
248
                                (?! " )                          # not followed by "
249
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
250
                        ~U',
251
                        'phrase/span',
1✔
252
                );
253

254
                // ~alternative span~
255
                $texy->registerLinePattern(
1✔
256
                        $this->parsePhrase(...),
1✔
257
                        '~
258
                                (?<! \~ )
259
                                \~
260
                                (?! \s )                         # not followed by space
261
                                ( (?: [^\r \~]++ | [ ] )+ )      # content (1)
262
                                ' . Patterns::MODIFIER . '?      # modifier (2)
263
                                (?<! \s )                        # not preceded by space
264
                                \~
265
                                (?! \~ )
266
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
267
                        ~U',
268
                        'phrase/span-alt',
1✔
269
                );
270

271
                // >>quote
272
                $texy->registerLinePattern(
1✔
273
                        $this->parsePhrase(...),
1✔
274
                        '~
275
                                (?<! > )                         # not preceded by >
276
                                >>
277
                                (?! [\s>] )                      # not followed by space or >
278
                                ( (?: [^\r\n <]++ | [ <] )+ )    # content (1)
279
                                ' . Patterns::MODIFIER . '?      # modifier (2)
280
                                (?<! [\s<] )                     # not preceded by space or
281
                                <<
282
                                (?! < )                          # not followed by <
283
                                (?: :(' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
284
                        ~U',
285
                        'phrase/quote',
1✔
286
                );
287

288
                // acronym/abbr "et al."((and others))
289
                $texy->registerLinePattern(
1✔
290
                        $this->parsePhrase(...),
1✔
291
                        '~
292
                                (?<! " )                         # not preceded by "
293
                                "
294
                                (?! \s )                         # not followed by space
295
                                ( (?: [^\r\n "]++ | [ ] )+ )     # content (1)
296
                                ' . Patterns::MODIFIER . '?      # modifier (2)
1✔
297
                                (?<! \s )                        # not preceded by space
298
                                "
299
                                (?! " )                          # not followed by "
300
                                \(\(
301
                                ( .+ )                           # explanation (3)
302
                                \)\)
303
                        ~U',
304
                        'phrase/acronym',
1✔
305
                );
306

307
                // acronym/abbr NATO((North Atlantic Treaty Organisation))
308
                $texy->registerLinePattern(
1✔
309
                        $this->parsePhrase(...),
1✔
310
                        '~
311
                                (?<! [' . Patterns::CHAR . '] )  # not preceded by char
312
                                ( [' . Patterns::CHAR . ']{2,} ) # at least 2 chars (1)
1✔
313
                                ()                               # modifier placeholder (2)
314
                                \(\(
315
                                ( (?: [^\n )]++ | [ )] )+ )      # explanation (3)
316
                                \)\)
317
                        ~U',
318
                        'phrase/acronym-alt',
1✔
319
                );
320

321
                // ''notexy''
322
                $texy->registerLinePattern(
1✔
323
                        $this->parseNoTexy(...),
1✔
324
                        '~
325
                                (?<! \' )                         # not preceded by quote
326
                                \'\'
327
                                (?! [\s\'] )                      # not followed by space or quote
328
                                ( (?: [^' . Patterns::MARK . '\r\n\']++ | \' )+ )  # content (1)
1✔
329
                                (?<! [\s\'] )                     # not preceded by space or quote
330
                                \'\'
331
                                (?! \' )                          # not followed by quote
332
                        ~U',
333
                        'phrase/notexy',
1✔
334
                );
335

336
                // `code`
337
                $texy->registerLinePattern(
1✔
338
                        $this->parsePhrase(...),
1✔
339
                        '~
340
                                `
341
                                ( \S (?: [^' . Patterns::MARK . '\r\n `]++ | [ `] )* )  # content (1)
342
                                ' . Patterns::MODIFIER . '?             # modifier (2)
343
                                (?<! \s )                               # not preceded by space
344
                                `
345
                                (?: : (' . Patterns::LINK_URL . ') )??  # optional link (3)
1✔
346
                        ~U',
347
                        'phrase/code',
1✔
348
                );
349

350
                // ....:LINK
351
                $texy->registerLinePattern(
1✔
352
                        $this->parsePhrase(...),
1✔
353
                        '~
354
                                ( [' . Patterns::CHAR . '0-9@#$%&.,_-]++ )  # allowed chars (1)
355
                                ()                                    # modifier placeholder (2)
356
                                : (?= \[ )                            # followed by :[
357
                                (' . Patterns::LINK_URL . ')          # link (3)
1✔
358
                        ~U',
359
                        'phrase/quicklink',
1✔
360
                );
361

362
                // [text |link]
363
                $texy->registerLinePattern(
1✔
364
                        $this->parsePhrase(...),
1✔
365
                        '~
366
                                (?<! \[ )                        # not preceded by [
367
                                \[
368
                                (?! [\s*] )                      # not followed by space or *
369
                                ( [^|\r\n\]]++ )                 # text (1)
370
                                \|
371
                                ( (?: [^' . Patterns::MARK . '|\r\n \]]++ | [ ] )+ )  # link (2)
372
                                ' . Patterns::MODIFIER . '?      # modifier (3)
1✔
373
                                (?<! \s )                        # not preceded by space
374
                                ]
375
                                (?! ] )                          # not followed by ]
376
                        ~U',
377
                        'phrase/wikilink',
1✔
378
                );
379

380
                // [text](link)
381
                $texy->registerLinePattern(
1✔
382
                        $this->parsePhrase(...),
1✔
383
                        '~
384
                                (?<! [[.] )                     # not preceded by [ or .
385
                                \[
386
                                (?! [\s*] )                     # not followed by space or *
387
                                ( (?: [^|\r\n \]]++ | [ ] )+ )  # text (1)
388
                                ' . Patterns::MODIFIER . '?     # modifier (2)
389
                                (?<! \s )                       # not preceded by space
390
                                ]
391
                                \(
392
                                ( (?: [^' . Patterns::MARK . '\r )]++ | [ ] )+ )  # link (3)
1✔
393
                                \)
394
                        ~U',
395
                        'phrase/markdown',
1✔
396
                );
397

398
                // \* escaped asterix
399
                $texy->registerLinePattern(
1✔
400
                        fn() => '*',
1✔
401
                        '~\\\\\*~',                      // \* -> *
1✔
402
                        'phrase/escaped-asterix',
1✔
403
                );
404

405
                $texy->allowed['phrase/ins'] = false;
1✔
406
                $texy->allowed['phrase/del'] = false;
1✔
407
                $texy->allowed['phrase/sup'] = false;
1✔
408
                $texy->allowed['phrase/sub'] = false;
1✔
409
        }
1✔
410

411

412
        /**
413
         * Callback for: **.... .(title)[class]{style}**:LINK.
414
         * @param  string[]  $matches
415
         */
416
        public function parsePhrase(LineParser $parser, array $matches, string $phrase): Texy\HtmlElement|string|null
1✔
417
        {
418
                [, $mContent, $mMod, $mLink] = $matches + [3 => null];
1✔
419
                // [1] => ...
420
                // [2] => .(title)[class]{style}
421
                // [3] => LINK
422

423
                if ($phrase === 'phrase/wikilink') {
1✔
424
                        [$mLink, $mMod] = [$mMod, $mLink];
1✔
425
                        $mContent = trim($mContent);
1✔
426
                }
427

428
                $texy = $this->texy;
1✔
429
                $mod = new Modifier($mMod);
1✔
430
                $link = null;
1✔
431

432
                $parser->again = $phrase !== 'phrase/code' && $phrase !== 'phrase/quicklink';
1✔
433

434
                if ($phrase === 'phrase/span' || $phrase === 'phrase/span-alt') {
1✔
435
                        if ($mLink == null) {
1✔
436
                                if (!$mMod) {
1✔
437
                                        return null; // means "..."
1✔
438
                                }
439
                        } else {
440
                                $link = $texy->linkModule->factoryLink($mLink, $mMod, $mContent);
1✔
441
                        }
442
                } elseif ($phrase === 'phrase/acronym' || $phrase === 'phrase/acronym-alt') {
1✔
443
                        $mod->title = trim(Texy\Helpers::unescapeHtml($mLink));
1✔
444

445
                } elseif ($mLink != null) {
1✔
446
                        $link = $texy->linkModule->factoryLink($mLink, null, $mContent);
1✔
447
                }
448

449
                return $texy->invokeAroundHandlers('phrase', $parser, [$phrase, $mContent, $mod, $link]);
1✔
450
        }
451

452

453
        /**
454
         * Callback for: any^2 any_2.
455
         * @param  string[]  $matches
456
         */
457
        public function parseSupSub(LineParser $parser, array $matches, string $phrase): Texy\HtmlElement|string|null
1✔
458
        {
459
                [, $mContent] = $matches;
1✔
460
                $mod = new Modifier;
1✔
461
                $link = null;
1✔
462
                $mContent = str_replace('-', "\u{2212}", $mContent); // &minus;
1✔
463
                return $this->texy->invokeAroundHandlers('phrase', $parser, [$phrase, $mContent, $mod, $link]);
1✔
464
        }
465

466

467
        /** @param  string[]  $matches */
468
        public function parseNoTexy(LineParser $parser, array $matches): string
469
        {
UNCOV
470
                [, $mContent] = $matches;
×
UNCOV
471
                return $this->texy->protect(htmlspecialchars($mContent, ENT_NOQUOTES, 'UTF-8'), Texy\Texy::CONTENT_TEXTUAL);
×
472
        }
473

474

475
        public function toElement(
1✔
476
                Texy\HandlerInvocation $invocation,
477
                string $phrase,
478
                string $content,
479
                Modifier $mod,
480
                ?Texy\Link $link = null,
481
        ): Texy\HtmlElement|string|null
482
        {
483
                $texy = $this->texy;
1✔
484
                $tag = $this->tags[$phrase] ?? null;
1✔
485

486
                if ($tag === 'a') {
1✔
487
                        $tag = $link && $this->linksAllowed ? null : 'span';
1✔
488
                }
489

490
                if ($phrase === 'phrase/code') {
1✔
491
                        $content = $texy->protect(htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8'), $texy::CONTENT_TEXTUAL);
1✔
492
                }
493

494
                if ($phrase === 'phrase/strong+em') {
1✔
495
                        $el = new Texy\HtmlElement($this->tags['phrase/strong']);
1✔
496
                        $el->create($this->tags['phrase/em'], $content);
1✔
497
                        $mod->decorate($texy, $el);
1✔
498

499
                } elseif ($tag) {
1✔
500
                        $el = new Texy\HtmlElement($tag, $content);
1✔
501
                        $mod->decorate($texy, $el);
1✔
502
                } else {
503
                        $el = $content; // trick
1✔
504
                }
505

506
                if ($link && $this->linksAllowed) {
1✔
507
                        return $texy->linkModule->linkToElement(null, $link, $el);
1✔
508
                }
509

510
                return $el;
1✔
511
        }
512
}
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