PageRenderTime 73ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/Opl/Opt/Compiler/Class.php

https://bitbucket.org/kandsten/hitta.sverok.se
PHP | 3663 lines | 2679 code | 193 blank | 791 comment | 484 complexity | 251f002e7a1918a53f611464877d43de MD5 | raw file
Possible License(s): GPL-3.0, MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * OPEN POWER LIBS <http://www.invenzzia.org>
  4. *
  5. * This file is subject to the new BSD license that is bundled
  6. * with this package in the file LICENSE. It is also available through
  7. * WWW at this URL: <http://www.invenzzia.org/license/new-bsd>
  8. *
  9. * Copyright (c) Invenzzia Group <http://www.invenzzia.org>
  10. * and other contributors. See website for details.
  11. *
  12. * $Id: Class.php 274 2009-12-29 10:17:48Z zyxist $
  13. */
  14. class Opt_Compiler_Class
  15. {
  16. // Opcodes
  17. const OP_VARIABLE = 1;
  18. const OP_LANGUAGE_VAR = 2;
  19. const OP_STRING = 4;
  20. const OP_NUMBER = 8;
  21. const OP_ARRAY = 16;
  22. const OP_OBJECT = 32;
  23. const OP_IDENTIFIER = 64;
  24. const OP_OPERATOR = 128;
  25. const OP_POST_OPERATOR = 256;
  26. const OP_PRE_OPERATOR = 512;
  27. const OP_ASSIGN = 1024;
  28. const OP_NULL = 2048;
  29. const OP_SQ_BRACKET = 4096;
  30. const OP_SQ_BRACKET_E = 8192;
  31. const OP_FUNCTION = 16384;
  32. const OP_METHOD = 32768;
  33. const OP_BRACKET = 65536;
  34. const OP_CLASS = 131072;
  35. const OP_CALL = 262144;
  36. const OP_FIELD = 524288;
  37. const OP_EXPRESSION = 1048576;
  38. const OP_OBJMAN = 2097152;
  39. const OP_BRACKET_E = 4194304;
  40. const OP_TU = 8388608;
  41. const OP_CURLY_BRACKET = 16777216;
  42. const ESCAPE_ON = true;
  43. const ESCAPE_OFF = false;
  44. const ESCAPE_BOTH = 2;
  45. // Current compilation
  46. protected $_template = NULL;
  47. protected $_attr = array();
  48. protected $_stack = NULL;
  49. protected $_node = NULL;
  50. static protected $_recursionDetector = NULL;
  51. // Compiler info
  52. protected $_tags = array();
  53. protected $_attributes = array();
  54. protected $_conversions = array();
  55. protected $_processors = array();
  56. protected $_dependencies = array();
  57. // OPT parser info
  58. protected $_tpl;
  59. protected $_instructions;
  60. protected $_namespaces;
  61. protected $_functions;
  62. protected $_classes;
  63. protected $_blocks;
  64. protected $_components;
  65. protected $_tf;
  66. protected $_entities;
  67. protected $_formnatInfo;
  68. protected $_formats = array();
  69. protected $_formatObj = array();
  70. protected $_inheritance;
  71. // Regular expressions
  72. private $_rCDataExpression = '/(\<\!\[CDATA\[|\]\]\>)/msi';
  73. private $_rCommentExpression = '/(\<\!\-\-|\-\-\>)/si';
  74. private $_rCommentSplitExpression = '/(\<\!\-\-(.*?)\-\-\>)/si';
  75. private $_rOpeningChar = '[a-zA-Z\:\_]';
  76. private $_rNameChar = '[a-zA-Z0-9\:\.\_\-]';
  77. private $_rNameExpression;
  78. private $_rXmlTagExpression;
  79. private $_rTagExpandExpression;
  80. private $_rQuirksTagExpression = '';
  81. private $_rExpressionTag = '/(\{([^\}]*)\})/msi';
  82. private $_rAttributeTokens = '/(?:[^\=\"\'\s]+|\=|\"|\'|\s)/x';
  83. private $_rPrologTokens = '/(?:[^\=\"\'\s]+|\=|\'|\"|\s)/x';
  84. private $_rModifiers = 'si';
  85. private $_rXmlHeader = '/(\<\?xml.+\?\>)/msi';
  86. private $_rProlog = '/\<\?xml(.+)\?\>|/msi';
  87. private $_rEncodingName = '/[A-Za-z]([A-Za-z0-9.\_]|\-)*/si';
  88. private $_rBacktickString = '`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`';
  89. private $_rSingleQuoteString = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
  90. private $_rHexadecimalNumber = '\-?0[xX][0-9a-fA-F]+';
  91. private $_rDecimalNumber = '[0-9]+\.?[0-9]*';
  92. private $_rLanguageVar = '\$[a-zA-Z0-9\_]+@[a-zA-Z0-9\_]+';
  93. private $_rVariable = '(\$|@)[a-zA-Z0-9\_\.]*';
  94. private $_rOperators = '\-\>|!==|===|==|!=|\=\>|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+\+|\-\-|\+|\-|\*|\/|\[|\]|\.|\:\:|\{|\}|\'|\"|';
  95. private $_rIdentifier = '[a-zA-Z\_]{1}[a-zA-Z0-9\_\.]*';
  96. private $_rLanguageVarExtract = '\$([a-zA-Z0-9\_]+)@([a-zA-Z0-9\_]+)';
  97. // Help fields
  98. private $_charset = null;
  99. private $_translationConversion = null;
  100. private $_initialMemory = null;
  101. private $_comments = 0;
  102. private $_standalone = false;
  103. private $_dynamicBlocks = null;
  104. static private $_templates = array();
  105. /**
  106. * Creates a new instance of the template compiler. The compiler can
  107. * be created, using the settings from the main OPT class or another
  108. * compiler.
  109. *
  110. * @param Opt_Class|Opt_Compiler_Class $tpl The initial object.
  111. */
  112. public function __construct($tpl)
  113. {
  114. if($tpl instanceof Opt_Class)
  115. {
  116. $this->_tpl = $tpl;
  117. $this->_namespaces = $tpl->_getList('_namespaces');
  118. $this->_classes = $tpl->_getList('_classes');
  119. $this->_functions = $tpl->_getList('_functions');
  120. $this->_components = $tpl->_getList('_components');
  121. $this->_blocks = $tpl->_getList('_blocks');
  122. $this->_phpFunctions = $tpl->_getList('_phpFunctions');
  123. $this->_formats = $tpl->_getList('_formats');
  124. $this->_tf = $tpl->_getList('_tf');
  125. $this->_entities = $tpl->_getList('_entities');
  126. $this->_charset = strtoupper($tpl->charset);
  127. // Create the processors and call their configuration method in the constructors.
  128. $instructions = $tpl->_getList('_instructions');
  129. foreach($instructions as $instructionClass)
  130. {
  131. $obj = new $instructionClass($this, $tpl);
  132. $this->_processors[$obj->getName()] = $obj;
  133. // Add the tags and attributes registered by this processor.
  134. foreach($obj->getInstructions() as $item)
  135. {
  136. $this->_instructions[$item] = $obj;
  137. }
  138. foreach($obj->getAttributes() as $item)
  139. {
  140. $this->_attributes[$item] = $obj;
  141. }
  142. }
  143. }
  144. elseif($tpl instanceof Opt_Compiler_Class)
  145. {
  146. // Simply import the data structures from that compiler.
  147. $this->_tpl = $tpl->_tpl;
  148. $this->_namespaces = $tpl->_namespaces;
  149. $this->_classes = $tpl->_classes;
  150. $this->_functions = $tpl->_functions;
  151. $this->_components = $tpl->_components;
  152. $this->_blocks = $tpl->_blocks;
  153. $this->_inheritance = $tpl->_inheritance;
  154. $this->_formatInfo = $tpl->_formatInfo;
  155. $this->_formats = $tpl->_formats;
  156. $this->_tf = $tpl->_tf;
  157. $this->_processor = $tpl->_processors;
  158. $this->_instructions = $tpl->_instructions;
  159. $this->_attributes = $tpl->_attributes;
  160. $this->_charset = $tpl->_charset;
  161. $this->_entities = $tpl->_entities;
  162. $tpl = $this->_tpl;
  163. }
  164. if($tpl->unicodeNames)
  165. {
  166. // Register unicode name regular expressions
  167. $this->_rOpeningChar = '(\p{Lu}|\p{Ll}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Nl}|\_|\:)';
  168. $this->_rNameChar = '(\p{Lu}|\p{Ll}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Nl}|\p{Mc}|\p{Me}|\p{Mn}|\p{Lm}|\p{Nd}|\_|\:|\.|\-)';
  169. $this->_rModifiers = 'msiu';
  170. }
  171. // Register the rest of the expressions
  172. $this->_rNameExpression = '/('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)/'.$this->_rModifiers;
  173. $this->_rXmlTagExpression = '/(\<((\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?)\>)/'.$this->_rModifiers;
  174. $this->_rTagExpandExpression = '/^(\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?$/'.$this->_rModifiers;
  175. $this->_rQuirksTagExpression = '/(\<((\/)?(('.implode('|', $this->_namespaces).')\:'.$this->_rNameChar.'*)( [^\<\>]+)?(\/)?)\>)/'.$this->_rModifiers;
  176. // We've just thrown the performance away by loading the compiler, so this won't make things worse
  177. // but the user may be happy :). However, don't show this message, if we are in the performance mode.
  178. if(!is_writable($tpl->compileDir) && $tpl->_compileMode != Opt_Class::CM_PERFORMANCE)
  179. {
  180. throw new Opt_FilesystemAccess_Exception('compilation', 'writeable');
  181. }
  182. // If the debug console is active, preload the XML tree classes.
  183. // Without it, the debug console would show crazy things about the memory usage.
  184. if($this->_tpl->debugConsole && !class_exists('Opt_Xml_Root'))
  185. {
  186. Opl_Loader::load('Opt_Xml_Root');
  187. Opl_Loader::load('Opt_Xml_Text');
  188. Opl_Loader::load('Opt_Xml_Cdata');
  189. Opl_Loader::load('Opt_Xml_Element');
  190. Opl_Loader::load('Opt_Xml_Attribute');
  191. Opl_Loader::load('Opt_Xml_Expression');
  192. Opl_Loader::load('Opt_Xml_Prolog');
  193. Opl_Loader::load('Opt_Xml_Dtd');
  194. Opl_Loader::load('Opt_Format_Array');
  195. }
  196. } // end __construct();
  197. /**
  198. * Allows to clone the original compiler, creating new instruction
  199. * processors for the new instance.
  200. */
  201. public function __clone()
  202. {
  203. $this->_processors = array();
  204. $this->_tags = array();
  205. $this->_attributes = array();
  206. $this->_conversions = array();
  207. $instructions = $this->_tpl->_getList('_instructions');
  208. $cnt = sizeof($instructions);
  209. for($i = 0; $i < $cnt; $i++)
  210. {
  211. $obj = new $instructions[$i]($this, $tpl);
  212. $this->_processors[$obj->getName()] = $obj;
  213. }
  214. } // end __clone();
  215. /*
  216. * General purpose tools and utilities
  217. */
  218. /**
  219. * Returns the currently processed template file name.
  220. *
  221. * @static
  222. * @return String The currently processed template name
  223. */
  224. static public function getCurrentTemplate()
  225. {
  226. return end(self::$_templates);
  227. } // end getCurrentTemplate();
  228. /**
  229. * Cleans the compiler state after the template compilation.
  230. * It is necessary in the exception processing - if the exception
  231. * is thrown in the middle of the compilation, the compiler becomes
  232. * useless, because it is locked. The compilation algorithm automatically
  233. * filters the exceptions, cleans the compiler state and throws the captured
  234. * exceptions again, to the script.
  235. *
  236. * @static
  237. */
  238. static public function cleanCompiler()
  239. {
  240. self::$_recursionDetector = null;
  241. self::$_templates = array();
  242. } // end cleanCompiler();
  243. /**
  244. * Returns the value of the compiler state variable or
  245. * NULL if the variable is not set.
  246. *
  247. * @param String $name Compiler variable name
  248. * @return Mixed The compiler variable value.
  249. */
  250. public function get($name)
  251. {
  252. if(!isset($this->_attr[$name]))
  253. {
  254. return NULL;
  255. }
  256. return $this->_attr[$name];
  257. } // end get();
  258. /**
  259. * Creates or modifies the compiler state variable.
  260. *
  261. * @param String $name The name
  262. * @param Mixed $value The value
  263. */
  264. public function set($name, $value)
  265. {
  266. $this->_attr[$name] = $value;
  267. } // end set();
  268. /**
  269. * Adds the escaping formula to the specified expression using the current escaping
  270. * rules:
  271. *
  272. * 1. The $status variable.
  273. * 2. The current template settings.
  274. * 3. The OPT settings.
  275. *
  276. * @param String $expression The PHP expression to be escaped.
  277. * @param Boolean $status The status of escaping for this expression or NULL, if not set.
  278. * @return String The expression with the escaping formula added, if necessary.
  279. */
  280. public function escape($expression, $status = null)
  281. {
  282. // OPT Configuration
  283. $escape = $this->_tpl->escape;
  284. // Template configuration
  285. if(!is_null($this->get('escaping')))
  286. {
  287. $escape = ($this->get('escaping') == true ? true : false);
  288. }
  289. // Expression settings
  290. if(!is_null($status))
  291. {
  292. $escape = ($status == true ? true : false);
  293. }
  294. if($escape)
  295. {
  296. // The user may define a custom escaping function
  297. if($this->isFunction('escape'))
  298. {
  299. if(strpos($this->_functions['escape'], '#', 0) !== false)
  300. {
  301. throw new Opt_InvalidArgumentFormat_Exception('escape', 'escape');
  302. }
  303. return $this->_functions['escape'].'('.$expression.')';
  304. }
  305. return 'htmlspecialchars('.$expression.')';
  306. }
  307. return $expression;
  308. } // end escape();
  309. /**
  310. * Returns the format object for the specified variable.
  311. *
  312. * @param String $variable The variable identifier.
  313. * @param Boolean $restore optional Whether to load a previously created format object (false) or to create a new one.
  314. * @return Opt_Compiler_Format The format object.
  315. */
  316. public function getFormat($variable, $restore = false)
  317. {
  318. $hc = $this->_tpl->defaultFormat;
  319. if(isset($this->_formatInfo[$variable]))
  320. {
  321. $hc = $this->_formatInfo[$variable];
  322. }
  323. if($restore && isset($this->_formatObj[$hc]))
  324. {
  325. return $this->_formatObj[$hc];
  326. }
  327. $top = $this->createFormat($variable, $hc);
  328. if($restore)
  329. {
  330. $this->_formatObj[$hc] = $top;
  331. }
  332. return $top;
  333. } // end getFormat();
  334. /**
  335. * Creates a format object for the specified description string.
  336. *
  337. * @param String $variable The variable name (for debug purposes)
  338. * @param String $hc The description string.
  339. * @return Opt_Compiler_Format The newly created format object.
  340. */
  341. public function createFormat($variable, $hc)
  342. {
  343. // Decorate the objects, if necessary
  344. $expanded = explode('/', $hc);
  345. $obj = null;
  346. foreach($expanded as $class)
  347. {
  348. if(!isset($this->_formats[$class]))
  349. {
  350. throw new Opt_FormatNotFound_Exception($variable, $class);
  351. }
  352. $hcName = $this->_formats[$class];
  353. if(!is_null($obj))
  354. {
  355. $obj->decorate($obj2 = new $hcName($this->_tpl, $this));
  356. $obj = $obj2;
  357. }
  358. else
  359. {
  360. $top = $obj = new $hcName($this->_tpl, $this, $hc);
  361. }
  362. }
  363. return $top;
  364. } // end createFormat();
  365. /**
  366. * Allows to export the list of variables and their data formats to
  367. * the template compiler.
  368. *
  369. * @param Array $list An associative array of pairs "variable => format description"
  370. */
  371. public function setFormatList(Array $list)
  372. {
  373. $this->_formatInfo = $list;
  374. } // end setFormatList();
  375. /**
  376. * Converts the specified item into another string using one of the
  377. * registered patterns. If the pattern is not found, the method returns
  378. * the original item unmodified.
  379. *
  380. * @param String $item The item to be converted.
  381. * @return String
  382. */
  383. public function convert($item)
  384. {
  385. // the converter allows to convert one name into another and keep it, if there is no
  386. // conversion pattern. Used in connection with sections + snippets.
  387. if(isset($this->_conversions[$item]))
  388. {
  389. return $this->_conversions[$item];
  390. }
  391. return $item;
  392. } // end convert();
  393. /**
  394. * Creates a new conversion pattern. The string $from will be converted
  395. * into $to.
  396. *
  397. * @param String $from The original string
  398. * @param String $to The new string
  399. */
  400. public function setConversion($from, $to)
  401. {
  402. $this->_conversions[$from] = $to;
  403. } // end setConversion();
  404. /**
  405. * Removes the conversion pattern from the compiler memory.
  406. *
  407. * @param String $from The original string.
  408. * @return Boolean
  409. */
  410. public function unsetConversion($from)
  411. {
  412. if(isset($this->_conversions[$from]))
  413. {
  414. unset($this->_conversions[$from]);
  415. return true;
  416. }
  417. return false;
  418. } // end unsetConversion();
  419. /**
  420. * Registers the dynamic inheritance rules for the templates. The
  421. * array taken as a parameter must be an associative array of pairs
  422. * 'extending' => 'extended' file names.
  423. *
  424. * @param Array $inheritance The list of inheritance rules.
  425. */
  426. public function setInheritance(Array $inheritance)
  427. {
  428. $this->_inheritance = $inheritance;
  429. } // end setInheritance();
  430. /**
  431. * Parses the entities in the specified text.
  432. *
  433. * @param String $text The original text
  434. * @return String
  435. */
  436. public function parseEntities($text)
  437. {
  438. return preg_replace_callback('/\&(([a-zA-Z\_\:]{1}[a-zA-Z0-9\_\:\-\.]*)|(\#((x[a-fA-F0-9]+)|([0-9]+))))\;/', array($this, '_decodeEntity'), $text);
  439. // return htmlspecialchars_decode(str_replace(array_keys($this->_entities), array_values($this->_entities), $text));
  440. } // end parseEntities();
  441. /**
  442. * Replaces only OPT-specific entities &lb; and &rb; to the corresponding
  443. * characters.
  444. *
  445. * @param String $text Input text
  446. * @return String output text
  447. */
  448. public function parseShortEntities($text)
  449. {
  450. return str_replace(array('&lb;', '&rb;'), array('{', '}'), $text);
  451. } // end parseShortEntities();
  452. /**
  453. * Replaces the XML special characters back to entities with smart ommiting of &
  454. * that already creates an entity.
  455. *
  456. * @param String $text Input text.
  457. * @return String Output text.
  458. */
  459. public function parseSpecialChars($text)
  460. {
  461. return htmlspecialchars($text);
  462. return preg_replace_callback('/(\&\#?[a-zA-Z0-9]*\;)|\<|\>|\"|\&/', array($this, '_entitize'), $text);
  463. } // end parseSpecialChars();
  464. /**
  465. * Returns 'true', if the argument is a valid identifier. An identifier
  466. * must begin with a letter or underscore, and later, the numbers are also
  467. * allowed.
  468. *
  469. * @param String $id The tested string
  470. * @return Boolean
  471. */
  472. public function isIdentifier($id)
  473. {
  474. return preg_match($this->_rEncodingName, $id);
  475. } // end isIdentifier();
  476. /**
  477. * Checks whether the specified tag name is registered as an instruction.
  478. * Returns its processor in case of success or NULL.
  479. *
  480. * @param String $tag The tag name (with the namespace)
  481. * @return Opt_Compiler_Processor|NULL The processor that registered this tag.
  482. */
  483. public function isInstruction($tag)
  484. {
  485. if(isset($this->_instructions[$tag]))
  486. {
  487. return $this->_instructions[$tag];
  488. }
  489. return NULL;
  490. } // end isInstruction();
  491. /**
  492. * Returns true, if the argument is the name of an OPT attribute.
  493. *
  494. * @param String $tag The attribute name
  495. * @return Boolean
  496. */
  497. public function isOptAttribute($tag)
  498. {
  499. if(isset($this->_attributes[$tag]))
  500. {
  501. return $this->_attributes[$tag];
  502. }
  503. return NULL;
  504. } // end isOptAttribute();
  505. /**
  506. * Returns true, if the argument is the OPT function name.
  507. *
  508. * @param String $name The function name
  509. * @return Boolean
  510. */
  511. public function isFunction($name)
  512. {
  513. if(isset($this->_functions[$name]))
  514. {
  515. return $this->_functions[$name];
  516. }
  517. return NULL;
  518. } // end isFunction();
  519. /**
  520. * Returns true, if the argument is the name of the class
  521. * accepted by OPT.
  522. *
  523. * @param String $id The class name.
  524. * @return Boolean
  525. */
  526. public function isClass($id)
  527. {
  528. if(isset($this->_classes[$id]))
  529. {
  530. return $this->_classes[$id];
  531. }
  532. return NULL;
  533. } // end isClass();
  534. /**
  535. * Returns true, if the argument is the name of the namespace
  536. * processed by OPT.
  537. *
  538. * @param String $ns The namespace name
  539. * @return Boolean
  540. */
  541. public function isNamespace($ns)
  542. {
  543. return in_array($ns, $this->_namespaces);
  544. } // end isNamespace();
  545. /**
  546. * Returns true, if the argument is the name of the component tag.
  547. * @param String $component The component tag name
  548. * @return Boolean
  549. */
  550. public function isComponent($component)
  551. {
  552. return isset($this->_components[$component]);
  553. } // end isComponent();
  554. /**
  555. * Returns true, if the argument is the name of the block tag.
  556. * @param String $block The block tag name.
  557. * @return Boolean
  558. */
  559. public function isBlock($block)
  560. {
  561. return isset($this->_blocks[$block]);
  562. } // end isComponent();
  563. /**
  564. * Returns true, if the argument is the processor name.
  565. *
  566. * @param String $name The instruction processor name
  567. * @return Boolean
  568. */
  569. public function isProcessor($name)
  570. {
  571. if(!isset($this->_processors[$name]))
  572. {
  573. return NULL;
  574. }
  575. return $this->_processors[$name];
  576. } // end isProcessor();
  577. /**
  578. * Returns the processor object with the specified name. If
  579. * the processor does not exist, it generates an exception.
  580. *
  581. * @param String $name The processor name
  582. * @return Opt_Compiler_Processor
  583. */
  584. public function processor($name)
  585. {
  586. if(!isset($this->_processors[$name]))
  587. {
  588. throw new Opt_ObjectNotExists_Exception('processor', $name);
  589. }
  590. return $this->_processors[$name];
  591. } // end processor();
  592. /**
  593. * Returns the component class name assigned to the specified
  594. * XML tag. If the component class is not registered, it throws
  595. * an exception.
  596. *
  597. * @param String $name The component XML tag name.
  598. * @return Opt_Component_Interface
  599. */
  600. public function component($name)
  601. {
  602. if(!isset($this->_components[$name]))
  603. {
  604. throw new Opt_ObjectNotExists_Exception('component', $name);
  605. }
  606. return $this->_components[$name];
  607. } // end component();
  608. /**
  609. * Returns the block class name assigned to the specified
  610. * XML tag. If the block class is not registered, it throws
  611. * an exception.
  612. *
  613. * @param String $name The block XML tag name.
  614. * @return Opt_Block_Interface
  615. */
  616. public function block($name)
  617. {
  618. if(!isset($this->_blocks[$name]))
  619. {
  620. throw new Opt_ObjectNotExists_Exception('block', $name);
  621. }
  622. return $this->_blocks[$name];
  623. } // end block();
  624. /**
  625. * Returns the template name that is inherited by the template '$name'
  626. *
  627. * @param String $name The "current" template file name
  628. * @return String
  629. */
  630. public function inherits($name)
  631. {
  632. if(isset($this->_inheritance[$name]))
  633. {
  634. return $this->_inheritance[$name];
  635. }
  636. return NULL;
  637. } // end inherits();
  638. /**
  639. * Adds the template file name to the dependency list of the currently
  640. * compiled file, so that it could be checked for modifications during
  641. * the execution.
  642. *
  643. * @param String $template The template file name.
  644. */
  645. public function addDependantTemplate($template)
  646. {
  647. if(in_array($template, $this->_dependencies))
  648. {
  649. $exception = new Opt_InheritanceRecursion_Exception($template);
  650. $exception->setData($this->_dependencies);
  651. throw $exception;
  652. }
  653. $this->_dependencies[] = $template;
  654. } // end addDependantTemplate();
  655. /**
  656. * Imports the dependencies from another compiler object and adds them
  657. * to the actual dependency list.
  658. *
  659. * @param Opt_Compiler_Class $compiler Another compiler object.
  660. */
  661. public function importDependencies(Opt_Compiler_Class $compiler)
  662. {
  663. $this->_dependencies = array_merge($this->_dependencies, $compiler->_dependencies);
  664. } // end importDependencies();
  665. /*
  666. * Internal tools and utilities
  667. */
  668. /**
  669. * Compiles the attribute part of the opening tag and extracts the tag
  670. * attributes to an array. Moreover, it performs the entity conversion
  671. * to the corresponding characters.
  672. *
  673. * @internal
  674. * @param String $attrList The attribute list string
  675. * @param string $tagName The tag name for debug purposes
  676. * @return Array The list of attributes with the values.
  677. */
  678. protected function _compileAttributes($attrList, $tagName = '')
  679. {
  680. // Tokenize the list
  681. preg_match_all($this->_rAttributeTokens, $attrList, $match, PREG_SET_ORDER);
  682. $size = sizeof($match);
  683. $result = array();
  684. for($i = 0; $i < $size; $i++)
  685. {
  686. /**
  687. * The algorithm scans the tokens on the list and determines, where
  688. * the beginning and the end of the attribute is. We do not use the
  689. * regular expressions, because they are not able to capture the
  690. * invalid content between the expressions.
  691. *
  692. * The sub-loops can modify the iteration variable to skip the found
  693. * elements, white characters etc. This means that the main loop
  694. * does only a few iteration number, equal approximately the number
  695. * of attributes.
  696. */
  697. if(!ctype_space($match[$i][0]))
  698. {
  699. if(!preg_match($this->_rNameExpression, $match[$i][0]))
  700. {
  701. return false;
  702. }
  703. $vret = false;
  704. $name = $match[$i][0];
  705. if(substr_count($name, ':') > 1)
  706. {
  707. throw new Opt_InvalidNamespace_Exception($name);
  708. }
  709. $value = null;
  710. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  711. if($match[$i][0] != '=')
  712. {
  713. if($this->_tpl->htmlAttributes)
  714. {
  715. $result[$name] = $name;
  716. continue;
  717. }
  718. else
  719. {
  720. return false;
  721. }
  722. }
  723. // Look for the attribute value start
  724. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  725. if($match[$i][0] != '"' && $match[$i][0] != '\'')
  726. {
  727. return false;
  728. }
  729. // Save the delimiter, because we will use it to make the error checking
  730. $delimiter = $match[$i][0];
  731. $value = '';
  732. for($i++; $i < $size; $i++)
  733. {
  734. if($match[$i][0] == $delimiter)
  735. {
  736. break;
  737. }
  738. $value .= $match[$i][0];
  739. }
  740. if(!isset($match[$i][0]))
  741. {
  742. return false;
  743. }
  744. if($match[$i][0] != $delimiter)
  745. {
  746. return false;
  747. }
  748. // We return the decoded attribute values, because they are
  749. // stored without the entities.
  750. if(isset($result[$name]))
  751. {
  752. throw new Opt_XmlDuplicatedAttribute_Exception($name, $tagName);
  753. }
  754. $result[$name] = htmlspecialchars_decode($value);
  755. }
  756. }
  757. return $result;
  758. } // end _compileAttributes();
  759. /**
  760. * Parses the XML prolog and returns its attributes as an array. The parsing
  761. * algorith is the same, as in _compileAttributes().
  762. *
  763. * @internal
  764. * @param String $prolog The prolog string.
  765. * @return Array
  766. */
  767. protected function _compileProlog($prolog)
  768. {
  769. // Tokenize the list
  770. preg_match_all($this->_rPrologTokens, $prolog, $match, PREG_SET_ORDER);
  771. $size = sizeof($match);
  772. $result = array();
  773. for($i = 0; $i < $size; $i++)
  774. {
  775. if(!ctype_space($match[$i][0]))
  776. {
  777. // Traverse through a single attribute
  778. if(!preg_match($this->_rNameExpression, $match[$i][0]))
  779. {
  780. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  781. }
  782. $vret = false;
  783. $name = $match[$i][0];
  784. $value = null;
  785. for($i++; $i < $size && ctype_space($match[$i][0]); $i++){}
  786. if($i >= $size || $match[$i][0] != '=')
  787. {
  788. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  789. }
  790. for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
  791. if($match[$i][0] != '"' && $match[$i][0] != '\'')
  792. {
  793. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  794. }
  795. $opening = $match[$i][0];
  796. $value = '';
  797. for($i++; $i < $size; $i++)
  798. {
  799. if($match[$i][0] == $opening)
  800. {
  801. break;
  802. }
  803. $value .= $match[$i][0];
  804. }
  805. if(!isset($match[$i][0]) || $match[$i][0] != $opening)
  806. {
  807. throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
  808. }
  809. // If we are here, the attribute is correct. No shit on the way detected.
  810. $result[$name] = $value;
  811. }
  812. }
  813. $returnedResult = $result;
  814. // Check, whether the arguments are correct.
  815. if(isset($result['version']))
  816. {
  817. // There is no other version so far, so report a warning. For 99,9% this is a mistake.
  818. if($result['version'] != '1.0')
  819. {
  820. $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: strange XML version: '.$result['version']);
  821. }
  822. unset($result['version']);
  823. }
  824. if(isset($result['encoding']))
  825. {
  826. if(!preg_match($this->_rEncodingName, $result['encoding']))
  827. {
  828. throw new Opt_XmlInvalidProlog_Exception('invalid encoding name format');
  829. }
  830. // The encoding should match the value we mentioned in the OPT configuration and sent to the browser.
  831. $result['encoding'] = strtolower($result['encoding']);
  832. $charset = is_null($this->_tpl->charset) ? null : strtolower($this->_tpl->charset);
  833. if($result['encoding'] != $charset && !is_null($charset))
  834. {
  835. $this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: the declared encoding: "'.$result['encoding'].'" differs from setContentType() setting: "'.$charset.'"');
  836. }
  837. unset($result['encoding']);
  838. }
  839. else
  840. {
  841. $this->_tpl->debugConsole and Opt_Support::warning('XML prolog warning: no encoding information. Remember your content must be pure UTF-8 or UTF-16 then.');
  842. }
  843. if(isset($result['standalone']))
  844. {
  845. if($result['standalone'] != 'yes' && $result['standalone'] != 'no')
  846. {
  847. throw new Opt_XmlInvalidProlog_Exception('invalid value for "standalone" attribute: "'.$result['standalone'].'"; expected: "yes", "no".');
  848. }
  849. unset($result['standalone']);
  850. }
  851. if(sizeof($result) > 0)
  852. {
  853. throw new Opt_XmlInvalidProlog_Exception('invalid attributes in prolog.');
  854. }
  855. return $returnedResult;
  856. } // end _compileProlog();
  857. /**
  858. * Adds the PHP code with dependencies to the code buffers in the tree
  859. * root node.
  860. *
  861. * @internal
  862. * @param Opt_Xml_Node $tree The tree root node.
  863. */
  864. protected function _addDependencies($tree)
  865. {
  866. // OK, there is really some info to include!
  867. $list = '';
  868. foreach($this->_dependencies as $a)
  869. {
  870. $list .= '\''.$a.'\',';
  871. }
  872. $tree->addBefore(Opt_Xml_Buffer::TAG_BEFORE, 'if(!$this->_massPreprocess($compileTime, array('.$list.'))){ ');
  873. $tree->addAfter(Opt_Xml_Buffer::TAG_AFTER, ' }else{ $compileTime = $this->_compile($this->_template); require(__FILE__); } ');
  874. } // end _addDependencies();
  875. /**
  876. * Compiles the current text block between two XML tags, creating a
  877. * complete Opt_Xml_Text node. It looks for the expressions in the
  878. * curly brackets, extracts them and packs as separate nodes.
  879. *
  880. * Moreover, it replaces the entities with the corresponding characters.
  881. *
  882. * @internal
  883. * @param Opt_Xml_Node $current The current XML node.
  884. * @param String $text The text block between two tags.
  885. * @param Boolean $noExpressions=false If true, do not look for the expressions.
  886. * @return Opt_Xml_Node The current XML node.
  887. */
  888. protected function _treeTextCompile($current, $text, $noExpressions = false)
  889. {
  890. // Yes, we parse entities, but the text itself should not contain
  891. // any special characters.
  892. if(strcspn($text, '<>') != strlen($text))
  893. {
  894. throw new Opt_XmlInvalidCharacter_Exception(htmlspecialchars($text));
  895. }
  896. if($noExpressions)
  897. {
  898. $current = $this->_treeTextAppend($current, $this->parseEntities($text));
  899. }
  900. preg_match_all($this->_rExpressionTag, $text, $result, PREG_SET_ORDER);
  901. $resultSize = sizeof($result);
  902. $offset = 0;
  903. for($i = 0; $i < $resultSize; $i++)
  904. {
  905. $id = strpos($text, $result[$i][0], $offset);
  906. if($id > $offset)
  907. {
  908. $current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, $id - $offset)));
  909. }
  910. $offset = $id + strlen($result[$i][0]);
  911. $current = $this->_treeTextAppend($current, new Opt_Xml_Expression($this->parseEntities($result[$i][2])));
  912. }
  913. $i--;
  914. // Now the remaining content of the file
  915. if(strlen($text) > $offset)
  916. {
  917. $current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, strlen($text) - $offset)));
  918. }
  919. return $current;
  920. } // end _treeTextCompile();
  921. /**
  922. * An utility method that simplifies inserting the text to the XML
  923. * tree. Depending on the last child type, it can create a new text
  924. * node or add the text to the existing one.
  925. *
  926. * @internal
  927. * @param Opt_Xml_Node $current The currently built XML node.
  928. * @param String|Opt_Xml_Node $text The text or the expression node.
  929. * @return Opt_Xml_Node The current XML node.
  930. */
  931. protected function _treeTextAppend($current, $text)
  932. {
  933. $last = $current->getLastChild();
  934. if(!is_object($last) || !($last instanceof Opt_Xml_Text))
  935. {
  936. if(!is_object($text))
  937. {
  938. $node = new Opt_Xml_Text($text);
  939. }
  940. else
  941. {
  942. $node = new Opt_Xml_Text();
  943. $node->appendChild($text);
  944. }
  945. $current->appendChild($node);
  946. }
  947. else
  948. {
  949. if(!is_object($text))
  950. {
  951. $last->appendData($text);
  952. }
  953. else
  954. {
  955. $last->appendChild($text);
  956. }
  957. }
  958. return $current;
  959. } // end _treeTextAppend();
  960. /**
  961. * A helper method for building the XML tree. It appends the
  962. * node to the current node and returns the new node that should
  963. * become the new current node.
  964. *
  965. * @internal
  966. * @param Opt_Xml_Node $current The current node.
  967. * @param Opt_Xml_Node $node The newly created node.
  968. * @param Boolean $goInto Whether we visit the new node.
  969. * @return Opt_Xml_Node
  970. */
  971. protected function _treeNodeAppend($current, $node, $goInto)
  972. {
  973. $current->appendChild($node);
  974. if($goInto)
  975. {
  976. return $node;
  977. }
  978. return $current;
  979. } // end _treeNodeAppend();
  980. /**
  981. * A helper method for building the XML tree. It jumps out of the
  982. * current node to the parent and switches to it.
  983. *
  984. * @internal
  985. * @param Opt_Xml_Node $current The current node.
  986. * @return Opt_Xml_Node
  987. */
  988. protected function _treeJumpOut($current)
  989. {
  990. $parent = $current->getParent();
  991. if(!is_null($parent))
  992. {
  993. return $parent;
  994. }
  995. return $current;
  996. } // end _treeJumpOut();
  997. /**
  998. * Looks for special OPT attributes in the element attribute list and
  999. * processes them. Returns the list of nodes that need to be postprocessed.
  1000. *
  1001. * @internal
  1002. * @param Opt_Xml_Element $node The scanned element.
  1003. * @param Boolean $specialNs Do we recognize "parse" and "str" namespaces?
  1004. * @return Array
  1005. */
  1006. protected function _processXml(Opt_Xml_Element $node, $specialNs = true)
  1007. {
  1008. if(!$node->hasAttributes())
  1009. {
  1010. return array();
  1011. }
  1012. $attributes = $node->getAttributes();
  1013. $pp = array();
  1014. // Look for special OPT attributes
  1015. foreach($attributes as $attr)
  1016. {
  1017. if($this->isNamespace($attr->getNamespace()))
  1018. {
  1019. $xml = $attr->getXmlName();
  1020. // Check the namespace we found
  1021. switch($attr->getNamespace())
  1022. {
  1023. case 'parse':
  1024. if($specialNs)
  1025. {
  1026. $result = $this->compileExpression((string)$attr, false, Opt_Compiler_Class::ESCAPE_BOTH);
  1027. $attr->addAfter(Opt_Xml_Buffer::ATTRIBUTE_VALUE, ' echo '.$result[0].'; ');
  1028. $attr->setNamespace(null);
  1029. }
  1030. break;
  1031. case 'str':
  1032. if($specialNs)
  1033. {
  1034. $attr->setNamespace(null);
  1035. }
  1036. break;
  1037. default:
  1038. if(isset($this->_attributes[$xml]))
  1039. {
  1040. $this->_attributes[$xml]->processAttribute($node, $attr);
  1041. if($attr->get('postprocess'))
  1042. {
  1043. $pp[] = array($this->_attributes[$xml], $attr);
  1044. }
  1045. }
  1046. $node->removeAttribute($xml);
  1047. }
  1048. }
  1049. }
  1050. return $pp;
  1051. } // end _processXml();
  1052. /**
  1053. * Runs the postprocessors for the specified attributes.
  1054. *
  1055. * @internal
  1056. * @param Opt_Xml_Node $node The scanned node.
  1057. * @param Array $list The list of XML attribute processors that need to be postprocessed.
  1058. */
  1059. protected function _postprocessXml(Opt_Xml_Node $node, Array $list)
  1060. {
  1061. $cnt = sizeof($list);
  1062. for($i = 0; $i < $cnt; $i++)
  1063. {
  1064. $list[$i][0]->postprocessAttribute($node, $list[$i][1]);
  1065. }
  1066. } // end _postprocessXml();
  1067. /**
  1068. * An utility method for the stage 2 and 3 of the compilation. It is
  1069. * used to create a non-recursive depth-first search algorithm. The
  1070. * current queue is sent to a stack, and the new queue if initialized,
  1071. * if $item contains children.
  1072. *
  1073. * @internal
  1074. * @param SplStack $stack The processing stack.
  1075. * @param SplQueue $queue The processing queue.
  1076. * @param Opt_Xml_Scannable $item The item, where to import the nodes from.
  1077. * @param Boolean $pp The postprocess flag.
  1078. * @return SplQueue The new queue (or the old one, if none has been created).
  1079. */
  1080. protected function _pushQueue($stack, $queue, $item, $pp)
  1081. {
  1082. if($item->hasChildren())
  1083. {
  1084. $stack->push(array($item, $queue, $pp));
  1085. $pp = NULL;
  1086. $queue = new SplQueue;
  1087. foreach($item as $child)
  1088. {
  1089. $queue->enqueue($child);
  1090. }
  1091. }
  1092. return $queue;
  1093. } // end _pushQueue();
  1094. /**
  1095. * Does the postprocessing in the second stage of compilation.
  1096. *
  1097. * @internal
  1098. * @param Opt_Xml_Node|Null $item The postprocessed node.
  1099. * @param Array $pp The list of postprocessed attributes.
  1100. */
  1101. protected function _doPostprocess($item, $pp)
  1102. {
  1103. // Postprocess code for the compilation stage 2
  1104. // Packed into a method, because it is used twice.
  1105. if(is_null($item))
  1106. {
  1107. return;
  1108. }
  1109. if(sizeof($pp) > 0)
  1110. {
  1111. $this->_postprocessXml($item, $pp);
  1112. }
  1113. if($item->get('postprocess'))
  1114. {
  1115. if(!is_null($processor = $this->isInstruction($item->getXmlName())))
  1116. {
  1117. $processor->postprocessNode($item);
  1118. }
  1119. elseif($this->isComponent($item->getXmlName()))
  1120. {
  1121. $processor = $this->processor('component');
  1122. $processor->postprocessComponent($item);
  1123. }
  1124. elseif($this->isBlock($item->getXmlName()))
  1125. {
  1126. $processor = $this->processor('block');
  1127. $processor->postprocessBlock($item);
  1128. }
  1129. else
  1130. {
  1131. throw new Opt_UnknownProcessor_Exception($item->getXmlName());
  1132. }
  1133. }
  1134. } // end _doPostprocess();
  1135. /**
  1136. * Does the post-linking for the third stage of the compilation and returns
  1137. * the linked code.
  1138. *
  1139. * @internal
  1140. * @param Opt_Xml_Node $item The linked item.
  1141. * @return String
  1142. */
  1143. protected function _doPostlinking($item)
  1144. {
  1145. // Post code
  1146. if(is_null($item))
  1147. {
  1148. return '';
  1149. }
  1150. // This prevents from displaying </> if the HTML node was hidden.
  1151. if($item->get('hidden') !== false)
  1152. {
  1153. return '';
  1154. }
  1155. if($item->get('_skip_postlinking') == true)
  1156. {
  1157. return '';
  1158. }
  1159. $output = '';
  1160. switch($item->getType())
  1161. {
  1162. case 'Opt_Xml_Text':
  1163. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1164. break;
  1165. case 'Opt_Xml_Element':
  1166. if($this->isNamespace($item->getNamespace()))
  1167. {
  1168. if($item->get('single'))
  1169. {
  1170. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_SINGLE_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1171. }
  1172. else
  1173. {
  1174. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE,
  1175. Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1176. }
  1177. }
  1178. else
  1179. {
  1180. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE).'</'.$item->get('_name').'>'.$item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
  1181. $item->set('_name', NULL);
  1182. }
  1183. break;
  1184. case 'Opt_Xml_Root':
  1185. $output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
  1186. break;
  1187. }
  1188. $this->_closeComments($item, $output);
  1189. return $output;
  1190. } // end _doPostlinking();
  1191. /**
  1192. * Closes the XML comment for the commented item.
  1193. *
  1194. * @internal
  1195. * @param Opt_Xml_Node $item The commented item.
  1196. * @param String &$output The reference to the output buffer.
  1197. */
  1198. protected function _closeComments($item, &$output)
  1199. {
  1200. if($item->get('commented'))
  1201. {
  1202. $this->_comments--;
  1203. if($this->_comments == 0)
  1204. {
  1205. // According to the XML grammar, the construct "--->" is not allowed.
  1206. if(strlen($output) > 0 && $output[strlen($output)-1] == '-')
  1207. {
  1208. throw new Opt_XmlComment_Exception('--->');
  1209. }
  1210. $output .= '-->';
  1211. }
  1212. }
  1213. } // end _closeComments();
  1214. /**
  1215. * Links the element attributes into a valid XML code and returns
  1216. * the output code.
  1217. *
  1218. * @internal
  1219. * @param Opt_Xml_Element $subitem The XML element.
  1220. * @return String
  1221. */
  1222. protected function _linkAttributes($subitem)
  1223. {
  1224. // Links the attributes into the PHP code
  1225. if($subitem->hasAttributes() || $subitem->bufferSize(Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES) > 0 || $subitem->bufferSize(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES) > 0)
  1226. {
  1227. $code = $subitem->buildCode(Opt_Xml_Buffer::TAG_ATTRIBUTES_BEFORE, Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES);
  1228. $attrList = $subitem->getAttributes();
  1229. // Link attributes into a string
  1230. foreach($attrList as $attribute)
  1231. {
  1232. $s = $attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_NAME);
  1233. switch($s)
  1234. {
  1235. case 0:
  1236. $code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN).' '.$attribute->getXmlName();
  1237. break;
  1238. case 1:
  1239. $code .= ($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_BEGIN) == 0 ? ' ' : '').$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN, ' ', Opt_Xml_Buffer::ATTRIBUTE_NAME);
  1240. break;
  1241. default:
  1242. throw new Opt_CompilerCodeBufferConflict_Exception(1, 'ATTRIBUTE_NAME', $subitem->getXmlName());
  1243. }
  1244. if($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_VALUE) == 0)
  1245. {
  1246. // Static value
  1247. if(!($this->_tpl->htmlAttributes && $attribute->getValue() == $attribute->getName()))
  1248. {
  1249. $code .= '="'.htmlspecialchars($attribute->getValue()).'"';
  1250. }
  1251. }
  1252. else
  1253. {
  1254. $code .= '="'.$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_VALUE).'"';
  1255. }
  1256. $code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_END);
  1257. }
  1258. return $code.$subitem->buildCode(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES, Opt_Xml_Buffer::TAG_ATTRIBUTES_AFTER);
  1259. }
  1260. return '';
  1261. } // end _linkAttributes();
  1262. /*
  1263. * Main compilation methods
  1264. */
  1265. /**
  1266. * The compilation launcher. It executes the proper compilation steps
  1267. * according to the inheritance rules etc.
  1268. *
  1269. * @param String $code The source code to be compiled.
  1270. * @param String $filename The source template filename.
  1271. * @param String $compiledFilename The output template filename.
  1272. * @param Int $mode The compilation mode.
  1273. */
  1274. public function compile($code, $filename, $compiledFilename, $mode)
  1275. {
  1276. try
  1277. {
  1278. // We cannot compile two templates at the same time
  1279. if(!is_null($this->_template))
  1280. {
  1281. throw new Opt_CompilerLocked_Exception($filename, $this->_template);
  1282. }
  1283. // Detecting recursive inclusion
  1284. if(is_null(self::$_recursionDetector))
  1285. {
  1286. self::$_recursionDetector = array(0 => $filename);
  1287. $weFree = true;
  1288. }
  1289. else
  1290. {
  1291. if(in_array($filename, self::$_recursionDetector))
  1292. {
  1293. $exception = new Opt_CompilerRecursion_Exception($filename);
  1294. $exception->setData(self::$_recursionDetector);
  1295. throw $exception;
  1296. }
  1297. self::$_recursionDetector[] = $filename;
  1298. $weFree = false;
  1299. }
  1300. // Cleaning up the processors
  1301. foreach($this->_processors as $proc)
  1302. {
  1303. $proc->reset();
  1304. }
  1305. // Initializing the template launcher
  1306. $this->set('template', $this->_template = $filename);
  1307. $this->set('mode', $mode);
  1308. $this->set('currentTemplate', $this->_template);
  1309. array_push(self::$_templates, $filename);
  1310. $this->_stack = new SplStack;
  1311. $i = 0;
  1312. $extend = $filename;
  1313. $memory = 0;
  1314. // The inheritance loop
  1315. do
  1316. {
  1317. // Stage 1 - code compilation
  1318. if($this->_tpl->debugConsole)
  1319. {
  1320. $initial = memory_get_usage();
  1321. $tree = $this->_stage1($code, $extend, $mode);
  1322. // Stage 2 - PHP tree processing
  1323. $this->_stack = array();
  1324. $this->_stage2($tree);
  1325. $this->set('escape', NULL);
  1326. unset($this->_stack);
  1327. $memory += (memory_get_usage() - $initial);
  1328. unset($code);
  1329. }
  1330. else
  1331. {
  1332. $tree = $this->_stage1($code, $extend, $mode);
  1333. unset($code);
  1334. // Stage 2 - PHP tree processing
  1335. $this->_stack = array();
  1336. $this->_stage2($tree);
  1337. $this->set('escape', NULL);
  1338. unset($this->_stack);
  1339. }
  1340. // if the template extends something, load it and also process
  1341. if(isset($extend) && $extend != $filename)
  1342. {
  1343. $this->addDependantTemplate($extend);
  1344. }
  1345. if(!is_null($snippet = $tree->get('snippet')))
  1346. {
  1347. $tree->dispose();
  1348. unset($tree);
  1349. // Change the specified snippet into a root node.
  1350. $tree = new Opt_Xml_Root;
  1351. $attribute = new Opt_Xml_Attribute('opt:use', $snippet);
  1352. $this->processor('snippet')->processAttribute($tree, $attribute);
  1353. $this->processor('snippet')->postprocessAttribute($tree, $attribute);
  1354. $this->_stage2($tree, true);
  1355. break;
  1356. }
  1357. if(!is_null($extend = $tree->get('extend')))
  1358. {
  1359. $tree->dispose();
  1360. unset($tree);
  1361. $this->set('currentTemplate', $extend);
  1362. array_pop(self::$_templates);
  1363. array_push(self::$_templates, $extend);
  1364. $code = $this->_tpl->_getSource($extend);
  1365. }
  1366. $i++;
  1367. }
  1368. while(!is_null($extend));
  1369. // There are some dependant templates. We must add a suitable PHP code
  1370. // to the output.
  1371. if(sizeof($this->_dependencies) > 0)
  1372. {
  1373. $this->_addDependencies($tree);
  1374. }
  1375. if($this->_tpl->debugConsole)
  1376. {
  1377. Opt_Support::addCompiledTemplate($this->_template, $memory);
  1378. }
  1379. // Stage 3 - linking the last tree
  1380. if(!is_null($compiledFilename))
  1381. {
  1382. $output = '';
  1383. $this->_dynamicBlocks = array();
  1384. $this->_stage3($output, $tree);
  1385. $tree->dispose();
  1386. unset($tree);
  1387. $output = str_replace('?><'.'?php', '', $output);
  1388. // Build the directories, if needed.
  1389. if(($pos = strrpos($compiledFilename, '/')) !== false)
  1390. {
  1391. $path = $this->_tpl->compileDir.substr($compiledFilename, 0, $pos);
  1392. if(!is_dir($path))
  1393. {
  1394. mkdir($path, 0750, true);
  1395. }
  1396. }
  1397. // Save the file
  1398. if(sizeof($this->_dynamicBlocks) > 0)
  1399. {
  1400. file_put_contents($this->_tpl->compileDir.$compiledFilename.'.dyn', serialize($this->_dynamicBlocks));
  1401. }
  1402. file_put_contents($this->_tpl->compileDir.$compiledFilename, $output);
  1403. }
  1404. else
  1405. {
  1406. $tree->dispose();
  1407. }
  1408. array_pop(self::$_templates);
  1409. $this->_inheritance = array();
  1410. if($weFree)
  1411. {
  1412. // Do the cleanup.
  1413. $this->_dependencies = array();
  1414. self::$_recursionDetector = NULL;
  1415. foreach($this->_processors as $processor)
  1416. {
  1417. $processor->reset();
  1418. }
  1419. }
  1420. $this->_template = NULL;
  1421. // Run the new garbage collector, if it is available.
  1422. /* if(version_compare(PHP_VERSION, '5.3.0', '>='))
  1423. {
  1424. gc_collect_cycles();
  1425. }*/
  1426. }
  1427. catch(Exception $e)
  1428. {
  1429. // Free the memory
  1430. if(isset($tree))
  1431. {
  1432. $tree->dispose();
  1433. }
  1434. // Clean the compiler state in case of exception
  1435. $this->_template = NULL;
  1436. $this->_dependencies = array();
  1437. self::$_recursionDetector = NULL;
  1438. foreach($this->_processors as $processor)
  1439. {
  1440. $processor->reset();
  1441. }
  1442. // Run the new garbage collector, if it is available.
  1443. /* if(version_compare(PHP_VERSION, '5.3.0', '>='))
  1444. {
  1445. gc_collect_cycles();
  1446. }*/
  1447. // And throw it forward.
  1448. throw $e;
  1449. }
  1450. } // end compile();
  1451. /**
  1452. * Compilation - stage 1 - parsing the input template and
  1453. * building an XML tree.
  1454. *
  1455. * @internal
  1456. * @param String &$code The code to be parsed
  1457. * @param String $filename Currently unused.
  1458. * @param Int $mode The compilation mode.
  1459. * @return Opt_Xml_Root The root node of the new tree.
  1460. */
  1461. protected function _stage1(&$code, $filename, $mode)
  1462. {
  1463. $current = $tree = new Opt_Xml_Root;
  1464. $codeSize = strlen($code);
  1465. $encoding = $this->_tpl->charset;
  1466. // First we have to find the prolog and DTD. Then we will be able to parse tags.
  1467. if($mode != Opt_Class::QUIRKS_MODE)
  1468. {
  1469. // Find and parse XML prolog
  1470. $endProlog = 0;
  1471. $endDoctype = 0;
  1472. if(substr($code, 0, 5) == '<?xml')
  1473. {
  1474. $endProlog = strpos($code, '?>', 5);
  1475. if($endProlog === false)
  1476. {
  1477. throw new Opt_XmlInvalidProlog_Exception('prolog ending is missing');
  1478. }
  1479. $values = $this->_compileProlog(substr($code, 5, $endProlog - 5));
  1480. $endProlog += 2;
  1481. if(!$this->_tpl->prologRequired)
  1482. {
  1483. // The prolog must be displayed
  1484. $tree->setProlog(new Opt_Xml_Prolog($values));
  1485. }
  1486. }
  1487. // Skip white spaces
  1488. for($i = $endProlog; $i < $codeSize; $i++)
  1489. {
  1490. if($code[$i] != ' ' && $code[$i] != ' ' && $code[$i] != "\r" && $code[$i] != "\n")
  1491. {
  1492. break;
  1493. }
  1494. }
  1495. // Try to find doctype at the new position.
  1496. $possibleDoctype = substr($code, $i, 9);
  1497. if($possibleDoctype == '<!doctype' || $possibleDoctype == '<!DOCTYPE')
  1498. {
  1499. // OK, we've found it, now determine the doctype end.
  1500. $bracketCounter = 0;
  1501. $doctypeStart = $i;
  1502. for($i += 9; $i < $codeSize; $i++)
  1503. {
  1504. if($code[$i] == '<')
  1505. {
  1506. $bracketCounter++;
  1507. }
  1508. else if($code[$i] == '>')
  1509. {
  1510. if($bracketCounter == 0)
  1511. {
  1512. $endDoctype = $i;
  1513. break;
  1514. }
  1515. $bracketCounter--;
  1516. }
  1517. }
  1518. if($endDoctype == 0)
  1519. {
  1520. throw new Opt_XmlInvalidDoctype_Exception('doctype ending is missing');
  1521. }
  1522. if(!$this->_tpl->prologRequired)
  1523. {
  1524. $tree->setDtd(new Opt_Xml_Dtd(substr($code, $doctypeStart, $i - $doctypeStart + 1)));
  1525. }
  1526. $endDoctype++;
  1527. }
  1528. else
  1529. {
  1530. $endDoctype = $endProlog;
  1531. }
  1532. // OK, now skip that part.
  1533. $code = substr($code, $endDoctype, $codeSize);
  1534. // In the quirks mode, some results from the regular expression parser are
  1535. // moved by one position, so we must add some dynamics here.
  1536. $attributeCell = 5;
  1537. $endingSlashCell = 6;
  1538. $tagExpression = $this->_rXmlTagExpression;
  1539. }
  1540. else
  1541. {
  1542. $tagExpression = $this->_rQuirksTagExpression;
  1543. $attributeCell = 6;
  1544. $endingSlashCell = 7;
  1545. }
  1546. // Split through the general groups (cdata-content)
  1547. $groups = preg_split($this->_rCDataExpression, $code,

Large files files are truncated, but you can click here to view the full file