[code]<?php
// deze gebruiken we om dingen als !, (, ), ;, etc. aan te geven
if(!defined('T_SYMBOL')) {
	/**
	 * @ignore
	 */
	define('T_SYMBOL', 0);
}

error_reporting(E_ALL);

/**
 * Standaardexceptie voor Tokenizer
 *
 * Het is zo dus makkelijker om excepties van deze module af te vangen
 *
 * @package Tokenizer
 */
class Tokenizer_Exception extends Exception { }

/**
 * De tokenizer klasse
 *
 * Gebruik:
 * <pre>$tokenizer = new Tokenizer('<?php echo $variabele; ?>', Tokenizer::INPUT_STRING);
 * // of
 * $tokenizer = new Tokenizer('/pad/naar/bestand.php', Tokenizer::INPUT_FILE);
 *
 * while($token = $tokenizer->getToken()) {
 *     echo $token->getName . ' - "' . $token->getContents() . '"<br />';
 * }</pre>
 *
 * @package Tokenizer
 */
class Tokenizer {
	/**
	 * Input is een bestandsnaam
	 */
	const INPUT_FILE   = 1;
	/**
	 * Input is een string met PHP-code
	 */
	const INPUT_STRING = 2;

	/**
	 * Bevat een array van {@link Token}s
	 *
	 * @var array
	 */
	private $_tokens   = array();
	/**
	 * De PHP-code, ofwel direct ingegeven, ofwel uit een bestand gelezen
	 *
	 * @var string
	 */
	private $_input    = '';

	/**
	 * Het aantal tokens dat $this->_tokens bevat
	 *
	 * @var integer
	 */
	private $_count    = 0;
	/**
	 * De pointer naar de huidige index in $this->_tokens
	 *
	 * @var integer
	 */
	private $_pointer  = 0;
	/**
	 * De klasse die ieder token representeert
	 *
	 * @var string
	 */
	private $_tokenClass = 'Token';

	/**
	 * Initialiseer de tokenizer.
	 *
	 * Als het een bestand is, lees het bestand in.
	 * Daarna wordt de string geparsed met token_get_all(),
	 * en is het klaar voor gebruik
	 *
	 * @param string $input Bestandsnaam of inputstring
	 * @param integer $type {@link Tokenizer::INPUT_FILE} of {@link Tokenizer::INPUT_STRING}
	 *
	 * @throws Tokenizer_Exception
	 */
	public function __construct($input, $type = null, $tokenClass = null) {
		$this->_input = $input;
		if($tokenClass === null) {
			$tokenClass = 'Token';
		}

		// check met behulp van reflection of gegeven tokenklasse wel
		// een instantie is die afgeleid is van Token_Abstract
		$reflection = new ReflectionClass($tokenClass);
		if(!$reflection->isSubclassOf(new ReflectionClass('Token_Abstract'))) {
			throw new Tokenizer_Exception('Tokenklasse is ongeldig');
		}
		unset($reflection);

		$this->_tokenClass = $tokenClass;

		if($type === null) {
			$type = self::INPUT_STRING;
		}
		switch($type) {
			case self::INPUT_FILE:
				// als het een bestand is, lees het in
				$this->_readFile();
				break;
			case self::INPUT_STRING:
				break;
			default:
				throw new Tokenizer_Exception('Onbekend type gegeven');
		}

		$this->_parseTokens();
	}
	
	/**
	 * Gemaksmethode om een bestand te tokenizen
	 *
	 * @param string $fileName
	 * @param string $tokenClass Eventuele tokenklasse
	 * @return Tokenizer
	 */
	static public function fromFile($fileName, $tokenClass = null) {
		return new Tokenizer($fileName, self::INPUT_FILE, $tokenClass);
	}
	
	/**
	 * Gemaksmethode om een string te tokenizen
	 *
	 * @param string $input
	 * @param string $tokenClass Eventuele tokenklasse
	 * @return Tokenizer
	 */
	static public function fromString($input, $tokenClass = null) {
		return new Tokenizer($fileName, self::INPUT_STRING, $tokenClass);
	}

