@vituum/vite-plugin-latte 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/latte/PlaceholderFunction.php +2 -2
  2. package/package.json +1 -1
  3. package/vendor/autoload.php +1 -1
  4. package/vendor/composer/autoload_classmap.php +51 -0
  5. package/vendor/composer/autoload_real.php +4 -4
  6. package/vendor/composer/autoload_static.php +53 -2
  7. package/vendor/composer/installed.json +96 -7
  8. package/vendor/composer/installed.php +14 -5
  9. package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +11 -2
  10. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/FilterNode.php +1 -1
  11. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +2 -2
  12. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NameNode.php +11 -21
  13. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +1 -1
  14. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +2 -2
  15. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +275 -280
  16. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +4 -4
  17. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +10 -2
  18. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +6 -2
  19. package/vendor/latte/latte/src/Latte/Engine.php +109 -97
  20. package/vendor/latte/latte/src/Latte/Essential/AuxiliaryIterator.php +46 -0
  21. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +42 -25
  22. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +76 -72
  23. package/vendor/latte/latte/src/Latte/Essential/Filters.php +95 -37
  24. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +7 -0
  25. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +36 -0
  26. package/vendor/latte/latte/src/Latte/Essential/Nodes/TemplatePrintNode.php +25 -3
  27. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarPrintNode.php +9 -2
  28. package/vendor/latte/latte/src/Latte/Essential/Passes.php +10 -50
  29. package/vendor/latte/latte/src/Latte/Helpers.php +3 -1
  30. package/vendor/latte/latte/src/Latte/Loader.php +1 -0
  31. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +1 -2
  32. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +1 -4
  33. package/vendor/latte/latte/src/Latte/Runtime/FunctionExecutor.php +68 -0
  34. package/vendor/latte/latte/src/Latte/Runtime/Template.php +1 -3
  35. package/vendor/nette/utils/.phpstorm.meta.php +13 -0
  36. package/vendor/nette/utils/composer.json +51 -0
  37. package/vendor/nette/utils/license.md +60 -0
  38. package/vendor/nette/utils/readme.md +56 -0
  39. package/vendor/nette/utils/src/HtmlStringable.php +22 -0
  40. package/vendor/nette/utils/src/Iterators/CachingIterator.php +164 -0
  41. package/vendor/nette/utils/src/Iterators/Mapper.php +34 -0
  42. package/vendor/nette/utils/src/SmartObject.php +140 -0
  43. package/vendor/nette/utils/src/StaticClass.php +34 -0
  44. package/vendor/nette/utils/src/Translator.php +25 -0
  45. package/vendor/nette/utils/src/Utils/ArrayHash.php +106 -0
  46. package/vendor/nette/utils/src/Utils/ArrayList.php +136 -0
  47. package/vendor/nette/utils/src/Utils/Arrays.php +522 -0
  48. package/vendor/nette/utils/src/Utils/Callback.php +137 -0
  49. package/vendor/nette/utils/src/Utils/DateTime.php +140 -0
  50. package/vendor/nette/utils/src/Utils/FileInfo.php +69 -0
  51. package/vendor/nette/utils/src/Utils/FileSystem.php +326 -0
  52. package/vendor/nette/utils/src/Utils/Finder.php +510 -0
  53. package/vendor/nette/utils/src/Utils/Floats.php +107 -0
  54. package/vendor/nette/utils/src/Utils/Helpers.php +104 -0
  55. package/vendor/nette/utils/src/Utils/Html.php +839 -0
  56. package/vendor/nette/utils/src/Utils/Image.php +829 -0
  57. package/vendor/nette/utils/src/Utils/ImageColor.php +75 -0
  58. package/vendor/nette/utils/src/Utils/ImageType.php +25 -0
  59. package/vendor/nette/utils/src/Utils/Iterables.php +159 -0
  60. package/vendor/nette/utils/src/Utils/Json.php +84 -0
  61. package/vendor/nette/utils/src/Utils/ObjectHelpers.php +229 -0
  62. package/vendor/nette/utils/src/Utils/Paginator.php +245 -0
  63. package/vendor/nette/utils/src/Utils/Random.php +52 -0
  64. package/vendor/nette/utils/src/Utils/Reflection.php +320 -0
  65. package/vendor/nette/utils/src/Utils/ReflectionMethod.php +36 -0
  66. package/vendor/nette/utils/src/Utils/Strings.php +708 -0
  67. package/vendor/nette/utils/src/Utils/Type.php +267 -0
  68. package/vendor/nette/utils/src/Utils/Validators.php +416 -0
  69. package/vendor/nette/utils/src/Utils/exceptions.php +50 -0
  70. package/vendor/nette/utils/src/compatibility.php +32 -0
  71. package/vendor/nette/utils/src/exceptions.php +109 -0
