Documentation is available at geshi.php
1 <?php
2 /**
3 * GeSHi - Generic Syntax Highlighter
4 *
5 * The GeSHi class for Generic Syntax Highlighting. Please refer to the documentation
6 * at http://qbnz.com/highlighter/documentation.php for more information about how to
7 * use this class.
8 *
9 * For changes, release notes, TODOs etc, see the relevant files in the docs/ directory
10 *
11 * This file is part of GeSHi.
12 *
13 * GeSHi is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * GeSHi is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with GeSHi; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 *
27 * @package core
28 * @author Nigel McNie <nigel@geshi.org>
29 * @copyright Copyright © 2004, 2005, Nigel McNie
30 * @license http://gnu.org/copyleft/gpl.html GNU GPL
31 * @version $Id: fsource_core__geshi.php.html,v 1.1 2006/01/23 09:27:28 wangcs Exp $
32 *
33 */
34
35 //
36 // GeSHi Constants
37 // You should use these constant names in your programs instead of
38 // their values - you never know when a value may change in a future
39 // version
40 //
41
42 /** The version of this GeSHi file */
42
43 define('GESHI_VERSION', '1.0.7.3');
44
45 /** For the future (though this may never be realised) */
45
46 define('GESHI_OUTPUT_HTML', 0);
47
48 /** Set the correct directory separator */
48
49 define('GESHI_DIR_SEPARATOR', ('WIN' != substr(PHP_OS, 0, 3)) ? '/' : '\\');
50
51 // Define the root directory for the GeSHi code tree
52 if (!defined('GESHI_ROOT')) {
53 /** The root directory for GeSHi */
53
54 define('GESHI_ROOT', dirname(__FILE__) . GESHI_DIR_SEPARATOR);
55 }
56 /** The language file directory for GeSHi
57 @access private */
58 define('GESHI_LANG_ROOT', GESHI_ROOT . 'geshi' . GESHI_DIR_SEPARATOR);
59
60
61 // Line numbers - use with enable_line_numbers()
62 /** Use no line numbers when building the result */
62
63 define('GESHI_NO_LINE_NUMBERS', 0);
64 /** Use normal line numbers when building the result */
64
65 define('GESHI_NORMAL_LINE_NUMBERS', 1);
66 /** Use fancy line numbers when building the result */
66
67 define('GESHI_FANCY_LINE_NUMBERS', 2);
68
69 // Container HTML type
70 /** Use nothing to surround the source */
70
71 define('GESHI_HEADER_NONE', 0);
72 /** Use a "div" to surround the source */
72
73 define('GESHI_HEADER_DIV', 1);
74 /** Use a "pre" to surround the source */
74
75 define('GESHI_HEADER_PRE', 2);
76
77 // Capatalisation constants
78 /** Lowercase keywords found */
78
79 define('GESHI_CAPS_NO_CHANGE', 0);
80 /** Uppercase keywords found */
80
81 define('GESHI_CAPS_UPPER', 1);
82 /** Leave keywords found as the case that they are */
82
83 define('GESHI_CAPS_LOWER', 2);
84
85 // Link style constants
86 /** Links in the source in the :link state */
86
87 define('GESHI_LINK', 0);
88 /** Links in the source in the :hover state */
88
89 define('GESHI_HOVER', 1);
90 /** Links in the source in the :active state */
90
91 define('GESHI_ACTIVE', 2);
92 /** Links in the source in the :visited state */
92
93 define('GESHI_VISITED', 3);
94
95 // Important string starter/finisher
96 // Note that if you change these, they should be as-is: i.e., don't
97 // write them as if they had been run through htmlentities()
98 /** The starter for important parts of the source */
98
99 define('GESHI_START_IMPORTANT', '<BEGIN GeSHi>');
100 /** The ender for important parts of the source */
100
101 define('GESHI_END_IMPORTANT', '<END GeSHi>');
102
103 /**#@+
104 * @access private
105 */
106 // When strict mode applies for a language
107 /** Strict mode never applies (this is the most common) */
107
108 define('GESHI_NEVER', 0);
109 /** Strict mode *might* apply, and can be enabled or
110 disabled by {@link GeSHi::enable_strict_mode()} */
111 define('GESHI_MAYBE', 1);
112 /** Strict mode always applies */
112
113 define('GESHI_ALWAYS', 2);
114
115 // Advanced regexp handling constants, used in language files
116 /** The key of the regex array defining what to search for */
116
117 define('GESHI_SEARCH', 0);
118 /** The key of the regex array defining what bracket group in a
119 matched search to use as a replacement */
120 define('GESHI_REPLACE', 1);
121 /** The key of the regex array defining any modifiers to the regular expression */
121
122 define('GESHI_MODIFIERS', 2);
123 /** The key of the regex array defining what bracket group in a
124 matched search to put before the replacement */
125 define('GESHI_BEFORE', 3);
126 /** The key of the regex array defining what bracket group in a
127 matched search to put after the replacement */
128 define('GESHI_AFTER', 4);
129
130 /** Used in language files to mark comments */
130
131 define('GESHI_COMMENTS', 0);
132
133 // Error detection - use these to analyse faults
134 /** No sourcecode to highlight was specified */
134
135 define('GESHI_ERROR_NO_INPUT', 1);
136 /** The language specified does not exist */
136
137 define('GESHI_ERROR_NO_SUCH_LANG', 2);
138 /** GeSHi could not open a file for reading (generally a language file) */
138
139 define('GESHI_ERROR_FILE_NOT_READABLE', 3);
140 /** The header type passed to {@link GeSHi::set_header_type()} was invalid */
140
141 define('GESHI_ERROR_INVALID_HEADER_TYPE', 4);
142 /** The line number type passed to {@link GeSHi::enable_line_numbers()} was invalid */
142
143 define('GESHI_ERROR_INVALID_LINE_NUMBER_TYPE', 5);
144 /**#@-*/
145
146
147 /**
148 * The GeSHi Class.
149 *
150 * Please refer to the documentation for GeSHi 1.0.X that is available
151 * at http://qbnz.com/highlighter/documentation.php for more information
152 * about how to use this class.
153 *
154 * @package core
155 * @author Nigel McNie <nigel@geshi.org>
156 * @copyright Copyright © 2004, 2005 Nigel McNie
157 */
158 class GeSHi
159 {
160 /**#@+
161 * @access private
162 */
163 /**
164 * The source code to highlight
165 * @var string
166 */
167 var $source = '';
168
169 /**
170 * The language to use when highlighting
171 * @var string
172 */
173 var $language = '';
174
175 /**
176 * The data for the language used
177 * @var array
178 */
179 var $language_data = array();
180
181 /**
182 * The path to the language files
183 * @var string
184 */
185 var $language_path = GESHI_LANG_ROOT;
186
187 /**
188 * The error message associated with an error
189 * @var string
190 * @todo check err reporting works
191 */
192 var $error = false;
193
194 /**
195 * Possible error messages
196 * @var array
197 */
198 var $error_messages = array(
199 GESHI_ERROR_NO_INPUT => 'No source code inputted',
200 GESHI_ERROR_NO_SUCH_LANG => 'GeSHi could not find the language {LANGUAGE} (using path {PATH})',
201 GESHI_ERROR_FILE_NOT_READABLE => 'The file specified for load_from_file was not readable',
202 GESHI_ERROR_INVALID_HEADER_TYPE => 'The header type specified is invalid',
203 GESHI_ERROR_INVALID_LINE_NUMBER_TYPE => 'The line number type specified is invalid'
204 );
205
206 /**
207 * Whether highlighting is strict or not
208 * @var boolean
209 */
210 var $strict_mode = false;
211
212 /**
213 * Whether to use CSS classes in output
214 * @var boolean
215 */
216 var $use_classes = false;
217
218 /**
219 * The type of header to use. Can be one of the following
220 * values:
221 *
222 * <ul>
223 * <li><b>GESHI_HEADER_PRE</b>: Source is outputted in
224 * a <pre> HTML element.</li>
225 * <li><b>GESHI_HEADER_DIV</b>: Source is outputted in
226 * a <div> HTML element.</li>
227 * </ul>
228 *
229 * @var int
230 */
231 var $header_type = GESHI_HEADER_PRE;
232
233 /**
234 * Array of permissions for which lexics should be highlighted
235 * @var array
236 */
237 var $lexic_permissions = array(
238 'KEYWORDS' => array(),
239 'COMMENTS' => array('MULTI' => true),
240 'REGEXPS' => array(),
241 'ESCAPE_CHAR' => true,
242 'BRACKETS' => true,
243 'SYMBOLS' => true,
244 'STRINGS' => true,
245 'NUMBERS' => true,
246 'METHODS' => true,
247 'SCRIPT' => true
248 );
249
250 /**
251 * The time it took to parse the code
252 * @var double
253 */
254 var $time = 0;
255
256 /**
257 * The content of the header block
258 * @var string
259 */
260 var $header_content = '';
261
262 /**
263 * The content of the footer block
264 * @var string
265 */
266 var $footer_content = '';
267
268 /**
269 * The style of the header block
270 * @var string
271 */
272 var $header_content_style = '';
273
274 /**
275 * The style of the footer block
276 * @var string
277 */
278 var $footer_content_style = '';
279
280 /**
281 * The styles for hyperlinks in the code
282 * @var array
283 */
284 var $link_styles = array();
285
286 /**
287 * Whether important blocks should be recognised or not
288 * @var boolean
289 * @todo REMOVE THIS FUNCTIONALITY!
290 */
291 var $enable_important_blocks = false;
292
293 /**
294 * Styles for important parts of the code
295 * @var string
296 * @todo As above - rethink the whole idea of important blocks as it is buggy and
297 * will be hard to implement in 1.2
298 */
299 var $important_styles = 'font-weight: bold; color: red;'; // Styles for important parts of the code
300
301 /**
302 * Whether CSS IDs should be added to the code
303 * @var boolean
304 */
305 var $add_ids = false;
306
307 /**
308 * Lines that should be highlighted extra
309 * @var array
310 */
311 var $highlight_extra_lines = array();
312
313 /**
314 * Styles of extra-highlighted lines
315 * @var string
316 */
317 var $highlight_extra_lines_style = 'color: #cc0; background-color: #ffc;';
318
319 /**
320 * Number at which line numbers should start at
321 * @var int
322 * @todo Warning documentation about XHTML compliance
323 */
324 var $line_numbers_start = 1;
325
326 /**
327 * The overall style for this code block
328 * @var string
329 */
330 var $overall_style = '';
331
332 /**
333 * The style for the actual code
334 * @var string
335 */
336 var $code_style = 'font-family: \'Courier New\', Courier, monospace; font-weight: normal;';
337
338 /**
339 * The overall class for this code block
340 * @var string
341 */
342 var $overall_class = '';
343
344 /**
345 * The overall ID for this code block
346 * @var string
347 */
348 var $overall_id = '';
349
350 /**
351 * Line number styles
352 * @var string
353 */
354 var $line_style1 = 'font-family: \'Courier New\', Courier, monospace; color: black; font-weight: normal; font-style: normal;';
355
356 /**
357 * Line number styles for fancy lines
358 * @var string
359 */
360 var $line_style2 = 'font-weight: bold;';
361
362 /**
363 * Flag for how line nubmers are displayed
364 * @var boolean
365 */
366 var $line_numbers = GESHI_NO_LINE_NUMBERS;
367
368 /**
369 * The "nth" value for fancy line highlighting
370 * @var int
371 */
372 var $line_nth_row = 0;
373
374 /**
375 * The size of tab stops
376 * @var int
377 */
378 var $tab_width = 8;
379
380 /**
381 * Default target for keyword links
382 * @var string
383 */
384 var $link_target = '';
385
386 /**
387 * The encoding to use for entity encoding
388 * @var string
389 */
390 var $encoding = 'ISO-8859-1';
391
392 /**
393 * Unused (planned for future)
394 * @var int
395 */
396 var $output_format = GESHI_OUTPUT_HTML;
397
398 /**#@-*/
399
400 /**
401 * Creates a new GeSHi object, with source and language
402 *
403 * @param string The source code to highlight
404 * @param string The language to highlight the source with
405 * @param string The path to the language file directory. <b>This
406 * is deprecated!</b> I've backported the auto path
407 * detection from the 1.1.X dev branch, so now it
408 * should be automatically set correctly. If you have
409 * renamed the language directory however, you will
410 * still need to set the path using this parameter or
411 * {@link GeSHi::set_language_path()}
412 * @since 1.0.0
413 */
414 function GeSHi ($source, $language, $path = '')
415 {
416 $this->set_source($source);
417 $this->set_language_path($path);
418 $this->set_language($language);
419 }
420
421 /**
422 * Returns an error message associated with the last GeSHi operation,
423 * or false if no error has occured
424 *
425 * @return string|falseAn error message if there has been an error, else false
426 * @since 1.0.0
427 */
428 function error ()
429 {
430 if ($this->error) {
431 $msg = $this->error_messages[$this->error];
432 $debug_tpl_vars = array(
433 '{LANGUAGE}' => $this->language,
434 '{PATH}' => $this->
435 language_path );
436 foreach ($debug_tpl_vars as $tpl => $var) {
437 $msg = str_replace($tpl, $var, $msg);
438 }
439 return "<br /><strong>GeSHi Error:</strong> $msg (code $this->error)<br />";
440 }
441 return false;
442 }
443
444 /**
445 * Gets a human-readable language name (thanks to Simon Patterson
446 * for the idea :))
447 *
448 * @return string The name for the current language
449 * @since 1.0.2
450 */
451 function get_language_name ()
452 {
453 if (GESHI_ERROR_NO_SUCH_LANG == $this->_error) {
454 return $this->language_data['LANG_NAME'] . ' (Unknown Language)';
455 }
456 return $this->language_data['LANG_NAME'];
457 }
458
459 /**
460 * Sets the source code for this object
461 *
462 * @param string The source code to highlight
463 * @since 1.0.0
464 */
465 function set_source ($source)
466 {
467 if ('' == trim($source)) {
468 $this->error = GESHI_ERROR_NO_INPUT;
469 }
470 $this->source = $source;
471 }
472
473 /**
474 * Sets the language for this object
475 *
476 * @param string The name of the language to use
477 * @since 1.0.0
478 */
479 function set_language ($language)
480 {
481 $this->error = false;
482 $this->strict_mode = GESHI_NEVER;
483
484 $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
485 $this->language = strtolower($language);
486
487 $file_name = $this->language_path . $this->language . '.php';
488 if (!is_readable($file_name)) {
489 $this->error = GESHI_ERROR_NO_SUCH_LANG;
490 return;
491 }
492 // Load the language for parsing
493 $this->load_language($file_name);
494 }
495
496 /**
497 * Sets the path to the directory containing the language files. Note
498 * that this path is relative to the directory of the script that included
499 * geshi.php, NOT geshi.php itself.
500 *
501 * @param string The path to the language directory
502 * @since 1.0.0
503 * @deprecated The path to the language files should now be automatically
504 * detected, so this method should no longer be needed. The
505 * 1.1.X branch handles manual setting of the path differently
506 * so this method will disappear in 1.2.0.
507 */
508 function set_language_path ($path)
509 {
510 if ($path) {
511 $this->language_path = ('/' == substr($path, strlen($path) - 1, 1)) ? $path : $path . '/';
512 }
513 }
514
515 /**
516 * Sets the type of header to be used.
517 *
518 * If GESHI_HEADER_DIV is used, the code is surrounded in a "div".This
519 * means more source code but more control over tab width and line-wrapping.
520 * GESHI_HEADER_PRE means that a "pre" is used - less source, but less
521 * control. Default is GESHI_HEADER_PRE.
522 *
523 * From 1.0.7.2, you can use GESHI_HEADER_NONE to specify that no header code
524 * should be outputted.
525 *
526 * @param int The type of header to be used
527 * @since 1.0.0
528 */
529 function set_header_type ($type)
530 {
531 if (GESHI_HEADER_DIV != $type && GESHI_HEADER_PRE != $type && GESHI_HEADER_NONE != $type) {
532 $this->error = GESHI_ERROR_INVALID_HEADER_TYPE;
533 return;
534 }
535 $this->header_type = $type;
536 }
537
538 /**
539 * Sets the styles for the code that will be outputted
540 * when this object is parsed. The style should be a
541 * string of valid stylesheet declarations
542 *
543 * @param string The overall style for the outputted code block
544 * @param boolean Whether to merge the styles with the current styles or not
545 * @since 1.0.0
546 */
547 function set_overall_style ($style, $preserve_defaults = false)
548 {
549 if (!$preserve_defaults) {
550 $this->overall_style = $style;
551 } else {
552 $this->overall_style .= $style;
553 }
554 }
555
556 /**
557 * Sets the overall classname for this block of code. This
558 * class can then be used in a stylesheet to style this object's
559 * output
560 *
561 * @param string The class name to use for this block of code
562 * @since 1.0.0
563 */
564 function set_overall_class ($class)
565 {
566 $this->overall_class = $class;
567 }
568
569 /**
570 * Sets the overall id for this block of code. This id can then
571 * be used in a stylesheet to style this object's output
572 *
573 * @param string The ID to use for this block of code
574 * @since 1.0.0
575 */
576 function set_overall_id ($id)
577 {
578 $this->overall_id = $id;
579 }
580
581 /**
582 * Sets whether CSS classes should be used to highlight the source. Default
583 * is off, calling this method with no arguments will turn it on
584 *
585 * @param boolean Whether to turn classes on or not
586 * @since 1.0.0
587 */
588 function enable_classes ($flag = true)
589 {
590 $this->use_classes = ($flag) ? true : false;
591 }
592
593 /**
594 * Sets the style for the actual code. This should be a string
595 * containing valid stylesheet declarations. If $preserve_defaults is
596 * true, then styles are merged with the default styles, with the
597 * user defined styles having priority
598 *
599 * Note: Use this method to override any style changes you made to
600 * the line numbers if you are using line numbers, else the line of
601 * code will have the same style as the line number! Consult the
602 * GeSHi documentation for more information about this.
603 *
604 * @param string The style to use for actual code
605 * @param boolean Whether to merge the current styles with the new styles
606 */
607 function set_code_style ($style, $preserve_defaults = false)
608 {
609 if (!$preserve_defaults) {
610 $this->code_style = $style;
611 } else {
612 $this->code_style .= $style;
613 }
614 }
615
616 /**
617 * Sets the styles for the line numbers.
618 *
619 * @param string The style for the line numbers that are "normal"
620 * @param string|booleanIf a string, this is the style of the line
621 * numbers that are "fancy", otherwise if boolean then this
622 * defines whether the normal styles should be merged with the
623 * new normal styles or not
624 * @param boolean If set, is the flag for whether to merge the "fancy"
625 * styles with the current styles or not
626 * @since 1.0.2
627 */
628 function set_line_style ($style1, $style2 = '', $preserve_defaults = false)
629 {
630 if (is_bool($style2)) {
631 $preserve_defaults = $style2;
632 $style2 = '';
633 }
634 if (!$preserve_defaults) {
635 $this->line_style1 = $style1;
636 $this->line_style2 = $style2;
637 } else {
638 $this->line_style1 .= $style1;
639 $this->line_style2 .= $style2;
640 }
641 }
642
643 /**
644 * Sets whether line numbers should be displayed.
645 *
646 * Valid values for the first parameter are:
647 *
648 * <ul>
649 * <li><b>GESHI_NO_LINE_NUMBERS</b>: Line numbers will not be displayed</li>
650 * <li><b>GESHI_NORMAL_LINE_NUMBERS</b>: Line numbers will be displayed</li>
651 * <li><b>GESHI_FANCY_LINE_NUMBERS</b>: Fancy line numbers will be displayed</li>
652 * </ul>
653 *
654 * For fancy line numbers, the second parameter is used to signal which lines
655 * are to be fancy. For example, if the value of this parameter is 5 then every
656 * 5th line will be fancy.
657 *
658 * @param int How line numbers should be displayed
659 * @param int Defines which lines are fancy
660 * @since 1.0.0
661 */
662 function enable_line_numbers ($flag, $nth_row = 5)
663 {
664 if (GESHI_NO_LINE_NUMBERS != $flag && GESHI_NORMAL_LINE_NUMBERS != $flag
665 && GESHI_FANCY_LINE_NUMBERS != $flag) {
666 $this->error = GESHI_ERROR_INVALID_LINE_NUMBER_TYPE;
667 }
668 $this->line_numbers = $flag;
669 $this->line_nth_row = $nth_row;
670 }
671
672 /**
673 * Sets the style for a keyword group. If $preserve_defaults is
674 * true, then styles are merged with the default styles, with the
675 * user defined styles having priority
676 *
677 * @param int The key of the keyword group to change the styles of
678 * @param string The style to make the keywords
679 * @param boolean Whether to merge the new styles with the old or just
680 * to overwrite them
681 * @since 1.0.0
682 */
683 function set_keyword_group_style ($key, $style, $preserve_defaults = false)
684 {
685 if (!$preserve_defaults) {
686 $this->language_data['STYLES']['KEYWORDS'][$key] = $style;
687 } else {
688 $this->language_data['STYLES']['KEYWORDS'][$key] .= $style;
689 }
690 }
691
692 /**
693 * Turns highlighting on/off for a keyword group
694 *
695 * @param int The key of the keyword group to turn on or off
696 * @param boolean Whether to turn highlighting for that group on or off
697 * @since 1.0.0
698 */
699 function set_keyword_group_highlighting ( $key, $flag = true )
700 {
701 $this->lexic_permissions['KEYWORDS'][$key] = ($flag) ? true : false;
702 }
703
704 /**
705 * Sets the styles for comment groups. If $preserve_defaults is
706 * true, then styles are merged with the default styles, with the
707 * user defined styles having priority
708 *
709 * @param int The key of the comment group to change the styles of
710 * @param string The style to make the comments
711 * @param boolean Whether to merge the new styles with the old or just
712 * to overwrite them
713 * @since 1.0.0
714 */
715 function set_comments_style ($key, $style, $preserve_defaults = false)
716 {
717 if (!$preserve_defaults) {
718 $this->language_data['STYLES']['COMMENTS'][$key] = $style;
719 } else {
720 $this->language_data['STYLES']['COMMENTS'][$key] .= $style;
721 }
722 }
723
724 /**
725 * Turns highlighting on/off for comment groups
726 *
727 * @param int The key of the comment group to turn on or off
728 * @param boolean Whether to turn highlighting for that group on or off
729 * @since 1.0.0
730 */
731 function set_comments_highlighting ($key, $flag = true)
732 {
733 $this->lexic_permissions['COMMENTS'][$key] = ($flag) ? true : false;
734 }
735
736 /**
737 * Sets the styles for escaped characters. If $preserve_defaults is
738 * true, then styles are merged with the default styles, with the
739 * user defined styles having priority
740 *
741 * @param string The style to make the escape characters
742 * @param boolean Whether to merge the new styles with the old or just
743 * to overwrite them
744 * @since 1.0.0
745 */
746 function set_escape_characters_style ($style, $preserve_defaults = false)
747 {
748 if (!$preserve_defaults) {
749 $this->language_data['STYLES']['ESCAPE_CHAR'][0] = $style;
750 } else {
751 $this->language_data['STYLES']['ESCAPE_CHAR'][0] .= $style;
752 }
753 }
754
755 /**
756 * Turns highlighting on/off for escaped characters
757 *
758 * @param boolean Whether to turn highlighting for escape characters on or off
759 * @since 1.0.0
760 */
761 function set_escape_characters_highlighting ($flag = true)
762 {
763 $this->lexic_permissions['ESCAPE_CHAR'] = ($flag) ? true : false;
764 }
765
766 /**
767 * Sets the styles for brackets. If $preserve_defaults is
768 * true, then styles are merged with the default styles, with the
769 * user defined styles having priority
770 *
771 * This method is DEPRECATED: use set_symbols_style instead.
772 * This method will be removed in 1.2.X
773 *
774 * @param string The style to make the brackets
775 * @param boolean Whether to merge the new styles with the old or just
776 * to overwrite them
777 * @since 1.0.0
778 * @deprecated In favour of set_symbols_style
779 */
780 function set_brackets_style ($style, $preserve_defaults = false)
781 {
782 if (!$preserve_defaults) {
783 $this->language_data['STYLES']['BRACKETS'][0] = $style;
784 } else {
785 $this->language_data['STYLES']['BRACKETS'][0] .= $style;
786 }
787 }
788
789 /**
790 * Turns highlighting on/off for brackets
791 *
792 * This method is DEPRECATED: use set_symbols_highlighting instead.
793 * This method will be remove in 1.2.X
794 *
795 * @param boolean Whether to turn highlighting for brackets on or off
796 * @since 1.0.0
797 * @deprecated In favour of set_symbols_highlighting
798 */
799 function set_brackets_highlighting ($flag)
800 {
801 $this->lexic_permissions['BRACKETS'] = ($flag) ? true : false;
802 }
803
804 /**
805 * Sets the styles for symbols. If $preserve_defaults is
806 * true, then styles are merged with the default styles, with the
807 * user defined styles having priority
808 *
809 * @param string The style to make the symbols
810 * @param boolean Whether to merge the new styles with the old or just
811 * to overwrite them
812 * @since 1.0.1
813 */
814 function set_symbols_style ($style, $preserve_defaults = false)
815 {
816 if (!$preserve_defaults) {
817 $this->language_data['STYLES']['SYMBOLS'][0] = $style;
818 } else {
819 $this->language_data['STYLES']['SYMBOLS'][0] .= $style;
820 }
821 // For backward compatibility
822 $this->set_brackets_style ($style, $preserve_defaults);
823 }
824
825 /**
826 * Turns highlighting on/off for symbols
827 *
828 * @param boolean Whether to turn highlighting for symbols on or off
829 * @since 1.0.0
830 */
831 function set_symbols_highlighting ($flag)
832 {
833 $this->lexic_permissions['SYMBOLS'] = ($flag) ? true : false;
834 // For backward compatibility
835 $this->set_brackets_highlighting ($flag);
836 }
837
838 /**
839 * Sets the styles for strings. If $preserve_defaults is
840 * true, then styles are merged with the default styles, with the
841 * user defined styles having priority
842 *
843 * @param string The style to make the escape characters
844 * @param boolean Whether to merge the new styles with the old or just
845 * to overwrite them
846 * @since 1.0.0
847 */
848 function set_strings_style ($style, $preserve_defaults = false)
849 {
850 if (!$preserve_defaults) {
851 $this->language_data['STYLES']['STRINGS'][0] = $style;
852 } else {
853 $this->language_data['STYLES']['STRINGS'][0] .= $style;
854 }
855 }
856
857 /**
858 * Turns highlighting on/off for strings
859 *
860 * @param boolean Whether to turn highlighting for strings on or off
861 * @since 1.0.0
862 */
863 function set_strings_highlighting ($flag)
864 {
865 $this->lexic_permissions['STRINGS'] = ($flag) ? true : false;
866 }
867
868 /**
869 * Sets the styles for numbers. If $preserve_defaults is
870 * true, then styles are merged with the default styles, with the
871 * user defined styles having priority
872 *
873 * @param string The style to make the numbers
874 * @param boolean Whether to merge the new styles with the old or just
875 * to overwrite them
876 * @since 1.0.0
877 */
878 function set_numbers_style ($style, $preserve_defaults = false)
879 {
880 if (!$preserve_defaults) {
881 $this->language_data['STYLES']['NUMBERS'][0] = $style;
882 } else {
883 $this->language_data['STYLES']['NUMBERS'][0] .= $style;
884 }
885 }
886
887 /**
888 * Turns highlighting on/off for numbers
889 *
890 * @param boolean Whether to turn highlighting for numbers on or off
891 * @since 1.0.0
892 */
893 function set_numbers_highlighting ($flag)
894 {
895 $this->lexic_permissions['NUMBERS'] = ($flag) ? true : false;
896 }
897
898 /**
899 * Sets the styles for methods. $key is a number that references the
900 * appropriate "object splitter" - see the language file for the language
901 * you are highlighting to get this number. If $preserve_defaults is
902 * true, then styles are merged with the default styles, with the
903 * user defined styles having priority
904 *
905 * @param int The key of the object splitter to change the styles of
906 * @param string The style to make the methods
907 * @param boolean Whether to merge the new styles with the old or just
908 * to overwrite them
909 * @since 1.0.0
910 */
911 function set_methods_style ($key, $style, $preserve_defaults = false)
912 {
913 if (!$preserve_defaults) {
914 $this->language_data['STYLES']['METHODS'][$key] = $style;
915 } else {
916 $this->language_data['STYLES']['METHODS'][$key] .= $style;
917 }
918 }
919
920 /**
921 * Turns highlighting on/off for methods
922 *
923 * @param boolean Whether to turn highlighting for methods on or off
924 * @since 1.0.0
925 */
926 function set_methods_highlighting ($flag)
927 {
928 $this->lexic_permissions['METHODS'] = ($flag) ? true : false;
929 }
930
931 /**
932 * Sets the styles for regexps. If $preserve_defaults is
933 * true, then styles are merged with the default styles, with the
934 * user defined styles having priority
935 *
936 * @param string The style to make the regular expression matches
937 * @param boolean Whether to merge the new styles with the old or just
938 * to overwrite them
939 * @since 1.0.0
940 */
941 function set_regexps_style ($key, $style, $preserve_defaults = false)
942 {
943 if (!$preserve_defaults) {
944 $this->language_data['STYLES']['REGEXPS'][$key] = $style;
945 } else {
946 $this->language_data['STYLES']['REGEXPS'][$key] .= $style;
947 }
948 }
949
950 /**
951 * Turns highlighting on/off for regexps
952 *
953 * @param int The key of the regular expression group to turn on or off
954 * @param boolean Whether to turn highlighting for the regular expression group on or off
955 * @since 1.0.0
956 */
957 function set_regexps_highlighting ($key, $flag)
958 {
959 $this->lexic_permissions['REGEXPS'][$key] = ($flag) ? true : false;
960 }
961
962 /**
963 * Sets whether a set of keywords are checked for in a case sensitive manner
964 *
965 * @param int The key of the keyword group to change the case sensitivity of
966 * @param boolean Whether to check in a case sensitive manner or not
967 * @since 1.0.0
968 */
969 function set_case_sensitivity ($key, $case)
970 {
971 $this->language_data['CASE_SENSITIVE'][$key] = ($case) ? true : false;
972 }
973
974 /**
975 * Sets the case that keywords should use when found. Use the constants:
976 *
977 * <ul>
978 * <li><b>GESHI_CAPS_NO_CHANGE</b>: leave keywords as-is</li>
979 * <li><b>GESHI_CAPS_UPPER</b>: convert all keywords to uppercase where found</li>
980 * <li><b>GESHI_CAPS_LOWER</b>: convert all keywords to lowercase where found</li>
981 * </ul>
982 *
983 * @param int A constant specifying what to do with matched keywords
984 * @since 1.0.1
985 * @todo Error check the passed value
986 */
987 function set_case_keywords ($case)
988 {
989 $this->language_data['CASE_KEYWORDS'] = $case;
990 }
991
992 /**
993 * Sets how many spaces a tab is substituted for
994 *
995 * Widths below zero are ignored
996 *
997 * @param int The tab width
998 * @since 1.0.0
999 */
1000 function set_tab_width ($width)
1001 {
1002 $this->tab_width = intval($width);
1003 }
1004
1005 /**
1006 * Enables/disables strict highlighting. Default is off, calling this
1007 * method without parameters will turn it on. See documentation
1008 * for more details on strict mode and where to use it.
1009 *
1010 * @param boolean Whether to enable strict mode or not
1011 * @since 1.0.0
1012 */
1013 function enable_strict_mode ($mode = true)
1014 {
1015 if (GESHI_MAYBE == $this->language_data['STRICT_MODE_APPLIES']) {
1016 $this->strict_mode = ($mode) ? true : false;
1017 }
1018 }
1019
1020 /**
1021 * Disables all highlighting
1022 *
1023 * @since 1.0.0
1024 * @todo Rewrite with an array traversal
1025 */
1026 function disable_highlighting ()
1027 {
1028 foreach ($this->lexic_permissions as $key => $value) {
1029 if (is_array($value)) {
1030 foreach ($value as $k => $v) {
1031 $this->lexic_permissions[$key][$k] = false;
1032 }
1033 } else {
1034 $this->lexic_permissions[$key] = false;
1035 }
1036 }
1037 // Context blocks
1038 $this->enable_important_blocks = false;
1039 }
1040
1041 /**
1042 * Enables all highlighting
1043 *
1044 * @since 1.0.0
1045 * @todo Rewrite with array traversal
1046 */
1047 function enable_highlighting ()
1048 {
1049 foreach ($this->lexic_permissions as $key => $value) {
1050 if (is_array($value)) {
1051 foreach ($value as $k => $v) {
1052 $this->lexic_permissions[$key][$k] = true;
1053 }
1054 } else {
1055 $this->lexic_permissions[$key] = true;
1056 }
1057 }
1058 // Context blocks
1059 $this->enable_important_blocks = true;
1060 }
1061
1062 /**
1063 * Given a file extension, this method returns either a valid geshi language
1064 * name, or the empty string if it couldn't be found
1065 *
1066 * @param string The extension to get a language name for
1067 * @param array A lookup array to use instead of the default
1068 * @since 1.0.5
1069 * @todo Re-think about how this method works (maybe make it private and/or make it
1070 * a extension->lang lookup?)
1071 * @todo static?
1072 */
1073 function get_language_name_from_extension ( $extension, $lookup = array() )
1074 {
1075 if ( !$lookup )
1076 {
1077 $lookup = array(
1078 'actionscript' => array('as'),
1079 'ada' => array('a', 'ada', 'adb', 'ads'),
1080 'apache' => array('conf'),
1081 'asm' => array('ash', 'asm'),
1082 'asp' => array('asp'),
1083 'bash' => array('sh'),
1084 'c' => array('c'),
1085 'c_mac' => array('c'),
1086 'caddcl' => array(),
1087 'cadlisp' => array(),
1088 'cpp' => array('cpp'),
1089 'csharp' => array(),
1090 'css' => array('css'),
1091 'delphi' => array('dpk', 'dpr'),
1092 'html4strict' => array('html', 'htm'),
1093 'java' => array('java'),
1094 'javascript' => array('js'),
1095 'lisp' => array('lisp'),
1096 'lua' => array('lua'),
1097 'mpasm' => array(),
1098 'nsis' => array(),
1099 'objc' => array(),
1100 'oobas' => array(),
1101 'oracle8' => array(),
1102 'pascal' => array('pas'),
1103 'perl' => array('pl', 'pm'),
1104 'php' => array('php', 'php5', 'phtml', 'phps'),
1105 'python' => array('py'),
1106 'qbasic' => array('bi'),
1107 'smarty' => array(),
1108 'vb' => array('bas'),
1109 'vbnet' => array(),
1110 'visualfoxpro' => array(),
1111 'xml' => array('xml')
1112 );
1113 }
1114
1115 foreach ($lookup as $lang => $extensions) {
1116 foreach ($extensions as $ext) {
1117 if ($ext == $extension) {
1118 return $lang;
1119 }
1120 }
1121 }
1122 return '';
1123 }
1124
1125 /**
1126 * Given a file name, this method loads its contents in, and attempts
1127 * to set the language automatically. An optional lookup table can be
1128 * passed for looking up the language name. If not specified a default
1129 * table is used
1130 *
1131 * The language table is in the form
1132 * <pre>array(
1133 * 'lang_name' => array('extension', 'extension', ...),
1134 * 'lang_name' ...
1135 * );</pre>
1136 *
1137 * @todo Complete rethink of this and above method
1138 * @since 1.0.5
1139 */
1140 function load_from_file ($file_name, $lookup = array())
1141 {
1142 if (is_readable($file_name)) {
1143 $this->set_source(implode('', file($file_name)));
1144 $this->set_language($this->get_language_name_from_extension(substr(strrchr($file_name, '.'), 1), $lookup));
1145 } else {
1146 $this->error = GESHI_ERROR_FILE_NOT_READABLE;
1147 }
1148 }
1149
1150 /**
1151 * Adds a keyword to a keyword group for highlighting
1152 *
1153 * @param int The key of the keyword group to add the keyword to
1154 * @param string The word to add to the keyword group
1155 * @since 1.0.0
1156 */
1157 function add_keyword ($key, $word)
1158 {
1159 $this->language_data['KEYWORDS'][$key][] = $word;
1160 }
1161
1162 /**
1163 * Removes a keyword from a keyword group
1164 *
1165 * @param int The key of the keyword group to remove the keyword from
1166 * @param string The word to remove from the keyword group
1167 * @since 1.0.0
1168 */
1169 function remove_keyword ($key, $word)
1170 {
1171 $this->language_data['KEYWORDS'][$key] =
1172 array_diff($this->language_data['KEYWORDS'][$key], array($word));
1173 }
1174
1175 /**
1176 * Creates a new keyword group
1177 *
1178 * @param int The key of the keyword group to create
1179 * @param string The styles for the keyword group
1180 * @param boolean Whether the keyword group is case sensitive ornot
1181 * @param array The words to use for the keyword group
1182 * @since 1.0.0
1183 */
1184 function add_keyword_group ( $key, $styles, $case_sensitive = true, $words = array() )
1185 {
1186 $words = (array) $words;
1187 $this->language_data['KEYWORDS'][$key] = $words;
1188 $this->lexic_permissions['KEYWORDS'][$key] = true;
1189 $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive;
1190 $this->language_data['STYLES']['KEYWORDS'][$key] = $styles;
1191 }
1192
1193 /**
1194 * Removes a keyword group
1195 *
1196 * @param int The key of the keyword group to remove
1197 * @since 1.0.0
1198 */
1199 function remove_keyword_group ($key)
1200 {
1201 unset($this->language_data['KEYWORDS'][$key]);
1202 unset($this->lexic_permissions['KEYWORDS'][$key]);
1203 unset($this->language_data['CASE_SENSITIVE'][$key]);
1204 unset($this->language_data['STYLES']['KEYWORDS'][$key]);
1205 }
1206
1207 /**
1208 * Sets the content of the header block
1209 *
1210 * @param string The content of the header block
1211 * @since 1.0.2
1212 */
1213 function set_header_content ($content)
1214 {
1215 $this->header_content = $content;
1216 }
1217
1218 /**
1219 * Sets the content of the footer block
1220 *
1221 * @param string The content of the footer block
1222 * @since 1.0.2
1223 */
1224 function set_footer_content ($content)
1225 {
1226 $this->footer_content = $content;
1227 }
1228
1229 /**
1230 * Sets the style for the header content
1231 *
1232 * @param string The style for the header content
1233 * @since 1.0.2
1234 */
1235 function set_header_content_style ($style)
1236 {
1237 $this->header_content_style = $style;
1238 }
1239
1240 /**
1241 * Sets the style for the footer content
1242 *
1243 * @param string The style for the footer content
1244 * @since 1.0.2
1245 */
1246 function set_footer_content_style ($style)
1247 {
1248 $this->footer_content_style = $style;
1249 }
1250
1251 /**
1252 * Sets the base URL to be used for keywords
1253 *
1254 * @param int The key of the keyword group to set the URL for
1255 * @param string The URL to set for the group. If {FNAME} is in
1256 * the url somewhere, it is replaced by the keyword
1257 * that the URL is being made for
1258 * @since 1.0.2
1259 */
1260 function set_url_for_keyword_group ($group, $url)
1261 {
1262 $this->language_data['URLS'][$group] = $url;
1263 }
1264
1265 /**
1266 * Sets styles for links in code
1267 *
1268 * @param int A constant that specifies what state the style is being
1269 * set for - e.g. :hover or :visited
1270 * @param string The styles to use for that state
1271 * @since 1.0.2
1272 */
1273 function set_link_styles ($type, $styles)
1274 {
1275 $this->link_styles[$type] = $styles;
1276 }
1277
1278 /**
1279 * Sets the target for links in code
1280 *
1281 * @param string The target for links in the code, e.g. _blank
1282 * @since 1.0.3
1283 */
1284 function set_link_target ( $target )
1285 {
1286 if (!$target) {
1287 $this->link_target = '';
1288 } else {
1289 $this->link_target = ' target="' . $target . '" ';
1290 }
1291 }
1292
1293 /**
1294 * Sets styles for important parts of the code
1295 *
1296 * @param string The styles to use on important parts of the code
1297 * @since 1.0.2
1298 */
1299 function set_important_styles ($styles)
1300 {
1301 $this->important_styles = $styles;
1302 }
1303
1304 /**
1305 * Sets whether context-important blocks are highlighted
1306 *
1307 * @todo REMOVE THIS SHIZ FROM GESHI!
1308 */
1309 function enable_important_blocks ( $flag )
1310 {
1311 $this->enable_important_blocks = ( $flag ) ? true : false;
1312 }
1313
1314 /**
1315 * Whether CSS IDs should be added to each line
1316 *
1317 * @param boolean If true, IDs will be added to each line.
1318 * @since 1.0.2
1319 */
1320 function enable_ids ($flag = true)
1321 {
1322 $this->add_ids = ($flag) ? true : false;
1323 }
1324
1325 /**
1326 * Specifies which lines to highlight extra
1327 *
1328 * @param mixed An array of line numbers to highlight, or just a line
1329 * number on its own.
1330 * @since 1.0.2
1331 * @todo Some data replication here that could be cut down on
1332 */
1333 function highlight_lines_extra ($lines)
1334 {
1335 if (is_array($lines)) {
1336 foreach ($lines as $line) {
1337 $this->highlight_extra_lines[intval($line)] = intval($line);
1338 }
1339 } else {
1340 $this->highlight_extra_lines[intval($lines)] = intval($lines);
1341 }
1342 }
1343
1344 /**
1345 * Sets the style for extra-highlighted lines
1346 *
1347 * @param string The style for extra-highlighted lines
1348 * @since 1.0.2
1349 */
1350 function set_highlight_lines_extra_style ($styles)
1351 {
1352 $this->highlight_extra_lines_style = $styles;
1353 }
1354
1355 /**
1356 * Sets what number line numbers should start at. Should
1357 * be a positive integer, and will be converted to one.
1358 *
1359 * <b>Warning:</b> Using this method will add the "start"
1360 * attribute to the <ol> that is used for line numbering.
1361 * This is <b>not</b> valid XHTML strict, so if that's what you
1362 * care about then don't use this method. Firefox is getting
1363 * support for the CSS method of doing this in 1.1 and Opera
1364 * has support for the CSS method, but (of course) IE doesn't
1365 * so it's not worth doing it the CSS way yet.
1366 *
1367 * @param int The number to start line numbers at
1368 * @since 1.0.2
1369 */
1370 function start_line_numbers_at ($number)
1371 {
1372 $this->line_numbers_start = abs(intval($number));
1373 }
1374
1375 /**
1376 * Sets the encoding used for htmlspecialchars(), for international
1377 * support.
1378 *
1379 * @param string The encoding to use for the source
1380 * @since 1.0.3
1381 */
1382 function set_encoding ($encoding)
1383 {
1384 if ($encoding) {
1385 $this->encoding = $encoding;
1386 }
1387 }
1388
1389 /**
1390 * Returns the code in $this->source, highlighted and surrounded by the
1391 * nessecary HTML.
1392 *
1393 * This should only be called ONCE, cos it's SLOW! If you want to highlight
1394 * the same source multiple times, you're better off doing a whole lot of
1395 * str_replaces to replace the <span>s
1396 *
1397 * @since 1.0.0
1398 */
1399 function parse_code ()
1400 {
1401 // Start the timer
1402 $start_time = microtime();
1403
1404 // Firstly, if there is an error, we won't highlight
1405 if ($this->error) {
1406 $result = $this->header();
1407 if ($this->header_type != GESHI_HEADER_PRE) {
1408 $result .= $this->indent(@htmlspecialchars($this->source, ENT_COMPAT, $this->encoding));
1409 } else {
1410 $result .= @htmlspecialchars($this->source, ENT_COMPAT, $this->encoding);
1411 }
1412 // Stop Timing
1413 $this->set_time($start_time, microtime());
1414 return $result . $this->footer();
1415 }
1416
1417 // Add spaces for regular expression matching and line numbers
1418 $code = ' ' . $this->source . ' ';
1419 // Replace all newlines to a common form.
1420 $code = str_replace("\r\n", "\n", $code);
1421 $code = str_replace("\r", "\n", $code);
1422
1423 // Initialise various stuff
1424 $length = strlen($code);
1425 $STRING_OPEN = '';
1426 $CLOSE_STRING = false;
1427 $ESCAPE_CHAR_OPEN = false;
1428 $COMMENT_MATCHED = false;
1429 // Turn highlighting on if strict mode doesn't apply to this language
1430 $HIGHLIGHTING_ON = ( !$this->strict_mode ) ? true : '';
1431 // Whether to highlight inside a block of code
1432 $HIGHLIGHT_INSIDE_STRICT = false;
1433 $stuff_to_parse = '';
1434 $result = '';
1435
1436 // "Important" selections are handled like multiline comments
1437 // @todo GET RID OF THIS SHIZ
1438 if ($this->enable_important_blocks) {
1439 $this->language_data['COMMENT_MULTI'][GESHI_START_IMPORTANT] = GESHI_END_IMPORTANT;
1440 }
1441
1442 if ($this->strict_mode) {
1443 // Break the source into bits. Each bit will be a portion of the code
1444 // within script delimiters - for example, HTML between < and >
1445 $parts = array(0 => array(0 => ''));
1446 $k = 0;
1447 for ($i = 0; $i < $length; $i++) {
1448 $char = substr($code, $i, 1);
1449 if (!$HIGHLIGHTING_ON) {
1450 foreach ($this->language_data['SCRIPT_DELIMITERS'] as $key => $delimiters) {
1451 foreach ($delimiters as $open => $close) {
1452 // Get the next little bit for this opening string
1453 $check = substr($code, $i, strlen($open));
1454 // If it matches...
1455 if ($check == $open) {
1456 // We start a new block with the highlightable
1457 // code in it
1458 $HIGHLIGHTING_ON = $open;
1459 $i += strlen($open) - 1;
1460 $char = $open;
1461 $parts[++$k][0] = $char;
1462
1463 // No point going around again...
1464 break(2);
1465 }
1466 }
1467 }
1468 } else {
1469 foreach ($this->language_data['SCRIPT_DELIMITERS'] as $key => $delimiters) {
1470 foreach ($delimiters as $open => $close) {
1471 if ($open == $HIGHLIGHTING_ON) {
1472 // Found the closing tag
1473 break(2);
1474 }
1475 }
1476 }
1477 // We check code from our current position BACKWARDS. This is so
1478 // the ending string for highlighting can be included in the block
1479 $check = substr($code, $i - strlen($close) + 1, strlen($close));
1480 if ($check == $close) {
1481 $HIGHLIGHTING_ON = '';
1482 // Add the string to the rest of the string for this part
1483 $parts[$k][1] = ( isset($parts[$k][1]) ) ? $parts[$k][1] . $char : $char;
1484 $parts[++$k][0] = '';
1485 $char = '';
1486 }
1487 }
1488 $parts[$k][1] = ( isset($parts[$k][1]) ) ? $parts[$k][1] . $char : $char;
1489 }
1490 $HIGHLIGHTING_ON = '';
1491 } else {
1492 // Not strict mode - simply dump the source into
1493 // the array at index 1 (the first highlightable block)
1494 $parts = array(
1495 1 => array(
1496 0 => '',
1497 1 => $code
1498 )
1499 );
1500 }
1501
1502 // Now we go through each part. We know that even-indexed parts are
1503 // code that shouldn't be highlighted, and odd-indexed parts should
1504 // be highlighted
1505 foreach ($parts as $key => $data) {
1506 $part = $data[1];
1507 // If this block should be highlighted...
1508 if ($key % 2) {
1509 if ($this->strict_mode) {
1510 // Find the class key for this block of code
1511 foreach ($this->language_data['SCRIPT_DELIMITERS'] as $script_key => $script_data) {
1512 foreach ($script_data as $open => $close) {
1513 if ($data[0] == $open) {
1514 break(2);
1515 }
1516 }
1517 }
1518
1519 if ($this->language_data['STYLES']['SCRIPT'][$script_key] != '' &&
1520 $this->lexic_permissions['SCRIPT']) {
1521 // Add a span element around the source to
1522 // highlight the overall source block
1523 if (!$this->use_classes &&
1524 $this->language_data['STYLES']['SCRIPT'][$script_key] != '') {
1525 $attributes = ' style="' . $this->language_data['STYLES']['SCRIPT'][$script_key] . '"';
1526 } else {
1527 $attributes = ' class="sc' . $script_key . '"';
1528 }
1529 $result .= "<span$attributes>";
1530 }
1531 }
1532
1533 if (!$this->strict_mode || $this->language_data['HIGHLIGHT_STRICT_BLOCK'][$script_key]) {
1534 // Now, highlight the code in this block. This code
1535 // is really the engine of GeSHi (along with the method
1536 // parse_non_string_part).
1537 $length = strlen($part);
1538 for ($i = 0; $i < $length; $i++) {
1539 // Get the next char
1540 $char = substr($part, $i, 1);
1541 // Is this char the newline and line numbers being used?
1542 if (($this->line_numbers != GESHI_NO_LINE_NUMBERS
1543 || count($this->highlight_extra_lines) > 0)
1544 && $char == "\n") {
1545 // If so, is there a string open? If there is, we should end it before
1546 // the newline and begin it again (so when <li>s are put in the source
1547 // remains XHTML compliant)
1548 // note to self: This opens up possibility of config files specifying
1549 // that languages can/cannot have multiline strings???
1550 if ($STRING_OPEN) {
1551 if (!$this->use_classes) {
1552 $attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][0] . '"';
1553 } else {
1554 $attributes = ' class="st0"';
1555 }
1556 $char = '</span>' . $char . "<span$attributes>";
1557 }
1558 } elseif ($char == $STRING_OPEN) {
1559 // A match of a string delimiter
1560 if (($this->lexic_permissions['ESCAPE_CHAR'] && $ESCAPE_CHAR_OPEN) ||
1561 ($this->lexic_permissions['STRINGS'] && !$ESCAPE_CHAR_OPEN)) {
1562 $char .= '</span>';
1563 }
1564 if (!$ESCAPE_CHAR_OPEN) {
1565 $STRING_OPEN = '';
1566 $CLOSE_STRING = true;
1567 }
1568 $ESCAPE_CHAR_OPEN = false;
1569 } elseif (in_array($char, $this->language_data['QUOTEMARKS']) &&
1570 ($STRING_OPEN == '') && $this->lexic_permissions['STRINGS']) {
1571 // The start of a new string
1572 $STRING_OPEN = $char;
1573 if (!$this->use_classes) {
1574 $attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][0] . '"';
1575 } else {
1576 $attributes = ' class="st0"';
1577 }
1578 $char = "<span$attributes>" . $char;
1579
1580 $result .= $this->parse_non_string_part( $stuff_to_parse );
1581 $stuff_to_parse = '';
1582 } elseif (($char == $this->language_data['ESCAPE_CHAR']) && ($STRING_OPEN != '')) {
1583 // An escape character
1584 if (!$ESCAPE_CHAR_OPEN) {
1585 $ESCAPE_CHAR_OPEN = true;
1586 if ($this->lexic_permissions['ESCAPE_CHAR']) {
1587 if (!$this->use_classes) {
1588 $attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][0] . '"';
1589 } else {
1590 $attributes = ' class="es0"';
1591 }
1592 $char = "<span$attributes>" . $char;
1593 }
1594 } else {
1595 $ESCAPE_CHAR_OPEN = false;
1596 if ($this->lexic_permissions['ESCAPE_CHAR']) {
1597 $char .= '</span>';
1598 }
1599 }
1600 } elseif ($ESCAPE_CHAR_OPEN) {
1601 if ($this->lexic_permissions['ESCAPE_CHAR']) {
1602 $char .= '</span>';
1603 }
1604 $ESCAPE_CHAR_OPEN = false;
1605 $test_str = $char;
1606 } elseif ($STRING_OPEN == '') {
1607 // Is this a multiline comment?
1608 foreach ($this->language_data['COMMENT_MULTI'] as $open => $close) {
1609 $com_len = strlen($open);
1610 $test_str = substr( $part, $i, $com_len );
1611 $test_str_match = $test_str;
1612 if ($open == $test_str) {
1613 $COMMENT_MATCHED = true;
1614 //@todo If remove important do remove here
1615 if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
1616 $test_str == GESHI_START_IMPORTANT) {
1617 if ($test_str != GESHI_START_IMPORTANT) {
1618 if (!$this->use_classes) {
1619 $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS']['MULTI'] . '"';
1620 } else {
1621 $attributes = ' class="coMULTI"';
1622 }
1623 $test_str = "<span$attributes>" . @htmlspecialchars($test_str, ENT_COMPAT, $this->encoding);
1624 } else {
1625 if (!$this->use_classes) {
1626 $attributes = ' style="' . $this->important_styles . '"';
1627 } else {
1628 $attributes = ' class="imp"';
1629 }
1630 // We don't include the start of the comment if it's an
1631 // "important" part
1632 $test_str = "<span$attributes>";
1633 }
1634 } else {
1635 $test_str = @htmlspecialchars($test_str, ENT_COMPAT, $this->encoding);
1636 }
1637
1638 $close_pos = strpos( $part, $close, $i + strlen($close) );
1639
1640 if ($close_pos === false) {
1641 $close_pos = strlen($part);
1642 }
1643
1644 // Short-cut through all the multiline code
1645 $rest_of_comment = @htmlspecialchars(substr($part, $i + $com_len, $close_pos - $i), ENT_COMPAT, $this->encoding);
1646 if (($this->lexic_permissions['COMMENTS']['MULTI'] ||
1647 $test_str_match == GESHI_START_IMPORTANT) &&
1648 ($this->line_numbers != GESHI_NO_LINE_NUMBERS ||
1649 count($this->highlight_extra_lines) > 0)) {
1650 // strreplace to put close span and open span around multiline newlines
1651 $test_str .= str_replace("\n", "</span>\n<span$attributes>", $rest_of_comment);
1652 } else {
1653 $test_str .= $rest_of_comment;
1654 }
1655
1656 if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
1657 $test_str_match == GESHI_START_IMPORTANT) {
1658 $test_str .= '</span>';
1659 }
1660 $i = $close_pos + $com_len - 1;
1661 // parse the rest
1662 $result .= $this->parse_non_string_part($stuff_to_parse);
1663 $stuff_to_parse = '';
1664 break;
1665 }
1666 }
1667 // If we haven't matched a multiline comment, try single-line comments
1668 if (!$COMMENT_MATCHED) {
1669 foreach ($this->language_data['COMMENT_SINGLE'] as $comment_key => $comment_mark) {
1670 $com_len = strlen($comment_mark);
1671 $test_str = substr($part, $i, $com_len);
1672 if ($this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS]) {
1673 $match = ($comment_mark == $test_str);
1674 } else {
1675 $match = (strtolower($comment_mark) == strtolower($test_str));
1676 }
1677 if ($match) {
1678 $COMMENT_MATCHED = true;
1679 if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
1680 if (!$this->use_classes) {
1681 $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment_key] . '"';
1682 } else {
1683 $attributes = ' class="co' . $comment_key . '"';
1684 }
1685 $test_str = "<span$attributes>" . @htmlspecialchars($this->change_case($test_str), ENT_COMPAT, $this->encoding);
1686 } else {
1687 $test_str = @htmlspecialchars($test_str, ENT_COMPAT, $this->encoding);
1688 }
1689 $close_pos = strpos($part, "\n", $i);
1690 if ($close_pos === false) {
1691 $close_pos = strlen($part);
1692 }
1693 $test_str .= @htmlspecialchars(substr($part, $i + $com_len, $close_pos - $i - $com_len), ENT_COMPAT, $this->encoding);
1694 if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
1695 $test_str .= "</span>";
1696 }
1697 $test_str .= "\n";
1698 $i = $close_pos;
1699 // parse the rest
1700 $result .= $this->parse_non_string_part($stuff_to_parse);
1701 $stuff_to_parse = '';
1702 break;
1703 }
1704 }
1705 }
1706 } elseif ($STRING_OPEN != '') {
1707 // Otherwise, convert it to HTML form
1708 if (strtolower($this->encoding) == 'utf-8') {
1709 //only escape <128 (we don't want to break multibyte chars)
1710 if (ord($char) < 128) {
1711 $char = @htmlspecialchars($char, ENT_COMPAT, $this->encoding);
1712 }
1713 } else {
1714 //encode everthing
1715 $char = @htmlspecialchars($char, ENT_COMPAT, $this->encoding);
1716 }
1717 }
1718 // Where are we adding this char?
1719 if (!$COMMENT_MATCHED) {
1720 if (($STRING_OPEN == '') && !$CLOSE_STRING) {
1721 $stuff_to_parse .= $char;
1722 } else {
1723 $result .= $char;
1724 $CLOSE_STRING = false;
1725 }
1726 } else {
1727 $result .= $test_str;
1728 $COMMENT_MATCHED = false;
1729 }
1730 }
1731 // Parse the last bit
1732 $result .= $this->parse_non_string_part($stuff_to_parse);
1733 $stuff_to_parse = '';
1734 } else {
1735 $result .= @htmlspecialchars($part, ENT_COMPAT, $this->encoding);
1736 }
1737 // Close the <span> that surrounds the block
1738 // Removed since the only time this is used is for php and it doesn't need a </span>
1739 /*if ($this->strict_mode && $this->lexic_permissions['SCRIPT']) {
1740 $result .= '</span>';
1741 }*/
1742 } else {
1743 // Else not a block to highlight
1744 $result .= @htmlspecialchars($part, ENT_COMPAT, $this->encoding);
1745 }
1746 }
1747
1748 // Parse the last stuff (redundant?)
1749 $result .= $this->parse_non_string_part($stuff_to_parse);
1750
1751 // Lop off the very first and last spaces
1752 $result = substr($result, 1, strlen($result) - 1);
1753
1754 // Are we still in a string?
1755 if ($STRING_OPEN) {
1756 $result .= '</span>';
1757 }
1758
1759 // We're finished: stop timing
1760 $this->set_time($start_time, microtime());
1761
1762 return $this->finalise($result);
1763 }
1764
1765 /**
1766 * Swaps out spaces and tabs for HTML indentation. Not needed if
1767 * the code is in a pre block...
1768 *
1769 * @param string The source to indent
1770 * @return string The source with HTML indenting applied
1771 * @since 1.0.0
1772 * @access private
1773 */
1774 function indent ($result)
1775 {
1776 /// Replace tabs with the correct number of spaces
1777 if (false !== strpos($result, "\t")) {
1778 $lines = explode("\n", $result);
1779 foreach ($lines as $key => $line) {
1780 if (false === strpos($line, "\t")) {
1781 $lines[$key] = $line;
1782 continue;
1783 }//echo 'checking line ' . $key . '<br />';
1784
1785 $pos = 0;
1786 $tab_width = $this->tab_width;
1787 $length = strlen($line);
1788 $result_line = '';
1789
1790 //echo '<pre>line: ' . htmlspecialchars($line) . '</pre>';
1791 $IN_TAG = false;
1792 for ($i = 0; $i < $length; $i++) {
1793 $char = substr($line, $i, 1);
1794 // Simple engine to work out whether we're in a tag.
1795 // If we are we modify $pos. This is so we ignore HTML
1796 // in the line and only workout the tab replacement
1797 // via the actual content of the string
1798 // This test could be improved to include strings in the
1799 // html so that < or > would be allowed in user's styles
1800 // (e.g. quotes: '<' '>'; or similar)
1801 if ($IN_TAG && '>' == $char) {
1802 $IN_TAG = false;
1803 $result_line .= '>';
1804 ++$pos;
1805 } elseif (!$IN_TAG && '<' == $char) {
1806 $IN_TAG = true;
1807 $result_line .= '<';
1808 ++$pos;
1809 } elseif (!$IN_TAG && '&' == $char) {
1810 //echo "matched & in line... ";
1811 $substr = substr($line, $i + 3, 4);
1812 //$substr_5 = substr($line, 5, 1);
1813 $posi = strpos($substr, ';');
1814 if (false !== $posi) {
1815 //echo "found entity at $posi\n";
1816 $pos += $posi + 3;
1817 }
1818 $result_line .= '&';
1819 } elseif (!$IN_TAG && "\t" == $char) {
1820 $str = '';
1821 // OPTIMISE - move $strs out. Make an array:
1822 // $tabs = array(
1823 // 1 => ' ',
1824 // 2 => ' ',
1825 // 3 => ' ' etc etc
1826 // to use instead of building a string every time
1827 $strs = array(0 => ' ', 1 => ' ');
1828 //echo "building (pos=$pos i=$i) (" . ($i - $pos) . ") " . ($tab_width - (($i - $pos) % $tab_width)) . " spaces\n";
1829 for ($k = 0; $k < ($tab_width - (($i - $pos) % $tab_width)); $k++) $str .= $strs[$k % 2];
1830 $result_line .= $str;
1831 //$pos--;
1832 $pos++;
1833 //$pos -= $tab_width-1;
1834
1835 if (false === strpos($line, "\t", $i + 1)) {
1836 //$lines[$key] = $result_line;
1837 //echo 'got here';
1838 $result_line .= substr($line, $i + 1);
1839 break;
1840 }
1841 } elseif ( $IN_TAG ) {
1842 ++$pos;
1843 $result_line .= $char;
1844 } else {
1845 $result_line .= $char;
1846 //++$pos;
1847 }
1848 }
1849 $lines[$key] = $result_line;
1850 }
1851 $result = implode("\n", $lines);
1852 }
1853 // Other whitespace
1854 $result = str_replace(' ', ' ', $result);
1855 $result = str_replace(' ', ' ', $result);
1856 $result = str_replace("\n ", "\n ", $result);
1857
1858 if ($this->line_numbers == GESHI_NO_LINE_NUMBERS) {
1859 $result = nl2br($result);
1860 }
1861 return $result;
1862 }
1863
1864 /**
1865 * Changes the case of a keyword for those languages where a change is asked for
1866 *
1867 * @param string The keyword to change the case of
1868 * @return string The keyword with its case changed
1869 * @since 1.0.0
1870 * @access private
1871 */
1872 function change_case ($instr)
1873 {
1874 if ($this->language_data['CASE_KEYWORDS'] == GESHI_CAPS_UPPER) {
1875 return strtoupper($instr);
1876 } elseif ($this->language_data['CASE_KEYWORDS'] == GESHI_CAPS_LOWER) {
1877 return strtolower($instr);
1878 }
1879 return $instr;
1880 }
1881
1882 /**
1883 * Adds a url to a keyword where needed.
1884 *
1885 * @param string The keyword to add the URL HTML to
1886 * @param int What group the keyword is from
1887 * @param boolean Whether to get the HTML for the start or end
1888 * @return The HTML for either the start or end of the HTML <a> tag
1889 * @since 1.0.2
1890 * @access private
1891 * @todo Get rid of ender
1892 */
1893 function add_url_to_keyword ($keyword, $group, $start_or_end)
1894 {
1895 if (isset($this->language_data['URLS'][$group]) &&
1896 $this->language_data['URLS'][$group] != '' &&
1897 substr($keyword, 0, 5) != '</') {
1898 // There is a base group for this keyword
1899 if ($start_or_end == 'BEGIN') {
1900 // HTML workaround... not good form (tm) but should work for 1.0.X
1901 $keyword = ( substr($keyword, 0, 4) == '<' ) ? substr($keyword, 4) : $keyword;
1902 $keyword = ( substr($keyword, -4) == '>' ) ? substr($keyword, 0, strlen($keyword) - 4) : $keyword;
1903 if ($keyword != '') {
1904 $keyword = ( $this->language_data['CASE_SENSITIVE'][$group] ) ? $keyword : strtolower($keyword);
1905 return '<|UR1|"' .
1906 str_replace(
1907 array('{FNAME}', '.'),
1908 array(@htmlspecialchars($keyword, ENT_COMPAT, $this->encoding), '<DOT>'),
1909 $this->language_data['URLS'][$group]
1910 ) . '">';
1911 }
1912 return '';
1913 } else {
1914 return '</a>';
1915 }
1916 }
1917 }
1918
1919 /**
1920 * Takes a string that has no strings or comments in it, and highlights
1921 * stuff like keywords, numbers and methods.
1922 *
1923 * @param string The string to parse for keyword, numbers etc.
1924 * @since 1.0.0
1925 * @access private
1926 * @todo BUGGY! Why? Why not build string and return?
1927 */
1928 function parse_non_string_part (&$stuff_to_parse)
1929 {
1930 $stuff_to_parse = ' ' . quotemeta(@htmlspecialchars($stuff_to_parse, ENT_COMPAT, $this->encoding));
1931 // These vars will disappear in the future
1932 $func = '$this->change_case';
1933 $func2 = '$this->add_url_to_keyword';
1934
1935 //
1936 // Regular expressions
1937 //
1938 foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
1939 if ($this->lexic_permissions['REGEXPS'][$key]) {
1940 if (is_array($regexp)) {
1941 $stuff_to_parse = preg_replace(
1942 "#" .
1943 $regexp[GESHI_SEARCH] .
1944 "#{$regexp[GESHI_MODIFIERS]}",
1945 "{$regexp[GESHI_BEFORE]}<|!REG3XP$key!>{$regexp[GESHI_REPLACE]}|>{$regexp[GESHI_AFTER]}",
1946 $stuff_to_parse
1947 );
1948 } else {
1949 $stuff_to_parse = preg_replace( "#(" . $regexp . ")#", "<|!REG3XP$key!>\\1|>", $stuff_to_parse);
1950 }
1951 }
1952 }
1953
1954 //
1955 // Highlight numbers. This regexp sucks... anyone with a regexp that WORKS
1956 // here wins a cookie if they send it to me. At the moment there's two doing
1957 // almost exactly the same thing, except the second one prevents a number
1958 // being highlighted twice (eg <span...><span...>5</span></span>)
1959 // Put /NUM!/ in for the styles, which gets replaced at the end.
1960 //
1961 if ($this->lexic_permissions['NUMBERS'] && preg_match('#[0-9]#', $stuff_to_parse )) {
1962 $stuff_to_parse = preg_replace('#([^a-zA-Z0-9\#])([0-9]+)([^a-zA-Z0-9])#', "\\1<|/NUM!/>\\2|>\\3", $stuff_to_parse);
1963 $stuff_to_parse = preg_replace('#([^a-zA-Z0-9\#>])([0-9]+)([^a-zA-Z0-9])#', "\\1<|/NUM!/>\\2|>\\3", $stuff_to_parse);
1964 }
1965
1966 // Highlight keywords
1967 // if there is a couple of alpha symbols there *might* be a keyword
1968 if (preg_match('#[a-zA-Z]{2,}#', $stuff_to_parse)) {
1969 foreach ($this->language_data['KEYWORDS'] as $k => $keywordset) {
1970 if ($this->lexic_permissions['KEYWORDS'][$k]) {
1971 foreach ($keywordset as $keyword) {
1972 $keyword = quotemeta($keyword);
1973 //
1974 // This replacement checks the word is on it's own (except if brackets etc
1975 // are next to it), then highlights it. We don't put the color=" for the span
1976 // in just yet - otherwise languages with the keywords "color" or "or" have
1977 // a fit.
1978 //
1979 if (false !== stristr($stuff_to_parse, $keyword )) {
1980 $stuff_to_parse .= ' ';
1981 // Might make a more unique string for putting the number in soon
1982 // Basically, we don't put the styles in yet because then the styles themselves will
1983 // get highlighted if the language has a CSS keyword in it (like CSS, for example ;))
1984 $styles = "/$k/";
1985 $keyword = quotemeta($keyword);
1986 if ($this->language_data['CASE_SENSITIVE'][$k]) {
1987 $stuff_to_parse = preg_replace(
1988 "#([^a-zA-Z0-9\$_\|\#;>])($keyword)([^a-zA-Z0-9_<\|%\-&])#e",
1989 "'\\1' . $func2('\\2', '$k', 'BEGIN') . '<|$styles>' . $func('\\2') . '|>' . $func2('\\2', '$k', 'END') . '\\3'",
1990 $stuff_to_parse
1991 );
1992 } else {
1993 // Change the case of the word.
1994 $stuff_to_parse = preg_replace(
1995 "#([^a-zA-Z0-9\$_\|\#;>])($keyword)([^a-zA-Z0-9_<\|%\-&])#ie",
1996 "'\\1' . $func2('\\2', '$k', 'BEGIN') . '<|$styles>' . $func('\\2') . '|>' . $func2('\\2', '$k', 'END') . '\\3'",
1997 $stuff_to_parse
1998 );
1999 }
2000 $stuff_to_parse = substr($stuff_to_parse, 0, strlen($stuff_to_parse) - 1);
2001 }
2002 }
2003 }
2004 }
2005 }
2006
2007 //
2008 // Now that's all done, replace /[number]/ with the correct styles
2009 //
2010 foreach ($this->language_data['KEYWORDS'] as $k => $kws) {
2011 if (!$this->use_classes) {
2012 $attributes = ' style="' . $this->language_data['STYLES']['KEYWORDS'][$k] . '"';
2013 } else {
2014 $attributes = ' class="kw' . $k . '"';
2015 }
2016 $stuff_to_parse = str_replace("/$k/", $attributes, $stuff_to_parse);
2017 }
2018
2019 // Put number styles in
2020 if (!$this->use_classes && $this->lexic_permissions['NUMBERS']) {
2021 $attributes = ' style="' . $this->language_data['STYLES']['NUMBERS'][0] . '"';
2022 } else {
2023 $attributes = ' class="nu0"';
2024 }
2025 $stuff_to_parse = str_replace('/NUM!/', $attributes, $stuff_to_parse);
2026
2027 //
2028 // Highlight methods and fields in objects
2029 //
2030 if ($this->lexic_permissions['METHODS'] && $this->language_data['OOLANG']) {
2031 foreach ($this->language_data['OBJECT_SPLITTERS'] as $key => $splitter) {
2032 if (false !== stristr($stuff_to_parse, $splitter)) {
2033 if (!$this->use_classes) {
2034 $attributes = ' style="' . $this->language_data['STYLES']['METHODS'][$key] . '"';
2035 } else {
2036 $attributes = ' class="me' . $key . '"';
2037 }
2038 $stuff_to_parse = preg_replace("#(" . quotemeta($this->language_data['OBJECT_SPLITTERS'][$key]) . "[\s]*)([a-zA-Z\*\(][a-zA-Z0-9_\*]*)#", "\\1<|$attributes>\\2|>", $stuff_to_parse);
2039 }
2040 }
2041 }
2042
2043 //
2044 // Highlight brackets. Yes, I've tried adding a semi-colon to this list.
2045 // You try it, and see what happens ;)
2046 // TODO: Fix lexic permissions not converting entities if shouldn't
2047 // be highlighting regardless
2048 //
2049 if ($this->lexic_permissions['BRACKETS']) {
2050 $code_entities_match = array('[', ']', '(', ')', '{', '}');
2051 if (!$this->use_classes) {
2052 $code_entities_replace = array(
2053 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">[|>',
2054 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">]|>',
2055 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">(|>',
2056 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">)|>',
2057 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">{|>',
2058 '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">}|>',
2059 );
2060 } else {
2061 $code_entities_replace = array(
2062 '<| class="br0">[|>',
2063 '<| class="br0">]|>',
2064 '<| class="br0">(|>',
2065 '<| class="br0">)|>',
2066 '<| class="br0">{|>',
2067 '<| class="br0">}|>',
2068 );
2069 }
2070 $stuff_to_parse = str_replace( $code_entities_match, $code_entities_replace, $stuff_to_parse );
2071 }
2072
2073 //
2074 // Add class/style for regexps
2075 //
2076 foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
2077 if ($this->lexic_permissions['REGEXPS'][$key]) {
2078 if (!$this->use_classes) {
2079 $attributes = ' style="' . $this->language_data['STYLES']['REGEXPS'][$key] . '"';
2080 } else {
2081 $attributes = ' class="re' . $key . '"';
2082 }
2083 $stuff_to_parse = str_replace("!REG3XP$key!", "$attributes", $stuff_to_parse);
2084 }
2085 }
2086
2087 // Replace <DOT> with . for urls
2088 $stuff_to_parse = str_replace('<DOT>', '.', $stuff_to_parse);
2089 // Replace <|UR1| with <a href= for urls also
2090 if (isset($this->link_styles[GESHI_LINK])) {
2091 if ($this->use_classes) {
2092 $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
2093 } else {
2094 $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' style="' . $this->link_styles[GESHI_LINK] . '" href=', $stuff_to_parse);
2095 }
2096 } else {
2097 $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
2098 }
2099
2100 //
2101 // NOW we add the span thingy ;)
2102 //
2103
2104 $stuff_to_parse = str_replace('<|', '<span', $stuff_to_parse);
2105 $stuff_to_parse = str_replace ( '|>', '</span>', $stuff_to_parse );
2106
2107 return substr(stripslashes($stuff_to_parse), 1);
2108 }
2109
2110 /**
2111 * Sets the time taken to parse the code
2112 *
2113 * @param microtime The time when parsing started
2114 * @param microtime The time when parsing ended
2115 * @since 1.0.2
2116 * @access private
2117 */
2118 function set_time ($start_time, $end_time)
2119 {
2120 $start = explode(' ', $start_time);
2121 $end = explode(' ', $end_time);
2122 $this->time = $end[0] + $end[1] - $start[0] - $start[1];
2123 }
2124
2125 /**
2126 * Gets the time taken to parse the code
2127 *
2128 * @return double The time taken to parse the code
2129 * @since 1.0.2
2130 */
2131 function get_time ()
2132 {
2133 return $this->time;
2134 }
2135
2136 /**
2137 * Gets language information and stores it for later use
2138 *
2139 * @access private
2140 * @todo Needs to load keys for lexic permissions for keywords, regexps etc
2141 */
2142 function load_language ($file_name)
2143 {
2144 $language_data = array();
2145 require $file_name;
2146 // Perhaps some checking might be added here later to check that
2147 // $language data is a valid thing but maybe not
2148 $this->language_data = $language_data;
2149 // Set strict mode if should be set
2150 if ($this->language_data['STRICT_MODE_APPLIES'] == GESHI_ALWAYS) {
2151 $this->strict_mode = true;
2152 }
2153 // Set permissions for all lexics to true
2154 // so they'll be highlighted by default
2155 foreach ($this->language_data['KEYWORDS'] as $key => $words) {
2156 $this->lexic_permissions['KEYWORDS'][$key] = true;
2157 }
2158 foreach ($this->language_data['COMMENT_SINGLE'] as $key => $comment) {
2159 $this->lexic_permissions['COMMENTS'][$key] = true;
2160 }
2161 foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
2162 $this->lexic_permissions['REGEXPS'][$key] = true;
2163 }
2164 $this->enable_highlighting();
2165 // Set default class for CSS
2166 $this->overall_class = $this->language;
2167 }
2168
2169 /**
2170 * Takes the parsed code and various options, and creates the HTML
2171 * surrounding it to make it look nice.
2172 *
2173 * @param string The code already parsed
2174 * @return string The code nicely finalised
2175 * @since 1.0.0
2176 * @access private
2177 */
2178 function finalise ($parsed_code)
2179 {
2180 // Remove end parts of important declarations
2181 // This is BUGGY!! My fault for bad code: fix coming in 1.2
2182 // @todo Remove this crap
2183 if ($this->enable_important_blocks &&
2184 (strstr($parsed_code, @htmlspecialchars(GESHI_START_IMPORTANT, ENT_COMPAT, $this->encoding)) === false)) {
2185 $parsed_code = str_replace(@htmlspecialchars(GESHI_END_IMPORTANT, ENT_COMPAT, $this->encoding), '', $parsed_code);
2186 }
2187
2188 // Add HTML whitespace stuff if we're using the <div> header
2189 if ($this->header_type != GESHI_HEADER_PRE) {
2190 $parsed_code = $this->indent($parsed_code);
2191 }
2192
2193 // If we're using line numbers, we insert <li>s and appropriate
2194 // markup to style them (otherwise we don't need to do anything)
2195 if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
2196 // If we're using the <pre> header, we shouldn't add newlines because
2197 // the <pre> will line-break them (and the <li>s already do this for us)
2198 $ls = ($this->header_type != GESHI_HEADER_PRE) ? "\n" : '';
2199 // Get code into lines
2200 $code = explode("\n", $parsed_code);
2201 // Set vars to defaults for following loop
2202 $parsed_code = '';
2203 $i = 0;
2204 // Foreach line...
2205 foreach ($code as $line) {
2206 $line = ( $line ) ? $line : ' ';
2207 // If this is a "special line"...
2208 if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS &&
2209 $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
2210 // Set the attributes to style the line
2211 if ($this->use_classes) {
2212 $attr = ' class="li2"';
2213 $def_attr = ' class="de2"';
2214 } else {
2215 $attr = ' style="' . $this->line_style2 . '"';
2216 // This style "covers up" the special styles set for special lines
2217 // so that styles applied to special lines don't apply to the actual
2218 // code on that line
2219 $def_attr = ' style="' . $this->code_style . '"';
2220 }
2221 // Span or div?
2222 $start = "<div$def_attr>";
2223 $end = '</div>';
2224 } else {
2225 if ($this->use_classes) {
2226 $attr = ' class="li1"';
2227 $def_attr = ' class="de1"';
2228 } else {
2229 $attr = ' style="' . $this->line_style1 . '"';
2230 $def_attr = ' style="' . $this->code_style . '"';
2231 }
2232 $start = "<div$def_attr>";
2233 $end = '</div>';
2234 }
2235
2236 ++$i;
2237 // Are we supposed to use ids? If so, add them
2238 if ($this->add_ids) {
2239 $attr .= " id=\"{$this->overall_id}-{$i}\"";
2240 }
2241 if ($this->use_classes && in_array($i, $this->highlight_extra_lines)) {
2242 $attr .= " class=\"ln-xtra\"";
2243 }
2244 if (!$this->use_classes && in_array($i, $this->highlight_extra_lines)) {
2245 $attr .= " style=\"{$this->highlight_extra_lines_style}\"";
2246 }
2247
2248 // Add in the line surrounded by appropriate list HTML
2249 $parsed_code .= "<li$attr>$start$line$end</li>$ls";
2250 }
2251 } else {
2252 // No line numbers, but still need to handle highlighting lines extra.
2253 // Have to use divs so the full width of the code is highlighted
2254 $code = explode("\n", $parsed_code);
2255 $parsed_code = '';
2256 $i = 0;
2257 foreach ($code as $line)
2258 {
2259 // Make lines have at least one space in them if they're empty
2260 $line = ($line) ? $line : ' ';
2261 if (in_array(++$i, $this->highlight_extra_lines)) {
2262 if ($this->use_classes) {
2263 $parsed_code .= '<div class="ln-xtra">';
2264 } else {
2265 $parsed_code .= "<div style=\"{$this->highlight_extra_lines_style}\">";
2266 }
2267 $parsed_code .= $line . "</div>\n";
2268 } else {
2269 $parsed_code .= $line . "\n";
2270 }
2271 }
2272 }
2273
2274 // purge some unnecessary stuff
2275 $parsed_code = preg_replace('#<span[^>]+>(\s*)</span>#', '\\1', $parsed_code);
2276 $parsed_code = preg_replace('#<div[^>]+>(\s*)</div>#', '\\1', $parsed_code);
2277
2278 if ($this->header_type == GESHI_HEADER_PRE) {
2279 // enforce line numbers when using pre
2280 $parsed_code = str_replace('<li></li>', '<li> </li>', $parsed_code);
2281 }
2282
2283 return $this->header() . chop($parsed_code) . $this->footer();
2284 }
2285
2286 /**
2287 * Creates the header for the code block (with correct attributes)
2288 *
2289 * @return string The header for the code block
2290 * @since 1.0.0
2291 * @access private
2292 */
2293 function header ()
2294 {
2295 // Get attributes needed
2296 $attributes = $this->get_attributes();
2297
2298 $ol_attributes = '';
2299
2300 if ($this->line_numbers_start != 1) {
2301 $ol_attributes .= ' start="' . $this->line_numbers_start . '"';
2302 }
2303
2304 // Get the header HTML
2305 $header = $this->format_header_content();
2306
2307 if (GESHI_HEADER_NONE == $this->header_type) {
2308 return "$header<ol$ol_attributes>";
2309 }
2310
2311 // Work out what to return and do it
2312 if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
2313 if ($this->header_type == GESHI_HEADER_PRE) {
2314 return "<pre$attributes>$header<ol$ol_attributes>";
2315 } elseif ($this->header_type == GESHI_HEADER_DIV) {
2316 return "<div$attributes>$header<ol$ol_attributes>";
2317 }
2318 } else {
2319 if ($this->header_type == GESHI_HEADER_PRE) {
2320 return "<pre$attributes>$header";
2321 } elseif ($this->header_type == GESHI_HEADER_DIV) {
2322 return "<div$attributes>$header";
2323 }
2324 }
2325 }
2326
2327 /**
2328 * Returns the header content, formatted for output
2329 *
2330 * @return string The header content, formatted for output
2331 * @since 1.0.2
2332 * @access private
2333 */
2334 function format_header_content ()
2335 {
2336 $header = $this->header_content;
2337 if ($header) {
2338 if ($this->header_type == GESHI_HEADER_PRE) {
2339 $header = str_replace("\n", '', $header);
2340 }
2341 $header = $this->replace_keywords($header);
2342
2343 if ($this->use_classes) {
2344 $attr = ' class="head"';
2345 } else {
2346 $attr = " style=\"{$this->header_content_style}\"";
2347 }
2348 return "<div$attr>$header</div>";
2349 }
2350 }
2351
2352 /**
2353 * Returns the footer for the code block.
2354 *
2355 * @return string The footer for the code block
2356 * @since 1.0.0
2357 * @access private
2358 */
2359 function footer ()
2360 {
2361 $footer_content = $this->format_footer_content();
2362
2363 if (GESHI_HEADER_NONE == $this->header_type) {
2364 return ($this->line_numbers != GESHI_NO_LINE_NUMBERS) ? '</ol>' . $footer_content
2365 : $footer_content;
2366 }
2367
2368 if ($this->header_type == GESHI_HEADER_DIV) {
2369 if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
2370 return "</ol>$footer_content</div>";
2371 }
2372 return "$footer_content</div>";
2373 } else {
2374 if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
2375 return "</ol>$footer_content</pre>";
2376 }
2377 return "$footer_content</pre>";
2378 }
2379 }
2380
2381 /**
2382 * Returns the footer content, formatted for output
2383 *
2384 * @return string The footer content, formatted for output
2385 * @since 1.0.2
2386 * @access private
2387 */
2388 function format_footer_content ()
2389 {
2390 $footer = $this->footer_content;
2391 if ($footer) {
2392 if ($this->header_type == GESHI_HEADER_PRE) {
2393 $footer = str_replace("\n", '', $footer);;
2394 }
2395 $footer = $this->replace_keywords($footer);
2396
2397 if ($this->use_classes) {
2398 $attr = ' class="foot"';
2399 } else {
2400 $attr = " style=\"{$this->footer_content_style}\">";
2401 }
2402 return "<div$attr>$footer</div>";
2403 }
2404 }
2405
2406 /**
2407 * Replaces certain keywords in the header and footer with
2408 * certain configuration values
2409 *
2410 * @param string The header or footer content to do replacement on
2411 * @return string The header or footer with replaced keywords
2412 * @since 1.0.2
2413 * @access private
2414 */
2415 function replace_keywords ($instr)
2416 {
2417 $keywords = $replacements = array();
2418
2419 $keywords[] = '<TIME>';
2420 $replacements[] = number_format($this->get_time(), 3);
2421
2422 $keywords[] = '<LANGUAGE>';
2423 $replacements[] = $this->language;
2424
2425 $keywords[] = '<VERSION>';
2426 $replacements[] = GESHI_VERSION;
2427
2428 return str_replace($keywords, $replacements, $instr);
2429 }
2430
2431 /**
2432 * Gets the CSS attributes for this code
2433 *
2434 * @return The CSS attributes for this code
2435 * @since 1.0.0
2436 * @access private
2437 * @todo Document behaviour change - class is outputted regardless of whether we're using classes or not.
2438 * Same with style
2439 */
2440 function get_attributes ()
2441 {
2442 $attributes = '';
2443
2444 if ($this->overall_class != '') {
2445 $attributes .= " class=\"{$this->overall_class}\"";
2446 }
2447 if ($this->overall_id != '') {
2448 $attributes .= " id=\"{$this->overall_id}\"";
2449 }
2450 if ($this->overall_style != '') {
2451 $attributes .= ' style="' . $this->overall_style . '"';
2452 }
2453 return $attributes;
2454 }
2455
2456 /**
2457 * Returns a stylesheet for the highlighted code. If $economy mode
2458 * is true, we only return the stylesheet declarations that matter for
2459 * this code block instead of the whole thing
2460 *
2461 * @param boolean Whether to use economy mode or not
2462 * @return string A stylesheet built on the data for the current language
2463 * @since 1.0.0
2464 */
2465 function get_stylesheet ($economy_mode = true)
2466 {
2467 // If there's an error, chances are that the language file
2468 // won't have populated the language data file, so we can't
2469 // risk getting a stylesheet...
2470 if ($this->error) {
2471 return '';
2472 }
2473 // First, work out what the selector should be. If there's an ID,
2474 // that should be used, the same for a class. Otherwise, a selector
2475 // of '' means that these styles will be applied anywhere
2476 $selector = ($this->overall_id != '') ? "#{$this->overall_id} " : '';
2477 $selector = ($selector == '' && $this->overall_class != '') ? ".{$this->overall_class} " : $selector;
2478
2479 // Header of the stylesheet
2480 if (!$economy_mode) {
2481 $stylesheet = "/**\n * GeSHi Dynamically Generated Stylesheet\n * --------------------------------------\n * Dynamically generated stylesheet for {$this->language}\n * CSS class: {$this->overall_class}, CSS id: {$this->overall_id}\n * GeSHi (c) Nigel McNie 2004 (http://qbnz.com/highlighter)\n */\n";
2482 } else {
2483 $stylesheet = '/* GeSHi (c) Nigel McNie 2004 (http://qbnz.com/highlighter) */' . "\n";
2484 }
2485
2486 // Set the <ol> to have no effect at all if there are line numbers
2487 // (<ol>s have margins that should be destroyed so all layout is
2488 // controlled by the set_overall_style method, which works on the
2489 // <pre> or <div> container). Additionally, set default styles for lines
2490 if (!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
2491 //$stylesheet .= "$selector, {$selector}ol, {$selector}ol li {margin: 0;}\n";
2492 $stylesheet .= "$selector.de1, $selector.de2 {{$this->code_style}}\n";
2493 }
2494
2495 // Add overall styles
2496 if (!$economy_mode || $this->overall_style != '') {
2497 $stylesheet .= "$selector {{$this->overall_style}}\n";
2498 }
2499
2500 // Add styles for links
2501 foreach ($this->link_styles as $key => $style) {
2502 if (!$economy_mode || $key == GESHI_LINK && $style != '') {
2503 $stylesheet .= "{$selector}a:link {{$style}}\n";
2504 }
2505 if (!$economy_mode || $key == GESHI_HOVER && $style != '') {
2506 $stylesheet .= "{$selector}a:hover {{$style}}\n";
2507 }
2508 if (!$economy_mode || $key == GESHI_ACTIVE && $style != '') {
2509 $stylesheet .= "{$selector}a:active {{$style}}\n";
2510 }
2511 if (!$economy_mode || $key == GESHI_VISITED && $style != '') {
2512 $stylesheet .= "{$selector}a:visited {{$style}}\n";
2513 }
2514 }
2515
2516 // Header and footer
2517 if (!$economy_mode || $this->header_content_style != '') {
2518 $stylesheet .= "$selector.head {{$this->header_content_style}}\n";
2519 }
2520 if (!$economy_mode || $this->footer_content_style != '') {
2521 $stylesheet .= "$selector.foot {{$this->footer_content_style}}\n";
2522 }
2523
2524 // Styles for important stuff
2525 if (!$economy_mode || $this->important_styles != '') {
2526 $stylesheet .= "$selector.imp {{$this->important_styles}}\n";
2527 }
2528
2529 // Styles for lines being highlighted extra
2530 if (!$economy_mode || count($this->highlight_extra_lines)) {
2531 $stylesheet .= "$selector.ln-xtra {{$this->highlight_extra_lines_style}}\n";
2532 }
2533
2534 // Simple line number styles
2535 if (!$economy_mode || ($this->line_numbers != GESHI_NO_LINE_NUMBERS && $this->line_style1 != '')) {
2536 $stylesheet .= "{$selector}li {{$this->line_style1}}\n";
2537 }
2538
2539 // If there is a style set for fancy line numbers, echo it out
2540 if (!$economy_mode || ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS && $this->line_style2 != '')) {
2541 $stylesheet .= "{$selector}li.li2 {{$this->line_style2}}\n";
2542 }
2543
2544 foreach ($this->language_data['STYLES']['KEYWORDS'] as $group => $styles) {
2545 if (!$economy_mode || !($economy_mode && (!$this->lexic_permissions['KEYWORDS'][$group] || $styles == ''))) {
2546 $stylesheet .= "$selector.kw$group {{$styles}}\n";
2547 }
2548 }
2549 foreach ($this->language_data['STYLES']['COMMENTS'] as $group => $styles) {
2550 if (!$economy_mode || !($economy_mode && $styles == '') &&
2551 !($economy_mode && !$this->lexic_permissions['COMMENTS'][$group])) {
2552 $stylesheet .= "$selector.co$group {{$styles}}\n";
2553 }
2554 }
2555 foreach ($this->language_data['STYLES']['ESCAPE_CHAR'] as $group => $styles) {
2556 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2557 !$this->lexic_permissions['ESCAPE_CHAR'])) {
2558 $stylesheet .= "$selector.es$group {{$styles}}\n";
2559 }
2560 }
2561 foreach ($this->language_data['STYLES']['SYMBOLS'] as $group => $styles) {
2562 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2563 !$this->lexic_permissions['BRACKETS'])) {
2564 $stylesheet .= "$selector.br$group {{$styles}}\n";
2565 }
2566 }
2567 foreach ($this->language_data['STYLES']['STRINGS'] as $group => $styles) {
2568 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2569 !$this->lexic_permissions['STRINGS'])) {
2570 $stylesheet .= "$selector.st$group {{$styles}}\n";
2571 }
2572 }
2573 foreach ($this->language_data['STYLES']['NUMBERS'] as $group => $styles) {
2574 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2575 !$this->lexic_permissions['NUMBERS'])) {
2576 $stylesheet .= "$selector.nu$group {{$styles}}\n";
2577 }
2578 }
2579 foreach ($this->language_data['STYLES']['METHODS'] as $group => $styles) {
2580 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2581 !$this->lexic_permissions['METHODS'])) {
2582 $stylesheet .= "$selector.me$group {{$styles}}\n";
2583 }
2584 }
2585 foreach ($this->language_data['STYLES']['SCRIPT'] as $group => $styles) {
2586 if (!$economy_mode || !($economy_mode && $styles == '')) {
2587 $stylesheet .= "$selector.sc$group {{$styles}}\n";
2588 }
2589 }
2590 foreach ($this->language_data['STYLES']['REGEXPS'] as $group => $styles) {
2591 if (!$economy_mode || !($economy_mode && $styles == '') && !($economy_mode &&
2592 !$this->lexic_permissions['REGEXPS'][$group])) {
2593 $stylesheet .= "$selector.re$group {{$styles}}\n";
2594 }
2595 }
2596
2597 return $stylesheet;
2598 }
2599
2600 } // End Class GeSHi
2601
2602
2603 if (!function_exists('geshi_highlight')) {
2604 /**
2605 * Easy way to highlight stuff. Behaves just like highlight_string
2606 *
2607 * @param string The code to highlight
2608 * @param string The language to highlight the code in
2609 * @param string The path to the language files. You can leave this blank if you need
2610 * as from version 1.0.7 the path should be automatically detected
2611 * @param boolean Whether to return the result or to echo
2612 * @return string The code highlighted (if $return is true)
2613 * @since 1.0.2
2614 */
2615 function geshi_highlight ($string, $language, $path, $return = false)
2616 {
2617 $geshi = new GeSHi($string, $language, $path);
2618 $geshi->set_header_type(GESHI_HEADER_DIV);
2619 if ($return) {
2620 return str_replace('<div>', '<code>', str_replace('</div>', '</code>', $geshi->parse_code()));
2621 }
2622 echo str_replace('<div>', '<code>', str_replace('</div>', '</code>', $geshi->parse_code()));
2623 if ($geshi->error()) {
2624 return false;
2625 }
2626 return true;
2627 }
2628 }
2629
Documentation generated on Thu, 22 Sep 2005 13:47:59 +1200 by phpDocumentor 1.2.3