Source for file Crafty_Compiler.class.php

Documentation is available at Crafty_Compiler.class.php

  1. <?PHP
  2.  
  3. /**
  4. * Implementation file of the Crafty_Compiler class.
  5. *
  6. * <pre>
  7. * PROJECT : Crafty
  8. * Template Engine.
  9. * AUTHOR : Crafty Team <crafty@zulan.net>
  10. * COPYRIGHT : (c) Thomas Ilsche, 2004
  11. *
  12. * FILE : [ROOT]\Crafty_Compiler.class.php
  13. * DESCRIPTION: Implementation file of the Crafty_Compiler class.
  14. * </pre>
  15. *
  16. * This library is free software; you can redistribute it and/or
  17. * modify it under the terms of the GNU Lesser General Public
  18. * License as published by the Free Software Foundation; either
  19. * version 2.1 of the License, or (at your option) any later version.
  20. *
  21. * This library is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  24. * Lesser General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Lesser General Public
  27. * License along with this library; if not, write to the Free Software
  28. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  29. *
  30. * @package Crafty_Compiler
  31. * @author Crafty Team <crafty@zulan.net>
  32. * @copyright Copyright (c) Thomas Ilsche 2005
  33. * @version 1.0.4.2005.02.18
  34. * @link http://crafty.zulan.net
  35. * @license http://www.gnu.org/licenses/lgpl.html
  36. * GNU Lesser General Public License Version 2.1
  37. ***/
  38.  
  39. /**
  40. * Crafty_Compiler class implementation
  41. *
  42. * This is the compiler class of the Crafty template engine. If you just want
  43. * to write templates, you will not need to make use of it.
  44. * The {@link Crafty} class will spawn an instance for internal usage if required.
  45. *
  46. * @package Crafty_Compiler
  47. * @author Crafty Team <crafty@zulan.net>
  48. * @copyright Copyright (c) Thomas Ilsche 2005
  49. * @version 1.0.4.2005.02.18
  50. * @link http://crafty.zulan.net
  51. * @license http://www.gnu.org/licenses/lgpl.html
  52. * GNU Lesser General Public License Version 2.1
  53. ***/
  54. class Crafty_Compiler
  55. {
  56. /**
  57. * Stores all {@link Crafty_Block} instances
  58. *
  59. * All blocks loaded will be created and stored in this array.
  60. * They are indexed by their name.
  61. *
  62. * @var array
  63. ***/
  64. public $blocks = array();
  65.  
  66. /**
  67. * Stores all Crafty_Core_* instances
  68. *
  69. * All Crafty_Core_* instances that are loaded, except for
  70. * {@link Crafty_Core_Define} and {@link Crafty_Core_Require}, will be stored in
  71. * this array indexed by their classname.
  72. *
  73. * @var array
  74. ***/
  75. public $cores = array();
  76.  
  77. /**
  78. * Stores the root {@link Crafty_Config} object
  79. *
  80. * This object is loaded by the {@link __construct() constructor} from the
  81. * Crafty_Compiler.config.xml file
  82. *
  83. * @var Crafty_Config
  84. ***/
  85. public $config;
  86.  
  87. /**
  88. * Stores a filehandle to the compiled (binary) template
  89. *
  90. * {@link Crafty_Block::compileElement()} will use this filehandle to write the
  91. * compiled content into the file.
  92. *
  93. * @var resource
  94. ***/
  95. public $write_c_file_handle;
  96.  
  97. /**
  98. * Object handle of a Crafty object
  99. *
  100. * This is the object that created the current Crafty_Compiler class.
  101. * Set by the {@link __construct() Constructor}
  102. *
  103. * @var Crafty
  104. ***/
  105. public $crafty;
  106.  
  107. /**
  108. * Are variable blocks used?
  109. *
  110. * If this is TRUE {@link compile()} will also compile every loaded block
  111. * as variable block into a separate file.
  112. *
  113. * @var boolean
  114. ***/
  115. public $variable_blocks_used = FALSE;
  116.  
  117. /**
  118. * Stores the name of the compiled template and its directory
  119. *
  120. * This is the MD5 hash of realpath($template_name), constructed by
  121. * {@link Crafty::display()}
  122. *
  123. * @var string
  124. ***/
  125. public $template_hash = '';
  126.  
  127. /**
  128. * Path to the uncompiled template
  129. *
  130. * Set by {@link load()} and used by several Crafty_Core objects,
  131. * e.g. to check if an external block is present.
  132. *
  133. * @var string
  134. ***/
  135. public $template_path = '';
  136.  
  137. /**
  138. * Array of strings that need to be replaced before parsing
  139. *
  140. * Double backslashes, backslash and single quote, backslash and double quote are
  141. * replaced during the process of parsing to protect the core pregs from beeing
  142. * confused.
  143. *
  144. * @var array
  145. ***/
  146. public $preparse_search = array();
  147.  
  148. /**
  149. * Temporary replacements during the parsing process for {@link $preparse_search}
  150. *
  151. * The {@link __construct constructor} will create this array by some sha1 hashes
  152. *
  153. * @var array
  154. */
  155. public $preparse_replace = array();
  156.  
  157. /**
  158. * Final replacements for {@link $preparse_search}
  159. *
  160. * Those will be replaced just after parsing. Security will be cared of
  161. * afterwards.
  162. *
  163. * @var array
  164. ***/
  165. public $preparse_final = array();
  166.  
  167. /**
  168. * Stores defined values
  169. *
  170. * Associative array of all defines, created by {@link load()} using
  171. * {@link Crafty_Core_Define}
  172. *
  173. * @var array
  174. ***/
  175. public $defines = array();
  176.  
  177. /**
  178. * Initializes a Crafty_Compiler instance
  179. *
  180. * The constructor intializes a Crafty_Compiler instance and loads the required
  181. * framework files, such as the class definitions for {@link Crafty_Config},
  182. * {@link Crafty_Block}, {@link Crafty_Core}, {@link Crafty_Core_Define},
  183. * {@link Crafty_Core_Require} and all other Crafty_Core_* classes.<br/>
  184. * It also instantiates the Crafty_Core_* classes and stores them in
  185. * {@link $cores}
  186. *
  187. * @param Crafty $crafty Crafty object that created this Compiler
  188. ***/
  189. public function __construct(Crafty $crafty)
  190. {
  191. require_once($crafty->path_crafty . 'Crafty_Config.class.php');
  192. require_once($crafty->path_crafty . 'Crafty_Block.class.php');
  193. require_once($crafty->path_crafty . 'core' . DIRECTORY_SEPARATOR
  194. . 'Crafty_Core.class.php');
  195.  
  196. $this->crafty = $crafty;
  197. $this->config = new crafty_config($crafty->path_crafty
  198. . 'Crafty_Compiler.config.xml', $this);
  199. $path_core = $crafty->path_crafty . 'core' . DIRECTORY_SEPARATOR;
  200.  
  201. /* escape sequences */
  202. $this->add_preparse('\\');
  203. $this->add_preparse('"');
  204. $this->add_preparse("'");
  205. $this->add_preparse(stripslashes($this->config->core->delimiter->left));
  206. $this->add_preparse(stripslashes($this->config->core->delimiter->right));
  207.  
  208. $this->defines['_CRAFTY_COMPILE_TIME']['static'] = time();
  209. $this->defines['_CRAFTY_VERSION']['static'] = CRAFTY_VERSION;
  210.  
  211. /* Loading the Core_* Casses */
  212. if (($dir = opendir($path_core)) === FALSE) {
  213. throw new Crafty_Exception("Cannot open core directory '"
  214. . $path_core ."'");
  215. return FALSE;
  216. }
  217. while ($file = readdir($dir)) {
  218. if ((substr($file, 0, 12) == 'Crafty_Core_')
  219. && (substr($file, -10) == '.class.php')) {
  220. require_once($path_core . $file);
  221. $core_class_name = substr($file, 0, -10);
  222. if (!class_exists($core_class_name)) {
  223. throw new Crafty_Exception("No class found named
  224. $core_class_name");
  225. } else {
  226. $new_core = new $core_class_name($this);
  227. if (!is_subclass_of($new_core, 'Crafty_Core')) {
  228. throw new Crafty_Exception("Crafty_Core_" . $new_core->name
  229. . ' is not a subclass of
  230. Crafty_Core and can not be used
  231. as core ');
  232. } else {
  233. $this->cores[$core_class_name] = $new_core;
  234. }
  235. }
  236. }
  237. }
  238. closedir($dir);
  239. require_once($crafty->path_crafty . 'Crafty_Core_Define.class.php');
  240. require_once($crafty->path_crafty . 'Crafty_Core_Require.class.php');
  241. }
  242.  
  243. /**
  244. * Loads a template file.
  245. *
  246. * This method loads a template file, parses the {@link $define}s using
  247. * {@link Crafty_Core_Define}, parses the requires using
  248. * {@link Crafty_Core_Require} and calling itself for each require.<br/>
  249. * Then it parses the blocks and stores them in the {@link $blocks} array.
  250. *
  251. * @param $template Relative path to the template
  252. * @return boolean State of success
  253. ***/
  254. public function load($template_o)
  255. {
  256. $template = realpath($template_o);
  257. if ($template === FALSE) {
  258. throw new Crafty_Exception('Template file "' . $template_o
  259. . '" does not exist');
  260. }
  261. $old_template_path = $this->template_path;
  262. $this->template_path = dirname($template);
  263.  
  264. if (!is_readable($template)) {
  265. throw new Crafty_Exception("Cannot open template file \"$template\"");
  266. }
  267. $content = file_get_contents($template);
  268.  
  269. /* Config var loader */
  270. $core_define = new Crafty_Core_Define($this);
  271. if ($core_define->getPattern() &&
  272. (preg_match_all($core_define->getPattern(), $content, $matches,
  273. PREG_SET_ORDER | PREG_OFFSET_CAPTURE) !== FALSE)) {
  274. foreach($matches as $match_num => $match) {
  275. $callback_handover = array();
  276. foreach($match as $sub) {
  277. $callback_handover[] = $sub[0];
  278. }
  279. try {
  280. $pcm = $core_define->pregCallback($callback_handover);
  281. } catch ( Crafty_Exception $e ) {
  282. $e->file = $template;
  283. $e->line = $this->line_number($content, $match[0][1]);
  284. throw $e;
  285. }
  286. /* Cheap solution but should do it */
  287. /* This is now (v.1.0.4) done by the _parameterCheck
  288. $this->defines = array_merge($pcm['data'], $this->defines); */
  289. }
  290. unset($pcm);
  291. }
  292. unset($core_define);
  293.  
  294. $core_require = new Crafty_Core_Require($this);
  295. if ($core_require->getPattern() &&
  296. (preg_match_all($core_require->getPattern(), $content, $matches,
  297. PREG_SET_ORDER | PREG_OFFSET_CAPTURE) !== FALSE)) {
  298. foreach($matches as $match_num => $match) {
  299. $callback_handover = array();
  300. foreach($match as $sub) {
  301. $callback_handover[] = $sub[0];
  302. }
  303. try {
  304. $pcm = $core_require->pregCallback($callback_handover);
  305. } catch ( Crafty_Exception $e ) {
  306. $e->file = $template;
  307. $e->line = $this->line_number($content, $match[0][1]);
  308. throw $e;
  309. }
  310. }
  311. unset($pcm);
  312. unset($matches);
  313. $this->variable_blocks_used = TRUE;
  314. }
  315. unset($core_require);
  316.  
  317. /* I dislike using temp vars but this will make it look better... really */
  318. $cbd = $this->config->block->delimiter;
  319.  
  320. $pattern = '/(?>' . sprintf($cbd->format, $cbd->start . '['
  321. . $cbd->top->separator[0] . '](.*?)')
  322. . '\r?\n?)'
  323. . '((?s).*?)'
  324. . sprintf($cbd->format, $cbd->end) . '/i';
  325.  
  326. if (!preg_match_all($pattern, $content, $matches,
  327. PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
  328. throw new Crafty_Exception('Can not find any BLOCK', $template);
  329. }
  330.  
  331. $split_pattern = '/[' . $cbd->top->separator[0] . ']/';
  332. foreach($matches as $number => $match) {
  333. $line_decl = $this->line_number($content, $match[1][1]);
  334.  
  335. $splitted = preg_split($split_pattern, $match[1][0]);
  336. /* Lowercase all parameters... */
  337. for($i = 0; $i < count($splitted); $i++) {
  338. $splitted[$i] = strtolower($splitted[$i]);
  339. }
  340. if (empty($splitted[0])) {
  341. throw new Crafty_Exception('Block with no name',
  342. $template, $line_decl);
  343. }
  344. $name = $splitted[0];
  345.  
  346. if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]*$/', $name)) {
  347. throw new Crafty_Exception("Invalid blockname: '". $name ."'",
  348. $template, $line_decl);
  349. }
  350. $block = new Crafty_Block($name, $this);
  351. if (in_array('external', $splitted)) {
  352. $block->is_external = TRUE;
  353. }
  354. $block->filename = $template;
  355. $block->line_decl = $line_decl;
  356. $block->line_cont = $this->line_number($content, $match[2][1]);
  357. if (isset($this->blocks[$block->name])) {
  358. throw new Crafty_Exception('Cannot redeclare block named "'
  359. . $block->name
  360. . '" (previously declared in "'
  361. . $this->blocks[$block->name]->filename
  362. . '" on line '
  363. . $this->blocks[$block->name]->line_decl
  364. . ')'
  365. , $template, $line_decl);
  366. }
  367. $block->load($match[2][0]);
  368. $this->blocks[$block->name] = $block;
  369. }
  370.  
  371. /* for recursion... */
  372. $this->template_path = $old_template_path;
  373. }
  374.  
  375. /**
  376. * Compiles a template
  377. *
  378. * This method basically opens a compiled template file, adds the php tags
  379. * and compiles the "main" block.<br/>
  380. * If {@link $variable_blocks_used variable blocks are used} it also compiles any
  381. * loaded template file into a separate file.
  382. *
  383. * @param $template_hash Hashfilename of the current template
  384. * @return boolean State of success
  385. ***/
  386. public function compile($template_hash)
  387. {
  388. $this->template_hash = $template_hash;
  389. $template_c = $this->crafty->path_templates_c . $this->template_hash
  390. . '.tpl.php';
  391. if (!isset($this->blocks['main'])) {
  392. throw new Crafty_Exception('No "main" block found');
  393. }
  394.  
  395. /* Initializing the prefix and suffix blocks */
  396. $block_prefix = new Crafty_Block('', $this);
  397. $block_prefix->content[] = "<?php\n";
  398. $block_prefix->content[] = '/* file generated by Crafty version ';
  399. $block_prefix->content[] = CRAFTY_VERSION;
  400. $block_prefix->content[] = ' at ';
  401. $block_prefix->content[] = date('Y-m-d H:i:s');
  402. $block_prefix->content[] = ', PHP version ';
  403. $block_prefix->content[] = PHP_VERSION;
  404. $block_prefix->content[] = " */\n";
  405.  
  406. $block_suffix = new Crafty_Block('', $this);
  407. $block_suffix->content[] = "?>\n";
  408.  
  409.  
  410.  
  411. if (file_exists($template_c)) {
  412. if (!is_writable($template_c)) {
  413. throw new Crafty_Exception("No write permission on \"$template_c\""
  414. . ' even though the file exists.<br />'
  415. . ' Please grant write access for the user'
  416. . ' / process running php on all files in'
  417. . ' the "'
  418. . $this->crafty->path_templates_c
  419. . '" directory.');
  420. }
  421. } else {
  422. if (!is_writable($this->crafty->path_templates_c)) {
  423. throw new Crafty_Exception('No write permission on "'
  424. . $this->crafty->path_templates_c
  425. . '". Please grant write access for the'
  426. . 'user / process running php.');
  427. }
  428. }
  429. $file_handle = fopen($template_c, 'w');
  430. $this->write_c_file_handle = $file_handle;
  431.  
  432. $block_prefix->compile();
  433. $this->blocks['main']->compile();
  434.  
  435. if ($this->variable_blocks_used) { /* OH MY GOD! WE'RE DOOMED! */
  436. $block_func_prefix = new Crafty_Block('', $this);
  437. $block_func_prefix->content[000] = 'function _';
  438. $block_func_prefix->content[010] = $this->template_hash;
  439. $block_func_prefix->content[020] = '_';
  440. $block_func_prefix->content[100] = ''; /* block name */
  441. $block_func_prefix->content[200] = "(\$p,&\$d){\n";
  442.  
  443. $block_func_suffix = new Crafty_Block('', $this);
  444. $block_func_suffix->content[0] = "}\n";
  445.  
  446. $block_func_inc = new Crafty_Block('', $this);
  447. $block_func_inc->content[000] = "include('";
  448. $block_func_inc->content[100] = '';
  449. $block_func_inc->content[200] = "');\n";
  450.  
  451. foreach($this->blocks as $block_name => $block) {
  452. $block_func_prefix->content[100] = $block_name;
  453. $block_func_prefix->compile();
  454. $block->name = "main.'.\$p.'";
  455. if (!$block->is_external) {
  456. $block->compile();
  457. } else {
  458. $block_dir = $this->crafty->path_templates_c . $this->template_hash
  459. . '_files';
  460. $block_file = $block_dir . DIRECTORY_SEPARATOR . $block_name
  461. . '.block.tpl.php';
  462. $block_func_inc->content[100] = $block_file;
  463. $block_func_inc->compile();
  464. }
  465. $block_func_suffix->compile();
  466. }
  467. }
  468.  
  469. $block_suffix->compile();
  470.  
  471. fclose($file_handle);
  472. $this->write_c_file_handle = NULL;
  473.  
  474. /* External variable blocks */
  475. if ($this->variable_blocks_used) { /* OH MY GOD! WE'RE DOOMED! */
  476. foreach($this->blocks as $block_name => $block) {
  477. if ($block->is_external) {
  478. $block_dir = $this->crafty->path_templates_c . $this->template_hash
  479. . '_files';
  480. if (!is_dir($block_dir)) {
  481. if (!mkdir($block_dir)) {
  482. throw new Crafty_Exception('Cannot create compiled block '
  483. . 'directory: "' . $block_dir
  484. . '" Check your chmod settings for '
  485. . 'the compiled template path.');
  486. }
  487. }
  488. $block_file = $block_dir . DIRECTORY_SEPARATOR . $block_name
  489. . '.block.tpl.php';
  490. if (($file_handle = fopen($block_file, 'w')) === FALSE) {
  491. throw new Crafty_Exception('Cannot open template compile '
  492. . "\"$template_c\" for writing");
  493. }
  494. $this->write_c_file_handle = $file_handle;
  495. $block_prefix->compile();
  496. /*$block->name = "main.'.\$p.'"; done already */
  497. $block->compile();
  498. $block_suffix->compile();
  499. fclose($file_handle);
  500. $this->write_c_file_handle = NULL;
  501. }
  502. }
  503. }
  504.  
  505. return TRUE;
  506. }
  507.  
  508. /**
  509. * Booleanize a value
  510. *
  511. * Check the string for true|yes|on|1 / false|no|off|0 and return a boolean in that
  512. * case
  513. *
  514. * @param string $value
  515. * @return mixed
  516. ***/
  517. public function booleanize($value)
  518. {
  519. if (preg_match('/^\s*(true|yes|on|1)\s*$/i', $value)) {
  520. return true;
  521. } elseif (preg_match('/^\s*(false|no|off|0)\s*$/i', $value)) {
  522. return false;
  523. } else {
  524. return $value;
  525. }
  526. }
  527.  
  528.  
  529. /**
  530. * Gets the linenumber of an offset
  531. *
  532. * @param string $content
  533. * @param int $offset
  534. * @param string $start_line Optional: Default 1
  535. * @return int
  536. ***/
  537. public function line_number($content, $offset, $start_line = 1)
  538. {
  539. return preg_match_all('/\r\n|\n|\r/', substr($content,0,$offset), $void) + $start_line;
  540. }
  541.  
  542. /**
  543. * Adds a character(sequence) that will be able to be escaped by a backslash
  544. *
  545. * @param string character(sequence) to be escapable
  546. ***/
  547. public function add_preparse($character)
  548. {
  549. $this->preparse_search[] = '\\' . $character;
  550. $this->preparse_replace[] = sha1($character . 'Crafty');
  551. $this->preparse_final[] = $character;
  552. }
  553. }
  554. ?>

Documentation generated on Sat, 19 Feb 2005 01:43:40 +0100 by phpDocumentor 1.3.0RC3