@@ -0,0 +1,839 @@
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 is_array, is_float, is_object, is_string;
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
+ use Nette\SmartObject;
237
+
238
+ /** @var array<string, mixed> element's attributes */
239
+ public $attrs = [];
240
+
241
+ /** void elements */
242
+ public static $emptyElements = [
243
+ 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
244
+ 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
245
+ 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
246
+ ];
247
+
248
+ /** @var array<int, HtmlStringable|string> nodes */
249
+ protected $children = [];
250
+
251
+ /** element's name */
252
+ private string $name = '';
253
+
254
+ private bool $isEmpty = false;
255
+
256
+
257
+ /**
258
+ * Constructs new HTML element.
259
+ * @param array|string $attrs element's attributes or plain text content
260
+ */
261
+ public static function el(?string $name = null, array|string|null $attrs = null): static
262
+ {
263
+ $el = new static;
264
+ $parts = explode(' ', (string) $name, 2);
265
+ $el->setName($parts[0]);
266
+
267
+ if (is_array($attrs)) {
268
+ $el->attrs = $attrs;
269
+
270
+ } elseif ($attrs !== null) {
271
+ $el->setText($attrs);
272
+ }
273
+
274
+ if (isset($parts[1])) {
275
+ foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
276
+ $el->attrs[$m[1]] = $m[3] ?? true;
277
+ }
278
+ }
279
+
280
+ return $el;
281
+ }
282
+
283
+
284
+ /**
285
+ * Returns an object representing HTML text.
286
+ */
287
+ public static function fromHtml(string $html): static
288
+ {
289
+ return (new static)->setHtml($html);
290
+ }
291
+
292
+
293
+ /**
294
+ * Returns an object representing plain text.
295
+ */
296
+ public static function fromText(string $text): static
297
+ {
298
+ return (new static)->setText($text);
299
+ }
300
+
301
+
302
+ /**
303
+ * Converts to HTML.
304
+ */
305
+ final public function toHtml(): string
306
+ {
307
+ return $this->render();
308
+ }
309
+
310
+
311
+ /**
312
+ * Converts to plain text.
313
+ */
314
+ final public function toText(): string
315
+ {
316
+ return $this->getText();
317
+ }
318
+
319
+
320
+ /**
321
+ * Converts given HTML code to plain text.
322
+ */
323
+ public static function htmlToText(string $html): string
324
+ {
325
+ return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
326
+ }
327
+
328
+
329
+ /**
330
+ * Changes element's name.
331
+ */
332
+ final public function setName(string $name, ?bool $isEmpty = null): static
333
+ {
334
+ $this->name = $name;
335
+ $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
336
+ return $this;
337
+ }
338
+
339
+
340
+ /**
341
+ * Returns element's name.
342
+ */
343
+ final public function getName(): string
344
+ {
345
+ return $this->name;
346
+ }
347
+
348
+
349
+ /**
350
+ * Is element empty?
351
+ */
352
+ final public function isEmpty(): bool
353
+ {
354
+ return $this->isEmpty;
355
+ }
356
+
357
+
358
+ /**
359
+ * Sets multiple attributes.
360
+ */
361
+ public function addAttributes(array $attrs): static
362
+ {
363
+ $this->attrs = array_merge($this->attrs, $attrs);
364
+ return $this;
365
+ }
366
+
367
+
368
+ /**
369
+ * Appends value to element's attribute.
370
+ */
371
+ public function appendAttribute(string $name, mixed $value, mixed $option = true): static
372
+ {
373
+ if (is_array($value)) {
374
+ $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
375
+ $this->attrs[$name] = $value + $prev;
376
+
377
+ } elseif ((string) $value === '') {
378
+ $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
379
+
380
+ } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
381
+ $this->attrs[$name][$value] = $option;
382
+
383
+ } else {
384
+ $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
385
+ }
386
+
387
+ return $this;
388
+ }
389
+
390
+
391
+ /**
392
+ * Sets element's attribute.
393
+ */
394
+ public function setAttribute(string $name, mixed $value): static
395
+ {
396
+ $this->attrs[$name] = $value;
397
+ return $this;
398
+ }
399
+
400
+
401
+ /**
402
+ * Returns element's attribute.
403
+ */
404
+ public function getAttribute(string $name): mixed
405
+ {
406
+ return $this->attrs[$name] ?? null;
407
+ }
408
+
409
+
410
+ /**
411
+ * Unsets element's attribute.
412
+ */
413
+ public function removeAttribute(string $name): static
414
+ {
415
+ unset($this->attrs[$name]);
416
+ return $this;
417
+ }
418
+
419
+
420
+ /**
421
+ * Unsets element's attributes.
422
+ */
423
+ public function removeAttributes(array $attributes): static
424
+ {
425
+ foreach ($attributes as $name) {
426
+ unset($this->attrs[$name]);
427
+ }
428
+
429
+ return $this;
430
+ }
431
+
432
+
433
+ /**
434
+ * Overloaded setter for element's attribute.
435
+ */
436
+ final public function __set(string $name, mixed $value): void
437
+ {
438
+ $this->attrs[$name] = $value;
439
+ }
440
+
441
+
442
+ /**
443
+ * Overloaded getter for element's attribute.
444
+ */
445
+ final public function &__get(string $name): mixed
446
+ {
447
+ return $this->attrs[$name];
448
+ }
449
+
450
+
451
+ /**
452
+ * Overloaded tester for element's attribute.
453
+ */
454
+ final public function __isset(string $name): bool
455
+ {
456
+ return isset($this->attrs[$name]);
457
+ }
458
+
459
+
460
+ /**
461
+ * Overloaded unsetter for element's attribute.
462
+ */
463
+ final public function __unset(string $name): void
464
+ {
465
+ unset($this->attrs[$name]);
466
+ }
467
+
468
+
469
+ /**
470
+ * Overloaded setter for element's attribute.
471
+ */
472
+ final public function __call(string $m, array $args): mixed
473
+ {
474
+ $p = substr($m, 0, 3);
475
+ if ($p === 'get' || $p === 'set' || $p === 'add') {
476
+ $m = substr($m, 3);
477
+ $m[0] = $m[0] | "\x20";
478
+ if ($p === 'get') {
479
+ return $this->attrs[$m] ?? null;
480
+
481
+ } elseif ($p === 'add') {
482
+ $args[] = true;
483
+ }
484
+ }
485
+
486
+ if (count($args) === 0) { // invalid
487
+
488
+ } elseif (count($args) === 1) { // set
489
+ $this->attrs[$m] = $args[0];
490
+
491
+ } else { // add
492
+ $this->appendAttribute($m, $args[0], $args[1]);
493
+ }
494
+
495
+ return $this;
496
+ }
497
+
498
+
499
+ /**
500
+ * Special setter for element's attribute.
501
+ */
502
+ final public function href(string $path, array $query = []): static
503
+ {
504
+ if ($query) {
505
+ $query = http_build_query($query, '', '&');
506
+ if ($query !== '') {
507
+ $path .= '?' . $query;
508
+ }
509
+ }
510
+
511
+ $this->attrs['href'] = $path;
512
+ return $this;
513
+ }
514
+
515
+
516
+ /**
517
+ * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
518
+ */
519
+ public function data(string $name, mixed $value = null): static
520
+ {
521
+ if (func_num_args() === 1) {
522
+ $this->attrs['data'] = $name;
523
+ } else {
524
+ $this->attrs["data-$name"] = is_bool($value)
525
+ ? json_encode($value)
526
+ : $value;
527
+ }
528
+
529
+ return $this;
530
+ }
531
+
532
+
533
+ /**
534
+ * Sets element's HTML content.
535
+ */
536
+ final public function setHtml(mixed $html): static
537
+ {
538
+ $this->children = [(string) $html];
539
+ return $this;
540
+ }
541
+
542
+
543
+ /**
544
+ * Returns element's HTML content.
545
+ */
546
+ final public function getHtml(): string
547
+ {
548
+ return implode('', $this->children);
549
+ }
550
+
551
+
552
+ /**
553
+ * Sets element's textual content.
554
+ */
555
+ final public function setText(mixed $text): static
556
+ {
557
+ if (!$text instanceof HtmlStringable) {
558
+ $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
559
+ }
560
+
561
+ $this->children = [(string) $text];
562
+ return $this;
563
+ }
564
+
565
+
566
+ /**
567
+ * Returns element's textual content.
568
+ */
569
+ final public function getText(): string
570
+ {
571
+ return self::htmlToText($this->getHtml());
572
+ }
573
+
574
+
575
+ /**
576
+ * Adds new element's child.
577
+ */
578
+ final public function addHtml(mixed $child): static
579
+ {
580
+ return $this->insert(null, $child);
581
+ }
582
+
583
+
584
+ /**
585
+ * Appends plain-text string to element content.
586
+ */
587
+ public function addText(mixed $text): static
588
+ {
589
+ if (!$text instanceof HtmlStringable) {
590
+ $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
591
+ }
592
+
593
+ return $this->insert(null, $text);
594
+ }
595
+
596
+
597
+ /**
598
+ * Creates and adds a new Html child.
599
+ */
600
+ final public function create(string $name, array|string|null $attrs = null): static
601
+ {
602
+ $this->insert(null, $child = static::el($name, $attrs));
603
+ return $child;
604
+ }
605
+
606
+
607
+ /**
608
+ * Inserts child node.
609
+ */
610
+ public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
611
+ {
612
+ $child = $child instanceof self ? $child : (string) $child;
613
+ if ($index === null) { // append
614
+ $this->children[] = $child;
615
+
616
+ } else { // insert or replace
617
+ array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
618
+ }
619
+
620
+ return $this;
621
+ }
622
+
623
+
624
+ /**
625
+ * Inserts (replaces) child node (\ArrayAccess implementation).
626
+ * @param int|null $index position or null for appending
627
+ * @param Html|string $child Html node or raw HTML string
628
+ */
629
+ final public function offsetSet($index, $child): void
630
+ {
631
+ $this->insert($index, $child, replace: true);
632
+ }
633
+
634
+
635
+ /**
636
+ * Returns child node (\ArrayAccess implementation).
637
+ * @param int $index
638
+ */
639
+ final public function offsetGet($index): HtmlStringable|string
640
+ {
641
+ return $this->children[$index];
642
+ }
643
+
644
+
645
+ /**
646
+ * Exists child node? (\ArrayAccess implementation).
647
+ * @param int $index
648
+ */
649
+ final public function offsetExists($index): bool
650
+ {
651
+ return isset($this->children[$index]);
652
+ }
653
+
654
+
655
+ /**
656
+ * Removes child node (\ArrayAccess implementation).
657
+ * @param int $index
658
+ */
659
+ public function offsetUnset($index): void
660
+ {
661
+ if (isset($this->children[$index])) {
662
+ array_splice($this->children, $index, 1);
663
+ }
664
+ }
665
+
666
+
667
+ /**
668
+ * Returns children count.
669
+ */
670
+ final public function count(): int
671
+ {
672
+ return count($this->children);
673
+ }
674
+
675
+
676
+ /**
677
+ * Removes all children.
678
+ */
679
+ public function removeChildren(): void
680
+ {
681
+ $this->children = [];
682
+ }
683
+
684
+
685
+ /**
686
+ * Iterates over elements.
687
+ * @return \ArrayIterator<int, HtmlStringable|string>
688
+ */
689
+ final public function getIterator(): \ArrayIterator
690
+ {
691
+ return new \ArrayIterator($this->children);
692
+ }
693
+
694
+
695
+ /**
696
+ * Returns all children.
697
+ */
698
+ final public function getChildren(): array
699
+ {
700
+ return $this->children;
701
+ }
702
+
703
+
704
+ /**
705
+ * Renders element's start tag, content and end tag.
706
+ */
707
+ final public function render(?int $indent = null): string
708
+ {
709
+ $s = $this->startTag();
710
+
711
+ if (!$this->isEmpty) {
712
+ // add content
713
+ if ($indent !== null) {
714
+ $indent++;
715
+ }
716
+
717
+ foreach ($this->children as $child) {
718
+ if ($child instanceof self) {
719
+ $s .= $child->render($indent);
720
+ } else {
721
+ $s .= $child;
722
+ }
723
+ }
724
+
725
+ // add end tag
726
+ $s .= $this->endTag();
727
+ }
728
+
729
+ if ($indent !== null) {
730
+ return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
731
+ }
732
+
733
+ return $s;
734
+ }
735
+
736
+
737
+ final public function __toString(): string
738
+ {
739
+ return $this->render();
740
+ }
741
+
742
+
743
+ /**
744
+ * Returns element's start tag.
745
+ */
746
+ final public function startTag(): string
747
+ {
748
+ return $this->name
749
+ ? '<' . $this->name . $this->attributes() . '>'
750
+ : '';
751
+ }
752
+
753
+
754
+ /**
755
+ * Returns element's end tag.
756
+ */
757
+ final public function endTag(): string
758
+ {
759
+ return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
760
+ }
761
+
762
+
763
+ /**
764
+ * Returns element's attributes.
765
+ * @internal
766
+ */
767
+ final public function attributes(): string
768
+ {
769
+ if (!is_array($this->attrs)) {
770
+ return '';
771
+ }
772
+
773
+ $s = '';
774
+ $attrs = $this->attrs;
775
+ foreach ($attrs as $key => $value) {
776
+ if ($value === null || $value === false) {
777
+ continue;
778
+
779
+ } elseif ($value === true) {
780
+ $s .= ' ' . $key;
781
+
782
+ continue;
783
+
784
+ } elseif (is_array($value)) {
785
+ if (strncmp($key, 'data-', 5) === 0) {
786
+ $value = Json::encode($value);
787
+
788
+ } else {
789
+ $tmp = null;
790
+ foreach ($value as $k => $v) {
791
+ if ($v != null) { // intentionally ==, skip nulls & empty string
792
+ // composite 'style' vs. 'others'
793
+ $tmp[] = $v === true
794
+ ? $k
795
+ : (is_string($k) ? $k . ':' . $v : $v);
796
+ }
797
+ }
798
+
799
+ if ($tmp === null) {
800
+ continue;
801
+ }
802
+
803
+ $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
804
+ }
805
+ } elseif (is_float($value)) {
806
+ $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
807
+
808
+ } else {
809
+ $value = (string) $value;
810
+ }
811
+
812
+ $q = str_contains($value, '"') ? "'" : '"';
813
+ $s .= ' ' . $key . '=' . $q
814
+ . str_replace(
815
+ ['&', $q, '<'],
816
+ ['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'],
817
+ $value,
818
+ )
819
+ . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
820
+ . $q;
821
+ }
822
+
823
+ $s = str_replace('@', '&#64;', $s);
824
+ return $s;
825
+ }
826
+
827
+
828
+ /**
829
+ * Clones all children too.
830
+ */
831
+ public function __clone()
832
+ {
833
+ foreach ($this->children as $key => $value) {
834
+ if (is_object($value)) {
835
+ $this->children[$key] = clone $value;
836
+ }
837
+ }
838
+ }
839
+ }