Introducing the GeSHi Syntax Highlighter for b2 (Take 2)

[Note: I am reposting this with updated installation instructions and code corrections due to a bug with the hack itself that rendered some of the code meaningless in the original post.]

Yesterday I posted on how I integrated the GeSHi Syntax Highlighter into FlashAnt to highlight AS2. I also mentioned how GeSHi supports over 30 languages. Having already gotten a request for the source -- read on, Richard :) -- and not being one who can leave well enough alone, I spent a little time this morning to clean up the hack and make it generic enough to support any language. Of course, nothing being simple, I was waylaid by an over-anxious slash-adding routine in b2 (which had me doubting my sanity and my PHP server settings), work, email and, quite unexpectedly, smileys :x -- which b2 insists on erroneously calling "smilies!" :roll: (Yes, along the way I have become intimately familiar with the various types of smileys on offer and their codes, so get ready for some smiley spam in future posts!) :mrgreen:

Although I could bore you to death with details of the various issues, suffice to say that I've now gotten the whole thing to work.

So, without further ado, taadaaa..! We now have an official GeSHi Syntax Highlighter hack for b2! :)

I'm releasing it under the GPL license as per the original b2 code.

Instructions for installing the hack:

1. Back-up your site if its not under version control. I take no responsibility if you somehow manage to blow up your computer, infuriate your wife or do something equally nasty to your pet hamster while trying to install this.

2. Add the following configuration information, customizing it for your needs, to the b2config.php file in the root folder of your b2 installation:

PHP:
  1. ////////////////////////////////////////////////////////////////////////
  2. //
  3. // Geshi Syntax Highlighter Configuration
  4. // Added by Aral Balkan. 17/12/2004
  5. // Comments? Suggestions? Email aral@ariaware.com
  6. // Docs, updates: http://flashant.org
  7. //
  8. ////////////////////////////////////////////////////////////////////////
  9.  
  10. //
  11. // The path to your language files
  12. // The default configuration assumes that you have a folder called
  13. // geshi in the root folder of your b2 installation.
  14. //
  15. $geshiLanguageFilesPath = 'geshi';
  16.  
  17. //
  18. // Maps the tags used to identify different languages to
  19. // the name of the Geshi syntax file and the nicely formatted
  20. // human-readable name to be displayed on top of the
  21. // highlighted code segment.
  22. //
  23. // So, for example, with the default settings, you will need
  24. // to surround ActionScript 2 code with as2 tags
  25. // and PHP code with php tags. Geshi will then
  26. // use the syntax rules in geshi/actionscript.php for the former
  27. // and geshi/php.php for the latter. The code segments for AS2
  28. // will be preceding with the label "ActionScript:" and for PHP
  29. // the label will be "PHP:".
  30. //
  31. $geshiLanguages = array
  32. (
  33. 'as2' => array ( 'actionscript', 'ActionScript' ),
  34. 'php' => array ( 'php', 'PHP' ),
  35. 'java' => array ( 'java', 'Java' ),
  36. 'csharp' => array ( 'csharp', 'C#' )
  37. );
  38.  
  39. //
  40. // Should GeSHi use line numbers in the output? (Does not appear
  41. // to display on IE but works well in FireFox. Degrades gracefully
  42. // in IE.)
  43. //
  44. $geshiEnableLineNumbers = true;
  45.  
  46. ////////////////////////////////////////////////////////////////////////
  47.  

3. Add the following lines of code to the top of the b2functions.php file in the b2-include folder:

PHP:
  1. // Include the GeSHi Syntax Highlighter
  2. include_once ('geshi.php');

4. In b2functions.php, again, add the following two functions:

