2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 * Highlighter base class
8 * LICENSE: This source file is subject to version 3.0 of the PHP license
9 * that is available through the world-wide-web at the following URI:
10 * http://www.php.net/license/3_0.txt. If you did not receive a copy of
11 * the PHP License and are unable to obtain it through the web, please
12 * send a note to license@php.net so we can mail you a copy immediately.
15 * @package Text_Highlighter
16 * @author Andrey Demenev <demenev@gmail.com>
17 * @copyright 2004-2006 Andrey Demenev
18 * @license http://www.php.net/license/3_0.txt PHP License
20 * @link http://pear.php.net/package/Text_Highlighter
23 // require_once 'PEAR.php';
27 // BC trick : define constants related to default
29 if (!defined('HL_NUMBERS_LI')) {
31 * Constant for use with $options['numbers']
32 * @see Text_Highlighter_Renderer_Html::_init()
37 define ('HL_NUMBERS_LI' , 1);
39 * Use 2-column table with line numbers in left column and code in right column.
40 * Forces $options['tag'] = HL_TAG_PRE
42 define ('HL_NUMBERS_TABLE' , 2);
49 * for our purpose, it is infinity
51 define ('HL_INFINITY', 1000000000);
56 * Text highlighter base class
58 * @author Andrey Demenev <demenev@gmail.com>
59 * @copyright 2004-2006 Andrey Demenev
60 * @license http://www.php.net/license/3_0.txt PHP License
61 * @version Release: @package_version@
62 * @link http://pear.php.net/package/Text_Highlighter
65 // {{{ Text_Highlighter
68 * Text highlighter base class
70 * This class implements all functions necessary for highlighting,
71 * but it does not contain highlighting rules. Actual highlighting is
72 * done using a descendent of this class.
74 * One is not supposed to manually create descendent classes.
75 * Instead, describe highlighting rules in XML format and
76 * use {@link Text_Highlighter_Generator} to create descendent class.
77 * Alternatively, an instance of a descendent class can be created
80 * Use {@link Text_Highlighter::factory()} to create an
81 * object for particular language highlighter
85 *require_once 'Text/Highlighter.php';
86 *$hlSQL = Text_Highlighter::factory('SQL',array('numbers'=>true));
87 *echo $hlSQL->highlight('SELECT * FROM table a WHERE id = 12');
90 * @author Andrey Demenev <demenev@gmail.com>
91 * @package Text_Highlighter
95 class Text_Highlighter
100 * Syntax highlighting rules.
101 * Auto-generated classes set this var
118 * Options. Keeped for BC
123 var $_options = array();
131 var $_conditions = array();
139 var $_disabled = array();
153 * Called by subclssses' constructors to enable/disable
154 * optional highlighter rules
156 * @param array $defines Conditional defines
160 function _checkDefines()
162 if (isset($this->_options['defines'])) {
163 $defines = $this->_options['defines'];
167 foreach ($this->_conditions as $name => $actions) {
168 foreach($actions as $action) {
169 $present = in_array($name, $defines);
171 $present = !$present;
174 unset($this->_disabled[$action[0]]);
176 $this->_disabled[$action[0]] = true;
186 * Create a new Highlighter object for specified language
188 * @param string $lang language, for example "SQL"
189 * @param array $options Rendering options. This
190 * parameter is only keeped for BC reasons, use
191 * {@link Text_Highlighter::setRenderer()} instead
193 * @return mixed a newly created Highlighter object, or
194 * a PEAR error object on error
199 function &factory($lang, $options = array())
201 $lang = strtoupper($lang);
202 @include_once 'Text/Highlighter/' . $lang . '.php';
204 $classname = 'Text_Highlighter_' . $lang;
206 if (!class_exists($classname)) {
207 return PEAR::raiseError('Highlighter for ' . $lang . ' not found');
210 $obj = new $classname($options);
219 * Set renderer object
221 * @param object $renderer Text_Highlighter_Renderer
225 function setRenderer(&$renderer)
227 $this->_renderer = $renderer;
233 * Helper function to find matching brackets
237 function _matchingBrackets($str)
239 return strtr($str, '()<>[]{}', ')(><][}{');
247 if (!empty($this->_tokenStack)) {
248 return array_pop($this->_tokenStack);
250 if ($this->_pos >= $this->_len) {
254 if ($this->_state != -1 && preg_match($this->_endpattern, $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos)) {
256 $endmatch = $m[0][0];
260 preg_match ($this->_regs[$this->_state], $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos);
264 foreach ($this->_counts[$this->_state] as $i=>$count) {
265 if (!isset($m[$n])) {
268 if ($m[$n][1]>-1 && ($endpos == -1 || $m[$n][1] < $endpos)) {
269 if ($this->_states[$this->_state][$i] != -1) {
270 $this->_tokenStack[] = array($this->_delim[$this->_state][$i], $m[$n][0]);
272 $inner = $this->_inner[$this->_state][$i];
273 if (isset($this->_parts[$this->_state][$i])) {
275 $partpos = $m[$n][1];
276 for ($j=1; $j<=$count; $j++) {
277 if ($m[$j+$n][1] < 0) {
280 if (isset($this->_parts[$this->_state][$i][$j])) {
281 if ($m[$j+$n][1] > $partpos) {
282 array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$j+$n][1]-$partpos)));
284 array_unshift($parts, array($this->_parts[$this->_state][$i][$j], $m[$j+$n][0]));
286 $partpos = $m[$j+$n][1] + strlen($m[$j+$n][0]);
288 if ($partpos < $m[$n][1] + strlen($m[$n][0])) {
289 array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$n][1] - $partpos + strlen($m[$n][0]))));
291 $this->_tokenStack = array_merge($this->_tokenStack, $parts);
293 foreach ($this->_keywords[$this->_state][$i] as $g => $re) {
294 if (isset($this->_disabled[$g])) {
297 if (preg_match($re, $m[$n][0])) {
298 $inner = $this->_kwmap[$g];
302 $this->_tokenStack[] = array($inner, $m[$n][0]);
305 if ($m[$n][1] > $this->_pos) {
306 $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $m[$n][1]-$this->_pos));
308 $this->_pos = $m[$n][1] + strlen($m[$n][0]);
309 if ($this->_states[$this->_state][$i] != -1) {
310 $this->_stack[] = array($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern);
311 $this->_lastinner = $this->_inner[$this->_state][$i];
312 $this->_lastdelim = $this->_delim[$this->_state][$i];
314 $this->_state = $this->_states[$this->_state][$i];
315 $this->_endpattern = $this->_end[$this->_state];
316 if ($this->_subst[$l][$i]) {
317 for ($k=0; $k<=$this->_counts[$l][$i]; $k++) {
318 if (!isset($m[$i+$k])) {
321 $quoted = preg_quote($m[$n+$k][0], '/');
322 $this->_endpattern = str_replace('%'.$k.'%', $quoted, $this->_endpattern);
323 $this->_endpattern = str_replace('%b'.$k.'%', $this->_matchingBrackets($quoted), $this->_endpattern);
327 return array_pop($this->_tokenStack);
333 $this->_tokenStack[] = array($this->_lastdelim, $endmatch);
334 if ($endpos > $this->_pos) {
335 $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $endpos-$this->_pos));
337 list($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern) = array_pop($this->_stack);
338 $this->_pos = $endpos + strlen($endmatch);
339 return array_pop($this->_tokenStack);
342 $this->_pos = HL_INFINITY;
343 return array($this->_lastinner, substr($this->_str, $p));
354 * @param string $str Code to highlight
356 * @return string Highlighted text
360 function highlight($str)
362 if (!($this->_renderer)) {
363 include_once('Text/Highlighter/Renderer/Html.php');
364 $this->_renderer = new Text_Highlighter_Renderer_Html($this->_options);
368 $this->_stack = array();
369 $this->_tokenStack = array();
370 $this->_lastinner = $this->_defClass;
371 $this->_lastdelim = $this->_defClass;
372 $this->_endpattern = '';
373 $this->_renderer->reset();
374 $this->_renderer->setCurrentLanguage($this->_language);
375 $this->_str = $this->_renderer->preprocess($str);
376 $this->_len = strlen($this->_str);
377 while ($token = $this->_getToken()) {
378 $this->_renderer->acceptToken($token[0], $token[1]);
380 $this->_renderer->finalize();
381 return $this->_renderer->getOutput();
394 * c-hanging-comment-ender-p: nil