Skip to content

Commit d87b210

Browse files
authored
Merge pull request #28 from Paneon/master
Update master
2 parents d2d9c21 + f956435 commit d87b210

26 files changed

+675
-34
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Compile vue files to twig templates with PHP
1919
|v-bind|partially working|
2020
|v-bind:style|:white_check_mark:|
2121
|v-bind:class|:white_check_mark:|
22-
|v-model||
23-
|v-pre||
24-
|v-cloak||
22+
|v-model|partially working|
23+
|v-pre|:white_check_mark:|
24+
|v-cloak|:white_check_mark:|
2525
|v-once|:white_check_mark:|
2626

2727

src/Compiler.php

Lines changed: 201 additions & 6 deletions
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\Pre;
1516
use Paneon\VueToTwig\Models\Property;
1617
use Paneon\VueToTwig\Models\Replacements;
1718
use Paneon\VueToTwig\Models\Slot;
@@ -39,6 +40,11 @@ class Compiler
3940
*/
4041
protected $lastCloseIf;
4142

43+
/**
44+
* @var mixed[]|null
45+
*/
46+
protected $selectData;
47+
4248
/**
4349
* @var LoggerInterface
4450
*/
@@ -69,6 +75,11 @@ class Compiler
6975
*/
7076
protected $properties;
7177

78+
/**
79+
* @var Pre[]
80+
*/
81+
protected $pre;
82+
7283
/**
7384
* @var mixed[]
7485
*/
@@ -105,9 +116,11 @@ public function __construct(DOMDocument $document, LoggerInterface $logger)
105116
$this->document = $document;
106117
$this->logger = $logger;
107118
$this->lastCloseIf = [];
119+
$this->selectData = null;
108120
$this->components = [];
109121
$this->banner = [];
110122
$this->properties = [];
123+
$this->pre = [];
111124
$this->rawBlocks = [];
112125

113126
$this->logger->debug("\n--------- New Compiler Instance ----------\n");
@@ -183,6 +196,8 @@ public function convert(): string
183196

184197
$html = $this->builder->concatConvertHandler($html, $this->properties);
185198

199+
$html = $this->replacePre($html);
200+
186201
if ($this->stripWhitespace) {
187202
$html = $this->stripWhitespace($html);
188203
}
@@ -211,9 +226,16 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
211226
if ($this->twigRemove($node)) {
212227
return $node;
213228
}
229+
if ($this->handlePre($node)) {
230+
return $node;
231+
}
214232
$this->replaceShowWithIf($node);
215233
$this->handleIf($node, $level);
216234
$this->handleFor($node);
235+
$modelData = $this->handleModel($node);
236+
if ($modelData && $modelData['type'] === 'option') {
237+
$this->selectData = $modelData;
238+
}
217239
$this->handleHtml($node);
218240
$this->handleText($node);
219241
$this->stripEventHandlers($node);
@@ -298,6 +320,10 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
298320

299321
if ($node instanceof DOMElement) {
300322
$this->handleAttributeBinding($node);
323+
$this->handleOption($node);
324+
if (isset($modelData)) {
325+
$this->handleRadioOrCheckbox($node, $modelData);
326+
}
301327
if ($level === 1) {
302328
foreach ($this->includeAttributes as $attribute) {
303329
$this->handleRootNodeAttribute($node, $attribute);
@@ -309,6 +335,10 @@ public function convertNode(DOMNode $node, int $level = 0): DOMNode
309335
$this->convertNode($childNode, $level + 1);
310336
}
311337

338+
if ($node->nodeName === 'selected') {
339+
$this->selectData = null;
340+
}
341+
312342
return $node;
313343
}
314344

@@ -373,16 +403,16 @@ public function registerProperties(DOMElement $scriptElement): void
373403
$property->setType($matchType[1]);
374404
}
375405

376-
if (preg_match('/default:\s*(?<default>[^,$]+)\s*,?/mx', $definition, $matchDefault)) {
406+
if (preg_match('/default:\s*(?<default>\[[^\[\]]+\]|[^,$]+)\s*,?/mx', $definition, $matchDefault)) {
377407
$property->setDefault(trim($matchDefault['default']));
378408
}
379409

380410
$this->properties[$propName] = $property;
381411
}
382412
}
383413

