Source for file geshi.php

Documentation is available at geshi.php

  1. <?php
  2. /**
  3.  * GeSHi - Generic Syntax Highlighter
  4.  *
  5.  * The GeSHi class for Generic Syntax Highlighting. Please refer to the
  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>, Benny Baumann <BenBE@omorphia.de>
  31.  * @copyright  (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
  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.8.1');
  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. // Define if GeSHi should be paranoid about security
  56. if (!defined('GESHI_SECURITY_PARANOID')) {
  57.     /** Tells GeSHi to be paranoid about security settings */
  58.     define('GESHI_SECURITY_PARANOID'false);
  59. }
  60.  
  61. // Line numbers - use with enable_line_numbers()
  62. /** Use no line numbers when building the result */
  63. define('GESHI_NO_LINE_NUMBERS'0);
  64. /** Use normal line numbers when building the result */
  65. define('GESHI_NORMAL_LINE_NUMBERS'1);
  66. /** Use fancy line numbers when building the result */
  67. define('GESHI_FANCY_LINE_NUMBERS'2);
  68.  
  69. // Container HTML type
  70. /** Use nothing to surround the source */
  71. define('GESHI_HEADER_NONE'0);
  72. /** Use a "div" to surround the source */
  73. define('GESHI_HEADER_DIV'1);
  74. /** Use a "pre" to surround the source */
  75. define('GESHI_HEADER_PRE'2);
  76. /** Use a pre to wrap lines when line numbers are enabled or to wrap the whole code. */
  77. define('GESHI_HEADER_PRE_VALID'3);
  78. /**
  79.  * Use a "table" to surround the source:
  80.  *
  81.  *  <table>
  82.  *    <thead><tr><td colspan="2">$header</td></tr></thead>
  83.  *    <tbody><tr><td><pre>$linenumbers</pre></td><td><pre>$code></pre></td></tr></tbody>
  84.  *    <tfooter><tr><td colspan="2">$footer</td></tr></tfoot>
  85.  *  </table>
  86.  *
  87.  * this is essentially only a workaround for Firefox, see sf#1651996 or take a look at
  88.  * https://bugzilla.mozilla.org/show_bug.cgi?id=365805
  89.  * @note when linenumbers are disabled this is essentially the same as GESHI_HEADER_PRE
  90.  */
  91. define('GESHI_HEADER_PRE_TABLE'4);
  92.  
  93. // Capatalisation constants
  94. /** Lowercase keywords found */
  95. define('GESHI_CAPS_NO_CHANGE'0);
  96. /** Uppercase keywords found */
  97. define('GESHI_CAPS_UPPER'1);
  98. /** Leave keywords found as the case that they are */
  99. define('GESHI_CAPS_LOWER'2);
  100.  
  101. // Link style constants
  102. /** Links in the source in the :link state */
  103. define('GESHI_LINK'0);
  104. /** Links in the source in the :hover state */
  105. define('GESHI_HOVER'1);
  106. /** Links in the source in the :active state */
  107. define('GESHI_ACTIVE'2);
  108. /** Links in the source in the :visited state */
  109. define('GESHI_VISITED'3);
  110.  
  111. // Important string starter/finisher
  112. // Note that if you change these, they should be as-is: i.e., don't
  113. // write them as if they had been run through htmlentities()
  114. /** The starter for important parts of the source */
  115. define('GESHI_START_IMPORTANT''<BEGIN GeSHi>');
  116. /** The ender for important parts of the source */
  117. define('GESHI_END_IMPORTANT''<END GeSHi>');
  118.  
  119. /**#@+
  120.  *  @access private
  121.  */
  122. // When strict mode applies for a language
  123. /** Strict mode never applies (this is the most common) */
  124. define('GESHI_NEVER'0);
  125. /** Strict mode *might* apply, and can be enabled or
  126.     disabled by {@link GeSHi->enable_strict_mode()} */
  127. define('GESHI_MAYBE'1);
  128. /** Strict mode always applies */
  129. define('GESHI_ALWAYS'2);
  130.  
  131. // Advanced regexp handling constants, used in language files
  132. /** The key of the regex array defining what to search for */
  133. define('GESHI_SEARCH'0);
  134. /** The key of the regex array defining what bracket group in a
  135.     matched search to use as a replacement */
  136. define('GESHI_REPLACE'1);
  137. /** The key of the regex array defining any modifiers to the regular expression */
  138. define('GESHI_MODIFIERS'2);
  139. /** The key of the regex array defining what bracket group in a
  140.     matched search to put before the replacement */
  141. define('GESHI_BEFORE'3);
  142. /** The key of the regex array defining what bracket group in a
  143.     matched search to put after the replacement */
  144. define('GESHI_AFTER'4);
  145. /** The key of the regex array defining a custom keyword to use
  146.     for this regexp's html tag class */
  147. define('GESHI_CLASS'5);
  148.  
  149. /** Used in language files to mark comments */
  150. define('GESHI_COMMENTS'0);
  151.  
  152. /** Used to work around missing PHP features **/
  153. define('GESHI_PHP_PRE_433'!(version_compare(PHP_VERSION'4.3.3'=== 1));
  154.  
  155. /** make sure we can call stripos **/
  156. if (!function_exists('stripos')) {
  157.     // the offset param of preg_match is not supported below PHP 4.3.3
  158.     if (GESHI_PHP_PRE_433{
  159.         /**
  160.          * @ignore
  161.          */
  162.         function stripos($haystack$needle$offset null{
  163.             if (!is_null($offset)) {
  164.                 $haystack substr($haystack$offset);
  165.             }
  166.             if (preg_match('/'preg_quote($needle'/''/'$haystack$matchPREG_OFFSET_CAPTURE)) {
  167.                 return $match[0][1];
  168.             }
  169.             return false;
  170.         }
  171.     }
  172.     else {
  173.         /**
  174.          * @ignore
  175.          */
  176.         function stripos($haystack$needle$offset null{
  177.             if (preg_match('/'preg_quote($needle'/''/'$haystack$matchPREG_OFFSET_CAPTURE$offset)) {
  178.                 return $match[0][1];
  179.             }
  180.             return false;
  181.         }
  182.     }
  183. }
  184.  
  185. /** some old PHP / PCRE subpatterns only support up to xxx subpatterns in
  186.     regular expressions. Set this to false if your PCRE lib is up to date
  187.     @see GeSHi->optimize_regexp_list()
  188.     **/
  189.  
  190. define('GESHI_MAX_PCRE_SUBPATTERNS'500);
  191. /** it's also important not to generate too long regular expressions
  192.     be generous here... but keep in mind, that when reaching this limit we
  193.     still have to close open patterns. 12k should do just fine on a 16k limit.
  194.     @see GeSHi->optimize_regexp_list()
  195.     **/
  196.  
  197. define('GESHI_MAX_PCRE_LENGTH'12288);
  198.  
  199. //Number format specification
  200. /** Basic number format for integers */
  201. define('GESHI_NUMBER_INT_BASIC'1);        //Default integers \d+
  202. /** Enhanced number format for integers like seen in C */
  203. define('GESHI_NUMBER_INT_CSTYLE'2);       //Default C-Style \d+[lL]?
  204. /** Number format to highlight binary numbers with a suffix "b" */
  205. define('GESHI_NUMBER_BIN_SUFFIX'16);           //[01]+[bB]
  206. /** Number format to highlight binary numbers with a prefix % */
  207. define('GESHI_NUMBER_BIN_PREFIX_PERCENT'32);   //%[01]+
  208. /** Number format to highlight binary numbers with a prefix 0b (C) */
  209. define('GESHI_NUMBER_BIN_PREFIX_0B'64);        //0b[01]+
  210. /** Number format to highlight octal numbers with a leading zero */
  211. define('GESHI_NUMBER_OCT_PREFIX'256);           //0[0-7]+
  212. /** Number format to highlight octal numbers with a suffix of o */
  213. define('GESHI_NUMBER_OCT_SUFFIX'512);           //[0-7]+[oO]
  214. /** Number format to highlight hex numbers with a prefix 0x */
  215. define('GESHI_NUMBER_HEX_PREFIX'4096);           //0x[0-9a-fA-F]+
  216. /** Number format to highlight hex numbers with a suffix of h */
  217. define('GESHI_NUMBER_HEX_SUFFIX'8192);           //[0-9][0-9a-fA-F]*h
  218. /** Number format to highlight floating-point numbers without support for scientific notation */
  219. define('GESHI_NUMBER_FLT_NONSCI'65536);          //\d+\.\d+
  220. /** Number format to highlight floating-point numbers without support for scientific notation */
  221. define('GESHI_NUMBER_FLT_NONSCI_F'131072);       //\d+(\.\d+)?f
  222. /** Number format to highlight floating-point numbers with support for scientific notation (E) and optional leading zero */
  223. define('GESHI_NUMBER_FLT_SCI_SHORT'262144);      //\.\d+e\d+
  224. /** Number format to highlight floating-point numbers with support for scientific notation (E) and required leading digit */
  225. define('GESHI_NUMBER_FLT_SCI_ZERO'524288);       //\d+(\.\d+)?e\d+
  226. //Custom formats are passed by RX array
  227.  
  228. // Error detection - use these to analyse faults
  229. /** No sourcecode to highlight was specified
  230.  * @deprecated
  231.  */
  232. define('GESHI_ERROR_NO_INPUT'1);
  233. /** The language specified does not exist */
  234. define('GESHI_ERROR_NO_SUCH_LANG'2);
  235. /** GeSHi could not open a file for reading (generally a language file) */
  236. define('GESHI_ERROR_FILE_NOT_READABLE'3);
  237. /** The header type passed to {@link GeSHi->set_header_type()} was invalid */
  238. define('GESHI_ERROR_INVALID_HEADER_TYPE'4);
  239. /** The line number type passed to {@link GeSHi->enable_line_numbers()} was invalid */
  240. define('GESHI_ERROR_INVALID_LINE_NUMBER_TYPE'5);
  241. /**#@-*/
  242.  
  243.  
  244. /**
  245.  * The GeSHi Class.
  246.  *
  247.  * Please refer to the documentation for GeSHi 1.0.X that is available
  248.  * at http://qbnz.com/highlighter/documentation.php for more information
  249.  * about how to use this class.
  250.  *
  251.  * @package   geshi
  252.  * @author    Nigel McNie <nigel@geshi.org>, Benny Baumann <BenBE@omorphia.de>
  253.  * @copyright (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
  254.  */
  255. class GeSHi {
  256.     /**#@+
  257.      * @access private
  258.      */
  259.     /**
  260.      * The source code to highlight
  261.      * @var string 
  262.      */
  263.     var $source '';
  264.  
  265.     /**
  266.      * The language to use when highlighting
  267.      * @var string 
  268.      */
  269.     var $language '';
  270.  
  271.     /**
  272.      * The data for the language used
  273.      * @var array 
  274.      */
  275.     var $language_data array();
  276.  
  277.     /**
  278.      * The path to the language files
  279.      * @var string 
  280.      */
  281.     var $language_path GESHI_LANG_ROOT;
  282.  
  283.     /**
  284.      * The error message associated with an error
  285.      * @var string 
  286.      * @todo check err reporting works
  287.      */
  288.     var $error false;
  289.  
  290.     /**
  291.      * Possible error messages
  292.      * @var array 
  293.      */
  294.     var $error_messages array(
  295.         GESHI_ERROR_NO_SUCH_LANG => 'GeSHi could not find the language {LANGUAGE} (using path {PATH})',
  296.         GESHI_ERROR_FILE_NOT_READABLE => 'The file specified for load_from_file was not readable',
  297.         GESHI_ERROR_INVALID_HEADER_TYPE => 'The header type specified is invalid',
  298.         GESHI_ERROR_INVALID_LINE_NUMBER_TYPE => 'The line number type specified is invalid'
  299.     );
  300.  
  301.     /**
  302.      * Whether highlighting is strict or not
  303.      * @var boolean 
  304.      */
  305.     var $strict_mode false;
  306.  
  307.     /**
  308.      * Whether to use CSS classes in output
  309.      * @var boolean 
  310.      */
  311.     var $use_classes false;
  312.  
  313.     /**
  314.      * The type of header to use. Can be one of the following
  315.      * values:
  316.      *
  317.      * - GESHI_HEADER_PRE: Source is outputted in a "pre" HTML element.
  318.      * - GESHI_HEADER_DIV: Source is outputted in a "div" HTML element.
  319.      * - GESHI_HEADER_NONE: No header is outputted.
  320.      *
  321.      * @var int 
  322.      */
  323.     var $header_type GESHI_HEADER_PRE;
  324.  
  325.     /**
  326.      * Array of permissions for which lexics should be highlighted
  327.      * @var array 
  328.      */
  329.     var $lexic_permissions array(
  330.         'KEYWORDS' =>    array(),
  331.         'COMMENTS' =>    array('MULTI' => true),
  332.         'REGEXPS' =>     array(),
  333.         'ESCAPE_CHAR' => true,
  334.         'BRACKETS' =>    true,
  335.         'SYMBOLS' =>     false,
  336.         'STRINGS' =>     true,
  337.         'NUMBERS' =>     true,
  338.         'METHODS' =>     true,
  339.         'SCRIPT' =>      true
  340.     );
  341.  
  342.     /**
  343.      * The time it took to parse the code
  344.      * @var double 
  345.      */
  346.     var $time 0;
  347.  
  348.     /**
  349.      * The content of the header block
  350.      * @var string 
  351.      */
  352.     var $header_content '';
  353.  
  354.     /**
  355.      * The content of the footer block
  356.      * @var string 
  357.      */
  358.     var $footer_content '';
  359.  
  360.     /**
  361.      * The style of the header block
  362.      * @var string 
  363.      */
  364.     var $header_content_style '';
  365.  
  366.     /**
  367.      * The style of the footer block
  368.      * @var string 
  369.      */
  370.     var $footer_content_style '';
  371.  
  372.     /**
  373.      * Tells if a block around the highlighted source should be forced
  374.      * if not using line numbering
  375.      * @var boolean 
  376.      */
  377.     var $force_code_block false;
  378.  
  379.     /**
  380.      * The styles for hyperlinks in the code
  381.      * @var array 
  382.      */
  383.     var $link_styles array();
  384.  
  385.     /**
  386.      * Whether important blocks should be recognised or not
  387.      * @var boolean 
  388.      * @deprecated
  389.      * @todo REMOVE THIS FUNCTIONALITY!
  390.      */
  391.     var $enable_important_blocks false;
  392.  
  393.     /**
  394.      * Styles for important parts of the code
  395.      * @var string 
  396.      * @deprecated
  397.      * @todo As above - rethink the whole idea of important blocks as it is buggy and
  398.      *  will be hard to implement in 1.2
  399.      */
  400.     var $important_styles 'font-weight: bold; color: red;'// Styles for important parts of the code
  401.  
  402.     /**
  403.      * Whether CSS IDs should be added to the code
  404.      * @var boolean 
  405.      */
  406.     var $add_ids false;
  407.  
  408.     /**
  409.      * Lines that should be highlighted extra
  410.      * @var array 
  411.      */
  412.     var $highlight_extra_lines array();
  413.  
  414.     /**
  415.      * Styles of lines that should be highlighted extra
  416.      * @var array 
  417.      */
  418.     var $highlight_extra_lines_styles array();
  419.  
  420.     /**
  421.      * Styles of extra-highlighted lines
  422.      * @var string 
  423.      */
  424.     var $highlight_extra_lines_style 'background-color: #ffc;';
  425.  
  426.     /**
  427.      * The line ending
  428.      * If null, nl2br() will be used on the result string.
  429.      * Otherwise, all instances of \n will be replaced with $line_ending
  430.      * @var string 
  431.      */
  432.     var $line_ending null;
  433.  
  434.     /**
  435.      * Number at which line numbers should start at
  436.      * @var int 
  437.      */
  438.     var $line_numbers_start 1;
  439.  
  440.     /**
  441.      * The overall style for this code block
  442.      * @var string 
  443.      */
  444.     var $overall_style 'font-family:monospace;';
  445.  
  446.     /**
  447.      *  The style for the actual code
  448.      * @var string 
  449.      */
  450.     var $code_style 'font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;';
  451.  
  452.     /**
  453.      * The overall class for this code block
  454.      * @var string 
  455.      */
  456.     var $overall_class '';
  457.  
  458.     /**
  459.      * The overall ID for this code block
  460.      * @var string 
  461.      */
  462.     var $overall_id '';
  463.  
  464.     /**
  465.      * Line number styles
  466.      * @var string 
  467.      */
  468.     var $line_style1 'font-weight: normal; vertical-align:top;';
  469.  
  470.     /**
  471.      * Line number styles for fancy lines
  472.      * @var string 
  473.      */
  474.     var $line_style2 'font-weight: bold; vertical-align:top;';
  475.  
  476.     /**
  477.      * Style for line numbers when GESHI_HEADER_PRE_TABLE is chosen
  478.      * @var string 
  479.      */
  480.     var $table_linenumber_style 'width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;';
  481.  
  482.     /**
  483.      * Flag for how line numbers are displayed
  484.      * @var boolean 
  485.      */
  486.     var $line_numbers GESHI_NO_LINE_NUMBERS;
  487.  
  488.     /**
  489.      * Flag to decide if multi line spans are allowed. Set it to false to make sure
  490.      * each tag is closed before and reopened after each linefeed.
  491.      * @var boolean 
  492.      */
  493.     var $allow_multiline_span true;
  494.  
  495.     /**
  496.      * The "nth" value for fancy line highlighting
  497.      * @var int 
  498.      */
  499.     var $line_nth_row 0;
  500.  
  501.     /**
  502.      * The size of tab stops
  503.      * @var int 
  504.      */
  505.     var $tab_width 8;
  506.  
  507.     /**
  508.      * Should we use language-defined tab stop widths?
  509.      * @var int 
  510.      */
  511.     var $use_language_tab_width false;
  512.  
  513.     /**
  514.      * Default target for keyword links
  515.      * @var string 
  516.      */
  517.     var $link_target '';
  518.  
  519.     /**
  520.      * The encoding to use for entity encoding
  521.      * NOTE: Used with Escape Char Sequences to fix UTF-8 handling (cf. SF#2037598)
  522.      * @var string 
  523.      */
  524.     var $encoding 'utf-8';
  525.  
  526.     /**
  527.      * Should keywords be linked?
  528.      * @var boolean 
  529.      */
  530.     var $keyword_links true;
  531.  
  532.     /**
  533.      * Currently loaded language file
  534.      * @var string 
  535.      * @since 1.0.7.22
  536.      */
  537.     var $loaded_language '';
  538.  
  539.     /**
  540.      * Wether the caches needed for parsing are built or not
  541.      *
  542.      * @var bool 
  543.      * @since 1.0.8
  544.      */
  545.     var $parse_cache_built false;
  546.  
  547.     /**
  548.      * Work around for Suhosin Patch with disabled /e modifier
  549.      *
  550.      * Note from suhosins author in config file:
  551.      * <blockquote>
  552.      *   The /e modifier inside <code>preg_replace()</code> allows code execution.
  553.      *   Often it is the cause for remote code execution exploits. It is wise to
  554.      *   deactivate this feature and test where in the application it is used.
  555.      *   The developer using the /e modifier should be made aware that he should
  556.      *   use <code>preg_replace_callback()</code> instead
  557.      * </blockquote>
  558.      *
  559.      * @var array 
  560.      * @since 1.0.8
  561.      */
  562.     var $_kw_replace_group 0;
  563.     var $_rx_key 0;
  564.  
  565.     /**
  566.      * some "callback parameters" for handle_multiline_regexps
  567.      *
  568.      * @since 1.0.8
  569.      * @access private
  570.      * @var string 
  571.      */
  572.     var $_hmr_before '';
  573.     var $_hmr_replace '';
  574.     var $_hmr_after '';
  575.     var $_hmr_key 0;
  576.  
  577.     /**#@-*/
  578.  
  579.     /**
  580.      * Creates a new GeSHi object, with source and language
  581.      *
  582.      * @param string The source code to highlight
  583.      * @param string The language to highlight the source with
  584.      * @param string The path to the language file directory. <b>This
  585.      *                is deprecated!</b> I've backported the auto path
  586.      *                detection from the 1.1.X dev branch, so now it
  587.      *                should be automatically set correctly. If you have
  588.      *                renamed the language directory however, you will
  589.      *                still need to set the path using this parameter or
  590.      *                {@link GeSHi->set_language_path()}
  591.      * @since 1.0.0
  592.      */
  593.     function GeSHi($source ''$language ''$path ''{
  594.         if (!empty($source)) {
  595.             $this->set_source($source);
  596.         }
  597.         if (!empty($language)) {
  598.             $this->set_language($language);
  599.         }
  600.         $this->set_language_path($path);
  601.     }
  602.  
  603.     /**
  604.      * Returns an error message associated with the last GeSHi operation,
  605.      * or false if no error has occured
  606.      *
  607.      * @return string|falseAn error message if there has been an error, else false
  608.      * @since  1.0.0
  609.      */
  610.     function error({
  611.         if ($this->error{
  612.             //Put some template variables for debugging here ...
  613.             $debug_tpl_vars array(
  614.                 '{LANGUAGE}' => $this->language,
  615.                 '{PATH}' => $this->language_path
  616.             );
  617.             $msg str_replace(
  618.                 array_keys($debug_tpl_vars),
  619.                 array_values($debug_tpl_vars),
  620.                 $this->error_messages[$this->error]);
  621.  
  622.             return "<br /><strong>GeSHi Error:</strong> $msg (code {$this->error})<br />";
  623.         }
  624.         return false;
  625.     }
  626.  
  627.     /**
  628.      * Gets a human-readable language name (thanks to Simon Patterson
  629.      * for the idea :))
  630.      *
  631.      * @return string The name for the current language
  632.      * @since  1.0.2
  633.      */
  634.     function get_language_name() {
  635.         if (GESHI_ERROR_NO_SUCH_LANG == $this->error) {
  636.             return $this->language_data['LANG_NAME'] . ' (Unknown Language)';
  637.         }
  638.         return $this->language_data['LANG_NAME'];
  639.     }
  640.  
  641. /**    
  642.      * Sets the source code for this object
  643.      *
  644.      * @param string The source code to highlight
  645.      * @since 1.0.0
  646.      */
  647.     function set_source($source) {
  648.         $this->source = $source;
  649.         $this->highlight_extra_lines = array();
  650.     }
  651.  
  652. /**    
  653.      * Sets the language for this object
  654.      *
  655.      * @note since 1.0.8 this function won't reset language-settings by default anymore!
  656.      *        if you need this set $force_reset = true
  657.      *
  658.      * @param string The name of the language to use
  659.      * @since 1.0.0
  660.      */
  661.     function set_language($language, $force_reset = false) {
  662.         if ($force_reset) {
  663.             $this->loaded_language = false;
  664.         }
  665.  
  666.         //Clean up the language name to prevent malicious code injection
  667.         $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
  668.  
  669.         $language = strtolower($language);
  670.  
  671.         //Retreive the full filename
  672.         $file_name = $this->language_path . $language . '.php';
  673.         if ($file_name == $this->loaded_language) {
  674.             // this language is already loaded!
  675.             return;
  676.         }
  677.  
  678.         $this->language = $language;
  679.  
  680.         $this->error = false;
  681.         $this->strict_mode = GESHI_NEVER;
  682.  
  683.         //Check if we can read the desired file
  684.         if (!is_readable($file_name)) {
  685.             $this->error = GESHI_ERROR_NO_SUCH_LANG;
  686.             return;
  687.         }
  688.  
  689.         // Load the language for parsing
  690.         $this->load_language($file_name);
  691.     }
  692.  
  693. /**    
  694.      * Sets the path to the directory containing the language files. Note
  695.      * that this path is relative to the directory of the script that included
  696.      * geshi.php, NOT geshi.php itself.
  697.      *
  698.      * @param string The path to the language directory
  699.      * @since 1.0.0
  700.      * @deprecated The path to the language files should now be automatically
  701.      *              detected, so this method should no longer be needed. The
  702.      *              1.1.X branch handles manual setting of the path differently
  703.      *              so this method will disappear in 1.2.0.
  704.      */
  705.     function set_language_path($path) {
  706.         if(strpos($path,':')) {
  707.             //Security Fix to prevent external directories using fopen wrappers.
  708.             if(DIRECTORY_SEPARATOR == "\\") {
  709.                 if(!preg_match('#^[a-zA-Z]:#', $path) || false !== strpos($path, ':', 2)) {
  710.                     return;
  711.                 }
  712.             } else {
  713.                 return;
  714.             }
  715.         }
  716.         if(preg_match('#[^/a-zA-Z0-9_\.\-\\\s:]#', $path)) {
  717.             //Security Fix to prevent external directories using fopen wrappers.
  718.             return;
  719.         }
  720.         if(<a href="../geshi/core/_geshi.php.html#defineGESHI_SECURITY_PARANOID">GESHI_SECURITY_PARANOID</a> && false !== strpos($path, '/.')) {
  721.             //Security Fix to prevent external directories using fopen wrappers.
  722.             return;
  723.         }
  724.         if(<a href="../geshi/core/_geshi.php.html#defineGESHI_SECURITY_PARANOID">GESHI_SECURITY_PARANOID</a> && false !== strpos($path, '..')) {
  725.             //Security Fix to prevent external directories using fopen wrappers.
  726.             return;
  727.         }
  728.         if ($path) {
  729.             $this->language_path = ('/' == $path[strlen($path) - 1]) ? $path : $path . '/';
  730.             $this->set_language($this->language); // otherwise set_language_path has no effect
  731.         }
  732.     }
  733.  
  734. /**    
  735.      * Sets the type of header to be used.
  736.      *
  737.      * If GESHI_HEADER_DIV is used, the code is surrounded in a "div".This
  738.      * means more source code but more control over tab width and line-wrapping.
  739.      * GESHI_HEADER_PRE means that a "pre" is used - less source, but less
  740.      * control. Default is GESHI_HEADER_PRE.
  741.      *
  742.      * From 1.0.7.2, you can use GESHI_HEADER_NONE to specify that no header code
  743.      * should be outputted.
  744.      *
  745.      * @param int The type of header to be used
  746.      * @since 1.0.0
  747.      */
  748.     function set_header_type($type) {
  749.         //Check if we got a valid header type
  750.         if (!in_array($type, array(<a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_NONE">GESHI_HEADER_NONE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_DIV">GESHI_HEADER_DIV</a>,
  751.             <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>))) {
  752.             $this->error = GESHI_ERROR_INVALID_HEADER_TYPE;
  753.             return;
  754.         }
  755.  
  756.         //Set that new header type
  757.         $this->header_type = $type;
  758.     }
  759.  
  760. /**    
  761.      * Sets the styles for the code that will be outputted
  762.      * when this object is parsed. The style should be a
  763.      * string of valid stylesheet declarations
  764.      *
  765.      * @param string  The overall style for the outputted code block
  766.      * @param boolean Whether to merge the styles with the current styles or not
  767.      * @since 1.0.0
  768.      */
  769.     function set_overall_style($style, $preserve_defaults = false) {
  770.         if (!$preserve_defaults) {
  771.             $this->overall_style = $style;
  772.         } else {
  773.             $this->overall_style .= $style;
  774.         }
  775.     }
  776.  
  777. /**    
  778.      * Sets the overall classname for this block of code. This
  779.      * class can then be used in a stylesheet to style this object's
  780.      * output
  781.      *
  782.      * @param string The class name to use for this block of code
  783.      * @since 1.0.0
  784.      */
  785.     function set_overall_class($class) {
  786.         $this->overall_class = $class;
  787.     }
  788.  
  789. /**    
  790.      * Sets the overall id for this block of code. This id can then
  791.      * be used in a stylesheet to style this object's output
  792.      *
  793.      * @param string The ID to use for this block of code
  794.      * @since 1.0.0
  795.      */
  796.     function set_overall_id($id) {
  797.         $this->overall_id = $id;
  798.     }
  799.  
  800. /**    
  801.      * Sets whether CSS classes should be used to highlight the source. Default
  802.      * is off, calling this method with no arguments will turn it on
  803.      *
  804.      * @param boolean Whether to turn classes on or not
  805.      * @since 1.0.0
  806.      */
  807.     function enable_classes($flag = true) {
  808.         $this->use_classes = ($flag) ? true : false;
  809.     }
  810.  
  811. /**    
  812.      * Sets the style for the actual code. This should be a string
  813.      * containing valid stylesheet declarations. If $preserve_defaults is
  814.      * true, then styles are merged with the default styles, with the
  815.      * user defined styles having priority
  816.      *
  817.      * Note: Use this method to override any style changes you made to
  818.      * the line numbers if you are using line numbers, else the line of
  819.      * code will have the same style as the line number! Consult the
  820.      * GeSHi documentation for more information about this.
  821.      *
  822.      * @param string  The style to use for actual code
  823.      * @param boolean Whether to merge the current styles with the new styles
  824.      * @since 1.0.2
  825.      */
  826.     function set_code_style($style, $preserve_defaults = false) {
  827.         if (!$preserve_defaults) {
  828.             $this->code_style = $style;
  829.         } else {
  830.             $this->code_style .= $style;
  831.         }
  832.     }
  833.  
  834. /**    
  835.      * Sets the styles for the line numbers.
  836.      *
  837.      * @param string The style for the line numbers that are "normal"
  838.      * @param string|booleanIf a string, this is the style of the line
  839.      *         numbers that are "fancy", otherwise if boolean then this
  840.      *         defines whether the normal styles should be merged with the
  841.      *         new normal styles or not
  842.      * @param boolean If set, is the flag for whether to merge the "fancy"
  843.      *         styles with the current styles or not
  844.      * @since 1.0.2
  845.      */
  846.     function set_line_style($style1, $style2 = '', $preserve_defaults = false) {
  847.         //Check if we got 2 or three parameters
  848.         if (is_bool($style2)) {
  849.             $preserve_defaults = $style2;
  850.             $style2 = '';
  851.         }
  852.  
  853.         //Actually set the new styles
  854.         if (!$preserve_defaults) {
  855.             $this->line_style1 = $style1;
  856.             $this->line_style2 = $style2;
  857.         } else {
  858.             $this->line_style1 .= $style1;
  859.             $this->line_style2 .= $style2;
  860.         }
  861.     }
  862.  
  863. /**    
  864.      * Sets whether line numbers should be displayed.
  865.      *
  866.      * Valid values for the first parameter are:
  867.      *
  868.      *  - GESHI_NO_LINE_NUMBERS: Line numbers will not be displayed
  869.      *  - GESHI_NORMAL_LINE_NUMBERS: Line numbers will be displayed
  870.      *  - GESHI_FANCY_LINE_NUMBERS: Fancy line numbers will be displayed
  871.      *
  872.      * For fancy line numbers, the second parameter is used to signal which lines
  873.      * are to be fancy. For example, if the value of this parameter is 5 then every
  874.      * 5th line will be fancy.
  875.      *
  876.      * @param int How line numbers should be displayed
  877.      * @param int Defines which lines are fancy
  878.      * @since 1.0.0
  879.      */
  880.     function enable_line_numbers($flag, $nth_row = 5) {
  881.         if (<a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> != $flag && <a href="../geshi/core/_geshi.php.html#defineGESHI_NORMAL_LINE_NUMBERS">GESHI_NORMAL_LINE_NUMBERS</a> != $flag
  882.             && <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> != $flag) {
  883.             $this->error = GESHI_ERROR_INVALID_LINE_NUMBER_TYPE;
  884.         }
  885.         $this->line_numbers = $flag;
  886.         $this->line_nth_row = $nth_row;
  887.     }
  888.  
  889. /**    
  890.      * Sets wether spans and other HTML markup generated by GeSHi can
  891.      * span over multiple lines or not. Defaults to true to reduce overhead.
  892.      * Set it to false if you want to manipulate the output or manually display
  893.      * the code in an ordered list.
  894.      *
  895.      * @param boolean Wether multiline spans are allowed or not
  896.      * @since 1.0.7.22
  897.      */
  898.     function enable_multiline_span($flag) {
  899.         $this->allow_multiline_span = (bool) $flag;
  900.     }
  901.  
  902. /**    
  903.      * Get current setting for multiline spans, see GeSHi->enable_multiline_span().
  904.      *
  905.      * @see enable_multiline_span
  906.      * @return bool 
  907.      */
  908.     function get_multiline_span() {
  909.         return $this->allow_multiline_span;
  910.     }
  911.  
  912. /**    
  913.      * Sets the style for a keyword group. If $preserve_defaults is
  914.      * true, then styles are merged with the default styles, with the
  915.      * user defined styles having priority
  916.      *
  917.      * @param int     The key of the keyword group to change the styles of
  918.      * @param string  The style to make the keywords
  919.      * @param boolean Whether to merge the new styles with the old or just
  920.      *                 to overwrite them
  921.      * @since 1.0.0
  922.      */
  923.     function set_keyword_group_style($key, $style, $preserve_defaults = false) {
  924.         //Set the style for this keyword group
  925.         if (!$preserve_defaults) {
  926.             $this->language_data['STYLES']['KEYWORDS'][$key] = $style;
  927.         } else {
  928.             $this->language_data['STYLES']['KEYWORDS'][$key] .= $style;
  929.         }
  930.  
  931.         //Update the lexic permissions
  932.         if (!isset($this->lexic_permissions['KEYWORDS'][$key])) {
  933.             $this->lexic_permissions['KEYWORDS'][$key] = true;
  934.         }
  935.     }
  936.  
  937. /**    
  938.      * Turns highlighting on/off for a keyword group
  939.      *
  940.      * @param int     The key of the keyword group to turn on or off
  941.      * @param boolean Whether to turn highlighting for that group on or off
  942.      * @since 1.0.0
  943.      */
  944.     function set_keyword_group_highlighting($key, $flag = true) {
  945.         $this->lexic_permissions['KEYWORDS'][$key] = ($flag) ? true : false;
  946.     }
  947.  
  948. /**    
  949.      * Sets the styles for comment groups.  If $preserve_defaults is
  950.      * true, then styles are merged with the default styles, with the
  951.      * user defined styles having priority
  952.      *
  953.      * @param int     The key of the comment group to change the styles of
  954.      * @param string  The style to make the comments
  955.      * @param boolean Whether to merge the new styles with the old or just
  956.      *                 to overwrite them
  957.      * @since 1.0.0
  958.      */
  959.     function set_comments_style($key, $style, $preserve_defaults = false) {
  960.         if (!$preserve_defaults) {
  961.             $this->language_data['STYLES']['COMMENTS'][$key] = $style;
  962.         } else {
  963.             $this->language_data['STYLES']['COMMENTS'][$key] .= $style;
  964.         }
  965.     }
  966.  
  967. /**    
  968.      * Turns highlighting on/off for comment groups
  969.      *
  970.      * @param int     The key of the comment group to turn on or off
  971.      * @param boolean Whether to turn highlighting for that group on or off
  972.      * @since 1.0.0
  973.      */
  974.     function set_comments_highlighting($key, $flag = true) {
  975.         $this->lexic_permissions['COMMENTS'][$key] = ($flag) ? true : false;
  976.     }
  977.  
  978. /**    
  979.      * Sets the styles for escaped characters. If $preserve_defaults is
  980.      * true, then styles are merged with the default styles, with the
  981.      * user defined styles having priority
  982.      *
  983.      * @param string  The style to make the escape characters
  984.      * @param boolean Whether to merge the new styles with the old or just
  985.      *                 to overwrite them
  986.      * @since 1.0.0
  987.      */
  988.     function set_escape_characters_style($style, $preserve_defaults = false) {
  989.         if (!$preserve_defaults) {
  990.             $this->language_data['STYLES']['ESCAPE_CHAR'][0] = $style;
  991.         } else {
  992.             $this->language_data['STYLES']['ESCAPE_CHAR'][0] .= $style;
  993.         }
  994.     }
  995.  
  996. /**    
  997.      * Turns highlighting on/off for escaped characters
  998.      *
  999.      * @param boolean Whether to turn highlighting for escape characters on or off
  1000.      * @since 1.0.0
  1001.      */
  1002.     function set_escape_characters_highlighting($flag = true) {
  1003.         $this->lexic_permissions['ESCAPE_CHAR'] = ($flag) ? true : false;
  1004.     }
  1005.  
  1006. /**    
  1007.      * Sets the styles for brackets. If $preserve_defaults is
  1008.      * true, then styles are merged with the default styles, with the
  1009.      * user defined styles having priority
  1010.      *
  1011.      * This method is DEPRECATED: use set_symbols_style instead.
  1012.      * This method will be removed in 1.2.X
  1013.      *
  1014.      * @param string  The style to make the brackets
  1015.      * @param boolean Whether to merge the new styles with the old or just
  1016.      *                 to overwrite them
  1017.      * @since 1.0.0
  1018.      * @deprecated In favour of set_symbols_style
  1019.      */
  1020.     function set_brackets_style($style, $preserve_defaults = false) {
  1021.         if (!$preserve_defaults) {
  1022.             $this->language_data['STYLES']['BRACKETS'][0] = $style;
  1023.         } else {
  1024.             $this->language_data['STYLES']['BRACKETS'][0] .= $style;
  1025.         }
  1026.     }
  1027.  
  1028. /**    
  1029.      * Turns highlighting on/off for brackets
  1030.      *
  1031.      * This method is DEPRECATED: use set_symbols_highlighting instead.
  1032.      * This method will be remove in 1.2.X
  1033.      *
  1034.      * @param boolean Whether to turn highlighting for brackets on or off
  1035.      * @since 1.0.0
  1036.      * @deprecated In favour of set_symbols_highlighting
  1037.      */
  1038.     function set_brackets_highlighting($flag) {
  1039.         $this->lexic_permissions['BRACKETS'] = ($flag) ? true : false;
  1040.     }
  1041.  
  1042. /**    
  1043.      * Sets the styles for symbols. If $preserve_defaults is
  1044.      * true, then styles are merged with the default styles, with the
  1045.      * user defined styles having priority
  1046.      *
  1047.      * @param string  The style to make the symbols
  1048.      * @param boolean Whether to merge the new styles with the old or just
  1049.      *                 to overwrite them
  1050.      * @param int     Tells the group of symbols for which style should be set.
  1051.      * @since 1.0.1
  1052.      */
  1053.     function set_symbols_style($style, $preserve_defaults = false, $group = 0) {
  1054.         // Update the style of symbols
  1055.         if (!$preserve_defaults) {
  1056.             $this->language_data['STYLES']['SYMBOLS'][$group] = $style;
  1057.         } else {
  1058.             $this->language_data['STYLES']['SYMBOLS'][$group] .= $style;
  1059.         }
  1060.  
  1061.         // For backward compatibility
  1062.         if (0 == $group) {
  1063.             $this->set_brackets_style ($style, $preserve_defaults);
  1064.         }
  1065.     }
  1066.  
  1067. /**    
  1068.      * Turns highlighting on/off for symbols
  1069.      *
  1070.      * @param boolean Whether to turn highlighting for symbols on or off
  1071.      * @since 1.0.0
  1072.      */
  1073.     function set_symbols_highlighting($flag) {
  1074.         // Update lexic permissions for this symbol group
  1075.         $this->lexic_permissions['SYMBOLS'] = ($flag) ? true : false;
  1076.  
  1077.         // For backward compatibility
  1078.         $this->set_brackets_highlighting ($flag);
  1079.     }
  1080.  
  1081. /**    
  1082.      * Sets the styles for strings. If $preserve_defaults is
  1083.      * true, then styles are merged with the default styles, with the
  1084.      * user defined styles having priority
  1085.      *
  1086.      * @param string  The style to make the escape characters
  1087.      * @param boolean Whether to merge the new styles with the old or just
  1088.      *                 to overwrite them
  1089.      * @since 1.0.0
  1090.      */
  1091.     function set_strings_style($style, $preserve_defaults = false) {
  1092.         if (!$preserve_defaults) {
  1093.             $this->language_data['STYLES']['STRINGS'][0] = $style;
  1094.         } else {
  1095.             $this->language_data['STYLES']['STRINGS'][0] .= $style;
  1096.         }
  1097.     }
  1098.  
  1099. /**    
  1100.      * Turns highlighting on/off for strings
  1101.      *
  1102.      * @param boolean Whether to turn highlighting for strings on or off
  1103.      * @since 1.0.0
  1104.      */
  1105.     function set_strings_highlighting($flag) {
  1106.         $this->lexic_permissions['STRINGS'] = ($flag) ? true : false;
  1107.     }
  1108.  
  1109. /**    
  1110.      * Sets the styles for numbers. If $preserve_defaults is
  1111.      * true, then styles are merged with the default styles, with the
  1112.      * user defined styles having priority
  1113.      *
  1114.      * @param string  The style to make the numbers
  1115.      * @param boolean Whether to merge the new styles with the old or just
  1116.      *                 to overwrite them
  1117.      * @since 1.0.0
  1118.      */
  1119.     function set_numbers_style($style, $preserve_defaults = false) {
  1120.         if (!$preserve_defaults) {
  1121.             $this->language_data['STYLES']['NUMBERS'][0] = $style;
  1122.         } else {
  1123.             $this->language_data['STYLES']['NUMBERS'][0] .= $style;
  1124.         }
  1125.     }
  1126.  
  1127. /**    
  1128.      * Turns highlighting on/off for numbers
  1129.      *
  1130.      * @param boolean Whether to turn highlighting for numbers on or off
  1131.      * @since 1.0.0
  1132.      */
  1133.     function set_numbers_highlighting($flag) {
  1134.         $this->lexic_permissions['NUMBERS'] = ($flag) ? true : false;
  1135.     }
  1136.  
  1137. /**    
  1138.      * Sets the styles for methods. $key is a number that references the
  1139.      * appropriate "object splitter" - see the language file for the language
  1140.      * you are highlighting to get this number. If $preserve_defaults is
  1141.      * true, then styles are merged with the default styles, with the
  1142.      * user defined styles having priority
  1143.      *
  1144.      * @param int     The key of the object splitter to change the styles of
  1145.      * @param string  The style to make the methods
  1146.      * @param boolean Whether to merge the new styles with the old or just
  1147.      *                 to overwrite them
  1148.      * @since 1.0.0
  1149.      */
  1150.     function set_methods_style($key, $style, $preserve_defaults = false) {
  1151.         if (!$preserve_defaults) {
  1152.             $this->language_data['STYLES']['METHODS'][$key] = $style;
  1153.         } else {
  1154.             $this->language_data['STYLES']['METHODS'][$key] .= $style;
  1155.         }
  1156.     }
  1157.  
  1158. /**    
  1159.      * Turns highlighting on/off for methods
  1160.      *
  1161.      * @param boolean Whether to turn highlighting for methods on or off
  1162.      * @since 1.0.0
  1163.      */
  1164.     function set_methods_highlighting($flag) {
  1165.         $this->lexic_permissions['METHODS'] = ($flag) ? true : false;
  1166.     }
  1167.  
  1168. /**    
  1169.      * Sets the styles for regexps. If $preserve_defaults is
  1170.      * true, then styles are merged with the default styles, with the
  1171.      * user defined styles having priority
  1172.      *
  1173.      * @param string  The style to make the regular expression matches
  1174.      * @param boolean Whether to merge the new styles with the old or just
  1175.      *                 to overwrite them
  1176.      * @since 1.0.0
  1177.      */
  1178.     function set_regexps_style($key, $style, $preserve_defaults = false) {
  1179.         if (!$preserve_defaults) {
  1180.             $this->language_data['STYLES']['REGEXPS'][$key] = $style;
  1181.         } else {
  1182.             $this->language_data['STYLES']['REGEXPS'][$key] .= $style;
  1183.         }
  1184.     }
  1185.  
  1186. /**    
  1187.      * Turns highlighting on/off for regexps
  1188.      *
  1189.      * @param int     The key of the regular expression group to turn on or off
  1190.      * @param boolean Whether to turn highlighting for the regular expression group on or off
  1191.      * @since 1.0.0
  1192.      */
  1193.     function set_regexps_highlighting($key, $flag) {
  1194.         $this->lexic_permissions['REGEXPS'][$key] = ($flag) ? true : false;
  1195.     }
  1196.  
  1197. /**    
  1198.      * Sets whether a set of keywords are checked for in a case sensitive manner
  1199.      *
  1200.      * @param int The key of the keyword group to change the case sensitivity of
  1201.      * @param boolean Whether to check in a case sensitive manner or not
  1202.      * @since 1.0.0
  1203.      */
  1204.     function set_case_sensitivity($key, $case) {
  1205.         $this->language_data['CASE_SENSITIVE'][$key] = ($case) ? true : false;
  1206.     }
  1207.  
  1208. /**    
  1209.      * Sets the case that keywords should use when found. Use the constants:
  1210.      *
  1211.      *  - GESHI_CAPS_NO_CHANGE: leave keywords as-is
  1212.      *  - GESHI_CAPS_UPPER: convert all keywords to uppercase where found
  1213.      *  - GESHI_CAPS_LOWER: convert all keywords to lowercase where found
  1214.      *
  1215.      * @param int A constant specifying what to do with matched keywords
  1216.      * @since 1.0.1
  1217.      */
  1218.     function set_case_keywords($case) {
  1219.         if (in_array($case, array(
  1220.             <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_NO_CHANGE">GESHI_CAPS_NO_CHANGE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_UPPER">GESHI_CAPS_UPPER</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_LOWER">GESHI_CAPS_LOWER</a>))) {
  1221.             $this->language_data['CASE_KEYWORDS'] = $case;
  1222.         }
  1223.     }
  1224.  
  1225. /**    
  1226.      * Sets how many spaces a tab is substituted for
  1227.      *
  1228.      * Widths below zero are ignored
  1229.      *
  1230.      * @param int The tab width
  1231.      * @since 1.0.0
  1232.      */
  1233.     function set_tab_width($width) {
  1234.         $this->tab_width = intval($width);
  1235.  
  1236.         //Check if it fit's the constraints:
  1237.         if ($this->tab_width < 1) {
  1238.             //Return it to the default
  1239.             $this->tab_width = 8;
  1240.         }
  1241.     }
  1242.  
  1243. /**    
  1244.      * Sets whether or not to use tab-stop width specifed by language
  1245.      *
  1246.      * @param boolean Whether to use language-specific tab-stop widths
  1247.      * @since 1.0.7.20
  1248.      */
  1249.     function set_use_language_tab_width($use) {
  1250.         $this->use_language_tab_width = (bool) $use;
  1251.     }
  1252.  
  1253. /**    
  1254.      * Returns the tab width to use, based on the current language and user
  1255.      * preference
  1256.      *
  1257.      * @return int Tab width
  1258.      * @since 1.0.7.20
  1259.      */
  1260.     function get_real_tab_width() {
  1261.         if (!$this->use_language_tab_width ||
  1262.             !isset($this->language_data['TAB_WIDTH'])) {
  1263.             return $this->tab_width;
  1264.         } else {
  1265.             return $this->language_data['TAB_WIDTH'];
  1266.         }
  1267.     }
  1268.  
  1269. /**    
  1270.      * Enables/disables strict highlighting. Default is off, calling this
  1271.      * method without parameters will turn it on. See documentation
  1272.      * for more details on strict mode and where to use it.
  1273.      *
  1274.      * @param boolean Whether to enable strict mode or not
  1275.      * @since 1.0.0
  1276.      */
  1277.     function enable_strict_mode($mode = true) {
  1278.         if (GESHI_MAYBE == $this->language_data['STRICT_MODE_APPLIES']) {
  1279.             $this->strict_mode = ($mode) ? GESHI_ALWAYS : GESHI_NEVER;
  1280.         }
  1281.     }
  1282.  
  1283. /**    
  1284.      * Disables all highlighting
  1285.      *
  1286.      * @since 1.0.0
  1287.      * @todo  Rewrite with array traversal
  1288.      * @deprecated In favour of enable_highlighting
  1289.      */
  1290.     function disable_highlighting() {
  1291.         $this->enable_highlighting(false);
  1292.     }
  1293.  
  1294. /**    
  1295.      * Enables all highlighting
  1296.      *
  1297.      * The optional flag parameter was added in version 1.0.7.21 and can be used
  1298.      * to enable (true) or disable (false) all highlighting.
  1299.      *
  1300.      * @since 1.0.0
  1301.      * @param boolean A flag specifying whether to enable or disable all highlighting
  1302.      * @todo  Rewrite with array traversal
  1303.      */
  1304.     function enable_highlighting($flag = true) {
  1305.         $flag = $flag ? true : false;
  1306.         foreach ($this->lexic_permissions as $key => $value) {
  1307.             if (is_array($value)) {
  1308.                 foreach ($value as $k => $v) {
  1309.                     $this->lexic_permissions[$key][$k] = $flag;
  1310.                 }
  1311.             } else {
  1312.                 $this->lexic_permissions[$key] = $flag;
  1313.             }
  1314.         }
  1315.  
  1316.         // Context blocks
  1317.         $this->enable_important_blocks = $flag;
  1318.     }
  1319.  
  1320. /**    
  1321.      * Given a file extension, this method returns either a valid geshi language
  1322.      * name, or the empty string if it couldn't be found
  1323.      *
  1324.      * @param string The extension to get a language name for
  1325.      * @param array  A lookup array to use instead of the default one
  1326.      * @since 1.0.5
  1327.      * @todo Re-think about how this method works (maybe make it private and/or make it
  1328.      *        a extension->lang lookup?)
  1329.      * @todo static?
  1330.      */
  1331.     function get_language_name_from_extension( $extension, $lookup = array() ) {
  1332.         if ( !is_array($lookup) || empty($lookup)) {
  1333.             $lookup = array(
  1334.                 'actionscript' => array('as'),
  1335.                 'ada' => array('a', 'ada', 'adb', 'ads'),
  1336.                 'apache' => array('conf'),
  1337.                 'asm' => array('ash', 'asm'),
  1338.                 'asp' => array('asp'),
  1339.                 'bash' => array('sh'),
  1340.                 'c' => array('c', 'h'),
  1341.                 'c_mac' => array('c', 'h'),
  1342.                 'caddcl' => array(),
  1343.                 'cadlisp' => array(),
  1344.                 'cdfg' => array('cdfg'),
  1345.                 'cobol' => array('cbl'),
  1346.                 'cpp' => array('cpp', 'h', 'hpp'),
  1347.                 'csharp' => array(),
  1348.                 'css' => array('css'),
  1349.                 'delphi' => array('dpk', 'dpr', 'pp', 'pas'),
  1350.                 'dos' => array('bat', 'cmd'),
  1351.                 'gettext' => array('po', 'pot'),
  1352.                 'html4strict' => array('html', 'htm'),
  1353.                 'ini' => array('ini', 'desktop'),
  1354.                 'java' => array('java'),
  1355.                 'javascript' => array('js'),
  1356.                 'klonec' => array('kl1'),
  1357.                 'klonecpp' => array('klx'),
  1358.                 'lisp' => array('lisp'),
  1359.                 'lua' => array('lua'),
  1360.                 'matlab' => array('m'),
  1361.                 'mpasm' => array(),
  1362.                 'nsis' => array(),
  1363.                 'objc' => array(),
  1364.                 'oobas' => array(),
  1365.                 'oracle8' => array(),
  1366.                 'pascal' => array(),
  1367.                 'perl' => array('pl', 'pm'),
  1368.                 'php' => array('php', 'php5', 'phtml', 'phps'),
  1369.                 'python' => array('py'),
  1370.                 'qbasic' => array('bi'),
  1371.                 'sas' => array('sas'),
  1372.                 'scilab' => array('sci'),
  1373.                 'smarty' => array(),
  1374.                 'vb' => array('bas'),
  1375.                 'vbnet' => array(),
  1376.                 'visualfoxpro' => array(),
  1377.                 'xml' => array('xml')
  1378.             );
  1379.         }
  1380.  
  1381.         foreach ($lookup as $lang => $extensions) {
  1382.             if (in_array($extension, $extensions)) {
  1383.                 return $lang;
  1384.             }
  1385.         }
  1386.         return '';
  1387.     }
  1388.  
  1389. /**    
  1390.      * Given a file name, this method loads its contents in, and attempts
  1391.      * to set the language automatically. An optional lookup table can be
  1392.      * passed for looking up the language name. If not specified a default
  1393.      * table is used
  1394.      *
  1395.      * The language table is in the form
  1396.      * <pre>array(
  1397.      *   'lang_name' => array('extension', 'extension', ...),
  1398.      *   'lang_name' ...
  1399.      * );</pre>
  1400.      *
  1401.      * @param string The filename to load the source from
  1402.      * @param array  A lookup array to use instead of the default one
  1403.      * @todo Complete rethink of this and above method
  1404.      * @since 1.0.5
  1405.      */
  1406.     function load_from_file($file_name, $lookup = array()) {
  1407.         if (is_readable($file_name)) {
  1408.             $this->set_source(file_get_contents($file_name));
  1409.             $this->set_language($this->get_language_name_from_extension(substr(strrchr($file_name, '.'), 1), $lookup));
  1410.         } else {
  1411.             $this->error = GESHI_ERROR_FILE_NOT_READABLE;
  1412.         }
  1413.     }
  1414.  
  1415. /**    
  1416.      * Adds a keyword to a keyword group for highlighting
  1417.      *
  1418.      * @param int    The key of the keyword group to add the keyword to
  1419.      * @param string The word to add to the keyword group
  1420.      * @since 1.0.0
  1421.      */
  1422.     function add_keyword($key, $word) {
  1423.         if (!in_array($word, $this->language_data['KEYWORDS'][$key])) {
  1424.             $this->language_data['KEYWORDS'][$key][] = $word;
  1425.  
  1426.             //NEW in 1.0.8 don't recompile the whole optimized regexp, simply append it
  1427.             if ($this->parse_cache_built) {
  1428.                 $subkey = count($this->language_data['CACHED_KEYWORD_LISTS'][$key]) - 1;
  1429.                 $this->language_data['CACHED_KEYWORD_LISTS'][$key][$subkey] .= '|' . preg_quote($word, '/');
  1430.             }
  1431.         }
  1432.     }
  1433.  
  1434. /**    
  1435.      * Removes a keyword from a keyword group
  1436.      *
  1437.      * @param int    The key of the keyword group to remove the keyword from
  1438.      * @param string The word to remove from the keyword group
  1439.      * @param bool   Wether to automatically recompile the optimized regexp list or not.
  1440.      *                Note: if you set this to false and @see GeSHi->parse_code() was already called once,
  1441.      *                for the current language, you have to manually call @see GeSHi->optimize_keyword_group()
  1442.      *                or the removed keyword will stay in cache and still be highlighted! On the other hand
  1443.      *                it might be too expensive to recompile the regexp list for every removal if you want to
  1444.      *                remove a lot of keywords.
  1445.      * @since 1.0.0
  1446.      */
  1447.     function remove_keyword($key, $word, $recompile = true) {
  1448.         $key_to_remove = array_search($word, $this->language_data['KEYWORDS'][$key]);
  1449.         if ($key_to_remove !== false) {
  1450.             unset($this->language_data['KEYWORDS'][$key][$key_to_remove]);
  1451.  
  1452.             //NEW in 1.0.8, optionally recompile keyword group
  1453.             if ($recompile && $this->parse_cache_built) {
  1454.                 $this->optimize_keyword_group($key);
  1455.             }
  1456.         }
  1457.     }
  1458.  
  1459. /**    
  1460.      * Creates a new keyword group
  1461.      *
  1462.      * @param int    The key of the keyword group to create
  1463.      * @param string The styles for the keyword group
  1464.      * @param boolean Whether the keyword group is case sensitive ornot
  1465.      * @param array  The words to use for the keyword group
  1466.      * @since 1.0.0
  1467.      */
  1468.     function add_keyword_group($key, $styles, $case_sensitive = true, $words = array()) {
  1469.         $words = (array) $words;
  1470.         if  (empty($words)) {
  1471.             // empty word lists mess up highlighting
  1472.             return false;
  1473.         }
  1474.  
  1475.         //Add the new keyword group internally
  1476.         $this->language_data['KEYWORDS'][$key] = $words;
  1477.         $this->lexic_permissions['KEYWORDS'][$key] = true;
  1478.         $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive;
  1479.         $this->language_data['STYLES']['KEYWORDS'][$key] = $styles;
  1480.  
  1481.         //NEW in 1.0.8, cache keyword regexp
  1482.         if ($this->parse_cache_built) {
  1483.             $this->optimize_keyword_group($key);
  1484.         }
  1485.     }
  1486.  
  1487. /**    
  1488.      * Removes a keyword group
  1489.      *
  1490.      * @param int    The key of the keyword group to remove
  1491.      * @since 1.0.0
  1492.      */
  1493.     function remove_keyword_group ($key) {
  1494.         //Remove the keyword group internally
  1495.         unset($this->language_data['KEYWORDS'][$key]);
  1496.         unset($this->lexic_permissions['KEYWORDS'][$key]);
  1497.         unset($this->language_data['CASE_SENSITIVE'][$key]);
  1498.         unset($this->language_data['STYLES']['KEYWORDS'][$key]);
  1499.  
  1500.         //NEW in 1.0.8
  1501.         unset($this->language_data['CACHED_KEYWORD_LISTS'][$key]);
  1502.     }
  1503.  
  1504. /**    
  1505.      * compile optimized regexp list for keyword group
  1506.      *
  1507.      * @param int   The key of the keyword group to compile & optimize
  1508.      * @since 1.0.8
  1509.      */
  1510.     function optimize_keyword_group($key) {
  1511.         $this->language_data['CACHED_KEYWORD_LISTS'][$key] =
  1512.             $this->optimize_regexp_list($this->language_data['KEYWORDS'][$key]);
  1513.     }
  1514.  
  1515. /**    
  1516.      * Sets the content of the header block
  1517.      *
  1518.      * @param string The content of the header block
  1519.      * @since 1.0.2
  1520.      */
  1521.     function set_header_content($content) {
  1522.         $this->header_content = $content;
  1523.     }
  1524.  
  1525. /**    
  1526.      * Sets the content of the footer block
  1527.      *
  1528.      * @param string The content of the footer block
  1529.      * @since 1.0.2
  1530.      */
  1531.     function set_footer_content($content) {
  1532.         $this->footer_content = $content;
  1533.     }
  1534.  
  1535. /**    
  1536.      * Sets the style for the header content
  1537.      *
  1538.      * @param string The style for the header content
  1539.      * @since 1.0.2
  1540.      */
  1541.     function set_header_content_style($style) {
  1542.         $this->header_content_style = $style;
  1543.     }
  1544.  
  1545. /**    
  1546.      * Sets the style for the footer content
  1547.      *
  1548.      * @param string The style for the footer content
  1549.      * @since 1.0.2
  1550.      */
  1551.     function set_footer_content_style($style) {
  1552.         $this->footer_content_style = $style;
  1553.     }
  1554.  
  1555. /**    
  1556.      * Sets whether to force a surrounding block around
  1557.      * the highlighted code or not
  1558.      *
  1559.      * @param boolean Tells whether to enable or disable this feature
  1560.      * @since 1.0.7.20
  1561.      */
  1562.     function enable_inner_code_block($flag) {
  1563.         $this->force_code_block = (bool)$flag;
  1564.     }
  1565.  
  1566. /**    
  1567.      * Sets the base URL to be used for keywords
  1568.      *
  1569.      * @param int The key of the keyword group to set the URL for
  1570.      * @param string The URL to set for the group. If {FNAME} is in
  1571.      *                the url somewhere, it is replaced by the keyword
  1572.      *                that the URL is being made for
  1573.      * @since 1.0.2
  1574.      */
  1575.     function set_url_for_keyword_group($group, $url) {
  1576.         $this->language_data['URLS'][$group] = $url;
  1577.     }
  1578.  
  1579. /**    
  1580.      * Sets styles for links in code
  1581.      *
  1582.      * @param int A constant that specifies what state the style is being
  1583.      *             set for - e.g. :hover or :visited
  1584.      * @param string The styles to use for that state
  1585.      * @since 1.0.2
  1586.      */
  1587.     function set_link_styles($type, $styles) {
  1588.         $this->link_styles[$type] = $styles;
  1589.     }
  1590.  
  1591. /**    
  1592.      * Sets the target for links in code
  1593.      *
  1594.      * @param string The target for links in the code, e.g. _blank
  1595.      * @since 1.0.3
  1596.      */
  1597.     function set_link_target($target) {
  1598.         if (!$target) {
  1599.             $this->link_target = '';
  1600.         } else {
  1601.             $this->link_target = ' target="' . $target . '" ';
  1602.         }
  1603.     }
  1604.  
  1605. /**    
  1606.      * Sets styles for important parts of the code
  1607.      *
  1608.      * @param string The styles to use on important parts of the code
  1609.      * @since 1.0.2
  1610.      */
  1611.     function set_important_styles($styles) {
  1612.         $this->important_styles = $styles;
  1613.     }
  1614.  
  1615. /**    
  1616.      * Sets whether context-important blocks are highlighted
  1617.      *
  1618.      * @param boolean Tells whether to enable or disable highlighting of important blocks
  1619.      * @todo REMOVE THIS SHIZ FROM GESHI!
  1620.      * @deprecated
  1621.      * @since 1.0.2
  1622.      */
  1623.     function enable_important_blocks($flag) {
  1624.         $this->enable_important_blocks = ( $flag ) ? true : false;
  1625.     }
  1626.  
  1627. /**    
  1628.      * Whether CSS IDs should be added to each line
  1629.      *
  1630.      * @param boolean If true, IDs will be added to each line.
  1631.      * @since 1.0.2
  1632.      */
  1633.     function enable_ids($flag = true) {
  1634.         $this->add_ids = ($flag) ? true : false;
  1635.     }
  1636.  
  1637. /**    
  1638.      * Specifies which lines to highlight extra
  1639.      *
  1640.      * The extra style parameter was added in 1.0.7.21.
  1641.      *
  1642.      * @param mixed An array of line numbers to highlight, or just a line
  1643.      *               number on its own.
  1644.      * @param string A string specifying the style to use for this line.
  1645.      *               If null is specified, the default style is used.
  1646.      *               If false is specified, the line will be removed from
  1647.      *               special highlighting
  1648.      * @since 1.0.2
  1649.      * @todo  Some data replication here that could be cut down on
  1650.      */
  1651.     function highlight_lines_extra($lines, $style = null) {
  1652.         if (is_array($lines)) {
  1653.             //Split up the job using single lines at a time
  1654.             foreach ($lines as $line) {
  1655.                 $this->highlight_lines_extra($line, $style);
  1656.             }
  1657.         } else {
  1658.             //Mark the line as being highlighted specially
  1659.             $lines = intval($lines);
  1660.             $this->highlight_extra_lines[$lines] = $lines;
  1661.  
  1662.             //Decide on which style to use
  1663.             if ($style === null) { //Check if we should use default style
  1664.                 unset($this->highlight_extra_lines_styles[$lines]);
  1665.             } else if ($style === false) { //Check if to remove this line
  1666.                 unset($this->highlight_extra_lines[$lines]);
  1667.                 unset($this->highlight_extra_lines_styles[$lines]);
  1668.             } else {
  1669.                 $this->highlight_extra_lines_styles[$lines] = $style;
  1670.             }
  1671.         }
  1672.     }
  1673.  
  1674. /**    
  1675.      * Sets the style for extra-highlighted lines
  1676.      *
  1677.      * @param string The style for extra-highlighted lines
  1678.      * @since 1.0.2
  1679.      */
  1680.     function set_highlight_lines_extra_style($styles) {
  1681.         $this->highlight_extra_lines_style = $styles;
  1682.     }
  1683.  
  1684. /**    
  1685.      * Sets the line-ending
  1686.      *
  1687.      * @param string The new line-ending
  1688.      * @since 1.0.2
  1689.      */
  1690.     function set_line_ending($line_ending) {
  1691.         $this->line_ending = (string)$line_ending;
  1692.     }
  1693.  
  1694. /**    
  1695.      * Sets what number line numbers should start at. Should
  1696.      * be a positive integer, and will be converted to one.
  1697.      *
  1698.      * <b>Warning:</b> Using this method will add the "start"
  1699.      * attribute to the &lt;ol&gt; that is used for line numbering.
  1700.      * This is <b>not</b> valid XHTML strict, so if that's what you
  1701.      * care about then don't use this method. Firefox is getting
  1702.      * support for the CSS method of doing this in 1.1 and Opera
  1703.      * has support for the CSS method, but (of course) IE doesn't
  1704.      * so it's not worth doing it the CSS way yet.
  1705.      *
  1706.      * @param int The number to start line numbers at
  1707.      * @since 1.0.2
  1708.      */
  1709.     function start_line_numbers_at($number) {
  1710.         $this->line_numbers_start = abs(intval($number));
  1711.     }
  1712.  
  1713. /**    
  1714.      * Sets the encoding used for htmlspecialchars(), for international
  1715.      * support.
  1716.      *
  1717.      * NOTE: This is not needed for now because htmlspecialchars() is not
  1718.      * being used (it has a security hole in PHP4 that has not been patched).
  1719.      * Maybe in a future version it may make a return for speed reasons, but
  1720.      * I doubt it.
  1721.      *
  1722.      * @param string The encoding to use for the source
  1723.      * @since 1.0.3
  1724.      */
  1725.     function set_encoding($encoding) {
  1726.         if ($encoding) {
  1727.           $this->encoding = strtolower($encoding);
  1728.         }
  1729.     }
  1730.  
  1731. /**    
  1732.      * Turns linking of keywords on or off.
  1733.      *
  1734.      * @param boolean If true, links will be added to keywords
  1735.      * @since 1.0.2
  1736.      */
  1737.     function enable_keyword_links($enable = true) {
  1738.         $this->keyword_links = (bool) $enable;
  1739.     }
  1740.  
  1741. /**    
  1742.      * Setup caches needed for styling. This is automatically called in
  1743.      * parse_code() and get_stylesheet() when appropriate. This function helps
  1744.      * stylesheet generators as they rely on some style information being
  1745.      * preprocessed
  1746.      *
  1747.      * @since 1.0.8
  1748.      * @access private
  1749.      */
  1750.     function build_style_cache() {
  1751.         //Build the style cache needed to highlight numbers appropriate
  1752.         if($this->lexic_permissions['NUMBERS']) {
  1753.             //First check what way highlighting information for numbers are given
  1754.             if(!isset($this->language_data['NUMBERS'])) {
  1755.                 $this->language_data['NUMBERS'] = 0;
  1756.             }
  1757.  
  1758.             if(is_array($this->language_data['NUMBERS'])) {
  1759.                 $this->language_data['NUMBERS_CACHE'] = $this->language_data['NUMBERS'];
  1760.             } else {
  1761.                 $this->language_data['NUMBERS_CACHE'] = array();
  1762.                 if(!$this->language_data['NUMBERS']) {
  1763.                     $this->language_data['NUMBERS'] =
  1764.                         GESHI_NUMBER_INT_BASIC |
  1765.                         GESHI_NUMBER_FLT_NONSCI;
  1766.                 }
  1767.  
  1768.                 for($i = 0, $j = $this->language_data['NUMBERS']; $j > 0; ++$i, $j>>=1) {
  1769.                     //Rearrange style indices if required ...
  1770.                     if(isset($this->language_data['STYLES']['NUMBERS'][1<<$i])) {
  1771.                         $this->language_data['STYLES']['NUMBERS'][$i] =
  1772.                             $this->language_data['STYLES']['NUMBERS'][1<<$i];
  1773.                         unset($this->language_data['STYLES']['NUMBERS'][1<<$i]);
  1774.                     }
  1775.  
  1776.                     //Check if this bit is set for highlighting
  1777.                     if($j&1) {
  1778.                         //So this bit is set ...
  1779.                         //Check if it belongs to group 0 or the actual stylegroup
  1780.                         if(isset($this->language_data['STYLES']['NUMBERS'][$i])) {
  1781.                             $this->language_data['NUMBERS_CACHE'][$i] = 1 << $i;
  1782.                         } else {
  1783.                             if(!isset($this->language_data['NUMBERS_CACHE'][0])) {
  1784.                                 $this->language_data['NUMBERS_CACHE'][0] = 0;
  1785.                             }
  1786.                             $this->language_data['NUMBERS_CACHE'][0] |= 1 << $i;
  1787.                         }
  1788.                     }
  1789.                 }
  1790.             }
  1791.         }
  1792.     }
  1793.  
  1794. /**    
  1795.      * Setup caches needed for parsing. This is automatically called in parse_code() when appropriate.
  1796.      * This function makes stylesheet generators much faster as they do not need these caches.
  1797.      *
  1798.      * @since 1.0.8
  1799.      * @access private
  1800.      */
  1801.     function build_parse_cache() {
  1802.         // cache symbol regexp
  1803.         //As this is a costy operation, we avoid doing it for multiple groups ...
  1804.         //Instead we perform it for all symbols at once.
  1805.         //
  1806.         //For this to work, we need to reorganize the data arrays.
  1807.         if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
  1808.             $this->language_data['MULTIPLE_SYMBOL_GROUPS'] = count($this->language_data['STYLES']['SYMBOLS']) > 1;
  1809.  
  1810.             $this->language_data['SYMBOL_DATA'] = array();
  1811.             $symbol_preg_multi = array(); // multi char symbols
  1812.             $symbol_preg_single = array(); // single char symbols
  1813.             foreach ($this->language_data['SYMBOLS'] as $key => $symbols) {
  1814.                 if (is_array($symbols)) {
  1815.                     foreach ($symbols as $sym) {
  1816.                         $sym = $this->hsc($sym);
  1817.                         if (!isset($this->language_data['SYMBOL_DATA'][$sym])) {
  1818.                             $this->language_data['SYMBOL_DATA'][$sym] = $key;
  1819.                             if (isset($sym[1])) { // multiple chars
  1820.                                 $symbol_preg_multi[] = preg_quote($sym, '/');
  1821.                             } else { // single char
  1822.                                 if ($sym == '-') {
  1823.                                     // don't trigger range out of order error
  1824.                                     $symbol_preg_single[] = '\-';
  1825.                                 } else {
  1826.                                     $symbol_preg_single[] = preg_quote($sym, '/');
  1827.                                 }
  1828.                             }
  1829.                         }
  1830.                     }
  1831.                 } else {
  1832.                     $symbols = $this->hsc($symbols);
  1833.                     if (!isset($this->language_data['SYMBOL_DATA'][$symbols])) {
  1834.                         $this->language_data['SYMBOL_DATA'][$symbols] = 0;
  1835.                         if (isset($symbols[1])) { // multiple chars
  1836.                             $symbol_preg_multi[] = preg_quote($symbols, '/');
  1837.                         } else if ($symbols == '-') {
  1838.                             // don't trigger range out of order error
  1839.                             $symbol_preg_single[] = '\-';
  1840.                         } else { // single char
  1841.                             $symbol_preg_single[] = preg_quote($symbols, '/');
  1842.                         }
  1843.                     }
  1844.                 }
  1845.             }
  1846.  
  1847.             //Now we have an array with each possible symbol as the key and the style as the actual data.
  1848.             //This way we can set the correct style just the moment we highlight ...
  1849.             //
  1850.             //Now we need to rewrite our array to get a search string that
  1851.             $symbol_preg = array();
  1852.             if (!empty($symbol_preg_multi)) {
  1853.                 rsort($symbol_preg_multi);
  1854.                 $symbol_preg[] = implode('|', $symbol_preg_multi);
  1855.             }
  1856.             if (!empty($symbol_preg_single)) {
  1857.                 rsort($symbol_preg_single);
  1858.                 $symbol_preg[] = '[' . implode('', $symbol_preg_single) . ']';
  1859.             }
  1860.             $this->language_data['SYMBOL_SEARCH'] = implode("|", $symbol_preg);
  1861.         }
  1862.  
  1863.         // cache optimized regexp for keyword matching
  1864.         // remove old cache
  1865.         $this->language_data['CACHED_KEYWORD_LISTS'] = array();
  1866.         foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
  1867.             if (!isset($this->lexic_permissions['KEYWORDS'][$key]) ||
  1868.                     $this->lexic_permissions['KEYWORDS'][$key]) {
  1869.                 $this->optimize_keyword_group($key);
  1870.             }
  1871.         }
  1872.  
  1873.         // brackets
  1874.         if ($this->lexic_permissions['BRACKETS']) {
  1875.             $this->language_data['CACHE_BRACKET_MATCH'] = array('[', ']', '(', ')', '{', '}');
  1876.             if (!$this->use_classes && isset($this->language_data['STYLES']['BRACKETS'][0])) {
  1877.                 $this->language_data['CACHE_BRACKET_REPLACE'] = array(
  1878.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#91;|>',
  1879.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#93;|>',
  1880.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#40;|>',
  1881.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#41;|>',
  1882.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#123;|>',
  1883.                     '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#125;|>',
  1884.                 );
  1885.             }
  1886.             else {
  1887.                 $this->language_data['CACHE_BRACKET_REPLACE'] = array(
  1888.                     '<| class="br0">&#91;|>',
  1889.                     '<| class="br0">&#93;|>',
  1890.                     '<| class="br0">&#40;|>',
  1891.                     '<| class="br0">&#41;|>',
  1892.                     '<| class="br0">&#123;|>',
  1893.                     '<| class="br0">&#125;|>',
  1894.                 );
  1895.             }
  1896.         }
  1897.  
  1898.         //Build the parse cache needed to highlight numbers appropriate
  1899.         if($this->lexic_permissions['NUMBERS']) {
  1900.             //Check if the style rearrangements have been processed ...
  1901.             //This also does some preprocessing to check which style groups are useable ...
  1902.             if(!isset($this->language_data['NUMBERS_CACHE'])) {
  1903.                 $this->build_style_cache();
  1904.             }
  1905.  
  1906.             //Number format specification
  1907.             //All this formats are matched case-insensitively!
  1908.             static $numbers_format = array(
  1909.                 GESHI_NUMBER_INT_BASIC =>
  1910.                     '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])([1-9]\d*?|0)(?![0-9a-z\.])',
  1911.                 GESHI_NUMBER_INT_CSTYLE =>
  1912.                     '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])([1-9]\d*?|0)l(?![0-9a-z\.])',
  1913.                 GESHI_NUMBER_BIN_SUFFIX =>
  1914.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[01]+?b(?![0-9a-z\.])',
  1915.                 GESHI_NUMBER_BIN_PREFIX_PERCENT =>
  1916.                     '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])%[01]+?(?![0-9a-z\.])',
  1917.                 GESHI_NUMBER_BIN_PREFIX_0B =>
  1918.                     '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])0b[01]+?(?![0-9a-z\.])',
  1919.                 GESHI_NUMBER_OCT_PREFIX =>
  1920.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0[0-7]+?(?![0-9a-z\.])',
  1921.                 GESHI_NUMBER_OCT_SUFFIX =>
  1922.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[0-7]+?o(?![0-9a-z\.])',
  1923.                 GESHI_NUMBER_HEX_PREFIX =>
  1924.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0x[0-9a-f]+?(?![0-9a-z\.])',
  1925.                 GESHI_NUMBER_HEX_SUFFIX =>
  1926.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d[0-9a-f]*?h(?![0-9a-z\.])',
  1927.                 GESHI_NUMBER_FLT_NONSCI =>
  1928.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d+?\.\d+?(?![0-9a-z\.])',
  1929.                 GESHI_NUMBER_FLT_NONSCI_F =>
  1930.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)f(?![0-9a-z\.])',
  1931.                 GESHI_NUMBER_FLT_SCI_SHORT =>
  1932.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\.\d+?(?:e[+\-]?\d+?)?(?![0-9a-z\.])',
  1933.                 GESHI_NUMBER_FLT_SCI_ZERO =>
  1934.                     '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)(?:e[+\-]?\d+?)?(?![0-9a-z\.])'
  1935.                 );
  1936.  
  1937.             //At this step we have an associative array with flag groups for a
  1938.             //specific style or an string denoting a regexp given its index.
  1939.             $this->language_data['NUMBERS_RXCACHE'] = array();
  1940.             foreach($this->language_data['NUMBERS_CACHE'] as $key => $rxdata) {
  1941.                 if(is_string($rxdata)) {
  1942.                     $regexp = $rxdata;
  1943.                 } else {
  1944.                     //This is a bitfield of number flags to highlight:
  1945.                     //Build an array, implode them together and make this the actual RX
  1946.                     $rxuse = array();
  1947.                     for($i = 1; $i <= $rxdata; $i<<=1) {
  1948.                         if($rxdata & $i) {
  1949.                             $rxuse[] = $numbers_format[$i];
  1950.                         }
  1951.                     }
  1952.                     $regexp = implode("|", $rxuse);
  1953.                 }
  1954.  
  1955.                 $this->language_data['NUMBERS_RXCACHE'][$key] =
  1956.                     "/(?<!<\|\/NUM!)(?<!\d\/>)($regexp)(?!\|>)/i";
  1957.             }
  1958.         }
  1959.  
  1960.         $this->parse_cache_built = true;
  1961.     }
  1962.  
  1963.     /**
  1964.      * Returns the code in $this->source, highlighted and surrounded by the
  1965.      * nessecary HTML.
  1966.      *
  1967.      * This should only be called ONCE, cos it's SLOW! If you want to highlight
  1968.      * the same source multiple times, you're better off doing a whole lot of
  1969.      * str_replaces to replace the &lt;span&gt;s
  1970.      *
  1971.      * @since 1.0.0
  1972.      */
  1973.     function parse_code () {
  1974.         // Start the timer
  1975.         $start_time = microtime();
  1976.  
  1977.         // Firstly, if there is an error, we won't highlight
  1978.         if ($this->error) {
  1979.             //Escape the source for output
  1980.             $result = $this->hsc($this->source);
  1981.  
  1982.             //This fix is related to SF#1923020, but has to be applied regardless of
  1983.             //actually highlighting symbols.
  1984.             $result = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $result);
  1985.  
  1986.             // Timing is irrelevant
  1987.             $this->set_time($start_time, $start_time);
  1988.             $this->finalise($result);
  1989.             return $result;
  1990.         }
  1991.  
  1992.         // make sure the parse cache is up2date
  1993.         if (!$this->parse_cache_built) {
  1994.             $this->build_parse_cache();
  1995.         }
  1996.  
  1997.         // Replace all newlines to a common form.
  1998.         $code = str_replace("\r\n", "\n", $this->source);
  1999.         $code = str_replace("\r", "\n", $code);
  2000.  
  2001.         // Add spaces for regular expression matching and line numbers
  2002. //        $code = "\n" . $code . "\n";
  2003.         // Initialise various stuff
  2004.         $length           = strlen($code);
  2005.         $COMMENT_MATCHED  = false;
  2006.         $stuff_to_parse   = '';
  2007.         $endresult        = '';
  2008.  
  2009.         // "Important" selections are handled like multiline comments
  2010.         // @todo GET RID OF THIS SHIZ
  2011.         if ($this->enable_important_blocks) {
  2012.             $this->language_data['COMMENT_MULTI'][<a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>] = <a href="../geshi/core/_geshi.php.html#defineGESHI_END_IMPORTANT">GESHI_END_IMPORTANT</a>;
  2013.         }
  2014.  
  2015.         if ($this->strict_mode) {
  2016.             // Break the source into bits. Each bit will be a portion of the code
  2017.             // within script delimiters - for example, HTML between < and >
  2018.             $k = 0;
  2019.             $parts = array();
  2020.             $matches = array();
  2021.             $next_match_pointer = null;
  2022.             // we use a copy to unset delimiters on demand (when they are not found)
  2023.             $delim_copy = $this->language_data['SCRIPT_DELIMITERS'];
  2024.             $i = 0;
  2025.             while ($i < $length) {
  2026.                 $next_match_pos = $length + 1; // never true
  2027.                 foreach ($delim_copy as $dk => $delimiters) {
  2028.                     if(is_array($delimiters)) {
  2029.                         foreach ($delimiters as $open => $close) {
  2030.                             // make sure the cache is setup properly
  2031.                             if (!isset($matches[$dk][$open])) {
  2032.                                 $matches[$dk][$open] = array(
  2033.                                     'next_match' => -1,
  2034.                                     'dk' => $dk,
  2035.  
  2036.                                     'open' => $open, // needed for grouping of adjacent code blocks (see below)
  2037.                                     'open_strlen' => strlen($open),
  2038.  
  2039.                                     'close' => $close,
  2040.                                     'close_strlen' => strlen($close),
  2041.                                 );
  2042.                             }
  2043.                             // Get the next little bit for this opening string
  2044.                             if ($matches[$dk][$open]['next_match'] < $i) {
  2045.                                 // only find the next pos if it was not already cached
  2046.                                 $open_pos = strpos($code, $open, $i);
  2047.                                 if ($open_pos === false) {
  2048.                                     // no match for this delimiter ever
  2049.                                     unset($delim_copy[$dk][$open]);
  2050.                                     continue;
  2051.                                 }
  2052.                                 $matches[$dk][$open]['next_match'] = $open_pos;
  2053.                             }
  2054.                             if ($matches[$dk][$open]['next_match'] < $next_match_pos) {
  2055.                                 //So we got a new match, update the close_pos
  2056.                                 $matches[$dk][$open]['close_pos'] =
  2057.                                     strpos($code, $close, $matches[$dk][$open]['next_match']+1);
  2058.  
  2059.                                 $next_match_pointer =& $matches[$dk][$open];
  2060.                                 $next_match_pos = $matches[$dk][$open]['next_match'];
  2061.                             }
  2062.                         }
  2063.                     } else {
  2064.                         //So we should match an RegExp as Strict Block ...
  2065. /**                        
  2066.                          * The value in $delimiters is expected to be an RegExp
  2067.                          * containing exactly 2 matching groups:
  2068.                          *  - Group 1 is the opener
  2069.                          *  - Group 2 is the closer
  2070.                          */
  2071.                         if(!GESHI_PHP_PRE_433 && //Needs proper rewrite to work with PHP >=4.3.0; 4.3.3 is guaranteed to work.
  2072.                             preg_match($delimiters, $code, $matches_rx, PREG_OFFSET_CAPTURE, $i)) {
  2073.                             //We got a match ...
  2074.                             $matches[$dk] = array(
  2075.                                 'next_match' => $matches_rx[1][1],
  2076.                                 'dk' => $dk,
  2077.  
  2078.                                 'close_strlen' => strlen($matches_rx[2][0]),
  2079.                                 'close_pos' => $matches_rx[2][1],
  2080.                                 );
  2081.                         } else {
  2082.                             // no match for this delimiter ever
  2083.                             unset($delim_copy[$dk]);
  2084.                             continue;
  2085.                         }
  2086.  
  2087.                         if ($matches[$dk]['next_match'] <= $next_match_pos) {
  2088.                             $next_match_pointer =& $matches[$dk];
  2089.                             $next_match_pos = $matches[$dk]['next_match'];
  2090.                         }
  2091.                     }
  2092.                 }
  2093.                 // non-highlightable text
  2094.                 $parts[$k] = array(
  2095.                     1 => substr($code, $i, $next_match_pos - $i)
  2096.                 );
  2097.                 ++$k;
  2098.  
  2099.                 if ($next_match_pos > $length) {
  2100.                     // out of bounds means no next match was found
  2101.                     break;
  2102.                 }
  2103.  
  2104.                 // highlightable code
  2105.                 $parts[$k][0] = $next_match_pointer['dk'];
  2106.  
  2107.                 //Only combine for non-rx script blocks
  2108.                 if(is_array($delim_copy[$next_match_pointer['dk']])) {
  2109.                     // group adjacent script blocks, e.g. <foobar><asdf> should be one block, not three!
  2110.                     $i = $next_match_pos + $next_match_pointer['open_strlen'];
  2111.                     while (true) {
  2112.                         $close_pos = strpos($code, $next_match_pointer['close'], $i);
  2113.                         if ($close_pos == false) {
  2114.                             break;
  2115.                         }
  2116.                         $i = $close_pos + $next_match_pointer['close_strlen'];
  2117.                         if ($i == $length) {
  2118.                             break;
  2119.                         }
  2120.                         if ($code[$i] == $next_match_pointer['open'][0] && ($next_match_pointer['open_strlen'] == 1 ||
  2121.                             substr($code, $i, $next_match_pointer['open_strlen']) == $next_match_pointer['open'])) {
  2122.                             // merge adjacent but make sure we don't merge things like <tag><!-- comment -->
  2123.                             foreach ($matches as $submatches) {
  2124.                                 foreach ($submatches as $match) {
  2125.                                     if ($match['next_match'] == $i) {
  2126.                                         // a different block already matches here!
  2127.                                         break 3;
  2128.                                     }
  2129.                                 }
  2130.                             }
  2131.                         } else {
  2132.                             break;
  2133.                         }
  2134.                     }
  2135.                 } else {
  2136.                     $close_pos = $next_match_pointer['close_pos'] + $next_match_pointer['close_strlen'];
  2137.                     $i = $close_pos;
  2138.                 }
  2139.  
  2140.                 if ($close_pos === false) {
  2141.                     // no closing delimiter found!
  2142.                     $parts[$k][1] = substr($code, $next_match_pos);
  2143.                     ++$k;
  2144.                     break;
  2145.                 } else {
  2146.                     $parts[$k][1] = substr($code, $next_match_pos, $i - $next_match_pos);
  2147.                     ++$k;
  2148.                 }
  2149.             }
  2150.             unset($delim_copy, $next_match_pointer, $next_match_pos, $matches);
  2151.             $num_parts = $k;
  2152.  
  2153.             if ($num_parts == 1 && $this->strict_mode == GESHI_MAYBE) {
  2154.                 // when we have only one part, we don't have anything to highlight at all.
  2155.                 // if we have a "maybe" strict language, this should be handled as highlightable code
  2156.                 $parts = array(
  2157.                     0 => array(
  2158.                         0 => '',
  2159.                         1 => ''
  2160.                     ),
  2161.                     1 => array(
  2162.                         0 => null,
  2163.                         1 => $parts[0][1]
  2164.                     )
  2165.                 );
  2166.                 $num_parts = 2;
  2167.             }
  2168.  
  2169.         } else {
  2170.             // Not strict mode - simply dump the source into
  2171.             // the array at index 1 (the first highlightable block)
  2172.             $parts = array(
  2173.                 0 => array(
  2174.                     0 => '',
  2175.                     1 => ''
  2176.                 ),
  2177.                 1 => array(
  2178.                     0 => null,
  2179.                     1 => $code
  2180.                 )
  2181.             );
  2182.             $num_parts = 2;
  2183.         }
  2184.  
  2185.         //Unset variables we won't need any longer
  2186.         unset($code);
  2187.  
  2188.         //Preload some repeatedly used values regarding hardquotes ...
  2189.         $hq = isset($this->language_data['HARDQUOTE']) ? $this->language_data['HARDQUOTE'][0] : false;
  2190.         $hq_strlen = strlen($hq);
  2191.  
  2192.         //Preload if line numbers are to be generated afterwards
  2193.         //Added a check if line breaks should be forced even without line numbers, fixes SF#1727398
  2194.         $check_linenumbers = $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> ||
  2195.             !empty($this->highlight_extra_lines) || !$this->allow_multiline_span;
  2196.  
  2197.         //preload the escape char for faster checking ...
  2198.         $escaped_escape_char = $this->hsc($this->language_data['ESCAPE_CHAR']);
  2199.  
  2200.         // this is used for single-line comments
  2201.         $sc_disallowed_before = "";
  2202.         $sc_disallowed_after = "";
  2203.  
  2204.         if (isset($this->language_data['PARSER_CONTROL'])) {
  2205.             if (isset($this->language_data['PARSER_CONTROL']['COMMENTS'])) {
  2206.                 if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'])) {
  2207.                     $sc_disallowed_before = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'];
  2208.                 }
  2209.                 if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'])) {
  2210.                     $sc_disallowed_after = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'];
  2211.                 }
  2212.             }
  2213.         }
  2214.  
  2215.         //Fix for SF#1932083: Multichar Quotemarks unsupported
  2216.         $is_string_starter = array();
  2217.         if ($this->lexic_permissions['STRINGS']) {
  2218.             foreach ($this->language_data['QUOTEMARKS'] as $quotemark) {
  2219.                 if (!isset($is_string_starter[$quotemark[0]])) {
  2220.                     $is_string_starter[$quotemark[0]] = (string)$quotemark;
  2221.                 } else if (is_string($is_string_starter[$quotemark[0]])) {
  2222.                     $is_string_starter[$quotemark[0]] = array(
  2223.                         $is_string_starter[$quotemark[0]],
  2224.                         $quotemark);
  2225.                 } else {
  2226.                     $is_string_starter[$quotemark[0]][] = $quotemark;
  2227.                 }
  2228.             }
  2229.         }
  2230.  
  2231.         // Now we go through each part. We know that even-indexed parts are
  2232.         // code that shouldn't be highlighted, and odd-indexed parts should
  2233.         // be highlighted
  2234.         for ($key = 0; $key < $num_parts; ++$key) {
  2235.             $STRICTATTRS = '';
  2236.  
  2237.             // If this block should be highlighted...
  2238.             if (!($key & 1)) {
  2239.                 // Else not a block to highlight
  2240.                 $endresult .= $this->hsc($parts[$key][1]);
  2241.                 unset($parts[$key]);
  2242.                 continue;
  2243.             }
  2244.  
  2245.             $result = '';
  2246.             $part = $parts[$key][1];
  2247.  
  2248.             $highlight_part = true;
  2249.             if ($this->strict_mode && !is_null($parts[$key][0])) {
  2250.                 // get the class key for this block of code
  2251.                 $script_key = $parts[$key][0];
  2252.                 $highlight_part = $this->language_data['HIGHLIGHT_STRICT_BLOCK'][$script_key];
  2253.                 if ($this->language_data['STYLES']['SCRIPT'][$script_key] != '' &&
  2254.                     $this->lexic_permissions['SCRIPT']) {
  2255.                     // Add a span element around the source to
  2256.                     // highlight the overall source block
  2257.                     if (!$this->use_classes &&
  2258.                         $this->language_data['STYLES']['SCRIPT'][$script_key] != '') {
  2259.                         $attributes = ' style="' . $this->language_data['STYLES']['SCRIPT'][$script_key] . '"';
  2260.                     } else {
  2261.                         $attributes = ' class="sc' . $script_key . '"';
  2262.                     }
  2263.                     $result .= "<span$attributes>";
  2264.                     $STRICTATTRS = $attributes;
  2265.                 }
  2266.             }
  2267.  
  2268.             if ($highlight_part) {
  2269.                 // Now, highlight the code in this block. This code
  2270.                 // is really the engine of GeSHi (along with the method
  2271.                 // parse_non_string_part).
  2272.                 // cache comment regexps incrementally
  2273.                 $next_comment_regexp_key = '';
  2274.                 $next_comment_regexp_pos = -1;
  2275.                 $next_comment_multi_pos = -1;
  2276.                 $next_comment_single_pos = -1;
  2277.                 $comment_regexp_cache_per_key = array();
  2278.                 $comment_multi_cache_per_key = array();
  2279.                 $comment_single_cache_per_key = array();
  2280.                 $next_open_comment_multi = '';
  2281.                 $next_comment_single_key = '';
  2282.                 $escape_regexp_cache_per_key = array();
  2283.                 $next_escape_regexp_key = '';
  2284.                 $next_escape_regexp_pos = -1;
  2285.  
  2286.                 $length = strlen($part);
  2287.                 for ($i = 0; $i < $length; ++$i) {
  2288.                     // Get the next char
  2289.                     $char = $part[$i];
  2290.                     $char_len = 1;
  2291.  
  2292.                     $string_started = false;
  2293.  
  2294.                     if (isset($is_string_starter[$char])) {
  2295.                         // Possibly the start of a new string ...
  2296.                         //Check which starter it was ...
  2297.                         //Fix for SF#1932083: Multichar Quotemarks unsupported
  2298.                         if (is_array($is_string_starter[$char])) {
  2299.                             $char_new = '';
  2300.                             foreach ($is_string_starter[$char] as $testchar) {
  2301.                                 if ($testchar === substr($part, $i, strlen($testchar)) &&
  2302.                                     strlen($testchar) > strlen($char_new)) {
  2303.                                     $char_new = $testchar;
  2304.                                     $string_started = true;
  2305.                                 }
  2306.                             }
  2307.                             if ($string_started) {
  2308.                                 $char = $char_new;
  2309.                             }
  2310.                         } else {
  2311.                             $testchar = $is_string_starter[$char];
  2312.                             if ($testchar === substr($part, $i, strlen($testchar))) {
  2313.                                 $char = $testchar;
  2314.                                 $string_started = true;
  2315.                             }
  2316.                         }
  2317.                         $char_len = strlen($char);
  2318.                     }
  2319.  
  2320.                     if ($string_started) {
  2321.                         // Hand out the correct style information for this string
  2322.                         $string_key = array_search($char, $this->language_data['QUOTEMARKS']);
  2323.                         if (!isset($this->language_data['STYLES']['STRINGS'][$string_key]) ||
  2324.                             !isset($this->language_data['STYLES']['ESCAPE_CHAR'][$string_key])) {
  2325.                             $string_key = 0;
  2326.                         }
  2327.  
  2328.                         // parse the stuff before this
  2329.                         $result .= $this->parse_non_string_part($stuff_to_parse);
  2330.                         $stuff_to_parse = '';
  2331.  
  2332.                         if (!$this->use_classes) {
  2333.                             $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][$string_key] . '"';
  2334.                         } else {
  2335.                             $string_attributes = ' class="st'.$string_key.'"';
  2336.                         }
  2337.  
  2338.                         // now handle the string
  2339.                         $string = "<span$string_attributes>GeSHi::hsc($char);
  2340.                         $start = $i + $char_len;
  2341.                         $string_open = true;
  2342.  
  2343.                         if(empty($this->language_data['ESCAPE_REGEXP'])) {
  2344.                             $next_escape_regexp_pos = $length;
  2345.                         }
  2346.  
  2347.                         do {
  2348.                             //Get the regular ending pos ...
  2349.                             $close_pos = strpos($part, $char, $start);
  2350.                             if(false === $close_pos) {
  2351.                                 $close_pos = $length;
  2352.                             }
  2353.  
  2354.                             if($this->lexic_permissions['ESCAPE_CHAR']) {
  2355.                                 // update escape regexp cache if needed
  2356.                                 if (isset($this->language_data['ESCAPE_REGEXP']) && $next_escape_regexp_pos < $start) {
  2357.                                     $next_escape_regexp_pos = $length;
  2358.                                     foreach ($this->language_data['ESCAPE_REGEXP'] as $escape_key => $regexp) {
  2359.                                         $match_i = false;
  2360.                                         if (isset($escape_regexp_cache_per_key[$escape_key]) &&
  2361.                                             ($escape_regexp_cache_per_key[$escape_key]['pos'] >= $start ||
  2362.                                              $escape_regexp_cache_per_key[$escape_key]['pos'] === false)) {
  2363.                                             // we have already matched something
  2364.                                             if ($escape_regexp_cache_per_key[$escape_key]['pos'] === false) {
  2365.                                                 // this comment is never matched
  2366.                                                 continue;
  2367.                                             }
  2368.                                             $match_i = $escape_regexp_cache_per_key[$escape_key]['pos'];
  2369.                                         } else if (
  2370.                                             //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
  2371.                                             (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $start), $match, PREG_OFFSET_CAPTURE)) ||
  2372.                                             (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $start))
  2373.                                             ) {
  2374.                                             $match_i = $match[0][1];
  2375.                                             if (GESHI_PHP_PRE_433) {
  2376.                                                 $match_i += $start;
  2377.                                             }
  2378.  
  2379.                                             $escape_regexp_cache_per_key[$escape_key] = array(
  2380.                                                 'key' => $escape_key,
  2381.                                                 'length' => strlen($match[0][0]),
  2382.                                                 'pos' => $match_i
  2383.                                             );
  2384.                                         } else {
  2385.                                             $escape_regexp_cache_per_key[$escape_key]['pos'] = false;
  2386.                                             continue;
  2387.                                         }
  2388.  
  2389.                                         if ($match_i !== false && $match_i < $next_escape_regexp_pos) {
  2390.                                             $next_escape_regexp_pos = $match_i;
  2391.                                             $next_escape_regexp_key = $escape_key;
  2392.                                             if ($match_i === $start) {
  2393.                                                 break;
  2394.                                             }
  2395.                                         }
  2396.                                     }
  2397.                                 }
  2398.  
  2399.                                 //Find the next simple escape position
  2400.                                 if('' != $this->language_data['ESCAPE_CHAR']) {
  2401.                                     $simple_escape = strpos($part, $this->language_data['ESCAPE_CHAR'], $start);
  2402.                                     if(false === $simple_escape) {
  2403.                                         $simple_escape = $length;
  2404.                                     }
  2405.                                 } else {
  2406.                                     $simple_escape = $length;
  2407.                                 }
  2408.                             } else {
  2409.                                 $next_escape_regexp_pos = $length;
  2410.                                 $simple_escape = $length;
  2411.                             }
  2412.  
  2413.                             if($simple_escape < $next_escape_regexp_pos &&
  2414.                                 $simple_escape < $length &&
  2415.                                 $simple_escape < $close_pos) {
  2416.                                 //The nexxt escape sequence is a simple one ...
  2417.                                 $es_pos = $simple_escape;
  2418.  
  2419.                                 //Add the stuff not in the string yet ...
  2420.                                 $string .= $this->hsc(substr($part, $start, $es_pos - $start));
  2421.  
  2422.                                 //Get the style for this escaped char ...
  2423.                                 if (!$this->use_classes) {
  2424.                                     $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][0] . '"';
  2425.                                 } else {
  2426.                                     $escape_char_attributes = ' class="es0"';
  2427.                                 }
  2428.  
  2429.                                 //Add the style for the escape char ...
  2430.                                 $string .= "<span$escape_char_attributes>.
  2431.                                     GeSHi::hsc($this->language_data['ESCAPE_CHAR']);
  2432.  
  2433.                                 //Get the byte AFTER the ESCAPE_CHAR we just found
  2434.                                 $es_char = $part[$es_pos + 1];
  2435.                                 if ($es_char == "\n") {
  2436.                                     // don't put a newline around newlines
  2437.                                     $string .= "</span>\n";
  2438.                                     $start = $es_pos + 2;
  2439.                                 } else if (ord($es_char) >= 128) {
  2440.                                     //This is an non-ASCII char (UTF8 or single byte)
  2441.                                     //This code tries to work around SF#2037598 ...
  2442.                                     if(function_exists('mb_substr')) {
  2443.                                         $es_char_m = mb_substr(substr($part, $es_pos+1, 16), 0, 1, $this->encoding);
  2444.                                         $string .= $es_char_m . '</span>';
  2445.                                     } else if (!GESHI_PHP_PRE_433 && 'utf-8' == $this->encoding) {
  2446.                                         if(preg_match("/[\xC2-\xDF][\x80-\xBF]".
  2447.                                             "|\xE0[\xA0-\xBF][\x80-\xBF]".
  2448.                                             "|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}".
  2449.                                             "|\xED[\x80-\x9F][\x80-\xBF]".
  2450.                                             "|\xF0[\x90-\xBF][\x80-\xBF]{2}".
  2451.                                             "|[\xF1-\xF3][\x80-\xBF]{3}".
  2452.                                             "|\xF4[\x80-\x8F][\x80-\xBF]{2}/s",
  2453.                                             $part, $es_char_m, null, $es_pos + 1)) {
  2454.                                             $es_char_m = $es_char_m[0];
  2455.                                         } else {
  2456.                                             $es_char_m = $es_char;
  2457.                                         }
  2458.                                         $string .= $this->hsc($es_char_m) . '</span>';
  2459.                                     } else {
  2460.                                         $es_char_m = $this->hsc($es_char);
  2461.                                     }
  2462.                                     $start = $es_pos + strlen($es_char_m) + 1;
  2463.                                 } else {
  2464.                                     $string .= $this->hsc($es_char) . '</span>';
  2465.                                     $start = $es_pos + 2;
  2466.                                 }
  2467.                             } else if ($next_escape_regexp_pos < $length &&
  2468.                                 $next_escape_regexp_pos < $close_pos) {
  2469.                                 $es_pos = $next_escape_regexp_pos;
  2470.                                 //Add the stuff not in the string yet ...
  2471.                                 $string .= $this->hsc(substr($part, $start, $es_pos - $start));
  2472.  
  2473.                                 //Get the key and length of this match ...
  2474.                                 $escape = $escape_regexp_cache_per_key[$next_escape_regexp_key];
  2475.                                 $escape_str = substr($part, $es_pos, $escape['length']);
  2476.                                 $escape_key = $escape['key'];
  2477.  
  2478.                                 //Get the style for this escaped char ...
  2479.                                 if (!$this->use_classes) {
  2480.                                     $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][$escape_key] . '"';
  2481.                                 } else {
  2482.                                     $escape_char_attributes = ' class="es' . $escape_key . '"';
  2483.                                 }
  2484.  
  2485.                                 //Add the style for the escape char ...
  2486.                                 $string .= "<span$escape_char_attributes>.
  2487.                                     $this->hsc($escape_str) . '</span>';
  2488.  
  2489.                                 $start = $es_pos + $escape['length'];
  2490.                             } else {
  2491.                                 //Copy the remainder of the string ...
  2492.                                 $string .= $this->hsc(substr($part, $start, $close_pos - $start + $char_len)) . '</span>';
  2493.                                 $start = $close_pos + $char_len;
  2494.                                 $string_open = false;
  2495.                             }
  2496.                         } while($string_open);
  2497.  
  2498.                         if ($check_linenumbers) {
  2499.                             // Are line numbers used? If, we should end the string before
  2500.                             // the newline and begin it again (so when <li>s are put in the source
  2501.                             // remains XHTML compliant)
  2502.                             // note to self: This opens up possibility of config files specifying
  2503.                             // that languages can/cannot have multiline strings???
  2504.                             $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
  2505.                         }
  2506.  
  2507.                         $result .= $string;
  2508.                         $string = '';
  2509.                         $i = $start - 1;
  2510.                         continue;
  2511.                     } else if ($this->lexic_permissions['STRINGS'] && $hq && $hq[0] == $char &&
  2512.                         substr($part, $i, $hq_strlen) == $hq) {
  2513.                         // The start of a hard quoted string
  2514.                         if (!$this->use_classes) {
  2515.                             $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS']['HARD'] . '"';
  2516.                             $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR']['HARD'] . '"';
  2517.                         } else {
  2518.                             $string_attributes = ' class="st_h"';
  2519.                             $escape_char_attributes = ' class="es_h"';
  2520.                         }
  2521.                         // parse the stuff before this
  2522.                         $result .= $this->parse_non_string_part($stuff_to_parse);
  2523.                         $stuff_to_parse = '';
  2524.  
  2525.                         // now handle the string
  2526.                         $string = '';
  2527.  
  2528.                         // look for closing quote
  2529.                         $start = $i + $hq_strlen;
  2530.                         while ($close_pos = strpos($part, $this->language_data['HARDQUOTE'][1], $start)) {
  2531.                             $start = $close_pos + 1;
  2532.                             if ($this->lexic_permissions['ESCAPE_CHAR'] && $part[$close_pos - 1] == $this->language_data['ESCAPE_CHAR']) {
  2533.                                 // make sure this quote is not escaped
  2534.                                 foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
  2535.                                     if (substr($part, $close_pos - 1, strlen($hardescape)) == $hardescape) {
  2536.                                         // check wether this quote is escaped or if it is something like '\\'
  2537.                                         $escape_char_pos = $close_pos - 1;
  2538.                                         while ($escape_char_pos > 0
  2539.                                                 && $part[$escape_char_pos - 1] == $this->language_data['ESCAPE_CHAR']) {
  2540.                                             --$escape_char_pos;
  2541.                                         }
  2542.                                         if (($close_pos - $escape_char_pos) & 1) {
  2543.                                             // uneven number of escape chars => this quote is escaped
  2544.                                             continue 2;
  2545.                                         }
  2546.                                     }
  2547.                                 }
  2548.                             }
  2549.  
  2550.                             // found closing quote
  2551.                             break;
  2552.                         }
  2553.  
  2554.                         //Found the closing delimiter?
  2555.                         if (!$close_pos) {
  2556.                             // span till the end of this $part when no closing delimiter is found
  2557.                             $close_pos = $length;
  2558.                         }
  2559.  
  2560.                         //Get the actual string
  2561.                         $string = substr($part, $i, $close_pos - $i + 1);
  2562.                         $i = $close_pos;
  2563.  
  2564.                         // handle escape chars and encode html chars
  2565.                         // (special because when we have escape chars within our string they may not be escaped)
  2566.                         if ($this->lexic_permissions['ESCAPE_CHAR'] && $this->language_data['ESCAPE_CHAR']) {
  2567.                             $start = 0;
  2568.                             $new_string = '';
  2569.                             while ($es_pos = strpos($string, $this->language_data['ESCAPE_CHAR'], $start)) {
  2570.                                 // hmtl escape stuff before
  2571.                                 $new_string .= $this->hsc(substr($string, $start, $es_pos - $start));
  2572.                                 // check if this is a hard escape
  2573.                                 foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
  2574.                                     if (substr($string, $es_pos, strlen($hardescape)) == $hardescape) {
  2575.                                         // indeed, this is a hardescape
  2576.                                         $new_string .= "<span$escape_char_attributes>.
  2577.                                             $this->hsc($hardescape) . '</span>';
  2578.                                         $start = $es_pos + strlen($hardescape);
  2579.                                         continue 2;
  2580.                                     }
  2581.                                 }
  2582.                                 // not a hard escape, but a normal escape
  2583.                                 // they come in pairs of two
  2584.                                 $c = 0;
  2585.                                 while (isset($string[$es_pos + $c]) && isset($string[$es_pos + $c + 1])
  2586.                                     && $string[$es_pos + $c] == $this->language_data['ESCAPE_CHAR']
  2587.                                     && $string[$es_pos + $c + 1] == $this->language_data['ESCAPE_CHAR']) {
  2588.                                     $c += 2;
  2589.                                 }
  2590.                                 if ($c) {
  2591.                                     $new_string .= "<span$escape_char_attributes>.
  2592.                                         str_repeat($escaped_escape_char, $c) .
  2593.                                         '</span>';
  2594.                                     $start = $es_pos + $c;
  2595.                                 } else {
  2596.                                     // this is just a single lonely escape char...
  2597.                                     $new_string .= $escaped_escape_char;
  2598.                                     $start = $es_pos + 1;
  2599.                                 }
  2600.                             }
  2601.                             $string = $new_string . $this->hsc(substr($string, $start));
  2602.                         } else {
  2603.                             $string = $this->hsc($string);
  2604.                         }
  2605.  
  2606.                         if ($check_linenumbers) {
  2607.                             // Are line numbers used? If, we should end the string before
  2608.                             // the newline and begin it again (so when <li>s are put in the source
  2609.                             // remains XHTML compliant)
  2610.                             // note to self: This opens up possibility of config files specifying
  2611.                             // that languages can/cannot have multiline strings???
  2612.                             $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
  2613.                         }
  2614.  
  2615.                         $result .= "<span$string_attributes>$string . '</span>';
  2616.                         $string = '';
  2617.                         continue;
  2618.                     } else {
  2619.                         // update regexp comment cache if needed
  2620.                         if (isset($this->language_data['COMMENT_REGEXP']) && $next_comment_regexp_pos < $i) {
  2621.                             $next_comment_regexp_pos = $length;
  2622.                             foreach ($this->language_data['COMMENT_REGEXP'] as $comment_key => $regexp) {
  2623.                                 $match_i = false;
  2624.                                 if (isset($comment_regexp_cache_per_key[$comment_key]) &&
  2625.                                     ($comment_regexp_cache_per_key[$comment_key]['pos'] >= $i ||
  2626.                                      $comment_regexp_cache_per_key[$comment_key]['pos'] === false)) {
  2627.                                     // we have already matched something
  2628.                                     if ($comment_regexp_cache_per_key[$comment_key]['pos'] === false) {
  2629.                                         // this comment is never matched
  2630.                                         continue;
  2631.                                     }
  2632.                                     $match_i = $comment_regexp_cache_per_key[$comment_key]['pos'];
  2633.                                 } else if (
  2634.                                     //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
  2635.                                     (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $i), $match, PREG_OFFSET_CAPTURE)) ||
  2636.                                     (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $i))
  2637.                                     ) {
  2638.                                     $match_i = $match[0][1];
  2639.                                     if (GESHI_PHP_PRE_433) {
  2640.                                         $match_i += $i;
  2641.                                     }
  2642.  
  2643.                                     $comment_regexp_cache_per_key[$comment_key] = array(
  2644.                                         'key' => $comment_key,
  2645.                                         'length' => strlen($match[0][0]),
  2646.                                         'pos' => $match_i
  2647.                                     );
  2648.                                 } else {
  2649.                                     $comment_regexp_cache_per_key[$comment_key]['pos'] = false;
  2650.                                     continue;
  2651.                                 }
  2652.  
  2653.                                 if ($match_i !== false && $match_i < $next_comment_regexp_pos) {
  2654.                                     $next_comment_regexp_pos = $match_i;
  2655.                                     $next_comment_regexp_key = $comment_key;
  2656.                                     if ($match_i === $i) {
  2657.                                         break;
  2658.                                     }
  2659.                                 }
  2660.                             }
  2661.                         }
  2662.                         //Have a look for regexp comments
  2663.                         if ($i == $next_comment_regexp_pos) {
  2664.                             $COMMENT_MATCHED = true;
  2665.                             $comment = $comment_regexp_cache_per_key[$next_comment_regexp_key];
  2666.                             $test_str = $this->hsc(substr($part, $i, $comment['length']));
  2667.  
  2668.                             //@todo If remove important do remove here
  2669.                             if ($this->lexic_permissions['COMMENTS']['MULTI']) {
  2670.                                 if (!$this->use_classes) {
  2671.                                     $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment['key']] . '"';
  2672.                                 } else {
  2673.                                     $attributes = ' class="co' . $comment['key'] . '"';
  2674.                                 }
  2675.  
  2676.                                 $test_str = "<span$attributes>$test_str . "</span>";
  2677.  
  2678.                                 // Short-cut through all the multiline code
  2679.                                 if ($check_linenumbers) {
  2680.                                     // strreplace to put close span and open span around multiline newlines
  2681.                                     $test_str = str_replace(
  2682.                                         "\n", "</span>\n<span$attributes>",
  2683.                                         str_replace("\n ", "\n&nbsp;", $test_str)
  2684.                                     );
  2685.                                 }
  2686.                             }
  2687.  
  2688.                             $i += $comment['length'] - 1;
  2689.  
  2690.                             // parse the rest
  2691.                             $result .= $this->parse_non_string_part($stuff_to_parse);
  2692.                             $stuff_to_parse = '';
  2693.                         }
  2694.  
  2695.                         // If we haven't matched a regexp comment, try multi-line comments
  2696.                         if (!$COMMENT_MATCHED) {
  2697.                             // Is this a multiline comment?
  2698.                             if (!empty($this->language_data['COMMENT_MULTI']) && $next_comment_multi_pos < $i) {
  2699.                                 $next_comment_multi_pos = $length;
  2700.                                 foreach ($this->language_data['COMMENT_MULTI'] as $open => $close) {
  2701.                                     $match_i = false;
  2702.                                     if (isset($comment_multi_cache_per_key[$open]) &&
  2703.                                         ($comment_multi_cache_per_key[$open] >= $i ||
  2704.                                          $comment_multi_cache_per_key[$open] === false)) {
  2705.                                         // we have already matched something
  2706.                                         if ($comment_multi_cache_per_key[$open] === false) {
  2707.                                             // this comment is never matched
  2708.                                             continue;
  2709.                                         }
  2710.                                         $match_i = $comment_multi_cache_per_key[$open];
  2711.                                     } else if (($match_i = stripos($part, $open, $i)) !== false) {
  2712.                                         $comment_multi_cache_per_key[$open] = $match_i;
  2713.                                     } else {
  2714.                                         $comment_multi_cache_per_key[$open] = false;
  2715.                                         continue;
  2716.                                     }
  2717.                                     if ($match_i !== false && $match_i < $next_comment_multi_pos) {
  2718.                                         $next_comment_multi_pos = $match_i;
  2719.                                         $next_open_comment_multi = $open;
  2720.                                         if ($match_i === $i) {
  2721.                                             break;
  2722.                                         }
  2723.                                     }
  2724.                                 }
  2725.                             }
  2726.                             if ($i == $next_comment_multi_pos) {
  2727.                                 $open = $next_open_comment_multi;
  2728.                                 $close = $this->language_data['COMMENT_MULTI'][$open];
  2729.                                 $open_strlen = strlen($open);
  2730.                                 $close_strlen = strlen($close);
  2731.                                 $COMMENT_MATCHED = true;
  2732.                                 $test_str_match = $open;
  2733.                                 //@todo If remove important do remove here
  2734.                                 if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
  2735.                                     $open == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
  2736.                                     if ($open != <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
  2737.                                         if (!$this->use_classes) {
  2738.                                             $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS']['MULTI'] . '"';
  2739.                                         } else {
  2740.                                             $attributes = ' class="coMULTI"';
  2741.                                         }
  2742.                                         $test_str = "<span$attributes>$this->hsc($open);
  2743.                                     } else {
  2744.                                         if (!$this->use_classes) {
  2745.                                             $attributes = ' style="' . $this->important_styles . '"';
  2746.                                         } else {
  2747.                                             $attributes = ' class="imp"';
  2748.                                         }
  2749.  
  2750.                                         // We don't include the start of the comment if it's an
  2751.                                         // "important" part
  2752.                                         $test_str = "<span$attributes>";
  2753.                                     }
  2754.                                 } else {
  2755.                                     $test_str = $this->hsc($open);
  2756.                                 }
  2757.  
  2758.                                 $close_pos = strpos( $part, $close, $i + $open_strlen );
  2759.  
  2760.                                 if ($close_pos === false) {
  2761.                                     $close_pos = $length;
  2762.                                 }
  2763.  
  2764.                                 // Short-cut through all the multiline code
  2765.                                 $rest_of_comment = $this->hsc(substr($part, $i + $open_strlen, $close_pos - $i - $open_strlen + $close_strlen));
  2766.                                 if (($this->lexic_permissions['COMMENTS']['MULTI'] ||
  2767.                                     $test_str_match == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) &&
  2768.                                     $check_linenumbers) {
  2769.  
  2770.                                     // strreplace to put close span and open span around multiline newlines
  2771.                                     $test_str .= str_replace(
  2772.                                         "\n", "</span>\n<span$attributes>",
  2773.                                         str_replace("\n ", "\n&nbsp;", $rest_of_comment)
  2774.                                     );
  2775.                                 } else {
  2776.                                     $test_str .= $rest_of_comment;
  2777.                                 }
  2778.  
  2779.                                 if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
  2780.                                     $test_str_match == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
  2781.                                     $test_str .= '</span>';
  2782.                                 }
  2783.  
  2784.                                 $i = $close_pos + $close_strlen - 1;
  2785.  
  2786.                                 // parse the rest
  2787.                                 $result .= $this->parse_non_string_part($stuff_to_parse);
  2788.                                 $stuff_to_parse = '';
  2789.                             }
  2790.                         }
  2791.  
  2792.                         // If we haven't matched a multiline comment, try single-line comments
  2793.                         if (!$COMMENT_MATCHED) {
  2794.                             // cache potential single line comment occurances
  2795.                             if (!empty($this->language_data['COMMENT_SINGLE']) && $next_comment_single_pos < $i) {
  2796.                                 $next_comment_single_pos = $length;
  2797.                                 foreach ($this->language_data['COMMENT_SINGLE'] as $comment_key => $comment_mark) {
  2798.                                     $match_i = false;
  2799.                                     if (isset($comment_single_cache_per_key[$comment_key]) &&
  2800.                                         ($comment_single_cache_per_key[$comment_key] >= $i ||
  2801.                                          $comment_single_cache_per_key[$comment_key] === false)) {
  2802.                                         // we have already matched something
  2803.                                         if ($comment_single_cache_per_key[$comment_key] === false) {
  2804.                                             // this comment is never matched
  2805.                                             continue;
  2806.                                         }
  2807.                                         $match_i = $comment_single_cache_per_key[$comment_key];
  2808.                                     } else if (
  2809.                                         // case sensitive comments
  2810.                                         ($this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
  2811.                                         ($match_i = stripos($part, $comment_mark, $i)) !== false) ||
  2812.                                         // non case sensitive
  2813.                                         (!$this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
  2814.                                           (($match_i = strpos($part, $comment_mark, $i)) !== false))) {
  2815.                                         $comment_single_cache_per_key[$comment_key] = $match_i;
  2816.                                     } else {
  2817.                                         $comment_single_cache_per_key[$comment_key] = false;
  2818.                                         continue;
  2819.                                     }
  2820.                                     if ($match_i !== false && $match_i < $next_comment_single_pos) {
  2821.                                         $next_comment_single_pos = $match_i;
  2822.                                         $next_comment_single_key = $comment_key;
  2823.                                         if ($match_i === $i) {
  2824.                                             break;
  2825.                                         }
  2826.                                     }
  2827.                                 }
  2828.                             }
  2829.                             if ($next_comment_single_pos == $i) {
  2830.                                 $comment_key = $next_comment_single_key;
  2831.                                 $comment_mark = $this->language_data['COMMENT_SINGLE'][$comment_key];
  2832.                                 $com_len = strlen($comment_mark);
  2833.  
  2834.                                 // This check will find special variables like $# in bash
  2835.                                 // or compiler directives of Delphi beginning {$
  2836.                                 if ((empty($sc_disallowed_before) || ($i == 0) ||
  2837.                                     (false === strpos($sc_disallowed_before, $part[$i-1]))) &&
  2838.                                     (empty($sc_disallowed_after) || ($length <= $i + $com_len) ||
  2839.                                     (false === strpos($sc_disallowed_after, $part[$i + $com_len]))))
  2840.                                 {
  2841.                                     // this is a valid comment
  2842.                                     $COMMENT_MATCHED = true;
  2843.                                     if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
  2844.                                         if (!$this->use_classes) {
  2845.                                             $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment_key] . '"';
  2846.                                         } else {
  2847.                                             $attributes = ' class="co' . $comment_key . '"';
  2848.                                         }
  2849.                                         $test_str = "<span$attributes>$this->hsc($this->change_case($comment_mark));
  2850.                                     } else {
  2851.                                         $test_str = $this->hsc($comment_mark);
  2852.                                     }
  2853.  
  2854.                                     //Check if this comment is the last in the source
  2855.                                     $close_pos = strpos($part, "\n", $i);
  2856.                                     $oops = false;
  2857.                                     if ($close_pos === false) {
  2858.                                         $close_pos = $length;
  2859.                                         $oops = true;
  2860.                                     }
  2861.                                     $test_str .= $this->hsc(substr($part, $i + $com_len, $close_pos - $i - $com_len));
  2862.                                     if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
  2863.                                         $test_str .= "</span>";
  2864.                                     }
  2865.  
  2866.                                     // Take into account that the comment might be the last in the source
  2867.                                     if (!$oops) {
  2868.                                       $test_str .= "\n";
  2869.                                     }
  2870.  
  2871.                                     $i = $close_pos;
  2872.  
  2873.                                     // parse the rest
  2874.                                     $result .= $this->parse_non_string_part($stuff_to_parse);
  2875.                                     $stuff_to_parse = '';
  2876.                                 }
  2877.                             }
  2878.                         }
  2879.                     }
  2880.  
  2881.                     // Where are we adding this char?
  2882.                     if (!$COMMENT_MATCHED) {
  2883.                         $stuff_to_parse .= $char;
  2884.                     } else {
  2885.                         $result .= $test_str;
  2886.                         unset($test_str);
  2887.                         $COMMENT_MATCHED = false;
  2888.                     }
  2889.                 }
  2890.                 // Parse the last bit
  2891.                 $result .= $this->parse_non_string_part($stuff_to_parse);
  2892.                 $stuff_to_parse = '';
  2893.             } else {
  2894.                 $result .= $this->hsc($part);
  2895.             }
  2896.             // Close the <span> that surrounds the block
  2897.             if ($STRICTATTRS != '') {
  2898.                 $result = str_replace("\n", "</span>\n<span$STRICTATTRS>", $result);
  2899.                 $result .= '</span>';
  2900.             }
  2901.  
  2902.             $endresult .= $result;
  2903.             unset($part, $parts[$key], $result);
  2904.         }
  2905.  
  2906.         //This fix is related to SF#1923020, but has to be applied regardless of
  2907.         //actually highlighting symbols.
  2908. /** NOTE: memorypeak #3 */        
  2909.         $endresult = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $endresult);
  2910.  
  2911. //        // Parse the last stuff (redundant?)
  2912. //        $result .= $this->parse_non_string_part($stuff_to_parse);
  2913.         // Lop off the very first and last spaces
  2914. //        $result = substr($result, 1, -1);
  2915.         // We're finished: stop timing
  2916.         $this->set_time($start_time, microtime());
  2917.  
  2918.         $this->finalise($endresult);
  2919.         return $endresult;
  2920.     }
  2921.  
  2922. /**    
  2923.      * Swaps out spaces and tabs for HTML indentation. Not needed if
  2924.      * the code is in a pre block...
  2925.      *
  2926.      * @param  string The source to indent (reference!)
  2927.      * @since  1.0.0
  2928.      * @access private
  2929.      */
  2930.     function indent(&$result) {
  2931.         /// Replace tabs with the correct number of spaces
  2932.         if (false !== strpos($result, "\t")) {
  2933.             $lines = explode("\n", $result);
  2934.             $result = null;//Save memory while we process the lines individually
  2935.             $tab_width = $this->get_real_tab_width();
  2936.             $tab_string = '&nbsp;' . str_repeat(' ', $tab_width);
  2937.  
  2938.             for ($key = 0, $n = count($lines); $key < $n; $key++) {
  2939.                 $line = $lines[$key];
  2940.                 if (false === strpos($line, "\t")) {
  2941.                     continue;
  2942.                 }
  2943.  
  2944.                 $pos = 0;
  2945.                 $length = strlen($line);
  2946.                 $lines[$key] = ''; // reduce memory
  2947.                 $IN_TAG = false;
  2948.                 for ($i = 0; $i < $length; ++$i) {
  2949.                     $char = $line[$i];
  2950.                     // Simple engine to work out whether we're in a tag.
  2951.                     // If we are we modify $pos. This is so we ignore HTML
  2952.                     // in the line and only workout the tab replacement
  2953.                     // via the actual content of the string
  2954.                     // This test could be improved to include strings in the
  2955.                     // html so that < or > would be allowed in user's styles
  2956.                     // (e.g. quotes: '<' '>'; or similar)
  2957.                     if ($IN_TAG) {
  2958.                         if ('>' == $char) {
  2959.                             $IN_TAG = false;
  2960.                         }
  2961.                         $lines[$key] .= $char;
  2962.                     } else if ('<' == $char) {
  2963.                         $IN_TAG = true;
  2964.                         $lines[$key] .= '<';
  2965.                     } else if ('&' == $char) {
  2966.                         $substr = substr($line, $i + 3, 5);
  2967.                         $posi = strpos($substr, ';');
  2968.                         if (false === $posi) {
  2969.                             ++$pos;
  2970.                         } else {
  2971.                             $pos -= $posi+2;
  2972.                         }
  2973.                         $lines[$key] .= $char;
  2974.                     } else if ("\t" == $char) {
  2975.                         $str = '';
  2976.                         // OPTIMISE - move $strs out. Make an array:
  2977.                         // $tabs = array(
  2978.                         //  1 => '&nbsp;',
  2979.                         //  2 => '&nbsp; ',
  2980.                         //  3 => '&nbsp; &nbsp;' etc etc
  2981.                         // to use instead of building a string every time
  2982.                         $tab_end_width = $tab_width - ($pos % $tab_width); //Moved out of the look as it doesn't change within the loop
  2983.                         if (($pos & 1) || 1 == $tab_end_width) {
  2984.                             $str .= substr($tab_string, 6, $tab_end_width);
  2985.                         } else {
  2986.                             $str .= substr($tab_string, 0, $tab_end_width+5);
  2987.                         }
  2988.                         $lines[$key] .= $str;
  2989.                         $pos += $tab_end_width;
  2990.  
  2991.                         if (false === strpos($line, "\t", $i + 1)) {
  2992.                             $lines[$key] .= substr($line, $i + 1);
  2993.                             break;
  2994.                         }
  2995.                     } else if (0 == $pos && ' ' == $char) {
  2996.                         $lines[$key] .= '&nbsp;';
  2997.                         ++$pos;
  2998.                     } else {
  2999.                         $lines[$key] .= $char;
  3000.                         ++$pos;
  3001.                     }
  3002.                 }
  3003.             }
  3004.             $result = implode("\n", $lines);
  3005.             unset($lines);//We don't need the lines separated beyond this --- free them!
  3006.         }
  3007.         // Other whitespace
  3008.         // BenBE: Fix to reduce the number of replacements to be done
  3009.         $result = preg_replace('/^ /m', '&nbsp;', $result);
  3010.         $result = str_replace('  ', ' &nbsp;', $result);
  3011.  
  3012.         if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3013.             if ($this->line_ending === null) {
  3014.                 $result = nl2br($result);
  3015.             } else {
  3016.                 $result = str_replace("\n", $this->line_ending, $result);
  3017.             }
  3018.         }
  3019.     }
  3020.  
  3021. /**    
  3022.      * Changes the case of a keyword for those languages where a change is asked for
  3023.      *
  3024.      * @param  string The keyword to change the case of
  3025.      * @return string The keyword with its case changed
  3026.      * @since  1.0.0
  3027.      * @access private
  3028.      */
  3029.     function change_case($instr) {
  3030.         switch ($this->language_data['CASE_KEYWORDS']) {
  3031.             case <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_UPPER">GESHI_CAPS_UPPER</a>:
  3032.                 return strtoupper($instr);
  3033.             case <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_LOWER">GESHI_CAPS_LOWER</a>:
  3034.                 return strtolower($instr);
  3035.             default:
  3036.                 return $instr;
  3037.         }
  3038.     }
  3039.  
  3040. /**    
  3041.      * Handles replacements of keywords to include markup and links if requested
  3042.      *
  3043.      * @param  string The keyword to add the Markup to
  3044.      * @return The HTML for the match found
  3045.      * @since  1.0.8
  3046.      * @access private
  3047.      *
  3048.      * @todo   Get rid of ender in keyword links
  3049.      */
  3050.     function handle_keyword_replace($match) {
  3051.         $k = $this->_kw_replace_group;
  3052.         $keyword = $match[0];
  3053.  
  3054.         $before = '';
  3055.         $after = '';
  3056.  
  3057.         if ($this->keyword_links) {
  3058.             // Keyword links have been ebabled
  3059.             if (isset($this->language_data['URLS'][$k]) &&
  3060.                 $this->language_data['URLS'][$k] != '') {
  3061.                 // There is a base group for this keyword
  3062.                 // Old system: strtolower
  3063.                 //$keyword = ( $this->language_data['CASE_SENSITIVE'][$group] ) ? $keyword : strtolower($keyword);
  3064.                 // New system: get keyword from language file to get correct case
  3065.                 if (!$this->language_data['CASE_SENSITIVE'][$k] &&
  3066.                     strpos($this->language_data['URLS'][$k], '{FNAME}') !== false) {
  3067.                     foreach ($this->language_data['KEYWORDS'][$k] as $word) {
  3068.                         if (strcasecmp($word, $keyword) == 0) {
  3069.                             break;
  3070.                         }
  3071.                     }
  3072.                 } else {
  3073.                     $word = $keyword;
  3074.                 }
  3075.  
  3076.                 $before = '<|UR1|"' .
  3077.                     str_replace(
  3078.                         array('{FNAME}', '{FNAMEL}', '{FNAMEU}', '.'),
  3079.                         array($this->hsc($word), $this->hsc(strtolower($word)),
  3080.                             $this->hsc(strtoupper($word)), '<DOT>'),
  3081.                         $this->language_data['URLS'][$k]
  3082.                     ) . '">';
  3083.                 $after = '</a>';
  3084.             }
  3085.         }
  3086.  
  3087.         return $before . '<|/'$k .'/>' . $this->change_case($keyword) . '|>' . $after;
  3088.     }
  3089.  
  3090. /**    
  3091.      * handles regular expressions highlighting-definitions with callback functions
  3092.      *
  3093.      * @note this is a callback, don't use it directly
  3094.      *
  3095.      * @param array the matches array
  3096.      * @return The highlighted string
  3097.      * @since 1.0.8
  3098.      * @access private
  3099.      */
  3100.     function handle_regexps_callback($matches) {
  3101.         // before: "' style=\"' . call_user_func(\"$func\", '\\1') . '\"\\1|>'",
  3102.         return  ' style="' . call_user_func($this->language_data['STYLES']['REGEXPS'][$this->_rx_key], $matches[1]) . '"'$matches[1] . '|>';
  3103.     }
  3104.  
  3105. /**    
  3106.      * handles newlines in REGEXPS matches. Set the _hmr_* vars before calling this
  3107.      *
  3108.      * @note this is a callback, don't use it directly
  3109.      *
  3110.      * @param array the matches array
  3111.      * @return string 
  3112.      * @since 1.0.8
  3113.      * @access private
  3114.      */
  3115.     function handle_multiline_regexps($matches) {
  3116.         $before = $this->_hmr_before;
  3117.         $after = $this->_hmr_after;
  3118.         if ($this->_hmr_replace) {
  3119.             $replace = $this->_hmr_replace;
  3120.             $search = array();
  3121.  
  3122.             foreach (array_keys($matches) as $k) {
  3123.                 $search[] = '\\' . $k;
  3124.             }
  3125.  
  3126.             $before = str_replace($search, $matches, $before);
  3127.             $after = str_replace($search, $matches, $after);
  3128.             $replace = str_replace($search, $matches, $replace);
  3129.         } else {
  3130.             $replace = $matches[0];
  3131.         }
  3132.         return $before
  3133.                     . '<|!REG3XP' . $this->_hmr_key .'!>'
  3134.                         . str_replace("\n", "|>\n<|!REG3XP" . $this->_hmr_key . '!>', $replace)
  3135.                     . '|>'
  3136.               . $after;
  3137.     }
  3138.  
  3139. /**    
  3140.      * Takes a string that has no strings or comments in it, and highlights
  3141.      * stuff like keywords, numbers and methods.
  3142.      *
  3143.      * @param string The string to parse for keyword, numbers etc.
  3144.      * @since 1.0.0
  3145.      * @access private
  3146.      * @todo BUGGY! Why? Why not build string and return?
  3147.      */
  3148.     function parse_non_string_part($stuff_to_parse) {
  3149.         $stuff_to_parse = ' ' . $this->hsc($stuff_to_parse);
  3150.  
  3151.         // Regular expressions
  3152.         foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
  3153.             if ($this->lexic_permissions['REGEXPS'][$key]) {
  3154.                 if (is_array($regexp)) {
  3155.                     if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3156.                         // produce valid HTML when we match multiple lines
  3157.                         $this->_hmr_replace = $regexp[GESHI_REPLACE];
  3158.                         $this->_hmr_before = $regexp[GESHI_BEFORE];
  3159.                         $this->_hmr_key = $key;
  3160.                         $this->_hmr_after = $regexp[GESHI_AFTER];
  3161.                         $stuff_to_parse = preg_replace_callback(
  3162.                             "/" . $regexp[GESHI_SEARCH] . "/{$regexp[GESHI_MODIFIERS]}",
  3163.                             array($this, 'handle_multiline_regexps'),
  3164.                             $stuff_to_parse);
  3165.                         $this->_hmr_replace = false;
  3166.                         $this->_hmr_before = '';
  3167.                         $this->_hmr_after = '';
  3168.                     } else {
  3169.                         $stuff_to_parse = preg_replace(
  3170.                             '/' . $regexp[GESHI_SEARCH] . '/' . $regexp[GESHI_MODIFIERS],
  3171.                             $regexp[GESHI_BEFORE] . '<|!REG3XP'$key .'!>' . $regexp[GESHI_REPLACE] . '|>' . $regexp[GESHI_AFTER],
  3172.                             $stuff_to_parse);
  3173.                     }
  3174.                 } else {
  3175.                     if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3176.                         // produce valid HTML when we match multiple lines
  3177.                         $this->_hmr_key = $key;
  3178.                         $stuff_to_parse = preg_replace_callback( "/(" . $regexp . ")/",
  3179.                                               array($this, 'handle_multiline_regexps'), $stuff_to_parse);
  3180.                         $this->_hmr_key = '';
  3181.                     } else {
  3182.                         $stuff_to_parse = preg_replace( "/(" . $regexp . ")/", "<|!REG3XP$key!>\\1|>", $stuff_to_parse);
  3183.                     }
  3184.                 }
  3185.             }
  3186.         }
  3187.  
  3188.         // Highlight numbers. As of 1.0.8 we support diffent types of numbers
  3189.         $numbers_found = false;
  3190.         if ($this->lexic_permissions['NUMBERS'] && preg_match('#\d#', $stuff_to_parse )) {
  3191.             $numbers_found = true;
  3192.  
  3193.             //For each of the formats ...
  3194.             foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
  3195.                 //Check if it should be highlighted ...
  3196.                 $stuff_to_parse = preg_replace($regexp, "<|/NUM!$id/>\\1|>", $stuff_to_parse);
  3197.             }
  3198.         }
  3199.  
  3200.         // Highlight keywords
  3201.         $disallowed_before = "(?<![a-zA-Z0-9\$_\|\#;>|^&";
  3202.         $disallowed_after = "(?![a-zA-Z0-9_\|%\\-&;";
  3203.         if ($this->lexic_permissions['STRINGS']) {
  3204.             $quotemarks = preg_quote(implode($this->language_data['QUOTEMARKS']), '/');
  3205.             $disallowed_before .= $quotemarks;
  3206.             $disallowed_after .= $quotemarks;
  3207.         }
  3208.         $disallowed_before .= "])";
  3209.         $disallowed_after .= "])";
  3210.  
  3211.         $parser_control_pergroup = false;
  3212.         if (isset($this->language_data['PARSER_CONTROL'])) {
  3213.             if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'])) {
  3214.                 $x = 0; // check wether per-keyword-group parser_control is enabled
  3215.                 if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'])) {
  3216.                     $disallowed_before = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'];
  3217.                     ++$x;
  3218.                 }
  3219.                 if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'])) {
  3220.                     $disallowed_after = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'];
  3221.                     ++$x;
  3222.                 }
  3223.                 $parser_control_pergroup = (count($this->language_data['PARSER_CONTROL']['KEYWORDS']) - $x) > 0;
  3224.             }
  3225.         }
  3226.  
  3227.         // if this is changed, don't forget to change it below
  3228. //        if (!empty($disallowed_before)) {
  3229. //            $disallowed_before = "(?<![$disallowed_before])";
  3230. //        }
  3231. //        if (!empty($disallowed_after)) {
  3232. //            $disallowed_after = "(?![$disallowed_after])";
  3233. //        }
  3234.         foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
  3235.             if (!isset($this->lexic_permissions['KEYWORDS'][$k]) ||
  3236.                 $this->lexic_permissions['KEYWORDS'][$k]) {
  3237.  
  3238.                 $case_sensitive = $this->language_data['CASE_SENSITIVE'][$k];
  3239.                 $modifiers = $case_sensitive ? '' : 'i';
  3240.  
  3241.                 // NEW in 1.0.8 - per-keyword-group parser control
  3242.                 $disallowed_before_local = $disallowed_before;
  3243.                 $disallowed_after_local = $disallowed_after;
  3244.                 if ($parser_control_pergroup && isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k])) {
  3245.                     if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'])) {
  3246.                         $disallowed_before_local =
  3247.                             $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'];
  3248.                     }
  3249.  
  3250.                     if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'])) {
  3251.                         $disallowed_after_local =
  3252.                             $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'];
  3253.                     }
  3254.                 }
  3255.  
  3256.                 $this->_kw_replace_group = $k;
  3257.  
  3258.                 //NEW in 1.0.8, the cached regexp list
  3259.                 // since we don't want PHP / PCRE to crash due to too large patterns we split them into smaller chunks
  3260.                 for ($set = 0, $set_length = count($this->language_data['CACHED_KEYWORD_LISTS'][$k]); $set <  $set_length; ++$set) {
  3261.                     $keywordset =& $this->language_data['CACHED_KEYWORD_LISTS'][$k][$set];
  3262.                     // Might make a more unique string for putting the number in soon
  3263.                     // Basically, we don't put the styles in yet because then the styles themselves will
  3264.                     // get highlighted if the language has a CSS keyword in it (like CSS, for example ;))
  3265.                     $stuff_to_parse = preg_replace_callback(
  3266.                         "/$disallowed_before_local({$keywordset})(?!\<DOT\>(?:htm|php))$disallowed_after_local/$modifiers",
  3267.                         array($this, 'handle_keyword_replace'),
  3268.                         $stuff_to_parse
  3269.                         );
  3270.                 }
  3271.             }
  3272.         }
  3273.  
  3274.         //
  3275.         // Now that's all done, replace /[number]/ with the correct styles
  3276.         //
  3277.         foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
  3278.             if (!$this->use_classes) {
  3279.                 $attributes = ' style="' .
  3280.                     (isset($this->language_data['STYLES']['KEYWORDS'][$k]) ?
  3281.                     $this->language_data['STYLES']['KEYWORDS'][$k] : "") . '"';
  3282.             } else {
  3283.                 $attributes = ' class="kw' . $k . '"';
  3284.             }
  3285.             $stuff_to_parse = str_replace("<|/$k/>", "<|$attributes>", $stuff_to_parse);
  3286.         }
  3287.  
  3288.         if ($numbers_found) {
  3289.             // Put number styles in
  3290.             foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
  3291. //Commented out for now, as this needs some review ...
  3292. //                if ($numbers_permissions & $id) {
  3293.                     //Get the appropriate style ...
  3294.                         //Checking for unset styles is done by the style cache builder ...
  3295.                     if (!$this->use_classes) {
  3296.                         $attributes = ' style="' . $this->language_data['STYLES']['NUMBERS'][$id] . '"';
  3297.                     } else {
  3298.                         $attributes = ' class="nu'.$id.'"';
  3299.                     }
  3300.  
  3301.                     //Set in the correct styles ...
  3302.                     $stuff_to_parse = str_replace("/NUM!$id/", $attributes, $stuff_to_parse);
  3303. //                }
  3304.             }
  3305.         }
  3306.  
  3307.         // Highlight methods and fields in objects
  3308.         if ($this->lexic_permissions['METHODS'] && $this->language_data['OOLANG']) {
  3309.             $oolang_spaces = "[\s]*";
  3310.             $oolang_before = "";
  3311.             $oolang_after = "[a-zA-Z][a-zA-Z0-9_]*";
  3312.             if (isset($this->language_data['PARSER_CONTROL'])) {
  3313.                 if (isset($this->language_data['PARSER_CONTROL']['OOLANG'])) {
  3314.                     if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'])) {
  3315.                         $oolang_before = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'];
  3316.                     }
  3317.                     if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'])) {
  3318.                         $oolang_after = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'];
  3319.                     }
  3320.                     if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'])) {
  3321.                         $oolang_spaces = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'];
  3322.                     }
  3323.                 }
  3324.             }
  3325.  
  3326.             foreach ($this->language_data['OBJECT_SPLITTERS'] as $key => $splitter) {
  3327.                 if (false !== strpos($stuff_to_parse, $splitter)) {
  3328.                     if (!$this->use_classes) {
  3329.                         $attributes = ' style="' . $this->language_data['STYLES']['METHODS'][$key] . '"';
  3330.                     } else {
  3331.                         $attributes = ' class="me' . $key . '"';
  3332.                     }
  3333.                     $stuff_to_parse = preg_replace("/($oolang_before)(preg_quote($this->language_data['OBJECT_SPLITTERS'][$key], '/') . ")($oolang_spaces)($oolang_after)/", "\\1\\2\\3<|$attributes>\\4|>", $stuff_to_parse);
  3334.                 }
  3335.             }
  3336.         }
  3337.  
  3338.         //
  3339.         // Highlight brackets. Yes, I've tried adding a semi-colon to this list.
  3340.         // You try it, and see what happens ;)
  3341.         // TODO: Fix lexic permissions not converting entities if shouldn't
  3342.         // be highlighting regardless
  3343.         //
  3344.         if ($this->lexic_permissions['BRACKETS']) {
  3345.             $stuff_to_parse = str_replace( $this->language_data['CACHE_BRACKET_MATCH'],
  3346.                               $this->language_data['CACHE_BRACKET_REPLACE'], $stuff_to_parse );
  3347.         }
  3348.  
  3349.  
  3350.         //FIX for symbol highlighting ...
  3351.         if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
  3352.             //Get all matches and throw away those witin a block that is already highlighted... (i.e. matched by a regexp)
  3353.             $n_symbols = preg_match_all("/<\|(?:<DOT>|[^>])+>(?:(?!\|>).*?)\|>|<\/a>|(?:" . $this->language_data['SYMBOL_SEARCH'] . ")+/", $stuff_to_parse, $pot_symbols, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
  3354.             $global_offset = 0;
  3355.             for ($s_id = 0; $s_id < $n_symbols; ++$s_id) {
  3356.                 $symbol_match = $pot_symbols[$s_id][0][0];
  3357.                 if (strpos($symbol_match, '<') !== false || strpos($symbol_match, '>') !== false) {
  3358.                     // already highlighted blocks _must_ include either < or >
  3359.                     // so if this conditional applies, we have to skip this match
  3360.                     // BenBE: UNLESS the block contains <SEMI> or <PIPE>
  3361.                     if(strpos($symbol_match, '<SEMI>') === false &&
  3362.                         strpos($symbol_match, '<PIPE>') === false) {
  3363.                         continue;
  3364.                     }
  3365.                 }
  3366.  
  3367.                 // if we reach this point, we have a valid match which needs to be highlighted
  3368.                 $symbol_length = strlen($symbol_match);
  3369.                 $symbol_offset = $pot_symbols[$s_id][0][1];
  3370.                 unset($pot_symbols[$s_id]);
  3371.                 $symbol_end = $symbol_length + $symbol_offset;
  3372.                 $symbol_hl = "";
  3373.  
  3374.                 // if we have multiple styles, we have to handle them properly
  3375.                 if ($this->language_data['MULTIPLE_SYMBOL_GROUPS']) {
  3376.                     $old_sym = -1;
  3377.                     // Split the current stuff to replace into its atomic symbols ...
  3378.                     preg_match_all("/" . $this->language_data['SYMBOL_SEARCH'] . "/", $symbol_match, $sym_match_syms, PREG_PATTERN_ORDER);
  3379.                     foreach ($sym_match_syms[0] as $sym_ms) {
  3380.                         //Check if consequtive symbols belong to the same group to save output ...
  3381.                         if (isset($this->language_data['SYMBOL_DATA'][$sym_ms])
  3382.                             && ($this->language_data['SYMBOL_DATA'][$sym_ms] != $old_sym)) {
  3383.                             if (-1 != $old_sym) {
  3384.                                 $symbol_hl .= "|>";
  3385.                             }
  3386.                             $old_sym = $this->language_data['SYMBOL_DATA'][$sym_ms];
  3387.                             if (!$this->use_classes) {
  3388.                                 $symbol_hl .= '<| style="' . $this->language_data['STYLES']['SYMBOLS'][$old_sym] . '">';
  3389.                             } else {
  3390.                                 $symbol_hl .= '<| class="sy' . $old_sym . '">';
  3391.                             }
  3392.                         }
  3393.                         $symbol_hl .= $sym_ms;
  3394.                     }
  3395.                     unset($sym_match_syms);
  3396.  
  3397.                     //Close remaining tags and insert the replacement at the right position ...
  3398.                     //Take caution if symbol_hl is empty to avoid doubled closing spans.
  3399.                     if (-1 != $old_sym) {
  3400.                         $symbol_hl .= "|>";
  3401.                     }
  3402.                 } else {
  3403.                     if (!$this->use_classes) {
  3404.                         $symbol_hl = '<| style="' . $this->language_data['STYLES']['SYMBOLS'][0] . '">';
  3405.                     } else {
  3406.                         $symbol_hl = '<| class="sy0">';
  3407.                     }
  3408.                     $symbol_hl .= $symbol_match . '|>';
  3409.                 }
  3410.  
  3411.                 $stuff_to_parse = substr_replace($stuff_to_parse, $symbol_hl, $symbol_offset + $global_offset, $symbol_length);
  3412.  
  3413.                 // since we replace old text with something of different size,
  3414.                 // we'll have to keep track of the differences
  3415.                 $global_offset += strlen($symbol_hl) - $symbol_length;
  3416.             }
  3417.         }
  3418.         //FIX for symbol highlighting ...
  3419.         // Add class/style for regexps
  3420.         foreach (array_keys($this->language_data['REGEXPS']) as $key) {
  3421.             if ($this->lexic_permissions['REGEXPS'][$key]) {
  3422.                 if (is_callable($this->language_data['STYLES']['REGEXPS'][$key])) {
  3423.                     $this->_rx_key = $key;
  3424.                     $stuff_to_parse = preg_replace_callback("/!REG3XP$key!(.*)\|>/U",
  3425.                         array($this, 'handle_regexps_callback'),
  3426.                         $stuff_to_parse);
  3427.                 } else {
  3428.                     if (!$this->use_classes) {
  3429.                         $attributes = ' style="' . $this->language_data['STYLES']['REGEXPS'][$key] . '"';
  3430.                     } else {
  3431.                         if (is_array($this->language_data['REGEXPS'][$key]) &&
  3432.                             array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$key])) {
  3433.                             $attributes = ' class="' .
  3434.                                 $this->language_data['REGEXPS'][$key][GESHI_CLASS] . '"';
  3435.                         } else {
  3436.                            $attributes = ' class="re' . $key . '"';
  3437.                         }
  3438.                     }
  3439.                     $stuff_to_parse = str_replace("!REG3XP$key!", "$attributes", $stuff_to_parse);
  3440.                 }
  3441.             }
  3442.         }
  3443.  
  3444.         // Replace <DOT> with . for urls
  3445.         $stuff_to_parse = str_replace('<DOT>', '.', $stuff_to_parse);
  3446.         // Replace <|UR1| with <a href= for urls also
  3447.         if (isset($this->link_styles[<a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>])) {
  3448.             if ($this->use_classes) {
  3449.                 $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
  3450.             } else {
  3451.                 $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' style="' . $this->link_styles[<a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>] . '" href=', $stuff_to_parse);
  3452.             }
  3453.         } else {
  3454.             $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
  3455.         }
  3456.  
  3457.         //
  3458.         // NOW we add the span thingy ;)
  3459.         //
  3460.         $stuff_to_parse = str_replace('<|', '<span', $stuff_to_parse);
  3461.         $stuff_to_parse = str_replace ( '|>', '</span>', $stuff_to_parse );
  3462.         return substr($stuff_to_parse, 1);
  3463.     }
  3464.  
  3465.     /**
  3466.      * Sets the time taken to parse the code
  3467.      *
  3468.      * @param microtime The time when parsing started
  3469.      * @param microtime The time when parsing ended
  3470.      * @since 1.0.2
  3471.      * @access private
  3472.      */
  3473.     function set_time($start_time, $end_time) {
  3474.         $start = explode(' ', $start_time);
  3475.         $end = explode(' ', $end_time);
  3476.         $this->time = $end[0] + $end[1] - $start[0] - $start[1];
  3477.     }
  3478.  
  3479.     /**
  3480.      * Gets the time taken to parse the code
  3481.      *
  3482.      * @return double The time taken to parse the code
  3483.      * @since  1.0.2
  3484.      */
  3485.     function get_time() {
  3486.         return $this->time;
  3487.     }
  3488.  
  3489.     /**
  3490.      * Merges arrays recursively, overwriting values of the first array with values of later arrays
  3491.      *
  3492.      * @since 1.0.8
  3493.      * @access private
  3494.      */
  3495.     function merge_arrays() {
  3496.         $arrays = func_get_args();
  3497.         $narrays = count($arrays);
  3498.  
  3499.         // check arguments
  3500.         // comment out if more performance is necessary (in this case the foreach loop will trigger a warning if the argument is not an array)
  3501.         for ($i = 0; $i < $narrays; $i ++) {
  3502.             if (!is_array($arrays[$i])) {
  3503.                 // also array_merge_recursive returns nothing in this case
  3504.                 trigger_error('Argument #' . ($i+1) . ' is not an array - trying to merge array with scalar! Returning false!', E_USER_WARNING);
  3505.                 return false;
  3506.             }
  3507.         }
  3508.  
  3509.         // the first array is in the output set in every case
  3510.         $ret = $arrays[0];
  3511.  
  3512.         // merege $ret with the remaining arrays
  3513.         for ($i = 1; $i < $narrays; $i ++) {
  3514.             foreach ($arrays[$i] as $key => $value) {
  3515.                 if (is_array($value) && isset($ret[$key])) {
  3516.                     // if $ret[$key] is not an array you try to merge an scalar value with an array - the result is not defined (incompatible arrays)
  3517.                     // in this case the call will trigger an E_USER_WARNING and the $ret[$key] will be false.
  3518.                     $ret[$key] = $this->merge_arrays($ret[$key], $value);
  3519.                 } else {
  3520.                     $ret[$key] = $value;
  3521.                 }
  3522.             }
  3523.         }
  3524.  
  3525.         return $ret;
  3526.     }
  3527.  
  3528.     /**
  3529.      * Gets language information and stores it for later use
  3530.      *
  3531.      * @param string The filename of the language file you want to load
  3532.      * @since 1.0.0
  3533.      * @access private
  3534.      * @todo Needs to load keys for lexic permissions for keywords, regexps etc
  3535.      */
  3536.     function load_language($file_name) {
  3537.         if ($file_name == $this->loaded_language) {
  3538.             // this file is already loaded!
  3539.             return;
  3540.         }
  3541.  
  3542.         //Prepare some stuff before actually loading the language file
  3543.         $this->loaded_language = $file_name;
  3544.         $this->parse_cache_built = false;
  3545.         $this->enable_highlighting();
  3546.         $language_data = array();
  3547.  
  3548.         //Load the language file
  3549.         require $file_name;
  3550.  
  3551.         // Perhaps some checking might be added here later to check that
  3552.         // $language data is a valid thing but maybe not
  3553.         $this->language_data = $language_data;
  3554.  
  3555.         // Set strict mode if should be set
  3556.         $this->strict_mode = $this->language_data['STRICT_MODE_APPLIES'];
  3557.  
  3558.         // Set permissions for all lexics to true
  3559.         // so they'll be highlighted by default
  3560.         foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
  3561.             if (!empty($this->language_data['KEYWORDS'][$key])) {
  3562.                 $this->lexic_permissions['KEYWORDS'][$key] = true;
  3563.             } else {
  3564.                 $this->lexic_permissions['KEYWORDS'][$key] = false;
  3565.             }
  3566.         }
  3567.  
  3568.         foreach (array_keys($this->language_data['COMMENT_SINGLE']) as $key) {
  3569.             $this->lexic_permissions['COMMENTS'][$key] = true;
  3570.         }
  3571.         foreach (array_keys($this->language_data['REGEXPS']) as $key) {
  3572.             $this->lexic_permissions['REGEXPS'][$key] = true;
  3573.         }
  3574.  
  3575.         // for BenBE and future code reviews:
  3576.         // we can use empty here since we only check for existance and emptiness of an array
  3577.         // if it is not an array at all but rather false or null this will work as intended as well
  3578.         // even if $this->language_data['PARSER_CONTROL'] is undefined this won't trigger a notice
  3579.         if (!empty($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'])) {
  3580.             foreach ($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'] as $flag => $value) {
  3581.                 // it's either true or false and maybe is true as well
  3582.                 $perm = $value !== GESHI_NEVER;
  3583.                 if ($flag == 'ALL') {
  3584.                     $this->enable_highlighting($perm);
  3585.                     continue;
  3586.                 }
  3587.                 if (!isset($this->lexic_permissions[$flag])) {
  3588.                     // unknown lexic permission
  3589.                     continue;
  3590.                 }
  3591.                 if (is_array($this->lexic_permissions[$flag])) {
  3592.                     foreach ($this->lexic_permissions[$flag] as $key => $val) {
  3593.                         $this->lexic_permissions[$flag][$key] = $perm;
  3594.                     }
  3595.                 } else {
  3596.                     $this->lexic_permissions[$flag] = $perm;
  3597.                 }
  3598.             }
  3599.             unset($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS']);
  3600.         }
  3601.  
  3602.         //NEW in 1.0.8: Allow styles to be loaded from a separate file to override defaults
  3603.         $style_filename = substr($file_name, 0, -4) . '.style.php';
  3604.         if (is_readable($style_filename)) {
  3605.             //Clear any style_data that could have been set before ...
  3606.             if (isset($style_data)) {
  3607.                 unset($style_data);
  3608.             }
  3609.  
  3610.             //Read the Style Information from the style file
  3611.             include $style_filename;
  3612.  
  3613.             //Apply the new styles to our current language styles
  3614.             if (isset($style_data) && is_array($style_data)) {
  3615.                 $this->language_data['STYLES'] =
  3616.                     $this->merge_arrays($this->language_data['STYLES'], $style_data);
  3617.             }
  3618.         }
  3619.     }
  3620.  
  3621.     /**
  3622.      * Takes the parsed code and various options, and creates the HTML
  3623.      * surrounding it to make it look nice.
  3624.      *
  3625.      * @param  string The code already parsed (reference!)
  3626.      * @since  1.0.0
  3627.      * @access private
  3628.      */
  3629.     function finalise(&$parsed_code) {
  3630.         // Remove end parts of important declarations
  3631.         // This is BUGGY!! My fault for bad code: fix coming in 1.2
  3632.         // @todo Remove this crap
  3633.         if ($this->enable_important_blocks &&
  3634.             (strpos($parsed_code, $this->hsc(<a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>)) === false)) {
  3635.             $parsed_code = str_replace($this->hsc(<a href="../geshi/core/_geshi.php.html#defineGESHI_END_IMPORTANT">GESHI_END_IMPORTANT</a>), '', $parsed_code);
  3636.         }
  3637.  
  3638.         // Add HTML whitespace stuff if we're using the <div> header
  3639.         if ($this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
  3640.             $this->indent($parsed_code);
  3641.         }
  3642.  
  3643.         // purge some unnecessary stuff
  3644.         /** NOTE: memorypeak #1 */
  3645.         $parsed_code = preg_replace('#<span[^>]+>(\s*)</span>#', '\\1', $parsed_code);
  3646.  
  3647.         // If we are using IDs for line numbers, there needs to be an overall
  3648.         // ID set to prevent collisions.
  3649.         if ($this->add_ids && !$this->overall_id) {
  3650.             $this->overall_id = 'geshi-' . substr(md5(microtime()), 0, 4);
  3651.         }
  3652.  
  3653.         // Get code into lines
  3654.         /** NOTE: memorypeak #2 */
  3655.         $code = explode("\n", $parsed_code);
  3656.         $parsed_code = $this->header();
  3657.  
  3658.         // If we're using line numbers, we insert <li>s and appropriate
  3659.         // markup to style them (otherwise we don't need to do anything)
  3660.         if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
  3661.             // If we're using the <pre> header, we shouldn't add newlines because
  3662.             // the <pre> will line-break them (and the <li>s already do this for us)
  3663.             $ls = ($this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) ? "\n" : '';
  3664.  
  3665.             // Set vars to defaults for following loop
  3666.             $i = 0;
  3667.  
  3668.             // Foreach line...
  3669.             for ($i = 0, $n = count($code); $i < $n;) {
  3670.                 //Reset the attributes for a new line ...
  3671.                 $attrs = array();
  3672.  
  3673.                 // Make lines have at least one space in them if they're empty
  3674.                 // BenBE: Checking emptiness using trim instead of relying on blanks
  3675.                 if ('' == trim($code[$i])) {
  3676.                     $code[$i] = '&nbsp;';
  3677.                 }
  3678.  
  3679.                 // If this is a "special line"...
  3680.                 if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
  3681.                     $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
  3682.                     // Set the attributes to style the line
  3683.                     if ($this->use_classes) {
  3684.                         //$attr = ' class="li2"';
  3685.                         $attrs['class'][] = 'li2';
  3686.                         $def_attr = ' class="de2"';
  3687.                     } else {
  3688.                         //$attr = ' style="' . $this->line_style2 . '"';
  3689.                         $attrs['style'][] = $this->line_style2;
  3690.                         // This style "covers up" the special styles set for special lines
  3691.                         // so that styles applied to special lines don't apply to the actual
  3692.                         // code on that line
  3693.                         $def_attr = ' style="' . $this->code_style . '"';
  3694.                     }
  3695.                 } else {
  3696.                     if ($this->use_classes) {
  3697.                         //$attr = ' class="li1"';
  3698.                         $attrs['class'][] = 'li1';
  3699.                         $def_attr = ' class="de1"';
  3700.                     } else {
  3701.                         //$attr = ' style="' . $this->line_style1 . '"';
  3702.                         $attrs['style'][] = $this->line_style1;
  3703.                         $def_attr = ' style="' . $this->code_style . '"';
  3704.                     }
  3705.                 }
  3706.  
  3707.                 //Check which type of tag to insert for this line
  3708.                 if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
  3709.                     $start = "<pre$def_attr>";
  3710.                     $end = '</pre>';
  3711.                 } else {
  3712.                     // Span or div?
  3713.                     $start = "<div$def_attr>";
  3714.                     $end = '</div>';
  3715.                 }
  3716.  
  3717.                 ++$i;
  3718.  
  3719.                 // Are we supposed to use ids? If so, add them
  3720.                 if ($this->add_ids) {
  3721.                     $attrs['id'][] = "$this->overall_id-$i";
  3722.                 }
  3723.  
  3724.                 //Is this some line with extra styles???
  3725.                 if (in_array($i, $this->highlight_extra_lines)) {
  3726.                     if ($this->use_classes) {
  3727.                         if (isset($this->highlight_extra_lines_styles[$i])) {
  3728.                             $attrs['class'][] = "lx$i";
  3729.                         } else {
  3730.                             $attrs['class'][] = "ln-xtra";
  3731.                         }
  3732.                     } else {
  3733.                         array_push($attrs['style'], $this->get_line_style($i));
  3734.                     }
  3735.                 }
  3736.  
  3737.                 // Add in the line surrounded by appropriate list HTML
  3738.                 $attr_string = '';
  3739.                 foreach ($attrs as $key => $attr) {
  3740.                     $attr_string .= ' ' . $key . '="' . implode(' ', $attr) . '"';
  3741.                 }
  3742.  
  3743.                 $parsed_code .= "<li$attr_string>$start{$code[$i-1]}$end</li>$ls";
  3744.                 unset($code[$i - 1]);
  3745.             }
  3746.         } else {
  3747.             $n = count($code);
  3748.             if ($this->use_classes) {
  3749.                 $attributes = ' class="de1"';
  3750.             } else {
  3751.                 $attributes = ' style="'$this->code_style .'"';
  3752.             }
  3753.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
  3754.                 $parsed_code .= '<pre'$attributes .'>';
  3755.             } elseif ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
  3756.                 if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3757.                     if ($this->use_classes) {
  3758.                         $attrs = ' class="ln"';
  3759.                     } else {
  3760.                         $attrs = ' style="'$this->table_linenumber_style .'"';
  3761.                     }
  3762.                     $parsed_code .= '<td'.$attrs.'><pre'.$attributes.'>';
  3763.                     // get linenumbers
  3764.                     // we don't merge it with the for below, since it should be better for
  3765.                     // memory consumption this way
  3766.                     // @todo: but... actually it would still be somewhat nice to merge the two loops
  3767.                     //        the mem peaks are at different positions
  3768.                     for ($i = 0; $i < $n; ++$i) {
  3769.                         $close = 0;
  3770.                         // fancy lines
  3771.                         if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
  3772.                             $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
  3773.                             // Set the attributes to style the line
  3774.                             if ($this->use_classes) {
  3775.                                 $parsed_code .= '<span class="xtra li2"><span class="de2">';
  3776.                             } else {
  3777.                                 // This style "covers up" the special styles set for special lines
  3778.                                 // so that styles applied to special lines don't apply to the actual
  3779.                                 // code on that line
  3780.                                 $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
  3781.                                                   .'<span style="' . $this->code_style .'">';
  3782.                             }
  3783.                             $close += 2;
  3784.                         }
  3785.                         //Is this some line with extra styles???
  3786.                         if (in_array($i + 1, $this->highlight_extra_lines)) {
  3787.                             if ($this->use_classes) {
  3788.                                 if (isset($this->highlight_extra_lines_styles[$i])) {
  3789.                                     $parsed_code .= "<span class=\"xtra lx$i\">";
  3790.                                 } else {
  3791.                                     $parsed_code .= "<span class=\"xtra ln-xtra\">";
  3792.                                 }
  3793.                             } else {
  3794.                                 $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
  3795.                             }
  3796.                             ++$close;
  3797.                         }
  3798.                         $parsed_code .= $this->line_numbers_start + $i;
  3799.                         if ($close) {
  3800.                             $parsed_code .= str_repeat('</span>', $close);
  3801.                         } else if ($i != $n) {
  3802.                             $parsed_code .= "\n";
  3803.                         }
  3804.                     }
  3805.                     $parsed_code .= '</pre></td><td'.$attributes.'>';
  3806.                 }
  3807.                 $parsed_code .= '<pre'$attributes .'>';
  3808.             }
  3809.             // No line numbers, but still need to handle highlighting lines extra.
  3810.             // Have to use divs so the full width of the code is highlighted
  3811.             $close = 0;
  3812.             for ($i = 0; $i < $n; ++$i) {
  3813.                 // Make lines have at least one space in them if they're empty
  3814.                 // BenBE: Checking emptiness using trim instead of relying on blanks
  3815.                 if ('' == trim($code[$i])) {
  3816.                     $code[$i] = '&nbsp;';
  3817.                 }
  3818.                 // fancy lines
  3819.                 if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
  3820.                     $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
  3821.                     // Set the attributes to style the line
  3822.                     if ($this->use_classes) {
  3823.                         $parsed_code .= '<span class="xtra li2"><span class="de2">';
  3824.                     } else {
  3825.                         // This style "covers up" the special styles set for special lines
  3826.                         // so that styles applied to special lines don't apply to the actual
  3827.                         // code on that line
  3828.                         $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
  3829.                                           .'<span style="' . $this->code_style .'">';
  3830.                     }
  3831.                     $close += 2;
  3832.                 }
  3833.                 //Is this some line with extra styles???
  3834.                 if (in_array($i + 1, $this->highlight_extra_lines)) {
  3835.                     if ($this->use_classes) {
  3836.                         if (isset($this->highlight_extra_lines_styles[$i])) {
  3837.                             $parsed_code .= "<span class=\"xtra lx$i\">";
  3838.                         } else {
  3839.                             $parsed_code .= "<span class=\"xtra ln-xtra\">";
  3840.                         }
  3841.                     } else {
  3842.                         $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
  3843.                     }
  3844.                     ++$close;
  3845.                 }
  3846.  
  3847.                 $parsed_code .= $code[$i];
  3848.  
  3849.                 if ($close) {
  3850.                   $parsed_code .= str_repeat('</span>', $close);
  3851.                   $close = 0;
  3852.                 }
  3853.                 elseif ($i + 1 < $n) {
  3854.                     $parsed_code .= "\n";
  3855.                 }
  3856.                 unset($code[$i]);
  3857.             }
  3858.  
  3859.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
  3860.                 $parsed_code .= '</pre>';
  3861.             }
  3862.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a> && $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3863.                 $parsed_code .= '</td>';
  3864.             }
  3865.         }
  3866.  
  3867.         $parsed_code .= $this->footer();
  3868.     }
  3869.  
  3870.     /**
  3871.      * Creates the header for the code block (with correct attributes)
  3872.      *
  3873.      * @return string The header for the code block
  3874.      * @since  1.0.0
  3875.      * @access private
  3876.      */
  3877.     function header() {
  3878.         // Get attributes needed
  3879.         /**
  3880.          * @todo   Document behaviour change - class is outputted regardless of whether
  3881.          *         we're using classes or not. Same with style
  3882.          */
  3883.         $attributes = ' class="' . $this->language;
  3884.         if ($this->overall_class != '') {
  3885.             $attributes .= " ".$this->overall_class;
  3886.         }
  3887.         $attributes .= '"';
  3888.  
  3889.         if ($this->overall_id != '') {
  3890.             $attributes .= " id=\"{$this->overall_id}\"";
  3891.         }
  3892.         if ($this->overall_style != '') {
  3893.             $attributes .= ' style="' . $this->overall_style . '"';
  3894.         }
  3895.  
  3896.         $ol_attributes = '';
  3897.  
  3898.         if ($this->line_numbers_start != 1) {
  3899.             $ol_attributes .= ' start="' . $this->line_numbers_start . '"';
  3900.         }
  3901.  
  3902.         // Get the header HTML
  3903.         $header = $this->header_content;
  3904.         if ($header) {
  3905.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
  3906.                 $header = str_replace("\n", '', $header);
  3907.             }
  3908.             $header = $this->replace_keywords($header);
  3909.  
  3910.             if ($this->use_classes) {
  3911.                 $attr = ' class="head"';
  3912.             } else {
  3913.                 $attr = " style=\"{$this->header_content_style}\"";
  3914.             }
  3915.             if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
  3916.                 $header = "<thead><tr><td colspan=\"2\" $attr>$header</td></tr></thead>";
  3917.             } else {
  3918.                 $header = "<div$attr>$header</div>";
  3919.             }
  3920.         }
  3921.  
  3922.         if (GESHI_HEADER_NONE == $this->header_type) {
  3923.             if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
  3924.                 return "$header<ol$attributes$ol_attributes>";
  3925.             }
  3926.             return $header . ($this->force_code_block ? '<div>' : '');
  3927.         }
  3928.  
  3929.         // Work out what to return and do it
  3930.         if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3931.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>) {
  3932.                 return "<pre$attributes>$header<ol$ol_attributes>";
  3933.             } else if ($this->header_type == GESHI_HEADER_DIV ||
  3934.                 $this->header_type == GESHI_HEADER_PRE_VALID) {
  3935.                 return "<div$attributes>$header<ol$ol_attributes>";
  3936.             } else if ($this->header_type == GESHI_HEADER_PRE_TABLE) {
  3937.                 return "<table$attributes>$header<tbody><tr class=\"li1\">";
  3938.             }
  3939.         } else {
  3940.             if ($this->header_type == GESHI_HEADER_PRE) {
  3941.                 return "<pre$attributes>$header"  .
  3942.                     ($this->force_code_block ? '<div>' : '');
  3943.             } else {
  3944.                 return "<div$attributes>$header.
  3945.                     ($this->force_code_block ? '<div>' : '');
  3946.             }
  3947.         }
  3948.     }
  3949.  
  3950.     /**
  3951.      * Returns the footer for the code block.
  3952.      *
  3953.      * @return string The footer for the code block
  3954.      * @since  1.0.0
  3955.      * @access private
  3956.      */
  3957.     function footer() {
  3958.         $footer = $this->footer_content;
  3959.         if ($footer) {
  3960.             if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>) {
  3961.                 $footer = str_replace("\n", '', $footer);;
  3962.             }
  3963.             $footer = $this->replace_keywords($footer);
  3964.  
  3965.             if ($this->use_classes) {
  3966.                 $attr = ' class="foot"';
  3967.             } else {
  3968.                 $attr = " style=\"{$this->footer_content_style}\"";
  3969.             }
  3970.             if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->linenumbers != GESHI_NO_LINE_NUMBERS) {
  3971.                 $footer = "<tfoot><tr><td colspan=\"2\">$footer</td></tr></tfoot>";
  3972.             } else {
  3973.                 $footer = "<div$attr>$footer</div>";
  3974.             }
  3975.         }
  3976.  
  3977.         if (GESHI_HEADER_NONE == $this->header_type) {
  3978.             return ($this->line_numbers != GESHI_NO_LINE_NUMBERS) ? '</ol>' . $footer : $footer;
  3979.         }
  3980.  
  3981.         if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_DIV">GESHI_HEADER_DIV</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
  3982.             if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  3983.                 return "</ol>$footer</div>";
  3984.             }
  3985.             return ($this->force_code_block ? '</div>' : '') .
  3986.                 "$footer</div>";
  3987.         }
  3988.         elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) {
  3989.             if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
  3990.                 return "</tr></tbody>$footer</table>";
  3991.             }
  3992.             return ($this->force_code_block ? '</div>' : '') .
  3993.                 "$footer</div>";
  3994.         }
  3995.         else {
  3996.             if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
  3997.                 return "</ol>$footer</pre>";
  3998.             }
  3999.             return ($this->force_code_block ? '</div>' : '') .
  4000.                 "$footer</pre>";
  4001.         }
  4002.     }
  4003.  
  4004.     /**
  4005.      * Replaces certain keywords in the header and footer with
  4006.      * certain configuration values
  4007.      *
  4008.      * @param  string The header or footer content to do replacement on
  4009.      * @return string The header or footer with replaced keywords
  4010.      * @since  1.0.2
  4011.      * @access private
  4012.      */
  4013.     function replace_keywords($instr) {
  4014.         $keywords = $replacements = array();
  4015.  
  4016.         $keywords[] = '<TIME>';
  4017.         $keywords[] = '{TIME}';
  4018.         $replacements[] = $replacements[] = number_format($time = $this->get_time(), 3);
  4019.  
  4020.         $keywords[] = '<LANGUAGE>';
  4021.         $keywords[] = '{LANGUAGE}';
  4022.         $replacements[] = $replacements[] = $this->language_data['LANG_NAME'];
  4023.  
  4024.         $keywords[] = '<VERSION>';
  4025.         $keywords[] = '{VERSION}';
  4026.         $replacements[] = $replacements[] = <a href="../geshi/core/_geshi.php.html#defineGESHI_VERSION">GESHI_VERSION</a>;
  4027.  
  4028.         $keywords[] = '<SPEED>';
  4029.         $keywords[] = '{SPEED}';
  4030.         if ($time <= 0) {
  4031.             $speed = 'N/A';
  4032.         } else {
  4033.             $speed = strlen($this->source) / $time;
  4034.             if ($speed >= 1024) {
  4035.                 $speed = sprintf("%.2f KB/s", $speed / 1024.0);
  4036.             } else {
  4037.                 $speed = sprintf("%.0f B/s", $speed);
  4038.             }
  4039.         }
  4040.         $replacements[] = $replacements[] = $speed;
  4041.  
  4042.         return str_replace($keywords, $replacements, $instr);
  4043.     }
  4044.  
  4045.     /**
  4046.      * Secure replacement for PHP built-in function htmlspecialchars().
  4047.      *
  4048.      * See ticket #427 (http://wush.net/trac/wikka/ticket/427) for the rationale
  4049.      * for this replacement function.
  4050.      *
  4051.      * The INTERFACE for this function is almost the same as that for
  4052.      * htmlspecialchars(), with the same default for quote style; however, there
  4053.      * is no 'charset' parameter. The reason for this is as follows:
  4054.      *
  4055.      * The PHP docs say:
  4056.      *      "The third argument charset defines character set used in conversion."
  4057.      *
  4058.      * I suspect PHP's htmlspecialchars() is working at the byte-value level and
  4059.      * thus _needs_ to know (or asssume) a character set because the special
  4060.      * characters to be replaced could exist at different code points in
  4061.      * different character sets. (If indeed htmlspecialchars() works at
  4062.      * byte-value level that goes some  way towards explaining why the
  4063.      * vulnerability would exist in this function, too, and not only in
  4064.      * htmlentities() which certainly is working at byte-value level.)
  4065.      *
  4066.      * This replacement function however works at character level and should
  4067.      * therefore be "immune" to character set differences - so no charset
  4068.      * parameter is needed or provided. If a third parameter is passed, it will
  4069.      * be silently ignored.
  4070.      *
  4071.      * In the OUTPUT there is a minor difference in that we use '&#39;' instead
  4072.      * of PHP's '&#039;' for a single quote: this provides compatibility with
  4073.      *      get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)
  4074.      * (see comment by mikiwoz at yahoo dot co dot uk on
  4075.      * http://php.net/htmlspecialchars); it also matches the entity definition
  4076.      * for XML 1.0
  4077.      * (http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters).
  4078.      * Like PHP we use a numeric character reference instead of '&apos;' for the
  4079.      * single quote. For the other special characters we use the named entity
  4080.      * references, as PHP is doing.
  4081.      *
  4082.      * @author      {@link http://wikkawiki.org/JavaWoman Marjolein Katsma}
  4083.      *
  4084.      * @license     http://www.gnu.org/copyleft/lgpl.html
  4085.      *              GNU Lesser General Public License
  4086.      * @copyright   Copyright 2007, {@link http://wikkawiki.org/CreditsPage
  4087.      *              Wikka Development Team}
  4088.      *
  4089.      * @access      private
  4090.      * @param       string  $string string to be converted
  4091.      * @param       integer $quote_style
  4092.      *                      - ENT_COMPAT:   escapes &, <, > and double quote (default)
  4093.      *                      - ENT_NOQUOTES: escapes only &, < and >
  4094.      *                      - ENT_QUOTES:   escapes &, <, >, double and single quotes
  4095.      * @return      string  converted string
  4096.      * @since       1.0.7.18
  4097.      */
  4098.     function hsc($string, $quote_style = ENT_COMPAT) {
  4099.         // init
  4100.         static $aTransSpecchar = array(
  4101.             '&' => '&amp;',
  4102.             '"' => '&quot;',
  4103.             '<' => '&lt;',
  4104.             '>' => '&gt;',
  4105.  
  4106.             //This fix is related to SF#1923020, but has to be applied
  4107.             //regardless of actually highlighting symbols.
  4108.             //Circumvent a bug with symbol highlighting
  4109.             //This is required as ; would produce undesirable side-effects if it
  4110.             //was not to be processed as an entity.
  4111.             ';' => '<SEMI>', // Force ; to be processed as entity
  4112.             '|' => '<PIPE>' // Force | to be processed as entity
  4113.             );                      // ENT_COMPAT set
  4114.         switch ($quote_style) {
  4115.             case ENT_NOQUOTES// don't convert double quotes
  4116.                 unset($aTransSpecchar['"']);
  4117.                 break;
  4118.             case ENT_QUOTES// convert single quotes as well
  4119.                 $aTransSpecchar["'"] = '&#39;'; // (apos) htmlspecialchars() uses '&#039;'
  4120.                 break;
  4121.         }
  4122.  
  4123.         // return translated string
  4124.         return strtr($string, $aTransSpecchar);
  4125.     }
  4126.  
  4127.     /**
  4128.      * Returns a stylesheet for the highlighted code. If $economy mode
  4129.      * is true, we only return the stylesheet declarations that matter for
  4130.      * this code block instead of the whole thing
  4131.      *
  4132.      * @param  boolean Whether to use economy mode or not
  4133.      * @return string A stylesheet built on the data for the current language
  4134.      * @since  1.0.0
  4135.      */
  4136.     function get_stylesheet($economy_mode = true) {
  4137.         // If there's an error, chances are that the language file
  4138.         // won't have populated the language data file, so we can't
  4139.         // risk getting a stylesheet...
  4140.         if ($this->error) {
  4141.             return '';
  4142.         }
  4143.  
  4144.         //Check if the style rearrangements have been processed ...
  4145.         //This also does some preprocessing to check which style groups are useable ...
  4146.         if(!isset($this->language_data['NUMBERS_CACHE'])) {
  4147.             $this->build_style_cache();
  4148.         }
  4149.  
  4150.         // First, work out what the selector should be. If there's an ID,
  4151.         // that should be used, the same for a class. Otherwise, a selector
  4152.         // of '' means that these styles will be applied anywhere
  4153.         if ($this->overall_id) {
  4154.             $selector = '#' . $this->overall_id;
  4155.         } else {
  4156.             $selector = '.' . $this->language;
  4157.             if ($this->overall_class) {
  4158.                 $selector .= '.' . $this->overall_class;
  4159.             }
  4160.         }
  4161.         $selector .= ' ';
  4162.  
  4163.         // Header of the stylesheet
  4164.         if (!$economy_mode) {
  4165.             $stylesheet = "/**\n".
  4166.                 " * GeSHi Dynamically Generated Stylesheet\n".
  4167.                 " * --------------------------------------\n".
  4168.                 " * Dynamically generated stylesheet for {$this->language}\n".
  4169.                 " * CSS class: {$this->overall_class}, CSS id: {$this->overall_id}\n".
  4170.                 " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
  4171.                 " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
  4172.                 " * --------------------------------------\n".
  4173.                 " */\n";
  4174.         } else {
  4175.             $stylesheet = "/**\n".
  4176.                 " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
  4177.                 " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
  4178.                 " */\n";
  4179.         }
  4180.  
  4181.         // Set the <ol> to have no effect at all if there are line numbers
  4182.         // (<ol>s have margins that should be destroyed so all layout is
  4183.         // controlled by the set_overall_style method, which works on the
  4184.         // <pre> or <div> container). Additionally, set default styles for lines
  4185.         if (!$economy_mode || $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
  4186.             //$stylesheet .= "$selector, {$selector}ol, {$selector}ol li {margin: 0;}\n";
  4187.             $stylesheet .= "$selector.de1, $selector.de2 {{$this->code_style}}\n";
  4188.         }
  4189.  
  4190.         // Add overall styles
  4191.         // note: neglect economy_mode, empty styles are meaningless
  4192.         if ($this->overall_style != '') {
  4193.             $stylesheet .= "$selector {{$this->overall_style}}\n";
  4194.         }
  4195.  
  4196.         // Add styles for links
  4197.         // note: economy mode does not make _any_ sense here
  4198.         //       either the style is empty and thus no selector is needed
  4199.         //       or the appropriate key is given.
  4200.         foreach ($this->link_styles as $key => $style) {
  4201.             if ($style != '') {
  4202.                 switch ($key) {
  4203.                     case <a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>:
  4204.                         $stylesheet .= "{$selector}a:link {{$style}}\n";
  4205.                         break;
  4206.                     case GESHI_HOVER:
  4207.                         $stylesheet .= "{$selector}a:hover {{$style}}\n";
  4208.                         break;
  4209.                     case GESHI_ACTIVE:
  4210.                         $stylesheet .= "{$selector}a:active {{$style}}\n";
  4211.                         break;
  4212.                     case GESHI_VISITED:
  4213.                         $stylesheet .= "{$selector}a:visited {{$style}}\n";
  4214.                         break;
  4215.                 }
  4216.             }
  4217.         }
  4218.  
  4219.         // Header and footer
  4220.         // note: neglect economy_mode, empty styles are meaningless
  4221.         if ($this->header_content_style != ''{
  4222.             $stylesheet .= "$selector.head {{$this->header_content_style}}\n";
  4223.         }
  4224.         if ($this->footer_content_style != ''{
  4225.             $stylesheet .= "$selector.foot {{$this->footer_content_style}}\n";
  4226.         }
  4227.  
  4228.         // Styles for important stuff
  4229.         // note: neglect economy_mode, empty styles are meaningless
  4230.         if ($this->important_styles != ''{
  4231.             $stylesheet .= "$selector.imp {{$this->important_styles}}\n";
  4232.         }
  4233.  
  4234.         // Simple line number styles
  4235.         if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS&& $this->line_style1 != ''{
  4236.             $stylesheet .= "{$selector}li, {$selector}.li1 {{$this->line_style1}}\n";
  4237.         }
  4238.         if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS&& $this->table_linenumber_style != ''{
  4239.             $stylesheet .= "{$selector}.ln {{$this->table_linenumber_style}}\n";
  4240.         }
  4241.         // If there is a style set for fancy line numbers, echo it out
  4242.         if ((!$economy_mode || $this->line_numbers == GESHI_FANCY_LINE_NUMBERS&& $this->line_style2 != ''{
  4243.             $stylesheet .= "{$selector}.li2 {{$this->line_style2}}\n";
  4244.         }
  4245.  
  4246.         // note: empty styles are meaningless
  4247.         foreach ($this->language_data['STYLES']['KEYWORDS'as $group => $styles{
  4248.             if ($styles != '' && (!$economy_mode ||
  4249.                 (isset($this->lexic_permissions['KEYWORDS'][$group]&&
  4250.                 $this->lexic_permissions['KEYWORDS'][$group]))) {
  4251.                 $stylesheet .= "$selector.kw$group {{$styles}}\n";
  4252.             }
  4253.         }
  4254.         foreach ($this->language_data['STYLES']['COMMENTS'as $group => $styles{
  4255.             if ($styles != '' && (!$economy_mode ||
  4256.                 (isset($this->lexic_permissions['COMMENTS'][$group]&&
  4257.                 $this->lexic_permissions['COMMENTS'][$group]||
  4258.                 (!empty($this->language_data['COMMENT_REGEXP']&&
  4259.                 !empty($this->language_data['COMMENT_REGEXP'][$group])))) {
  4260.                 $stylesheet .= "$selector.co$group {{$styles}}\n";
  4261.             }
  4262.         }
  4263.         foreach ($this->language_data['STYLES']['ESCAPE_CHAR'as $group => $styles{
  4264.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['ESCAPE_CHAR'])) {
  4265.                 // NEW: since 1.0.8 we have to handle hardescapes
  4266.                 if ($group === 'HARD'{
  4267.                     $group '_h';
  4268.                 }
  4269.                 $stylesheet .= "$selector.es$group {{$styles}}\n";
  4270.             }
  4271.         }
  4272.         foreach ($this->language_data['STYLES']['BRACKETS'as $group => $styles{
  4273.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['BRACKETS'])) {
  4274.                 $stylesheet .= "$selector.br$group {{$styles}}\n";
  4275.             }
  4276.         }
  4277.         foreach ($this->language_data['STYLES']['SYMBOLS'as $group => $styles{
  4278.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['SYMBOLS'])) {
  4279.                 $stylesheet .= "$selector.sy$group {{$styles}}\n";
  4280.             }
  4281.         }
  4282.         foreach ($this->language_data['STYLES']['STRINGS'as $group => $styles{
  4283.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['STRINGS'])) {
  4284.                 // NEW: since 1.0.8 we have to handle hardquotes
  4285.                 if ($group === 'HARD'{
  4286.                     $group '_h';
  4287.                 }
  4288.                 $stylesheet .= "$selector.st$group {{$styles}}\n";
  4289.             }
  4290.         }
  4291.         foreach ($this->language_data['STYLES']['NUMBERS'as $group => $styles{
  4292.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['NUMBERS'])) {
  4293.                 $stylesheet .= "$selector.nu$group {{$styles}}\n";
  4294.             }
  4295.         }
  4296.         foreach ($this->language_data['STYLES']['METHODS'as $group => $styles{
  4297.             if ($styles != '' && (!$economy_mode || $this->lexic_permissions['METHODS'])) {
  4298.                 $stylesheet .= "$selector.me$group {{$styles}}\n";
  4299.             }
  4300.         }
  4301.         // note: neglect economy_mode, empty styles are meaningless
  4302.         foreach ($this->language_data['STYLES']['SCRIPT'as $group => $styles{
  4303.             if ($styles != ''{
  4304.                 $stylesheet .= "$selector.sc$group {{$styles}}\n";
  4305.             }
  4306.         }
  4307.         foreach ($this->language_data['STYLES']['REGEXPS'as $group => $styles{
  4308.             if ($styles != '' && (!$economy_mode ||
  4309.                 (isset($this->lexic_permissions['REGEXPS'][$group]&&
  4310.                 $this->lexic_permissions['REGEXPS'][$group]))) {
  4311.                 if (is_array($this->language_data['REGEXPS'][$group]&&
  4312.                     array_key_exists(GESHI_CLASS$this->language_data['REGEXPS'][$group])) {
  4313.                     $stylesheet .= "$selector.";
  4314.                     $stylesheet .= $this->language_data['REGEXPS'][$group][GESHI_CLASS];
  4315.                     $stylesheet .= " {{$styles}}\n";
  4316.                 else {
  4317.                     $stylesheet .= "$selector.re$group {{$styles}}\n";
  4318.                 }
  4319.             }
  4320.         }
  4321.         // Styles for lines being highlighted extra
  4322.         if (!$economy_mode || (count($this->highlight_extra_lines)!=count($this->highlight_extra_lines_styles))) {
  4323.             $stylesheet .= "{$selector}.ln-xtra, {$selector}li.ln-xtra, {$selector}div.ln-xtra {{$this->highlight_extra_lines_style}}\n";
  4324.         }
  4325.         $stylesheet .= "{$selector}span.xtra { display:block; }\n";
  4326.         foreach ($this->highlight_extra_lines_styles as $lineid => $linestyle{
  4327.             $stylesheet .= "{$selector}.lx$lineid, {$selector}li.lx$lineid, {$selector}div.lx$lineid {{$linestyle}}\n";
  4328.         }
  4329.  
  4330.         return $stylesheet;
  4331.     }
  4332.  
  4333.     /**
  4334.      * Get's the style that is used for the specified line
  4335.      *
  4336.      * @param int The line number information is requested for
  4337.      * @access private
  4338.      * @since 1.0.7.21
  4339.      */
  4340.     function get_line_style($line{
  4341.         //$style = null;
  4342.         $style null;
  4343.         if (isset($this->highlight_extra_lines_styles[$line])) {
  4344.             $style $this->highlight_extra_lines_styles[$line];
  4345.         else // if no "extra" style assigned
  4346.             $style $this->highlight_extra_lines_style;
  4347.         }
  4348.  
  4349.         return $style;
  4350.     }
  4351.  
  4352.     /**
  4353.     * this functions creates an optimized regular expression list
  4354.     * of an array of strings.
  4355.     *
  4356.     * Example:
  4357.     * <code>$list = array('faa', 'foo', 'foobar');
  4358.     *          => string 'f(aa|oo(bar)?)'</code>
  4359.     *
  4360.     * @param $list array of (unquoted) strings
  4361.     * @param $regexp_delimiter your regular expression delimiter, @see preg_quote()
  4362.     * @return string for regular expression
  4363.     * @author Milian Wolff <mail@milianw.de>
  4364.     * @since 1.0.8
  4365.     * @access private
  4366.     */
  4367.     function optimize_regexp_list($list$regexp_delimiter '/'{
  4368.         $regex_chars array('.''\\''+''*''?''[''^'']''$',
  4369.             '('')''{''}''=''!''<''>''|'':'$regexp_delimiter);
  4370.         sort($list);
  4371.         $regexp_list array('');
  4372.         $num_subpatterns 0;
  4373.         $list_key 0;
  4374.  
  4375.         // the tokens which we will use to generate the regexp list
  4376.         $tokens array();
  4377.         $prev_keys array();
  4378.         // go through all entries of the list and generate the token list
  4379.         $cur_len 0;
  4380.         for ($i 0$i_max count($list)$i $i_max++$i{
  4381.             if ($cur_len GESHI_MAX_PCRE_LENGTH{
  4382.                 // seems like the length of this pcre is growing exorbitantly
  4383.                 $regexp_list[++$list_key$this->_optimize_regexp_list_tokens_to_string($tokens);
  4384.                 $num_subpatterns substr_count($regexp_list[$list_key]'(?:');
  4385.                 $tokens array();
  4386.                 $cur_len 0;
  4387.             }
  4388.             $level 0;
  4389.             $entry preg_quote((string) $list[$i]$regexp_delimiter);
  4390.             $pointer &$tokens;
  4391.             // properly assign the new entry to the correct position in the token array
  4392.             // possibly generate smaller common denominator keys
  4393.             while (true{
  4394.                 // get the common denominator
  4395.                 if (isset($prev_keys[$level])) {
  4396.                     if ($prev_keys[$level== $entry{
  4397.                         // this is a duplicate entry, skip it
  4398.                         continue 2;
  4399.                     }
  4400.                     $char 0;
  4401.                     while (isset($entry[$char]&& isset($prev_keys[$level][$char])
  4402.                             && $entry[$char== $prev_keys[$level][$char]{
  4403.                         ++$char;
  4404.                     }
  4405.                     if ($char 0{
  4406.                         // this entry has at least some chars in common with the current key
  4407.                         if ($char == strlen($prev_keys[$level])) {
  4408.                             // current key is totally matched, i.e. this entry has just some bits appended
  4409.                             $pointer &$pointer[$prev_keys[$level]];
  4410.                         else {
  4411.                             // only part of the keys match
  4412.                             $new_key_part1 substr($prev_keys[$level]0$char);
  4413.                             $new_key_part2 substr($prev_keys[$level]$char);
  4414.  
  4415.                             if (in_array($new_key_part1[0]$regex_chars)
  4416.                                 || in_array($new_key_part2[0]$regex_chars)) {
  4417.                                 // this is bad, a regex char as first character
  4418.                                 $pointer[$entryarray('' => true);
  4419.                                 array_splice($prev_keys$levelcount($prev_keys)$entry);
  4420.                                 $cur_len += strlen($entry);
  4421.                                 continue;
  4422.                             else {
  4423.                                 // relocate previous tokens
  4424.                                 $pointer[$new_key_part1array($new_key_part2 => $pointer[$prev_keys[$level]]);
  4425.                                 unset($pointer[$prev_keys[$level]]);
  4426.                                 $pointer &$pointer[$new_key_part1];
  4427.                                 // recreate key index
  4428.                                 array_splice($prev_keys$levelcount($prev_keys)array($new_key_part1$new_key_part2));
  4429.                                 $cur_len += strlen($new_key_part2);
  4430.                             }
  4431.                         }
  4432.                         ++$level;
  4433.                         $entry substr($entry$char);
  4434.                         continue;
  4435.                     }
  4436.                     // else: fall trough, i.e. no common denominator was found
  4437.                 }
  4438.                 if ($level == && !empty($tokens)) {
  4439.                     // we can dump current tokens into the string and throw them away afterwards
  4440.                     $new_entry $this->_optimize_regexp_list_tokens_to_string($tokens);
  4441.                     $new_subpatterns substr_count($new_entry'(?:');
  4442.                     if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns $new_subpatterns GESHI_MAX_PCRE_SUBPATTERNS{
  4443.                         $regexp_list[++$list_key$new_entry;
  4444.                         $num_subpatterns $new_subpatterns;
  4445.                     else {
  4446.                         if (!empty($regexp_list[$list_key])) {
  4447.                             $new_entry '|' $new_entry;
  4448.                         }
  4449.                         $regexp_list[$list_key.= $new_entry;
  4450.                         $num_subpatterns += $new_subpatterns;
  4451.                     }
  4452.                     $tokens array();
  4453.                     $cur_len 0;
  4454.                 }
  4455.                 // no further common denominator found
  4456.                 $pointer[$entryarray('' => true);
  4457.                 array_splice($prev_keys$levelcount($prev_keys)$entry);
  4458.  
  4459.                 $cur_len += strlen($entry);
  4460.                 break;
  4461.             }
  4462.             unset($list[$i]);
  4463.         }
  4464.         // make sure the last tokens get converted as well
  4465.         $new_entry $this->_optimize_regexp_list_tokens_to_string($tokens);
  4466.         if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns substr_count($new_entry'(?:'GESHI_MAX_PCRE_SUBPATTERNS{
  4467.             $regexp_list[++$list_key$new_entry;
  4468.         else {
  4469.             if (!empty($regexp_list[$list_key])) {
  4470.                 $new_entry '|' $new_entry;
  4471.             }
  4472.             $regexp_list[$list_key.= $new_entry;
  4473.         }
  4474.         return $regexp_list;
  4475.     }
  4476.     /**
  4477.     * this function creates the appropriate regexp string of an token array
  4478.     * you should not call this function directly, @see $this->optimize_regexp_list().
  4479.     *
  4480.     * @param &$tokens array of tokens
  4481.     * @param $recursed bool to know wether we recursed or not
  4482.     * @return string
  4483.     * @author Milian Wolff <mail@milianw.de>
  4484.     * @since 1.0.8
  4485.     * @access private
  4486.     */
  4487.     function _optimize_regexp_list_tokens_to_string(&$tokens$recursed false{
  4488.         $list '';
  4489.         foreach ($tokens as $token => $sub_tokens{
  4490.             $list .= $token;
  4491.             $close_entry = isset($sub_tokens['']);
  4492.             unset($sub_tokens['']);
  4493.             if (!empty($sub_tokens)) {
  4494.                 $list .= '(?:' $this->_optimize_regexp_list_tokens_to_string($sub_tokenstrue')';
  4495.                 if ($close_entry{
  4496.                     // make sub_tokens optional
  4497.                     $list .= '?';
  4498.                 }
  4499.             }
  4500.             $list .= '|';
  4501.         }
  4502.         if (!$recursed{
  4503.             // do some optimizations
  4504.             // common trailing strings
  4505.             // BUGGY!
  4506.             //$list = preg_replace_callback('#(?<=^|\:|\|)\w+?(\w+)(?:\|.+\1)+(?=\|)#', create_function(
  4507.             //    '$matches', 'return "(?:" . preg_replace("#" . preg_quote($matches[1], "#") . "(?=\||$)#", "", $matches[0]) . ")" . $matches[1];'), $list);
  4508.             // (?:p)? => p?
  4509.             $list preg_replace('#\(\?\:(.)\)\?#''\1?'$list);
  4510.             // (?:a|b|c|d|...)? => [abcd...]?
  4511.             // TODO: a|bb|c => [ac]|bb
  4512.             static $callback_2;
  4513.             if (!isset($callback_2)) {
  4514.                 $callback_2 create_function('$matches''return "[" . str_replace("|", "", $matches[1]) . "]";');
  4515.             }
  4516.             $list preg_replace_callback('#\(\?\:((?:.\|)+.)\)#'$callback_2$list);
  4517.         }
  4518.         // return $list without trailing pipe
  4519.         return substr($list0-1);
  4520.     }
  4521. // End Class GeSHi
  4522.  
  4523.  
  4524. if (!function_exists('geshi_highlight')) {
  4525.     /**
  4526.      * Easy way to highlight stuff. Behaves just like highlight_string
  4527.      *
  4528.      * @param string The code to highlight
  4529.      * @param string The language to highlight the code in
  4530.      * @param string The path to the language files. You can leave this blank if you need
  4531.      *               as from version 1.0.7 the path should be automatically detected
  4532.      * @param boolean Whether to return the result or to echo
  4533.      * @return string The code highlighted (if $return is true)
  4534.      * @since 1.0.2
  4535.      */
  4536.     function geshi_highlight($string$language$path null$return false{
  4537.         $geshi new GeSHi($string$language$path);
  4538.         $geshi->set_header_type(GESHI_HEADER_NONE);
  4539.  
  4540.         if ($return{
  4541.             return '<code>' $geshi->parse_code('</code>';
  4542.         }
  4543.  
  4544.         echo '<code>' $geshi->parse_code('</code>';
  4545.  
  4546.         if ($geshi->error()) {
  4547.             return false;
  4548.         }
  4549.         return true;
  4550.     }
  4551. }
  4552.  
  4553. ?>

Documentation generated on Sun, 26 Oct 2008 23:21:01 +0100 by phpDocumentor 1.4.2