	/**
	 * Lees het bestand in $this->_input in
	 *
	 * @throws Tokenizer_Exception
	 */
	private function _readFile() {
		// check of het bestand te lezen is
		if(!is_readable($this->_input)) {
			throw new Tokenizer_Exception('Gegeven bestand is niet leesbaar');
		}

		$this->_input = file_get_contents($this->_input);
	}

	/**
	 * Parse $this->_input, en plaats alles in $this->_tokens
	 */
	private function _parseTokens() {
		$tokens = token_get_all(str_replace(array("\r\n", "\r"), "\n", $this->_input));

		for($i = 0; isset($tokens[$i]); ++$i) {
			// voeg ieder token toe als Token-object.
			$this->_tokens[] = call_user_func(array($this->_tokenClass, 'fromArray'), $tokens[$i]);
		}

		$this->_count = count($this->_tokens);
	}

	/**
	 * Haal een token op
	 *
	 * Gebruik (gebruik liever {@link Tokenizer::getNextToken()}:
	 * <pre>$tokens = new Tokenizer_File('/pad/naar/bestand.php');
	 * $i = 0;
	 * while($token = $tokens->getToken($i++)) {
	 *     echo $token;
	 * }</pre>
	 *
	 * <b>Let op:</b> als je met {@link Tokenizer::getNextToken()} al een
	 * token hebt opgehaald, staat de pointer al op het volgende element,
	 * dus moet je voor naar achter een meer doen, en voor naar voren een
	 * minder!
	 *
	 * @param integer $step
	 * @return Token_Abstract|false
	 */
	public function getToken($step = 0) {
		$pointer = $this->_pointer;
		if($step <> 0) {
			$pointer += $step;
		}

		if($pointer < 0 || $pointer >= $this->_count) {
			return false;
		}

		return $this->_tokens[$pointer];
	}

	/**
	 * Haal het volgende token op
	 *
	 * Gebruik:
	 * <pre>$tokens = new Tokenizer_File('/pad/naar/bestand.php');
	 * while($token = $tokens->getNextToken()) {
	 *     echo $token;
	 * }</pre>
	 *
	 * @return Token_Abstract|false
	 */
	public function getNextToken() {
		if($this->_pointer >= $this->_count) {
			return false;
		}

		return $this->_tokens[$this->_pointer++];
	}

	/**
	 * Verschuif de pointer van de tokensarray
	 *
	 * @param integer $step
	 */
	public function step($step) {
		$this->_pointer += $step;
		$this->_pointer  = max(0, min($this->_count, $this->_pointer));
	}
}

/**
 * Gemaksklasse voor het tokenizen van bestanden
 *
 * @package Tokenizer
 */
class Tokenizer_File extends Tokenizer {
	/**
	 * Enkel bestandsnaam hier
	 *
	 * @param string $fileName De bestandsnaam
	 * @param string $tokenClass Eventuele tokenklasse
	 */
	public function __construct($fileName, $tokenClass = null) {
		parent::__construct($fileName, Tokenizer::INPUT_FILE, $tokenClass);
	}
}

/**
 * Gemaksklasse voor het tokenizer van strings
 *
 * @package Tokenizer
 */
class Tokenizer_String extends Tokenizer {
	/**
	 * Enkel een string hier
	 *
	 * @param string $input De inputstring met PHP code
	 * @param string $tokenClass Eventuele tokenklasse
	 */
	public function __construct($input, $tokenClass = null) {
		parent::__construct($input, Tokenizer::INPUT_STRING, $tokenClass);
	}
}

/**
 * Abstracte implementatie van een token
 *
 * Deze implementatie is al voorgebouwd op PHP-tokens,
 * je kunt dit natuurlijk aanpassen door het te extenden
 * naar je eigen ontwerp.
 *
 * @package Tokenizer
 */
abstract class Token_Abstract {
	/**
	 * Type token, afhankelijk van implementatie
	 *
	 * @var mixed
	 */
	private $_type = null;
	/**
	 * Inhoud van het token
	 *
	 * @var string
	 */
	private $_contents = '';

