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

nette / utils / 22290136219

23 Feb 2026 01:47AM UTC coverage: 93.125% (-0.003%) from 93.128%
22290136219

push

github

dg
added CLAUDE.md

2086 of 2240 relevant lines covered (93.13%)

0.93 hits per line

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

97.24
/src/Utils/Html.php
1
<?php declare(strict_types=1);
1✔
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Utils;
9

10
use Nette\HtmlStringable;
11
use function array_merge, array_splice, count, explode, func_num_args, html_entity_decode, htmlspecialchars, http_build_query, implode, is_array, is_bool, is_float, is_object, is_string, json_encode, max, number_format, rtrim, str_contains, str_repeat, str_replace, strip_tags, strncmp, strpbrk, substr;
12
use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
13

14

15
/**
16
 * HTML helper.
17
 *
18
 * @property ?string $accept
19
 * @property ?string $accesskey
20
 * @property ?string $action
21
 * @property ?string $align
22
 * @property ?string $allow
23
 * @property ?string $alt
24
 * @property ?bool   $async
25
 * @property ?string $autocapitalize
26
 * @property ?string $autocomplete
27
 * @property ?bool   $autofocus
28
 * @property ?bool   $autoplay
29
 * @property ?string $charset
30
 * @property ?bool   $checked
31
 * @property ?string $cite
32
 * @property ?string $class
33
 * @property ?int    $cols
34
 * @property ?int    $colspan
35
 * @property ?string $content
36
 * @property ?bool   $contenteditable
37
 * @property ?bool   $controls
38
 * @property ?string $coords
39
 * @property ?string $crossorigin
40
 * @property ?string $data
41
 * @property ?string $datetime
42
 * @property ?string $decoding
43
 * @property ?bool   $default
44
 * @property ?bool   $defer
45
 * @property ?string $dir
46
 * @property ?string $dirname
47
 * @property ?bool   $disabled
48
 * @property ?bool   $download
49
 * @property ?string $draggable
50
 * @property ?string $dropzone
51
 * @property ?string $enctype
52
 * @property ?string $for
53
 * @property ?string $form
54
 * @property ?string $formaction
55
 * @property ?string $formenctype
56
 * @property ?string $formmethod
57
 * @property ?bool   $formnovalidate
58
 * @property ?string $formtarget
59
 * @property ?string $headers
60
 * @property ?int    $height
61
 * @property ?bool   $hidden
62
 * @property ?float  $high
63
 * @property ?string $href
64
 * @property ?string $hreflang
65
 * @property ?string $id
66
 * @property ?string $integrity
67
 * @property ?string $inputmode
68
 * @property ?bool   $ismap
69
 * @property ?string $itemprop
70
 * @property ?string $kind
71
 * @property ?string $label
72
 * @property ?string $lang
73
 * @property ?string $list
74
 * @property ?bool   $loop
75
 * @property ?float  $low
76
 * @property ?float  $max
77
 * @property ?int    $maxlength
78
 * @property ?int    $minlength
79
 * @property ?string $media
80
 * @property ?string $method
81
 * @property ?float  $min
82
 * @property ?bool   $multiple
83
 * @property ?bool   $muted
84
 * @property ?string $name
85
 * @property ?bool   $novalidate
86
 * @property ?bool   $open
87
 * @property ?float  $optimum
88
 * @property ?string $pattern
89
 * @property ?string $ping
90
 * @property ?string $placeholder
91
 * @property ?string $poster
92
 * @property ?string $preload
93
 * @property ?string $radiogroup
94
 * @property ?bool   $readonly
95
 * @property ?string $rel
96
 * @property ?bool   $required
97
 * @property ?bool   $reversed
98
 * @property ?int    $rows
99
 * @property ?int    $rowspan
100
 * @property ?string $sandbox
101
 * @property ?string $scope
102
 * @property ?bool   $selected
103
 * @property ?string $shape
104
 * @property ?int    $size
105
 * @property ?string $sizes
106
 * @property ?string $slot
107
 * @property ?int    $span
108
 * @property ?string $spellcheck
109
 * @property ?string $src
110
 * @property ?string $srcdoc
111
 * @property ?string $srclang
112
 * @property ?string $srcset
113
 * @property ?int    $start
114
 * @property ?float  $step
115
 * @property ?string $style
116
 * @property ?int    $tabindex
117
 * @property ?string $target
118
 * @property ?string $title
119
 * @property ?string $translate
120
 * @property ?string $type
121
 * @property ?string $usemap
122
 * @property ?string $value
123
 * @property ?int    $width
124
 * @property ?string $wrap
125
 *
126
 * @method self accept(?string $val)
127
 * @method self accesskey(?string $val, bool $state = null)
128
 * @method self action(?string $val)
129
 * @method self align(?string $val)
130
 * @method self allow(?string $val, bool $state = null)
131
 * @method self alt(?string $val)
132
 * @method self async(?bool $val)
133
 * @method self autocapitalize(?string $val)
134
 * @method self autocomplete(?string $val)
135
 * @method self autofocus(?bool $val)
136
 * @method self autoplay(?bool $val)
137
 * @method self charset(?string $val)
138
 * @method self checked(?bool $val)
139
 * @method self cite(?string $val)
140
 * @method self class(?string $val, bool $state = null)
141
 * @method self cols(?int $val)
142
 * @method self colspan(?int $val)
143
 * @method self content(?string $val)
144
 * @method self contenteditable(?bool $val)
145
 * @method self controls(?bool $val)
146
 * @method self coords(?string $val)
147
 * @method self crossorigin(?string $val)
148
 * @method self datetime(?string $val)
149
 * @method self decoding(?string $val)
150
 * @method self default(?bool $val)
151
 * @method self defer(?bool $val)
152
 * @method self dir(?string $val)
153
 * @method self dirname(?string $val)
154
 * @method self disabled(?bool $val)
155
 * @method self download(?bool $val)
156
 * @method self draggable(?string $val)
157
 * @method self dropzone(?string $val)
158
 * @method self enctype(?string $val)
159
 * @method self for(?string $val)
160
 * @method self form(?string $val)
161
 * @method self formaction(?string $val)
162
 * @method self formenctype(?string $val)
163
 * @method self formmethod(?string $val)
164
 * @method self formnovalidate(?bool $val)
165
 * @method self formtarget(?string $val)
166
 * @method self headers(?string $val, bool $state = null)
167
 * @method self height(?int $val)
168
 * @method self hidden(?bool $val)
169
 * @method self high(?float $val)
170
 * @method self hreflang(?string $val)
171
 * @method self id(?string $val)
172
 * @method self integrity(?string $val)
173
 * @method self inputmode(?string $val)
174
 * @method self ismap(?bool $val)
175
 * @method self itemprop(?string $val)
176
 * @method self kind(?string $val)
177
 * @method self label(?string $val)
178
 * @method self lang(?string $val)
179
 * @method self list(?string $val)
180
 * @method self loop(?bool $val)
181
 * @method self low(?float $val)
182
 * @method self max(?float $val)
183
 * @method self maxlength(?int $val)
184
 * @method self minlength(?int $val)
185
 * @method self media(?string $val)
186
 * @method self method(?string $val)
187
 * @method self min(?float $val)
188
 * @method self multiple(?bool $val)
189
 * @method self muted(?bool $val)
190
 * @method self name(?string $val)
191
 * @method self novalidate(?bool $val)
192
 * @method self open(?bool $val)
193
 * @method self optimum(?float $val)
194
 * @method self pattern(?string $val)
195
 * @method self ping(?string $val, bool $state = null)
196
 * @method self placeholder(?string $val)
197
 * @method self poster(?string $val)
198
 * @method self preload(?string $val)
199
 * @method self radiogroup(?string $val)
200
 * @method self readonly(?bool $val)
201
 * @method self rel(?string $val)
202
 * @method self required(?bool $val)
203
 * @method self reversed(?bool $val)
204
 * @method self rows(?int $val)
205
 * @method self rowspan(?int $val)
206
 * @method self sandbox(?string $val, bool $state = null)
207
 * @method self scope(?string $val)
208
 * @method self selected(?bool $val)
209
 * @method self shape(?string $val)
210
 * @method self size(?int $val)
211
 * @method self sizes(?string $val)
212
 * @method self slot(?string $val)
213
 * @method self span(?int $val)
214
 * @method self spellcheck(?string $val)
215
 * @method self src(?string $val)
216
 * @method self srcdoc(?string $val)
217
 * @method self srclang(?string $val)
218
 * @method self srcset(?string $val)
219
 * @method self start(?int $val)
220
 * @method self step(?float $val)
221
 * @method self style(?string $property, string $val = null)
222
 * @method self tabindex(?int $val)
223
 * @method self target(?string $val)
224
 * @method self title(?string $val)
225
 * @method self translate(?string $val)
226
 * @method self type(?string $val)
227
 * @method self usemap(?string $val)
228
 * @method self value(?string $val)
229
 * @method self width(?int $val)
230
 * @method self wrap(?string $val)
231
 *
232
 * @implements \IteratorAggregate<int, self|string>
233
 * @implements \ArrayAccess<int, self|string>
234
 */
