/lib/eztemplate/classes/eztemplatemultipassparser.php

https://github.com/GunioRobot/ezpublish · PHP · 904 lines · 865 code · 13 blank · 26 comment · 21 complexity · 496f2c01f632da39af2099c4c818d3a4 MD5 · raw file

  1. <?php
  2. /**
  3. * File containing the eZTemplateMultiPassParser class.
  4. *
  5. * @copyright Copyright (C) 1999-2011 eZ Systems AS. All rights reserved.
  6. * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
  7. * @version //autogentag//
  8. * @package lib
  9. */
  10. /*!
  11. \class eZTemplateMultiPassParser eztemplatemultipassparser.php
  12. \brief The class eZTemplateMultiPassParser does
  13. */
  14. class eZTemplateMultiPassParser extends eZTemplateParser
  15. {
  16. /*!
  17. Constructor
  18. */
  19. function eZTemplateMultiPassParser()
  20. {
  21. $this->ElementParser = eZTemplateElementParser::instance();
  22. }
  23. /*!
  24. Parses the template file $sourceText. See the description of this class
  25. for more information on the parsing process.
  26. */
  27. function parse( $tpl, $sourceText, &$rootElement, $rootNamespace, &$resourceData )
  28. {
  29. $relatedResource = $resourceData['resource'];
  30. $relatedTemplateName = $resourceData['template-filename'];
  31. $currentRoot =& $rootElement;
  32. $leftDelimiter = $tpl->LDelim;
  33. $rightDelimiter = $tpl->RDelim;
  34. $sourceLength = strlen( $sourceText );
  35. $sourcePosition = 0;
  36. eZDebug::accumulatorStart( 'template_multi_parser_1', 'template_total', 'Template parser: create text elements' );
  37. $textElements = $this->parseIntoTextElements( $tpl, $sourceText, $sourcePosition,
  38. $leftDelimiter, $rightDelimiter, $sourceLength,
  39. $relatedTemplateName );
  40. eZDebug::accumulatorStop( 'template_multi_parser_1' );
  41. eZDebug::accumulatorStart( 'template_multi_parser_2', 'template_total', 'Template parser: remove whitespace' );
  42. $textElements = $this->parseWhitespaceRemoval( $tpl, $textElements );
  43. eZDebug::accumulatorStop( 'template_multi_parser_2' );
  44. eZDebug::accumulatorStart( 'template_multi_parser_3', 'template_total', 'Template parser: construct tree' );
  45. $this->parseIntoTree( $tpl, $textElements, $currentRoot,
  46. $rootNamespace, $relatedResource, $relatedTemplateName );
  47. eZDebug::accumulatorStop( 'template_multi_parser_3' );
  48. }
  49. function gotoEndPosition( $text, $line, $column, &$endLine, &$endColumn )
  50. {
  51. $lines = preg_split( "#\r\n|\r|\n#", $text );
  52. $c = count( $lines );
  53. if ( $c > 0 )
  54. {
  55. $endLine = $line + $c - 1;
  56. $lastLine = $lines[$c - 1];
  57. if ( $c > 1 )
  58. $endColumn = strlen( $lastLine );
  59. else
  60. $endColumn = $column + strlen( $lastLine );
  61. }
  62. else
  63. {
  64. $endLine = $line;
  65. $endColumn = $column;
  66. }
  67. }
  68. function parseIntoTextElements( $tpl, $sourceText, $sourcePosition,
  69. $leftDelimiter, $rightDelimiter, $sourceLength,
  70. $relatedTemplateName )
  71. {
  72. if ( $tpl->ShowDetails )
  73. eZDebug::addTimingPoint( "Parse pass 1 (simple tag parsing)" );
  74. $currentLine = 1;
  75. $currentColumn = 0;
  76. $textElements = array();
  77. while( $sourcePosition < $sourceLength )
  78. {
  79. $tagPos = strpos( $sourceText, $leftDelimiter, $sourcePosition );
  80. if ( $tagPos === false )
  81. {
  82. // No more tags
  83. unset( $data );
  84. $data = substr( $sourceText, $sourcePosition );
  85. $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
  86. $textElements[] = array( "text" => $data,
  87. "type" => eZTemplate::ELEMENT_TEXT,
  88. 'placement' => array( 'templatefile' => $relatedTemplateName,
  89. 'start' => array( 'line' => $currentLine,
  90. 'column' => $currentColumn,
  91. 'position' => $sourcePosition ),
  92. 'stop' => array( 'line' => $endLine,
  93. 'column' => $endColumn,
  94. 'position' => $sourceLength - 1 ) ) );
  95. $sourcePosition = $sourceLength;
  96. $currentLine = $endLine;
  97. $currentColumn = $endColumn;
  98. }
  99. else
  100. {
  101. $blockStart = $tagPos;
  102. $tagPos++;
  103. if ( $tagPos < $sourceLength and
  104. $sourceText[$tagPos] == "*" ) // Comment
  105. {
  106. $endPos = strpos( $sourceText, "*$rightDelimiter", $tagPos + 1 );
  107. $len = $endPos - $tagPos;
  108. if ( $sourcePosition < $blockStart )
  109. {
  110. // Add text before tag.
  111. unset( $data );
  112. $data = substr( $sourceText, $sourcePosition, $blockStart - $sourcePosition );
  113. $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
  114. $textElements[] = array( "text" => $data,
  115. "type" => eZTemplate::ELEMENT_TEXT,
  116. 'placement' => array( 'templatefile' => $relatedTemplateName,
  117. 'start' => array( 'line' => $currentLine,
  118. 'column' => $currentColumn,
  119. 'position' => $sourcePosition ),
  120. 'stop' => array( 'line' => $endLine,
  121. 'column' => $endColumn,
  122. 'position' => $blockStart ) ) );
  123. $currentLine = $endLine;
  124. $currentColumn = $endColumn;
  125. }
  126. if ( $endPos === false )
  127. {
  128. $endPos = $sourceLength;
  129. $blockEnd = $sourceLength;
  130. }
  131. else
  132. {
  133. $blockEnd = $endPos + 2;
  134. }
  135. $comment_text = substr( $sourceText, $tagPos + 1, $endPos - $tagPos - 1 );
  136. $this->gotoEndPosition( $comment_text, $currentLine, $currentColumn, $endLine, $endColumn );
  137. $textElements[] = array( "text" => $comment_text,
  138. "type" => eZTemplate::ELEMENT_COMMENT,
  139. 'placement' => array( 'templatefile' => $relatedTemplateName,
  140. 'start' => array( 'line' => $currentLine,
  141. 'column' => $currentColumn,
  142. 'position' => $tagPos + 1 ),
  143. 'stop' => array( 'line' => $endLine,
  144. 'column' => $endColumn,
  145. 'position' => $endPos - 1 ) ) );
  146. if ( $sourcePosition < $blockEnd )
  147. $sourcePosition = $blockEnd;
  148. $currentLine = $endLine;
  149. $currentColumn = $endColumn;
  150. // eZDebug::writeDebug( "eZTemplate: Comment: $comment" );
  151. }
  152. else
  153. {
  154. $tmp_pos = $tagPos;
  155. while( ( $endPos = strpos( $sourceText, $rightDelimiter, $tmp_pos ) ) !== false )
  156. {
  157. if ( $sourceText[$endPos-1] != "\\" )
  158. break;
  159. $tmp_pos = $endPos + 1;
  160. }
  161. if ( $endPos === false )
  162. {
  163. // Unterminated tag
  164. $data = substr( $sourceText, $sourcePosition );
  165. $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
  166. $textBefore = substr( $sourceText, $sourcePosition, $tagPos - $sourcePosition );
  167. $textPortion = substr( $sourceText, $tagPos );
  168. $this->gotoEndPosition( $textBefore, $currentLine, $currentColumn, $tagStartLine, $tagStartColumn );
  169. $this->gotoEndPosition( $textPortion, $tagStartLine, $tagStartColumn, $tagEndLine, $tagEndColumn );
  170. $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
  171. "Unterminated tag, needs a $rightDelimiter to end the tag.\n" . $leftDelimiter . $textPortion,
  172. array( array( $tagStartLine, $tagStartColumn, $tagPosition ),
  173. array( $tagEndLine, $tagEndColumn, $sourceLength - 1 ),
  174. $relatedTemplateName ) );
  175. $textElements[] = array( "text" => $data,
  176. "type" => eZTemplate::ELEMENT_TEXT,
  177. 'placement' => array( 'templatefile' => $relatedTemplateName,
  178. 'start' => array( 'line' => $currentLine,
  179. 'column' => $currentColumn,
  180. 'position' => $sourcePosition ),
  181. 'stop' => array( 'line' => $endLine,
  182. 'column' => $endColumn,
  183. 'position' => $sourceLength - 1 ) ) );
  184. $sourcePosition = $sourceLength;
  185. $currentLine = $endLine;
  186. $currentColumn = $endColumn;
  187. }
  188. else
  189. {
  190. $blockEnd = $endPos + 1;
  191. $len = $endPos - $tagPos;
  192. if ( $sourcePosition < $blockStart )
  193. {
  194. // Add text before tag.
  195. $data = substr( $sourceText, $sourcePosition, $blockStart - $sourcePosition );
  196. $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
  197. $textElements[] = array( "text" => $data,
  198. "type" => eZTemplate::ELEMENT_TEXT,
  199. 'placement' => array( 'templatefile' => $relatedTemplateName,
  200. 'start' => array( 'line' => $currentLine,
  201. 'column' => $currentColumn,
  202. 'position' => $sourcePosition ),
  203. 'stop' => array( 'line' => $endLine,
  204. 'column' => $endColumn,
  205. 'position' => $blockStart ) ) );
  206. $currentLine = $endLine;
  207. $currentColumn = $endColumn;
  208. }
  209. $tag = substr( $sourceText, $tagPos, $len );
  210. $tag = preg_replace( "/\\\\[}]/", "}", $tag );
  211. $tagTrim = trim( $tag );
  212. $isEndTag = false;
  213. $isSingleTag = false;
  214. if ( $tag[0] == "/" )
  215. {
  216. $isEndTag = true;
  217. $tag = substr( $tag, 1 );
  218. }
  219. else if ( $tagTrim[strlen( $tagTrim ) - 1] == "/" )
  220. {
  221. $isSingleTag = true;
  222. $tagTrim = trim( substr( $tagTrim, 0, strlen( $tagTrim ) - 1 ) );
  223. $tag = $tagTrim;
  224. }
  225. $this->gotoEndPosition( $tag, $currentLine, $currentColumn, $endLine, $endColumn );
  226. if ( $tag[0] == "$" or
  227. $tag[0] == "\"" or
  228. $tag[0] == "'" or
  229. is_numeric( $tag[0] ) or
  230. ( $tag[0] == '-' and
  231. isset( $tag[1] ) and
  232. is_numeric( $tag[1] ) ) or
  233. preg_match( "/^[a-z0-9_-]+\(/", $tag ) )
  234. {
  235. $textElements[] = array( "text" => $tag,
  236. "type" => eZTemplate::ELEMENT_VARIABLE,
  237. 'placement' => array( 'templatefile' => $relatedTemplateName,
  238. 'start' => array( 'line' => $currentLine,
  239. 'column' => $currentColumn,
  240. 'position' => $blockStart + 1 ),
  241. 'stop' => array( 'line' => $endLine,
  242. 'column' => $endColumn,
  243. 'position' => $blockEnd - 1 ) ) );
  244. }
  245. else
  246. {
  247. $type = eZTemplate::ELEMENT_NORMAL_TAG;
  248. if ( $isEndTag )
  249. $type = eZTemplate::ELEMENT_END_TAG;
  250. else if ( $isSingleTag )
  251. $type = eZTemplate::ELEMENT_SINGLE_TAG;
  252. $spacepos = strpos( $tag, " " );
  253. if ( $spacepos === false )
  254. $name = $tag;
  255. else
  256. $name = substr( $tag, 0, $spacepos );
  257. if ( isset( $tpl->Literals[$name] ) )
  258. {
  259. $literalEndTag = "{/$name}";
  260. $literalEndPos = strpos( $sourceText, $literalEndTag, $blockEnd );
  261. if ( $literalEndPos === false )
  262. $literalEndPos = $sourceLength;
  263. $data = substr( $sourceText, $blockEnd, $literalEndPos - $blockEnd );
  264. $this->gotoEndPosition( $data, $currentLine, $currentColumn, $endLine, $endColumn );
  265. $blockEnd = $literalEndPos + strlen( $literalEndTag );
  266. $textElements[] = array( "text" => $data,
  267. "type" => eZTemplate::ELEMENT_TEXT,
  268. 'placement' => false );
  269. }
  270. else
  271. $textElements[] = array( "text" => $tag,
  272. "name" => $name,
  273. "type" => $type,
  274. 'placement' => array( 'templatefile' => $relatedTemplateName,
  275. 'start' => array( 'line' => $currentLine,
  276. 'column' => $currentColumn,
  277. 'position' => $blockStart + 1 ),
  278. 'stop' => array( 'line' => $endLine,
  279. 'column' => $endColumn,
  280. 'position' => $blockEnd - 1 ) ) );
  281. }
  282. if ( $sourcePosition < $blockEnd )
  283. $sourcePosition = $blockEnd;
  284. $currentLine = $endLine;
  285. $currentColumn = $endColumn;
  286. }
  287. }
  288. }
  289. }
  290. return $textElements;
  291. }
  292. function parseWhitespaceRemoval( $tpl, &$textElements )
  293. {
  294. if ( $tpl->ShowDetails )
  295. eZDebug::addTimingPoint( "Parse pass 2 (whitespace removal)" );
  296. $tempTextElements = array();
  297. $textElements[] = null;
  298. $element = false;
  299. foreach( $textElements as $nextElement )
  300. {
  301. if ( $element )
  302. {
  303. switch ( $element["type"] )
  304. {
  305. case eZTemplate::ELEMENT_COMMENT:
  306. {
  307. // Ignore comments
  308. } break;
  309. case eZTemplate::ELEMENT_TEXT:
  310. case eZTemplate::ELEMENT_VARIABLE:
  311. {
  312. if ( $nextElement !== null )
  313. {
  314. switch ( $nextElement["type"] )
  315. {
  316. case eZTemplate::ELEMENT_END_TAG:
  317. case eZTemplate::ELEMENT_SINGLE_TAG:
  318. case eZTemplate::ELEMENT_NORMAL_TAG:
  319. {
  320. $text = $element["text"];
  321. $text_cnt = strlen( $text );
  322. if ( $text_cnt > 0 )
  323. {
  324. $char = $text[$text_cnt - 1];
  325. if ( $char == "\n" )
  326. {
  327. $element["text"] = substr( $text, 0, $text_cnt - 1 );
  328. }
  329. }
  330. } break;
  331. }
  332. }
  333. if ( $element["text"] !== '' )
  334. {
  335. $tempTextElements[] = $element;
  336. }
  337. } break;
  338. case eZTemplate::ELEMENT_END_TAG:
  339. case eZTemplate::ELEMENT_SINGLE_TAG:
  340. case eZTemplate::ELEMENT_NORMAL_TAG:
  341. {
  342. if ( $nextElement !== null )
  343. {
  344. switch ( $nextElement["type"] )
  345. {
  346. case eZTemplate::ELEMENT_TEXT:
  347. case eZTemplate::ELEMENT_VARIABLE:
  348. {
  349. $text = $nextElement["text"];
  350. $text_cnt = strlen( $text );
  351. if ( $text_cnt > 0 )
  352. {
  353. $char = $text[0];
  354. if ( $char == "\n" )
  355. {
  356. $nextElement["text"] = substr( $text, 1 );
  357. }
  358. }
  359. } break;
  360. }
  361. }
  362. $tempTextElements[] = $element;
  363. } break;
  364. }
  365. }
  366. $element = $nextElement;
  367. }
  368. return $tempTextElements;
  369. }
  370. function appendChild( &$root, &$node )
  371. {
  372. if ( !is_array( $root[1] ) )
  373. $root[1] = array();
  374. $root[1][] =& $node;
  375. }
  376. function parseIntoTree( $tpl, &$textElements, &$treeRoot,
  377. $rootNamespace, $relatedResource, $relatedTemplateName )
  378. {
  379. $outerElseTags = array( 'else' => array( 'if', 'elseif' ), 'section-else' => array( 'section' ) );
  380. $currentRoot =& $treeRoot;
  381. if ( $tpl->ShowDetails )
  382. eZDebug::addTimingPoint( "Parse pass 3 (build tree)" );
  383. $tagStack = array();
  384. foreach( $textElements as $elementKey => $element )
  385. {
  386. $elementPlacement = $element['placement'];
  387. $startLine = $elementPlacement['start']['line'];
  388. $startColumn = $elementPlacement['start']['column'];
  389. $startPosition = $elementPlacement['start']['position'];
  390. $stopLine = $elementPlacement['stop']['line'];
  391. $stopColumn = $elementPlacement['stop']['column'];
  392. $stopPosition = $elementPlacement['stop']['position'];
  393. $templateFile = $elementPlacement['templatefile'];
  394. $placement = array( array( $startLine,
  395. $startColumn,
  396. $startPosition ),
  397. array( $stopLine,
  398. $stopColumn,
  399. $stopPosition ),
  400. $templateFile );
  401. switch ( $element["type"] )
  402. {
  403. case eZTemplate::ELEMENT_TEXT:
  404. {
  405. unset( $node );
  406. $node = array( eZTemplate::NODE_TEXT,
  407. false,
  408. $element['text'],
  409. $placement );
  410. $this->appendChild( $currentRoot, $node );
  411. } break;
  412. case eZTemplate::ELEMENT_VARIABLE:
  413. {
  414. $text = $element["text"];
  415. $text_len = strlen( $text );
  416. $var_data = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, 0, $var_end, $text_len, $rootNamespace );
  417. unset( $node );
  418. $node = array( eZTemplate::NODE_VARIABLE,
  419. false,
  420. $var_data,
  421. $placement );
  422. $this->appendChild( $currentRoot, $node );
  423. if ( $var_end < $text_len )
  424. {
  425. $placement = $element['placement'];
  426. $startLine = $placement['start']['line'];
  427. $startColumn = $placement['start']['column'];
  428. $subText = substr( $text, 0, $var_end );
  429. $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
  430. $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
  431. "Extra characters found in expression, they will be ignored.\n" .
  432. substr( $text, $var_end, $text_len - $var_end ) . "' (" . substr( $text, 0, $var_end ) . ")",
  433. $placement );
  434. }
  435. } break;
  436. case eZTemplate::ELEMENT_SINGLE_TAG:
  437. case eZTemplate::ELEMENT_NORMAL_TAG:
  438. case eZTemplate::ELEMENT_END_TAG:
  439. {
  440. $text = $element["text"];
  441. $text_len = strlen( $text );
  442. $type = $element["type"];
  443. $ident_pos = $this->ElementParser->identifierEndPosition( $tpl, $text, 0, $text_len );
  444. $tag = substr( $text, 0, $ident_pos - 0 );
  445. $attr_pos = $ident_pos;
  446. unset( $args );
  447. $args = array();
  448. // special handling for some functions having complex syntax
  449. if ( $type == eZTemplate::ELEMENT_NORMAL_TAG &&
  450. in_array( $tag, array( 'if', 'elseif', 'while', 'for', 'foreach', 'def', 'undef',
  451. 'set', 'let', 'default', 'set-block', 'append-block', 'section' ) ) )
  452. {
  453. $attr_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_pos, $text_len );
  454. if ( $tag == 'if' || $tag == 'elseif' )
  455. $this->parseUnnamedCondition( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  456. elseif ( $tag == 'while' )
  457. $this->parseWhileFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  458. elseif ( $tag == 'for' )
  459. $this->parseForFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  460. elseif ( $tag == 'foreach' )
  461. $this->parseForeachFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  462. elseif ( $tag == 'def' || $tag == 'undef' )
  463. $this->parseDefFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  464. elseif ( $tag == 'set' || $tag == 'let' || $tag == 'default' )
  465. $this->parseSetFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  466. elseif ( $tag == 'set-block' || $tag == 'append-block' )
  467. $this->parseBlockFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  468. elseif ( $tag == 'section' )
  469. $this->parseSectionFunction( $tag, $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  470. }
  471. elseif ( $type == eZTemplate::ELEMENT_END_TAG && $tag == 'do' )
  472. {
  473. $this->parseDoFunction( $args, $tpl, $text, $text_len, $attr_pos, $relatedTemplateName, $startLine, $startColumn, $rootNamespace );
  474. }
  475. // other functions having simplier syntax are parsed below
  476. $lastPosition = false;
  477. while ( $attr_pos < $text_len )
  478. {
  479. if ( $lastPosition !== false and
  480. $lastPosition == $attr_pos )
  481. {
  482. break;
  483. }
  484. $lastPosition = $attr_pos;
  485. $attr_pos_start = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_pos, $text_len );
  486. if ( $attr_pos_start == $attr_pos and
  487. $attr_pos_start < $text_len )
  488. {
  489. $placement = $element['placement'];
  490. $startLine = $placement['start']['line'];
  491. $startColumn = $placement['start']['column'];
  492. $subText = substr( $text, 0, $attr_pos );
  493. $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
  494. $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]" . "\n" .
  495. "Extra characters found, should have been a whitespace or the end of the expression\n".
  496. "Characters: '" . substr( $text, $attr_pos ) . "'" );
  497. break;
  498. }
  499. $attr_pos = $attr_pos_start;
  500. $attr_name_pos = $this->ElementParser->identifierEndPosition( $tpl, $text, $attr_pos, $text_len );
  501. $attr_name = substr( $text, $attr_pos, $attr_name_pos - $attr_pos );
  502. /* Skip whitespace between here and the next one */
  503. $equal_sign_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $attr_name_pos, $text_len );
  504. if ( ( $equal_sign_pos < $text_len ) && ( $text[$equal_sign_pos] == '=' ) )
  505. {
  506. $attr_name_pos = $equal_sign_pos;
  507. }
  508. if ( $attr_name_pos >= $text_len or
  509. ( $text[$attr_name_pos] != '=' and
  510. preg_match( "/[ \t\r\n]/", $text[$attr_name_pos] ) ) )
  511. {
  512. unset( $var_data );
  513. $var_data = array();
  514. $var_data[] = array( eZTemplate::TYPE_NUMERIC, // type
  515. true, // content
  516. false // debug
  517. );
  518. $args[$attr_name] = $var_data;
  519. $attr_pos = $attr_name_pos;
  520. continue;
  521. }
  522. if ( $text[$attr_name_pos] != "=" )
  523. {
  524. $placement = $element['placement'];
  525. $startLine = $placement['start']['line'];
  526. $startColumn = $placement['start']['column'];
  527. $subText = substr( $text, 0, $attr_name_pos );
  528. $this->gotoEndPosition( $subText, $startLine, $startColumn, $currentLine, $currentColumn );
  529. $tpl->error( "", "parser error @ $relatedTemplateName:$currentLine" . "[$currentColumn]\n".
  530. "Invalid parameter characters in function '$tag': '" .
  531. substr( $text, $attr_name_pos ) . "'" );
  532. break;
  533. }
  534. ++$attr_name_pos;
  535. unset( $var_data );
  536. $var_data = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $attr_name_pos, $var_end, $text_len, $rootNamespace );
  537. $args[$attr_name] = $var_data;
  538. $attr_pos = $var_end;
  539. }
  540. if ( $type == eZTemplate::ELEMENT_END_TAG and count( $args ) > 0 )
  541. {
  542. if ( $tag != 'do' )
  543. {
  544. $placement = $element['placement'];
  545. $startLine = $placement['start']['line'];
  546. $startColumn = $placement['start']['column'];
  547. $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
  548. "End tag \"$tag\" cannot have attributes\n$tpl->LDelim/" . $text . $tpl->RDelim,
  549. $element['placement'] );
  550. $args = array();
  551. }
  552. }
  553. if ( $type == eZTemplate::ELEMENT_NORMAL_TAG )
  554. {
  555. $ignoreCurrentTag = false;
  556. if( in_array( $tag, array_keys($outerElseTags) ) ) // 'esle'-kind operators
  557. {
  558. unset( $oldTag );
  559. unset( $oldTagName );
  560. $oldTag = end( $tagStack );
  561. $oldTagName = $oldTag["Tag"];
  562. $ignoreCurrentTag = true;
  563. if ( in_array( $oldTagName, $outerElseTags[$tag] ) )
  564. {
  565. $ignoreCurrentTag = false;
  566. }
  567. else // if there is incorrect 'else' using
  568. {
  569. // checking for 'if' in stack
  570. $tagBackStack = $tagStack;
  571. $lastIfKey = false;
  572. foreach ( $tagBackStack as $prevTagKey => $prevTag )
  573. {
  574. if ( in_array( $prevTag['Tag'], $outerElseTags[$tag] ) )
  575. {
  576. $lastIfKey = $prevTagKey;
  577. }
  578. }
  579. if( $lastIfKey !== false )
  580. {
  581. // checking for later tags (search for closing tag)
  582. $laterElements = array_slice( $textElements, $elementKey + 1 );
  583. $laterStack = array();
  584. foreach( $laterElements as $laterElement )
  585. {
  586. if ( $laterElement['type'] == eZTemplate::ELEMENT_NORMAL_TAG)
  587. {
  588. if( !in_array( $laterElement['name'], array_keys($outerElseTags) ) )
  589. {
  590. $laterStack[] = $laterElement['name'];
  591. }
  592. else
  593. {
  594. if ( $laterStack === array() )
  595. {
  596. break; // double else (current else will be ignored)
  597. }
  598. }
  599. }
  600. elseif ( $laterElement['type'] == eZTemplate::ELEMENT_END_TAG )
  601. {
  602. if ( $laterStack !== array() )
  603. {
  604. if ( array_pop( $laterStack ) !== $laterElement['name'] )
  605. {
  606. break; // later tags mismatch (current else will be ignored)
  607. }
  608. }
  609. else
  610. {
  611. if ( $laterElement['name'] == $oldTagName)
  612. {
  613. break; // previous tag correctly closed (current else will be ignored)
  614. }
  615. elseif ( in_array( $laterElement['name'], $outerElseTags[$tag] ) )
  616. {
  617. $ignoreCurrentTag = false;
  618. break; // 'if' tag correctly closed (inner tags must be closed too)
  619. }
  620. }
  621. }
  622. }
  623. }
  624. $place = $element['placement'];
  625. $startLine = $place['start']['line'];
  626. $startColumn = $place['start']['column'];
  627. if ( $ignoreCurrentTag )
  628. {
  629. $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
  630. "Unterminated tag \"$oldTagName\" does not match tag \"$tag\". \"$tag\" will be ignored.",
  631. $element['placement'] );
  632. }
  633. else
  634. {
  635. unset( $currentRoot );
  636. $lastIfKey++;
  637. $currentRoot =& $tagStack[$lastIfKey]["Root"];
  638. $autoTerminatedTags = array_slice( $tagStack, $lastIfKey);
  639. $autoTerminatedTagNames = array();
  640. foreach( $autoTerminatedTags as $autoTerminatedTag)
  641. $autoTerminatedTagNames[] = $autoTerminatedTag["Tag"];
  642. $tagStack = array_slice( $tagStack, 0, $lastIfKey);
  643. $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
  644. "Unterminated tag \"".implode( "\", \"", $autoTerminatedTagNames)."\" does not match tag \"$tag\" and will be autoterminated.",
  645. $element['placement'] );
  646. unset( $autoTerminatedTags );
  647. unset( $autoTerminatedTagNames );
  648. }
  649. unset( $lastIfKey );
  650. }
  651. }
  652. if ( !$ignoreCurrentTag )
  653. {
  654. unset( $node );
  655. $node = array( eZTemplate::NODE_FUNCTION,
  656. false,
  657. $tag,
  658. $args,
  659. $placement );
  660. $this->appendChild( $currentRoot, $node );
  661. $has_children = true;
  662. if ( isset( $tpl->FunctionAttributes[$tag] ) )
  663. {
  664. if ( is_array( $tpl->FunctionAttributes[$tag] ) )
  665. $tpl->loadAndRegisterFunctions( $tpl->FunctionAttributes[$tag] );
  666. $has_children = $tpl->FunctionAttributes[$tag];
  667. }
  668. else if ( isset( $tpl->Functions[$tag] ) )
  669. {
  670. if ( is_array( $tpl->Functions[$tag] ) )
  671. $tpl->loadAndRegisterFunctions( $tpl->Functions[$tag] );
  672. $has_children = $tpl->hasChildren( $tpl->Functions[$tag], $tag );
  673. }
  674. if ( $has_children )
  675. {
  676. $tagStack[] = array( "Root" => &$currentRoot,
  677. "Tag" => $tag );
  678. unset( $currentRoot );
  679. $currentRoot =& $node;
  680. }
  681. }
  682. }
  683. else if ( $type == eZTemplate::ELEMENT_END_TAG )
  684. {
  685. $has_children = true;
  686. if ( isset( $tpl->FunctionAttributes[$tag] ) )
  687. {
  688. if ( is_array( $tpl->FunctionAttributes[$tag] ) )
  689. $tpl->loadAndRegisterFunctions( $tpl->FunctionAttributes[$tag] );
  690. $has_children = $tpl->FunctionAttributes[$tag];
  691. }
  692. else if ( isset( $tpl->Functions[$tag] ) )
  693. {
  694. if ( is_array( $tpl->Functions[$tag] ) )
  695. $tpl->loadAndRegisterFunctions( $tpl->Functions[$tag] );
  696. $has_children = $tpl->hasChildren( $tpl->Functions[$tag], $tag );
  697. }
  698. if ( !$has_children )
  699. {
  700. $placement = $element['placement'];
  701. $startLine = $placement['start']['line'];
  702. $startColumn = $placement['start']['column'];
  703. $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
  704. "End tag \"$tag\" for function which does not accept children, ignoring tag",
  705. $element['placement'] );
  706. }
  707. else
  708. {
  709. unset( $oldTag );
  710. unset( $oldTagName );
  711. $oldTag = array_pop( $tagStack );
  712. $oldTagName = $oldTag["Tag"];
  713. unset( $currentRoot );
  714. $currentRoot =& $oldTag["Root"];
  715. if ( $oldTagName != $tag )
  716. {
  717. $placement = $element['placement'];
  718. $startLine = $placement['start']['line'];
  719. $startColumn = $placement['start']['column'];
  720. $tpl->error( "", "parser error @ $relatedTemplateName:$startLine" . "[$startColumn]" . "\n" .
  721. "Unterminated tag \"$oldTagName\" does not match tag \"$tag\"",
  722. $element['placement'] );
  723. }
  724. // a dirty hack to pass arguments specified in {/do} to the corresponding function.
  725. if ( $tag == 'do' )
  726. {
  727. $doOpenTag =& $currentRoot[1][count( $currentRoot[1] ) - 1];
  728. $doOpenTag[3] =& $args;
  729. }
  730. }
  731. }
  732. else // eZTemplate::ELEMENT_SINGLE_TAG
  733. {
  734. unset( $node );
  735. $node = array( eZTemplate::NODE_FUNCTION,
  736. false,
  737. $tag,
  738. $args,
  739. $placement );
  740. $this->appendChild( $currentRoot, $node );
  741. }
  742. unset( $tag );
  743. } break;
  744. }
  745. }
  746. if ( $tpl->ShowDetails )
  747. eZDebug::addTimingPoint( "Parse pass 3 done" );
  748. }
  749. /*!
  750. * parse 'sequence' loop parameter: "sequence <array> as <$seqVar>"
  751. */
  752. function parseSequenceParameter( $parseSequenceKeyword, $funcName, &$args, $tpl, &$text, &$text_len, &$cur_pos,
  753. $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
  754. {
  755. if ( $parseSequenceKeyword )
  756. {
  757. // parse 'sequence' keyword
  758. $sequenceEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
  759. $sequence = substr( $text, $cur_pos, $sequenceEndPos-$cur_pos );
  760. if ( $sequence != 'sequence' )
  761. {
  762. $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
  763. $funcName, "Expected keyword 'sequence' not found" );
  764. return false;
  765. }
  766. $cur_pos = $sequenceEndPos;
  767. // skip whitespaces
  768. $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
  769. }
  770. // parse sequence array
  771. $args['sequence_array'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
  772. // skip whitespaces
  773. $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
  774. // parse 'as' keyword
  775. $asEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $cur_pos, $text_len );
  776. $word = substr( $text, $cur_pos, $asEndPos-$cur_pos );
  777. if ( $word != 'as' )
  778. {
  779. $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
  780. $funcName, "Expected keyword 'as' not found" );
  781. unset( $args['sequence_array'] );
  782. return false;
  783. }
  784. $cur_pos = $asEndPos;
  785. // skip whitespaces
  786. $cur_pos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $cur_pos, $text_len );
  787. // parse sequence variable
  788. $seqVar = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $cur_pos, $cur_pos, $text_len, $rootNamespace );
  789. if ( !$seqVar )
  790. {
  791. $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
  792. $funcName, "Sequence variable name cannot be empty" );
  793. unset( $args['sequence_array'] );
  794. return false;
  795. }
  796. $args['sequence_var'] = $seqVar;
  797. return true;
  798. }
  799. /*!
  800. Parse {for} function.
  801. Syntax:
  802. \code
  803. // for <firstValue> to <lastValue> as <$loopVar> [sequence <array> as <$var>]
  804. \endcode
  805. */
  806. function parseForFunction( &$args, $tpl, &$text, &$text_len, &$cur_pos,
  807. $relatedTemplateName, $startLine, $startColumn, $rootNamespace )
  808. {
  809. $firstValStartPos = $cur_pos;
  810. // parse first value
  811. $firstVal = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $firstValStartPos, $firstValEndPos, $text_len, $rootNamespace
  812. /*, eZTemplate::TYPE_NUMERIC_BIT | eZTemplate::TYPE_VARIABLE_BIT*/ );
  813. $args['first_val'] = $firstVal;
  814. $toStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $firstValEndPos, $text_len );
  815. // parse 'to'
  816. $toEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $toStartPos, $text_len );
  817. $to = substr( $text, $toStartPos, $toEndPos-$toStartPos );
  818. if ( $to != 'to' )
  819. {
  820. $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
  821. 'for', "Expected keyword 'to' not found" );
  822. return;
  823. }
  824. $lastValStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $toEndPos, $text_len );
  825. // parse last value
  826. $args['last_val'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $lastValStartPos, $lastValEndPos, $text_len, $rootNamespace );
  827. $asStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $lastValEndPos, $text_len );
  828. // parse 'as'
  829. $asEndPos = $this->ElementParser->identifierEndPosition( $tpl, $text, $asStartPos, $text_len );
  830. $as = substr( $text, $asStartPos, $asEndPos-$asStartPos );
  831. if ( $as != 'as' )
  832. {
  833. $this->showParseErrorMessage( $tpl, $text, $text_len, $cur_pos, $relatedTemplateName, $startLine, $startColumn,
  834. 'for', "Expected keyword 'as' not found" );
  835. return;
  836. }
  837. $loopVarStartPos = $this->ElementParser->whitespaceEndPos( $tpl, $text, $asEndPos, $text_len );
  838. // parse loop variable
  839. $args['loop_var'] = $this->ElementParser->parseVariableTag( $tpl, $relatedTemplateName, $text, $loopVarStartPos, $loopVarEndPos, $text