$header | |
$linenumbers | $code> |
$footer |
* The /e modifier inside* * @var array * @since 1.0.8 */ var $_kw_replace_group = 0; var $_rx_key = 0; /** * some "callback parameters" for handle_multiline_regexps * * @since 1.0.8 * @access private * @var string */ var $_hmr_before = ''; var $_hmr_replace = ''; var $_hmr_after = ''; var $_hmr_key = 0; /**#@-*/ /** * Creates a new GeSHi object, with source and language * * @param string The source code to highlight * @param string The language to highlight the source with * @param string The path to the language file directory. This * is deprecated! I've backported the auto path * detection from the 1.1.X dev branch, so now it * should be automatically set correctly. If you have * renamed the language directory however, you will * still need to set the path using this parameter or * {@link GeSHi->set_language_path()} * @since 1.0.0 */ function GeSHi($source = '', $language = '', $path = '') { if (!empty($source)) { $this->set_source($source); } if (!empty($language)) { $this->set_language($language); } $this->set_language_path($path); } /** * Returns an error message associated with the last GeSHi operation, * or false if no error has occured * * @return string|false An error message if there has been an error, else false * @since 1.0.0 */ function error() { if ($this->error) { //Put some template variables for debugging here ... $debug_tpl_vars = array( '{LANGUAGE}' => $this->language, '{PATH}' => $this->language_path ); $msg = str_replace( array_keys($debug_tpl_vars), array_values($debug_tpl_vars), $this->error_messages[$this->error]); return "preg_replace()
allows code execution. * Often it is the cause for remote code execution exploits. It is wise to * deactivate this feature and test where in the application it is used. * The developer using the /e modifier should be made aware that he should * usepreg_replace_callback()
instead *
array( * 'lang_name' => array('extension', 'extension', ...), * 'lang_name' ... * );* * @param string The filename to load the source from * @param array A lookup array to use instead of the default one * @todo Complete rethink of this and above method * @since 1.0.5 */ function load_from_file($file_name, $lookup = array()) { if (is_readable($file_name)) { $this->set_source(file_get_contents($file_name)); $this->set_language($this->get_language_name_from_extension(substr(strrchr($file_name, '.'), 1), $lookup)); } else { $this->error = GESHI_ERROR_FILE_NOT_READABLE; } } /** * Adds a keyword to a keyword group for highlighting * * @param int The key of the keyword group to add the keyword to * @param string The word to add to the keyword group * @since 1.0.0 */ function add_keyword($key, $word) { if (!in_array($word, $this->language_data['KEYWORDS'][$key])) { $this->language_data['KEYWORDS'][$key][] = $word; //NEW in 1.0.8 don't recompile the whole optimized regexp, simply append it if ($this->parse_cache_built) { $subkey = count($this->language_data['CACHED_KEYWORD_LISTS'][$key]) - 1; $this->language_data['CACHED_KEYWORD_LISTS'][$key][$subkey] .= '|' . preg_quote($word, '/'); } } } /** * Removes a keyword from a keyword group * * @param int The key of the keyword group to remove the keyword from * @param string The word to remove from the keyword group * @param bool Wether to automatically recompile the optimized regexp list or not. * Note: if you set this to false and @see GeSHi->parse_code() was already called once, * for the current language, you have to manually call @see GeSHi->optimize_keyword_group() * or the removed keyword will stay in cache and still be highlighted! On the other hand * it might be too expensive to recompile the regexp list for every removal if you want to * remove a lot of keywords. * @since 1.0.0 */ function remove_keyword($key, $word, $recompile = true) { $key_to_remove = array_search($word, $this->language_data['KEYWORDS'][$key]); if ($key_to_remove !== false) { unset($this->language_data['KEYWORDS'][$key][$key_to_remove]); //NEW in 1.0.8, optionally recompile keyword group if ($recompile && $this->parse_cache_built) { $this->optimize_keyword_group($key); } } } /** * Creates a new keyword group * * @param int The key of the keyword group to create * @param string The styles for the keyword group * @param boolean Whether the keyword group is case sensitive ornot * @param array The words to use for the keyword group * @since 1.0.0 */ function add_keyword_group($key, $styles, $case_sensitive = true, $words = array()) { $words = (array) $words; if (empty($words)) { // empty word lists mess up highlighting return false; } //Add the new keyword group internally $this->language_data['KEYWORDS'][$key] = $words; $this->lexic_permissions['KEYWORDS'][$key] = true; $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive; $this->language_data['STYLES']['KEYWORDS'][$key] = $styles; //NEW in 1.0.8, cache keyword regexp if ($this->parse_cache_built) { $this->optimize_keyword_group($key); } } /** * Removes a keyword group * * @param int The key of the keyword group to remove * @since 1.0.0 */ function remove_keyword_group ($key) { //Remove the keyword group internally unset($this->language_data['KEYWORDS'][$key]); unset($this->lexic_permissions['KEYWORDS'][$key]); unset($this->language_data['CASE_SENSITIVE'][$key]); unset($this->language_data['STYLES']['KEYWORDS'][$key]); //NEW in 1.0.8 unset($this->language_data['CACHED_KEYWORD_LISTS'][$key]); } /** * compile optimized regexp list for keyword group * * @param int The key of the keyword group to compile & optimize * @since 1.0.8 */ function optimize_keyword_group($key) { $this->language_data['CACHED_KEYWORD_LISTS'][$key] = $this->optimize_regexp_list($this->language_data['KEYWORDS'][$key]); $space_as_whitespace = false; if(isset($this->language_data['PARSER_CONTROL'])) { if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'])) { if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['SPACE_AS_WHITESPACE'])) { $space_as_whitespace = $this->language_data['PARSER_CONTROL']['KEYWORDS']['SPACE_AS_WHITESPACE']; } if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE'])) { if(isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE'])) { $space_as_whitespace = $this->language_data['PARSER_CONTROL']['KEYWORDS'][$key]['SPACE_AS_WHITESPACE']; } } } } if($space_as_whitespace) { foreach($this->language_data['CACHED_KEYWORD_LISTS'][$key] as $rxk => $rxv) { $this->language_data['CACHED_KEYWORD_LISTS'][$key][$rxk] = str_replace(" ", "\\s+", $rxv); } } } /** * Sets the content of the header block * * @param string The content of the header block * @since 1.0.2 */ function set_header_content($content) { $this->header_content = $content; } /** * Sets the content of the footer block * * @param string The content of the footer block * @since 1.0.2 */ function set_footer_content($content) { $this->footer_content = $content; } /** * Sets the style for the header content * * @param string The style for the header content * @since 1.0.2 */ function set_header_content_style($style) { $this->header_content_style = $style; } /** * Sets the style for the footer content * * @param string The style for the footer content * @since 1.0.2 */ function set_footer_content_style($style) { $this->footer_content_style = $style; } /** * Sets whether to force a surrounding block around * the highlighted code or not * * @param boolean Tells whether to enable or disable this feature * @since 1.0.7.20 */ function enable_inner_code_block($flag) { $this->force_code_block = (bool)$flag; } /** * Sets the base URL to be used for keywords * * @param int The key of the keyword group to set the URL for * @param string The URL to set for the group. If {FNAME} is in * the url somewhere, it is replaced by the keyword * that the URL is being made for * @since 1.0.2 */ function set_url_for_keyword_group($group, $url) { $this->language_data['URLS'][$group] = $url; } /** * Sets styles for links in code * * @param int A constant that specifies what state the style is being * set for - e.g. :hover or :visited * @param string The styles to use for that state * @since 1.0.2 */ function set_link_styles($type, $styles) { $this->link_styles[$type] = $styles; } /** * Sets the target for links in code * * @param string The target for links in the code, e.g. _blank * @since 1.0.3 */ function set_link_target($target) { if (!$target) { $this->link_target = ''; } else { $this->link_target = ' target="' . $target . '"'; } } /** * Sets styles for important parts of the code * * @param string The styles to use on important parts of the code * @since 1.0.2 */ function set_important_styles($styles) { $this->important_styles = $styles; } /** * Sets whether context-important blocks are highlighted * * @param boolean Tells whether to enable or disable highlighting of important blocks * @todo REMOVE THIS SHIZ FROM GESHI! * @deprecated * @since 1.0.2 */ function enable_important_blocks($flag) { $this->enable_important_blocks = ( $flag ) ? true : false; } /** * Whether CSS IDs should be added to each line * * @param boolean If true, IDs will be added to each line. * @since 1.0.2 */ function enable_ids($flag = true) { $this->add_ids = ($flag) ? true : false; } /** * Specifies which lines to highlight extra * * The extra style parameter was added in 1.0.7.21. * * @param mixed An array of line numbers to highlight, or just a line * number on its own. * @param string A string specifying the style to use for this line. * If null is specified, the default style is used. * If false is specified, the line will be removed from * special highlighting * @since 1.0.2 * @todo Some data replication here that could be cut down on */ function highlight_lines_extra($lines, $style = null) { if (is_array($lines)) { //Split up the job using single lines at a time foreach ($lines as $line) { $this->highlight_lines_extra($line, $style); } } else { //Mark the line as being highlighted specially $lines = intval($lines); $this->highlight_extra_lines[$lines] = $lines; //Decide on which style to use if ($style === null) { //Check if we should use default style unset($this->highlight_extra_lines_styles[$lines]); } else if ($style === false) { //Check if to remove this line unset($this->highlight_extra_lines[$lines]); unset($this->highlight_extra_lines_styles[$lines]); } else { $this->highlight_extra_lines_styles[$lines] = $style; } } } /** * Sets the style for extra-highlighted lines * * @param string The style for extra-highlighted lines * @since 1.0.2 */ function set_highlight_lines_extra_style($styles) { $this->highlight_extra_lines_style = $styles; } /** * Sets the line-ending * * @param string The new line-ending * @since 1.0.2 */ function set_line_ending($line_ending) { $this->line_ending = (string)$line_ending; } /** * Sets what number line numbers should start at. Should * be a positive integer, and will be converted to one. * * Warning: Using this method will add the "start" * attribute to the <ol> that is used for line numbering. * This is not valid XHTML strict, so if that's what you * care about then don't use this method. Firefox is getting * support for the CSS method of doing this in 1.1 and Opera * has support for the CSS method, but (of course) IE doesn't * so it's not worth doing it the CSS way yet. * * @param int The number to start line numbers at * @since 1.0.2 */ function start_line_numbers_at($number) { $this->line_numbers_start = abs(intval($number)); } /** * Sets the encoding used for htmlspecialchars(), for international * support. * * NOTE: This is not needed for now because htmlspecialchars() is not * being used (it has a security hole in PHP4 that has not been patched). * Maybe in a future version it may make a return for speed reasons, but * I doubt it. * * @param string The encoding to use for the source * @since 1.0.3 */ function set_encoding($encoding) { if ($encoding) { $this->encoding = strtolower($encoding); } } /** * Turns linking of keywords on or off. * * @param boolean If true, links will be added to keywords * @since 1.0.2 */ function enable_keyword_links($enable = true) { $this->keyword_links = (bool) $enable; } /** * Setup caches needed for styling. This is automatically called in * parse_code() and get_stylesheet() when appropriate. This function helps * stylesheet generators as they rely on some style information being * preprocessed * * @since 1.0.8 * @access private */ function build_style_cache() { //Build the style cache needed to highlight numbers appropriate if($this->lexic_permissions['NUMBERS']) { //First check what way highlighting information for numbers are given if(!isset($this->language_data['NUMBERS'])) { $this->language_data['NUMBERS'] = 0; } if(is_array($this->language_data['NUMBERS'])) { $this->language_data['NUMBERS_CACHE'] = $this->language_data['NUMBERS']; } else { $this->language_data['NUMBERS_CACHE'] = array(); if(!$this->language_data['NUMBERS']) { $this->language_data['NUMBERS'] = GESHI_NUMBER_INT_BASIC | GESHI_NUMBER_FLT_NONSCI; } for($i = 0, $j = $this->language_data['NUMBERS']; $j > 0; ++$i, $j>>=1) { //Rearrange style indices if required ... if(isset($this->language_data['STYLES']['NUMBERS'][1<<$i])) { $this->language_data['STYLES']['NUMBERS'][$i] = $this->language_data['STYLES']['NUMBERS'][1<<$i]; unset($this->language_data['STYLES']['NUMBERS'][1<<$i]); } //Check if this bit is set for highlighting if($j&1) { //So this bit is set ... //Check if it belongs to group 0 or the actual stylegroup if(isset($this->language_data['STYLES']['NUMBERS'][$i])) { $this->language_data['NUMBERS_CACHE'][$i] = 1 << $i; } else { if(!isset($this->language_data['NUMBERS_CACHE'][0])) { $this->language_data['NUMBERS_CACHE'][0] = 0; } $this->language_data['NUMBERS_CACHE'][0] |= 1 << $i; } } } } } } /** * Setup caches needed for parsing. This is automatically called in parse_code() when appropriate. * This function makes stylesheet generators much faster as they do not need these caches. * * @since 1.0.8 * @access private */ function build_parse_cache() { // cache symbol regexp //As this is a costy operation, we avoid doing it for multiple groups ... //Instead we perform it for all symbols at once. // //For this to work, we need to reorganize the data arrays. if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) { $this->language_data['MULTIPLE_SYMBOL_GROUPS'] = count($this->language_data['STYLES']['SYMBOLS']) > 1; $this->language_data['SYMBOL_DATA'] = array(); $symbol_preg_multi = array(); // multi char symbols $symbol_preg_single = array(); // single char symbols foreach ($this->language_data['SYMBOLS'] as $key => $symbols) { if (is_array($symbols)) { foreach ($symbols as $sym) { $sym = $this->hsc($sym); if (!isset($this->language_data['SYMBOL_DATA'][$sym])) { $this->language_data['SYMBOL_DATA'][$sym] = $key; if (isset($sym[1])) { // multiple chars $symbol_preg_multi[] = preg_quote($sym, '/'); } else { // single char if ($sym == '-') { // don't trigger range out of order error $symbol_preg_single[] = '\-'; } else { $symbol_preg_single[] = preg_quote($sym, '/'); } } } } } else { $symbols = $this->hsc($symbols); if (!isset($this->language_data['SYMBOL_DATA'][$symbols])) { $this->language_data['SYMBOL_DATA'][$symbols] = 0; if (isset($symbols[1])) { // multiple chars $symbol_preg_multi[] = preg_quote($symbols, '/'); } else if ($symbols == '-') { // don't trigger range out of order error $symbol_preg_single[] = '\-'; } else { // single char $symbol_preg_single[] = preg_quote($symbols, '/'); } } } } //Now we have an array with each possible symbol as the key and the style as the actual data. //This way we can set the correct style just the moment we highlight ... // //Now we need to rewrite our array to get a search string that $symbol_preg = array(); if (!empty($symbol_preg_multi)) { rsort($symbol_preg_multi); $symbol_preg[] = implode('|', $symbol_preg_multi); } if (!empty($symbol_preg_single)) { rsort($symbol_preg_single); $symbol_preg[] = '[' . implode('', $symbol_preg_single) . ']'; } $this->language_data['SYMBOL_SEARCH'] = implode("|", $symbol_preg); } // cache optimized regexp for keyword matching // remove old cache $this->language_data['CACHED_KEYWORD_LISTS'] = array(); foreach (array_keys($this->language_data['KEYWORDS']) as $key) { if (!isset($this->lexic_permissions['KEYWORDS'][$key]) || $this->lexic_permissions['KEYWORDS'][$key]) { $this->optimize_keyword_group($key); } } // brackets if ($this->lexic_permissions['BRACKETS']) { $this->language_data['CACHE_BRACKET_MATCH'] = array('[', ']', '(', ')', '{', '}'); if (!$this->use_classes && isset($this->language_data['STYLES']['BRACKETS'][0])) { $this->language_data['CACHE_BRACKET_REPLACE'] = array( '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">[|>', '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">]|>', '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">(|>', '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">)|>', '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">{|>', '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">}|>', ); } else { $this->language_data['CACHE_BRACKET_REPLACE'] = array( '<| class="br0">[|>', '<| class="br0">]|>', '<| class="br0">(|>', '<| class="br0">)|>', '<| class="br0">{|>', '<| class="br0">}|>', ); } } //Build the parse cache needed to highlight numbers appropriate if($this->lexic_permissions['NUMBERS']) { //Check if the style rearrangements have been processed ... //This also does some preprocessing to check which style groups are useable ... if(!isset($this->language_data['NUMBERS_CACHE'])) { $this->build_style_cache(); } //Number format specification //All this formats are matched case-insensitively! static $numbers_format = array( GESHI_NUMBER_INT_BASIC => '(? '(? '(? '(? '(? '(? '(? '(? '(? '(? '(? '(? '(?language_data['NUMBERS_RXCACHE'] = array(); foreach($this->language_data['NUMBERS_CACHE'] as $key => $rxdata) { if(is_string($rxdata)) { $regexp = $rxdata; } else { //This is a bitfield of number flags to highlight: //Build an array, implode them together and make this the actual RX $rxuse = array(); for($i = 1; $i <= $rxdata; $i<<=1) { if($rxdata & $i) { $rxuse[] = $numbers_format[$i]; } } $regexp = implode("|", $rxuse); } $this->language_data['NUMBERS_RXCACHE'][$key] = "/(?)($regexp)(?!\|>)/i"; } } $this->parse_cache_built = true; } /** * Returns the code in $this->source, highlighted and surrounded by the * nessecary HTML. * * This should only be called ONCE, cos it's SLOW! If you want to highlight * the same source multiple times, you're better off doing a whole lot of * str_replaces to replace the <span>s * * @since 1.0.0 */ function parse_code () { // Start the timer $start_time = microtime(); // Firstly, if there is an error, we won't highlight if ($this->error) { //Escape the source for output $result = $this->hsc($this->source); //This fix is related to SF#1923020, but has to be applied regardless of //actually highlighting symbols. $result = str_replace(array('
header, we shouldn't add newlines because // thewill line-break them (and the
"; $end = '
'; } elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) { if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { if ($this->use_classes) { $attrs = ' class="ln"'; } else { $attrs = ' style="'. $this->table_linenumber_style .'"'; } $parsed_code .= ' '; // get linenumbers // we don't merge it with the for below, since it should be better for // memory consumption this way // @todo: but... actually it would still be somewhat nice to merge the two loops // the mem peaks are at different positions for ($i = 0; $i < $n; ++$i) { $close = 0; // fancy lines if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS && $i % $this->line_nth_row == ($this->line_nth_row - 1)) { // Set the attributes to style the line if ($this->use_classes) { $parsed_code .= ''; } else { // This style "covers up" the special styles set for special lines // so that styles applied to special lines don't apply to the actual // code on that line $parsed_code .= '' .''; } $close += 2; } //Is this some line with extra styles??? if (in_array($i + 1, $this->highlight_extra_lines)) { if ($this->use_classes) { if (isset($this->highlight_extra_lines_styles[$i])) { $parsed_code .= ""; } else { $parsed_code .= ""; } } else { $parsed_code .= "get_line_style($i) . "\">"; } ++$close; } $parsed_code .= $this->line_numbers_start + $i; if ($close) { $parsed_code .= str_repeat('', $close); } else if ($i != $n) { $parsed_code .= "\n"; } } $parsed_code .= ''; } $parsed_code .= ' '; } // No line numbers, but still need to handle highlighting lines extra. // Have to use divs so the full width of the code is highlighted $close = 0; for ($i = 0; $i < $n; ++$i) { // Make lines have at least one space in them if they're empty // BenBE: Checking emptiness using trim instead of relying on blanks if ('' == trim($code[$i])) { $code[$i] = ' '; } // fancy lines if ($this->line_numbers == GESHI_FANCY_LINE_NUMBERS && $i % $this->line_nth_row == ($this->line_nth_row - 1)) { // Set the attributes to style the line if ($this->use_classes) { $parsed_code .= ''; } else { // This style "covers up" the special styles set for special lines // so that styles applied to special lines don't apply to the actual // code on that line $parsed_code .= '' .''; } $close += 2; } //Is this some line with extra styles??? if (in_array($i + 1, $this->highlight_extra_lines)) { if ($this->use_classes) { if (isset($this->highlight_extra_lines_styles[$i])) { $parsed_code .= ""; } else { $parsed_code .= ""; } } else { $parsed_code .= "get_line_style($i) . "\">"; } ++$close; } $parsed_code .= $code[$i]; if ($close) { $parsed_code .= str_repeat('', $close); $close = 0; } elseif ($i + 1 < $n) { $parsed_code .= "\n"; } unset($code[$i]); } if ($this->header_type == GESHI_HEADER_PRE_VALID || $this->header_type == GESHI_HEADER_PRE_TABLE) { $parsed_code .= ''; } if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) { $parsed_code .= ''; } } $parsed_code .= $this->footer(); } /** * Creates the header for the code block (with correct attributes) * * @return string The header for the code block * @since 1.0.0 * @access private */ function header() { // Get attributes needed /** * @todo Document behaviour change - class is outputted regardless of whether * we're using classes or not. Same with style */ $attributes = ' class="' . $this->language; if ($this->overall_class != '') { $attributes .= " ".$this->overall_class; } $attributes .= '"'; if ($this->overall_id != '') { $attributes .= " id=\"{$this->overall_id}\""; } if ($this->overall_style != '') { $attributes .= ' style="' . $this->overall_style . '"'; } $ol_attributes = ''; if ($this->line_numbers_start != 1) { $ol_attributes .= ' start="' . $this->line_numbers_start . '"'; } // Get the header HTML $header = $this->header_content; if ($header) { if ($this->header_type == GESHI_HEADER_PRE || $this->header_type == GESHI_HEADER_PRE_VALID) { $header = str_replace("\n", '', $header); } $header = $this->replace_keywords($header); if ($this->use_classes) { $attr = ' class="head"'; } else { $attr = " style=\"{$this->header_content_style}\""; } if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) { $header = "$header"; } else { $header = "
$header"; } } if (GESHI_HEADER_NONE == $this->header_type) { if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { return "$header"; } return $header . ($this->force_code_block ? '
' : ''); } // Work out what to return and do it if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { if ($this->header_type == GESHI_HEADER_PRE) { return "' : '') . "$footer"; } elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) { if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { return "$footer"; } return ($this->force_code_block ? '' : '') . "$footer"; } else { if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { return "$footer"; } return ($this->force_code_block ? '' : '') . "$footer"; } } /** * Replaces certain keywords in the header and footer with * certain configuration values * * @param string The header or footer content to do replacement on * @return string The header or footer with replaced keywords * @since 1.0.2 * @access private */ function replace_keywords($instr) { $keywords = $replacements = array(); $keywords[] = '$header"; } else if ($this->header_type == GESHI_HEADER_DIV || $this->header_type == GESHI_HEADER_PRE_VALID) { return "
$header"; } else if ($this->header_type == GESHI_HEADER_PRE_TABLE) { return "
$header"; } } else { if ($this->header_type == GESHI_HEADER_PRE) { return "
$header" . ($this->force_code_block ? '' : ''); } else { return ""; } return ($this->force_code_block ? '$header" . ($this->force_code_block ? '' : ''); } } } /** * Returns the footer for the code block. * * @return string The footer for the code block * @since 1.0.0 * @access private */ function footer() { $footer = $this->footer_content; if ($footer) { if ($this->header_type == GESHI_HEADER_PRE) { $footer = str_replace("\n", '', $footer);; } $footer = $this->replace_keywords($footer); if ($this->use_classes) { $attr = ' class="foot"'; } else { $attr = " style=\"{$this->footer_content_style}\""; } if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) { $footer = "$footer"; } else { $footer = ""; } } if (GESHI_HEADER_NONE == $this->header_type) { return ($this->line_numbers != GESHI_NO_LINE_NUMBERS) ? '' . $footer : $footer; } if ($this->header_type == GESHI_HEADER_DIV || $this->header_type == GESHI_HEADER_PRE_VALID) { if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) { return "$footer$footer
orcontainer). Additionally, set default styles for lines if (!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) { //$stylesheet .= "$selector, {$selector}ol, {$selector}ol li {margin: 0;}\n"; $stylesheet .= "$selector.de1, $selector.de2 {{$this->code_style}}\n"; } // Add overall styles // note: neglect economy_mode, empty styles are meaningless if ($this->overall_style != '') { $stylesheet .= "$selector {{$this->overall_style}}\n"; } // Add styles for links // note: economy mode does not make _any_ sense here // either the style is empty and thus no selector is needed // or the appropriate key is given. foreach ($this->link_styles as $key => $style) { if ($style != '') { switch ($key) { case GESHI_LINK: $stylesheet .= "{$selector}a:link {{$style}}\n"; break; case GESHI_HOVER: $stylesheet .= "{$selector}a:hover {{$style}}\n"; break; case GESHI_ACTIVE: $stylesheet .= "{$selector}a:active {{$style}}\n"; break; case GESHI_VISITED: $stylesheet .= "{$selector}a:visited {{$style}}\n"; break; } } } // Header and footer // note: neglect economy_mode, empty styles are meaningless if ($this->header_content_style != '') { $stylesheet .= "$selector.head {{$this->header_content_style}}\n"; } if ($this->footer_content_style != '') { $stylesheet .= "$selector.foot {{$this->footer_content_style}}\n"; } // Styles for important stuff // note: neglect economy_mode, empty styles are meaningless if ($this->important_styles != '') { $stylesheet .= "$selector.imp {{$this->important_styles}}\n"; } // Simple line number styles if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->line_style1 != '') { $stylesheet .= "{$selector}li, {$selector}.li1 {{$this->line_style1}}\n"; } if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->table_linenumber_style != '') { $stylesheet .= "{$selector}.ln {{$this->table_linenumber_style}}\n"; } // If there is a style set for fancy line numbers, echo it out if ((!$economy_mode || $this->line_numbers == GESHI_FANCY_LINE_NUMBERS) && $this->line_style2 != '') { $stylesheet .= "{$selector}.li2 {{$this->line_style2}}\n"; } // note: empty styles are meaningless foreach ($this->language_data['STYLES']['KEYWORDS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || (isset($this->lexic_permissions['KEYWORDS'][$group]) && $this->lexic_permissions['KEYWORDS'][$group]))) { $stylesheet .= "$selector.kw$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['COMMENTS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || (isset($this->lexic_permissions['COMMENTS'][$group]) && $this->lexic_permissions['COMMENTS'][$group]) || (!empty($this->language_data['COMMENT_REGEXP']) && !empty($this->language_data['COMMENT_REGEXP'][$group])))) { $stylesheet .= "$selector.co$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['ESCAPE_CHAR'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['ESCAPE_CHAR'])) { // NEW: since 1.0.8 we have to handle hardescapes if ($group === 'HARD') { $group = '_h'; } $stylesheet .= "$selector.es$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['BRACKETS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['BRACKETS'])) { $stylesheet .= "$selector.br$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['SYMBOLS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['SYMBOLS'])) { $stylesheet .= "$selector.sy$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['STRINGS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['STRINGS'])) { // NEW: since 1.0.8 we have to handle hardquotes if ($group === 'HARD') { $group = '_h'; } $stylesheet .= "$selector.st$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['NUMBERS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['NUMBERS'])) { $stylesheet .= "$selector.nu$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['METHODS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || $this->lexic_permissions['METHODS'])) { $stylesheet .= "$selector.me$group {{$styles}}\n"; } } // note: neglect economy_mode, empty styles are meaningless foreach ($this->language_data['STYLES']['SCRIPT'] as $group => $styles) { if ($styles != '') { $stylesheet .= "$selector.sc$group {{$styles}}\n"; } } foreach ($this->language_data['STYLES']['REGEXPS'] as $group => $styles) { if ($styles != '' && (!$economy_mode || (isset($this->lexic_permissions['REGEXPS'][$group]) && $this->lexic_permissions['REGEXPS'][$group]))) { if (is_array($this->language_data['REGEXPS'][$group]) && array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$group])) { $stylesheet .= "$selector."; $stylesheet .= $this->language_data['REGEXPS'][$group][GESHI_CLASS]; $stylesheet .= " {{$styles}}\n"; } else { $stylesheet .= "$selector.re$group {{$styles}}\n"; } } } // Styles for lines being highlighted extra if (!$economy_mode || (count($this->highlight_extra_lines)!=count($this->highlight_extra_lines_styles))) { $stylesheet .= "{$selector}.ln-xtra, {$selector}li.ln-xtra, {$selector}div.ln-xtra {{$this->highlight_extra_lines_style}}\n"; } $stylesheet .= "{$selector}span.xtra { display:block; }\n"; foreach ($this->highlight_extra_lines_styles as $lineid => $linestyle) { $stylesheet .= "{$selector}.lx$lineid, {$selector}li.lx$lineid, {$selector}div.lx$lineid {{$linestyle}}\n"; } return $stylesheet; } /** * Get's the style that is used for the specified line * * @param int The line number information is requested for * @access private * @since 1.0.7.21 */ function get_line_style($line) { //$style = null; $style = null; if (isset($this->highlight_extra_lines_styles[$line])) { $style = $this->highlight_extra_lines_styles[$line]; } else { // if no "extra" style assigned $style = $this->highlight_extra_lines_style; } return $style; } /** * this functions creates an optimized regular expression list * of an array of strings. * * Example: *$list = array('faa', 'foo', 'foobar'); * => string 'f(aa|oo(bar)?)'
* * @param $list array of (unquoted) strings * @param $regexp_delimiter your regular expression delimiter, @see preg_quote() * @return string for regular expression * @author Milian Wolff* @since 1.0.8 * @access private */ function optimize_regexp_list($list, $regexp_delimiter = '/') { $regex_chars = array('.', '\\', '+', '*', '?', '[', '^', ']', '$', '(', ')', '{', '}', '=', '!', '<', '>', '|', ':', $regexp_delimiter); sort($list); $regexp_list = array(''); $num_subpatterns = 0; $list_key = 0; // the tokens which we will use to generate the regexp list $tokens = array(); $prev_keys = array(); // go through all entries of the list and generate the token list $cur_len = 0; for ($i = 0, $i_max = count($list); $i < $i_max; ++$i) { if ($cur_len > GESHI_MAX_PCRE_LENGTH) { // seems like the length of this pcre is growing exorbitantly $regexp_list[++$list_key] = $this->_optimize_regexp_list_tokens_to_string($tokens); $num_subpatterns = substr_count($regexp_list[$list_key], '(?:'); $tokens = array(); $cur_len = 0; } $level = 0; $entry = preg_quote((string) $list[$i], $regexp_delimiter); $pointer = &$tokens; // properly assign the new entry to the correct position in the token array // possibly generate smaller common denominator keys while (true) { // get the common denominator if (isset($prev_keys[$level])) { if ($prev_keys[$level] == $entry) { // this is a duplicate entry, skip it continue 2; } $char = 0; while (isset($entry[$char]) && isset($prev_keys[$level][$char]) && $entry[$char] == $prev_keys[$level][$char]) { ++$char; } if ($char > 0) { // this entry has at least some chars in common with the current key if ($char == strlen($prev_keys[$level])) { // current key is totally matched, i.e. this entry has just some bits appended $pointer = &$pointer[$prev_keys[$level]]; } else { // only part of the keys match $new_key_part1 = substr($prev_keys[$level], 0, $char); $new_key_part2 = substr($prev_keys[$level], $char); if (in_array($new_key_part1[0], $regex_chars) || in_array($new_key_part2[0], $regex_chars)) { // this is bad, a regex char as first character $pointer[$entry] = array('' => true); array_splice($prev_keys, $level, count($prev_keys), $entry); $cur_len += strlen($entry); continue; } else { // relocate previous tokens $pointer[$new_key_part1] = array($new_key_part2 => $pointer[$prev_keys[$level]]); unset($pointer[$prev_keys[$level]]); $pointer = &$pointer[$new_key_part1]; // recreate key index array_splice($prev_keys, $level, count($prev_keys), array($new_key_part1, $new_key_part2)); $cur_len += strlen($new_key_part2); } } ++$level; $entry = substr($entry, $char); continue; } // else: fall trough, i.e. no common denominator was found } if ($level == 0 && !empty($tokens)) { // we can dump current tokens into the string and throw them away afterwards $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens); $new_subpatterns = substr_count($new_entry, '(?:'); if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + $new_subpatterns > GESHI_MAX_PCRE_SUBPATTERNS) { $regexp_list[++$list_key] = $new_entry; $num_subpatterns = $new_subpatterns; } else { if (!empty($regexp_list[$list_key])) { $new_entry = '|' . $new_entry; } $regexp_list[$list_key] .= $new_entry; $num_subpatterns += $new_subpatterns; } $tokens = array(); $cur_len = 0; } // no further common denominator found $pointer[$entry] = array('' => true); array_splice($prev_keys, $level, count($prev_keys), $entry); $cur_len += strlen($entry); break; } unset($list[$i]); } // make sure the last tokens get converted as well $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens); if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + substr_count($new_entry, '(?:') > GESHI_MAX_PCRE_SUBPATTERNS) { $regexp_list[++$list_key] = $new_entry; } else { if (!empty($regexp_list[$list_key])) { $new_entry = '|' . $new_entry; } $regexp_list[$list_key] .= $new_entry; } return $regexp_list; } /** * this function creates the appropriate regexp string of an token array * you should not call this function directly, @see $this->optimize_regexp_list(). * * @param &$tokens array of tokens * @param $recursed bool to know wether we recursed or not * @return string * @author Milian Wolff /] [lib/] [geshi.php] - Blame information for rev 3* @since 1.0.8 * @access private */ function _optimize_regexp_list_tokens_to_string(&$tokens, $recursed = false) { $list = ''; foreach ($tokens as $token => $sub_tokens) { $list .= $token; $close_entry = isset($sub_tokens['']); unset($sub_tokens['']); if (!empty($sub_tokens)) { $list .= '(?:' . $this->_optimize_regexp_list_tokens_to_string($sub_tokens, true) . ')'; if ($close_entry) { // make sub_tokens optional $list .= '?'; } } $list .= '|'; } if (!$recursed) { // do some optimizations // common trailing strings // BUGGY! //$list = preg_replace_callback('#(?<=^|\:|\|)\w+?(\w+)(?:\|.+\1)+(?=\|)#', create_function( // '$matches', 'return "(?:" . preg_replace("#" . preg_quote($matches[1], "#") . "(?=\||$)#", "", $matches[0]) . ")" . $matches[1];'), $list); // (?:p)? => p? $list = preg_replace('#\(\?\:(.)\)\?#', '\1?', $list); // (?:a|b|c|d|...)? => [abcd...]? // TODO: a|bb|c => [ac]|bb static $callback_2; if (!isset($callback_2)) { $callback_2 = create_function('$matches', 'return "[" . str_replace("|", "", $matches[1]) . "]";'); } $list = preg_replace_callback('#\(\?\:((?:.\|)+.)\)#', $callback_2, $list); } // return $list without trailing pipe return substr($list, 0, -1); } } // End Class GeSHi if (!function_exists('geshi_highlight')) { /** * Easy way to highlight stuff. Behaves just like highlight_string * * @param string The code to highlight * @param string The language to highlight the code in * @param string The path to the language files. You can leave this blank if you need * as from version 1.0.7 the path should be automatically detected * @param boolean Whether to return the result or to echo * @return string The code highlighted (if $return is true) * @since 1.0.2 */ function geshi_highlight($string, $language, $path = null, $return = false) { $geshi = new GeSHi($string, $language, $path); $geshi->set_header_type(GESHI_HEADER_NONE); if ($return) { return ' ' . $geshi->parse_code() . '
'; } echo '' . $geshi->parse_code() . '
'; if ($geshi->error()) { return false; } return true; } } ?>WebSVN - websvn - Blame - Rev 3 - /lib/geshi.php
[
websvn
Subversion Repositories:
Line No. Rev Author Line Powered by WebSVN 2.2.1