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

dg / texy / 21344532034

26 Jan 2026 02:43AM UTC coverage: 91.98% (-0.4%) from 92.376%
21344532034

push

github

dg
added CLAUDE.md

2397 of 2606 relevant lines covered (91.98%)

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->solve(...));
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.'))??()#Uus',
60
                        'phrase/strong'
61
                );
62
                */
63

64
                // ***strong+emphasis***
65
                $texy->registerLinePattern(
1✔
66
                        $this->patternPhrase(...),
1✔
67
                        '#(?<![*\\\])\*\*\*(?![\s*])((?:[^ *]++|[ *])+)' . Patterns::MODIFIER . '?(?<![\s*\\\])\*\*\*(?!\*)(?::(' . Patterns::LINK_URL . '))??()#Uus',
1✔
68
                        'phrase/strong+em',
1✔
69
                );
70

71
                // **strong**
72
                $texy->registerLinePattern(
1✔
73
                        $this->patternPhrase(...),
1✔
74
                        '#(?<![*\\\])\*\*(?![\s*])((?:[^ *]++|[ *])+)' . Patterns::MODIFIER . '?(?<![\s*\\\])\*\*(?!\*)(?::(' . Patterns::LINK_URL . '))??()#Uus',
1✔
75
                        'phrase/strong',
1✔
76
                );
77

78
                // //emphasis//
79
                $texy->registerLinePattern(
1✔
80
                        $this->patternPhrase(...),
1✔
81
                        '#(?<![/:])\/\/(?![\s/])((?:[^ /]++|[ /])+)' . Patterns::MODIFIER . '?(?<![\s/:])\/\/(?!\/)(?::(' . Patterns::LINK_URL . '))??()#Uus',
1✔
82
                        'phrase/em',
1✔
83
                );
84

85
                // *emphasisAlt*
86
                $texy->registerLinePattern(
1✔
87
                        $this->patternPhrase(...),
1✔
88
                        '#(?<![*\\\])\*(?![\s*])((?:[^\s*]++|[*])+)' . Patterns::MODIFIER . '?(?<![\s*\\\])\*(?!\*)(?::(' . Patterns::LINK_URL . '))??()#Uus',
1✔
89
                        'phrase/em-alt',
1✔
90
                );
91

92
                // *emphasisAlt2*
93
                $texy->registerLinePattern(
1✔
94
                        $this->patternPhrase(...),
1✔
95
                        '#(?<![^\s.,;:<>()"\'' . Patterns::MARK . '-])\*(?![\s*])((?:[^ *]++|[ *])+)' . Patterns::MODIFIER . '?(?<![\s*\\\])\*(?![^\s.,;:<>()"?!\'-])(?::(' . Patterns::LINK_URL . '))??()#Uus',
1✔
96
                        'phrase/em-alt2',
1✔
97
                );
98

99
                // ++inserted++
100
                $texy->registerLinePattern(
1✔
101
                        $this->patternPhrase(...),
1✔
102
                        '#(?<!\+)\+\+(?![\s+])((?:[^\r\n +]++|[ +])+)' . Patterns::MODIFIER . '?(?<![\s+])\+\+(?!\+)()#Uu',
1✔
103
                        'phrase/ins',
1✔
104
                );
105

106
                // --deleted--
107
                $texy->registerLinePattern(
1✔
108
                        $this->patternPhrase(...),
1✔
109
                        '#(?<![<-])\-\-(?![\s>-])((?:[^\r\n -]++|[ -])+)' . Patterns::MODIFIER . '?(?<![\s<-])\-\-(?![>-])()#Uu',
1✔
110
                        'phrase/del',
1✔
111
                );
112

113
                // ^^superscript^^
114
                $texy->registerLinePattern(
1✔
115
                        $this->patternPhrase(...),
1✔
116
                        '#(?<!\^)\^\^(?![\s^])((?:[^\r\n ^]++|[ ^])+)' . Patterns::MODIFIER . '?(?<![\s^])\^\^(?!\^)()#Uu',
1✔
117
                        'phrase/sup',
1✔
118
                );
119

120
                // m^2 alternative superscript
121
                $texy->registerLinePattern(
1✔
122
                        $this->patternSupSub(...),
1✔
123
                        '#(?<=[a-z0-9])\^([n0-9+-]{1,4}?)(?![a-z0-9])#Uui',
1✔
124
                        'phrase/sup-alt',
1✔
125
                );
126

127
                // __subscript__
128
                $texy->registerLinePattern(
1✔
129
                        $this->patternPhrase(...),
1✔
130
                        '#(?<!\_)\_\_(?![\s_])((?:[^\r\n _]++|[ _])+)' . Patterns::MODIFIER . '?(?<![\s_])\_\_(?!\_)()#Uu',
1✔
131
                        'phrase/sub',
1✔
132
                );
