/lib/eztemplate/classes/eztemplateelementparser.php

http://github.com/ezsystems/ezpublish · PHP · 603 lines · 528 code · 21 blank · 54 comment · 76 complexity · 35f72d8d3decce898a15f4c6515727a3 MD5 · raw file

  1. <?php
  2. /**
  3. * File containing the eZTemplateElementParser class.
  4. *
  5. * @copyright Copyright (C) eZ Systems AS. All rights reserved.
  6. * @license For full copyright and license information view LICENSE file distributed with this source code.
  7. * @version //autogentag//
  8. * @package lib
  9. */
  10. /*!
  11. \class eZTemplateElementParser eztemplateelementparser.php
  12. \brief The class eZTemplateElementParser does
  13. */
  14. class eZTemplateElementParser
  15. {
  16. function templateTypeName( $type )
  17. {
  18. switch ( $type )
  19. {
  20. case eZTemplate::TYPE_STRING:
  21. return "string";
  22. case eZTemplate::TYPE_NUMERIC:
  23. return "numeric";
  24. case eZTemplate::TYPE_IDENTIFIER:
  25. return "identifier";
  26. case eZTemplate::TYPE_VARIABLE:
  27. return "variable";
  28. case eZTemplate::TYPE_ATTRIBUTE:
  29. return "attribute";
  30. }
  31. return null;
  32. }
  33. /*!
  34. Parses the variable and operators into a structure.
  35. */
  36. function parseVariableTag( $tpl, $relatedTemplateName, &$text, $startPosition, &$endPosition, $textLength, $defaultNamespace,
  37. $allowedType = false, $maxElements = false, $endMarker = false,
  38. $undefinedType = eZTemplate::TYPE_ATTRIBUTE )
  39. {
  40. $currentPosition = $startPosition;
  41. $elements = array();
  42. $lastPosition = false;
  43. if ( $allowedType === false )
  44. $allowedType = eZTemplate::TYPE_BASIC;
  45. while ( $currentPosition < $textLength and
  46. ( $maxElements === false or
  47. count( $elements ) < $maxElements ) )
  48. {
  49. if ( $lastPosition !== false and
  50. $lastPosition == $currentPosition )
  51. {
  52. $tpl->error( "ElementParser::parseVariableTag", "parser error @ $relatedTemplateName[$currentPosition]\n" .
  53. "Parser position did not move, this is most likely a bug in the template parser." );
  54. break;
  55. }
  56. $lastPosition = $currentPosition;
  57. $currentPosition = $this->whitespaceEndPos( $tpl, $text, $currentPosition, $textLength );
  58. if ( $currentPosition >= $textLength )
  59. continue;
  60. if ( $endMarker !== false )
  61. {
  62. if ( $currentPosition < $textLength and
  63. strpos( $endMarker, $text[$currentPosition] ) !== false )
  64. break;
  65. }
  66. if ( $text[$currentPosition] == '|' )
  67. {
  68. if ( !( $allowedType & eZTemplate::TYPE_OPERATOR_BIT ) )
  69. {
  70. $currentPosition = $lastPosition;
  71. break;
  72. }
  73. $maxOperatorElements = 1;
  74. $operatorEndMarker = false;
  75. $currentOperatorPosition = $currentPosition + 1;
  76. $operatorEndPosition = false;
  77. $operatorElements = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentOperatorPosition, $operatorEndPosition, $textLength, $defaultNamespace,
  78. eZTemplate::TYPE_OPERATOR_BIT, $maxOperatorElements, $operatorEndMarker, eZTemplate::TYPE_OPERATOR );
  79. if ( $operatorEndPosition > $currentOperatorPosition )
  80. {
  81. $elements = array_merge( $elements, $operatorElements );
  82. $currentPosition = $operatorEndPosition;
  83. }
  84. }
  85. else if ( $text[$currentPosition] == '.' or
  86. $text[$currentPosition] == '[' )
  87. {
  88. if ( !( $allowedType & eZTemplate::TYPE_ATTRIBUTE_BIT ) )
  89. {
  90. $currentPosition = $lastPosition;
  91. break;
  92. }
  93. $maxAttributeElements = 1;
  94. $attributeEndMarker = false;
  95. if ( $text[$currentPosition] == '[' )
  96. {
  97. $maxAttributeElements = false;
  98. $attributeEndMarker = ']';
  99. }
  100. ++$currentPosition;
  101. $attributeEndPosition = false;
  102. $attributeElements = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentPosition, $attributeEndPosition, $textLength, $defaultNamespace,
  103. eZTemplate::TYPE_BASIC, $maxAttributeElements, $attributeEndMarker );
  104. if ( $attributeEndPosition > $currentPosition )
  105. {
  106. $element = array( eZTemplate::TYPE_ATTRIBUTE, // type
  107. $attributeElements, // content
  108. false // debug
  109. );
  110. $elements[] = $element;
  111. if ( $attributeEndMarker !== false )
  112. $attributeEndPosition += strlen( $attributeEndMarker );
  113. $currentPosition = $attributeEndPosition;
  114. }
  115. }
  116. else if ( $text[$currentPosition] == "$" )
  117. {
  118. if ( !( $allowedType & eZTemplate::TYPE_VARIABLE_BIT ) )
  119. {
  120. $currentPosition = $lastPosition;
  121. break;
  122. }
  123. ++$currentPosition;
  124. $variableEndPosition = $this->variableEndPos( $tpl, $relatedTemplateName, $text, $currentPosition, $textLength,
  125. $variableNamespace, $variableName, $namespaceScope );
  126. if ( $variableEndPosition > $currentPosition )
  127. {
  128. $element = array( eZTemplate::TYPE_VARIABLE, // type
  129. array( $variableNamespace,
  130. $namespaceScope,
  131. $variableName ), // content
  132. false // debug
  133. );
  134. $elements[] = $element;
  135. $currentPosition = $variableEndPosition;
  136. $allowedType = eZTemplate::TYPE_MODIFIER_MASK;
  137. }
  138. }
  139. else if ( $text[$currentPosition] == "'" or
  140. $text[$currentPosition] == '"' )
  141. {
  142. if ( !( $allowedType & eZTemplate::TYPE_STRING_BIT) )
  143. {
  144. $currentPosition = $lastPosition;
  145. break;
  146. }
  147. $quote = $text[$currentPosition];
  148. ++$currentPosition;
  149. $quoteEndPosition = $this->quoteEndPos( $tpl, $text, $currentPosition, $textLength, $quote );
  150. $string = substr( $text, $currentPosition, $quoteEndPosition - $currentPosition );
  151. $string = $this->unescapeCharacters( $string );
  152. $element = array( eZTemplate::TYPE_STRING, // type
  153. $string, // content
  154. false // debug
  155. );
  156. $elements[] = $element;
  157. $currentPosition = $quoteEndPosition + 1;
  158. $allowedType = eZTemplate::TYPE_OPERATOR_BIT;
  159. }
  160. else
  161. {
  162. $float = true;
  163. $numericEndPosition = $this->numericEndPos( $tpl, $text, $currentPosition, $textLength, $float );
  164. if ( $numericEndPosition > $currentPosition )
  165. {
  166. if ( !( $allowedType & eZTemplate::TYPE_NUMERIC_BIT ) )
  167. {
  168. $currentPosition = $lastPosition;
  169. break;
  170. }
  171. // We got a number
  172. $number = substr( $text, $currentPosition, $numericEndPosition - $currentPosition );
  173. if ( $float )
  174. $number = (float)$number;
  175. else
  176. $number = (int)$number;
  177. $element = array( eZTemplate::TYPE_NUMERIC, // type
  178. $number, // content
  179. false // debug
  180. );
  181. $elements[] = $element;
  182. $currentPosition = $numericEndPosition;
  183. $allowedType = eZTemplate::TYPE_OPERATOR_BIT;
  184. }
  185. else
  186. {
  187. $identifierEndPosition = $this->identifierEndPosition( $tpl, $text, $currentPosition, $textLength );
  188. if ( $currentPosition == $identifierEndPosition )
  189. {
  190. $currentPosition = $lastPosition;
  191. break;
  192. }
  193. if ( ( $identifierEndPosition < $textLength and
  194. $text[$identifierEndPosition] == '(' ) or
  195. $undefinedType == eZTemplate::TYPE_OPERATOR )
  196. {
  197. if ( !( $allowedType & eZTemplate::TYPE_OPERATOR_BIT ) )
  198. {
  199. $currentPosition = $lastPosition;
  200. break;
  201. }
  202. $operatorName = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
  203. $operatorParameterElements = array( $operatorName );
  204. if ( $identifierEndPosition < $textLength and
  205. $text[$identifierEndPosition] == '(' )
  206. {
  207. $currentPosition = $identifierEndPosition + 1;
  208. $currentOperatorPosition = $currentPosition;
  209. $operatorDone = false;
  210. $parameterCount = 0;
  211. while ( !$operatorDone )
  212. {
  213. $operatorEndPosition = false;
  214. $operatorParameterElement = $this->parseVariableTag( $tpl, $relatedTemplateName, $text, $currentOperatorPosition, $operatorEndPosition, $textLength, $defaultNamespace,
  215. eZTemplate::TYPE_BASIC, false, ',)' );
  216. if ( $operatorEndPosition < $textLength and
  217. $text[$operatorEndPosition] == ',' )
  218. {
  219. if ( $operatorEndPosition == $currentOperatorPosition )
  220. {
  221. $operatorParameterElements[] = null;
  222. }
  223. else
  224. $operatorParameterElements[] = $operatorParameterElement;
  225. ++$parameterCount;
  226. $currentOperatorPosition = $operatorEndPosition + 1;
  227. }
  228. else if ( $operatorEndPosition < $textLength and
  229. $text[$operatorEndPosition] == ')' )
  230. {
  231. $operatorDone = true;
  232. if ( $operatorEndPosition == $currentOperatorPosition )
  233. {
  234. if ( $parameterCount > 0 )
  235. {
  236. $operatorParameterElements[] = null;
  237. ++$parameterCount;
  238. }
  239. }
  240. else
  241. {
  242. $operatorParameterElements[] = $operatorParameterElement;
  243. ++$parameterCount;
  244. }
  245. ++$operatorEndPosition;
  246. }
  247. else
  248. {
  249. $currentPosition = $lastPosition;
  250. break;
  251. }
  252. }
  253. if ( !$operatorDone )
  254. break;
  255. }
  256. else
  257. {
  258. $operatorEndPosition = $identifierEndPosition;
  259. }
  260. $element = array( eZTemplate::TYPE_OPERATOR, // type
  261. $operatorParameterElements, // content
  262. false // debug
  263. );
  264. $elements[] = $element;
  265. $currentPosition = $operatorEndPosition;
  266. $allowedType = eZTemplate::TYPE_MODIFIER_MASK;
  267. }
  268. else
  269. {
  270. if ( !( $allowedType & eZTemplate::TYPE_IDENTIFIER_BIT ) )
  271. {
  272. $currentPosition = $lastPosition;
  273. break;
  274. }
  275. $identifier = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
  276. $element = array( eZTemplate::TYPE_IDENTIFIER, // type
  277. $identifier, // content
  278. false // debug
  279. );
  280. $elements[] = $element;
  281. $currentPosition = $identifierEndPosition;
  282. $allowedType = eZTemplate::TYPE_NONE;
  283. }
  284. }
  285. }
  286. }
  287. $endPosition = $currentPosition;
  288. return $elements;
  289. }
  290. /*!
  291. Returns the end position of the variable.
  292. */
  293. function variableEndPos( $tpl, $relatedTemplateName, &$text, $startPosition, $textLength,
  294. &$namespace, &$name, &$scope )
  295. {
  296. $currentPosition = $startPosition;
  297. $namespaces = array();
  298. $variableName = false;
  299. $lastPosition = false;
  300. $scopeType = eZTemplate::NAMESPACE_SCOPE_LOCAL;
  301. $scopeRead = false;
  302. while ( $currentPosition < $textLength )
  303. {
  304. if ( $lastPosition !== false and
  305. $lastPosition == $currentPosition )
  306. {
  307. $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
  308. "Parser position did not move, this is most likely a bug in the template parser." );
  309. break;
  310. }
  311. $lastPosition = $currentPosition;
  312. if ( $text[$currentPosition] == '#' )
  313. {
  314. if ( $scopeRead )
  315. {
  316. $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
  317. "Namespace scope already declared, cannot set to global." );
  318. }
  319. else
  320. {
  321. $scopeType = eZTemplate::NAMESPACE_SCOPE_GLOBAL;
  322. }
  323. $scopeRead = true;
  324. ++$currentPosition;
  325. }
  326. else if ( $text[$currentPosition] == ':' )
  327. {
  328. if ( $scopeRead )
  329. {
  330. $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
  331. "Namespace scope already declared, cannot set to relative." );
  332. }
  333. else
  334. {
  335. $scopeType = eZTemplate::NAMESPACE_SCOPE_RELATIVE;
  336. }
  337. $scopeRead = true;
  338. ++$currentPosition;
  339. }
  340. else
  341. {
  342. $identifierEndPosition = $this->identifierEndPosition( $tpl, $text, $currentPosition, $textLength );
  343. if ( $identifierEndPosition > $currentPosition )
  344. {
  345. $identifier = substr( $text, $currentPosition, $identifierEndPosition - $currentPosition );
  346. $currentPosition = $identifierEndPosition;
  347. if ( $identifierEndPosition < $textLength and
  348. $text[$identifierEndPosition] == ':' )
  349. {
  350. $namespaces[] = $identifier;
  351. ++$currentPosition;
  352. }
  353. else
  354. $variableName = $identifier;
  355. }
  356. else if ( $identifierEndPosition < $textLength and
  357. ( $text[$identifierEndPosition] != ":" and
  358. $text[$identifierEndPosition] != "#" ) )
  359. {
  360. if ( $variableName === false )
  361. {
  362. $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
  363. "No variable name found, this is most likely a bug in the template parser." );
  364. return $startPosition;
  365. }
  366. break;
  367. }
  368. else
  369. {
  370. $tpl->error( "ElementParser::variableEndPos", "parser error @ $relatedTemplateName\[" . $currentPosition . "]\n" .
  371. "Missing identifier for variable name or namespace, this is most likely a bug in the template parser." );
  372. return $startPosition;
  373. }
  374. }
  375. }
  376. $scope = $scopeType;
  377. $namespace = implode( ':', $namespaces );
  378. $name = $variableName;
  379. return $currentPosition;
  380. }
  381. /*!
  382. Finds any escaped characters and unescapes them if they are one of:
  383. - \n - A newline
  384. - \r - A carriage return
  385. - \t - A tab
  386. - \' - A single quote
  387. - \" - A double quote
  388. If the escaped character is not known it keeps both characters (escape + character).
  389. \return The transformed string without escape characters.
  390. */
  391. function unescapeCharacters( $string )
  392. {
  393. $newString = '';
  394. $len = strlen( $string );
  395. // Fix escaped characters (double-quote, single-quote, newline, carriage-return, tab)
  396. for ( $i = 0; $i < $len; ++$i )
  397. {
  398. $c = $string[$i];
  399. // If we don't have an escape character we keep it as-is
  400. if ( $c != "\\" )
  401. {
  402. $newString .= $c;
  403. continue;
  404. }
  405. // If this is the last character we keep it as-is
  406. if ( $i + 1 >= $len )
  407. {
  408. $newString .= $c;
  409. break;
  410. }
  411. $c2 = $string[++$i];
  412. switch ( $c2 )
  413. {
  414. case 'n':
  415. {
  416. $newString .= "\n";
  417. } break;
  418. case 'r':
  419. {
  420. $newString .= "\r";
  421. } break;
  422. case 't':
  423. {
  424. $newString .= "\t";
  425. } break;
  426. case "'":
  427. case '"':
  428. case '\\':
  429. {
  430. $newString .= $c2;
  431. } break;
  432. // If it is not known we keep the characters.
  433. default:
  434. {
  435. $newString .= $c . $c2;
  436. }
  437. }
  438. }
  439. return $newString;
  440. }
  441. /*!
  442. Returns the end position of the identifier.
  443. If no identifier was found the end position is returned.
  444. */
  445. function identifierEndPosition( $tpl, &$text, $start_pos, $len )
  446. {
  447. $pos = $start_pos;
  448. while ( $pos < $len )
  449. {
  450. if ( !preg_match( "/^[a-zA-Z0-9_-]$/", $text[$pos] ) )
  451. {
  452. return $pos;
  453. }
  454. ++$pos;
  455. }
  456. return $pos;
  457. }
  458. /*!
  459. Returns the end position of the quote $quote.
  460. If no quote was found the end position is returned.
  461. */
  462. function quoteEndPos( $tpl, &$text, $startPosition, $textLength, $quote )
  463. {
  464. $currentPosition = $startPosition;
  465. while ( $currentPosition < $textLength )
  466. {
  467. if ( $text[$currentPosition] == "\\" )
  468. ++$currentPosition;
  469. else if ( $text[$currentPosition] == $quote )
  470. return $currentPosition;
  471. ++$currentPosition;
  472. }
  473. return $currentPosition;
  474. }
  475. /*!
  476. Returns the end position of the numeric.
  477. If no numeric was found the end position is returned.
  478. */
  479. function numericEndPos( $tpl, &$text, $start_pos, $len,
  480. &$float )
  481. {
  482. $pos = $start_pos;
  483. $has_comma = false;
  484. $numberPos = $pos;
  485. if ( $pos < $len )
  486. {
  487. if ( $text[$pos] == '-' )
  488. {
  489. ++$pos;
  490. $numberPos = $pos;
  491. }
  492. }
  493. while ( $pos < $len )
  494. {
  495. if ( $text[$pos] == "." and $float )
  496. {
  497. if ( $has_comma )
  498. {
  499. if ( !$has_comma and
  500. $float )
  501. $float = false;
  502. return $pos;
  503. }
  504. $has_comma = $pos;
  505. }
  506. else if ( $text[$pos] < '0' or $text[$pos] > '9' )
  507. {
  508. if ( !$has_comma and
  509. $float )
  510. $float = false;
  511. if ( $pos < $len and
  512. $has_comma and
  513. $pos == $has_comma + 1 )
  514. {
  515. return $start_pos;
  516. }
  517. if ( $pos == $numberPos )
  518. {
  519. return $start_pos;
  520. }
  521. return $pos;
  522. }
  523. ++$pos;
  524. }
  525. if ( !$has_comma and
  526. $float )
  527. $float = false;
  528. if ( $has_comma and
  529. $start_pos + 1 == $pos )
  530. {
  531. return $start_pos;
  532. }
  533. return $pos;
  534. }
  535. /*!
  536. Returns the position of the first non-whitespace characters.
  537. */
  538. function whitespaceEndPos( $tpl, &$text, $currentPosition, $textLength )
  539. {
  540. if ( $currentPosition >= $textLength )
  541. return $currentPosition;
  542. while( $currentPosition < $textLength and
  543. ( $text[$currentPosition] === ' '
  544. or $text[$currentPosition] === "\n"
  545. or $text[$currentPosition] === "\t"
  546. or $text[$currentPosition] === "\r" ) )
  547. {
  548. ++$currentPosition;
  549. }
  550. return $currentPosition;
  551. }
  552. /*!
  553. Returns the position of the first non-whitespace characters.
  554. */
  555. function isWhitespace( $tpl, &$text, $startPosition )
  556. {
  557. return ( $text[$startPosition] === ' '
  558. or $text[$startPosition] === "\n"
  559. or $text[$startPosition] === "\t"
  560. or $text[$startPosition] === "\r" );
  561. }
  562. /**
  563. * Returns a shared instance of the eZTemplateElementParser class.
  564. *
  565. * @return eZTemplateElementParser
  566. */
  567. static function instance()
  568. {
  569. if ( !isset( $GLOBALS['eZTemplateElementParserInstance'] ) ||
  570. !( $GLOBALS['eZTemplateElementParserInstance'] instanceof eZTemplateElementParser ) )
  571. {
  572. $GLOBALS['eZTemplateElementParserInstance'] = new eZTemplateElementParser();
  573. }
  574. return $GLOBALS['eZTemplateElementParserInstance'];
  575. }
  576. }
  577. ?>