@@ -77,6 +77,11 @@ class Compiler
7777 */
7878 protected $ rawBlocks = [];
7979
80+ /**
81+ * @var string[]
82+ */
83+ protected $ includeAttributes = ['class ' , 'style ' ];
84+
8085 /**
8186 * Compiler constructor.
8287 */
@@ -178,6 +183,7 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
178183 } elseif ($ node instanceof DOMDocument) {
179184 $ this ->logger ->warning ('Document node found. ' );
180185 } elseif ($ node instanceof DOMElement) {
186+ $ this ->twigRemove ($ node );
181187 $ this ->replaceShowWithIf ($ node );
182188 $ this ->handleIf ($ node , $ level );
183189 $ this ->handleFor ($ node );
@@ -238,7 +244,7 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
238244 $ include = $ this ->document ->createTextNode (
239245 $ this ->builder ->createIncludePartial (
240246 $ usedComponent ->getPath (),
241- $ usedComponent ->getProperties ()
247+ $ this -> preparePropertiesForInclude ( $ usedComponent ->getProperties () )
242248 )
243249 );
244250
@@ -273,7 +279,9 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
273279 if ($ node instanceof DOMElement) {
274280 $ this ->handleAttributeBinding ($ node );
275281 if ($ level === 1 ) {
276- $ this ->handleRootNodeClassAttribute ($ node );
282+ foreach ($ this ->includeAttributes as $ attribute ) {
283+ $ this ->handleRootNodeAttribute ($ node , $ attribute );
284+ }
277285 }
278286 }
279287
@@ -284,6 +292,44 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
284292 return $ node ;
285293 }
286294
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+
287333 public function registerProperties (DOMElement $ scriptElement ): void
288334 {
289335 $ content = $ this ->innerHtmlOfNode ($ scriptElement );
@@ -349,7 +395,6 @@ private function handleAttributeBinding(DOMElement $node): void
349395 $ this ->logger ->debug ('- handle: ' . $ name . ' = ' . $ value );
350396
351397 $ staticValues = $ node ->hasAttribute ($ name ) ? $ node ->getAttribute ($ name ) : '' ;
352- $ dynamicValues = [];
353398
354399 // Remove originally bound attribute
355400 $ this ->logger ->debug ('- remove original ' . $ attribute ->name );
@@ -359,73 +404,7 @@ private function handleAttributeBinding(DOMElement $node): void
359404 continue ;
360405 }
361406
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 );
429408
430409 /* @see https://gitlab.gnome.org/GNOME/libxml2/-/blob/LIBXML2.6.32/HTMLtree.c#L657 */
431410 switch ($ name ) {
@@ -451,6 +430,97 @@ private function handleAttributeBinding(DOMElement $node): void
451430 }
452431 }
453432
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+
454524 /**
455525 * @throws ReflectionException
456526 */
@@ -641,7 +711,10 @@ protected function addDefaultsToVariable(string $varName, string $string): strin
641711 return $ string ;
642712 }
643713
644- private function transformCamelCaseToCSS (string $ property ): string
714+ /**
715+ * @throws RuntimeException
716+ */
717+ public function transformCamelCaseToCSS (string $ property ): string
645718 {
646719 $ cssProperty = preg_replace ('/([A-Z])/ ' , '-$1 ' , $ property );
647720
@@ -671,17 +744,20 @@ private function stripEventHandlers(DOMElement $node): void
671744 */
672745 protected function implodeAttributeValue (string $ attribute , array $ values , string $ oldValue ): string
673746 {
674- $ glue = ' ' ;
675-
676747 if ($ attribute === 'style ' ) {
677- $ glue = '; ' ;
748+ if (!empty ($ oldValue )) {
749+ $ oldValue = trim ($ oldValue , '; ' ) . '; ' ;
750+ }
751+ foreach ($ values as &$ value ) {
752+ $ value = trim ($ value , '; ' ) . '; ' ;
753+ }
678754 }
679755
680756 if (!empty ($ oldValue )) {
681757 $ values = array_merge ([$ oldValue ], $ values );
682758 }
683759
684- return implode ($ glue , $ values );
760+ return trim ( implode (' ' , $ values) );
685761 }
686762
687763 /**
@@ -735,7 +811,7 @@ public function refactorTemplateString(string $value): string
735811 $ templateStringContent = '" ' . $ matches ['content ' ] . '" ' ;
736812 $ value = preg_replace (
737813 '/\${(.+)}/ ' ,
738- '{{ $1 }} ' ,
814+ '" ~ ( $1 ) ~ " ' ,
739815 $ templateStringContent
740816 );
741817 }
@@ -786,6 +862,15 @@ public function setStripWhitespace(bool $stripWhitespace): Compiler
786862 return $ this ;
787863 }
788864
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+
789874 /**
790875 * @param mixed $value
791876 */
@@ -859,16 +944,19 @@ protected function insertDefaultValues(): void
859944 }
860945 }
861946
862- protected function handleRootNodeClassAttribute (DOMElement $ node ): DOMElement
947+ protected function handleRootNodeAttribute (DOMElement $ node, ? string $ name = null ): DOMElement
863948 {
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 ;
868956 } else {
869- $ attributeVClass = new DOMAttr (' class ' , $ classString );
957+ $ attribute = new DOMAttr ($ name , $ string );
870958 }
871- $ node ->setAttributeNode ($ attributeVClass );
959+ $ node ->setAttributeNode ($ attribute );
872960
873961 return $ node ;
874962 }
@@ -880,4 +968,11 @@ private function handleCommentNode(DOMComment $node): void
880968 $ node ->parentNode ->removeChild ($ node );
881969 }
882970 }
971+
972+ private function twigRemove (DOMElement $ node ): void
973+ {
974+ if ($ node ->hasAttribute ('data-twig-remove ' )) {
975+ $ node ->parentNode ->removeChild ($ node );
976+ }
977+ }
883978}
0 commit comments