PHP:
  1. //////////////////////////////////////////////////////////////////////
  2. //
  3. // GeSHi syntax highlighter module for b2
  4. //
  5. // Added by Aral Balkan. 17/12/2004
  6. // Comments? Suggestions? Email aral@ariaware.com
  7. // Docs, updates: http://flashant.org
  8. //
  9. //////////////////////////////////////////////////////////////////////
  10. function syntaxHighlight ( $content )
  11. {
  12. // Configuration information from b2config.php
  13. global $geshiLanguages;
  14.  
  15. // Highlight each language in turn
  16. foreach ( $geshiLanguages as $tag => $languageInfo )
  17. {
  18. $content = geshiHighlight ( $content, $tag, $languageInfo[0], $languageInfo[1] );
  19. }
  20. return $content;
  21. }
  22.  
  23. function geshiHighlight ( $content, $tag, $languageName, $languageDisplayName )
  24. {
  25. // Configuration information from b2config.php
  26. global $geshiLanguageFilesPath, $geshiEnableLineNumbers;
  27.  
  28. // Create a GeSHi instance
  29. $geshi = new GeSHi('', $languageName, $geshiLanguageFilesPath);
  30. $geshi->enable_line_numbers($geshiEnableLineNumbers);
  31.  
  32. // Match the contents of the requested tag
  33. // and return them in an array.
  34. $preg="/<".$tag.">(.*?)<\/".$tag.">/si";
  35. preg_match_all($preg,$content,$code);
  36.  
  37. foreach ($code[1] as $rawSource)
  38. {
  39. // We don't want smiley faces :)
  40. $rawSource = str_replace( ': P', ': P', $rawSource );
  41. $geshi->set_source( $rawSource );
  42. $highlightedSource = $geshi->parse_code();
  43. //
  44. // Got this style from the ActionScript highlighting hack for VBulletin
  45. // that we use on the London MMUG web site. Works well for b2!
  46. // Should parameterize the style settings into b2config at some point.
  47. // For the time being, just alter them here.
  48. //
  49. $styleHack = '<div style="margin:20px; margin-top:5px">';
  50. $styleHack .= '<div class="smallfont" style="margin- bottom:2px">'.$languageDisplayName.':</div>';
  51. $styleHack .= '<div class="alt2" style="margin:0px; padding:6px; border:1px ';
  52. $styleHack .= 'inset; width:420px; height:178px; overflow:auto">';
  53. $highlightedSource = $styleHack . $highlightedSource . '</div></div>';
  54. $content = str_replace( "<$tag>".$rawSource."</$tag>", $highlightedSource, $content );
  55. }
  56. // Return the highlighted content
  57. return $content;
  58. }
  59. //////////////////////////////////////////////////////////////////////
  60.  

5. Replace the convert_smilies method in b2functions.php with the one below. You need to do this so that b2 doesn't make smileys out of your data-type declarations (things like var something:Preloader would otherwise show up with a :P in the middle!) This new method uses a regular expression that is not nearly as greedy as str_replace.

PHP:
  1. function convert_smilies($content)
  2. {
  3. global $smilies_directory, $use_smilies;
  4. global $b2smiliestrans, $b2_smiliesreplace;
  5. if ($use_smilies)
  6. {
  7. foreach ( $b2smiliestrans as $smilie => $img )
  8. {
  9. // Escape characters that have special meaning for RegExp
  10. // and build the RegExp.
  11. $smilieRegExp = str_replace ( '(', '\(', $smilie );
  12. $smilieRegExp = str_replace ( ')', '\)', $smilieRegExp );
  13. $smilieRegExp = str_replace ( '?', '\?', $smilieRegExp );
  14. $smilieRegExp = str_replace ( '|', '\|', $smilieRegExp );
  15. $smilieRegexp = '/((?<=[ (\\t\\n>])|\\r\\n|\\A)'.$smilieRegExp.'((?=[ ,.)!\\t\\n<])|\\r\\n|\\Z)/';
  16. $content = preg_replace ( $smilieRegexp, $b2_smiliesreplace[$smilie], $content );
  17. }
  18. }
  19.  
  20. return ($content);
  21. }