133

134
                // m_2 alternative subscript
135
                $texy->registerLinePattern(
1✔
136
                        $this->patternSupSub(...),
1✔
137
                        '#(?<=[a-z])\_([n0-9]{1,3})(?![a-z0-9])#Uui',
1✔
138
                        'phrase/sub-alt',
1✔
139
                );
140

141
                // "span"
142
                $texy->registerLinePattern(
1✔
143
                        $this->patternPhrase(...),
1✔
144
                        '#(?<!\")\"(?!\s)((?:[^\r "]++|[ ])+)' . Patterns::MODIFIER . '?(?<!\s)\"(?!\")(?::(' . Patterns::LINK_URL . '))??()#Uu',
1✔
145
                        'phrase/span',
1✔
146
                );
147

148
                // ~alternative span~
149
                $texy->registerLinePattern(
1✔
150
                        $this->patternPhrase(...),
1✔
151
                        '#(?<!\~)\~(?!\s)((?:[^\r ~]++|[ ])+)' . Patterns::MODIFIER . '?(?<!\s)\~(?!\~)(?::(' . Patterns::LINK_URL . '))??()#Uu',
1✔
152
                        'phrase/span-alt',
1✔
153
                );
154

155
                // >>quote<<
156
                $texy->registerLinePattern(
1✔
157
                        $this->patternPhrase(...),
1✔
158
                        '#(?<!\>)\>\>(?![\s>])((?:[^\r\n <]++|[ <])+)' . Patterns::MODIFIER . '?(?<![\s<])\<\<(?!\<)(?::(' . Patterns::LINK_URL . '))??()#Uu',
1✔
159
                        'phrase/quote',
1✔
160
                );
161

162
                // acronym/abbr "et al."((and others))
163
                $texy->registerLinePattern(
1✔
164
                        $this->patternPhrase(...),
1✔
165
                        '#(?<!\")\"(?!\s)((?:[^\r\n "]++|[ ])+)' . Patterns::MODIFIER . '?(?<!\s)\"(?!\")\(\((.+)\)\)()#Uu',
1✔
166
                        'phrase/acronym',
1✔
167
                );
168

169
                // acronym/abbr NATO((North Atlantic Treaty Organisation))
170
                $texy->registerLinePattern(
1✔
171
                        $this->patternPhrase(...),
1✔
172
                        '#(?<![' . Patterns::CHAR . '])([' . Patterns::CHAR . ']{2,})()\(\(((?:[^\n )]++|[ )])+)\)\)#Uu',
1✔
173
                        'phrase/acronym-alt',
1✔
174
                );
175

176
                // ''notexy''
177
                $texy->registerLinePattern(
1✔
178
                        $this->patternNoTexy(...),
1✔
179
                        '#(?<!\')\'\'(?![\s\'])((?:[^' . Patterns::MARK . '\r\n\']++|[\'])+)(?<![\s\'])\'\'(?!\')()#Uu',
1✔
180
                        'phrase/notexy',
1✔
181
                );
182

183
                // `code`
184
                $texy->registerLinePattern(
1✔
185
                        $this->patternPhrase(...),
1✔
186
                        '#\`(\S(?:[^' . Patterns::MARK . '\r\n `]++|[ `])*)' . Patterns::MODIFIER . '?(?<!\s)\`(?::(' . Patterns::LINK_URL . '))??()#Uu',
1✔
187
                        'phrase/code',
1✔
188
                );
189

190
                // ....:LINK
191
                $texy->registerLinePattern(
1✔
192
                        $this->patternPhrase(...),
1✔
193
                        '#([' . Patterns::CHAR . '0-9@\#$%&.,_-]++)()(?=:\[)(?::(' . Patterns::LINK_URL . '))()#Uu',
1✔
194
                        'phrase/quicklink',
1✔
195
                );
196

197
                // [text |link]
198
                $texy->registerLinePattern(
1✔
199
                        $this->patternPhrase(...),
1✔
200
                        '#(?<!\[)\[(?![\s*])([^|\r\n\]]++)\|((?:[^' . Patterns::MARK . '|\r\n \]]++|[ ])+)' . Patterns::MODIFIER . '?(?<!\s)\](?!\])()#Uu',
1✔
201
                        'phrase/wikilink',
1✔
202
                );
203

204
                // [text](link)
205
                $texy->registerLinePattern(
1✔
206
                        $this->patternPhrase(...),
1✔
207
                        '#(?<![[.])\[(?![\s*])((?:[^|\r\n \]]++|[ ])+)' . Patterns::MODIFIER . '?(?<!\s)\]\(((?:[^' . Patterns::MARK . '\r )]++|[ ])+)\)()#Uu',
1✔
208
                        'phrase/markdown',
1✔
209
                );
210

211
                // \* escaped asterix