	/**
	 * Factoryimplementatie voor ieder token.
	 *
	 * @param mixed $type
	 * @param string $contents
	 */
	protected function __construct($type, $contents) {
		$this->_type = $type;
		$this->_contents = $contents;
	}

	/**
	 * Implementatie van __toString, zie {@link Token_Abstract::getHtml()}
	 *
	 * @return unknown
	 */
	public function __toString() {
		return $this->getHtml();
	}

	/**
	 * Haal de naam op van het type token
	 *
	 * Hier geimplementeerd met {@link token_name}
	 *
	 * @return string
	 */
	public function getName() {
		return $this->_type == T_SYMBOL ? 'T_SYMBOL' : token_name($this->_type);
	}

	/**
	 * Haal het type token op
	 *
	 * @return mixed
	 */
	public function getType() {
		return $this->_type;
	}

	/**
	 * Haal de inhoud van dit token op
	 *
	 * @return string
	 */
	public function getContents() {
		return $this->_contents;
	}

	/**
	 * Haal een HTML-representatie van dit token op.
	 *
	 * Zie {@link Token_Abstract::getContents()}
	 *
	 * @return string
	 */
	public function getHtml() {
		return str_replace("\t", '    ', htmlspecialchars($this->getContents()));
	}

	/**
	 * Kijk of dit token van het aangegeven type is
	 *
	 * @param mixed $type
	 * @return boolean
	 */
	public function is($type) {
		if(is_array($type)) {
			return in_array($this->_type, $type);
		} else {
			return $this->_type == $type;
		}
	}

	/**
	 * Te implementeren methode die zorgt voor een nieuw object op basis van een array
	 *
	 * @param array|string $array
	 * @return Token_Abstract
	 */
	abstract static public function fromArray($array);
}

/**
 * Representatie van een token.
 *
 * Alleen te gebruiken in combinatie met {@link Tokenizer}, niet op zichzelf
 *
 * @package Tokenizer
 */
class Token extends Token_Abstract {
	/**
	 * Implementatie van {@link Token_Abstract::fromArray()}
	 *
	 * @param array|string $array
	 * @return Token
	 */
	static public function fromArray($array) {
		if(!is_array($array)) {
			$array = array(T_SYMBOL, $array);
		} elseif(!isset($array[0], $array[1]) || !is_int($array[0])) {
			throw new Tokenizer_Exception('Ongeldig token gegeven');
		}

		return new self($array[0], $array[1]);
	}
}




//---------------------------------------------------------------------
/**
 * VOORBEELD:
 *
 * Syntax highlighting met de Tokenizer
 */
//---------------------------------------------------------------------




/**
 * Implementatie van een token die in de {@link Highlighter} wordt gebruikt
 *
 * @package Tokenizer
 * @subpackage Highlighter
 */