235
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
236
{
237
        /** @var array<string, mixed>  element's attributes */
238
        public array $attrs = [];
239

240
        /** @var array<string, int>  void elements */
241
        public static array $emptyElements = [
242
                'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
243
                'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
244
                'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
245
        ];
246

247
        /** @var array<int, self|string> nodes */
248
        protected array $children = [];
249

250
        /** element's name */
251
        private string $name = '';
252

253
        private bool $isEmpty = false;
254

255

256
        /**
257
         * Constructs new HTML element.
258
         * @param  array<string, mixed>|string|null  $attrs element's attributes or plain text content
259
         */
260
        public static function el(?string $name = null, array|string|null $attrs = null): static
1✔
261
        {
262
                $el = new static;
1✔
263
                $parts = explode(' ', (string) $name, 2);
1✔
264
                $el->setName($parts[0]);
1✔
265

266
                if (is_array($attrs)) {
1✔
267
                        $el->attrs = $attrs;
×
268

269
                } elseif ($attrs !== null) {
1✔
270
                        $el->setText($attrs);
×
271
                }
272

273
                if (isset($parts[1])) {
1✔
274
                        foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
1✔
275
                                $el->attrs[$m[1]] = $m[3] ?? true;
1✔
276
                        }
277
                }
278

279
                return $el;
1✔
280
        }
