@@ -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 */
@@ -238,7 +243,7 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
238243 $ include = $ this ->document ->createTextNode (
239244 $ this ->builder ->createIncludePartial (
240245 $ usedComponent ->getPath (),
241- $ usedComponent ->getProperties ()
246+ $ this -> preparePropertiesForInclude ( $ usedComponent ->getProperties () )
242247 )
243248 );
244249
@@ -273,7 +278,9 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
273278 if ($ node instanceof DOMElement) {
274279 $ this ->handleAttributeBinding ($ node );
275280 if ($ level === 1 ) {
276- $ this ->handleRootNodeClassAttribute ($ node );
281+ foreach ($ this ->includeAttributes as $ attribute ) {
282+ $ this ->handleRootNodeAttribute ($ node , $ attribute );
283+ }
277284 }
278285 }
279286
@@ -284,6 +291,44 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
284291 return $ node ;
285292 }
286293
294+ /**
295+ * @param Property[] $variables
296+ *
297+ * @throws ReflectionException
298+ *
299+ * @return Property[]
300+ */
301+ private function preparePropertiesForInclude (array $ variables ): array
302+ {
303+ $ values = [];
304+ foreach ($ variables as $ key => $ variable ) {
305+ $ name = $ variable ->getName ();
306+ $ value = $ variable ->getValue ();
307+ if (in_array ($ name , $ this ->includeAttributes )) {
308+ if ($ variable ->isBinding ()) {
309+ $ values [$ name ][] = $ this ->handleBinding ($ value , $ name , null , false )[0 ];
310+ } else {
311+ $ values [$ name ][] = $ value ;
312+ }
313+ unset($ variables [$ key ]);
314+ }
315+ }
316+
317+ foreach ($ this ->includeAttributes as $ attribute ) {
318+ $ glue = ' ~ " " ~ ' ;
319+ if ($ attribute === 'style ' ) {
320+ $ glue = ' ~ "; " ~ ' ;
321+ }
322+ $ variables [] = new Property (
323+ $ attribute ,
324+ $ values [$ attribute ] ?? null ? implode ($ glue , $ values [$ attribute ]) : '"" ' ,
325+ false
326+ );
327+ }
328+
329+ return $ variables ;
330+ }
331+
287332 public function registerProperties (DOMElement $ scriptElement ): void
288333 {
289334 $ content = $ this ->innerHtmlOfNode ($ scriptElement );
@@ -349,7 +394,6 @@ private function handleAttributeBinding(DOMElement $node): void
349394 $ this ->logger ->debug ('- handle: ' . $ name . ' = ' . $ value );
350395
351396 $ staticValues = $ node ->hasAttribute ($ name ) ? $ node ->getAttribute ($ name ) : '' ;
352- $ dynamicValues = [];
353397
354398 // Remove originally bound attribute
355399 $ this ->logger ->debug ('- remove original ' . $ attribute ->name );
@@ -359,73 +403,7 @@ private function handleAttributeBinding(DOMElement $node): void
359403 continue ;
360404 }
361405
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- }
406+ $ dynamicValues = $ this ->handleBinding ($ value , $ name , $ node );
429407
430408 /* @see https://gitlab.gnome.org/GNOME/libxml2/-/blob/LIBXML2.6.32/HTMLtree.c#L657 */
431409 switch ($ name ) {
@@ -451,6 +429,97 @@ private function handleAttributeBinding(DOMElement $node): void
451429 }
452430 }
453431
432+ /**
433+ * @throws ReflectionException
434+ *
435+ * @return string[]
436+ */
437+ public function handleBinding (string $ value , string $ name , ?DOMElement $ node = null , bool $ twigOutput = true ): array
438+ {
439+ $ dynamicValues = [];
440+
441+ $ regexArrayBinding = '/^\[([^\]]+)\]$/ ' ;
442+ $ regexArrayElements = '/((?:[ \'"])(?<elements>[^ \'"])[ \'"])/ ' ;
443+ $ regexTemplateString = '/^`(?P<content>.+)`$/ ' ;
444+ $ regexObjectBinding = '/^\{(?<elements>[^\}]+)\}$/ ' ;
445+ $ regexObjectElements = '/[" \']?(?<class>[^" \']+)[" \']?\s*:\s*(?<condition>[^,]+)/x ' ;
446+
447+ if ($ value === 'true ' ) {
448+ $ this ->logger ->debug ('- setAttribute ' . $ name );
449+ if ($ node ) {
450+ $ node ->setAttribute ($ name , $ name );
451+ }
452+ } elseif (preg_match ($ regexArrayBinding , $ value , $ matches )) {
453+ $ this ->logger ->debug ('- array binding ' , ['value ' => $ value ]);
454+
455+ if (preg_match_all ($ regexArrayElements , $ value , $ arrayMatch )) {
456+ $ value = $ arrayMatch ['elements ' ];
457+ $ this ->logger ->debug ('- ' , ['match ' => $ arrayMatch ]);
458+ } else {
459+ $ value = [];
460+ }
461+
462+ if ($ name === 'style ' ) {
463+ foreach ($ value as $ prop => $ setting ) {
464+ if ($ setting ) {
465+ $ prop = strtolower ($ this ->transformCamelCaseToCSS ($ prop ));
466+ $ dynamicValues [] = sprintf ('%s:%s ' , $ prop , $ setting );
467+ }
468+ }
469+ } elseif ($ name === 'class ' ) {
470+ foreach ($ value as $ className ) {
471+ $ dynamicValues [] = $ className ;
472+ }
473+ }
474+ } elseif (preg_match ($ regexObjectBinding , $ value , $ matches )) {
475+ $ this ->logger ->debug ('- object binding ' , ['value ' => $ value ]);
476+
477+ $ items = explode (', ' , $ matches ['elements ' ]);
478+
479+ foreach ($ items as $ item ) {
480+ if (preg_match ($ regexObjectElements , $ item , $ matchElement )) {
481+ $ dynamicValues [] = $ this ->prepareBindingOutput (
482+ $ this ->builder ->refactorCondition ($ matchElement ['condition ' ]) . ' ? \'' . $ matchElement ['class ' ] . ' \'' ,
483+ $ twigOutput
484+ );
485+ }
486+ }
487+ } elseif (preg_match ($ regexTemplateString , $ value , $ matches )) {
488+ // <div :class="`abc ${someDynamicClass}`">
489+ $ templateStringContent = $ matches ['content ' ];
490+
491+ preg_match_all ('/\${([^}]+)}/ ' , $ templateStringContent , $ matches , PREG_SET_ORDER );
492+ foreach ($ matches as $ match ) {
493+ $ templateStringContent = str_replace (
494+ $ match [0 ],
495+ $ this ->prepareBindingOutput ($ this ->builder ->refactorCondition ($ match [1 ]), $ twigOutput ),
496+ $ templateStringContent
497+ );
498+ }
499+
500+ $ dynamicValues [] = $ templateStringContent ;
501+ } else {
502+ $ value = $ this ->builder ->refactorCondition ($ value );
503+ $ this ->logger ->debug (sprintf ('- setAttribute "%s" with value "%s" ' , $ name , $ value ));
504+ $ dynamicValues [] = $ this ->prepareBindingOutput ($ value , $ twigOutput );
505+ }
506+
507+ return $ dynamicValues ;
508+ }
509+
510+ private function prepareBindingOutput (string $ value , bool $ twigOutput = true ): string
511+ {
512+ $ open = Replacements::getSanitizedConstant ('DOUBLE_CURLY_OPEN ' );
513+ $ close = Replacements::getSanitizedConstant ('DOUBLE_CURLY_CLOSE ' );
514+
515+ if (!$ twigOutput ) {
516+ $ open = '( ' ;
517+ $ close = ') ' ;
518+ }
519+
520+ return $ open . ' ' . $ value . ' ' . $ close ;
521+ }
522+
454523 /**
455524 * @throws ReflectionException
456525 */
@@ -641,7 +710,10 @@ protected function addDefaultsToVariable(string $varName, string $string): strin
641710 return $ string ;
642711 }
643712
644- private function transformCamelCaseToCSS (string $ property ): string
713+ /**
714+ * @throws RuntimeException
715+ */
716+ public function transformCamelCaseToCSS (string $ property ): string
645717 {
646718 $ cssProperty = preg_replace ('/([A-Z])/ ' , '-$1 ' , $ property );
647719
@@ -671,17 +743,20 @@ private function stripEventHandlers(DOMElement $node): void
671743 */
672744 protected function implodeAttributeValue (string $ attribute , array $ values , string $ oldValue ): string
673745 {
674- $ glue = ' ' ;
675-
676746 if ($ attribute === 'style ' ) {
677- $ glue = '; ' ;
747+ if (!empty ($ oldValue )) {
748+ $ oldValue = trim ($ oldValue , '; ' ) . '; ' ;
749+ }
750+ foreach ($ values as &$ value ) {
751+ $ value = trim ($ value , '; ' ) . '; ' ;
752+ }
678753 }
679754
680755 if (!empty ($ oldValue )) {
681756 $ values = array_merge ([$ oldValue ], $ values );
682757 }
683758
684- return implode ($ glue , $ values );
759+ return trim ( implode (' ' , $ values) );
685760 }
686761
687762 /**
@@ -735,7 +810,7 @@ public function refactorTemplateString(string $value): string
735810 $ templateStringContent = '" ' . $ matches ['content ' ] . '" ' ;
736811 $ value = preg_replace (
737812 '/\${(.+)}/ ' ,
738- '{{ $1 }} ' ,
813+ '" ~ ( $1 ) ~ " ' ,
739814 $ templateStringContent
740815 );
741816 }
@@ -786,6 +861,15 @@ public function setStripWhitespace(bool $stripWhitespace): Compiler
786861 return $ this ;
787862 }
788863
864+ public function disableStyleInclude (): Compiler
865+ {
866+ if (($ key = array_search ('style ' , $ this ->includeAttributes )) !== false ) {
867+ unset($ this ->includeAttributes [$ key ]);
868+ }
869+
870+ return $ this ;
871+ }
872+
789873 /**
790874 * @param mixed $value
791875 */
@@ -859,16 +943,19 @@ protected function insertDefaultValues(): void
859943 }
860944 }
861945
862- protected function handleRootNodeClassAttribute (DOMElement $ node ): DOMElement
946+ protected function handleRootNodeAttribute (DOMElement $ node, ? string $ name = null ): DOMElement
863947 {
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 ;
948+ if (!$ name ) {
949+ return $ node ;
950+ }
951+ $ string = $ this ->prepareBindingOutput ($ name . '|default( \'\') ' );
952+ if ($ node ->hasAttribute ($ name )) {
953+ $ attribute = $ node ->getAttributeNode ($ name );
954+ $ attribute ->value .= ' ' . $ string ;
868955 } else {
869- $ attributeVClass = new DOMAttr (' class ' , $ classString );
956+ $ attribute = new DOMAttr ($ name , $ string );
870957 }
871- $ node ->setAttributeNode ($ attributeVClass );
958+ $ node ->setAttributeNode ($ attribute );
872959
873960 return $ node ;
874961 }
0 commit comments