6. Next, in b2edit.php (root folder of your b2 installation), you need to comment out the overly anxious slash adding routine which appears to be left-over code. It should not be necessary as the various methods in b2functions.php all carry out the adding of slashes when necessary. We need to remove this otherwise GeSHi rightly gets confused when it finds code with slashes behind every single and double quote!

You can see the section to be commented out below, with a bit of the surrounding code to put it into context.

PHP:
  1. <?php
  2. $title = "Post / Edit";
  3. /* <Edit> */
  4.  
  5. // Removed due to conflict with the GeSHi Syntax Highlighter hack.
  6. /*
  7. function add_magic_quotes($array) {
  8. foreach ($array as $k => $v) {
  9. if (is_array($v)) {
  10. $array[$k] = add_magic_quotes($v);
  11. } else {
  12. $array[$k] = addslashes($v);
  13. }
  14. }
  15. return $array;
  16. }
  17. if (!get_magic_quotes_gpc()) {
  18. $HTTP_GET_VARS = add_magic_quotes($HTTP_GET_VARS);
  19. $HTTP_POST_VARS = add_magic_quotes($HTTP_POST_VARS);
  20. $HTTP_COOKIE_VARS = add_magic_quotes($HTTP_COOKIE_VARS);
  21. }
  22. */
  23.  
  24. $b2varstoreset = array('action','safe_mode','withcomments','c','posts','poststart','content','edited_post_title','comment_error','profile', 'trackback_url');
  25. for ($i=0; $i<count($b2varstoreset); $i += 1) {

7. Also in b2edit.php, search for "case 'post':" (without the quatation marks). After the $content = format_to_post($content); line, add the following line of code:

PHP:
  1. $content = addslashes ( $content );

8. Next, modify the format_to_post() method in b2functions.php to add the syntax highlighting (see line 4.) It should resemble the code below once you've implemented it:

PHP:
  1. function format_to_post($content) {
  2. global $post_autobr,$comment_autobr;
  3.  
  4. // Add Geshi syntax highlighting to code segments
  5. $content = syntaxHighlight ( $content );
  6. $content = addslashes($content);
  7.  
  8. if ($post_autobr || $comment_autobr) { $content = autobrize($content); }
  9. return($content);
  10. }

9. Finally, you need to download GeSHi and copy (FTP, etc.) geshi.php and the geshi folder (with the various language files) to the root folder of your b2 installation.

That's it! You can now define custom tags for highlighting a plethora of languages in your posts. GeSHi currently supports Actionscript ADA, Apache Log, ASM, ASP, Bash, C, C for Macs, C#, C++, CAD DCL, CadLisp, CSS, Delphi, HTML, Java, Javascript, Lisp, Lua, NSIS, Objective C, OpenOffice BASIC, Pascal, Perl, PHP, Python, Q(uick)BASIC, Smarty, SQL, VB.NET, Visual BASIC, Visual Fox Pro and XML.

It's now possible to switch from...

ActionScript:
  1. class Isnt extends MovieClip
  2. {
  3. function Isnt ()
  4. {
  5. trace ( "Isn't " );
  6. }
  7. }

To, say...

Java:
  1. class This
  2. {
  3. public static void main(String args[])
  4. {
  5. System.out.println( "this " );
  6. }
  7. }

To...

C#:
  1. using System;
  2.  
  3. public class Cool
  4. {
  5. public static void Main()
  6. {
  7. Console.WriteLine( " cool?" );
  8. }
  9. }

Isn't this cool?

I haven't tested the performance hit when you have lots of languages enabled (works fine on four, currently) but it should not be a problem since the code is only executed when you are making a post.

Note that once you've highlighted your code, it will not revert back to its unhighlighted state when you edit the post. If you need to make changes to the code, I suggest you either make a separate post and copy the code from there or copy the whole post, delete it and repost it. I'm going to look into how easy it will be to hack the Preview functionality so that it includes the hack. That should make it easier to edit posts for code updates.

I hope you find this hack useful and please feel free to email me with your comments, suggestions and improvements.