/inc/hyperlight/hyperlight.php
PHP | 1033 lines | 622 code | 133 blank | 278 comment | 89 complexity | 9b34829f1fda8b42d08dcbe54bd1ecac MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
- <?php
- /*
- * Copyright 2008 Konrad Rudolph
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- /*
- * TODO list
- * =========
- *
- * - FIXME Nested syntax elements create redundant nested tags under certain
- * circumstances. This can be reproduced by the following PHP snippet:
- *
- * <pre class="<?php echo; ? >">
- *
- * (Remove space between `?` and `>`).
- * Although this no longer occurs, it is fixed by checking for `$token === ''`
- * in the `emit*` methods. This should never happen anyway. Probably something
- * to do with the zero-width lookahead in the PHP syntax definition.
- *
- * - `hyperlight_calculate_fold_marks`: refactor, write proper handler
- *
- * - Line numbers (on client-side?)
- *
- */
- /**
- * Hyperlight source code highlighter for PHP.
- * @package hyperlight
- */
- /** @ignore */
- require_once dirname(__FILE__) . '/preg_helper.php';
- if (!function_exists('array_peek')) {
- /**
- * @internal
- * This does exactly what you think it does. */
- function array_peek(array &$array) {
- $cnt = count($array);
- return $cnt === 0 ? null : $array[$cnt - 1];
- }
- }
- /**
- * @internal
- * For internal debugging purposes.
- */
- function dump($obj, $descr = null) {
- if ($descr !== null)
- echo "<h3>$descr</h3>";
- ob_start();
- var_dump($obj);
- $dump = ob_get_clean();
- ?><pre><?php echo htmlspecialchars($dump); ?></pre><?php
- return true;
- }
- /**
- * Raised when the grammar offers a rule that has not been defined.
- */
- class NoMatchingRuleException extends Exception {
- /** @internal */
- public function __construct($states, $position, $code) {
- $state = array_pop($states);
- parent::__construct(
- "State '$state' has no matching rule at position $position:\n" .
- $this->errorSurrounding($code, $position)
- );
- }
- // Try to extract the location of the error more or less precisely.
- // Only used for a comprehensive display.
- private function errorSurrounding($code, $pos) {
- $size = 10;
- $begin = $pos < $size ? 0 : $pos - $size;
- $end = $pos + $size > strlen($code) ? strlen($code) : $pos + $size;
- $offs = $pos - $begin;
- return substr($code, $begin, $end - $begin) . "\n" . sprintf("%{$offs}s", '^');
- }
- }
- /**
- * Represents a nesting rule in the grammar of a language definition.
- *
- * Individual rules can either be represented by raw strings ("simple" rules) or
- * by a nesting rule. Nesting rules specify where they can start and end. Inside
- * a nesting rule, other rules may be applied (both simple and nesting).
- * For example, a nesting rule may define a string literal. Inside that string,
- * other rules may be applied that recognize escape sequences.
- *
- * To use a nesting rule, supply how it may start and end, e.g.:
- * <code>
- * $string_rule = array('string' => new Rule('/"/', '/"/'));
- * </code>
- * You also need to specify nested states:
- * <code>
- * $string_states = array('string' => 'escaped');
- * <code>
- * Now you can add another rule for <var>escaped</var>:
- * <code>
- * $escaped_rule = array('escaped' => '/\\(x\d{1,4}|.)/');
- * </code>
- */
- class Rule {
- /**
- * Common rules.
- */
- const ALL_WHITESPACE = '/(\s|\r|\n)+/';
- const C_IDENTIFIER = '/[a-z_][a-z0-9_]*/i';
- const C_COMMENT = '#//.*?\n|/\*.*?\*/#s';
- const C_MULTILINECOMMENT = '#/\*.*?\*/#s';
- const DOUBLEQUOTESTRING = '/"(?:\\\\"|.)*?"/s';
- const SINGLEQUOTESTRING = "/'(?:\\\\'|.)*?'/s";
- const C_DOUBLEQUOTESTRING = '/L?"(?:\\\\"|.)*?"/s';
- const C_SINGLEQUOTESTRING = "/L?'(?:\\\\'|.)*?'/s";
- const STRING = '/"(?:\\\\"|.)*?"|\'(?:\\\\\'|.)*?\'/s';
- const C_NUMBER = '/
- (?: # Integer followed by optional fractional part.
- (?:
- 0(?:
- x[0-9a-f]+
- |
- [0-7]*
- )
- |
- \d+
- )
- (?:\.\d*)?
- (?:e[+-]\d+)?
- )
- |
- (?: # Just the fractional part.
- (?:\.\d+)
- (?:e[+-]?\d+)?
- )
- /ix';
- private $_start;
- private $_end;
- /** @ignore */
- public function __construct($start, $end = null) {
- $this->_start = $start;
- $this->_end = $end;
- }
- /**
- * Returns the pattern with which this rule starts.
- * @return string
- */
- public function start() {
- return $this->_start;
- }
- /**
- * Returns the pattern with which this rule may end.
- * @return string
- */
- public function end() {
- return $this->_end;
- }
- }
- /**
- * Abstract base class of all Hyperlight language definitions.
- *
- * In order to define a new language definition, this class is inherited.
- * The only function that needs to be overridden is the constructor. Helper
- * functions from the base class can then be called to construct the grammar
- * and store additional information.
- * The name of the subclass must be of the schema <var>{Lang}Language</var>,
- * where <var>{Lang}</var> is a short, unique name for the language starting
- * with a capital letter and continuing in lower case. For example,
- * <var>PhpLanguage</var> is a valid name. The language definition must
- * reside in a file located at <var>languages/{lang}.php</var>. Here,
- * <var>{lang}</var> is the all-lowercase spelling of the name, e.g.
- * <var>languages/php.php</var>.
- *
- */
- abstract class HyperLanguage {
- private $_states = array();
- private $_rules = array();
- private $_mappings = array();
- private $_info = array();
- private $_extensions = array();
- private $_caseInsensitive = false;
- private $_postProcessors = array();
- private static $_languageCache = array();
- private static $_compiledLanguageCache = array();
- private static $_filetypes;
- /**
- * Indices for information.
- */
- const NAME = 1;
- const VERSION = 2;
- const AUTHOR = 10;
- const WEBSITE = 5;
- const EMAIL = 6;
- /**
- * Retrieves a language definition name based on a file extension.
- *
- * Uses the contents of the <var>languages/filetypes</var> file to
- * guess the language definition name from a file name extension.
- * This file has to be generated using the
- * <var>collect-filetypes.php</var> script every time the language
- * definitions have been changed.
- *
- * @param string $ext the file name extension.
- * @return string The language definition name or <var>NULL</var>.
- */
- public static function nameFromExt($ext) {
- if (self::$_filetypes === null) {
- $ft_content = file('languages/filetypes', 1);
- foreach ($ft_content as $line) {
- list ($name, $extensions) = explode(':', trim($line));
- $extensions = explode(',', $extensions);
- // Inverse lookup.
- foreach ($extensions as $extension)
- $ft_data[$extension] = $name;
- }
- self::$_filetypes = $ft_data;
- }
- $ext = strtolower($ext);
- return
- array_key_exists($ext, self::$_filetypes) ?
- self::$_filetypes[strtolower($ext)] : null;
- }
- public static function compile(HyperLanguage $lang) {
- $id = $lang->id();
- if (!isset(self::$_compiledLanguageCache[$id]))
- self::$_compiledLanguageCache[$id] = $lang->makeCompiledLanguage();
- return self::$_compiledLanguageCache[$id];
- }
- public static function compileFromName($lang) {
- return self::compile(self::fromName($lang));
- }
- protected static function exists($lang) {
- return isset(self::$_languageCache[$lang]) or
- file_exists("languages/$lang.php");
- }
- protected static function fromName($lang) {
- if (!isset(self::$_languageCache[$lang])) {
- require_once dirname(__FILE__) . "/$lang.php";
- $klass = ucfirst("{$lang}Language");
- self::$_languageCache[$lang] = new $klass();
- }
- return self::$_languageCache[$lang];
- }
- public function id() {
- $klass = get_class($this);
- return strtolower(substr($klass, 0, strlen($klass) - strlen('Language')));
- }
- protected function setCaseInsensitive($value) {
- $this->_caseInsensitive = $value;
- }
- protected function addStates(array $states) {
- $this->_states = self::mergeProperties($this->_states, $states);
- }
- protected function getState($key) {
- return $this->_states[$key];
- }
- protected function removeState($key) {
- unset($this->_states[$key]);
- }
- protected function addRules(array $rules) {
- $this->_rules = self::mergeProperties($this->_rules, $rules);
- }
- protected function getRule($key) {
- return $this->_rules[$key];
- }
- protected function removeRule($key) {
- unset($this->_rules[$key]);
- }
- protected function addMappings(array $mappings) {
- // TODO Implement nested mappings.
- $this->_mappings = array_merge($this->_mappings, $mappings);
- }
- protected function getMapping($key) {
- return $this->_mappings[$key];
- }
- protected function removeMapping($key) {
- unset($this->_mappings[$key]);
- }
- protected function setInfo(array $info) {
- $this->_info = $info;
- }
- protected function setExtensions(array $extensions) {
- $this->_extensions = $extensions;
- }
- protected function addPostprocessing($rule, HyperLanguage $language) {
- $this->_postProcessors[$rule] = $language;
- }
- // protected function addNestedLanguage(HyperLanguage $language, $hoistBackRules) {
- // $prefix = get_class($language);
- // if (!is_array($hoistBackRules))
- // $hoistBackRules = array($hoistBackRules);
- //
- // $states = array(); // Step 1: states
- //
- // foreach ($language->_states as $stateName => $state) {
- // $prefixedRules = array();
- //
- // if (strstr($stateName, ' ')) {
- // $parts = explode(' ', $stateName);
- // $prefixed = array();
- // foreach ($parts as $part)
- // $prefixed[] = "$prefix$part";
- // $stateName = implode(' ', $prefixed);
- // }
- // else
- // $stateName = "$prefix$stateName";
- //
- // foreach ($state as $key => $rule) {
- // if (is_string($key) and is_array($rule)) {
- // $nestedRules = array();
- // foreach ($rule as $nestedRule)
- // $nestedRules[] = ($nestedRule === '') ? '' :
- // "$prefix$nestedRule";
- //
- // $prefixedRules["$prefix$key"] = $nestedRules;
- // }
- // else
- // $prefixedRules[] = "$prefix$rule";
- // }
- //
- // if ($stateName === 'init')
- // $prefixedRules = array_merge($hoistBackRules, $prefixedRules);
- //
- // $states[$stateName] = $prefixedRules;
- // }
- //
- // $rules = array(); // Step 2: rules
- // // Mappings need to set up already!
- // $mappings = array();
- //
- // foreach ($language->_rules as $ruleName => $rule) {
- // if (is_array($rule)) {
- // $nestedRules = array();
- // foreach ($rule as $nestedName => $nestedRule) {
- // if (is_string($nestedName)) {
- // $nestedRules["$prefix$nestedName"] = $nestedRule;
- // $mappings["$prefix$nestedName"] = $nestedName;
- // }
- // else
- // $nestedRules[] = $nestedRule;
- // }
- // $rules["$prefix$ruleName"] = $nestedRules;
- // }
- // else {
- // $rules["$prefix$ruleName"] = $rule;
- // $mappings["$prefix$ruleName"] = $ruleName;
- // }
- // }
- //
- // // Step 3: mappings.
- //
- // foreach ($language->_mappings as $ruleName => $cssClass) {
- // if (strstr($ruleName, ' ')) {
- // $parts = explode(' ', $ruleName);
- // $prefixed = array();
- // foreach ($parts as $part)
- // $prefixed[] = "$prefix$part";
- // $mappings[implode(' ', $prefixed)] = $cssClass;
- // }
- // else
- // $mappings["$prefix$ruleName"] = $cssClass;
- // }
- //
- // $this->addStates($states);
- // $this->addRules($rules);
- // $this->addMappings($mappings);
- //
- // return $prefix . 'init';
- // }
- private function makeCompiledLanguage() {
- return new HyperlightCompiledLanguage(
- $this->id(),
- $this->_info,
- $this->_extensions,
- $this->_states,
- $this->_rules,
- $this->_mappings,
- $this->_caseInsensitive,
- $this->_postProcessors
- );
- }
- private static function mergeProperties(array $old, array $new) {
- foreach ($new as $key => $value) {
- if (is_string($key)) {
- if (isset($old[$key]) and is_array($old[$key]))
- $old[$key] = array_merge($old[$key], $new);
- else
- $old[$key] = $value;
- }
- else
- $old[] = $value;
- }
- return $old;
- }
- }
- class HyperlightCompiledLanguage {
- private $_id;
- private $_info;
- private $_extensions;
- private $_states;
- private $_rules;
- private $_mappings;
- private $_caseInsensitive;
- private $_postProcessors = array();
- public function __construct($id, $info, $extensions, $states, $rules, $mappings, $caseInsensitive, $postProcessors) {
- $this->_id = $id;
- $this->_info = $info;
- $this->_extensions = $extensions;
- $this->_caseInsensitive = $caseInsensitive;
- $this->_states = $this->compileStates($states);
- $this->_rules = $this->compileRules($rules);
- $this->_mappings = $mappings;
- foreach ($postProcessors as $ppkey => $ppvalue)
- $this->_postProcessors[$ppkey] = HyperLanguage::compile($ppvalue);
- }
- public function id() {
- return $this->_id;
- }
- public function name() {
- return $this->_info[HyperLanguage::NAME];
- }
- public function authorName() {
- if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info))
- return null;
- $author = $this->_info[HyperLanguage::AUTHOR];
- if (is_string($author))
- return $author;
- if (!array_key_exists(HyperLanguage::NAME, $author))
- return null;
- return $author[HyperLanguage::NAME];
- }
- public function authorWebsite() {
- if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info) or
- !is_array($this->_info[HyperLanguage::AUTHOR]) or
- !array_key_exists(HyperLanguage::WEBSITE, $this->_info[HyperLanguage::AUTHOR]))
- return null;
- return $this->_info[HyperLanguage::AUTHOR][HyperLanguage::WEBSITE];
- }
- public function authorEmail() {
- if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info) or
- !is_array($this->_info[HyperLanguage::AUTHOR]) or
- !array_key_exists(HyperLanguage::EMAIL, $this->_info[HyperLanguage::AUTHOR]))
- return null;
- return $this->_info[HyperLanguage::AUTHOR][HyperLanguage::EMAIL];
- }
- public function authorContact() {
- $email = $this->authorEmail();
- return $email !== null ? $email : $this->authorWebsite();
- }
- public function extensions() {
- return $this->_extensions;
- }
- public function state($stateName) {
- return $this->_states[$stateName];
- }
- public function rule($ruleName) {
- return $this->_rules[$ruleName];
- }
- public function className($state) {
- if (array_key_exists($state, $this->_mappings))
- return $this->_mappings[$state];
- else if (strstr($state, ' ') === false)
- // No mapping for state.
- return $state;
- else {
- // Try mapping parts of nested state name.
- $parts = explode(' ', $state);
- $ret = array();
- foreach ($parts as $part) {
- if (array_key_exists($part, $this->_mappings))
- $ret[] = $this->_mappings[$part];
- else
- $ret[] = $part;
- }
- return implode(' ', $ret);
- }
- }
- public function postProcessors() {
- return $this->_postProcessors;
- }
- private function compileStates($states) {
- $ret = array();
- foreach ($states as $name => $state) {
- $newstate = array();
- if (!is_array($state))
- $state = array($state);
- foreach ($state as $key => $elem) {
- if ($elem === null)
- continue;
- if (is_string($key)) {
- if (!is_array($elem))
- $elem = array($elem);
- foreach ($elem as $el2) {
- if ($el2 === '')
- $newstate[] = $key;
- else
- $newstate[] = "$key $el2";
- }
- }
- else
- $newstate[] = $elem;
- }
- $ret[$name] = $newstate;
- }
- return $ret;
- }
- private function compileRules($rules) {
- $tmp = array();
- // Preprocess keyword list and flatten nested lists:
- // End of regular expression matching keywords.
- $end = $this->_caseInsensitive ? ')\b/i' : ')\b/';
- foreach ($rules as $name => $rule) {
- if (is_array($rule)) {
- if (self::isAssocArray($rule)) {
- // Array is a nested list of rules.
- foreach ($rule as $key => $value) {
- if (is_array($value))
- // Array represents a list of keywords.
- $value = '/\b(?:' . implode('|', $value) . $end;
- if (!is_string($key) or strlen($key) === 0)
- $tmp[$name] = $value;
- else
- $tmp["$name $key"] = $value;
- }
- }
- else {
- // Array represents a list of keywords.
- $rule = '/\b(?:' . implode('|', $rule) . $end;
- $tmp[$name] = $rule;
- }
- }
- else {
- $tmp[$name] = $rule;
- } // if (is_array($rule))
- } // foreach
- $ret = array();
- foreach ($this->_states as $name => $state) {
- $regex_rules = array();
- $regex_names = array();
- $nesting_rules = array();
- foreach ($state as $rule_name) {
- $rule = $tmp[$rule_name];
- if ($rule instanceof Rule)
- $nesting_rules[$rule_name] = $rule;
- else {
- $regex_rules[] = $rule;
- $regex_names[] = $rule_name;
- }
- }
- $ret[$name] = array_merge(
- array(preg_merge('|', $regex_rules, $regex_names)),
- $nesting_rules
- );
- }
- return $ret;
- }
- private static function isAssocArray(array $array) {
- foreach($array as $key => $_)
- if (is_string($key))
- return true;
- return false;
- }
- }
- class Hyperlight {
- private $_lang;
- private $_result;
- private $_states;
- private $_omitSpans;
- private $_postProcessors = array();
- public function __construct($lang) {
- if (is_string($lang))
- $this->_lang = HyperLanguage::compileFromName(strtolower($lang));
- else if ($lang instanceof HyperlightCompiledLanguage)
- $this->_lang = $lang;
- else if ($lang instanceof HyperLanguage)
- $this->_lang = HyperLanguage::compile($lang);
- else
- trigger_error(
- 'Invalid argument type for $lang to Hyperlight::__construct',
- E_USER_ERROR
- );
- foreach ($this->_lang->postProcessors() as $ppkey => $ppvalue)
- $this->_postProcessors[$ppkey] = new Hyperlight($ppvalue);
- $this->reset();
- }
- public function language() {
- return $this->_lang;
- }
- public function reset() {
- $this->_states = array('init');
- $this->_omitSpans = array();
- }
- public function render($code) {
- // Normalize line breaks.
- $this->_code = preg_replace('/\r\n?/', "\n", $code);
- $fm = hyperlight_calculate_fold_marks($this->_code, $this->language()->id());
- return hyperlight_apply_fold_marks($this->renderCode(), $fm);
- }
- public function renderAndPrint($code) {
- echo $this->render($code);
- }
- private function renderCode() {
- $code = $this->_code;
- $pos = 0;
- $len = strlen($code);
- $this->_result = '';
- $state = array_peek($this->_states);
- // If there are open states (reentrant parsing), open the corresponding
- // tags first:
- for ($i = 1; $i < count($this->_states); ++$i)
- if (!$this->_omitSpans[$i - 1]) {
- $class = $this->_lang->className($this->_states[$i]);
- $this->write("<span class=\"$class\">");
- }
- // Emergency break to catch faulty rules.
- $prev_pos = -1;
- while ($pos < $len) {
- // The token next to the current position, after the inner loop completes.
- // i.e. $closest_hit = array($matched_text, $position)
- $closest_hit = array('', $len);
- // The rule that found this token.
- $closest_rule = null;
- $rules = $this->_lang->rule($state);
- foreach ($rules as $name => $rule) {
- if ($rule instanceof Rule)
- $this->matchIfCloser(
- $rule->start(), $name, $pos, $closest_hit, $closest_rule
- );
- else if (preg_match($rule, $code, $matches, PREG_OFFSET_CAPTURE, $pos) == 1) {
- // Search which of the sub-patterns matched.
- foreach ($matches as $group => $match) {
- if (!is_string($group))
- continue;
- if ($match[1] !== -1) {
- $closest_hit = $match;
- $closest_rule = str_replace('_', ' ', $group);
- break;
- }
- }
- }
- } // foreach ($rules)
- // If we're currently inside a rule, check whether we've come to the
- // end of it, or the end of any other rule we're nested in.
- if (count($this->_states) > 1) {
- $n = count($this->_states) - 1;
- do {
- $rule = $this->_lang->rule($this->_states[$n - 1]);
- $rule = $rule[$this->_states[$n]];
- --$n;
- if ($n < 0)
- throw new NoMatchingRuleException($this->_states, $pos, $code);
- } while ($rule->end() === null);
- $this->matchIfCloser($rule->end(), $n + 1, $pos, $closest_hit, $closest_rule);
- }
- // We take the closest hit:
- if ($closest_hit[1] > $pos)
- $this->emit(substr($code, $pos, $closest_hit[1] - $pos));
- $prev_pos = $pos;
- $pos = $closest_hit[1] + strlen($closest_hit[0]);
- if ($prev_pos === $pos and is_string($closest_rule))
- if (array_key_exists($closest_rule, $this->_lang->rule($state))) {
- array_push($this->_states, $closest_rule);
- $state = $closest_rule;
- $this->emitPartial('', $closest_rule);
- }
- if ($closest_hit[1] === $len)
- break;
- else if (!is_string($closest_rule)) {
- // Pop state.
- if (count($this->_states) <= $closest_rule)
- throw new NoMatchingRuleException($this->_states, $pos, $code);
- while (count($this->_states) > $closest_rule + 1) {
- $lastState = array_pop($this->_states);
- $this->emitPop('', $lastState);
- }
- $lastState = array_pop($this->_states);
- $state = array_peek($this->_states);
- $this->emitPop($closest_hit[0], $lastState);
- }
- else if (array_key_exists($closest_rule, $this->_lang->rule($state))) {
- // Push state.
- array_push($this->_states, $closest_rule);
- $state = $closest_rule;
- $this->emitPartial($closest_hit[0], $closest_rule);
- }
- else
- $this->emit($closest_hit[0], $closest_rule);
- } // while ($pos < $len)
- // Close any tags that are still open (can happen in incomplete code
- // fragments that don't necessarily signify an error (consider PHP
- // embedded in HTML, or a C++ preprocessor code not ending on newline).
-
- $omitSpansBackup = $this->_omitSpans;
- for ($i = count($this->_states); $i > 1; --$i)
- $this->emitPop();
- $this->_omitSpans = $omitSpansBackup;
- return $this->_result;
- }
- private function matchIfCloser($expr, $next, $pos, &$closest_hit, &$closest_rule) {
- $matches = array();
- if (preg_match($expr, $this->_code, $matches, PREG_OFFSET_CAPTURE, $pos) == 1) {
- if (
- (
- // Two hits at same position -- compare length
- // For equal lengths: first come, first serve.
- $matches[0][1] == $closest_hit[1] and
- strlen($matches[0][0]) > strlen($closest_hit[0])
- ) or
- $matches[0][1] < $closest_hit[1]
- ) {
- $closest_hit = $matches[0];
- $closest_rule = $next;
- }
- }
- }
- private function processToken($token) {
- if ($token === '')
- return '';
- $nest_lang = array_peek($this->_states);
- if (array_key_exists($nest_lang, $this->_postProcessors))
- return $this->_postProcessors[$nest_lang]->render($token);
- else
- #return self::htmlentities($token);
- return htmlspecialchars($token, ENT_NOQUOTES);
- }
- private function emit($token, $class = '') {
- $token = $this->processToken($token);
- if ($token === '')
- return;
- $class = $this->_lang->className($class);
- if ($class === '')
- $this->write($token);
- else
- $this->write("<span class=\"$class\">$token</span>");
- }
- private function emitPartial($token, $class) {
- $token = $this->processToken($token);
- $class = $this->_lang->className($class);
- if ($class === '') {
- if ($token !== '')
- $this->write($token);
- array_push($this->_omitSpans, true);
- }
- else {
- $this->write("<span class=\"$class\">$token");
- array_push($this->_omitSpans, false);
- }
- }
- private function emitPop($token = '', $class = '') {
- $token = $this->processToken($token);
- if (array_pop($this->_omitSpans))
- $this->write($token);
- else
- $this->write("$token</span>");
- }
- private function write($text) {
- $this->_result .= $text;
- }
- // // DAMN! What did I need them for? Something to do with encoding âŚ
- // // but why not use the `$charset` argument on `htmlspecialchars`?
- // private static function htmlentitiesCallback($match) {
- // switch ($match[0]) {
- // case '<': return '<';
- // case '>': return '>';
- // case '&': return '&';
- // }
- // }
- //
- // private static function htmlentities($text) {
- // return htmlspecialchars($text, ENT_NOQUOTES);
- // return preg_replace_callback(
- // '/[<>&]/', array('Hyperlight', 'htmlentitiesCallback'), $text
- // );
- // }
- } // class Hyperlight
- /**
- * <var>echo</var>s a highlighted code.
- *
- * For example, the following
- * <code>
- * hyperlight('<?php echo \'Hello, world\'; ?>', 'php');
- * </code>
- * results in:
- * <code>
- * <pre class="source-code php">...</pre>
- * </code>
- *
- * @param string $code The code.
- * @param string $lang The language of the code.
- * @param string $tag The surrounding tag to use. Optional.
- * @param array $attributes Attributes to decorate {@link $tag} with.
- * If no tag is given, this argument can be passed in its place. This
- * behaviour will be assumed if the third argument is an array.
- * Attributes must be given as a hash of key value pairs.
- */
- function hyperlight($code, $lang, $tag = 'pre', array $attributes = array()) {
- if ($code == '')
- die("`hyperlight` needs a code to work on!");
- if ($lang == '')
- die("`hyperlight` needs to know the code's language!");
- if (is_array($tag) and !empty($attributes))
- die("Can't pass array arguments for \$tag *and* \$attributes to `hyperlight`!");
- if ($tag == '')
- $tag = 'pre';
- if (is_array($tag)) {
- $attributes = $tag;
- $tag = 'pre';
- }
- $lang = htmlspecialchars(strtolower($lang));
- $class = "source-code $lang";
- $attr = array();
- foreach ($attributes as $key => $value) {
- if ($key == 'class')
- $class .= ' ' . htmlspecialchars($value);
- else
- $attr[] = htmlspecialchars($key) . '="' .
- htmlspecialchars($value) . '"';
- }
- $attr = empty($attr) ? '' : ' ' . implode(' ', $attr);
- $hl = new Hyperlight($lang);
- echo "<$tag class=\"$class\"$attr>";
- $hl->renderAndPrint(trim($code));
- echo "</$tag>";
- }
- /**
- * Is the same as:
- * <code>
- * hyperlight(file_get_contents($filename), $lang, $tag, $attributes);
- * </code>
- * @see hyperlight()
- */
- function hyperlight_file($filename, $lang = null, $tag = 'pre', array $attributes = array()) {
- if ($lang == '') {
- // Try to guess it from file extension.
- $pos = strrpos($filename, '.');
- if ($pos !== false) {
- $ext = substr($filename, $pos + 1);
- $lang = HyperLanguage::nameFromExt($ext);
- }
- }
- hyperlight(file_get_contents($filename), $lang, $tag, $attributes);
- }
- if (defined('HYPERLIGHT_SHORTCUT')) {
- function hy() {
- $args = func_get_args();
- call_user_func_array('hyperlight', $args);
- }
- function hyf() {
- $args = func_get_args();
- call_user_func_array('hyperlight_file', $args);
- }
- }
- function hyperlight_calculate_fold_marks($code, $lang) {
- $supporting_languages = array('csharp', 'vb');
- if (!in_array($lang, $supporting_languages))
- return array();
- $fold_begin_marks = array('/^\s*#Region/', '/^\s*#region/');
- $fold_end_marks = array('/^\s*#End Region/', '/\s*#endregion/');
- $lines = preg_split('/\r|\n|\r\n/', $code);
- $fold_begin = array();
- foreach ($fold_begin_marks as $fbm)
- $fold_begin = $fold_begin + preg_grep($fbm, $lines);
- $fold_end = array();
- foreach ($fold_end_marks as $fem)
- $fold_end = $fold_end + preg_grep($fem, $lines);
- if (count($fold_begin) !== count($fold_end) or count($fold_begin) === 0)
- return array();
- $fb = array();
- $fe = array();
- foreach ($fold_begin as $line => $_)
- $fb[] = $line;
- foreach ($fold_end as $line => $_)
- $fe[] = $line;
- $ret = array();
- for ($i = 0; $i < count($fb); $i++)
- $ret[$fb[$i]] = $fe[$i];
- return $ret;
- }
- function hyperlight_apply_fold_marks($code, array $fold_marks) {
- if ($fold_marks === null or count($fold_marks) === 0)
- return $code;
- $lines = explode("\n", $code);
- foreach ($fold_marks as $begin => $end) {
- $lines[$begin] = '<span class="fold-header">' . $lines[$begin] . '<span class="dots"> </span></span>';
- $lines[$begin + 1] = '<span class="fold">' . $lines[$begin + 1];
- $lines[$end + 1] = '</span>' . $lines[$end + 1];
- }
- return implode("\n", $lines);
- }
- ?>