class Token_Highlight extends Token_Abstract {
	/**
	 * Lijst met types die de tokenizer van PHP herkent als "syntax"
	 *
	 * De waarde van iedere entry, betekent dat het type te linken is naar de manual
	 *
	 * @var array
	 */
	static protected $syntaxList = array(
		T_REQUIRE_ONCE => true, T_REQUIRE => true, T_EVAL => true, T_INCLUDE_ONCE => true, T_INCLUDE => true,
		T_LOGICAL_OR => false, T_LOGICAL_XOR => false, T_LOGICAL_AND => false, T_PRINT => true, T_SR_EQUAL => false,
		T_SL_EQUAL => false, T_XOR_EQUAL => false, T_OR_EQUAL => false, T_AND_EQUAL => false, T_MOD_EQUAL => false,
		T_CONCAT_EQUAL => false, T_DIV_EQUAL => false, T_MUL_EQUAL => false, T_MINUS_EQUAL => false,
		T_PLUS_EQUAL => false, T_BOOLEAN_OR => false, T_BOOLEAN_AND => false, T_IS_NOT_IDENTICAL => false,
		T_IS_IDENTICAL => false, T_IS_NOT_EQUAL => false, T_IS_EQUAL => false, T_IS_GREATER_OR_EQUAL => false,
		T_IS_SMALLER_OR_EQUAL => false, T_SR => false, T_SL => false, T_INSTANCEOF => true, T_UNSET_CAST => false,
		T_BOOL_CAST => false, T_OBJECT_CAST => false, T_ARRAY_CAST => false, T_STRING_CAST => false,
		T_DOUBLE_CAST => false, T_INT_CAST => false, T_DEC => false, T_INC => false, T_CLONE => true, T_NEW => true,
		T_EXIT => true, T_IF => true, T_ELSEIF => true, T_ELSE => true, T_ENDIF => true, T_ECHO => true,
		T_DO => true, T_WHILE => true, T_ENDWHILE => false, T_FOR => true, T_ENDFOR => false, T_FOREACH => true,
		T_ENDFOREACH => false, T_DECLARE => true, T_ENDDECLARE => false, T_AS => false, T_SWITCH => true,
		T_ENDSWITCH => false, T_CASE => true, T_DEFAULT => false, T_BREAK => true, T_CONTINUE => true,
		T_FUNCTION => true, T_CONST => false, T_RETURN => true, T_TRY => true, T_CATCH => true, T_THROW => true,
		T_USE => true, T_GLOBAL => true, T_PUBLIC => true, T_PROTECTED => true, T_PRIVATE => true, T_FINAL => false,
		T_ABSTRACT => true, T_STATIC => true, T_VAR => false, T_UNSET => true, T_ISSET => true, T_EMPTY => true,
		T_HALT_COMPILER => true, T_CLASS => true, T_INTERFACE => true, T_EXTENDS => true, T_IMPLEMENTS => true,
		T_OBJECT_OPERATOR => false, T_DOUBLE_ARROW => false, T_LIST => true, T_ARRAY => true, T_CLASS_C => false,
		T_METHOD_C => false, T_FUNC_C => false, T_START_HEREDOC => false, T_END_HEREDOC => false,
		T_PAAMAYIM_NEKUDOTAYIM => false
	);

	/**
	 * Is dit token een keyword?
	 *
	 * @var boolean
	 */
	private $_isSyntax = false;
	/**
	 * Heeft dit token een entry in de PHP manual?
	 *
	 * @var boolean
	 */
	private $_isLinkable = false;

	/**
	 * Setup
	 *
	 * @param mixed $type
	 * @param string $contents
	 */
	protected function __construct($type, $contents) {
		parent::__construct($type, $contents);

		// is het token een keyword?
		if(isset(self::$syntaxList[$type])) {
			$this->_isSyntax = true;
			$this->_isLinkable = self::$syntaxList[$type];
		}
	}

	/**
	 * Implementatie van {@link Token_Abstract::fromArray()}
	 *
	 * @param array|string $array
	 * @return Token_Highlight
	 */
	static public function fromArray($array) {
		if(!is_array($array)) {
			$array = array(T_SYMBOL, $array);
		} elseif(!isset($array[0], $array[1]) || !is_int($array[0])) {
			throw new Tokenizer_Exception('Ongeldig token gegeven');
		}

		return new self($array[0], $array[1]);
	}

	/**
	 * Kijk of het een keyword is
	 *
	 * @return boolean
	 */
	public function isSyntax() {
		return $this->_isSyntax;
	}

	/**
	 * Kijk of het linkable is
	 *
	 * @return boolean
	 */
	public function isLinkable() {
		return $this->_isLinkable;
	}

	/**
	 * Kijk of het een string-quote is
	 *
	 * In PHP wordt bij string met dubbele quotes waarin
	 * variabelen staan, de quotes apart gegeven.
	 *
	 * @return boolean
	 */
	public function isStringToken() {
		return $this->getContents() == '"';
	}
}


/**
 * Voorbeeldimplementatie van de tokenizer, in combinatie
 * met een eigen {@link Token}-implementatie
 *
 * @package Tokenizer
 * @subpackage Highlighter
 */
