217 lines
7.5 KiB
PHP
217 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace KupShop\FeedGeneratorBundle\Configuration;
|
|
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\AttributeNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\ConditionNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\CycleNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\DescriptionNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\ElementNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\ExpressionNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\INode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\MultilineNode;
|
|
use KupShop\FeedGeneratorBundle\Configuration\Nodes\ValueNode;
|
|
|
|
class V8BuildingVisitor implements IConfigurationVisitor
|
|
{
|
|
protected bool $indent = false;
|
|
|
|
protected $outputJs;
|
|
|
|
protected $indentDepth = 0;
|
|
|
|
public function setIndent(bool $indent): self
|
|
{
|
|
$this->indent = $indent;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function initialize(): void
|
|
{
|
|
$this->indentDepth = 0;
|
|
$this->outputJs = '';
|
|
|
|
// define __PHP object for relevant properties from PHP
|
|
$this->addLine('var __PHP = {};');
|
|
$this->addLine('__PHP.__properties = PHP.__properties;');
|
|
$this->addLine('__PHP.__methods = PHP.__methods;');
|
|
$this->addLine('__PHP.__contextProperties = PHP.__contextProperties;');
|
|
|
|
$this->addLine('var __buildXML = function() {');
|
|
|
|
// clear custom global variables (see https://github.com/phpv8/v8js/pull/340)
|
|
// skip properties, methods and contextProperties and: exit,sleep,print,var_dump,require,global,PHP,isFalse,strval,__XMLWriter,__buildXML,__reportCounters,console
|
|
$this->addLine("for (const prop in global) { if (!Object.keys(__PHP.__properties).includes(prop) && !Object.keys(__PHP.__methods).includes(prop) && !Object.keys(__PHP.__contextProperties).includes(prop) && !['exit', 'sleep', 'print', 'var_dump', 'require', 'global', '__PHP', 'isFalse', 'strval' , '__XMLWriter', '__buildXML', '__reportCounters', 'reportCounter', 'console', 'dayjs', 'utils'].includes(prop)) { delete global[prop]; }}");
|
|
|
|
$this->addLine('var __xmlWriter = new __XMLWriter('.($this->indent ? 'true' : 'false').');');
|
|
}
|
|
|
|
public function getOutput(): string
|
|
{
|
|
$this->indentDepth = 0;
|
|
$this->addLine('return __xmlWriter.toString();');
|
|
$this->addLine('};');
|
|
|
|
return $this->outputJs;
|
|
}
|
|
|
|
/**
|
|
* @param INode[] $nodes
|
|
*/
|
|
private function processNodes(array $nodes, int $depth = 0)
|
|
{
|
|
$close = true;
|
|
foreach ($nodes as $node) {
|
|
$this->indentDepth = $depth;
|
|
if ($node->accept($this) && $node->hasNodes()) {
|
|
$this->processNodes($node->getNodes(), $depth + 1);
|
|
}
|
|
$this->indentDepth = $depth;
|
|
$node->accept($this, $close);
|
|
}
|
|
}
|
|
|
|
public function visitConfiguration(Configuration $configuration, &$handle = null)
|
|
{
|
|
$this->processNodes($configuration->getNodes());
|
|
}
|
|
|
|
public function visitAttribute(AttributeNode $node, &$handle)
|
|
{
|
|
if (empty($node->getValue())) {
|
|
return null;
|
|
}
|
|
if ($handle) {
|
|
$this->addLine('__xmlWriter.endAttribute();');
|
|
|
|
return null;
|
|
}
|
|
$this->addLine('__xmlWriter.startAttribute('.json_encode($node->getValue()).');');
|
|
|
|
return true;
|
|
}
|
|
|
|
public function visitCondition(ConditionNode $node, &$handle)
|
|
{
|
|
if (empty($node->getValue())) {
|
|
return false;
|
|
}
|
|
if ($handle) {
|
|
$this->addLine('}');
|
|
|
|
return false;
|
|
}
|
|
|
|
// Immediately invoked function execution
|
|
$this->addLine('if ((function(){');
|
|
// maintain backward compatibility: zero in a string should evaluate to false
|
|
$this->addLine(' const __condResult = '.$node->getValue().';');
|
|
$this->addLine(' return __condResult === "0" ? false : __condResult;');
|
|
$this->addLine('})()) {');
|
|
|
|
return true;
|
|
}
|
|
|
|
public function visitCycle(CycleNode $node, &$handle)
|
|
{
|
|
if (empty($node->getValue())) {
|
|
return false;
|
|
}
|
|
if ($handle) {
|
|
$this->addLine('}');
|
|
// $this->addLine('}})();');
|
|
|
|
return false;
|
|
}
|
|
|
|
// temporary variables approach
|
|
// for `photos.slice(0, Math.min(photos.length, 10))` to work is still required to have separate wrapper for each object in MultiWrapper
|
|
// $iterableName = '___iterable'.$this->indentDepth;
|
|
// $indexName = !empty($node->getIndexAccessName()) ? $node->getIndexAccessName() : '___index'.$this->indentDepth;;
|
|
// $this->addLine('(function(){ const '.$iterableName.' = (function(){ return '.$node->getValue().'; })()');
|
|
// $this->addLine('for (const '.$indexName.' in '.$iterableName.') {');
|
|
// if (!empty($node->getValueAccessName())) {
|
|
// $this->addLine('const '.$node->getValueAccessName().' = '.$iterableName.'['.$indexName.'];');
|
|
// }
|
|
|
|
// Object.entries() approach
|
|
if (empty($node->getValueAccessName()) && empty($node->getIndexAccessName())) {
|
|
// iterate without access to index/value
|
|
$this->addLine('for (const [] of Object.entries((function(){');
|
|
$this->addLine(' return '.$node->getValue().';');
|
|
$this->addLine('})())) {');
|
|
} else {
|
|
$this->addLine('for (const ['.($node->getIndexAccessName() ?: 'undefined').', '.($node->getValueAccessName() ?: 'undefined').'] of Object.entries((function(){');
|
|
$this->addLine(' return '.$node->getValue().';');
|
|
$this->addLine('})())) {');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function visitElement(ElementNode $node, &$handle)
|
|
{
|
|
if (empty($node->getValue())) {
|
|
return false;
|
|
}
|
|
if ($handle) {
|
|
$this->addLine('__xmlWriter.endElement();');
|
|
|
|
return false;
|
|
}
|
|
|
|
// TODO: support dynamic element/attribute names?
|
|
$this->addLine('__xmlWriter.startElement('.json_encode($node->getValue()).');');
|
|
|
|
return true;
|
|
}
|
|
|
|
public function visitExpression(ExpressionNode $node, &$handle): bool
|
|
{
|
|
if ($handle || empty($node->getValue())) {
|
|
return false;
|
|
}
|
|
// Immediately invoked function execution
|
|
$this->addLine('__xmlWriter.text((function(){');
|
|
$this->addLine(' return '.$node->getValue().';');
|
|
$this->addLine('})());');
|
|
|
|
return false;
|
|
}
|
|
|
|
public function visitMultiline(MultilineNode $node, &$handle): bool
|
|
{
|
|
if ($handle || empty($node->getValue())) {
|
|
return false;
|
|
}
|
|
// Immediately invoked function execution
|
|
$this->addLine('__xmlWriter.text((function(){');
|
|
// indent content
|
|
$this->addLine(' '.join(PHP_EOL.str_repeat(' ', $this->indentDepth + 1), explode(PHP_EOL, $node->getValue())));
|
|
$this->addLine(' return "";');
|
|
$this->addLine('})());');
|
|
|
|
return false;
|
|
}
|
|
|
|
public function visitValue(ValueNode $node, &$handle)
|
|
{
|
|
if (!$handle) {
|
|
$this->addLine('__xmlWriter.text('.json_encode($node->getValue() ?? '').');');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function visitDescription(DescriptionNode $node, &$handle)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected function addLine(string $line): void
|
|
{
|
|
$this->outputJs .= PHP_EOL.str_repeat(' ', $this->indentDepth).$line;
|
|
}
|
|
}
|