You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
14 KiB
444 lines
14 KiB
<?php
|
|
/**
|
|
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
|
* @package General
|
|
*/
|
|
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstSequence.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstPrint.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstText.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstWith.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstForeach.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstIf.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstMath.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstSpintax.php';
|
|
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstXPath.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstString.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstInteger.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstFloat.php';
|
|
require_once dirname(__FILE__) . '/ast/XmlImportAstFunction.php';
|
|
|
|
/**
|
|
* Parses a list of nodes into AST (Abstract Syntax Tree)
|
|
*/
|
|
class XmlImportTemplateParser
|
|
{
|
|
/**
|
|
* List of tokens
|
|
*
|
|
* @var array
|
|
*/
|
|
private $tokens;
|
|
|
|
/**
|
|
* Current index
|
|
*
|
|
* @var int
|
|
*/
|
|
private $index = -1;
|
|
|
|
/**
|
|
* Stack that stores possible block endings
|
|
*
|
|
* @var array
|
|
*/
|
|
private $clauseStack = array();
|
|
|
|
/**
|
|
* Stack of sequences
|
|
*
|
|
* @var array
|
|
*/
|
|
private $sequenceStack = array();
|
|
|
|
/**
|
|
* Whether else subclause is allowed
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $elseAllowed = false;
|
|
|
|
/**
|
|
* Creates new instance
|
|
*
|
|
* @param array $tokens
|
|
*/
|
|
public function __construct(array $tokens)
|
|
{
|
|
$this->tokens = $tokens;
|
|
}
|
|
|
|
/**
|
|
* Parses the list of tokens into AST tree
|
|
*
|
|
* @return XmlImportAstSequence
|
|
*/
|
|
public function parse()
|
|
{
|
|
$result = $this->parseSequence();
|
|
|
|
if (count($this->clauseStack) > 0)
|
|
throw new XmlImportException("Unexpected end of template.");
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Parses sequence
|
|
*
|
|
* @return XmlImportAstSequence
|
|
*/
|
|
private function parseSequence()
|
|
{
|
|
if (($this->index + 1) == count($this->tokens))
|
|
throw new XmlImportException("Reached end of template but statement sequence expected");
|
|
$sequence = new XmlImportAstSequence();
|
|
array_push($this->sequenceStack, $sequence);
|
|
if (count($this->clauseStack) == 0)
|
|
{
|
|
while (($this->index + 1) < count($this->tokens))
|
|
{
|
|
$sequence->addStatement($this->parseStatement());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (($this->index + 1) < count($this->tokens))
|
|
{
|
|
if ($this->tokens[$this->index + 1]->getKind() == $this->clauseStack[count($this->clauseStack) - 1])
|
|
{
|
|
$this->index++;
|
|
array_pop($this->clauseStack);
|
|
break;
|
|
}
|
|
$statement = $this->parseStatement();
|
|
if (is_null($statement)){
|
|
array_pop($this->sequenceStack);
|
|
return $sequence;
|
|
}
|
|
$sequence->addStatement($statement);
|
|
}
|
|
}
|
|
array_pop($this->sequenceStack);
|
|
|
|
return $sequence;
|
|
}
|
|
|
|
/**
|
|
* Parses statement
|
|
*
|
|
* @return XmlImportAstText|XmlImportAstWith|XmlImportAstForeach|XmlImportAstPrint|XmlImportAstIf
|
|
*/
|
|
private function parseStatement()
|
|
{
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_TEXT)
|
|
{
|
|
return new XmlImportAstText($this->tokens[++$this->index]->getValue());
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_PRINT)
|
|
{
|
|
$this->index++;
|
|
return new XmlImportAstPrint($this->parseExpression());
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_WITH)
|
|
{
|
|
return $this->parseWith();
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FOREACH)
|
|
{
|
|
return $this->parseForeach();
|
|
}
|
|
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_MATH)
|
|
{
|
|
return new XmlImportAstPrint($this->parseExpression());
|
|
}
|
|
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_SPINTAX)
|
|
{
|
|
return new XmlImportAstPrint($this->parseExpression());
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_IF)
|
|
{
|
|
return $this->parseIf();
|
|
}
|
|
elseif($this->clauseStack[count($this->clauseStack) - 1] == XmlImportToken::KIND_ENDIF &&
|
|
in_array($this->tokens[$this->index + 1]->getKind(), array(XmlImportToken::KIND_ELSE, XmlImportToken::KIND_ELSEIF)))
|
|
{
|
|
if ($this->elseAllowed)
|
|
{
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSE)
|
|
$this->elseAllowed = false;
|
|
}
|
|
else
|
|
{
|
|
throw new XmlImportException("ELSEIF or ELSE is not allowed again after ELSE");
|
|
}
|
|
return null;
|
|
}
|
|
else
|
|
throw new XmlImportException ("Unexpected token {$this->tokens[$this->index + 1]->getKind()}, statement was expected.");
|
|
}
|
|
|
|
/**
|
|
* Parses expression
|
|
*
|
|
* @return XmlImportAstXPath|XmlImportAstFunction|XmlImportAstMath|XmlImportAstSpintax|XmlImportAstString|XmlImportAstInteger|XmlImportAstFloat
|
|
*/
|
|
private function parseExpression()
|
|
{
|
|
if ($this->index + 1 == count($this->tokens))
|
|
throw new XmlImportException("Reached end of template but expression was expected");
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FUNCTION)
|
|
{
|
|
return $this->parseFunction();
|
|
}
|
|
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_MATH)
|
|
{
|
|
return $this->parseMath();
|
|
}
|
|
elseif($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_SPINTAX)
|
|
{
|
|
return $this->parseSpintax();
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_XPATH)
|
|
{
|
|
$xpath = new XmlImportAstXPath($this->tokens[++$this->index]->getValue());
|
|
$this->sequenceStack[count($this->sequenceStack) - 1]->addVariable($xpath);
|
|
return $xpath;
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_STRING || $this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPERATION)
|
|
{
|
|
return new XmlImportAstString($this->tokens[++$this->index]->getValue());
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_INT)
|
|
{
|
|
return new XmlImportAstInteger($this->tokens[++$this->index]->getValue());
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_FLOAT)
|
|
{
|
|
return new XmlImportAstFloat($this->tokens[++$this->index]->getValue());
|
|
}
|
|
else
|
|
throw new XmlImportException("Unexpected token " . $this->tokens[$this->index + 1]->getKind());
|
|
}
|
|
|
|
/**
|
|
* Parses function
|
|
*
|
|
* @return XmlImportAstFunction
|
|
*/
|
|
private function parseFunction()
|
|
{
|
|
$function = new XmlImportAstFunction($this->tokens[++$this->index]->getValue());
|
|
|
|
if ($this->tokens[$this->index + 1]->getKind() != XmlImportToken::KIND_OPEN)
|
|
throw new XmlImportException ("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
$this->index++;
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $function;
|
|
}
|
|
else
|
|
{
|
|
while ($this->index < count($this->tokens) - 2)
|
|
{
|
|
$function->addArgument($this->parseExpression());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $function;
|
|
break;
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_COMMA)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Comma or closing brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
}
|
|
throw new XmlImportException("Unexpected end of {$function->getName()} function argument list");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses function
|
|
*
|
|
* @return XmlImportAstFunction|XmlImportAstMath
|
|
*/
|
|
private function parseMath()
|
|
{
|
|
$math = new XmlImportAstMath($this->tokens[++$this->index]->getValue());
|
|
if ($this->tokens[$this->index + 1]->getKind() != XmlImportToken::KIND_OPEN)
|
|
throw new XmlImportException ("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
$this->index++;
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $math;
|
|
}
|
|
else
|
|
{
|
|
while ($this->index < count($this->tokens) - 2)
|
|
{
|
|
$math->addArgument($this->parseExpression());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $math;
|
|
break;
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_COMMA)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Comma or closing brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
}
|
|
throw new XmlImportException("Unexpected end of MATH argument list");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses function
|
|
*
|
|
* @return XmlImportSpintaxFunction|XmlImportAstSpintax
|
|
*/
|
|
private function parseSpintax()
|
|
{
|
|
$spintax = new XmlImportAstSpintax($this->tokens[++$this->index]->getValue());
|
|
if ($this->tokens[$this->index + 1]->getKind() != XmlImportToken::KIND_OPEN)
|
|
throw new XmlImportException ("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
$this->index++;
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $spintax;
|
|
}
|
|
else
|
|
{
|
|
while ($this->index < count($this->tokens) - 2)
|
|
{
|
|
$spintax->addArgument($this->parseExpression());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
{
|
|
$this->index++;
|
|
return $spintax;
|
|
break;
|
|
}
|
|
elseif ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_COMMA)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Comma or closing brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
}
|
|
throw new XmlImportException("Unexpected end of {$function->getName()} function argument list");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses clause that uses XPath and returns XPath
|
|
*
|
|
* @return XmlImportAstXPath
|
|
*/
|
|
private function parseXPathDependant()
|
|
{
|
|
$this->index++;
|
|
|
|
if ($this->index + 1 == count($this->tokens))
|
|
throw new XmlImportException("Reached end of template but expression was expected");
|
|
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_XPATH)
|
|
{
|
|
$xpath = new XmlImportAstXPath($this->tokens[++$this->index]->getValue());
|
|
$this->sequenceStack[count($this->sequenceStack) - 1]->addVariable($xpath);
|
|
}
|
|
else
|
|
throw new XmlImportException("XPath expression expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
return $xpath;
|
|
}
|
|
|
|
/**
|
|
* Parses WITH clause
|
|
*
|
|
* @return XmlImportAstWith
|
|
*/
|
|
private function parseWith()
|
|
{
|
|
$xpath = $this->parseXPathDependant();
|
|
//store sequence exit
|
|
array_push($this->clauseStack, XmlImportToken::KIND_ENDWITH);
|
|
return new XmlImportAstWith($xpath, $this->parseSequence());
|
|
}
|
|
|
|
/**
|
|
* Parses FOREACH clause
|
|
*
|
|
* @return XmlImportAstForeach
|
|
*/
|
|
private function parseForeach()
|
|
{
|
|
$xpath = $this->parseXPathDependant();
|
|
|
|
array_push($this->clauseStack, XmlImportToken::KIND_ENDFOREACH);
|
|
return new XmlImportAstForeach($xpath, $this->parseSequence());
|
|
}
|
|
|
|
/**
|
|
* Parses IF clause
|
|
*
|
|
* @return XmlImportAstIf
|
|
*/
|
|
private function parseIf()
|
|
{
|
|
$this->index++;
|
|
$this->elseAllowed = true;
|
|
array_push($this->clauseStack, XmlImportToken::KIND_ENDIF);
|
|
|
|
if ($this->index + 1 == count($this->tokens))
|
|
throw new XmlImportException("Reached end of template but expression was expected");
|
|
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
|
|
$if = new XmlImportAstIf($this->parseExpression());
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
$if->addIfBody($this->parseSequence());
|
|
|
|
if ($this->index + 1 != count($this->tokens))
|
|
{
|
|
while ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSEIF)
|
|
{
|
|
$this->index++;
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_OPEN)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Open brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
$condition = $this->parseExpression();
|
|
if ($this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_CLOSE)
|
|
$this->index++;
|
|
else
|
|
throw new XmlImportException("Close brace expected instead of " . $this->tokens[$this->index + 1]->getKind());
|
|
|
|
$elseif = new XmlImportAstElseif($condition, $this->parseSequence());
|
|
$if->addElseif($elseif);
|
|
if ($this->index + 1 == count($this->tokens))
|
|
break;
|
|
}
|
|
if ($this->index + 1 < count($this->tokens) && $this->tokens[$this->index + 1]->getKind() == XmlImportToken::KIND_ELSE)
|
|
{
|
|
$this->index++;
|
|
$if->addElseBody($this->parseSequence());
|
|
}
|
|
}
|
|
|
|
return $if;
|
|
}
|
|
} |