want to join us? register, it's easy | help

Integrating Textile And GeSHi

Integration

Firstly you'll need to get a copy of both Textile and GeSHi.

Now here is the full code:

<?php
 
class ArticleRenderer
{
	function render_content($content)
	{
		require_once('Textile.php');
                require_once('geshi.php');
 
		$text = preg_replace_callback('@\<code(.*?)\>(.+?)\</code\>@s', array($this, 'geshi_callback'), $content);
 
		$textile = @new Textile();
		$content = @$textile->process($text);
 
		return $content;
	}
 
	function geshi_callback($matches)
	{
		$lang_match = array();
 
		if (preg_match('@(lang|language)="([^\"]*)"@', $matches[1], $lang_match))
		{
			$lang = $lang_match[2];
		}
		else
		{
			$lang = "php";
		}
 
		$_line_numbering = true;
		if (preg_match('@linenum="([^\"]*)"@', $matches[1], $line_number_match))
		{
			switch ($line_number_match[1])
			{
				case 'false' :
					{
						$_line_numbering = false;
						break;
					}
				case 'true' :
					{
						$_line_numbering = true;
						break;
					}
				default: $_line_numbering = true;
			}
		}
 
		$text = $matches[2];
 
		// Inline or block level piece?
		$multiline = ereg("[\n\r]", $text);
 
		$geshi = new GeSHi($text, $lang);
 
		if ($_line_numbering)
		{
			$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);
		}
 
		//parse the code
		$text = $geshi->parse_code();
 
		if ($multiline)
		{
			$text = "\r\n==\r\n". '<div class="code">' . $text . '</div>' . "\r\n==\r\n";
		}
 
		return $text;
	}
}
 
?>

How it works

First, we have to find any text that is enclosed by the "code" tags. This is done using the preg_replace_callback function in PHP.

$text = preg_replace_callback('@\<code(.*?)\>(.+?)\</code\>@s', array($this, 'geshi_callback'), $content);

The regex used here will match any attributes the "code" tag has, for example lang="php" or linenum="false".

When a match is found, the matched text is passed to the callback function as an array of values. Once the callback function finishes, it returns the new text that will replace the found text.

The array of values that are passed to the callback function will contain (in 0-indexed order):

  1. The entire text that was matched
  2. The attributes that were matched, ie lang="php" linenum="false"
  3. The code you want highlighted

Now we need to find out what language we want to highlight in. So we simply use a regex to find the language that we want:

if (preg_match('@(lang|language)="([^\"]*)"@', $matches[1], $lang_match))
{
   $lang = $lang_match[2];
}
else
{
    $lang = "php";
}

The regex will find the language that the user defined in the "code" tag. If it doesn't find the language attribute, then we default to "php". If you want to default to something else then simply replace this with appropriate ID.

Now, the next bit of code simply checks whether the user wanted line numbering

// default is false;
$_line_numbering = false;
if (preg_match('@linenum="([^\"]*)"@', $matches[1], $line_number_match))
{
    switch ($line_number_match[1])
    {
        case 'false' :
            {
                $_line_numbering = false;
                break;
            }
        case 'true' :
            {
                $_line_numbering = true;
                break;
            }
        default:
            {
                $_line_numbering = false;
            }
    }
}

Now we know the language of the code, and whether we want line numbers, we can go ahead and call GeSHi to do the syntax highlighting.

$geshi = new GeSHi($text, $lang);
 
if ($_line_numbering)
{
    $geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);
}
 
//parse the code
$text = $geshi->parse_code();

Now we have the highlighted code stored in $text. The string will be encoded in html, so we must tell Textile not to do anything else to this bit of code. To do this, we use the "==" markup in Textile. Anything enclosed with "==" will not be processed by Textile.

if ($multiline)
{
    $text = "\r\n==\r\n". '<div class="code">' . $text . '</div>' . "\r\n==\r\n";
}
 
return $text;

This bit of code encloses the $text with "==" so that Textile will not mangle the HTML formatted code.

Conclusion

You should now be able to integrate Textile and GeSHi together. There is a lot of improvements that could be added to the code, for example more attributes for the "code" tag could be included.