[ Index ]

PHP Cross Reference of Mambo 4.6.5

[ Variables ]     [ Functions ]     [ Classes ]     [ Constants ]     [ Statistics ]

title

Body

[close]

/mambots/content/geshi/ -> geshi.php (source)

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