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