com.lokorin.lokorin.lib
[ class tree: com.lokorin.lokorin.lib ] [ index: com.lokorin.lokorin.lib ] [ all elements ]

Source for file lib_css.php

Documentation is available at lib_css.php

  1. <?php
  2. /*
  3. * Lokorin.com
  4. * Copyright 2004-2006
  5. * Licensed under the GNU LGPL. See COPYING for full terms.
  6. */
  7. /**
  8. * Describes a library responsible for processing and caching CSS files. The
  9. * library allows variables to be used within the CSS files.
  10. * @author Andreas Launila
  11. * @version $Revision: 1.2 $
  12. * @package com.lokorin.lokorin.lib
  13. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  14. */
  15. /**
  16. * For access to common constants and functions.
  17. */
  18. require_once('common.php');
  19.  
  20. /**
  21. * Unlinks all cached CSS files.
  22. * @return integer The number of files that were unlinked.
  23. * @throws IOException If the css cache folder couldn't be opened.
  24. */
  25. function clearCssCache() {
  26. //Unlink all files in the cache directory with a .css extension.
  27. if(!($dir = @opendir(DOC_ROOT.FOLDER_CSS_CACHE))) {
  28. throw new IOException("The CSS cache folder could not be opened.");
  29. }
  30. $counter = 0;
  31. while(($file = readdir($dir)) !== false) {
  32. if(($file != '..') && ($file != '.') && (substr($file, -4) == ".css")) {
  33. unlink(DOC_ROOT.FOLDER_CACHE.$file);
  34. $counter++;
  35. }
  36. }
  37. closedir($dir);
  38. return $counter;
  39. }
  40.  
  41. /**
  42. * A class that handles everything that has to do with CSS, all CSS files that
  43. * are to be included are recorded by this class. The appropriate actions are
  44. * then taken to make sure that each of the files are then handled correctly
  45. * when the page should be displayed. Implements the singleton pattern.
  46. * @author Andreas Launila
  47. * @package com.lokorin.lokorin.lib
  48. */
  49. class CssHandler implements HeadAddable {
  50. /**
  51. * The one and only instance of the class.
  52. * @var CssHandler
  53. */
  54. static private $instance;
  55. /**
  56. * The names of all CSS files that should be included when displaying the
  57. * page.
  58. */
  59. private $cssNames;
  60. /**
  61. * The name of the XML resource file that contains all CSS values.
  62. */
  63. const CSS_VALUE_FILE_NAME = 'cssValues.xml';
  64. /**
  65. * The CSS values as a tree structure. The array may contain arrays, in
  66. * which case the key is the name of the node. The array may also contain
  67. * textual values, which are leaves.
  68. * @var array
  69. */
  70. private $cssValues;
  71. /**
  72. * Gets the one and only instance of the class.
  73. * @return CssTemplateConverter The one and only instance.
  74. */
  75. static public function getInstance() {
  76. if(!isset(CssHandler::$instance)) {
  77. CssHandler::$instance = new CssHandler();
  78. }
  79. return CssHandler::$instance;
  80. }
  81. /**
  82. * Constructor for CssConverter. Adds itself as additional head information
  83. * to the page.
  84. */
  85. private function __construct() {
  86. $this->cssNames = array();
  87. Page::getInstance()->addToHead($this);
  88. }
  89. /**
  90. * Gets the XHTML link that represents the specified CSS name.
  91. * @param $cssName The name of the CSS file to which the link should be
  92. * created.
  93. * @access public
  94. */
  95. private function getCssLink($cssName) {
  96. $fileName = $this->stripCssExtension($cssName).'.css';
  97. $cssFile = FOLDER_CSS_CACHE.$fileName;
  98. if(!file_exists(DOC_ROOT.$cssFile)) {
  99. //A cached version of the file does not exist, create it.
  100. $text = file_get_contents(DOC_ROOT.FOLDER_CSS.$fileName);
  101. if($text === false) {
  102. //Could not read the file.
  103. throw new IOException('The css file ('.$cssFile.') could not ' .
  104. 'be read.');
  105. }
  106. file_put_contents(DOC_ROOT.$cssFile, $this->convertTemplate($text));
  107. }
  108. return '<link href="'.PAGE_ROOT.$cssFile.'" type="text/css" rel="stylesheet"/>';
  109. }
  110. /**
  111. * Strips any .css extensions that occur last in a specified text.
  112. * @param string $text The text from which .css extensions should be stripped.
  113. * @return string Text without a .css extension.
  114. */
  115. private function stripCssExtension($text) {
  116. //The method is crude and removes all occurences of '.css', alter it if it
  117. //causes problems.
  118. return str_replace('.css', '', $text);
  119. }
  120. /**
  121. * Converts a string containing a CSS template to a string representing
  122. * a real CSS file.
  123. * @param string $template A string with CSS but with added tokens that
  124. * have special meaning, making it a CSS template.
  125. * @return string The corresponding CSS (without any special tokens).
  126. * @throws IllegalArgumentException If the template contains unexpected
  127. * variables.
  128. */
  129. private function convertTemplate($template) {
  130. if(!isset($this->cssValues)) {
  131. //Make sure that the css values are loaded.
  132. $this->cssValues = $this->loadCssValueTree();
  133. }
  134. //Replace CSS value tokens with their real values.
  135. foreach($this->cssValues as $key => $values) {
  136. $template = $this->replaceCssValues($template, $values, array($key));
  137. }
  138. //Check if there are still any unreplaced values left.
  139. $matches = array();
  140. preg_match("/\\$[^\s;]+/", $template, $matches);
  141. if(count($matches) > 0) {
  142. throw new IllegalArgumentException("Unexpected variable (" .
  143. $matches[0] . ") in the template.");
  144. }
  145. return $template;
  146. }
  147. /**
  148. * Loads all CSS values into a tree structure. The resulting tree structure
  149. * reflects the XML resource file, but does not contain the root node.
  150. * @return array An array containing a tree structure. The array may contain
  151. * arrays, in which case the key is the name of the node. The array may
  152. * also contain textual values, which are leaves.
  153. * @throws IOException If the CSS value files can't be accessed.
  154. */
  155. private function loadCssValueTree() {
  156. //Read the contents of the file.
  157. $text = file_get_contents(
  158. DOC_ROOT.FOLDER_RESOURCES.CssHandler::CSS_VALUE_FILE_NAME);
  159. if($text === false) {
  160. throw new IOException("The CSS value file could not be read.");
  161. }
  162. //Parse it.
  163. $xml = simplexml_load_string($text);
  164. //Parse the root node.
  165. return $this->parseCssValueNode($xml);
  166. }
  167. /**
  168. * Recursivly parses a css value node and turns it into an array structure.
  169. * @param SimpleXmlElement $node The node that should be parsed.
  170. * @return array An array tree structure representing the node. Each array
  171. * in the structure is a subnode and each value that is not an array
  172. * represents a leaf.
  173. */
  174. private function parseCssValueNode(SimpleXmlElement $node) {
  175. $values = array();
  176. foreach($node as $name => $child) {
  177. //Parse all nodes.
  178. $values[$name] = $this->parseCssValueNode($child);
  179. }
  180.  
  181. if(count($values) == 0) {
  182. //No nodes found, it's a leaf.
  183. return (string)$node;
  184. }
  185. return $values;
  186. }
  187. /**
  188. * Replaces all css values defined by the specified array in the specified
  189. * text. The values are represented in the css text as
  190. * $namespace:subcat:value, the subcat may or may not be used, any number
  191. * of subctegories can be used after the namespace but before the value.
  192. * @param string $text The text in which values should be replaced.
  193. * @param array $values The values that shpould be replaced, the array may
  194. * contain subarrays, which are also processed.
  195. * @param array $steps The steps that have been taken so far. The elements
  196. * should be sorted in the order that they have been traversed. I.e. the
  197. * namespace should be first, then the first subcat and so on.
  198. * @return string The text but with the css values replaced.
  199. */
  200. private function replaceCssValues($text, $values, $steps) {
  201. if(!is_array($values)) {
  202. return $text;
  203. }
  204. foreach($values as $name => $value) {
  205. $loopSteps = $steps;
  206. $loopSteps[] = $name;
  207. if(is_array($value)) {
  208. //Handle the array recursivly.
  209. $text = $this->replaceCssValues($text, $value,
  210. $loopSteps + array($name));
  211. } else {
  212. $token = $this->getToken($loopSteps);
  213. $value = $this->getValue($loopSteps);
  214. //Replace the value.
  215. $text = str_replace($token." ", $value." ", $text);
  216. //We allow a trailing semicolon too.
  217. $text = str_replace($token.";", $value.";", $text);
  218. }
  219. }
  220. return $text;
  221. }
  222. /**
  223. * Gets the CSS value that correspond to the specified steps.
  224. * @param array $steps The steps that have been taken to get to the value.
  225. * The elements should be sorted in the order that they have been
  226. * traversed. I.e. the namespace should be first, then the first subcat
  227. * and so on.
  228. * @return string The corresponding CSS value.
  229. * @throws NoSuchElementException If the specified steps do not correspond
  230. * to an existing CSS value.
  231. * @throws IllegalArgumentException If the steps didn't end at a leaf.
  232. */
  233. private function getValue($steps) {
  234. if(!isset($this->cssValues)) {
  235. //Make sure that the css values are loaded.
  236. $this->cssValues = $this->loadCssValueTree();
  237. }
  238.  
  239. $node = $this->cssValues;
  240. foreach($steps as $step) {
  241. if(!isset($node[$step])) {
  242. //Value not found, throw an exception.
  243. throw new NoSuchElementException("No value could be found " .
  244. "for the parameter (" . implode(", ", $steps). ")");
  245. }
  246. $node = $node[$step];
  247. }
  248. if(is_array($node)) {
  249. throw new IllegalArgumentException("The steps (" .
  250. implode(", ", $steps) . ") did not end at a leaf.");
  251. }
  252. return $node;
  253. }
  254. /**
  255. * Gets the token that represents the steps take. It's the token that should
  256. * be replaced in any text by the corresponding value.
  257. * @param array $steps The steps that have been taken to get to the value.
  258. * The elements should be sorted in the order that they have been
  259. * traversed. I.e. the namespace should be first, then the first subcat
  260. * and so on.
  261. * @return string The corresponding token.
  262. */
  263. private function getToken($steps) {
  264. $token = "\$";
  265. foreach($steps as $step) {
  266. if(strlen($token) != 1) {
  267. $token .= ":";
  268. }
  269. $token .= $step;
  270. }
  271. return $token;
  272. }
  273. /**
  274. * Ensures that a css file with the specified name is included in the page
  275. * output.
  276. * @param string $cssName The name of the css file without the .css extension,
  277. * i.e. 'form' if one wants to ensure that the file in the css dir named
  278. * 'form.css' is included.
  279. */
  280. public function includeCss($cssName) {
  281. try {
  282. if(strpos($cssName, '/') !== false) {
  283. throw new InvalidArguementException('Invalid library name ' .
  284. '('.$cssName.').');
  285. }
  286. if(!file_exists(DOC_ROOT.FOLDER_CSS.$cssName.'.css')) {
  287. throw new FileNotFoundException('CSS file ('.$cssName.'.css) ' .
  288. 'does not exist.');
  289. }
  290. //Add it if it is not already included.
  291. if(!in_array($cssName, $this->cssNames)) {
  292. $this->cssNames[] = $cssName;
  293. }
  294. } catch(Exception $e) {
  295. logException($e);
  296. }
  297. }
  298. /**
  299. * Gets all the CSS links that should be added.
  300. */
  301. public function getHeadAddition() {
  302. $links = array();
  303. foreach($this->cssNames as $cssName) {
  304. try {
  305. $links[] = $this->getCssLink($cssName);
  306. } catch(Exception $e) {
  307. logException($e);
  308. }
  309. }
  310. return implode("\n", $links);
  311. }
  312. }
  313. ?>

Documentation generated on Sun, 16 Apr 2006 21:03:15 +0200 by phpDocumentor 1.3.0RC4