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

nette / utils / 20431395313

22 Dec 2025 12:06PM UTC coverage: 93.164% (+71.8%) from 21.324%
20431395313

push

github

dg
Html::addText() accepts int|null for back compatibility [Closes #332][Closes #333]

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

140 existing lines in 15 files now uncovered.

2058 of 2209 relevant lines covered (93.16%)

0.93 hits per line

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

96.7
/src/Utils/Html.php
1
<?php
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
declare(strict_types=1);
9

10
namespace Nette\Utils;
11

12
use Nette\HtmlStringable;
13
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;
14
use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
15

16

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

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

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

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

252
        private bool $isEmpty = false;
253

254

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

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

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

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

278
                return $el;
1✔
279
        }
280

281

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

290

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

299

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

308

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

317

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

326

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

337

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

346

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

355

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

365

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

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

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

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

385
                return $this;
1✔
386
        }
387

388

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

398

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

407

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

417

418
        /**
419
         * Unsets element's attributes.
420
         */
421
        public function removeAttributes(array $attributes): static
1✔
422
        {
423
                foreach ($attributes as $name) {
1✔
424
                        unset($this->attrs[$name]);
1✔
425
                }
426

427
                return $this;
1✔
428
        }
429

430

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

439

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

448

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

457

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

466

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

479
                        } elseif ($p === 'add') {
1✔
480
                                $args[] = true;
1✔
481
                        }
482
                }
483

484
                if (count($args) === 0) { // invalid
1✔
485

486
                } elseif (count($args) === 1) { // set
1✔
487
                        $this->attrs[$m] = $args[0];
1✔
488

489
                } else { // add
490
                        $this->appendAttribute($m, $args[0], $args[1]);
1✔
491
                }
492

493
                return $this;
1✔
494
        }
495

496

497
        /**
498
         * Special setter for element's attribute.
499
         */
500
        final public function href(string $path, array $query = []): static
1✔
501
        {
502
                if ($query) {
1✔
503
                        $query = http_build_query($query, '', '&');
1✔
504
                        if ($query !== '') {
1✔
505
                                $path .= '?' . $query;
1✔
506
                        }
507
                }
508

509
                $this->attrs['href'] = $path;
1✔
510
                return $this;
1✔
511
        }
512

513

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

527
                return $this;
1✔
528
        }
529

530

531
        /**
532
         * Sets element's HTML content.
533
         */
534
        final public function setHtml(mixed $html): static
1✔
535
        {
536
                $this->children = [(string) $html];
1✔
537
                return $this;
1✔
538
        }
539

540

541
        /**
542
         * Returns element's HTML content.
543
         */
544
        final public function getHtml(): string
545
        {
546
                return implode('', $this->children);
1✔
547
        }
548

549

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

559
                $this->children = [(string) $text];
1✔
560
                return $this;
1✔
561
        }
562

563

564
        /**
565
         * Returns element's textual content.
566
         */
567
        final public function getText(): string
568
        {
569
                return self::htmlToText($this->getHtml());
1✔
570
        }
571

572

573
        /**
574
         * Adds new element's child.
575
         */
576
        final public function addHtml(HtmlStringable|string $child): static
1✔
577
        {
578
                return $this->insert(null, $child);
1✔
579
        }
580

581

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

591
                return $this->insert(null, $text);
1✔
592
        }
593

594

595
        /**
596
         * Creates and adds a new Html child.
597
         */
598
        final public function create(string $name, array|string|null $attrs = null): static
1✔
599
        {
600
                $this->insert(null, $child = static::el($name, $attrs));
1✔
601
                return $child;
1✔
602
        }
603

604

605
        /**
606
         * Inserts child node.
607
         */
608
        public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
1✔
609
        {
610
                $child = $child instanceof self ? $child : (string) $child;
1✔
611
                if ($index === null) { // append
1✔
612
                        $this->children[] = $child;
1✔
613

614
                } else { // insert or replace
UNCOV
615
                        array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
×
616
                }
617

618
                return $this;
1✔
619
        }
620

621

622
        /**
623
         * Inserts (replaces) child node (\ArrayAccess implementation).
624
         * @param  int|null  $index  position or null for appending
625
         * @param  Html|string  $child  Html node or raw HTML string
626
         */
627
        final public function offsetSet($index, $child): void
