@@ -77,6 +77,11 @@ class Compiler
77
77
*/
78
78
protected $ rawBlocks = [];
79
79
80
+ /**
81
+ * @var string[]
82
+ */
83
+ protected $ includeAttributes = ['class ' , 'style ' ];
84
+
80
85
/**
81
86
* Compiler constructor.
82
87
*/
@@ -178,6 +183,7 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
178
183
} elseif ($ node instanceof DOMDocument) {
179
184
$ this ->logger ->warning ('Document node found. ' );
180
185
} elseif ($ node instanceof DOMElement) {
186
+ $ this ->twigRemove ($ node );
181
187
$ this ->replaceShowWithIf ($ node );
182
188
$ this ->handleIf ($ node , $ level );
183
189
$ this ->handleFor ($ node );
@@ -238,7 +244,7 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
238
244
$ include = $ this ->document ->createTextNode (
239
245
$ this ->builder ->createIncludePartial (
240
246
$ usedComponent ->getPath (),
241
- $ usedComponent ->getProperties ()
247
+ $ this -> preparePropertiesForInclude ( $ usedComponent ->getProperties () )
242
248
)
243
249
);
244
250
@@ -273,7 +279,9 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
273
279
if ($ node instanceof DOMElement) {
274
280
$ this ->handleAttributeBinding ($ node );
275
281
if ($ level === 1 ) {
276
- $ this ->handleRootNodeClassAttribute ($ node );
282
+ foreach ($ this ->includeAttributes as $ attribute ) {
283
+ $ this ->handleRootNodeAttribute ($ node , $ attribute );
284
+ }
277
285
}
278
286
}
279
287
@@ -284,6 +292,44 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
284
292
return $ node ;
285
293
}
286
294
295
+ /**
296
+ * @param Property[] $variables
297
+ *
298
+ * @throws ReflectionException
299
+ *
300
+ * @return Property[]
301
+ */
302
+ private function preparePropertiesForInclude (array $ variables ): array
303
+ {
304
+ $ values = [];
305
+ foreach ($ variables as $ key => $ variable ) {
306
+ $ name = $ variable ->getName ();
307
+ $ value = $ variable ->getValue ();
308
+ if (in_array ($ name , $ this ->includeAttributes )) {
309
+ if ($ variable ->isBinding ()) {
310
+ $ values [$ name ][] = $ this ->handleBinding ($ value , $ name , null , false )[0 ];
311
+ } else {
312
+ $ values [$ name ][] = $ value ;
313
+ }
314
+ unset($ variables [$ key ]);
315
+ }
316
+ }
317
+
318
+ foreach ($ this ->includeAttributes as $ attribute ) {
319
+ $ glue = ' ~ " " ~ ' ;
320
+ if ($ attribute === 'style ' ) {
321
+ $ glue = ' ~ "; " ~ ' ;
322
+ }
323
+ $ variables [] = new Property (
324
+ $ attribute ,
325
+ $ values [$ attribute ] ?? null ? implode ($ glue , $ values [$ attribute ]) : '"" ' ,
326
+ false
327
+ );
328
+ }
329
+
330
+ return $ variables ;
331
+ }
332
+
287
333
public function registerProperties (DOMElement $ scriptElement ): void
288
334
{
289
335
$ content = $ this ->innerHtmlOfNode ($ scriptElement );
@@ -349,7 +395,6 @@ private function handleAttributeBinding(DOMElement $node): void
349
395
$ this ->logger ->debug ('- handle: ' . $ name . ' = ' . $ value );
350
396
351
397
$ staticValues = $ node ->hasAttribute ($ name ) ? $ node ->getAttribute ($ name ) : '' ;
352
- $ dynamicValues = [];
353
398
354
399
// Remove originally bound attribute
355
400
$ this ->logger ->debug ('- remove original ' . $ attribute ->name );
@@ -359,73 +404,7 @@ private function handleAttributeBinding(DOMElement $node): void
359
404
continue ;
360
405
}
361
406
362
- $ regexArrayBinding = '/^\[([^\]]+)\]$/ ' ;
363
- $ regexArrayElements = '/((?:[ \'"])(?<elements>[^ \'"])[ \'"])/ ' ;
364
- $ regexTemplateString = '/^`(?P<content>.+)`$/ ' ;
365
- $ regexObjectBinding = '/^\{(?<elements>[^\}]+)\}$/ ' ;
366
- $ regexObjectElements = '/[" \']?(?<class>[^" \']+)[" \']?:\s*(?<condition>[^,]+)/x ' ;
367
-
368
- if ($ value === 'true ' ) {
369
- $ this ->logger ->debug ('- setAttribute ' . $ name );
370
- $ node ->setAttribute ($ name , $ name );
371
- } elseif (preg_match ($ regexArrayBinding , $ value , $ matches )) {
372
- $ this ->logger ->debug ('- array binding ' , ['value ' => $ value ]);
373
-
374
- if (preg_match_all ($ regexArrayElements , $ value , $ arrayMatch )) {
375
- $ value = $ arrayMatch ['elements ' ];
376
- $ this ->logger ->debug ('- ' , ['match ' => $ arrayMatch ]);
377
- } else {
378
- $ value = [];
379
- }
380
-
381
- if ($ name === 'style ' ) {
382
- foreach ($ value as $ prop => $ setting ) {
383
- if ($ setting ) {
384
- $ prop = strtolower ($ this ->transformCamelCaseToCSS ($ prop ));
385
- $ dynamicValues [] = sprintf ('%s:%s ' , $ prop , $ setting );
386
- }
387
- }
388
- } elseif ($ name === 'class ' ) {
389
- foreach ($ value as $ className ) {
390
- $ dynamicValues [] = $ className ;
391
- }
392
- }
393
- } elseif (preg_match ($ regexObjectBinding , $ value , $ matches )) {
394
- $ this ->logger ->debug ('- object binding ' , ['value ' => $ value ]);
395
-
396
- $ items = explode (', ' , $ matches ['elements ' ]);
397
-
398
- foreach ($ items as $ item ) {
399
- if (preg_match ($ regexObjectElements , $ item , $ matchElement )) {
400
- $ dynamicValues [] = sprintf (
401
- '{{ %s ? \'%s \' }} ' ,
402
- $ this ->builder ->refactorCondition ($ matchElement ['condition ' ]),
403
- $ matchElement ['class ' ] . ' '
404
- );
405
- }
406
- }
407
- } elseif (preg_match ($ regexTemplateString , $ value , $ matches )) {
408
- // <div :class="`abc ${someDynamicClass}`">
409
- $ templateStringContent = $ matches ['content ' ];
410
-
411
- preg_match_all ('/\${([^}]+)}/ ' , $ templateStringContent , $ matches , PREG_SET_ORDER );
412
- foreach ($ matches as $ match ) {
413
- $ templateStringContent = str_replace (
414
- $ match [0 ],
415
- '{{ ' . $ this ->builder ->refactorCondition ($ match [1 ]) . ' }} ' ,
416
- $ templateStringContent
417
- );
418
- }
419
-
420
- $ dynamicValues [] = $ templateStringContent ;
421
- } else {
422
- $ value = $ this ->builder ->refactorCondition ($ value );
423
- $ this ->logger ->debug (sprintf ('- setAttribute "%s" with value "%s" ' , $ name , $ value ));
424
- $ dynamicValues [] =
425
- Replacements::getSanitizedConstant ('DOUBLE_CURLY_OPEN ' ) .
426
- $ value .
427
- Replacements::getSanitizedConstant ('DOUBLE_CURLY_CLOSE ' );
428
- }
407
+ $ dynamicValues = $ this ->handleBinding ($ value , $ name , $ node );
429
408
430
409
/* @see https://gitlab.gnome.org/GNOME/libxml2/-/blob/LIBXML2.6.32/HTMLtree.c#L657 */
431
410
switch ($ name ) {
@@ -451,6 +430,97 @@ private function handleAttributeBinding(DOMElement $node): void
451
430
}
452
431
}
453
432
433
+ /**
434
+ * @throws ReflectionException
435
+ *
436
+ * @return string[]
437
+ */
438
+ public function handleBinding (string $ value , string $ name , ?DOMElement $ node = null , bool $ twigOutput = true ): array
439
+ {
440
+ $ dynamicValues = [];
441
+
442
+ $ regexArrayBinding = '/^\[([^\]]+)\]$/ ' ;
443
+ $ regexArrayElements = '/((?:[ \'"])(?<elements>[^ \'"])[ \'"])/ ' ;
444
+ $ regexTemplateString = '/^`(?P<content>.+)`$/ ' ;
445
+ $ regexObjectBinding = '/^\{(?<elements>[^\}]+)\}$/ ' ;
446
+ $ regexObjectElements = '/[" \']?(?<class>[^" \']+)[" \']?\s*:\s*(?<condition>[^,]+)/x ' ;
447
+
448
+ if ($ value === 'true ' ) {
449
+ $ this ->logger ->debug ('- setAttribute ' . $ name );
450
+ if ($ node ) {
451
+ $ node ->setAttribute ($ name , $ name );
452
+ }
453
+ } elseif (preg_match ($ regexArrayBinding , $ value , $ matches )) {
454
+ $ this ->logger ->debug ('- array binding ' , ['value ' => $ value ]);
455
+
456
+ if (preg_match_all ($ regexArrayElements , $ value , $ arrayMatch )) {
457
+ $ value = $ arrayMatch ['elements ' ];
458
+ $ this ->logger ->debug ('- ' , ['match ' => $ arrayMatch ]);
459
+ } else {
460
+ $ value = [];
461
+ }
462
+
463
+ if ($ name === 'style ' ) {
464
+ foreach ($ value as $ prop => $ setting ) {
465
+ if ($ setting ) {
466
+ $ prop = strtolower ($ this ->transformCamelCaseToCSS ($ prop ));
467
+ $ dynamicValues [] = sprintf ('%s:%s ' , $ prop , $ setting );
468
+ }
469
+ }
470
+ } elseif ($ name === 'class ' ) {
471
+ foreach ($ value as $ className ) {
472
+ $ dynamicValues [] = $ className ;
473
+ }
474
+ }
475
+ } elseif (preg_match ($ regexObjectBinding , $ value , $ matches )) {
476
+ $ this ->logger ->debug ('- object binding ' , ['value ' => $ value ]);
477
+
478
+ $ items = explode (', ' , $ matches ['elements ' ]);
479
+
480
+ foreach ($ items as $ item ) {
481
+ if (preg_match ($ regexObjectElements , $ item , $ matchElement )) {
482
+ $ dynamicValues [] = $ this ->prepareBindingOutput (
483
+ $ this ->builder ->refactorCondition ($ matchElement ['condition ' ]) . ' ? \'' . $ matchElement ['class ' ] . ' \'' ,
484
+ $ twigOutput
485
+ );
486
+ }
487
+ }
488
+ } elseif (preg_match ($ regexTemplateString , $ value , $ matches )) {
489
+ // <div :class="`abc ${someDynamicClass}`">
490
+ $ templateStringContent = $ matches ['content ' ];
491
+
492
+ preg_match_all ('/\${([^}]+)}/ ' , $ templateStringContent , $ matches , PREG_SET_ORDER );
493
+ foreach ($ matches as $ match ) {
494
+ $ templateStringContent = str_replace (
495
+ $ match [0 ],
496
+ $ this ->prepareBindingOutput ($ this ->builder ->refactorCondition ($ match [1 ]), $ twigOutput ),
497
+ $ templateStringContent
498
+ );
499
+ }
500
+
501
+ $ dynamicValues [] = $ templateStringContent ;
502
+ } else {
503
+ $ value = $ this ->builder ->refactorCondition ($ value );
504
+ $ this ->logger ->debug (sprintf ('- setAttribute "%s" with value "%s" ' , $ name , $ value ));
505
+ $ dynamicValues [] = $ this ->prepareBindingOutput ($ value , $ twigOutput );
506
+ }
507
+
508
+ return $ dynamicValues ;
509
+ }
510
+
511
+ private function prepareBindingOutput (string $ value , bool $ twigOutput = true ): string
512
+ {
513
+ $ open = Replacements::getSanitizedConstant ('DOUBLE_CURLY_OPEN ' );
514
+ $ close = Replacements::getSanitizedConstant ('DOUBLE_CURLY_CLOSE ' );
515
+
516
+ if (!$ twigOutput ) {
517
+ $ open = '( ' ;
518
+ $ close = ') ' ;
519
+ }
520
+
521
+ return $ open . ' ' . $ value . ' ' . $ close ;
522
+ }
523
+
454
524
/**
455
525
* @throws ReflectionException
456
526
*/
@@ -641,7 +711,10 @@ protected function addDefaultsToVariable(string $varName, string $string): strin
641
711
return $ string ;
642
712
}
643
713
644
- private function transformCamelCaseToCSS (string $ property ): string
714
+ /**
715
+ * @throws RuntimeException
716
+ */
717
+ public function transformCamelCaseToCSS (string $ property ): string
645
718
{
646
719
$ cssProperty = preg_replace ('/([A-Z])/ ' , '-$1 ' , $ property );
647
720
@@ -671,17 +744,20 @@ private function stripEventHandlers(DOMElement $node): void
671
744
*/
672
745
protected function implodeAttributeValue (string $ attribute , array $ values , string $ oldValue ): string
673
746
{
674
- $ glue = ' ' ;
675
-
676
747
if ($ attribute === 'style ' ) {
677
- $ glue = '; ' ;
748
+ if (!empty ($ oldValue )) {
749
+ $ oldValue = trim ($ oldValue , '; ' ) . '; ' ;
750
+ }
751
+ foreach ($ values as &$ value ) {
752
+ $ value = trim ($ value , '; ' ) . '; ' ;
753
+ }
678
754
}
679
755
680
756
if (!empty ($ oldValue )) {
681
757
$ values = array_merge ([$ oldValue ], $ values );
682
758
}
683
759
684
- return implode ($ glue , $ values );
760
+ return trim ( implode (' ' , $ values) );
685
761
}
686
762
687
763
/**
@@ -735,7 +811,7 @@ public function refactorTemplateString(string $value): string
735
811
$ templateStringContent = '" ' . $ matches ['content ' ] . '" ' ;
736
812
$ value = preg_replace (
737
813
'/\${(.+)}/ ' ,
738
- '{{ $1 }} ' ,
814
+ '" ~ ( $1 ) ~ " ' ,
739
815
$ templateStringContent
740
816
);
741
817
}
@@ -786,6 +862,15 @@ public function setStripWhitespace(bool $stripWhitespace): Compiler
786
862
return $ this ;
787
863
}
788
864
865
+ public function disableStyleInclude (): Compiler
866
+ {
867
+ if (($ key = array_search ('style ' , $ this ->includeAttributes )) !== false ) {
868
+ unset($ this ->includeAttributes [$ key ]);
869
+ }
870
+
871
+ return $ this ;
872
+ }
873
+
789
874
/**
790
875
* @param mixed $value
791
876
*/
@@ -859,16 +944,19 @@ protected function insertDefaultValues(): void
859
944
}
860
945
}
861
946
862
- protected function handleRootNodeClassAttribute (DOMElement $ node ): DOMElement
947
+ protected function handleRootNodeAttribute (DOMElement $ node, ? string $ name = null ): DOMElement
863
948
{
864
- $ classString = "__DOUBLE_CURLY_OPEN__class__PIPE__default('')__DOUBLE_CURLY_CLOSE__ " ;
865
- if ($ node ->hasAttribute ('class ' )) {
866
- $ attributeVClass = $ node ->getAttributeNode ('class ' );
867
- $ attributeVClass ->value .= ' ' . $ classString ;
949
+ if (!$ name ) {
950
+ return $ node ;
951
+ }
952
+ $ string = $ this ->prepareBindingOutput ($ name . '|default( \'\') ' );
953
+ if ($ node ->hasAttribute ($ name )) {
954
+ $ attribute = $ node ->getAttributeNode ($ name );
955
+ $ attribute ->value .= ' ' . $ string ;
868
956
} else {
869
- $ attributeVClass = new DOMAttr (' class ' , $ classString );
957
+ $ attribute = new DOMAttr ($ name , $ string );
870
958
}
871
- $ node ->setAttributeNode ($ attributeVClass );
959
+ $ node ->setAttributeNode ($ attribute );
872
960
873
961
return $ node ;
874
962
}
@@ -880,4 +968,11 @@ private function handleCommentNode(DOMComment $node): void
880
968
$ node ->parentNode ->removeChild ($ node );
881
969
}
882
970
}
971
+
972
+ private function twigRemove (DOMElement $ node ): void
973
+ {
974
+ if ($ node ->hasAttribute ('data-twig-remove ' )) {
975
+ $ node ->parentNode ->removeChild ($ node );
976
+ }
977
+ }
883
978
}
0 commit comments