384-
$typeScriptRegexProps = '/\@Prop\s*\({(?<propOptions>.*?)}\)[^;]*?(?<propName>[a-zA-Z0-9_$]+)\!?\:\s*(?<propType>[a-zA-Z]+)[^;\@]*;/msx';
385-
$typeScriptRegexDefault = '/default\s*\:\s*(?<defaultValue>\'(?:.(?!(?<![\\\\])\'))*.?\'|"(?:.(?!(?<![\\\\])"))*.?"|[a-zA-Z0-9_]+)/msx';
414+
$typeScriptRegexProps = '/\@Prop\s*\({(?<propOptions>.*?)}\)[^;]*?(?<propName>[a-zA-Z0-9_$]+)\!?\:\s*(?<propType>[a-zA-Z\[\]]+)[^;\@]*;/msx';
415+
$typeScriptRegexDefault = '/default\s*\:\s*(?<defaultValue>\'(?:.(?!(?<![\\\\])\'))*.?\'|"(?:.(?!(?<![\\\\])"))*.?"|[a-zA-Z0-9_]+|\[[^\[\]]+\])/msx';
386416
if (preg_match_all($typeScriptRegexProps, $content, $typeScriptMatches, PREG_SET_ORDER)) {
387417
$this->properties = [];
388418
foreach ($typeScriptMatches as $typeScriptMatch) {
@@ -396,6 +426,42 @@ public function registerProperties(DOMElement $scriptElement): void
396426
}
397427
}
398428