212
                $texy->registerLinePattern(
1✔
213
                        fn() => '*',
1✔
214
                        '#\\\\\*#',
1✔
215
                        'phrase/escaped-asterix',
1✔
216
                );
217

218

219
                $texy->allowed['phrase/ins'] = false;
1✔
220
                $texy->allowed['phrase/del'] = false;
1✔
221
                $texy->allowed['phrase/sup'] = false;
1✔
222
                $texy->allowed['phrase/sub'] = false;
1✔
223
        }
1✔
224

225

226
        /**
227
         * Callback for: **.... .(title)[class]{style}**:LINK.
228
         * @param  string[]  $matches
229
         */
230
        public function patternPhrase(LineParser $parser, array $matches, string $phrase): Texy\HtmlElement|string|null
1✔
231
        {
232
                [, $mContent, $mMod, $mLink] = $matches;
1✔
233
                // [1] => **
234
                // [2] => ...
235
                // [3] => .(title)[class]{style}
236
                // [4] => LINK
237

238
                if ($phrase === 'phrase/wikilink') {
1✔
239
                        [$mLink, $mMod] = [$mMod, $mLink];
1✔
240
                        $mContent = trim($mContent);
1✔
241
                }
242

243
                $texy = $this->texy;
1✔
244
                $mod = new Modifier($mMod);
1✔
245
                $link = null;
1✔
246

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

249
                if ($phrase === 'phrase/span' || $phrase === 'phrase/span-alt') {
1✔
250
                        if ($mLink == null) {
1✔
251
                                if (!$mMod) {
1✔
252
                                        return null; // means "..."
1✔
253
                                }
254
                        } else {
255
                                $link = $texy->linkModule->factoryLink($mLink, $mMod, $mContent);
1✔
256
                        }
257
                } elseif ($phrase === 'phrase/acronym' || $phrase === 'phrase/acronym-alt') {
1✔
258
                        $mod->title = trim(Texy\Helpers::unescapeHtml($mLink));
1✔
259

260
                } elseif ($mLink != null) {
1✔
261
                        $link = $texy->linkModule->factoryLink($mLink, null, $mContent);
1✔
262
                }
263

264
                return $texy->invokeAroundHandlers('phrase', $parser, [$phrase, $mContent, $mod, $link]);
1✔
265
        }
266

267

268
        /**
269
         * Callback for: any^2 any_2.
270
         * @param  string[]  $matches
271
         */
272
        public function patternSupSub(LineParser $parser, array $matches, string $phrase): Texy\HtmlElement|string|null
1✔
273
        {
274
                [, $mContent] = $matches;
1✔
275
                $mod = new Modifier;
1✔
276
                $link = null;
1✔
277
                $mContent = str_replace('-', "\u{2212}", $mContent); // &minus;
1✔
278
                return $this->texy->invokeAroundHandlers('phrase', $parser, [$phrase, $mContent, $mod, $link]);
1✔
279
        }
280

281

282
        /** @param  string[]  $matches */
283
        public function patternNoTexy(LineParser $parser, array $matches): string
284
        {
285
                [, $mContent] = $matches;
×
286
                return $this->texy->protect(htmlspecialchars($mContent, ENT_NOQUOTES, 'UTF-8'), Texy\Texy::CONTENT_TEXTUAL);
×
287
        }
288

289

290
        /**
291
         * Finish invocation.
292
         */
293
        private function solve(
1✔
294
                Texy\HandlerInvocation $invocation,
295
                string $phrase,
296
                string $content,
297
                Modifier $mod,
298
                ?Texy\Link $link = null,
299
        ): Texy\HtmlElement|string|null
300
        {
301
                $texy = $this->texy;
1✔
302
                $tag = $this->tags[$phrase] ?? null;
1✔
303

304
                if ($tag === 'a') {
1✔
305
                        $tag = $link && $this->linksAllowed ? null : 'span';
1✔
306
                }
307

308
                if ($phrase === 'phrase/code') {
1✔
309
                        $content = $texy->protect(htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8'), $texy::CONTENT_TEXTUAL);
1✔
310
                }
311

312
                if ($phrase === 'phrase/strong+em') {
1✔
313
                        $el = new Texy\HtmlElement($this->tags['phrase/strong']);
1✔
314
                        $el->create($this->tags['phrase/em'], $content);
1✔
315
                        $mod->decorate($texy, $el);
1✔
316

317
                } elseif ($tag) {
1✔
318
                        $el = new Texy\HtmlElement($tag, $content);
1✔
319
                        $mod->decorate($texy, $el);
1✔
320
                } else {
321
                        $el = $content; // trick
1✔
322
                }
323

324
                if ($link && $this->linksAllowed) {
1✔
325
                        return $texy->linkModule->solve(null, $link, $el);
1✔
326
                }
327

328
                return $el;
1✔
329
        }
330
}
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