/wp-content/plugins/wp-all-import-pro/libraries/XmlImportTemplateScanner.php

https://gitlab.com/najomie/fit-hippie · PHP · 442 lines · 367 code · 15 blank · 60 comment · 43 complexity · 9d2f23e404ed0f0ebc5abeca257a7441 MD5 · raw file

  1. <?php
  2. /**
  3. * @author Olexandr Zanichkovsky <olexandr.zanichkovsky@zophiatech.com>
  4. * @package General
  5. */
  6. require_once dirname(__FILE__) . '/XmlImportToken.php';
  7. require_once dirname(__FILE__) . '/XmlImportException.php';
  8. /**
  9. * Used to scan string into a list of tokens
  10. */
  11. final class XmlImportTemplateScanner
  12. {
  13. /**
  14. * Language keywords
  15. *
  16. * @var array
  17. */
  18. private $keywords = array(
  19. 'IF',
  20. 'ELSEIF',
  21. 'ELSE',
  22. 'ENDIF',
  23. 'FOREACH',
  24. 'ENDFOREACH',
  25. 'WITH',
  26. 'ENDWITH',
  27. 'MATH',
  28. 'SPINTAX'
  29. );
  30. /**
  31. * Parsing text
  32. */
  33. const STATE_TEXT = 'STATE_TEXT';
  34. /**
  35. * Parsing XPath
  36. */
  37. const STATE_XPATH = 'STATE_XPATH';
  38. /**
  39. * Parsing Language
  40. */
  41. const STATE_LANG = 'STATE_LANG';
  42. /**
  43. * Whether it is lang block start
  44. *
  45. * @var bool
  46. */
  47. private $isLangBegin = false;
  48. private $previous_ch = false;
  49. /**
  50. * Current parsing state
  51. *
  52. * @var string
  53. */
  54. private $currentState = XmlImportTemplateScanner::STATE_TEXT;
  55. /**
  56. * Scans template from XmlImportReaderInterface and returns the list of tokens
  57. *
  58. * @param XmlImportReaderInterface $input
  59. * @return array
  60. */
  61. public function scan(XmlImportReaderInterface $input)
  62. {
  63. $results = array();
  64. while (($ch = $input->peek()) !== false)
  65. {
  66. switch ($this->currentState)
  67. {
  68. case XmlImportTemplateScanner::STATE_TEXT:
  69. if ($ch == '[')
  70. {
  71. $this->previous_ch = '[';
  72. $this->currentState = XmlImportTemplateScanner::STATE_LANG;
  73. $this->isLangBegin = true;
  74. //omit [
  75. $input->read();
  76. }
  77. elseif ($ch == '{')
  78. {
  79. $this->currentState = XmlImportTemplateScanner::STATE_XPATH;
  80. //omit {
  81. $input->read();
  82. }
  83. else
  84. {
  85. $results[] = $this->scanText($input);
  86. }
  87. break;
  88. case XmlImportTemplateScanner::STATE_XPATH:
  89. $results = array_merge($results, $this->scanXPath($input, false));
  90. break;
  91. case XmlImportTemplateScanner::STATE_LANG:
  92. $ch = $input->peek();
  93. if (preg_match('/\s/', $ch))
  94. {
  95. //omit space
  96. $input->read();
  97. }
  98. elseif (preg_match('/[_a-z]/i', $ch))
  99. {
  100. $result = $this->scanName($input);
  101. if (is_array($result))
  102. $results = array_merge($results, $result);
  103. else
  104. $results[] = $result;
  105. }
  106. elseif (preg_match('/(\d)/', $ch))
  107. {
  108. $result = $this->scanNumber($input);
  109. if (is_array($result))
  110. $results = array_merge($results, $result);
  111. else
  112. $results[] = $result;
  113. }
  114. elseif ($ch == '"')
  115. {
  116. //omit "
  117. $input->read();
  118. $result = $this->scanString($input);
  119. if (is_array($result))
  120. $results = array_merge($results, $result);
  121. else
  122. $results[] = $result;
  123. }
  124. elseif ($ch == '{')
  125. {
  126. $input->read();
  127. $result = $this->scanXPath($input);
  128. if (is_array($result))
  129. $results = array_merge($results, $result);
  130. else
  131. $results[] = $result;
  132. }
  133. elseif ($ch == '(')
  134. {
  135. $this->isLangBegin = false;
  136. $input->read();
  137. $results[] = new XmlImportToken(XmlImportToken::KIND_OPEN);
  138. }
  139. elseif ($ch == ')')
  140. {
  141. $this->isLangBegin = false;
  142. $input->read();
  143. $results[] = new XmlImportToken(XmlImportToken::KIND_CLOSE);
  144. }
  145. elseif ($ch == ',')
  146. {
  147. $this->isLangBegin = false;
  148. $input->read();
  149. $results[] = new XmlImportToken(XmlImportToken::KIND_COMMA);
  150. }
  151. elseif ($ch == "]")
  152. {
  153. $this->isLangBegin = false;
  154. $this->currentState = XmlImportTemplateScanner::STATE_TEXT;
  155. //omit ]
  156. $input->read();
  157. }
  158. elseif ($ch == "|" || $ch == "+" || $ch == "-" || $ch == "*" || $ch == "/")
  159. {
  160. $results[] = $this->scanText($input);
  161. }
  162. else{
  163. if ($ch == "'"){
  164. throw new XmlImportException("Unexpected symbol ' - When using shortcodes/PHP functions, use double quotes \", not single quotes '");
  165. }
  166. else{
  167. throw new XmlImportException("Unexpected symbol '$ch'");
  168. }
  169. }
  170. break;
  171. }
  172. }
  173. return $results;
  174. }
  175. /**
  176. * Scans text
  177. *
  178. * @param XmlImportReaderInterface $input
  179. * @return XmlImportToken
  180. */
  181. private function scanText($input)
  182. {
  183. $accum = $input->read();
  184. while (($ch = $input->peek()) !== false)
  185. {
  186. if ($ch == '{' && $accum[strlen($accum) - 1] != "\\")
  187. {
  188. $this->currentState = XmlImportTemplateScanner::STATE_XPATH;
  189. //omit {
  190. $input->read();
  191. break;
  192. }
  193. elseif ($ch == '[' && $accum[strlen($accum) - 1] != "\\")
  194. {
  195. $this->currentState = XmlImportTemplateScanner::STATE_LANG;
  196. $this->isLangBegin = true;
  197. //omit [
  198. $input->read();
  199. break;
  200. }
  201. elseif ($accum == '/' && $this->previous_ch == "["){
  202. $accum = "[" . $accum . $input->read();
  203. //$this->previous_ch = false;
  204. }
  205. else
  206. $accum .= $input->read();
  207. $this->previous_ch = $ch;
  208. }
  209. $accum = str_replace(array("\\[", "\\{"), array('[', '{'), $accum);
  210. return new XmlImportToken(XmlImportToken::KIND_TEXT, $accum);
  211. }
  212. /**
  213. * Scans XPath
  214. *
  215. * @param XmlImportReaderInterface $input
  216. * @param bool $insideLang
  217. * @return XmlImportToken
  218. */
  219. private function scanXPath($input, $insideLang = true)
  220. {
  221. $accum = '';
  222. while(($ch = $input->peek()) !== false)
  223. {
  224. if ($ch == '}' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
  225. {
  226. //skip }
  227. $input->read();
  228. $accum = str_replace("\\}", '}', $accum);
  229. if ($insideLang)
  230. {
  231. if ($this->isLangBegin)
  232. {
  233. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
  234. }
  235. else
  236. return new XmlImportToken(XmlImportToken::KIND_XPATH, $accum);
  237. }
  238. else
  239. {
  240. $this->currentState = XmlImportTemplateScanner::STATE_TEXT;
  241. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_XPATH, $accum));
  242. }
  243. }
  244. else
  245. $accum .= $input->read();
  246. }
  247. throw new XmlImportException('Unexpected end of XPath expression \'' . $accum . '\'');
  248. }
  249. /**
  250. * Scans name
  251. *
  252. * @param XmlImportReaderInterface $input
  253. * @return XmlImportToken
  254. */
  255. private function scanName(XmlImportReaderInterface $input)
  256. {
  257. $accum = $input->read();
  258. $is_function = false;
  259. while (preg_match('%[/_a-z0-9=\s\-"]%i', $input->peek(), $matches))
  260. {
  261. $accum .= $input->read();
  262. if ($input->peek() === false)
  263. throw new XmlImportException("Unexpected end of function or keyword name \"$accum\"");
  264. }
  265. $ch = $input->peek();
  266. if ($ch == "(") $is_function = true;
  267. if (in_array(strtoupper(trim($accum)), $this->keywords))
  268. {
  269. return new XmlImportToken(strtoupper($accum));
  270. }
  271. else
  272. {
  273. if (strpos($accum, "=") !== false or (shortcode_exists($accum) and !$is_function) or ! $is_function) {
  274. $this->isLangBegin = false;
  275. return new XmlImportToken(XmlImportToken::KIND_TEXT, '[' . trim(trim($accum, "["), "]") . ']');
  276. }
  277. if ($this->isLangBegin)
  278. {
  279. $this->isLangBegin = false;
  280. if ( function_exists($accum) or in_array($accum, array('array')))
  281. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum));
  282. else
  283. throw new XmlImportException("Call to undefined function \"$accum\"");
  284. }
  285. else{
  286. if ( function_exists($accum) or in_array($accum, array('array')))
  287. return new XmlImportToken(XmlImportToken::KIND_FUNCTION, $accum);
  288. else
  289. throw new XmlImportException("Call to undefined function \"$accum\"");
  290. }
  291. }
  292. }
  293. /**
  294. * Scans string literal
  295. *
  296. * @param XmlImportReaderInterface $input
  297. * @return XmlImportToken
  298. */
  299. private function scanString(XmlImportReaderInterface $input)
  300. {
  301. $accum = '';
  302. while(($ch = $input->peek()) !== false)
  303. {
  304. if ($ch == '"' && (strlen($accum) == 0 || $accum[strlen($accum) - 1] != "\\"))
  305. {
  306. //skip "
  307. $input->read();
  308. $accum = str_replace("\\\"", '"', $accum);
  309. if ($this->isLangBegin)
  310. {
  311. $this->isLangBegin = false;
  312. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_STRING, $accum));
  313. }
  314. else
  315. {
  316. return new XmlImportToken(XmlImportToken::KIND_STRING, $accum);
  317. }
  318. }
  319. else
  320. $accum .= $input->read();
  321. }
  322. throw new XmlImportException('Unexpected end of string literal "' . $accum . '"');
  323. }
  324. /**
  325. * Scans number
  326. *
  327. * @param XmlImportReaderInterface $input
  328. * @return XmlImportToken
  329. */
  330. private function scanNumber(XmlImportReaderInterface $input)
  331. {
  332. $isInt = true;
  333. $accum = $this->scanInt($input);
  334. if ($input->peek() == '.')
  335. {
  336. $isInt = false;
  337. $accum .= $input->read();
  338. $accum .= $this->scanNumberFrac($input);
  339. }
  340. if (strtolower($input->peek()) == 'e' )
  341. {
  342. $isInt = false;
  343. $accum .= $input->read();
  344. $accum .= $this->scanInt($input);
  345. }
  346. if ($isInt)
  347. {
  348. if ($this->isLangBegin)
  349. {
  350. $this->isLangBegin = false;
  351. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_INT, intval($accum)));
  352. }
  353. else
  354. {
  355. return new XmlImportToken(XmlImportToken::KIND_INT, intval($accum));
  356. }
  357. }
  358. else
  359. {
  360. if ($this->isLangBegin)
  361. {
  362. $this->isLangBegin = false;
  363. return array(new XmlImportToken(XmlImportToken::KIND_PRINT), new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum)));
  364. }
  365. else
  366. {
  367. return new XmlImportToken(XmlImportToken::KIND_FLOAT, floatval($accum));
  368. }
  369. }
  370. }
  371. /**
  372. * Scans integer number
  373. *
  374. * @param XmlImportReaderInterface $input
  375. * @return string
  376. */
  377. private function scanInt(XmlImportReaderInterface $input)
  378. {
  379. if (preg_match('/(\d)/', $input->peek()))
  380. {
  381. $accum = $input->read();
  382. if ($accum == '-' && !preg_match('/\d/', $input->peek()))
  383. throw new XmlImportException("Expected digit after a minus");
  384. while (preg_match('/\d/', $input->peek()))
  385. {
  386. $accum .= $input->read();
  387. }
  388. return $accum;
  389. }
  390. else
  391. throw new XmlImportException("digit or '-' expected in a number");
  392. }
  393. /**
  394. * Scans fraction part of a number
  395. *
  396. * @param XmlImportReaderInterface $input
  397. * @return string
  398. */
  399. private function scanNumberFrac(XmlImportReaderInterface $input)
  400. {
  401. $accum = '';
  402. while (preg_match('/\d/', $input->peek()))
  403. {
  404. $accum .= $input->read();
  405. }
  406. if (strlen($accum) == 0)
  407. throw new XmlImportException("Digits are expected after a '.'");
  408. return $accum;
  409. }
  410. }