Skip to content

Commit f0b3427

Browse files
authored
Merge pull request #46 from tronsha/bugfix/attribute-handling
Fix attribute handling
2 parents a5e1c54 + b656b53 commit f0b3427

File tree

7 files changed

+183
-3
lines changed

7 files changed

+183
-3
lines changed

src/Compiler.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ private function handleAttributeBinding(DOMElement $node)
395395

396396
$dynamicValues[] = $templateStringContent;
397397
} else {
398+
$value = $this->builder->refactorCondition($value);
398399
$this->logger->debug(sprintf('- setAttribute "%s" with value "%s"', $name, $value));
399400
$dynamicValues[] =
400401
Replacements::getSanitizedConstant('DOUBLE_CURLY_OPEN') .
@@ -605,12 +606,16 @@ private function transformCamelCaseToCSS(string $property): string
605606

606607
private function stripEventHandlers(DOMElement $node)
607608
{
609+
$removeAttributes = [];
608610
/** @var DOMAttr $attribute */
609611
foreach ($node->attributes as $attribute) {
610612
if (strpos($attribute->name, 'v-on:') === 0 || strpos($attribute->name, '@') === 0) {
611-
$node->removeAttribute($attribute->name);
613+
$removeAttributes[] = $attribute->name;
612614
}
613615
}
616+
foreach ($removeAttributes as $removeAttribute) {
617+
$node->removeAttribute($removeAttribute);
618+
}
614619
}
615620

616621
protected function implodeAttributeValue(string $attribute, array $values, string $oldValue): string

src/Utils/TwigBuilder.php

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,41 @@ public function sanitizeAttributeValue(string $value): string
171171

172172
public function refactorCondition(string $condition): string
173173
{
174+
$refactoredCondition = '';
175+
$charsCount = mb_strlen($condition, 'UTF-8');
176+
$quoteChar = null;
177+
$lastChar = null;
178+
$buffer = '';
179+
180+
for ($i = 0; $i < $charsCount; $i++) {
181+
$char = mb_substr($condition, $i, 1, 'UTF-8');
182+
if ($quoteChar === null && ($char === '"' || $char === '\'')) {
183+
$quoteChar = $char;
184+
if ($buffer !== '') {
185+
$refactoredCondition .= $this->refactorConditionPart($buffer);
186+
$buffer = '';
187+
}
188+
$refactoredCondition .= $char;
189+
} elseif ($quoteChar === $char && $lastChar !== '\\') {
190+
$quoteChar = null;
191+
$refactoredCondition .= $char;
192+
} else {
193+
if ($quoteChar === null) {
194+
$buffer .= $char;
195+
} else {
196+
$refactoredCondition .= $char;
197+
}
198+
}
199+
$lastChar = $char;
200+
}
201+
if ($buffer !== '') {
202+
$refactoredCondition .= $this->refactorConditionPart($buffer);
203+
}
204+
205+
return $refactoredCondition;
206+
}
207+
208+
private function refactorConditionPart($condition) {
174209
$condition = str_replace('===', '==', $condition);
175210
$condition = str_replace('!==', '!=', $condition);
176211
$condition = str_replace('&&', 'and', $condition);
@@ -179,6 +214,8 @@ public function refactorCondition(string $condition): string
179214
$condition = str_replace('.length', '|length', $condition);
180215
$condition = str_replace('.trim', '|trim', $condition);
181216

217+
// $condition = $this->convertConcat($condition);
218+
182219
foreach (Replacements::getConstants() as $constant => $value) {
183220
$condition = str_replace($value, Replacements::getSanitizedConstant($constant), $condition);
184221
}
@@ -188,9 +225,74 @@ public function refactorCondition(string $condition): string
188225

189226
public function refactorTextNode(string $content): string
190227
{
191-
$content = str_replace('.length', '|length', $content);
192-
$content = str_replace('.trim', '|trim', $content);
228+
$refactoredContent = '';
229+
$charsCount = mb_strlen($content, 'UTF-8');
230+
$open = false;
231+
$lastChar = null;
232+
$quoteChar = null;
233+
$buffer = '';
234+
235+
for ($i = 0; $i < $charsCount; $i++) {
236+
$char = mb_substr($content, $i, 1, 'UTF-8');
237+
if ($open === false) {
238+
$refactoredContent .= $char;
239+
if ($char === '{' && $lastChar === '{') {
240+
$open = true;
241+
}
242+
} else {
243+
$buffer .= $char;
244+
if ($quoteChar === null && ($char === '"' || $char === '\'')) {
245+
$quoteChar = $char;
246+
} elseif ($quoteChar === $char && $lastChar !== '\\') {
247+
$quoteChar = null;
248+
}
249+
if ($quoteChar === null && $char === '}' && $lastChar === '}') {
250+
$open = false;
251+
$buffer = $this->convertTemplateString(trim($buffer, '}'));
252+
$refactoredContent .= $this->refactorCondition($buffer) . '}}';
253+
$buffer = '';
254+
}
255+
}
256+
$lastChar = $char;
257+
}
193258

259+
return $refactoredContent;
260+
}
261+
262+
private function convertConcat($content) {
263+
if (preg_match_all('/(\S*)(\s*\+\s*(\S+))+/', $content, $matches, PREG_SET_ORDER )) {
264+
foreach ($matches as $match) {
265+
$parts = explode('+', $match[0]);
266+
$lastPart = null;
267+
$convertedContent = '';
268+
foreach ($parts as $part) {
269+
$part = trim($part);
270+
if ($lastPart !== null) {
271+
if (is_numeric($lastPart) && is_numeric($part)) {
272+
$convertedContent .= ' + ' . $part;
273+
} else {
274+
$convertedContent .= ' ~ ' . $part;
275+
}
276+
} else {
277+
$convertedContent = $part;
278+
}
279+
$lastPart = $part;
280+
}
281+
$content = str_replace($match[0], $convertedContent, $content);
282+
}
283+
}
284+
285+
return $content;
286+
}
287+
288+
private function convertTemplateString($content) {
289+
if (preg_match_all('/\`([^\`]+)\`/', $content, $matches, PREG_SET_ORDER )) {
290+
foreach ($matches as $match) {
291+
$match[1] = str_replace('${', '\' ~ ', $match[1]);
292+
$match[1] = str_replace('}', ' ~ \'', $match[1]);
293+
$content = str_replace($match[0], '\'' . $match[1] . '\'', $content);
294+
}
295+
}
194296
return $content;
195297
}
196298

tests/TextNodeTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Paneon\VueToTwig\Tests;
4+
5+
class TextNodeTest extends AbstractTestCase
6+
{
7+
public function testTextNode()
8+
{
9+
$html = '<template><div>foo {{ bar.trim }}</div></template>';
10+
$expected = '<div class="{{class|default(\'\')}}">foo {{ bar|trim }}</div>';
11+
12+
$compiler = $this->createCompiler($html);
13+
14+
$actual = $compiler->convert();
15+
16+
$this->assertEqualHtml($expected, $actual);
17+
}
18+
19+
public function testTextNodeNoReplace()
20+
{
21+
$html = '<template><div>foo.trim {{ \'foo === bar\' }}</div></template>';
22+
$expected = '<div class="{{class|default(\'\')}}">foo.trim {{ \'foo === bar\' }}</div>';
23+
24+
$compiler = $this->createCompiler($html);
25+
26+
$actual = $compiler->convert();
27+
28+
$this->assertEqualHtml($expected, $actual);
29+
}
30+
31+
public function testTextNodeDontCloseInQuote()
32+
{
33+
$html = '<template><div>{{ \'}}\' || foo.length }}</div></template>';
34+
$expected = '<div class="{{class|default(\'\')}}">{{ \'}}\' or foo|length }}</div>';
35+
36+
$compiler = $this->createCompiler($html);
37+
38+
$actual = $compiler->convert();
39+
40+
$this->assertEqualHtml($expected, $actual);
41+
}
42+
43+
public function testTextNodeWithTemplateString()
44+
{
45+
$html = '<template><div>{{ `Var = ${var}` }}</div></template>';
46+
$expected = '<div class="{{class|default(\'\')}}">{{ \'Var = \' ~ var ~ \'\' }}</div>';
47+
48+
$compiler = $this->createCompiler($html);
49+
50+
$actual = $compiler->convert();
51+
52+
$this->assertEqualHtml($expected, $actual);
53+
}
54+
55+
56+
public function testTextNodeNumbers()
57+
{
58+
$html = '<template><div>{{ 1 + 1 }}</div></template>';
59+
$expected = '<div class="{{class|default(\'\')}}">{{ 1 + 1 }}</div>';
60+
61+
$compiler = $this->createCompiler($html);
62+
63+
$actual = $compiler->convert();
64+
65+
$this->assertEqualHtml($expected, $actual);
66+
}
67+
}

tests/fixtures/vue-bind/binding-with-template-string.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
Hello World
44
</div>
55
<div class="{{ isTrue ? 'a' : 'b' }}"></div>
6+
<div style="{{ isTrue ? 'display: block;' : 'display: none !important;' }}"></div>
67
</div>

tests/fixtures/vue-bind/binding-with-template-string.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Hello World
55
</div>
66
<div :class="`${isTrue ? 'a' : 'b'}`"></div>
7+
<div :style="`${isTrue ? 'display: block;' : 'display: none !important;'}`"></div>
78
</div>
89
</template>
910

tests/fixtures/vue-bind/bindings.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
<img src="{{imageSrc}}">
66
<div class="a b c"></div>
77
<div title="Title" class="category-filter-list categories {{ isSomething ? 'block ' }} {{ not isSomething ? 'block2 ' }}"></div>
8+
<div class="{{getClasses(not hasSomething)}}"></div>
9+
<div style="{{'display: none !important'}}">Hidden</div>
810
</div>
911
</div>

tests/fixtures/vue-bind/bindings.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<img :src="imageSrc">
77
<div :class="['a', 'b', 'c']"></div>
88
<div title="Title" :class="{ 'block': isSomething, 'block2': !isSomething}" class="category-filter-list categories"></div>
9+
<div :class="getClasses(!hasSomething)" ></div>
10+
<div :style="'display: none !important'">Hidden</div>
911
</div>
1012
</div>
1113
</template>

0 commit comments

Comments
 (0)