Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
/**
4
 * Highlighter base class
5
 *
6
 * PHP versions 4 and 5
7
 *
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.
13
 *
14
 * @category   Text
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
19
 * @version    CVS: $Id: Highlighter.php,v 1.1 2007/06/03 02:35:28 ssttoo Exp $
20
 * @link       http://pear.php.net/package/Text_Highlighter
21
 */
22
 
23
// {{{ BC constants
24
 
25
// BC trick : define constants related to default
26
// renderer if needed
27
if (!defined('HL_NUMBERS_LI')) {
28
    /**#@+
29
     * Constant for use with $options['numbers']
30
     * @see Text_Highlighter_Renderer_Html::_init()
31
     */
32
    /**
33
     * use numbered list
34
     */
35
    define ('HL_NUMBERS_LI'    ,    1);
36
    /**
37
     * Use 2-column table with line numbers in left column and code in  right column.
38
     * Forces $options['tag'] = HL_TAG_PRE
39
     */
40
    define ('HL_NUMBERS_TABLE'    , 2);
41
    /**#@-*/
42
}
43
 
44
// }}}
45
// {{{ constants
46
/**
47
 * for our purpose, it is infinity
48
 */
49
define ('HL_INFINITY',      1000000000);
50
 
51
// }}}
52
 
53
/**
54
 * Text highlighter base class
55
 *
56
 * @author     Andrey Demenev <demenev@gmail.com>
57
 * @copyright  2004-2006 Andrey Demenev
58
 * @license    http://www.php.net/license/3_0.txt  PHP License
59
 * @version    Release: 0.7.0
60
 * @link       http://pear.php.net/package/Text_Highlighter
61
 */
62
 
63
// {{{ Text_Highlighter
64
 
65
/**
66
 * Text highlighter base class
67
 *
68
 * This class implements all functions necessary for highlighting,
69
 * but it does not contain highlighting rules. Actual highlighting is
70
 * done using a descendent of this class.
71
 *
72
 * One is not supposed to manually create descendent classes.
73
 * Instead, describe highlighting  rules in XML format and
74
 * use {@link Text_Highlighter_Generator} to create descendent class.
75
 * Alternatively, an instance of a descendent class can be created
76
 * directly.
77
 *
78
 * Use {@link Text_Highlighter::factory()} to create an
79
 * object for particular language highlighter
80
 *
81
 * Usage example
82
 * <code>
83
 *require_once 'Text/Highlighter.php';
84
 *$hlSQL =& Text_Highlighter::factory('SQL',array('numbers'=>true));
85
 *echo $hlSQL->highlight('SELECT * FROM table a WHERE id = 12');
86
 * </code>
87
 *
88
 * @author Andrey Demenev <demenev@gmail.com>
89
 * @package Text_Highlighter
90
 * @access public
91
 */
92
 
