Skip to content

Commit afd3ba4

Browse files
authored
Merge pull request #33 from Paneon/master
Update master
2 parents 1fa7200 + 59888b6 commit afd3ba4

19 files changed

+403
-3
lines changed

src/Compiler.php

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use DOMText;
1313
use Exception;
1414
use Paneon\VueToTwig\Models\Component;
15+
use Paneon\VueToTwig\Models\Data;
1516
use Paneon\VueToTwig\Models\Pre;
1617
use Paneon\VueToTwig\Models\Property;
1718
use Paneon\VueToTwig\Models\Replacements;
@@ -75,6 +76,11 @@ class Compiler
7576
*/
7677
protected $properties;
7778

79+
/**
80+
* @var Data[]|null
81+
*/
82+
protected $data = null;
83+
7884
/**
7985
* @var Pre[]
8086
*/
@@ -171,6 +177,10 @@ public function convert(): string
171177
if ($scriptElement) {
172178
$this->registerProperties($scriptElement);
173179
$this->insertDefaultValues();
180+
if ($this->data !== null) {
181+
$this->registerData($scriptElement);
182+
$this->insertData();
183+
}
174184
}
175185

176186
if ($twigBlocks->length) {
@@ -246,6 +256,9 @@ private function handleTwigConfig(string $twigConfig): void
246256
$attributes = array_map(function ($item) { return trim($item); }, $attributes);
247257
$this->attributesWithIf = array_merge($this->attributesWithIf, $attributes);
248258
}
259+
if ($config['disable-data-support'] ?? false) {
260+
$this->data = null;
261+
}
249262
}
250263

251264
/**
@@ -484,6 +497,74 @@ public function registerProperties(DOMElement $scriptElement): void
484497
}
485498
}
486499

500+
public function registerData(DOMElement $scriptElement): void
501+
{
502+
$content = $this->innerHtmlOfNode($scriptElement);
503+
if ($scriptElement->hasAttribute('lang') && $scriptElement->getAttribute('lang') === 'ts') {
504+
// TypeScript
505+
preg_match_all('/private\s+(\S+)\s*=\s*(.+?);\ *\n/msi', $content, $matches, PREG_SET_ORDER);
506+
foreach ($matches as $match) {
507+
$this->data[] = new Data(
508+
trim($match[1]),
509+
trim($this->builder->refactorCondition(str_replace('this.', '', $match[2])))
510+
);
511+
}
512+
} else {
513+
// JavaScript
514+
if (preg_match('/data\(\)\s*{\s*return\s*{(.+?)\s*}\s*;\s*}\s*,/msi', $content, $match)) {
515+
$dataString = $match[1];
516+
$charsCount = mb_strlen($dataString, 'UTF-8');
517+
$dataArray = [];
518+
$dataCount = 0;
519+
$dataArray[$dataCount] = '';
520+
$bracketOpenCount = 0;
521+
$quoteChar = null;
522+
$lastChar = null;
523+
$commentOpen = false;
524+
for ($i = 0; $i < $charsCount; ++$i) {
525+
$char = mb_substr($dataString, $i, 1, 'UTF-8');
526+
$nextChar = mb_substr($dataString, $i + 1, 1, 'UTF-8');
527+
if ($char === '*' && $nextChar === '/') {
528+
++$i;
529+
$commentOpen = false;
530+
continue;
531+
}
532+
if (($char === '/' && $nextChar === '*') || $commentOpen) {
533+
$commentOpen = true;
534+
continue;
535+
}
536+
if ($quoteChar === null && ($char === '"' || $char === '\'')) {
537+
$quoteChar = $char;
538+
} elseif ($quoteChar === $char && $lastChar !== '\\') {
539+
$quoteChar = null;
540+
}
541+
if (($char === '[' || $char === '{') && $quoteChar === null) {
542+
++$bracketOpenCount;
543+
$dataArray[$dataCount] .= $char;
544+
} elseif (($char === ']' || $char === '}') && $quoteChar === null) {
545+
--$bracketOpenCount;
546+
$dataArray[$dataCount] .= $char;
547+
} elseif ($char === ',' && $bracketOpenCount === 0 && $quoteChar === null) {
548+
++$dataCount;
549+
$dataArray[$dataCount] = '';
550+
} else {
551+
$dataArray[$dataCount] .= $char;
552+
}
553+
$lastChar = $char;
554+
}
555+
foreach ($dataArray as $data) {
556+
if (substr_count($data, ':')) {
557+
[$name, $value] = explode(':', $data, 2);
558+
$this->data[] = new Data(
559+
trim($name),
560+
trim($this->builder->refactorCondition(str_replace('this.', '', $value)))
561+
);
562+
}
563+
}
564+
}
565+
}
566+
}
567+
487568
/**
488569
* @throws Exception
489570
*/
@@ -675,6 +756,12 @@ public function handleBinding(string $value, string $name, ?DOMElement $node = n
675756
} else {
676757
$value = $this->builder->refactorCondition($value);
677758
$this->logger->debug(sprintf('- setAttribute "%s" with value "%s"', $name, $value));
759+
if (substr_count($value, '`')) {
760+
preg_match_all('/`[^`]+`/', $value, $matches);
761+
foreach ($matches as $match) {
762+
$value = str_replace($match[0], $this->refactorTemplateString($match[0]), $value);
763+
}
764+
}
678765
$dynamicValues[] = $this->builder->prepareBindingOutput($value, $twigOutput);
679766
}
680767