281

282

283
        /**
284
         * Returns an object representing HTML text.
285
         */
286
        public static function fromHtml(string $html): static
1✔
287
        {
288
                return (new static)->setHtml($html);
1✔
289
        }
290

291

292
        /**
293
         * Returns an object representing plain text.
294
         */
295
        public static function fromText(string $text): static
1✔
296
        {
297
                return (new static)->setText($text);
1✔
298
        }
299

300

301
        /**
302
         * Converts to HTML.
303
         */
304
        final public function toHtml(): string
305
        {
306
                return $this->render();
1✔
307
        }
308

309

310
        /**
311
         * Converts to plain text.
312
         */
313
        final public function toText(): string
314
        {
315
                return $this->getText();
1✔
316
        }
317

318

319
        /**
320
         * Converts given HTML code to plain text.
321
         */
322
        public static function htmlToText(string $html): string
1✔
323
        {
324
                return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
1✔
325
        }
326

327

328
        /**
329
         * Changes element's name.
330
         */
331
        final public function setName(string $name, ?bool $isEmpty = null): static
1✔
332
        {
333
                $this->name = $name;
1✔
334
                $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
1✔
335
                return $this;
1✔
336
        }
337

338

339
        /**
340
         * Returns element's name.
341
         */
342
        final public function getName(): string
343
        {
344
                return $this->name;
1✔
345
        }
346

347

348
        /**
349
         * Is element empty?
350
         */
351
        final public function isEmpty(): bool
352
        {
353
                return $this->isEmpty;
×
354
        }
355

356

357
        /**
358
         * Sets multiple attributes.
359
         * @param  array<string, mixed>  $attrs
360
         */
361
        public function addAttributes(array $attrs): static
1✔
362
        {
363
                $this->attrs = array_merge($this->attrs, $attrs);
1✔
364
                return $this;
1✔
365
        }
366

367

368
        /**
369
         * Appends value to element's attribute.
370
         */