93
class Text_Highlighter
94
{
95
    // {{{ members
96
 
97
    /**
98
     * Syntax highlighting rules.
99
     * Auto-generated classes set this var
100
     *
101
     * @access protected
102
     * @see _init
103
     * @var array
104
     */
105
    var $_syntax;
106
 
107
    /**
108
     * Renderer object.
109
     *
110
     * @access private
111
     * @var array
112
     */
113
    var $_renderer;
114
 
115
    /**
116
     * Options. Keeped for BC
117
     *
118
     * @access protected
119
     * @var array
120
     */
121
    var $_options = array();
122
 
123
    /**
124
     * Conditionds
125
     *
126
     * @access protected
127
     * @var array
128
     */
129
    var $_conditions = array();
130
 
131
    /**
132
     * Disabled keywords
133
     *
134
     * @access protected
135
     * @var array
136
     */
137
    var $_disabled = array();
138
 
139
    /**
140
     * Language
141
     *
142
     * @access protected
143
     * @var string
144
     */
145
    var $_language = '';
146
 
147
    // }}}
148
    // {{{ _checkDefines
149
 
150
    /**
151
     * Called by subclssses' constructors to enable/disable
152
     * optional highlighter rules
153
     *
154
     * @param array $defines  Conditional defines
155
     *
156
     * @access protected
157
     */
158
    function _checkDefines()
159
    {
160
        if (isset($this->_options['defines'])) {
161
            $defines = $this->_options['defines'];
162
        } else {
163
            $defines = array();
164
        }
165
        foreach ($this->_conditions as $name => $actions) {
166
            foreach($actions as $action) {
167
                $present = in_array($name, $defines);
168
                if (!$action[1]) {
169
                    $present = !$present;
170
                }
171
                if ($present) {
172
                    unset($this->_disabled[$action[0]]);
173
                } else {
174
                    $this->_disabled[$action[0]] = true;
175
                }
176
            }
177
        }
178
    }
179
 
180
    // }}}
181
    // {{{ factory
182
 
183
    /**
184
     * Create a new Highlighter object for specified language
185
     *
186
     * @param string $lang    language, for example "SQL"
187
     * @param array  $options Rendering options. This
188
     * parameter is only keeped for BC reasons, use
189
     * {@link Text_Highlighter::setRenderer()} instead
190
     *
191
     * @return mixed a newly created Highlighter object, or
192
     * a PEAR error object on error
193
     *
194
     * @static
195
     * @access public
196
     */
197
    public static function factory($lang, $options = array())
198
    {
199
        $lang = strtoupper($lang);
200
        $langFile = dirname(__FILE__)."/Highlighter/$lang.php";
201
        if (is_file($langFile))
202
        	include_once $langFile;
203
        else
204
        	return false;
205
 
206
        $classname = 'Text_Highlighter_' . $lang;
207
 
208
        if (!class_exists($classname))
209
        	return false;
210
 
211
		return new $classname($options);
212
    }
213
 
214
    // }}}
215
    // {{{ setRenderer
216
 
217
    /**
218
     * Set renderer object
219
     *
220
     * @param object $renderer  Text_Highlighter_Renderer
221
     *
222
     * @access public
223
     */
224
    function setRenderer($renderer)
225
    {
226
        $this->_renderer = $renderer;
227
    }
228
 
229
    // }}}
230
 
231
    /**
232
     * Helper function to find matching brackets
233
     *
234
     * @access private
235
     */
236
    function _matchingBrackets($str)
237
    {
238
        return strtr($str, '()<>[]{}', ')(><][}{');
239
    }
240
 
241
 
242
 
243
 
244
    function _getToken()
245
    {
246
        if (!empty($this->_tokenStack)) {
247
            return array_pop($this->_tokenStack);
248
        }
249
        if ($this->_pos >= $this->_len) {
250
            return NULL;
251
        }
252
 
253
        if ($this->_state != -1 && preg_match($this->_endpattern, $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos)) {
254
            $endpos = $m[0][1];
255
            $endmatch = $m[0][0];
256
        } else {
257
            $endpos = -1;
258
        }
259
        preg_match ($this->_regs[$this->_state], $this->_str, $m, PREG_OFFSET_CAPTURE, $this->_pos);
260
        $n = 1;
261
 
262
 
263
         foreach ($this->_counts[$this->_state] as $i=>$count) {
264
            if (!isset($m[$n])) {
265
                break;
266
            }
267
            if ($m[$n][1]>-1 && ($endpos == -1 || $m[$n][1] < $endpos)) {
268
                if ($this->_states[$this->_state][$i] != -1) {
269
                    $this->_tokenStack[] = array($this->_delim[$this->_state][$i], $m[$n][0]);
270
                } else {
271
                    $inner = $this->_inner[$this->_state][$i];
272
                    if (isset($this->_parts[$this->_state][$i])) {
273
                        $parts = array();
274
                        $partpos = $m[$n][1];
275
                        for ($j=1; $j<=$count; $j++) {
276
                            if ($m[$j+$n][1] < 0) {
277
                                continue;
278
                            }
279
                            if (isset($this->_parts[$this->_state][$i][$j])) {
280
                                if ($m[$j+$n][1] > $partpos) {
281
                                    array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$j+$n][1]-$partpos)));
282
                                }
283
                                array_unshift($parts, array($this->_parts[$this->_state][$i][$j], $m[$j+$n][0]));
284
                            }
285
                            $partpos = $m[$j+$n][1] + strlen($m[$j+$n][0]);
286
                        }
287
                        if ($partpos < $m[$n][1] + strlen($m[$n][0])) {
288
                            array_unshift($parts, array($inner, substr($this->_str, $partpos, $m[$n][1] - $partpos + strlen($m[$n][0]))));
289
                        }
290
                        $this->_tokenStack = array_merge($this->_tokenStack, $parts);
291
                    } else {
292
                        foreach ($this->_keywords[$this->_state][$i] as $g => $re) {
293
                            if (isset($this->_disabled[$g])) {
294
                                continue;
295
                            }
296
                            if (preg_match($re, $m[$n][0])) {
297
                                $inner = $this->_kwmap[$g];
298
                                break;
299
                            }
300
                        }
301
                        $this->_tokenStack[] = array($inner, $m[$n][0]);
302
                    }
303
                }