429+
/**
430+
* @throws Exception
431+
*/
432+
public function handlePre(DOMElement $node): bool
433+
{
434+
if (!$node->hasAttribute('v-pre')) {
435+
return false;
436+
}
437+
$node->removeAttribute('v-pre');
438+
$html = $this->document->saveHTML($node);
439+
$parentNode = $node->parentNode;
440+
$parentNode->removeChild($node);
441+
$pre = new Pre('{% verbatim %}' . $html . '{% endverbatim %}');
442+
$key = $pre->getPreContentVariableString();
443+
$replacer = $this->document->createTextNode($key);
444+
$parentNode->appendChild($replacer);
445+
$this->pre[$key] = $pre;
446+
447+
return true;
448+
}
449+
450+
protected function replacePre(string $html): string
451+
{
452+
if (preg_match_all(Pre::PRE_REGEX, $html, $matches)) {
453+
foreach ($matches[0] as $key) {
454+
$html = str_replace(
455+
$key,
456+
$this->pre[$key]->getValue(),
457+
$html
458+
);
459+
}
460+
}
461+
462+
return $html;
463+
}
464+
399465
public function replaceShowWithIf(DOMElement $node): void
400466
{
401467
if ($node->hasAttribute('v-show')) {
@@ -553,7 +619,7 @@ private function cleanupAttributes(DOMElement $node): void
553619
/** @var DOMAttr $attribute */
554620
foreach ($node->attributes as $attribute) {
555621
if (
556-
(preg_match('/^v-([a-z]*)/', $attribute->name, $matches) === 1 && $matches[1] !== 'bind' && $matches[1] !== 'slot')
622+
(preg_match('/^v-([a-z]*)/', $attribute->name, $matches) === 1 && $matches[1] !== 'bind' && $matches[1] !== 'slot' && $matches[1] !== 'cloak')
557623
|| preg_match('/^[:]?ref$/', $attribute->name) === 1
558624
) {
559625
$removeAttributes[] = $attribute->name;
@@ -678,6 +744,135 @@ private function handleFor(DOMElement $node): void
678744
$node->removeAttribute('v-for');
679745
}
680746

747+
/**
748+
* @return mixed|void
749+
*/
750+
private function handleModel(DOMElement $node)
751+
{
752+
if (!$node->hasAttribute('v-model')) {
753+
return;
754+
}
755+
756+
$modelValue = $node->getAttribute('v-model');
757+
$node->removeAttribute('v-mode');
758+
759+
switch ($node->nodeName) {
760+
case 'textarea':
761+
$node->setAttribute('v-text', $modelValue);
762+
763+
return null;
764+
case 'input':
765+
$typeAttribute = $node->getAttribute('type');
766+
if ($typeAttribute === 'checkbox') {
767+
return [
768+
'value' => $modelValue,
769+
'type' => 'checkbox',
770+
];
771+
} elseif ($typeAttribute === 'radio') {
772+
return [
773+
'value' => $modelValue,
774+
'type' => 'radio',
775+
];
776+
} else {
777+
$node->setAttribute(':value', $modelValue);
778+
}
779+
780+
return null;
781+
case 'select':
782+
return [
783+
'value' => $modelValue,
784+
'multiple' => $node->hasAttribute('multiple'),
785+
'type' => 'option',
786+
];
787+
default:
788+
return null;
789+
}
790+
}
791+
792+
/**
793+
* @throws ReflectionException
794+
*/
795+
private function handleOption(DOMElement $node): void
796+
{
797+
if ($node->tagName !== 'option' || $this->selectData === null) {
798+
return;
799+
}
800+
801+
if ($node->hasAttribute('value')) {
802+
$value = $node->getAttribute('value');
803+
} else {
804+
$value = trim($node->textContent);
805+
}
806+
807+
$value = '"' . str_replace(['__DOUBLE_CURLY_OPEN__', '__DOUBLE_CURLY_CLOSE__'], ['" ~', '~ "'], $value) . '"';
808+
809+
if ($this->selectData['multiple']) {
810+
$condition = $this->selectData['value'] . ' is iterable and ' . $value . ' in ' . $this->selectData['value'];
811+
} else {
812+
$condition = $this->selectData['value'] . ' == ' . $value;
813+
}
814+
815+
$this->addAttributeIf($node, $condition, 'selected', 'selected');
816+
}
817+
818+
/**
819+
* @param mixed[] $modelData
820+
*
821+
* @throws ReflectionException
822+
*/
823+
private function handleRadioOrCheckbox(DOMElement $node, array $modelData): void
824+
{
825+
if (!$node->hasAttribute('value')
826+
|| !$node->hasAttribute('type')
827+
|| ($node->getAttribute('type') !== 'radio' && $node->getAttribute('type') !== 'checkbox')) {
828+
return;
829+
}
830+
831+
$value = $node->getAttribute('value');
832+
833+
$value = '"' . str_replace(['__DOUBLE_CURLY_OPEN__', '__DOUBLE_CURLY_CLOSE__'], ['" ~', '~ "'], $value) . '"';
834+
835+
if ($modelData['type'] === 'checkbox') {
836+
$condition = '(' . $modelData['value'] . ' is iterable and ' . $value . ' in ' . $modelData['value'] . ') '
837+
. ' or (' . $modelData['value'] . ' is not iterable and ' . $modelData['value'] . ')';
838+
} else {
839+
$condition = $modelData['value'] . ' == ' . $value;
840+
}
841+
842+
$this->addAttributeIf($node, $condition, 'checked', 'checked');
843+
}
844+
845+
/**
846+
* @throws ReflectionException
847+
*/
848+
private function addAttributeIf(DOMElement $node, string $condition, string $attributeName, string $attributeValue): void
849+
{
850+
/** @var DOMElement $clonedNode */
851+
$clonedNode = $node->cloneNode(true);
852+
$node->setAttribute($attributeName, $attributeValue);
853+
854+
if ($clonedNode->hasAttribute($attributeName)) {
855+
$clonedNode->removeAttribute($attributeName);
856+
}
857+
858+
$node->parentNode->insertBefore(
859+
$this->document->createTextNode($this->builder->createIf($condition)),
860+
$node
861+
);
862+
$node->parentNode->insertBefore(
863+
$this->document->createTextNode($this->builder->createEndIf()),
864+
$node->nextSibling
865+
);
866+
$node->parentNode->insertBefore(
867+
$clonedNode,
868+
$node->nextSibling
869+
);
870+
$node->parentNode->insertBefore(
871+
$this->document->createTextNode($this->builder->createElse()),
872+
$node->nextSibling
873+
);
874+
}
875+
681876
private function handleHtml(DOMElement $node): void
682877
{
683878
if (!$node->hasAttribute('v-html')) {
@@ -689,7 +884,7 @@ private function handleHtml(DOMElement $node): void
689884
while ($node->hasChildNodes()) {
690885
$node->removeChild($node->firstChild);
691886
}
692-
$node->appendChild(new DOMText('{{' . $html . '|raw}}'));
887+
$node->appendChild(new DOMText($this->builder->prepareBindingOutput($html . '|raw')));
693888
}
694889

695890
private function handleText(DOMElement $node): void
@@ -703,7 +898,7 @@ private function handleText(DOMElement $node): void
703898
while ($node->hasChildNodes()) {
704899
$node->removeChild($node->firstChild);
705900
}
706-
$node->appendChild(new DOMText('{{' . $text . '}}'));
901+
$node->appendChild(new DOMText($this->builder->prepareBindingOutput($text)));
707902
}
708903

709904
/**

src/Models/Pre.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Paneon\VueToTwig\Models;
6+
7+
use Exception;
8+
use Ramsey\Uuid\Uuid;
9+
10+
class Pre
11+
{
12+
public const PRE_REGEX = '/__PRE_[a-f0-9]{8}_[a-f0-9]{4}_[a-f0-9]{4}_[a-f0-9]{4}_[a-f0-9]{12}__/';
13+
14+
/**
15+
* @var string
16+
*/
17+
protected $uuid;
18+
19+
/**
20+
* @var string
21+
*/
22+
protected $value;
23+
24+
/**
25+
* Slot constructor.
26+
*
27+
* @throws Exception
28+
*/
29+
public function __construct(string $value)
30+
{
31+
$this->uuid = Uuid::uuid4()->toString();
32+
$this->value = $value;
33+
}
34+
35+
public function getValue(): string
36+
{
37+
return $this->value;
38+
}
39+
40+
public function setValue(string $value): void
41+
{
42+
$this->value = $value;
43+
}
44+
45+
public function getPreContentVariableString(): string
46+
{
47+
return '__PRE_' . str_replace('-', '_', $this->uuid) . '__';
48+
}
49+
50+
public function getUuid(): string
51+
{
52+
return $this->uuid;
53+
}
54+
}

0 commit comments

Comments
 (0)