Skip to content

Commit f0344c1

Browse files
authored
Merge pull request #23 from Paneon/master
Update master
2 parents 020603b + a0878f2 commit f0344c1

Some content is hidden

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

44 files changed

+291
-165
lines changed

src/Compiler.php

Lines changed: 178 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
*/
@@ -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
}

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)