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.
443 lines
12 KiB
443 lines
12 KiB
<?php
|
|
/**
|
|
* @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
|
|
* @package General
|
|
*/
|
|
|
|
require_once dirname(__FILE__) . '/XmlImportToken.php';
|
|
require_once dirname(__FILE__) . '/XmlImportException.php';
|
|
|
|
/**
|
|
* Used to scan string into a list of tokens
|
|
*/
|
|
final class XmlImportTemplateScanner
|
|
{
|
|
/**
|
|
* Language keywords
|
|
*
|
|
* @var array
|
|
*/
|
|
private $keywords = array(
|
|
'IF',
|
|
'ELSEIF',
|
|
'ELSE',
|
|
'ENDIF',
|
|
'FOREACH',
|
|
'ENDFOREACH',
|
|
'WITH',
|
|
'ENDWITH',
|
|
'MATH',
|
|
'SPINTAX'
|
|
);
|
|
|
|
/**
|
|
* Parsing text
|
|
*/
|
|
const STATE_TEXT = 'STATE_TEXT';
|
|
|
|
/**
|
|
* Parsing XPath
|
|
*/
|
|
const STATE_XPATH = 'STATE_XPATH';
|
|
|
|
/**
|
|
* Parsing Language
|
|
*/
|
|
const STATE_LANG = 'STATE_LANG';
|
|
|
|
/**
|
|
* Whether it is lang block start
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $isLangBegin = false;
|
|
|
|
private $previous_ch = false;
|
|
|
|
/**
|
|
* Current parsing state
|
|
*
|
|
* @var string
|
|
*/
|
|
private $currentState = XmlImportTemplateScanner::STATE_TEXT;
|
|
|
|
/**
|
|
* Scans template from XmlImportReaderInterface and returns the list of tokens
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return array
|
|
*/
|
|
public function scan(XmlImportReaderInterface $input)
|
|
{
|
|
$results = array();
|
|
|
|
while (($ch = $input->peek()) !== false)
|
|
{
|
|
switch ($this->currentState)
|
|
{
|
|
case XmlImportTemplateScanner::STATE_TEXT:
|
|
|
|
if ($ch == '[' && PMXI_Plugin::$is_php_allowed)
|
|
{
|
|
$this->previous_ch = '[';
|
|
$this->currentState = XmlImportTemplateScanner::STATE_LANG;
|
|
$this->isLangBegin = true;
|
|
//omit [
|
|
$input->read();
|
|
}
|
|
else
|
|
if ($ch == '{')
|
|
{
|
|
$this->currentState = XmlImportTemplateScanner::STATE_XPATH;
|
|
//omit {
|
|
$input->read();
|
|
}
|
|
else
|
|
{
|
|
$results[] = $this->scanText($input);
|
|
}
|
|
break;
|
|
case XmlImportTemplateScanner::STATE_XPATH:
|
|
$results = array_merge($results, $this->scanXPath($input, false));
|
|
break;
|
|
case XmlImportTemplateScanner::STATE_LANG:
|
|
$ch = $input->peek();
|
|
|
|
if (preg_match('/\s/', $ch))
|
|
{
|
|
//omit space
|
|
$input->read();
|
|
}
|
|
elseif (preg_match('/[_a-z]/i', $ch))
|
|
{
|
|
$result = $this->scanName($input);
|
|
if (is_array($result))
|
|
$results = array_merge($results, $result);
|
|
else
|
|
$results[] = $result;
|
|
}
|
|
elseif (preg_match('/(\d)/', $ch))
|
|
{
|
|
$result = $this->scanNumber($input);
|
|
if (is_array($result))
|
|
$results = array_merge($results, $result);
|
|
else
|
|
$results[] = $result;
|
|
}
|
|
elseif ($ch == '"')
|
|
{
|
|
//omit "
|
|
$input->read();
|
|
$result = $this->scanString($input);
|
|
if (is_array($result))
|
|
$results = array_merge($results, $result);
|
|
else
|
|
$results[] = $result;
|
|
}
|
|
elseif ($ch == '{')
|
|
{
|
|
$input->read();
|
|
$result = $this->scanXPath($input);
|
|
if (is_array($result))
|
|
$results = array_merge($results, $result);
|
|
else
|
|
$results[] = $result;
|
|
}
|
|
elseif ($ch == '(')
|
|
{
|
|
$this->isLangBegin = false;
|
|
$input->read();
|
|
$results[] = new XmlImportToken(XmlImportToken::KIND_OPEN);
|
|
}
|
|
elseif ($ch == ')')
|
|
{
|
|
$this->isLangBegin = false;
|
|
$input->read();
|
|
$results[] = new XmlImportToken(XmlImportToken::KIND_CLOSE);
|
|
}
|
|
elseif ($ch == ',')
|
|
{
|
|
$this->isLangBegin = false;
|
|
$input->read();
|
|
$results[] = new XmlImportToken(XmlImportToken::KIND_COMMA);
|
|
}
|
|
elseif ($ch == "]")
|
|
{
|
|
$this->isLangBegin = false;
|
|
$this->currentState = XmlImportTemplateScanner::STATE_TEXT;
|
|
//omit ]
|
|
$input->read();
|
|
}
|
|
elseif ($ch == "|" || $ch == "+" || $ch == "-" || $ch == "*" || $ch == "/")
|
|
{
|
|
$results[] = $this->scanText($input);
|
|
}
|
|
else{
|
|
if ($ch == "'"){
|
|
throw new XmlImportException("Unexpected symbol ' - When using shortcodes/PHP functions, use double quotes \", not single quotes '");
|
|
}
|
|
else{
|
|
throw new XmlImportException("Unexpected symbol '$ch'");
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Scans text
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return XmlImportToken
|
|
*/
|
|
private function scanText($input)
|
|
{
|
|
$accum = $input->read();
|
|
while (($ch = $input->peek()) !== false)
|
|
{
|
|
if ($ch == '{' && $accum[strlen($accum) - 1] != "\\")
|
|
{
|
|
$this->currentState = XmlImportTemplateScanner::STATE_XPATH;
|
|
//omit {
|
|
$input->read();
|
|
break;
|
|
}
|
|
elseif ($ch == '[' && $accum[strlen($accum) - 1] != "\\")
|
|
{
|
|
|
|
$this->currentState = XmlImportTemplateScanner::STATE_LANG;
|
|
$this->isLangBegin = true;
|
|
//omit [
|
|
$input->read();
|
|
break;
|
|
}
|
|
elseif ($accum == '/' && $this->previous_ch == "["){
|
|
$accum = "[" . $accum . $input->read();
|
|
//$this->previous_ch = false;
|
|
}
|
|
else
|
|
$accum .= $input->read();
|
|
$this->previous_ch = $ch;
|
|
}
|
|
$accum = str_replace(array("\\[", "\\{"), array('[', '{'), $accum);
|
|
return new XmlImportToken(XmlImportToken::KIND_TEXT, $accum);
|
|
}
|
|
|
|
/**
|
|
* Scans XPath
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @param bool $insideLang
|
|
* @return XmlImportToken|array
|
|
*/
|
|
private function scanXPath($input, $insideLang = true)
|
|
{
|
|
$accum = '';
|
|
while(($ch = $input->peek()) !== false)
|
|
{
|
|
if ($ch == '}' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
|
|
{
|
|
//skip }
|
|
$input->read();
|
|
$accum = str_replace("\\}", '}', $accum);
|
|
if ($insideLang)
|
|
{
|
|
if ($this->isLangBegin)
|
|
{
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
|
|
}
|
|
else
|
|
return new XmlImportToken(XmlImportToken::KIND_XPATH, $accum);
|
|
}
|
|
else
|
|
{
|
|
$this->currentState = XmlImportTemplateScanner::STATE_TEXT;
|
|
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
|
|
}
|
|
}
|
|
else
|
|
$accum .= $input->read();
|
|
}
|
|
throw new XmlImportException('Unexpected end of XPath expression \'' . $accum . '\'');
|
|
}
|
|
|
|
/**
|
|
* Scans name
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return XmlImportToken|array
|
|
*/
|
|
private function scanName(XmlImportReaderInterface $input)
|
|
{
|
|
$accum = $input->read();
|
|
|
|
$is_function = false;
|
|
while (preg_match('%[/_a-z0-9=\s\-"]%i', $input->peek(), $matches))
|
|
{
|
|
$accum .= $input->read();
|
|
if ($input->peek() === false)
|
|
throw new XmlImportException("Unexpected end of function or keyword name \"$accum\"");
|
|
}
|
|
|
|
$ch = $input->peek();
|
|
|
|
if ($ch == "(") $is_function = true;
|
|
|
|
if (in_array(strtoupper(trim($accum)), $this->keywords))
|
|
{
|
|
return new XmlImportToken(strtoupper($accum));
|
|
}
|
|
else
|
|
{
|
|
|
|
if (strpos($accum, "=") !== false or (shortcode_exists($accum) and !$is_function) or ! $is_function) {
|
|
$this->isLangBegin = false;
|
|
return new XmlImportToken(XmlImportToken::KIND_TEXT, '[' . trim(trim($accum, "["), "]") . ']');
|
|
|
|
}
|
|
|
|
if ($this->isLangBegin)
|
|
{
|
|
$this->isLangBegin = false;
|
|
if ( function_exists($accum) or in_array($accum, array('array')))
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum));
|
|
else
|
|
throw new XmlImportException("Call to undefined function \"$accum\"");
|
|
|
|
}
|
|
else{
|
|
if ( function_exists($accum) or in_array($accum, array('array')))
|
|
return new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum);
|
|
else
|
|
throw new XmlImportException("Call to undefined function \"$accum\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scans string literal
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return XmlImportToken|array
|
|
*/
|
|
private function scanString(XmlImportReaderInterface $input)
|
|
{
|
|
$accum = '';
|
|
while(($ch = $input->peek()) !== false)
|
|
{
|
|
if ($ch == '"' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
|
|
{
|
|
|
|
//skip "
|
|
$input->read();
|
|
$accum = str_replace("\\\"", '"', $accum);
|
|
if ($this->isLangBegin)
|
|
{
|
|
$this->isLangBegin = false;
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_STRING, $accum));
|
|
}
|
|
else
|
|
{
|
|
return new XmlImportToken(XmlImportToken::KIND_STRING, $accum);
|
|
}
|
|
}
|
|
else
|
|
$accum .= $input->read();
|
|
}
|
|
throw new XmlImportException('Unexpected end of string literal "' . $accum . '"');
|
|
}
|
|
|
|
/**
|
|
* Scans number
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return XmlImportToken|array
|
|
*/
|
|
private function scanNumber(XmlImportReaderInterface $input)
|
|
{
|
|
$isInt = true;
|
|
$accum = $this->scanInt($input);
|
|
if ($input->peek() == '.')
|
|
{
|
|
$isInt = false;
|
|
$accum .= $input->read();
|
|
$accum .= $this->scanNumberFrac($input);
|
|
}
|
|
if (strtolower($input->peek()) == 'e' )
|
|
{
|
|
$isInt = false;
|
|
$accum .= $input->read();
|
|
$accum .= $this->scanInt($input);
|
|
}
|
|
if ($isInt)
|
|
{
|
|
if ($this->isLangBegin)
|
|
{
|
|
$this->isLangBegin = false;
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_INT, intval($accum)));
|
|
}
|
|
else
|
|
{
|
|
return new XmlImportToken(XmlImportToken::KIND_INT, intval($accum));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->isLangBegin)
|
|
{
|
|
$this->isLangBegin = false;
|
|
return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum)));
|
|
}
|
|
else
|
|
{
|
|
return new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scans integer number
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return string
|
|
*/
|
|
private function scanInt(XmlImportReaderInterface $input)
|
|
{
|
|
if (preg_match('/(\d)/', $input->peek()))
|
|
{
|
|
$accum = $input->read();
|
|
if ($accum == '-' && !preg_match('/\d/', $input->peek()))
|
|
throw new XmlImportException("Expected digit after a minus");
|
|
while (preg_match('/\d/', $input->peek()))
|
|
{
|
|
$accum .= $input->read();
|
|
}
|
|
return $accum;
|
|
}
|
|
else
|
|
throw new XmlImportException("digit or '-' expected in a number");
|
|
}
|
|
|
|
/**
|
|
* Scans fraction part of a number
|
|
*
|
|
* @param XmlImportReaderInterface $input
|
|
* @return string
|
|
*/
|
|
private function scanNumberFrac(XmlImportReaderInterface $input)
|
|
{
|
|
$accum = '';
|
|
while (preg_match('/\d/', $input->peek()))
|
|
{
|
|
$accum .= $input->read();
|
|
}
|
|
if (strlen($accum) == 0)
|
|
throw new XmlImportException("Digits are expected after a '.'");
|
|
return $accum;
|
|
}
|
|
}
|