• 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

98.58
/src/Texy/Modules/PhraseModule.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\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
 * Phrases module.
22
 */
23
final class PhraseModule extends Texy\Module
24
{
25
        public array $tags = [
26
                'phrase/strong' => 'strong', // or 'b'
27
                'phrase/em' => 'em', // or 'i'
28
                'phrase/em-alt' => 'em',
29
                'phrase/em-alt2' => 'em',
30
                'phrase/ins' => 'ins',
31
                'phrase/del' => 'del',
32
                'phrase/sup' => 'sup',
33
                'phrase/sup-alt' => 'sup',
34
                'phrase/sub' => 'sub',
35
                'phrase/sub-alt' => 'sub',
36
                'phrase/span' => 'a',
37
                'phrase/span-alt' => 'a',
38
                'phrase/acronym' => 'abbr',
39
                'phrase/acronym-alt' => 'abbr',
40
                'phrase/code' => 'code',
41
                'phrase/quote' => 'q',
42
                'phrase/quicklink' => 'a',
43
        ];
44

45
        public bool $linksAllowed = true;
46

47

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

410

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

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

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

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

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

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

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

450

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

463

464
        public function parseNoTexy(LineParser $parser, array $matches): string
465
        {
UNCOV
466
                [, $mContent] = $matches;
×
UNCOV
467
                return $this->texy->protect(htmlspecialchars($mContent, ENT_NOQUOTES, 'UTF-8'), Texy\Texy::CONTENT_TEXTUAL);
×
468
        }
469

470

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

482
                if ($tag === 'a') {
1✔
483
                        $tag = $link && $this->linksAllowed ? null : 'span';
1✔
484
                }
485

486
                if ($phrase === 'phrase/code') {
1✔
487
                        $content = $texy->protect(htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8'), $texy::CONTENT_TEXTUAL);
1✔
488
                }
489

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

495
                } elseif ($tag) {
1✔
496
                        $el = new Texy\HtmlElement($tag, $content);
1✔
497
                        $mod->decorate($texy, $el);
1✔
498
                } else {
499
                        $el = $content; // trick
1✔
500
                }
501

502
                if ($link && $this->linksAllowed) {
1✔
503
                        return $texy->linkModule->linkToElement(null, $link, $el);
1✔
504
                }
505

506
                return $el;
1✔
507
        }
508
}
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