371
        public function appendAttribute(string $name, mixed $value, mixed $option = true): static
1✔
372
        {
373
                if (is_array($value)) {
1✔
374
                        $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
1✔
375
                        $this->attrs[$name] = $value + $prev;
1✔
376

377
                } elseif ((string) $value === '') {
1✔
378
                        $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
1✔
379

380
                } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
1✔
381
                        $this->attrs[$name][$value] = $option;
1✔
382

383
                } else {
384
                        $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
1✔
385
                }
386

387
                return $this;
1✔
388
        }
389

390

391
        /**
392
         * Sets element's attribute.
393
         */
394
        public function setAttribute(string $name, mixed $value): static
1✔
395
        {
396
                $this->attrs[$name] = $value;
1✔
397
                return $this;
1✔
398
        }
399

400

401
        /**
402
         * Returns element's attribute.
403
         */
404
        public function getAttribute(string $name): mixed
1✔
405
        {
406
                return $this->attrs[$name] ?? null;
1✔
407
        }
408

409

410
        /**
411
         * Unsets element's attribute.
412
         */
413
        public function removeAttribute(string $name): static
1✔
414
        {
415
                unset($this->attrs[$name]);
1✔
416
                return $this;
1✔
417
        }
418

419

420
        /**
421
         * Unsets element's attributes.
422
         * @param  list<string>  $attributes
423
         */
424
        public function removeAttributes(array $attributes): static
1✔
425
        {
426
                foreach ($attributes as $name) {
1✔
427
                        unset($this->attrs[$name]);
1✔
428
                }
429

430
                return $this;
1✔
431
        }
432

433

434
        /**
435
         * Overloaded setter for element's attribute.
436
         */
437
        final public function __set(string $name, mixed $value): void
1✔
438
        {
439
                $this->attrs[$name] = $value;
1✔
440
        }
1✔
441

442

443
        /**
444
         * Overloaded getter for element's attribute.
445
         */
446
        final public function &__get(string $name): mixed
1✔
447
        {
448
                return $this->attrs[$name];
1✔
449
        }
450

451

452
        /**
453
         * Overloaded tester for element's attribute.
454
         */
455
        final public function __isset(string $name): bool
1✔
456
        {
457
                return isset($this->attrs[$name]);
1✔
458
        }
459

460

461
        /**
462
         * Overloaded unsetter for element's attribute.
463
         */
464
        final public function __unset(string $name): void
1✔
465
        {
466
                unset($this->attrs[$name]);
1✔
467
        }
1✔
468

469

470
        /**
471
         * Overloaded setter for element's attribute.
472
         * @param  mixed[]  $args
473
         */
474
        final public function __call(string $m, array $args): mixed
1✔
475
        {
476
                $p = substr($m, 0, 3);
1✔
477
                if ($p === 'get' || $p === 'set' || $p === 'add') {
1✔
478
                        $m = substr($m, 3);
1✔
479
                        $m[0] = $m[0] | "\x20";
1✔
480
                        if ($p === 'get') {
1✔
481
                                return $this->attrs[$m] ?? null;
1✔
482

483
                        } elseif ($p === 'add') {
1✔
484
                                $args[] = true;
1✔
485
                        }
486
                }
487

488
                if (count($args) === 0) { // invalid
1✔
489

490
                } elseif (count($args) === 1) { // set
1✔
491
                        $this->attrs[$m] = $args[0];
1✔
492

493
                } else { // add
494
                        $this->appendAttribute($m, $args[0], $args[1]);
1✔
495
                }
496

497
                return $this;
1✔
498
        }
499

500

501
        /**
502
         * Special setter for element's attribute.
503
         * @param  array<string, mixed>  $query
504
         */
505
        final public function href(string $path, array $query = []): static
1✔
506
        {
507
                if ($query) {
1✔
508
                        $query = http_build_query($query, '', '&');
1✔
509
                        if ($query !== '') {
1✔
510
                                $path .= '?' . $query;
1✔
511
                        }
512
                }
513

514
                $this->attrs['href'] = $path;
1✔
515
                return $this;
1✔
516
        }
517

518

519
        /**
520
         * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
521
         */
522
        public function data(string $name, mixed $value = null): static
