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

dg / texy / 29746802114

20 Jul 2026 01:19PM UTC coverage: 94.677% (+0.04%) from 94.641%
29746802114

push

github

dg
Dead code removed

- Engine::getPatternNames() and Texy::getEngine(): no callers in src or tests.
- LinkDefinitionModule::resolveUrl(): unreachable. Both bracket forms it
  handles ([*img*] and [ref]) are already consumed by earlier returns in
  resolveLinkNodeUrl(), so it could only ever return its argument unchanged.
- Text\Renderer: CodeBlockNode type 'comment' never exists; comments are a
  separate CommentNode. Leftover from before the node split.

1 of 1 new or added line in 1 file covered. (100.0%)

74 existing lines in 15 files now uncovered.

3344 of 3532 relevant lines covered (94.68%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.45
/src/Texy/Modules/HtmlModule.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\Nodes\HtmlCommentNode;
12
use Texy\Nodes\HtmlTagNode;
13
use Texy\ParseContext;
14
use Texy\Range;
15
use Texy\Regexp;
16
use Texy\Syntax;
17
use function str_ends_with, strlen, strtolower, strtr, substr, trim;
18

19

20
/**
21
 * Processes HTML tags and comments in input text.
22
 */
23
final class HtmlModule extends Texy\Module
24
{
25
        public function __construct(
1✔
26
                private Texy\Texy $texy,
27
        ) {
28
                $texy->addHandler('afterParse', $this->processPassthrough(...));
1✔
29
        }
1✔
30

31

32
        /**
33
         * Pairs passthrough tags into HtmlElementNode trees and evaluates the
34
         * tag whitelist over them (transform phase): rejected tags become text.
35
         */
36
        public function processPassthrough(Texy\Nodes\DocumentNode $doc): void
1✔
37
        {
38
                if (empty($this->texy->allowed[Syntax::HtmlTag])) {
1✔
39
                        return;
×
40
                }
41

42
                (new Texy\Passes\HtmlPairingPass)->process($doc);
1✔
43
                (new Texy\Passes\HtmlSanitizePass($this->texy->htmlPolicy))
1✔
44
                        ->process($doc);
1✔
45
        }
1✔
46

47

48
        public function beforeParse(string &$text): void
1✔
49
        {
50
                $this->texy->registerLinePattern(
1✔
51
                        $this->parseTag(...),
1✔
52
                        '~
1✔
53
                                < (/?)                          # tag begin
54
                                ([a-z][a-z0-9_:-]{0,50})        # tag name
55
                                (
56
                                        (?:
57
                                                \s++ [a-z0-9_:-]++ |          # attribute name
58
                                                = \s*+ " [^"]*+ " |           # attribute value in double quotes
59
                                                = \s*+ \' [^\']*+ \' |        # attribute value in single quotes
60
                                                = [^\s>]++                    # attribute value without quotes
61
                                        )*
62
                                )
63
                                \s*+
64
                                (/?)                             # self-closing slash
65
                                >
66
                        ~isx',
67
                        Syntax::HtmlTag,
1✔
68
                );
69

70
                $this->texy->registerLinePattern(
1✔
71
                        $this->parseComment(...),
1✔
72
                        '~
1✔
73
                                <!--
74
                                ( .*? )
75
                                -->
76
                        ~isx',
77
                        Syntax::HtmlComment,
1✔
78
                );
79
        }
1✔
80

81

82
        /**
83
         * Parses <!-- comment -->
84
         * @param  array{string, string}  $matches
85
         * @param  array{int, int}  $offsets
86
         */
87
        public function parseComment(ParseContext $context, array $matches, array $offsets): HtmlCommentNode
1✔
88
        {
89
                return new HtmlCommentNode($matches[1], new Range($offsets[0], strlen($matches[0])));
1✔
90
        }
91

92

93
        /**
94
         * Parses <tag attr="...">
95
         * @param  array{string, string, string, string, string}  $matches
96
         * @param  array{int, int, int, int, int}  $offsets
97
         */
98
        public function parseTag(ParseContext $context, array $matches, array $offsets): ?HtmlTagNode
1✔
99
        {
100
                [, $mEnd, $mTag, $mAttr, $mEmpty] = $matches;
1✔
101

102
                $isStart = $mEnd !== '/';
1✔
103
                $isEmpty = $mEmpty === '/';
1✔
104
                if (!$isEmpty && str_ends_with($mAttr, '/')) {
1✔
UNCOV
105
                        $mAttr = substr($mAttr, 0, -1);
×
UNCOV
106
                        $isEmpty = true;
×
107
                }
108

109
                // error - can't close empty element
110
                if ($isEmpty && !$isStart) {
1✔
UNCOV
111
                        return null;
×
112
                }
113

114
                // error - end element with attrs
115
                $mAttr = trim(strtr($mAttr, "\n", ' '));
1✔
116
                if ($mAttr && !$isStart) {
1✔
117
                        return null;
1✔
118
                }
119

120
                return new HtmlTagNode(
1✔
121
                        $mTag,
1✔
122
                        $isStart ? $this->parseAttributes($mAttr) : [],
1✔
123
                        closing: !$isStart,
1✔
124
                        selfClosing: $isEmpty,
125
                        range: new Range($offsets[0], strlen($matches[0])),
1✔
126
                );
127
        }
128

129

130
        /** @return array<string, string|bool> */
131
        private function parseAttributes(string $attrs): array
1✔
132
        {
133
                $res = [];
1✔
134
                $matches = Regexp::matchAll(
1✔
135
                        $attrs,
1✔
136
                        <<<'X'
137
                                ~
1✔
138
                                ([a-z0-9_:-]+)                 # attribute name
139
                                \s*
140
                                (?:
141
                                        = \s*                      # equals sign
142
                                        (
143
                                                ' [^']* ' |            # single quoted value
144
                                                " [^"]* " |            # double quoted value
145
                                                [^'"\s]+               # unquoted value
146
                                        )
147
                                )?
148
                                ~isx
149
                                X,
150
                );
151

152
                /** @var array{string, string, ?string} $m */
153
                foreach ($matches as $m) {
1✔
154
                        $key = strtolower($m[1]);
1✔
155
                        $value = $m[2];
1✔
156
                        if ($value == null) {
1✔
157
                                $res[$key] = true;
1✔
158
                        } elseif ($value[0] === '\'' || $value[0] === '"') {
1✔
159
                                $res[$key] = trim(Texy\Helpers::unescapeHtml(substr($value, 1, -1)));
1✔
160
                        } else {
161
                                $res[$key] = trim(Texy\Helpers::unescapeHtml($value));
1✔
162
                        }
163
                }
164

165
                return $res;
1✔
166
        }
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc