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

dg / texy / 12879688081

21 Jan 2025 03:39AM UTC coverage: 92.224%. Remained the same
12879688081

push

github

dg
regexp: uses unmatched as null (BC break)

14 of 14 new or added lines in 6 files covered. (100.0%)

1 existing line in 1 file now uncovered.

2372 of 2572 relevant lines covered (92.22%)

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.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

17

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

43
        public bool $linksAllowed = true;
44

45

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

50
                $texy->addHandler('phrase', $this->solve(...));
1✔
51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

408

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

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

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

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

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

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

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

448

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

461

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

468

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

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

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

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

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

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

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

© 2025 Coveralls, Inc