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

nette / utils / 15764605036

19 Jun 2025 06:55PM UTC coverage: 92.467% (-0.05%) from 92.516%
15764605036

push

github

dg
Strings::trim() trims Line Separator and Ideographic Space (#326)

These characters occur in our data.

There are more characters listed at https://en.wikipedia.org/wiki/Whitespace_character#Unicode, maybe all of them should be included?

2050 of 2217 relevant lines covered (92.47%)

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

17

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

239
        /** @var array<string, mixed>  element's attributes */
240
        public $attrs = [];
241

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

249
        /** @var array<int, HtmlStringable|string> nodes */
250
        protected $children = [];
251

252
        /** element's name */
253
        private string $name = '';
254

255
        private bool $isEmpty = false;
256

257

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

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

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

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

281
                return $el;
1✔
282
        }
283

284

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

293

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

302

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

311

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

320

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

329

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

340

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

349

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

358

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

368

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

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

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

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

388
                return $this;
1✔
389
        }
390

391

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

401

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

410

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

420

421
        /**
422
         * Unsets element's 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
         */
473
        final public function __call(string $m, array $args): mixed
1✔
474
        {
475
                $p = substr($m, 0, 3);
1✔
476
                if ($p === 'get' || $p === 'set' || $p === 'add') {
1✔
477
                        $m = substr($m, 3);
1✔
478
                        $m[0] = $m[0] | "\x20";
1✔
479
                        if ($p === 'get') {
1✔
480
                                return $this->attrs[$m] ?? null;
1✔
481

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

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

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

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

496
                return $this;
1✔
497
        }
498

499

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

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

516

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

530
                return $this;
1✔
531
        }
532

533

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

543

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

552

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

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

566

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

575

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

584

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

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

597

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

607

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

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

621
                return $this;
1✔
622
        }
623

624

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

635

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

645

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

655

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

667

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

676

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

685

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

695

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

704

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

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

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

726
                        // add end tag
727
                        $s .= $this->endTag();
1✔
728
                }
729

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

734
                return $s;
1✔
735
        }
736

737

738
        final public function __toString(): string
739
        {
740
                return $this->render();
1✔
741
        }
742

743

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

754

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

763

764
        /**
765
         * Returns element's attributes.
766
         * @internal
767
         */
768
        final public function attributes(): string
769
        {
770
                if (!is_array($this->attrs)) {
1✔
771
                        return '';
×
772
                }
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 (strncmp($key, 'data-', 5) === 0) {
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