<?php
/**
* @brief class for source-code highlighting
* It uses the PEAR package <http://pear.php.net/package/Text_Highlighter> Text_Highligher.
* It generates valid XHTML-strict code. Currently it can highlight the following languages:
* C++, CSS, DIFF, DTD, HTML, Java, JavaScript, MySQL, Perl, PHP, Python, Ruby, SQL, XML.
* You can highligh files, strings or content. To highlight several code inside a content
* set a tag \<pre lang="?code language?"\>YOUR CODE\</pre\> around your code. ?code language?
* stands for your code-language in which the text should be highlighted.
* ----------------------------------------------------------------------------
* Contributor page <http://www.optima-software.de>
* Released under the GNU General Public License V2
* ----------------------------------------------------------------------------
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/03
* @version 0.9.0t
**/
define('HL_PEAR_PKG_NOT_FOUND', 'Can not highlight text. Install PEAR package <em>Text_Highlighter</em>!');
define('HL_FILE_NOT_FOUND', 'File <em>%s</em> not found!');
define('HL_FILE_CANNOT_READ', 'Can not read file <em>%s</em>');
define('HL_FILE_TO_LARGE', 'A filesize of <strong>%s</strong> KB is not allowed! Max filesize: %s');
define('HL_STRING_TO_LONG', 'Your code string is too long: Max string length: %s');
define('HL_COPY_INFO', '<!-- Sourcecode highlight for webpages by Stephan Spies <http://optima-software.de> -->');
define('HL_COPY_INFO_END', '<!-- END OF sourcecode highlight -->');
class highlighter {
/** @note public members **/
public $default_language = 'php';
public $tabsize = 2; // at the moment, this does nothing to PEAR
public $max_filesize = 1000; // [KB], set 0 for no limit
public $max_strlength = 10000; // set 0 for no limit
/** @note private members **/
private $_errors = array();
private $_hl;
private $_code = '';
private $_as_table;
private $_numbers;
/**
* @brief class constructor, check for PEAR installation
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function __construct (){
if (@include_once('Text/Highlighter.php')){
$this->_hl = new Text_Highlighter;
}
if ( !is_object($this->_hl) ) {
$this->addError(HL_PEAR_PKG_NOT_FOUND);
}
}//END function __construct
/**
* @brief highlight a given string
* @param $text string to highlight
* @param $language [optional, default=''] programming language for highlighting, if empty we use $this->default_language
* @param $as_table [optional, default=false] output a table, not <\ul\> or \<ol\>, not recomended
* @param $numbers [optional, default=true] if numbering is true we use a \<ol\> else a \<ul\>
* @retval string highlighted string, better us output() for getting the results
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function highlight_string ($text, $language='', $as_table = false, $numbers = true){
if ( is_object($this->_hl) ) {
empty($language) ? $lng = $this->default_language : $lng = $language;
$options = array(
'numbers' => $as_table == true ? HL_NUMBERS_TABLE : ( $numbers == true ? HL_NUMBERS_LI : 0 ),
'tabsize' => $this->tabsize /** @note at the moment, this does nothing to PEAR **/
);
$obj = $this->_hl->factory($lng, $options);
if ( isset($obj->message) ) {
/** @note PEAR ERROR **/
$this->addError($obj->message);
return false;
}
if ( $this->max_strlength != 0 ){
if ( (strlen($this->_code) + strlen($text)) > $this->max_strlength){
$this->addError(sprintf(HL_STRING_TO_LONG, $this->max_strlength));
return false;
}
}
$text = stripslashes($text);
$result = '';
$result .= '<div class="' . $lng . '">' . "\r\n";
$result .= $obj->highlight($text) . "\r\n";
$result .= '</div>' . "\r\n";
$this->_code .= $result;
return $result;
}//if ( is_object($this->_hl) )
}//public function highlight_text
/**
* @brief highlight a given file
* @param $file file to highlight
* @param $language [optional, default=''] programming language for highlighting, if empty we use $this->default_language
* @param $as_table [optional, default=false] output a table, not <\ul\> or \<ol\>, not recomended
* @param $numbers [optional, default=true] if numbering is true we use a \<ol\> else a \<ul\>
* @retval string highlighted string, better us output() for getting the results
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function highlight_file ($file, $language='', $as_table = false, $numbers = true){
if ( $code = $this->open_file($file) ){
return $this->highlight_string($code, $language, $as_table, $numbers);
} else {
return false;
}
}// END public function highlight_file
/**
* @brief open file and give back code
* @param $file filename to open
* @retval string text inside file
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
private function open_file ($file){
if (!file_exists($file) ) {
$this->addError(sprintf(HL_FILE_NOT_FOUND, $file));
return false;
}
$f = fopen($file, 'r+');
if (!$f){
$this->addError(sprintf(HL_FILE_CANNOT_READ, $file));
return false;
}
if ( $this->max_filesize != 0 ){
$sizeKB = filesize($file) / 1024;
if ($sizeKB > $this->max_filesize){
$this->addError(sprintf(HL_FILE_TO_LARGE, round($sizeKB, 2), $this->max_filesize));
return false;
}
}//if ( $this->max_filesize != 0 )
return fread( $f, filesize($file) );
}// END private function open_file
/**
* @brief highlight a content, this function highlights all code-snippets between <pre lang="language"\> Tags. "language" means the programming language for the current snippet.
* @param $text string to highlight
* @param $as_table [optional, default=false] output a table, not <\ul\> or \<ol\>, not recomended
* @param $numbers [optional, default=true] if numbering is true we use a \<ol\> else a \<ul\>
* @retval string content with highlighted code-snippets, better us output() for getting the results
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function highlight_content ($content, $as_table = false, $numbers = true){
$this->_as_table = $as_table;
$this->_numbers = $numbers;
$matches = array();
$match = "/<pre([^>]*)>(.*?)<\/pre>/is";
$this->_code = preg_replace_callback($match,
array('self', 'highlight_string_callback'),
$content
);
return $this->_code;
}//END public function highlight_content
/**
* @brief highlight a content stored in a file, this function highlights all code-snippets between <pre lang="language"\> Tags. "language" means the programming language for the current snippet.
* @param $file file to highligh
* @param $as_table [optional, default=false] output a table, not <\ul\> or \<ol\>, not recomended
* @param $numbers [optional, default=true] if numbering is true we use a \<ol\> else a \<ul\>
* @retval string content with highlighted code-snippets, better us output() for getting the results
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function highlight_content_file ($file, $as_table = false, $numbers = true){
if ( $content = $this->open_file($file) ){
return $this->highlight_content($content, $as_table, $numbers);
} else {
return false;
}
}//END public function highlight_content
/**
* @brief callback function for preg_replace_callback
* @param $match
* @retval string highlighted string
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
private function highlight_string_callback ($match){
$attr = stripslashes($match[1]);
$code = stripslashes($match[2]);
$re_lang = '/\s+lang\s*=\s*["\']?([^"\']+)["\']?/xi';
$num = preg_match($re_lang, $attr, $lang);
if ($num) {
$code = $this->htmlunspecialchars($code);
$res = $this->highlight_string($code, $lang[1], $this->_as_table, $this->_numbers);
return $res;
}
}
/**
* @brief add error to error stack
* @param $msg
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
private function addError($msg) {
if (!empty($msg)){
$this->_errors[] = array(
'uri' => $_SERVER['REQUEST_URI'],
'message' => $msg
);
}//if if (!empty($msg))
}//END private function addError
/**
* @brief output the results, use this function to get back highlighted string or content
* @retval string highlighted string
* @author Stephan Spies <info(at)optima-software(dot)de>
* @date 2006/08/01
**/
public function output(){
$_res = HL_COPY_INFO . "\r\n";
if ( sizeof($this->_errors) > 0 ){
foreach( $this->_errors as $error ) {
$_res .= 'CLASS ERROR: ' . $error['message'] . ' REQUEST: ' . $error['uri'] . '<br />';
}//END foreach( $this->_errors as $error
} else {
$_res .= $this->_code;
$this->_code = '';
$_res = eregi_replace('<li> <span class="[A-Za-z0-9\-]+"></span></li>', '', $_res);
$_res = eregi_replace('<li> <span class="[A-Za-z0-9\-]+"> </span></li>', '', $_res);
$_res = eregi_replace('<span class="[A-Za-z0-9\-]+"> </span>', ' ', $_res);
$_res = eregi_replace('<span class="[A-Za-z0-9\-]+"></span>', '', $_res);
}
$_res .= "\r\n" . HL_COPY_INFO_END . "\r\n";
return $_res;
}//END private function output
/**
* @brief we will reverse the htmlspecialchars, to convert XML entities back to the right character.
* @param $code original code
* @retval string
* @author Jan Tank <http://zeek.luli.de/>
* @date unknown
**/
private function htmlunspecialchars($code) {
$func = create_function('$match',
'$value = intval($match[1]);'.
'return ($value < 256) ? chr($value) : $match[1];');
$tran = get_html_translation_table(HTML_ENTITIES);
$tran = array_flip($tran);
$code = strtr($code, $tran);
$code = preg_replace_callback("/&#([0-9]{1,5});/is", $func, $code);
return $code;
}//END private function htmlunspecialchars
}//END class highlighter
?>