class Highlighter {
	/**
	 * De tokenizer
	 *
	 * @var Tokenizer
	 */
	private $_tokenizer = null;
	/**
	 * Kleuren voor syntax highlighting
	 *
	 * @var array
	 */
	private $_typeToClass = array();

	/**
	 * Stel wat variabelen leeg in, en klaar.
	 */
	public function __construct() {
		$this->_tokenizer = null;
		$this->_typeToClass = array();
	}

	/**
	 * Highlight een bestand
	 *
	 * @param string $fileName Bestandsnaam die gehighlight dient te worden
	 * @return string Uiteindelijke output
	 */
	public function highlightFile($fileName) {
		// gebruik de gemaksklasse Tokenizer_File voor bestands-tokenizing
		$this->_tokenizer = new Tokenizer_File($fileName, 'Token_Highlight');
		$output = $this->_highlight();
		$this->_tokenizer = null;
		return $output;
	}

	/**
	 * Highlight een string
	 *
	 * @param string $string PHP-code die gehighlight dient te worden
	 * @return string
	 */
	public function highlightString($string) {
		// gebruik de gemaksklasse Tokenizer_String voor string-tokenizing
		$this->_tokenizer = new Tokenizer_String($string, 'Token_Highlight');
		$output = $this->_highlight();
		$this->_tokenizer = null;
		return $output;
	}

	/**
	 * Stel een classnaam in per type token
	 *
	 * @param mixed $type 'default', 'keyword', een type of een array van dezen
	 * @param string $class
	 */
	public function setClass($type, $class = null) {
		if($class === null && is_array($type)) {
			// kan worden aangeroepen met een array
			foreach($type as $key => $value) {
				$this->setClass($key, $value);
			}
		} else {
			$this->_typeToClass[$type] = $class;
		}

		return $this;
	}

	/**
	 * Haal de classnaam op voor dit token
	 *
	 * @param mixed $token {@link Token_Abstract} of een generiek type
	 * @return string|false
	 */
	public function getClass($token) {
		if(!$token instanceof Token_Abstract) {
			// in dit geval is het een string, een type dus
			if(isset($this->_typeToClass[$token])) {
				return $this->_typeToClass[$token];
			} elseif(isset($this->_typeToClass['default'])) {
				return $this->_typeToClass['default'];
			}
		} else {
			// in dit geval is het een token
			$class = $token->getType();
			if(isset($this->_typeToClass[$class])) {
				return $this->_typeToClass[$class];
			} elseif(isset($this->_typeToClass['keyword']) && $token->isSyntax()) {
				return $this->_typeToClass['keyword'];
			} elseif(isset($this->_typeToClass['default'])) {
				return $this->_typeToClass['default'];
			}
		}
		return false;
	}

	/**
	 * Opschoning van de output.
	 *
	 * Haalt momenteel dubbele spans achter elkaar weg
	 *
	 * @param string $output
	 * @return string
	 */
	protected function _cleanupOutput($output) {
		do {
			// vervang 2 aansluitende span's met dezelfde class
			// door 1 span die beide omsluit
			$newOutput = preg_replace(
				'~(<span(?:[^<>"\']|"[^"]*"|\'[^\']*\')*>)((?:(?!</span>).)+)</span>\1~is',
				'$1$2',
				$output
			);
		  // zolang de output verandert
		} while($output <> $newOutput && $output = $newOutput);

		return $newOutput;
	}

