• 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

98.73
/src/Texy/Modules/PhraseModule.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\Modules;
9

10
use Texy;
11
use Texy\InlineParser;
12
use Texy\Modifier;
13
use Texy\Patterns;
14
use function htmlspecialchars, str_replace, trim;
15
use const ENT_NOQUOTES;
16

17

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

44
        public bool $linksAllowed = true;
45

46

47
        public function __construct(
1✔
48
                private Texy\Texy $texy,
49
        ) {
50
                $texy->allowed['phrase/ins'] = false;
1✔
51
                $texy->allowed['phrase/del'] = false;
1✔
52
                $texy->allowed['phrase/sup'] = false;
1✔
53
                $texy->allowed['phrase/sub'] = false;
1✔
54
                $texy->addHandler('phrase', $this->solve(...));
1✔
55
        }
1✔
56

57

58
        public function beforeParse(string &$text): void
1✔
59
        {
60
                $texy = $this->texy;
1✔
61
                /*
62
                // UNIVERSAL
63
                $texy->registerLinePattern(
64
                        array($this, 'patternPhrase'),
65
                        '~((?>([*+/^_"\~`-])+?))(?!\s)(.*(?!\2).)'.Texy\Patterns::MODIFIER.'?(?<!\s)\1(?!\2)(?::('.Texy\Patterns::LINK_URL.'))??~Us',
66
                        'phrase/strong'
67
                );
68
                */
69

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

404
                // \* escaped asterix
405
                $texy->registerLinePattern(
1✔
406
                        fn() => '*',
1✔
407
                        '~\\\\\*~',                      // \* -> *
1✔
408
                        'phrase/escaped-asterix',
1✔
409
                );
410
        }
1✔
411

412

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

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

429
                $texy = $this->texy;
1✔
430
                $mod = Modifier::parse($mMod);
1✔
431
                $link = null;
1✔
432

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

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

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

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

453

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

467

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

475

476
        /**
477
         * Finish invocation.
478
         */
479
        private function solve(
1✔
480
                Texy\HandlerInvocation $invocation,
481
                string $phrase,
482
                string $content,
483
                Modifier $mod,
484
                ?Texy\Link $link = null,
485
        ): Texy\HtmlElement|string|null
486
        {
487
                $texy = $this->texy;
1✔
488
                $tag = $this->tags[$phrase] ?? null;
1✔
489

490
                if ($tag === 'a') {
1✔
491
                        $tag = $link && $this->linksAllowed ? null : 'span';
1✔
492
                }
493

494
                if ($phrase === 'phrase/code') {
1✔
495
                        $content = $texy->protect(htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8'), $texy::CONTENT_TEXTUAL);
1✔
496
                }
497

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

503
                } elseif ($tag) {
1✔
504
                        $el = new Texy\HtmlElement($tag, $content);
1✔
505
                        $mod->decorate($texy, $el);
1✔
506
                } else {
507
                        $el = $content; // trick
1✔
508
                }
509

510
                if ($link && $this->linksAllowed) {
1✔
511
                        return $texy->linkModule->solve(null, $link, $el);
1✔
512
                }
513

514
                return $el;
1✔
515
        }
516
}
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