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