1✔
523
        {
524
                if (func_num_args() === 1) {
1✔
525
                        $this->attrs['data'] = $name;
1✔
526
                } else {
527
                        $this->attrs["data-$name"] = is_bool($value)
1✔
528
                                ? json_encode($value)
1✔
529
                                : $value;
1✔
530
                }
531

532
                return $this;
1✔
533
        }
534

535

536
        /**
537
         * Sets element's HTML content.
538
         */
539
        final public function setHtml(mixed $html): static
1✔
540
        {
541
                $this->children = [(string) $html];
1✔
542
                return $this;
1✔
543
        }
544

545

546
        /**
547
         * Returns element's HTML content.
548
         */
549
        final public function getHtml(): string
550
        {
551
                return implode('', $this->children);
1✔
552
        }
553

554

555
        /**
556
         * Sets element's textual content.
557
         */
558
        final public function setText(mixed $text): static
1✔
559
        {
560
                if (!$text instanceof HtmlStringable) {
1✔
561
                        $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
1✔
562
                }
563

564
                $this->children = [(string) $text];
1✔
565
                return $this;
1✔
566
        }
567

568

569
        /**
570
         * Returns element's textual content.
571
         */
572
        final public function getText(): string
573
        {
574
                return self::htmlToText($this->getHtml());
1✔
575
        }
576

577

578
        /**
579
         * Adds new element's child.
580
         */
581
        final public function addHtml(HtmlStringable|string $child): static
1✔
582
        {
583
                return $this->insert(null, $child);
1✔
584
        }
585

586

587
        /**
588
         * Appends plain-text string to element content.
589
         */
590
        public function addText(\Stringable|string|int|null $text): static
1✔
591
        {
592
                if (!$text instanceof HtmlStringable) {
1✔
593
                        $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
1✔
594
                }
595

596
                return $this->insert(null, $text);
1✔
597
        }
598

599

600
        /**
601
         * Creates and adds a new Html child.
602
         * @param  array<string, mixed>|string|null  $attrs
603
         */
604
        final public function create(string $name, array|string|null $attrs = null): static
1✔
605
        {
606
                $this->insert(null, $child = static::el($name, $attrs));
1✔
607
                return $child;
1✔
608
        }
609

610

611
        /**
612
         * Inserts child node.
613
         */
614
        public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
1✔
615
        {
616
                $child = $child instanceof self ? $child : (string) $child;
1✔
617
                if ($index === null) { // append
1✔
618
                        $this->children[] = $child;
1✔
619

620
                } else { // insert or replace
621
                        array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
×
622
                }
623

624
                return $this;
1✔
625
        }
626

627

628
        /**
629
         * Inserts (replaces) child node (\ArrayAccess implementation).
630
         * @param  ?int  $index  position or null for appending
631
         * @param  Html|string  $child  Html node or raw HTML string
632
         */
633
        final public function offsetSet($index, $child): void
634
        {
635
                $this->insert($index, $child, replace: true);
×
636
        }
637

638

639
        /**
640
         * Returns child node (\ArrayAccess implementation).
641
         * @param  int  $index
642
         */
643
        final public function offsetGet($index): self|string
644
        {
645
                return $this->children[$index];
1✔
646
        }
647

648

649
        /**
650
         * Exists child node? (\ArrayAccess implementation).
651
         * @param  int  $index
652
         */
653
        final public function offsetExists($index): bool
654
        {
655
                return isset($this->children[$index]);
1✔
656
        }
657

658

659
        /**
660
         * Removes child node (\ArrayAccess implementation).
661
         * @param  int  $index
662
         */
663
        public function offsetUnset($index): void
664
        {
665
                if (isset($this->children[$index])) {
1✔
666
                        array_splice($this->children, $index, 1);
1✔
667
                }
668
        }
1✔
669

670

671
        /**
672
         * Returns children count.
673
         */
674
        final public function count(): int
675
        {
676
                return count($this->children);
1✔
677
        }
678

679

680
        /**
681
         * Removes all children.
682
         */
683
        public function removeChildren(): void
684
        {
685
                $this->children = [];
1✔
686
        }
1✔
687

688

