PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/branches/jsrewrite/jfx-private/classes/geshi_dev/geshi/classes/class.geshistyler.php

http://jfxcms.googlecode.com/
PHP | 640 lines | 309 code | 86 blank | 245 comment | 57 complexity | 0897e52f0b5e6deedcb2a151b5745279 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * GeSHi - Generic Syntax Highlighter
  4. * <pre>
  5. * File: geshi/classes/class.geshicodecontext.php
  6. * Author: Nigel McNie
  7. * E-mail: nigel@geshi.org
  8. * </pre>
  9. *
  10. * For information on how to use GeSHi, please consult the documentation
  11. * found in the docs/ directory, or online at http://geshi.org/docs/
  12. *
  13. * This program is part of GeSHi.
  14. *
  15. * This program is free software; you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation; either version 2 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program; if not, write to the Free Software
  27. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  28. *
  29. * @package geshi
  30. * @subpackage core
  31. * @author Nigel McNie <nigel@geshi.org>
  32. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL
  33. * @copyright (C) 2004 - 2006 Nigel McNie
  34. * @version $Id: class.geshistyler.php 2139 2009-07-22 21:40:26Z benbe $
  35. *
  36. */
  37. /**
  38. * The GeSHiStyler class
  39. *
  40. * @package geshi
  41. * @subpackage core
  42. * @author Nigel McNie <nigel@geshi.org>
  43. * @since 1.1.0
  44. * @version $Revision: 2139 $
  45. */
  46. class GeSHiStyler
  47. {
  48. // {{{ properties
  49. /**
  50. * @var string
  51. */
  52. private $charset;
  53. /**
  54. * Array of themes to attempt to use for highlighting, in
  55. * preference order
  56. *
  57. * @var array
  58. */
  59. private $themes = array('default');
  60. /**
  61. * @var string
  62. * Note: only set once language name is determined to be valid
  63. */
  64. private $language = '';
  65. /**
  66. * @var boolean
  67. */
  68. private $reloadThemeData = true;
  69. /**#@+
  70. * @access private
  71. */
  72. /**
  73. * @var array
  74. */
  75. private $_styleData = array();
  76. /**
  77. * @var array
  78. */
  79. private $_wildcardStyleData = array();
  80. /**
  81. * @var array
  82. */
  83. private $_contextCacheData = array();
  84. /**
  85. * @var GeSHiCodeParser
  86. */
  87. private $_codeParser = null;
  88. /**
  89. * @var GeSHiRenderer
  90. */
  91. private $_renderer = null;
  92. /**
  93. * @var string
  94. */
  95. private $_parsedCode = '';
  96. /**#@-*/
  97. // }}}
  98. // {{{ setLanguage()
  99. /**
  100. * Sets the language of this styler.
  101. */
  102. function setLanguage ($language_name)
  103. {
  104. $this->language = $language_name;
  105. }
  106. // }}}
  107. // {{{ setStyle()
  108. /**
  109. * Sets the style of a specific context. Language name is prefixed,
  110. * to make theme files shorter and easier
  111. */
  112. function setStyle ($context_name, $style, $start_name = 'start', $end_name = 'end')
  113. {
  114. geshi_dbg('GeSHiStyler::setStyle(' . $context_name . ', ' . $style . ')');
  115. if ($context_name) {
  116. $context_name = "/$context_name";
  117. }
  118. $this->setRawStyle($this->language . $context_name, $style);
  119. }
  120. // }}}
  121. // {{{ setRawStyle()
  122. /**
  123. * Sets styles with explicit control over style name
  124. */
  125. function setRawStyle ($context_name, $style)
  126. {
  127. $style = GeSHiStyler::_parseCSS($style);
  128. if (substr($context_name, -1) != '*') {
  129. $this->_styleData[$context_name] = $style;
  130. } else {
  131. $this->_wildcardStyleData[substr($context_name, 0, -2)] = $style;
  132. }
  133. }
  134. // }}}
  135. // {{{ removeStyleData()
  136. /**
  137. * Removes any style data for the related context, including
  138. * data for the start and end of the context
  139. */
  140. function removeStyleData ($context_name, $context_start_name = 'start', $context_end_name = 'end')
  141. {
  142. unset($this->_styleData[$context_name]);
  143. unset($this->_styleData["$context_name/$context_start_name"]);
  144. unset($this->_styleData["$context_name/$context_end_name"]);
  145. geshi_dbg(' removed style data for ' . $context_name);
  146. }
  147. // }}}
  148. // {{{ getStyle()
  149. function getStyle ($context_name)
  150. {
  151. if (isset($this->_styleData[$context_name])) {
  152. return $this->_styleData[$context_name];
  153. }
  154. // If style for starter/ender requested and we got here, use the default
  155. if ('/end' == substr($context_name, -4)) {
  156. $this->_styleData[$context_name] = $this->getStyle(substr($context_name, 0, -4));
  157. return $this->_styleData[$context_name];
  158. }
  159. if ('/start' == substr($context_name, -6)) {
  160. $this->_styleData[$context_name] = $this->getStyle(substr($context_name, 0, -6));
  161. return $this->_styleData[$context_name];
  162. }
  163. // Check for a one-level wildcard match
  164. $wildcard_idx = substr($context_name, 0, strrpos($context_name, '/'));
  165. if (isset($this->_wildcardStyleData[$wildcard_idx])) {
  166. $this->_styleData[$context_name] = $this->_wildcardStyleData[$wildcard_idx];
  167. return $this->_wildcardStyleData[$wildcard_idx];
  168. }
  169. // Maybe a deeper match?
  170. foreach ($this->_wildcardStyleData as $context => $style) {
  171. if (substr($context_name, 0, strlen($context)) == $context) {
  172. $this->_styleData[$context_name] = $style;
  173. return $style;
  174. }
  175. }
  176. //@todo [blocking 1.1.5] Make the default style for otherwise unstyled elements configurable
  177. $this->_styleData[$context_name] = GeSHiStyler::_parseCSS('color:#000;');
  178. return $this->_styleData[$context_name];
  179. }
  180. // }}}
  181. // {{{ loadStyles()
  182. function loadStyles ($language = '', $load_theme = false)
  183. {
  184. if (!$language) {
  185. $language = $this->language;
  186. }
  187. geshi_dbg('GeSHiStyler::loadStyles(' . $language . ')');
  188. if ($this->reloadThemeData) {
  189. geshi_dbg(' Loading theme data');
  190. // Trash old data
  191. if ($load_theme) {
  192. geshi_dbg(' Old data trashed');
  193. $this->_styleData = array();
  194. }
  195. // Lie for a short while, to get extra style names to behave
  196. $tmp = $this->language;
  197. $this->language = $language;
  198. foreach ($this->themes as $theme) {
  199. $theme_file = GESHI_THEMES_ROOT . $theme . GESHI_DIR_SEP . $language . '.php';
  200. if (is_readable($theme_file)) {
  201. require $theme_file;
  202. break;
  203. }
  204. }
  205. if ($load_theme) {
  206. $this->reloadThemeData = false;
  207. }
  208. $this->language = $tmp;
  209. }
  210. }
  211. // }}}
  212. // {{{ resetParseData()
  213. /**
  214. * Sets up GeSHiStyler for assisting with parsing.
  215. * Makes sure that GeSHiStyler has a code parser and
  216. * renderer associated with it.
  217. */
  218. function resetParseData ()
  219. {
  220. // Set result to empty
  221. $this->_parsedCode = '';
  222. // If the language we are using does not have a code
  223. // parser associated with it, use the default one
  224. if (is_null($this->_codeParser)) {
  225. /** Get the GeSHiCodeParser class */
  226. require_once GESHI_CLASSES_ROOT . 'class.geshicodeparser.php';
  227. /** Get the default code parser class */
  228. require_once GESHI_CLASSES_ROOT . 'class.geshidefaultcodeparser.php';
  229. $this->_codeParser = new GeSHiDefaultCodeParser($this, $this->language);
  230. }
  231. // It the user did not explicitly set a renderer with GeSHi::accept(), then
  232. // use the default renderer (HTML)
  233. if (is_null($this->_renderer)) {
  234. /** Get the GeSHiRenderer class */
  235. require_once GESHI_CLASSES_ROOT . 'class.geshirenderer.php';
  236. /** Get the renderer class */
  237. require_once GESHI_RENDERERS_ROOT . 'class.geshirendererhtml.php';
  238. $this->_renderer = new GeSHiRendererHTML;
  239. }
  240. //Allow the code renderer to preprocess the code
  241. $this->_renderer->renderPreview();
  242. // Load theme data now
  243. $this->loadStyles('', true);
  244. }
  245. // }}}
  246. // {{{ setCodeParser()
  247. /**
  248. * Sets the code parser that will be used. This is used by language
  249. * files in the geshi/languages directory to set their code parser
  250. *
  251. * @param GeSHiCodeParser The code parser to use
  252. */
  253. function setCodeParser (GeSHiCodeParser $codeparser)
  254. {
  255. $this->_codeParser = $codeparser;
  256. }
  257. // }}}
  258. // {{{ setRenderer()
  259. /**
  260. * Sets the renderer that will be used.
  261. *
  262. * @param GeSHiRenderer $renderer The renderer to use
  263. */
  264. function setRenderer (GeSHiRenderer $renderer)
  265. {
  266. $this->_renderer = $renderer;
  267. }
  268. // }}}
  269. // {{{ useThemes()
  270. /**
  271. * Sets the themes to use
  272. */
  273. function useThemes (array $themes)
  274. {
  275. $this->themes = array_merge($themes, $this->themes);
  276. $this->themes = array_unique($this->themes);
  277. // Could check here: get first element of orig. $this->themes, if different now then reload
  278. $this->reloadThemeData = true;
  279. }
  280. // }}}
  281. // {{{ addParseData()
  282. /**
  283. * Recieves parse data from the context tree. Sends the
  284. * data on to the code parser, then to the renderer for
  285. * building the result string
  286. */
  287. function addParseData ($code, $context_name, array $data = array(), $complex = false)
  288. {
  289. // @todo [blocking 1.1.5] test this, esp. not passing back anything and passing back multiple
  290. // can use PHP code parser for this
  291. // @todo [blocking 1.1.9] since we are only looking for whitespace at start and end this can
  292. // be optimised
  293. if (GESHI_COMPLEX_NO == $complex) {
  294. $this->_addToParsedCode(array($code, $context_name, $data));
  295. } elseif (GESHI_COMPLEX_PASSALL == $complex) {
  296. // Parse all at once
  297. $this->_addToParsedCode($this->_codeParser->parseToken($code, $context_name, $data));
  298. } elseif (GESHI_COMPLEX_TOKENISE == $complex) {
  299. $matches = array();
  300. preg_match_all('/^(\s*)(.*?)(\s*)$/si', $code, $matches);
  301. //echo 'START<br />';
  302. //print_r($matches);
  303. if ($matches[1][0]) {
  304. $this->_addToParsedCode($this->_codeParser->parseToken($matches[1][0],
  305. $context_name, $data));
  306. }
  307. if ('' != $matches[2][0]) {
  308. while ('' != $matches[2][0]) {
  309. $pos = geshi_get_position($matches[2][0], 'REGEX#(\s+)#');
  310. if (false !== $pos['pos']) {
  311. // Parse the token up to the whitespace
  312. //echo 'code: |' . substr($matches[2][0], 0, $pos['pos']) . '|<br />';
  313. $this->_addToParsedCode(
  314. $this->_codeParser->parseToken(substr($matches[2][0], 0, $pos['pos']),
  315. $context_name, $data)
  316. );
  317. // Parse the whitespace
  318. //echo 'ws: |' . substr($matches[2][0], $pos['pos'], $pos['len']) . '|<br />';
  319. $this->_addToParsedCode(
  320. $this->_codeParser->parseToken(substr($matches[2][0], $pos['pos'], $pos['len']),
  321. $context_name, $data)
  322. );
  323. // Trim what we just parsed
  324. $matches[2][0] = substr($matches[2][0], $pos['pos'] + $pos['len']);
  325. } else {
  326. // No more whitespace
  327. //echo 'no more whitespace: |' . $matches[2][0] . '<br />';
  328. $this->_addToParsedCode($this->_codeParser->parseToken($matches[2][0],
  329. $context_name, $data));
  330. break;
  331. }
  332. }
  333. }
  334. if ($matches[3][0]) {
  335. $this->_addToParsedCode($this->_codeParser->parseToken($matches[3][0],
  336. $context_name, $data));
  337. }
  338. } // else wtf???
  339. }
  340. // }}}
  341. // {{{ _addToParsedCode()
  342. /**
  343. * Adds data from the renderer to the parsed code
  344. */
  345. function _addToParsedCode (array $data)
  346. {
  347. //todo: Rework parser so this function always gets an array of tokens
  348. if ($data) {
  349. if (!is_array($data[0])) {
  350. $this->_parsedCode .= $this->_renderer->parseToken($data[0], $data[1], $data[2]);
  351. } else {
  352. foreach ($data as $dat) {
  353. $this->_parsedCode .= $this->_renderer->parseToken($dat[0], $dat[1], $dat[2]);
  354. }
  355. }
  356. }
  357. }
  358. // }}}
  359. // {{{ addParseDataStart()
  360. function addParseDataStart ($code, $context_name, $start_name = 'start', $complex = false)
  361. {
  362. $this->addParseData($code, "$context_name/$start_name", array(), $complex);
  363. }
  364. // }}}
  365. // {{{ addParseDataEnd()
  366. function addParseDataEnd ($code, $context_name, $end_name = 'end', $complex = false)
  367. {
  368. $this->addParseData($code, "$context_name/$end_name", array(), $complex);
  369. }
  370. // }}}
  371. // {{{ getParsedCode()
  372. function getParsedCode ()
  373. {
  374. // Flush the last of the code
  375. $this->_addToParsedCode($this->_codeParser->flush());
  376. //Allow the code renderer to postprocess the code
  377. $this->_renderer->renderPostview();
  378. $result = $this->_renderer->getHeader() . $this->_parsedCode . $this->_renderer->getFooter();
  379. $this->_parsedCode = '';
  380. return $result;
  381. }
  382. // }}}
  383. // {{{ getRendererOption()
  384. /**
  385. * Retrieves renderer specific data controlling
  386. * how the renderer outputs source
  387. *
  388. * @abstract
  389. * @param string The name of the Renderer specific option to retrieve
  390. * @param mixed The default value for this property
  391. */
  392. function getRendererOption ($name, $default) {}
  393. // }}}
  394. // {{{ setRendererOption()
  395. /**
  396. * Sets up renderer specific data controlling
  397. * how the renderer works
  398. *
  399. * @abstract
  400. * @param string The name of the Renderer specific option to modify
  401. * @param mixed The new value for the renderer specific value of the option to modify
  402. */
  403. function setRendererOption ($name, $value) {}
  404. // }}}
  405. // {{{ _parseCSS
  406. /**
  407. * Parse a CSS string into our internal data format
  408. *
  409. * @param mixed The input format information to convert
  410. * @return array
  411. */
  412. function _parseCSS ($style) {
  413. $result = array(
  414. "font" => array(
  415. "color" => array(
  416. "R" => 0.0, //Red channel
  417. "G" => 0.0, //Green channel
  418. "B" => 0.0, //Blue channel
  419. "A" => 0.0 //Transparency (optional)
  420. ),
  421. "style" => array(
  422. "bold" => false, //Bold font
  423. "italic" => false, //Italic / Emphasized font
  424. "underline" => 0, //Boolean interpretation allowed
  425. "strike" => false //Strike out text, optional
  426. ),
  427. "special" => array( //Optional, additional font settings
  428. "rotate" => 0
  429. )
  430. ),
  431. "border" => array(
  432. "l" => false, //The left border, see comment below
  433. "r" => false, //The right border, see comment below
  434. "t" => false, //The top border, see comment below
  435. "b" => false //The bottom border, see comment below
  436. /*
  437. * If a border is present it contains a color attribute (as for font)
  438. * and a style attribute telling the kind of line to use.
  439. * Additional attributes like padding and margins can be supplied.
  440. */
  441. ),
  442. "back" => array(
  443. "color" => false, //color of the background, transparent if missing
  444. )
  445. );
  446. if(is_array($style)) {
  447. return GeSHiStyler::array_merge_recursive_unique($style, $result);
  448. }
  449. //No array, so we have to parse CSS ...
  450. //First of the color:
  451. if(preg_match('/\b(?<!-)color\s*:\s*(#[\da-f]{3}(?:[\da-f]{3})?|\w+)/', $style, $match)) {
  452. //We got a color, let's analyze it:
  453. $result['font']['color'] = GeSHiStyler::_parseColor($match[1]);
  454. }
  455. if(preg_match('/\b(?<!-)font-style\s*:\s*(\w+)/', $style, $match)) {
  456. //We got a color, let's analyze it:
  457. $result['font']['style']['italic'] = 'italic' == strtolower($match[1]);
  458. }
  459. if(preg_match('/\b(?<!-)font-weight\s*:\s*(\w+)/', $style, $match)) {
  460. //We got a color, let's analyze it:
  461. $result['font']['style']['bold'] = 'bold' == strtolower($match[1]);
  462. }
  463. if(preg_match('/\b(?<!-)text-decoration\s*:\s*(\w+)/', $style, $match)) {
  464. //We got a color, let's analyze it:
  465. $result['font']['style']['underline'] = 'underline' == strtolower($match[1]);
  466. }
  467. return $result;
  468. }
  469. // }}}
  470. private static function array_merge_recursive_unique()
  471. {
  472. $arrays = func_get_args();
  473. $remains = $arrays;
  474. // We walk through each arrays and put value in the results (without
  475. // considering previous value).
  476. $result = array();
  477. // loop available array
  478. foreach($arrays as $array) {
  479. // The first remaining array is $array. We are processing it. So
  480. // we remove it from remaing arrays.
  481. array_shift($remains);
  482. // We don't care non array param, like array_merge since PHP 5.0.
  483. if(is_array($array)) {
  484. // Loop values
  485. foreach($array as $key => $value) {
  486. if(is_array($value)) {
  487. // we gather all remaining arrays that have such key available
  488. $args = array();
  489. foreach($remains as $remain) {
  490. if(array_key_exists($key, $remain)) {
  491. array_push($args, $remain[$key]);
  492. }
  493. }
  494. if(count($args) > 2) {
  495. // put the recursion
  496. $result[$key] = call_user_func_array(array(__CLASS__, __FUNCTION__), $args);
  497. } else {
  498. foreach($value as $vkey => $vval) {
  499. $result[$key][$vkey] = $vval;
  500. }
  501. }
  502. } else {
  503. // simply put the value
  504. $result[$key] = $value;
  505. }
  506. }
  507. }
  508. }
  509. return $result;
  510. }
  511. /**
  512. * GeSHiStyler::_parseColor()
  513. *
  514. * @param string $color
  515. * @return
  516. */
  517. private static function _parseColor($color)
  518. {
  519. $result = array("R" => 0.0, "G" => 0.0, "B" => 0.0, "A" => 0.0);
  520. if('' == $color) {
  521. return $result;
  522. }
  523. if('#' != $color[0]) {
  524. static $htmlColors = array(
  525. "black" => array("R"=>0.0, "G"=>0.0, "B"=>0.0, "A"=>0.0),
  526. "white" => array("R"=>1.0, "G"=>1.0, "B"=>1.0, "A"=>0.0),
  527. "red" => array("R"=>1.0, "G"=>0.0, "B"=>0.0, "A"=>0.0),
  528. "yellow" => array("R"=>1.0, "G"=>1.0, "B"=>0.0, "A"=>0.0),
  529. "lime" => array("R"=>0.0, "G"=>1.0, "B"=>0.0, "A"=>0.0),
  530. "cyan" => array("R"=>0.0, "G"=>1.0, "B"=>1.0, "A"=>0.0),
  531. "blue" => array("R"=>0.0, "G"=>0.0, "B"=>1.0, "A"=>0.0),
  532. "magenta" => array("R"=>1.0, "G"=>0.0, "B"=>1.0, "A"=>0.0),
  533. "darkgrey" => array("R"=>0.4, "G"=>0.4, "B"=>0.4, "A"=>0.0),
  534. "lightgrey" => array("R"=>0.8, "G"=>0.8, "B"=>0.8, "A"=>0.0),
  535. );
  536. if(isset($htmlColors[$color])) {
  537. return $htmlColors[$color];
  538. } else {
  539. return $result;
  540. }
  541. }
  542. if(4 == strlen($color)) {
  543. $result['R'] = intval($color[1], 16) / 15.0;
  544. $result['G'] = intval($color[2], 16) / 15.0;
  545. $result['B'] = intval($color[3], 16) / 15.0;
  546. } else {
  547. $result['R'] = intval($color[1].$color[2], 16) / 255.0;
  548. $result['G'] = intval($color[3].$color[4], 16) / 255.0;
  549. $result['B'] = intval($color[5].$color[6], 16) / 255.0;
  550. }
  551. return $result;
  552. }
  553. }
  554. ?>