@@ -1080,7 +1167,7 @@ public function refactorTemplateString(string $value): string
10801167
if (preg_match('/^`(?P<content>.+)`$/', $value, $matches)) {
10811168
$templateStringContent = '"' . $matches['content'] . '"';
10821169
$value = preg_replace(
1083-
'/\${(.+)}/',
1170+
'/\${([^{}]+)}/',
10841171
'" ~ ( $1 ) ~ "',
10851172
$templateStringContent
10861173
);
@@ -1141,6 +1228,13 @@ public function disableStyleInclude(): Compiler
11411228
return $this;
11421229
}
11431230

1231+
public function enableDataSupport(): Compiler
1232+
{
1233+
$this->data = [];
1234+
1235+
return $this;
1236+
}
1237+
11441238
public function setStyleBlockOutputType(int $outputType): Compiler
11451239
{
11461240
$this->styleBuilder->setOutputType($outputType);
@@ -1268,6 +1362,13 @@ protected function insertDefaultValues(): void
12681362
}
12691363
}
12701364

1365+
protected function insertData(): void
1366+
{
1367+
foreach ($this->data as $data) {
1368+
$this->rawBlocks[] = '{% set ' . $data->getName() . ' = ' . $data->getValue() . ' %}';
1369+
}
1370+
}
1371+
12711372
protected function handleRootNodeAttribute(DOMElement $node, ?string $name = null): DOMElement
12721373
{
12731374
if (!$name) {

src/Models/Data.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Paneon\VueToTwig\Models;
6+
7+
class Data
8+
{
9+
/**
10+
* @var string
11+
*/
12+
protected $name;
13+
14+
/**
15+
* @var string
16+
*/
17+
protected $value;
18+
19+
/**
20+
* Property constructor.
21+
*/
22+
public function __construct(string $name, string $value)
23+
{
24+
$this->name = $name;
25+
if (substr_count($value, 'window.')) {
26+
$value = 'null';
27+
}
28+
$this->value = $value;
29+
}
30+
31+
public function getName(): string
32+
{
33+
return $this->name;
34+
}
35+
36+
public function getValue(): string
37+
{
38+
return $this->value;
39+
}
40+
}

src/Utils/StyleBuilder.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ public function compile(?DOMElement $styleElement): ?string
9393

9494
if ($styleElement->hasAttribute('scoped')) {
9595
$this->hasScoped = true;
96-
$style = preg_replace('/((?:^|[^},]*?)\S+)(\s*[{,])/i', '$1[' . $this->scopedAttribute . ']$2', $style);
96+
$style = preg_replace(
97+
'/((?:^|\s)\s*[^@\s,][a-z0-9-_]+?)((?::{1,2}[a-z-]+)?\s*[{,])/i',
98+
'$1[' . $this->scopedAttribute . ']$2',
99+
$style
100+
);
97101
}
98102

99103
return '<style>' . $style . '</style>';

tests/DataTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Paneon\VueToTwig\Tests;
4+
5+
use Exception;
6+
7+
class DataTest extends AbstractTestCase
8+
{
9+
/**
10+
* @dataProvider dataProvider
11+
*
12+
* @param mixed $html
13+
* @param mixed $expected
14+
*
15+
* @throws Exception
16+
*/
17+
public function testData($html, $expected)
18+
{
19+
$compiler = $this->createCompiler($html);
20+
21+
$compiler->enableDataSupport();
22+
23+
$actual = $compiler->convert();
24+
25+
$this->assertEqualHtml($expected, $actual);
26+
}
27+
28+
/**
29+
* @return array
30+
*/
31+
public function dataProvider()
32+
{
33+
return $this->loadFixturesFromDir('data');
34+
}
35+
}

tests/fixtures/data/data-config.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{% set bar = bar|default('baz') %}
2+
<div class="{{ class|default('') }}" style="{{ style|default('') }}"> Hello World! </div>

tests/fixtures/data/data-config.vue

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<div>
3+
Hello World!
4+
</div>
5+
</template>
6+
7+
<twig-config>
8+
disable-data-support = true;
9+
</twig-config>
10+
11+
<script>
12+
export default {
13+
name: 'Foo',
14+
props: {
15+
bar: {
16+
type: String,
17+
default: 'baz',
18+
},
19+
},
20+
data() {
21+
return {
22+
myNumber: 1,
23+
myString: 'foo',
24+
myArray: [
25+
'a',
26+
'b',
27+
'c',
28+
],
29+
myObject: {
30+
a: 'x',
31+
b: 'y',
32+
c: 'z',
33+
},
34+
calculatedNumber: this.myString.length + 1,
35+
};
36+
},
37+
};
38+
</script>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% set bar = bar|default('baz') %}
2+
{% set myNumber = 1 %}
3+
{% set myString = 'foo' %}
4+
{% set myArray = ['a', 'b', 'c'] %}
5+
{% set myObject = { a: 'x', b: 'y', c: 'z' } %}
6+
<div class="{{ class|default('') }}" style="{{ style|default('') }}"> Hello World! </div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<div>
3+
Hello World!
4+
</div>
5+
</template>
6+
7+
<script lang="ts">
8+
import { Vue, Component, Prop } from 'vue-property-decorator';
9+
10+
@Component()
11+
export default class Foo extends Vue {
12+
13+
@Prop({ type: String, default: 'baz' }) readonly bar!: string;
14+
15+
private myNumber = 1;
16+
17+
private myString = 'foo';
18+
19+
private myArray = ['a', 'b', 'c'];
20+
21+
private myObject = { a: 'x', b: 'y', c: 'z' };
22+
};
23+
</script>

tests/fixtures/data/data-window.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{% set foo = null %}
2+
<div class="{{ class|default('') }}" style="{{ style|default('') }}"> Hello World! </div>

tests/fixtures/data/data-window.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<div>
3+
Hello World!
4+
</div>
5+
</template>
6+
7+
<script>
8+
export default {
9+
name: 'Foo',
10+
data() {
11+
return {
12+
foo: window.location.href.indexOf('foo') != -1 ? 'www.foo.baz' : 'www.bar.baz'
13+
};
14+
},
15+
};
16+
</script>

0 commit comments

Comments
 (0)