	/**
	 * De almachtige highlighter
	 *
	 * Deze zorgt voor de correcte highlighting, en voor function linking
	 *
	 * @return string
	 */
	protected function _highlight() {
		$output = '';
		// zitten we in een ""-string?
		$stringFlag = false;
		// of is die net gesloten?
		$lastStringFlag = false;
		while($token = $this->_tokenizer->getNextToken()) {
			// E_NOTICE voorkomen, hier definieren
			$functionFlag = false;
			if($token->isStringToken()) {
				$stringFlag = !$stringFlag;
				if(!$stringFlag) {
					$lastStringFlag = true;
				}
			}
			// als we niet in een string zitten, normale volgorde
			if(!$stringFlag && !$lastStringFlag) {
				// probeer de class op te halen voor dit token
				if($class = $this->getClass($token)) {
					$class = ' class="' . $class . '"';
				}
				// check of het een functie _kan_ zijn
				if($token->is(T_STRING)) {
					// het 3e token dat geweest is (we zijn al een stap vooruit!)
					$prevToken = $this->_tokenizer->getToken(-3);
					// kijk of het token bestond, en of het niet een functiedeclaratie aangeeft
					if(!$prevToken instanceof Token_Abstract || !$prevToken->is(T_FUNCTION)) {
						// check het volgende token, en het token daarna op een (
						for($i = 0; $i <= 1; ++$i) {
							$nextToken = $this->_tokenizer->getToken($i);
							if($nextToken instanceof Token_Abstract && $nextToken->getContents() == '(') {
								$functionFlag = $this->getManualLink($token->getContents());
								break;
							}
						}
						unset($nextToken);
					}
					unset($prevToken);
				// kijken of het misschien syntax is, en of het te linken is
				} elseif($token->isSyntax() && $token->isLinkable()) {
					$functionFlag = $this->getManualLink($token->getContents(), false);
				}
			} else {
				$lastStringFlag = false;
				// probeer de classnaam op te halen voor dubbel-quotes-strings
				if($class = $this->getClass(T_ENCAPSED_AND_WHITESPACE)) {
					$class = ' class="' . $class . '"';
				}
			}
			// append aan de output
			// is het een functie, voeg dan de a-tag in
			if($functionFlag !== false) {
				$output .= '<a href="' . htmlspecialchars($functionFlag) . '"' . $class . '>' . $token->getHtml() . '</a>';
			} else {
				$output .= '<span' . $class . '>' . $token->getHtml() . '</span>';
			}
		}
		// retourneer de code met code-tags
		return '<pre>' . $this->_cleanupOutput($output) . '</pre>';
	}

	/**
	 * Als de opgegeven functie of keyword bestaat, geef een URL naar de manual
	 *
	 * @param string $functionName
	 * @return string|false
	 */
	public function getManualLink($functionName, $isFunction = true) {
		// OF het is geen functie, OF de functie moet bestaan
		if(!$isFunction XOR function_exists($functionName)) {
			return 'http://www.php.net/' . str_replace('_', '-', strtolower($functionName));
		}
		return false;
	}
}

header('Content-Type: text/html;charset=iso-8859-1');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl">
    <head>
        <title>Voorbeeld van OO-implementatie van de PHP-tokenizer</title>
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1;" />
		<style type="text/css">
			.highlight_blue {
				color: #00b;
			}
			.highlight_html {
				color: #000;
			}
			.highlight_orange {
				color: #ff8000;
			}
			.highlight_green {
				color: #070;
			}
			.highlight_red {
				color: #d00;
			}
		</style>
	</head>
	<body>
<?php
// parsetijd bijhouden
$time = microtime(true);
$highlighter = new Highlighter;
// stel de default classes in die worden gebruikt
$highlighter->setClass(array(
	// standaardkleur
	'default'                  => 'highlight_blue',
	// keywords (syntax)
	'keyword'                  => 'highlight_green',
	// symbolen ('(', ')', '!', ';', etc.)
	T_SYMBOL                   => 'highlight_green',
	// strings
	T_ENCAPSED_AND_WHITESPACE  => 'highlight_red',
	T_CONSTANT_ENCAPSED_STRING => 'highlight_red',
	// html
	T_INLINE_HTML              => 'highlight_black',
	// commentaar
	T_COMMENT                  => 'highlight_orange',
	T_DOC_COMMENT              => 'highlight_orange'
));
// hopla, daar krijgen we al geldige content
echo $highlighter->highlightFile(__FILE__);
$time = microtime(true) - $time;
?>

		<pre><b>Parsetijd:</b> <?php echo number_format($time, 5, '.', '')?></pre>
	</body>
</html>[/code]