689
        /**
690
         * Iterates over elements.
691
         * @return \ArrayIterator<int, self|string>
692
         */
693
        final public function getIterator(): \ArrayIterator
694
        {
695
                return new \ArrayIterator($this->children);
1✔
696
        }
697

698

699
        /**
700
         * Returns all children.
701
         * @return array<int, self|string>
702
         */
703
        final public function getChildren(): array
704
        {
705
                return $this->children;
1✔
706
        }
707

708

709
        /**
710
         * Renders element's start tag, content and end tag.
711
         */
712
        final public function render(?int $indent = null): string
1✔
713
        {
714
                $s = $this->startTag();
1✔
715

716
                if (!$this->isEmpty) {
1✔
717
                        // add content
718
                        if ($indent !== null) {
1✔
719
                                $indent++;
1✔
720
                        }
721

722
                        foreach ($this->children as $child) {
1✔
723
                                if ($child instanceof self) {
1✔
724
                                        $s .= $child->render($indent);
1✔
725
                                } else {
726
                                        $s .= $child;
1✔
727
                                }
728
                        }
729

730
                        // add end tag
731
                        $s .= $this->endTag();
1✔
732
                }
733

734
                if ($indent !== null) {
1✔
735
                        return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
1✔
736
                }
737

738
                return $s;
1✔
739
        }
740

741

742
        final public function __toString(): string
743
        {
744
                return $this->render();
1✔
745
        }
746

747

748
        /**
749
         * Returns element's start tag.
750
         */
751
        final public function startTag(): string
752
        {
753
                return $this->name
1✔
754
                        ? '<' . $this->name . $this->attributes() . '>'
1✔
755
                        : '';
1✔
756
        }
757

758

759
        /**
760
         * Returns element's end tag.
761
         */
762
        final public function endTag(): string
763
        {
764
                return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
1✔
765
        }
766

767

768
        /**
769
         * Returns element's attributes.
770
         * @internal
771
         */
772
        final public function attributes(): string
773
        {
774
                $s = '';
1✔
775
                $attrs = $this->attrs;
1✔
776
                foreach ($attrs as $key => $value) {
1✔
777
                        if ($value === null || $value === false) {
1✔
778
                                continue;
1✔
779

780
                        } elseif ($value === true) {
1✔
781
                                $s .= ' ' . $key;
1✔
782

783
                                continue;
1✔
784

785
                        } elseif (is_array($value)) {
1✔
786
                                if (str_starts_with($key, 'data-')) {
1✔
787
                                        $value = Json::encode($value);
1✔
788

789
                                } else {
790
                                        $tmp = null;
1✔
791
                                        foreach ($value as $k => $v) {
1✔
792
                                                if ($v != null) { // intentionally ==, skip nulls & empty string
1✔
793
                                                        // composite 'style' vs. 'others'
794
                                                        $tmp[] = $v === true
1✔
795
                                                                ? $k
1✔
796
                                                                : (is_string($k) ? $k . ':' . $v : $v);
1✔
797
                                                }
798
                                        }
799

800
                                        if ($tmp === null) {
1✔
801
                                                continue;
1✔
802
                                        }
803

804
                                        $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
1✔
805
                                }
806
                        } elseif (is_float($value)) {
1✔
807
                                $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
1✔
808

809
                        } else {
810
                                $value = (string) $value;
1✔
811
                        }
812

813
                        $q = str_contains($value, '"') ? "'" : '"';
1✔
814
                        $s .= ' ' . $key . '=' . $q
1✔
815
                                . str_replace(
1✔
816
                                        ['&', $q, '<'],
1✔
817
                                        ['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'],
1✔
818
                                        $value,
1✔
819
                                )
820
                                . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
1✔
821
                                . $q;
1✔
822
                }
823

824
                $s = str_replace('@', '&#64;', $s);
1✔
825
                return $s;
1✔
826
        }
827

828

829
        /**
830
         * Clones all children too.
831
         */
832
        public function __clone()
833
        {
834
                foreach ($this->children as $key => $value) {
1✔
835
                        if (is_object($value)) {
1✔
836
                                $this->children[$key] = clone $value;
1✔
837
                        }
838
                }
839
        }
1✔
840
}
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