304
                if ($m[$n][1] > $this->_pos) {
305
                    $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $m[$n][1]-$this->_pos));
306
                }
307
                $this->_pos = $m[$n][1] + strlen($m[$n][0]);
308
                if ($this->_states[$this->_state][$i] != -1) {
309
                    $this->_stack[] = array($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern);
310
                    $this->_lastinner = $this->_inner[$this->_state][$i];
311
                    $this->_lastdelim = $this->_delim[$this->_state][$i];
312
                    $l = $this->_state;
313
                    $this->_state = $this->_states[$this->_state][$i];
314
                    $this->_endpattern = $this->_end[$this->_state];
315
                    if ($this->_subst[$l][$i]) {
316
                        for ($k=0; $k<=$this->_counts[$l][$i]; $k++) {
317
                            if (!isset($m[$i+$k])) {
318
                                break;
319
                            }
320
                            $quoted = preg_quote($m[$n+$k][0], '/');
321
                            $this->_endpattern = str_replace('%'.$k.'%', $quoted, $this->_endpattern);
322
                            $this->_endpattern = str_replace('%b'.$k.'%', $this->_matchingBrackets($quoted), $this->_endpattern);
323
                        }
324
                    }
325
                }
326
                return array_pop($this->_tokenStack);
327
            }
328
            $n += $count + 1;
329
        }
330
 
331
        if ($endpos > -1) {
332
            $this->_tokenStack[] = array($this->_lastdelim, $endmatch);
333
            if ($endpos > $this->_pos) {
334
                $this->_tokenStack[] = array($this->_lastinner, substr($this->_str, $this->_pos, $endpos-$this->_pos));
335
            }
336
            list($this->_state, $this->_lastdelim, $this->_lastinner, $this->_endpattern) = array_pop($this->_stack);
337
            $this->_pos = $endpos + strlen($endmatch);
338
            return array_pop($this->_tokenStack);
339
        }
340
        $p = $this->_pos;
341
        $this->_pos = HL_INFINITY;
342
        return array($this->_lastinner, substr($this->_str, $p));
343
    }
344
 
345
 
346
 
347
 
348
    // {{{ highlight
349
 
350
    /**
351
     * Highlights code
352
     *
353
     * @param  string $str      Code to highlight
354
     * @access public
355
     * @return string Highlighted text
356
     *
357
     */
358
 
359
    function highlight($str)
360
    {
361
        if (!($this->_renderer)) {
362
            include_once('Text/Highlighter/Renderer/Html.php');
363
            $this->_renderer = new Text_Highlighter_Renderer_Html($this->_options);
364
        }
365
        $this->_state = -1;
366
        $this->_pos = 0;
367
        $this->_stack = array();
368
        $this->_tokenStack = array();
369
        $this->_lastinner = $this->_defClass;
370
        $this->_lastdelim = $this->_defClass;
371
        $this->_endpattern = '';
372
        $this->_renderer->reset();
373
        $this->_renderer->setCurrentLanguage($this->_language);
374
        $this->_str = $this->_renderer->preprocess($str);
375
        $this->_len = strlen($this->_str);
376
        while ($token = $this->_getToken()) {
377
            $this->_renderer->acceptToken($token[0], $token[1]);
378
        }
379
        $this->_renderer->finalize();
380
        return $this->_renderer->getOutput();
381
    }
382
 
383
    // }}}
384
 
385
}
386
 
387
// }}}
388
 
389
/*
390
 * Local variables:
391
 * tab-width: 4
392
 * c-basic-offset: 4
393
 * c-hanging-comment-ender-p: nil
394
 * End:
395
 */
396
 
397
?>