Skip to content

Commit 9026eb9

Browse files
authored
Merge pull request #58 from tronsha/feature/include-style
Fix #48 Fix #49 Add include style
2 parents 09625d1 + 3ddb7a7 commit 9026eb9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+259
-165
lines changed

src/Compiler.php

Lines changed: 170 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/Utils/TwigBuilder.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,6 @@ public function createBlock(string $content): string
148148
*/
149149
public function createIncludePartial(string $partialPath, array $variables = []): string
150150
{
151-
$hasClassProperty = false;
152-
foreach ($variables as $variable) {
153-
if ($variable->getName() === 'class') {
154-
$hasClassProperty = true;
155-
}
156-
}
157-
158-
if (!$hasClassProperty) {
159-
$variables[] = new Property('class', '""', false);
160-
}
161-
162151
$serializedProperties = $this->serializeComponentProperties($variables);
163152

164153
return $this->createBlock('include "' . $partialPath . '" with ' . $serializedProperties);

tests/CleanupAttributesTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function testCleanupAttributes()
1313
{
1414
$vueTemplate = '<template><div v-foo="bar" ref="reference">dummy</div></template>';
1515

16-
$expected = '<div class="{{class|default(\'\')}}">dummy</div>';
16+
$expected = '<div class="{{ class|default(\'\') }}" style="{{ style|default(\'\') }}">dummy</div>';
1717

1818
$compiler = $this->createCompiler($vueTemplate);
1919

0 commit comments

Comments
 (0)