628
        {
UNCOV
629
                $this->insert($index, $child, replace: true);
×
630
        }
631

632

633
        /**
634
         * Returns child node (\ArrayAccess implementation).
635
         * @param  int  $index
636
         */
637
        final public function offsetGet($index): HtmlStringable|string
638
        {
639
                return $this->children[$index];
1✔
640
        }
641

642

643
        /**
644
         * Exists child node? (\ArrayAccess implementation).
645
         * @param  int  $index
646
         */
647
        final public function offsetExists($index): bool
648
        {
649
                return isset($this->children[$index]);
1✔
650
        }
651

652

653
        /**
654
         * Removes child node (\ArrayAccess implementation).
655
         * @param  int  $index
656
         */
657
        public function offsetUnset($index): void
658
        {
659
                if (isset($this->children[$index])) {
1✔
660
                        array_splice($this->children, $index, 1);
1✔
661
                }
662
        }
1✔
663

664

665
        /**
666
         * Returns children count.
667
         */
668
        final public function count(): int
669
        {
670
                return count($this->children);
1✔
671
        }
672

673

674
        /**
675
         * Removes all children.
676
         */
677
        public function removeChildren(): void
678
        {
679
                $this->children = [];
1✔
680
        }
1✔
681

682

683
        /**
684
         * Iterates over elements.
685
         * @return \ArrayIterator<int, HtmlStringable|string>
686
         */
687
        final public function getIterator(): \ArrayIterator
688
        {
689
                return new \ArrayIterator($this->children);
1✔
690
        }
691

692

693
        /**
694
         * Returns all children.
695
         */
696
        final public function getChildren(): array
697
        {
698
                return $this->children;
1✔
699
        }
700

701

702
        /**
703
         * Renders element's start tag, content and end tag.
704
         */
705
        final public function render(?int $indent = null): string
1✔
706
        {
707
                $s = $this->startTag();
1✔
708

709
                if (!$this->isEmpty) {
1✔
710
                        // add content
711
                        if ($indent !== null) {
1✔
712
                                $indent++;
1✔
713
                        }
714

715
                        foreach ($this->children as $child) {
1✔
716
                                if ($child instanceof self) {
1✔
717
                                        $s .= $child->render($indent);
1✔
718
                                } else {
719
                                        $s .= $child;
1✔
720
                                }
721
                        }
722

723
                        // add end tag
724
                        $s .= $this->endTag();
1✔
725
                }
726

727
                if ($indent !== null) {
1✔
728
                        return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
1✔
729
                }
730

731
                return $s;
1✔
732
        }
733

734

735
        final public function __toString(): string
736
        {
737
                return $this->render();
1✔
738
        }
739

740

741
        /**
742
         * Returns element's start tag.
743
         */
744
        final public function startTag(): string
745
        {
746
                return $this->name
1✔
747
                        ? '<' . $this->name . $this->attributes() . '>'
1✔
748
                        : '';
1✔
749
        }
750

751

752
        /**
753
         * Returns element's end tag.
754
         */
755
        final public function endTag(): string
756
        {
757
                return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
1✔
758
        }
759

760

761
        /**
762
         * Returns element's attributes.
763
         * @internal
764
         */
765
        final public function attributes(): string
766
        {
767
                if (!is_array($this->attrs)) {
1✔
UNCOV
768
                        return '';
×
769
                }
770

771
                $s = '';
1✔
772
                $attrs = $this->attrs;
1✔
773
                foreach ($attrs as $key => $value) {
1✔
774
                        if ($value === null || $value === false) {
1✔
775
                                continue;
1✔
776

777
                        } elseif ($value === true) {
1✔
778
                                $s .= ' ' . $key;
1✔
779

780
                                continue;
1✔
781

782
                        } elseif (is_array($value)) {
1✔
783
                                if (strncmp($key, 'data-', 5) === 0) {
1✔
784
                                        $value = Json::encode($value);
1✔
785

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

797
                                        if ($tmp === null) {
1✔
798
                                                continue;
1✔
799
                                        }
800

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

806
                        } else {
807
                                $value = (string) $value;
1✔
808
                        }
809

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

821
                $s = str_replace('@', '&#64;', $s);
1✔
822
                return $s;
1✔
823
        }
824

825

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