First commit

This commit is contained in:
Pierre Hubert
2016-11-19 12:08:12 +01:00
commit 990540b2b9
4706 changed files with 931207 additions and 0 deletions

1
3rdparty/luminous/src/.htaccess vendored Executable file
View File

@ -0,0 +1 @@
Deny from all

97
3rdparty/luminous/src/cache/cache.class.php vendored Executable file
View File

@ -0,0 +1,97 @@
<?php
/// @cond ALL
/**
* Cache superclass provides a skeleton for implementations using the filesystem
* or SQL, or anything else.
*/
abstract class LuminousCache {
protected $gz = true;
protected $id = null;
protected $timeout = 0;
protected $cache_hit = false;
private $use_cache = true;
private $creation_check = false;
private $errors = array();
public function __construct($id) {
$this->id = $id;
}
public function set_purge_time($seconds) {
$this->timeout = $seconds;
}
private function _compress($data) {
return $this->gz? gzcompress($data) : $data;
}
private function _decompress($data) {
return $this->gz? gzuncompress($data) : $data;
}
protected function log_error($msg) {
$this->errors[] = $msg;
}
public function has_errors() { return !empty($this->errrors); }
public function errors() { return $this->errors; }
protected abstract function _create();
protected abstract function _read();
protected abstract function _write($data);
protected abstract function _update();
protected abstract function _purge();
private function purge() {
assert($this->creation_check);
if ($this->use_cache)
$this->_purge();
}
private function create() {
if ($this->creation_check) return;
$this->creation_check = true;
if (!$this->_create()) {
$this->use_cache = false;
} else {
$this->purge();
}
}
/**
* @brief Reads from the cache
* @returns the cached string or @c null
*/
public function read() {
$this->create();
if (!$this->use_cache) return null;
$contents = $this->_read();
if ($contents !== false) {
$this->cache_hit = true;
$contents = $this->_decompress($contents);
$this->_update();
return $contents;
} else return null;
}
/**
* @brief Writes into the cache
* @param $data the data to write
*/
public function write($data) {
$this->create();
$this->purge();
if (!$this->cache_hit && $this->use_cache)
$this->_write($this->_compress($data));
}
}
/// @endcond
// ends 'ALL'

150
3rdparty/luminous/src/cache/fscache.class.php vendored Executable file
View File

@ -0,0 +1,150 @@
<?php
/// @cond ALL
/**
* File system cache driver
* @brief The FS cache driver handles storing the cache on the filesystem
*
* The general structure of the cache is as follows:
* @c /cache_dir/prefix/cache_id
*
* @c cache_dir is the root cache directory (normally luminous/cache),
* @c prefix is used to separate things out into multiple locations - we take
* the first two characters of the @c cache_id just to reduce the number of
* files in any one directory (and maybe on some filesystems this allows
* slightly faster lookups?).
* @c cache_id is the unique identifier of the input string (with its first
* two character missing, for @c prefix).
*
* This driver implements necessary functions for reading/writing the cache
* and performing maintenance.
*
*/
class LuminousFileSystemCache extends LuminousCache {
/// root cache directory
private $dir = null;
/// full path to the cached file (for convenience)
private $path = null;
/// subdir within the cache - we factor out the first two
/// characters of the filename, this reduces the number of files in
/// any one folder.
private $subdir = null;
/// the base filename of the cached file
private $filename = null;
public function __construct($id) {
$this->dir = luminous::root() . '/cache/';
$this->subdir = substr($id, 0, 2);
$this->filename = substr($id, 2);
$this->path = rtrim($this->dir, '/') . '/' .
$this->subdir . '/' . $this->filename;
parent::__construct($id);
}
protected function _log_error($file, $msg) {
$this->log_error( str_replace('%s', "\"$file\"", $msg) . "\n"
. "File exists: " . var_export(file_exists($file), true) . "\n"
. "Readable?: " . var_export(is_readable($file), true) . "\n"
. "Writable?: " . var_export(is_writable($file), true) . "\n"
. (!file_exists($this->dir)?
"Your cache dir (\"{$this->dir}\") does not exist! \n" : '' )
. (file_exists($this->dir) && !is_writable($this->dir)?
"Your cache dir (\"{$this->dir}\") is not writable! \n" : '' )
);
}
protected function _create() {
$target = $this->dir . '/' . $this->subdir;
if (!@mkdir($target, 0777, true) && !is_dir($target)) {
$this->_log_error($target, "%s does not exist, and cannot create.");
return false;
}
return true;
}
protected function _update() {
if (!(@touch($this->path))) {
$this->_log_error($this->path, "Failed to update (touch) %s");
}
}
protected function _read() {
$contents = false;
if (file_exists($this->path)) {
$contents = @file_get_contents($this->path);
if ($contents === false)
$this->_log_error($this->path, 'Failed to read %s"');
}
return $contents;
}
protected function _write($data) {
if (@file_put_contents($this->path, $data, LOCK_EX) === false) {
$this->_log_error($this->path, "Error writing to %s");
}
}
/**
* Purges the contents of a directory recursively
*/
private function _purge_recurse($dir) {
$base = $dir . '/';
$time = time();
if (substr($dir, 0, strlen($this->dir)) !== $this->dir) {
// uh oh, we somehow tried to escape from the cache directory
assert(0);
return;
}
foreach(scandir($dir) as $f) {
$fn = $base . $f;
if ($f[0] === '.') continue;
if (is_dir($fn)) {
$this->_purge_recurse($fn);
}
else {
$update = filemtime($fn);
if ($time - $update > $this->timeout) {
unlink($fn);
}
}
}
}
protected function _purge() {
if ($this->timeout <= 0) return;
$purge_file = $this->dir . '/.purgedata';
if (!file_exists($purge_file)) @touch($purge_file);
$last = 0;
$fh = @fopen($purge_file, 'r+');
if (!$fh) {
$this->_log_error($purge_file,
"Error encountered opening %s for writing");
return;
}
$time = time();
if (flock($fh, LOCK_EX)) {
if (filesize($purge_file))
$last = (int)fread($fh, filesize($purge_file));
else $last = 0;
if ($time - $last > 60*60*24) {
rewind($fh);
ftruncate($fh, 0);
rewind($fh);
fwrite($fh, $time);
$this->_purge_recurse($this->dir);
}
flock($fh, LOCK_UN);
fclose($fh);
}
}
}
/// @endcond

20
3rdparty/luminous/src/cache/sql/cache.mysql vendored Executable file
View File

@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS `luminous_cache`
(
id INT NOT NULL AUTO_INCREMENT,
-- this is the cache ID, it's the hex representation of an MD5sum.
cache_id CHAR(32) NOT NULL UNIQUE,
-- cached output
output MEDIUMTEXT NOT NULL,
-- date of insertion
insert_date INT NOT NULL,
-- date of most recent hit
hit_date INT NOT NULL,
PRIMARY KEY (id),
-- we create an index on cache ID because it's what's most interesting, but in reality, we don't
-- usually need to worry about many bytes. 6 chars gives us 3 bytes,
-- or roughly 16 million combinations
INDEX(cache_id(6)),
INDEX(hit_date)
) ENGINE = MYISAM;
-- we explictly set MyISAM because at least on my Xampp installation, innodb is painfully slow

149
3rdparty/luminous/src/cache/sqlcache.class.php vendored Executable file
View File

@ -0,0 +1,149 @@
<?php
/// @cond ALL
class LuminousSQLSafetyException extends Exception {}
/*
* A note regarding escaping:
* Escaping is hard because we don't want to rely on an RBDMS specific escaping
* function.
* Therefore:
* All the data and queries are specifically designed such that escaping is
* unnecessary. String types are either b64 or b16, which means no inconvenient
* characters, and integer types are, well, integers.
*/
class LuminousSQLCache extends LuminousCache {
static $table_name = 'luminous_cache';
static $queries = array(
// FIXME: INSERT IGNORE is MySQL specific.
// we do need an ignore on duplicate because there's a race condition
// between reading from the cache and then writing into it if the
// read failed
'insert' => 'INSERT IGNORE INTO `%s` (cache_id, output, insert_date, hit_date)
VALUES("%s", "%s", %d, %d);',
'update' => 'UPDATE `%s` SET hit_date=%d WHERE cache_id="%s";',
'select' => 'SELECT output FROM `%s` WHERE cache_id="%s";',
'purge' => 'DELETE FROM `%s` WHERE hit_date <= %d AND cache_id != "last_purge";',
'get_purge_time' => 'SELECT hit_date FROM `%s` WHERE cache_id="last_purge" LIMIT 1;',
'set_purge_time' => 'UPDATE `%s` SET hit_date = %d WHERE cache_id="last_purge";',
'set_purge_time_initial' => 'INSERT IGNORE INTO `%s` (cache_id, hit_date)
VALUES ("last_purge", %d);'
);
private $sql_function = null;
public function set_sql_function($func) {
$this->sql_function = $func;
}
private static function _safety_check($string) {
// we should only be handling very restricted data in queries.
// http://en.wikipedia.org/wiki/Base64#Variants_summary_table
if (is_int($string)
|| (is_string($string)
&& preg_match('@^[a-zA-Z0-9\-\+=_/\.:!]*$@i', $string)))
return $string;
else {
throw new LuminousSQLSafetyException();
}
}
private function _query($sql) {
return call_user_func($this->sql_function, $sql);
}
protected function _create() {
try {
if (!is_callable($this->sql_function))
throw new Exception('LuminousSQLCache does not have a callable SQL function');
$r = $this->_query(file_get_contents(dirname(__FILE__) . '/sql/cache.mysql'));
if ($r === false)
throw new Exception('Creation of cache table failed (query returned false)');
} catch(Exception $e) {
$this->log_error($e->getMessage());
return false;
}
return true;
}
protected function _update() {
$this->_query(
sprintf(self::$queries['update'],
self::_safety_check(self::$table_name),
time(),
self::_safety_check($this->id)
)
);
}
protected function _read() {
$ret = false;
try {
$ret = $this->_query(
sprintf(self::$queries['select'],
self::_safety_check(self::$table_name),
self::_safety_check($this->id)
)
);
if (!empty($ret) && isset($ret[0]) && isset($ret[0]['output'])) {
return base64_decode($ret[0]['output']);
}
} catch (LuminousSQLSafetyException $e) {}
return false;
}
protected function _write($data) {
$data = base64_encode($data);
$time = time();
// try {
$this->_query(sprintf(self::$queries['insert'],
self::_safety_check(self::$table_name),
self::_safety_check($this->id),
self::_safety_check($data),
self::_safety_check($time),
self::_safety_check($time)
));
// } catch(LuminousSQLSafetyException $e) {}
}
protected function _purge() {
if ($this->timeout <= 0) return;
$purge_time_ = $this->_query(
sprintf(self::$queries['get_purge_time'],
self::_safety_check(self::$table_name),
self::_safety_check(time())
)
);
$purge_time = 0;
if ($purge_time_ !== false
&& !empty($purge_time_) && isset($purge_time_[0]['hit_date'])) {
$purge_time = $purge_time_[0]['hit_date'];
} else {
// we need to insert the record
$this->_query(
sprintf(self::$queries['set_purge_time_initial'],
self::_safety_check(self::$table_name),
self::_safety_check(time())
)
);
}
if ($purge_time < time() - 60*60*24) {
// XXX: does this need to be in a try block?
try {
$this->_query(
sprintf(self::$queries['purge'],
self::_safety_check(self::$table_name),
self::_safety_check(time() - $this->timeout)));
} catch(LuminousSQLSafetyException $e) {}
$this->_query(
sprintf(self::$queries['set_purge_time'],
self::_safety_check(self::$table_name),
self::_safety_check(time())
)
);
}
}
}
/// @endcond

222
3rdparty/luminous/src/cli.php vendored Executable file
View File

@ -0,0 +1,222 @@
<?php
///@cond ALL
/* command line interface */
class LuminousCLI {
private $options = array(
'input-file' => null,
'output-file' => null,
'lang' => null,
'format' => 'html-full',
'height' => 0,
'theme' => 'geonyx',
'code' => null,
'line-numbers' => true,
);
private $cmd_option_map = array(
'-i' => 'input-file',
'-o' => 'output-file',
'-l' => 'lang',
'-f' => 'format',
'-h' => 'height',
'-t' => 'theme',
);
static function print_help() {
echo <<<EOF
Usage:
php luminous.php [OPTIONS] [SOURCE_CODE]
SOURCE_CODE may be omitted if you specify -i. Use '-' to read code from stdin.
Options:
-f <format> Output format. This can be:
'html' - An HTML snippet (div). CSS is not included
on the page, the style-sheets must be included by
hand.
'html-full' - A full HTML page. CSS is embedded.
'latex' - A LaTeX document.
Default: 'html-full'
-h <height> Constrains the height of the widget with 'html'
formatter. Has no effect on other formatters.
Default: 0
-i <filename> Input file. If this is omitted, SOURCE_CODE is used.
-l <language> Language code. If this is omitted, the language is
guessed.
-o <filename> Output file to write. If this is omitted, stdout is
used.
-t <theme> Theme to use. See --list-themes for valid themes
--no-numbers Disables line numbering
--list-codes Lists valid language codes and exits
--list-themes Lists valid themes and exits
--help Display this text and exit
--version Display version number and exit
EOF;
}
function error($string) {
echo "Error: $string
see --help for help
";
exit(1);
}
function set_lookahead($option, $i) {
global $argv;
if (isset($argv[$i+1]))
$this->options[$this->cmd_option_map[$option]] = $argv[$i+1];
else self::error('Missing option for ' . $option);
}
function list_codes() {
foreach(luminous::scanners() as $name=>$codes) {
echo sprintf("%s: %s\n", $name, join(', ', $codes));
}
exit(0);
}
function list_themes() {
echo preg_replace('/\.css$/m', '', join("\n", luminous::themes()) . "\n");
exit(0);
}
function parse_args() {
global $argv, $argc;
for($i=1; $i<$argc; $i++) {
$a = $argv[$i];
if (isset($this->cmd_option_map[$a])) {
$this->set_lookahead($a, $i++);
}
elseif ($a === '--list-codes') {
self::list_codes();
}
elseif ($a === '--list-themes') {
self::list_themes();
}
elseif ($a === '--help') {
self::print_help();
exit(0);
}
elseif($a === '--version') {
echo LUMINOUS_VERSION;
echo "\n";
exit(0);
}
elseif ($a === '--no-numbers') {
$this->options['line-numbers'] = false;
}
else {
if ($this->options['code'] !== null) {
self::error('Unknown option: ' . $a);
} else {
$this->options['code'] = $a;
}
}
}
}
function highlight() {
$this->parse_args();
// figure out the code
// error cases are:
// no input file or source code,
if ($this->options['code'] === null
&& $this->options['input-file'] === null) {
$this->error('No input file or source code specified');
}
// or both input file and source code
elseif ($this->options['code'] !== null
&& $this->options['input-file'] !== null) {
$this->error('Input file (-i) and source code specified. You probably '
. 'didn\'t mean this');
}
// is there an input file? use that.
if ($this->options['input-file'] !== null) {
$c = @file_get_contents($this->options['input-file']);
if ($c === false) {
$this->error('Could not read from ' . $this->options['input-file']);
}
else {
$this->options['code'] = $c;
}
}
// else we're expecting code to have been given on the command line,
// but it might be '-' which means read stdin
elseif ($this->options['code'] === '-') {
$code = '';
while (($line = fgets(STDIN)) !== false)
$code .= $line;
$this->options['code'] = $code;
}
// set the formatter
luminous::set('format', $this->options['format']);
// lame check that the formatter is okay
try { luminous::formatter(); }
catch(Exception $e) {
$this->error('Unknown formatter ' . $this->options['format']);
}
// set the theme
$valid_themes = luminous::themes();
$theme = $this->options['theme'];
if (!preg_match('/\.css$/', $theme)) $theme .= '.css';
if (!luminous::theme_exists($theme)) {
$this->error('No such theme: ' . $theme);
} else {
luminous::set('theme', $theme);
}
// set the language
if ($this->options['lang'] === null) {
// guessing
$this->options['lang'] = luminous::guess_language($this->options['code']);
}
// user provided language
$scanners = luminous::scanners();
$valid_scanner = false;
foreach($scanners as $lang=>$codes) {
if (in_array($this->options['lang'], $codes)) {
$valid_scanner = true;
break;
}
}
if (!$valid_scanner) $this->error('No such language: '
. $this->options['lang']);
// other options
luminous::set('max-height', $this->options['height']);
luminous::set('line-numbers', $this->options['line-numbers']);
$h = luminous::highlight($this->options['lang'], $this->options['code']);
if ($this->options['output-file'] !== null) {
$r = @file_put_contents($this->options['output-file'], $h, LOCK_EX);
if ($r === false)
$this->error('Could not write to ' . $this->options['output-file']);
} else {
echo $h;
}
exit(0);
}
}
function main() {
$luminous_cli = new LuminousCLI();
$luminous_cli->highlight();
}
main();
///@endcond

296
3rdparty/luminous/src/core/filters.class.php vendored Executable file
View File

@ -0,0 +1,296 @@
<?php
/**
* @cond CORE
* @brief A collection of useful common filters.
*
* Filters are either stream filters or individual filters.
* Stream filters operate on the entire token stream, and return the new
* token stream. Individual filters operate on individual tokens (bound by type),
* and return the new token. Any publicly available member here is one of those,
* therefore the return and param documents are omitted.
*
*/
// Poor man's namespace.
class LuminousFilters {
/**
* @brief Gets the expected number of arguments to a doc-comment command/tag
* @param $command the name of the command
* @returns The expected number of arguments for a command, this is either
* 0 or 1 at the moment
* @internal
*/
private static function doxygen_arg_length($command) {
switch(strtolower($command)) {
case "addtogroup":
case "category":
case "class":
case "cond":
case "def":
case "defgroup":
case "dir":
case "elseif":
case "enum":
case "exception":
case "example":
case "extends":
case "if":
case "ifnot":
case "file":
case "headerfile":
case "implements":
case "ingroup":
case "interface":
case "memberof":
case "namespace":
case "package":
case "page":
case "par":
case "param":
case "relates":
case "relatesalso":
case "retval":
case 'see':
case 'since':
case "tparam":
case "throw":
case "throws":
case "weakgroup":
case "xrefitem":
return 1;
default: return 0;
}
}
/**
* @brief callback to doxygenize
* Highlights Doxygen-esque doc-comment syntax.
* This is a callback to doxygenize().
* @return the highlighted string
* @internal
*/
private static function doxygenize_cb($matches) {
$lead = $matches[1];
$tag_char = $matches[2];
$tag = $matches[3];
$line = "";
if (isset($matches[4]))
$line = $matches[4];
$len = -1;
// JSDoc-like
$l_ = ltrim($line);
if (isset($l_[0]) && $l_[0] === '{') {
$line = preg_replace('/({[^}]*})/', "<DOCPROPERTY>$1</DOCPROPERTY>", $line);
return "$lead<DOCTAG>$tag_char$tag</DOCTAG>$line";
}
else
$len = self::doxygen_arg_length($tag);
if($len === 0)
return "$lead<DOCTAG>$tag_char$tag</DOCTAG>$line";
else {
$l = explode(' ', $line);
$start = "$lead<DOCTAG>$tag_char$tag</DOCTAG><DOCPROPERTY>";
$j = 0;
$c = count($l);
for($i=0; $j<$len && $i<$c; $i++)
{
$s = $l[$i];
$start .= $s . ' ';
unset($l[$i]);
if (trim($s) !== '')
$j++;
}
$start = preg_replace('/ $/', '', $start);
$start .= "</DOCPROPERTY>";
$l = array_values($l);
if (!empty($l)) $start .= ' ';
$start .= implode(' ', $l);
return $start;
}
}
/**
* @brief Highlights doc-comment tags inside a comment block.
*
* @see generic_doc_comment
* @internal
*/
static function doxygenize($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace_callback("/(^(?>[\/\*#\s\{]*))([\@\\\])([\\w]*)(\}|[ \t]+.*?)?$/m",
array('LuminousFilters', 'doxygenize_cb'), $token[1]);
return $token;
}
/**
* @brief Generic filter to highlight JavaDoc, PHPDoc, Doxygen, JSdoc, and similar doc comment syntax.
*
* A cursory check will be performed to try to validate that the token
* really is a doc-comment, it does this by checking for common formats.
*
* If the check is successful, the token will be switched to type
* 'DOCCOMMENT' and its doc-tags will be highlighted
*
* This is a wrapper around doxygenize(). If the checks are not necessary,
* or incorrect for your situation, you may instead choose to use
* doxygenize() directly.
*/
static function generic_doc_comment($token) {
// checks if a comment is in the form:
// xyyz where x may = y but y != z.
// This matches, say, /** comment but does not match /********/
// same with /// comment but not ///////////////
$s = $token[1];
if (isset($s[3])
&& ($s[2] === $s[1] || $s[2] === '!')
&& !ctype_space($s[0])
&& !ctype_space($s[1])
&& $s[3] !== $s[2]
)
{
$token[0] = 'DOCCOMMENT';
$token = self::doxygenize($token);
}
return $token;
}
/**
* @brief Highlights comment notes
* Highlights keywords in comments, i.e. "NOTE", "XXX", "FIXME", "TODO",
* "HACK", "BUG"
*/
static function comment_note($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace('/\\b(?:NOTE|XXX|FIXME|TODO|HACK|BUG):?/',
'<COMMENT_NOTE>$0</COMMENT_NOTE>', $token[1]);
return $token;
}
/**
* @brief Highlights generic escape sequences in strings
* Highlights escape sequences in strings. There is no checking on which
* sequences are legal, this is simply a generic function which checks for
* \\u... unicode, \\d... octal, \\x... hex and finally just any character
* following a backslash.
*/
static function string($token) {
if (strpos($token[1], '\\') === false) return $token;
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace('/
\\\\
(?:
(?:u[a-f0-9]{4,8}) # unicode
|\d{1,3} # octal
|x[a-fA-F0-9]{2} # hex
|. # whatever
)
/xi', '<ESC>$0</ESC>', $token[1]);
return $token;
}
/**
* @brief Tries to highlight PCRE style regular expression syntax
*/
static function pcre($token, $delimited=true) {
$token = self::string($token);
$token = LuminousUtils::escape_token($token);
$str = &$token[1];
$flags = array();
if ($delimited) {
$str = preg_replace('/^[^[:alnum:]<>\s]/', '<DELIMITER>$0</DELIMITER>', $str);
if (preg_match("/[[:alpha:]]+$/", $str, $matches)){
$m = $matches[0];
$flags = str_split($m);
$str = preg_replace("/((?<!\A)[^[:alnum:]\s<>])([[:alpha:]]+)$/",
"<DELIMITER>$1</DELIMITER><KEYWORD>$2</KEYWORD>", $str);
} else
$str = preg_replace('/[^[:alnum:]<>]$/', '<DELIMITER>$0</DELIMITER>', $str);
}
$str = preg_replace("/((?<!\\\)[\*\+\.|])|((?<![\(\\\])\?)/",
"<REGEX_OPERATOR>$0</REGEX_OPERATOR>", $str);
$str = preg_replace("/(?<=\()\?(?:(?:[a-zA-Z:!|=])|(?:(?:&lt;)[=!]))/",
"<REGEX_SUBPATTERN>$0</REGEX_SUBPATTERN>", $str);
$str = preg_replace("/(?<!\\\)[\(\)]/",
"<REGEX_SUBPATTERN_MARKER>$0</REGEX_SUBPATTERN_MARKER>", $str);
$str = preg_replace("/(?<!\\\)[\[\]]/",
"<REGEX_CLASS_MARKER>$0</REGEX_CLASS_MARKER>", $str);
$str = preg_replace("/(?<!\\\)
\{
(
((?>\d+)(,(?>\d+)?)?)
|
(,(?>\d+))
)
\}/x", "<REGEX_REPEAT_MARKER>$0</REGEX_REPEAT_MARKER>", $str);
// extended regex: # signifies a comment
if (in_array('x', $flags))
$str = preg_replace('/(?<!\\\)#.*$/m', '<COMMENT>$0</COMMENT>',
$str);
return $token;
}
/**
* @brief Translates any token type of an uppercase/numeric IDENT to 'CONSTANT'
*/
static function upper_to_constant($token) {
// check for this because it may have been mapped to a function or something
if ($token[0] === 'IDENT' && preg_match('/^[A-Z_][A-Z_0-9]{3,}$/', $token[1]))
$token[0] = 'CONSTANT';
return $token;
}
/**
* @brief Translates anything of type 'IDENT' to the null type
*/
static function clean_ident($token) {
if ($token[0] === 'IDENT') $token[0] = null;
return $token;
}
/**
* @brief Attempts to apply OO syntax highlighting
*
* Tries to apply generic OO syntax highlighting. Any identifer immediately
* preceding a '.', '::' or '->' token is mapped to an 'OO'.
* Any identifer token immediatel following any of those tokens is mapped to
* an 'OBJ'.
* This is a stream filter.
*/
static function oo_stream_filter($tokens) {
$c = count($tokens);
for($i=0; $i<$c; $i++) {
if ($tokens[$i][0] !== 'IDENT') continue;
if ($i > 0) {
$s = $tokens[$i-1][1];
if ($s === '.' || $s === '->' || $s === '::') {
$tokens[$i][0] = 'OO';
$i++;
continue;
}
}
if ($i < $c-1) {
$s = $tokens[$i+1][1];
if ($s === '.' || $s === '->' || $s === '::') {
$tokens[$i][0] = 'OBJ';
$i++;
}
}
}
return $tokens;
}
}
/// @endcond
// end CORE

1854
3rdparty/luminous/src/core/scanner.class.php vendored Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
<?php
/**
* @cond CORE
* @brief A basic preg_match wrapper which caches its results
*
* @note This class is used by Scanner and should not need to be used by
* anything else.
*
* A class encapsulating the process of searching for a substring efficiently
* it handles caching of results.
*
* @warning One instance should be used only incrementally along a string.
* i.e. do not call it with index = 5 then index = 1.
*
* @internal
*
*/
class LuminousStringSearch
{
/// A copy of the string to operate on.
private $string;
/**
* The cache is stored as a map of pattern => result,
* the result is an array of:
* (0=>index, 1=>match_groups), OR, it is false if there are no more results
* left in the string.
*/
private $cache = array();
public function __construct($str) {
$this->string = $str;
}
/**
* @brief Performs a search for the given pattern past the given index.
* @param $search the pattern to search for
* @param $index the minimum string index (offset) of a result
* @param $matches a reference to the return location of the match groups
* @return the index or false if no match is found.
*/
public function
match($search, $index, &$matches) {
$r = false; // return value
if (isset($this->cache[$search])) {
$a = $this->cache[$search];
if ($a === false) return false; // no more results
$r = $a[0];
$matches = $a[1];
assert($matches !== null);
if ($r >= $index) // cache is good!
return $r;
}
// cache not set, or out of date, we have to perform the match
if (!($ret = preg_match($search, $this->string, $matches_,
PREG_OFFSET_CAPTURE, $index))) {
if ($ret === false && LUMINOUS_DEBUG) {
throw new Exception('preg_match returned false for pattern: "'
. $search . '", with code: ' . LuminousUtils::pcre_error_decode(
preg_last_error()) . " with string length " . strlen($this->string)
. " and offset " . $index
);
}
$this->cache[$search] = false;
return false;
}
$r = $matches_[0][1];
// strip the offsets from the match_groups
foreach($matches_ as $i=>&$v)
$v = $v[0];
$this->cache[$search] = array($r, $matches_);
$matches = $matches_;
return $r;
}
}
/// @endcond

View File

@ -0,0 +1,49 @@
<?php
/**
* @cond CORE
* @brief A set of pre-defined patterns to match various common tokens
*/
abstract class LuminousTokenPresets {
/// multi-line double quoted string using backslash escapes
static $DOUBLE_STR = '/" (?> [^"\\\\]+ | \\\\.)* (?:"|$)/xs';
/// single line double quoted string using backslash escapes
static $DOUBLE_STR_SL = "/\"(?> [^\\\\\"\n]+ | \\\\.)*(?:\$|\")/xms";
/// multi-line single quote string using backslash escapes
static $SINGLE_STR = "/' (?> [^'\\\\]+ | \\\\.)* (?:'|\$)/xs";
/// single line single quoted string using backslash escapes
static $SINGLE_STR_SL = "/'(?> [^\\\\'\n]+ | \\\\.)*(?:\$|')/xms";
/// Single quoted c-style character
static $CHAR = "(?: \\\\(?: x[A-F0-9]{1,2}| . ) | . ) (?: '|\$)/ixm";
/// hexadecimal literal
static $NUM_HEX = '/0[Xx][a-fA-F0-9]+/';
/// Real number, i.e. an integer or a float, optionally with an exponent
static $NUM_REAL = '/
(?: \d+ (?: \.\d+ )? | \.?\d+) # int, fraction or significand
(?:e[+-]?\d+)? # exponent
/ix';
/// Single line c++ style comment
static $C_COMMENT_SL = '% // .* %x';
/// Multiline C style comment
static $C_COMMENT_ML = '% / \* (?> [^\\*]+ | \\*(?!/) )* (?: \\*/ | $) %sx';
/// Perl/Python/Ruby style hash-comment (single line)
static $PERL_COMMENT = '/#.*/';
/// SQL style single quoted string using '' to escape
static $SQL_SINGLE_STR = "/ ' (?> [^']+ | '' )* (?: '|\$)/x";
/// SQL style single quoted string using '' or \' to escape
static $SQL_SINGLE_STR_BSLASH = "/ ' (?> [^'\\\\]+ | '' | \\\\. )* (?: '|\$)/x";
}
/// @endcond

118
3rdparty/luminous/src/core/utils.class.php vendored Executable file
View File

@ -0,0 +1,118 @@
<?php
/**
* @cond CORE
* @brief A set of utility functions for scanners
*/
class LuminousUtils {
/**
* @brief Tries to balance a delimiter
*
* Tries to 'balance' a single character delimiter, i.e:
* '(' is mapped to ')'
* '{' is mapped to '}',
* '[' is mapped to ']',
* '<' is mapped to '>'
* Any other character is mapped to itself.
*
* @param $delimiter the left/opening delimiter to try to balance
* @return The corresponding close delimiter character, or the input
* character.
*/
static function balance_delimiter($delimiter) {
switch($delimiter) {
case '(' : return ')';
case '{' : return '}';
case '[' : return ']';
case '<' : return '>';
default: return $delimiter;
}
}
/**
* @brief Escapes a string suitable for use in XML
*
* Escapes a string according to the Luminous internal escaping format
* (this is currently htmlspecialchars with ENT_NOQUOTES.)
* @param $string the string to escape
* @return the escaped string
*/
static function escape_string($string) {
return htmlspecialchars($string, ENT_NOQUOTES);
}
/**
* @brief Escapes a token so its string is suitable for use in XML
*
* Escapes a token. If the token is already escaped, nothing changes.
* If the token is not escaped, the escaped flag is set (index 2) and the
* token text (index 1) is escaped according to the internal escaping format
* @param $token the token to escape
* @return the escaped token
*/
static function escape_token($token) {
$esc = &$token[2];
if (!$esc) {
$str = &$token[1];
$str = htmlspecialchars($str, ENT_NOQUOTES);
$esc = true;
}
return $token;
}
/**
* @brief Wraps a block of text in an XML tag
*
* Tags a block of text. The block is assumed to have been escaped correctly
* with LuminousUtils::escape_string.
* @param $type the type to tag the string as, this is the token name
* @param $block the block of text
* @param $split_multiline if this is set to true, the tags are closed at
* the end of each line and re-opened again on the next line. This is
* useful for output formats like HTML, where spanning multiple lines
* could break the markup
* @return The tagged block of text. This resembles an XML fragment.
*/
static function tag_block($type, $block, $split_multiline=true) {
if ($type === null) return $block;
$open = '<' . $type . '>';
$close = '</' . $type . '>';
if ($split_multiline)
return $open . str_replace("\n", $close . "\n" . $open, $block) .
$close;
else
return $open . $block . $close;
}
/**
* @brief Decodes PCRE error codes to human readable strings
*
* Decodes a PCRE error code, which was returned by preg_last_error(), to
* something readable
* @param $errcode the error code
* @return the error description, as string. This is currently the same
* as the constant name, so the constant PREG_NO_ERROR is mapped to the
* string 'PREG_NO_ERROR'
*/
static function pcre_error_decode($errcode) {
switch ($errcode) {
case PREG_NO_ERROR:
return 'PREG_NO_ERROR';
case PREG_INTERNAL_ERROR:
return 'PREG_INTERNAL_ERROR';
case PREG_BACKTRACK_LIMIT_ERROR:
return 'PREG_BACKTRACK_LIMIT_ERROR';
case PREG_RECURSION_LIMIT_ERROR:
return 'PREG_RECURSION_LIMIT_ERROR';
case PREG_BAD_UTF8_ERROR:
return 'PREG_BAD_UTF8_ERROR';
case PREG_BAD_UTF8_OFFSET_ERROR:
return 'PREG_BAD_UTF8_OFFSET_ERROR';
default:
return "Unknown error code `$errcode'";
}
}
}
/// @endcond
// ends CORE

3
3rdparty/luminous/src/debug.php vendored Executable file
View File

@ -0,0 +1,3 @@
<?php
define('LUMINOUS_DEBUG', false);

20
3rdparty/luminous/src/doxygen.php vendored Executable file
View File

@ -0,0 +1,20 @@
<?php
// A few things to make Doxygen a little nicer. No code here.
/**
* @mainpage
*
* This is the API documentation for Luminous, a PHP syntax highlighter.
* This is mostly for development purposes, and not a lot of use to users.
*
* However, users may be interested in the \link luminous user's API \endlink
* and the LuminousOptions class
*
*
* Luminous site: http://luminous.asgaard.co.uk
*
* User's documentation: http://luminous.asgaard.co.uk/index.php/docs/show/index
*
*
*/

View File

@ -0,0 +1,173 @@
<?php
/// @cond ALL
/**
* \file luminous_formatter.class.php
* \brief Formatting logic -- converts Luminous output into displayable formats
*/
/**
* \brief Abstract class to convert Luminous output into a universal format.
*
* Abstract base class to implement an output formatter. A formatter
* will convert Luminous's tags into some kind of output (e.g. HTML), by
* overriding the method Format().
*/
abstract class LuminousFormatter {
/// Number of chars to wrap at
public $wrap_length = 120;
/// Don't use this yet.
public $language_specific_tags = false;
/**
* Tab width, in spaces. If this is -1 or 0, tabs will not be converted. This
* is not recommended as browsers may render tabs as different widths which
* will break the wrapping.
*/
public $tab_width = 2;
/// Whether or not to add line numbering
public $line_numbers = true;
/// Number of first line
public $start_line = 1;
/// An array of lines to be highlighted initially, if the formatter supports
/// it
public $highlight_lines = array();
/// sets whether or not to link URIs.
public $link = true;
/**
* Height of the resulting output. This may or may not make any sense
* depending on the output format.
*
* Use 0 or -1 for no limit.
*/
public $height = 0;
/**
* The language of the source code being highlighted. Formatters may choose
* to do something with this.
*/
public $language = null;
/**
* The main method for interacting with formatter objects.
* @param src the input string, which is of the form output by an instance of
* Luminous.
* @return The input string reformatted to some other specification.
*/
public abstract function format($src);
/**
* If relevant, the formatter should implement this and use LuminousCSSParser
* to port the theme.
* @param $theme A CSS string representing the theme
*/
public function set_theme($theme)
{
}
/**
* @internal
* Handles line wrapping.
* @param line the line which needs to be broken. This is a reference, which
* will be operated upon. After calling, $line will have appropriate line
* breaks to wrap to the given width, and will contain at least one line break
* at the end.
* @param wrap_length the width to wrap to.
*
* @return the number of lines it was broken up into (1 obviously means no
* wrapping occurred.).
*
* @todo wrap to indent? or not? hm.
*
*/
protected static function wrap_line(&$line, $wrap_length) {
// The vast majority of lines will not need wrapping so it pays to
// check this first.
if ($wrap_length <= 0 || !isset($line[$wrap_length])
|| strlen(strip_tags($line)) < $wrap_length) {
$line .= "\n";
return 1;
}
$line_split = preg_split('/((?:<.*?>)|(?:&.*?;)|[ \t]+)/',
$line, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
$strlen = 0;
$line_cpy = "";
$num_lines = 1;
$num_open = 0;
foreach($line_split as $l) {
$l0 = $l[0];
if ($l0 === '<') {
$line_cpy .= $l;
continue;
}
$s = strlen($l);
if($l0 === '&') {
// html entity codes only count as 1 char.
if(++$strlen > $wrap_length) {
$strlen = 1;
$line_cpy .= "\n";
$num_lines++;
}
$line_cpy .= $l;
continue;
}
if ($s+$strlen <= $wrap_length) {
$line_cpy .= $l;
$strlen += $s;
continue;
}
if ($s <= $wrap_length) {
$line_cpy .= "\n" . $l;
$num_lines++;
$strlen = $s;
continue;
}
// at this point, the line needs wrapping.
// bump us up to the next line
$diff = $wrap_length-$strlen;
$line_cpy .= substr($l, 0, $diff) . "\n";
$l_ = substr($l, $diff);
// now start copying.
$strlen = 0;
// this would probably be marginally faster if it did its own arithmetic
// instead of calling strlen
while (strlen($l_) > 0) {
$strl = strlen($l_);
$num_lines++;
if ($strl > $wrap_length) {
$line_cpy .= substr($l_, 0, $wrap_length) . "\n";
$l_ = substr($l_, $wrap_length);
} else {
$line_cpy .= $l_;
$strlen = $strl;
break;
}
}
}
$line = $line_cpy . "\n";
return $num_lines;
}
}
/// @endcond

View File

@ -0,0 +1,344 @@
<?php
/// @cond ALL
/**
* Collection of templates and templating utilities
*/
class LuminousHTMLTemplates {
// NOTE Don't worry about whitespace in the templates - it gets stripped from the innerHTML,
// so the <pre>s aren't affected. Make it readable :)
/// Normal container
const container_template = '
<div
class="luminous"
data-language="{language}"
style="{height_css}"
>
{subelement}
</div>';
/// Inline code container
const inline_template = '
<div
class="luminous inline"
data-language="{language}"
>
{subelement}
</div>';
/// line number-less
const numberless_template = '
<pre
class="code"
>
{code}
</pre>';
/// line numbered
// NOTE: there's a good reason we use tables here and that's because
// nothing else works reliably.
const numbered_template = '
<table>
<tbody>
<tr>
<td>
<pre class="line-numbers">
{line_numbers}
</pre>
</td>
<td class="code-container">
<pre class="code numbered"
data-startline="{start_line}"
data-highlightlines="{highlight_lines}"
>
{code}
</pre>
</td>
</tr>
</tbody>
</table>';
private static function _strip_template_whitespace_cb($matches) {
return ($matches[0][0] === '<')? $matches[0] : '';
}
private static function _strip_template_whitespace($string) {
return preg_replace_callback('/\s+|<[^>]++>/',
array('self', '_strip_template_whitespace_cb'),
$string);
}
/**
* Formats a string with a given set of values
* The format syntax uses {xyz} as a placeholder, which will be
* substituted from the 'xyz' key from $variables
*
* @param $template The template string
* @param $variables An associative (keyed) array of values to be substituted
* @param $strip_whitespace_from_template If @c TRUE, the template's whitespace is removed.
* This allows templates to be written to be easeier to read, without having to worry about
* the pre element inherting any unintended whitespace
*/
public static function format($template, $variables, $strip_whitespace_from_template = true) {
if ($strip_whitespace_from_template) {
$template = self::_strip_template_whitespace($template);
}
foreach($variables as $search => $replace) {
$template = str_replace("{" . $search . "}", $replace, $template);
}
return $template;
}
}
class LuminousFormatterHTML extends LuminousFormatter {
// overridden by inline formatter
protected $inline = false;
public $height = 0;
/**
* strict HTML standards: the target attribute won't be used in links
* \since 0.5.7
*/
public $strict_standards = false;
private function height_css() {
$height = trim('' . $this->height);
$css = '';
if (!empty($height) && (int)$height > 0) {
// look for units, use px is there are none
if (!preg_match('/\D$/', $height)) $height .= 'px';
$css = "max-height: {$height};";
}
else
$css = '';
return $css;
}
private static function template_cb($matches) {
}
// strips out unnecessary whitespace from a template
private static function template($t, $vars=array()) {
$t = preg_replace_callback('/\s+|<[^>]++>/',
array('self', 'template_cb'),
$t);
array_unshift($vars, $t);
$code = call_user_func_array('sprintf', $vars);
return $code;
}
private function lines_numberless($src) {
$lines = array();
$lines_original = explode("\n", $src);
foreach($lines_original as $line) {
$l = $line;
$num = $this->wrap_line($l, $this->wrap_length);
// strip the newline if we're going to join it. Seems the easiest way to
// fix http://code.google.com/p/luminous/issues/detail?id=10
$l = substr($l, 0, -1);
$lines[] = $l;
}
$lines = implode("\n", $lines);
return $lines;
}
private function format_numberless($src) {
return LuminousHTMLTemplates::format(
LuminousHTMLTemplates::numberless_template,
array(
'height_css' => $this->height_css(),
'code' => $this->lines_numberless($src)
)
);
}
public function format($src) {
$line_numbers = false;
if ($this->link) $src = $this->linkify($src);
$code_block = null;
if ($this->line_numbers) {
$code_block = $this->format_numbered($src);
}
else {
$code_block = $this->format_numberless($src);
}
// convert </ABC> to </span>
$code_block = preg_replace('/(?<=<\/)[A-Z_0-9]+(?=>)/S', 'span',
$code_block);
// convert <ABC> to <span class=ABC>
$cb = create_function('$matches',
'$m1 = strtolower($matches[1]);
return "<span class=\'" . $m1 . "\'>";
');
$code_block = preg_replace_callback('/<([A-Z_0-9]+)>/', $cb, $code_block);
$format_data = array(
'language' => ($this->language === null)? '' : htmlentities($this->language),
'subelement' => $code_block,
'height_css' => $this->height_css()
);
return LuminousHTMLTemplates::format(
$this->inline? LuminousHTMLTemplates::inline_template :
LuminousHTMLTemplates::container_template,
$format_data
);
}
/**
* Detects and links URLs - callback
*/
protected function linkify_cb($matches) {
$uri = (isset($matches[1]) && strlen(trim($matches[1])))? $matches[0]
: "http://" . $matches[0];
// we dont want to link if it would cause malformed HTML
$open_tags = array();
$close_tags = array();
preg_match_all("/<(?!\/)([^\s>]*).*?>/", $matches[0], $open_tags,
PREG_SET_ORDER);
preg_match_all("/<\/([^\s>]*).*?>/", $matches[0], $close_tags,
PREG_SET_ORDER);
if (count($open_tags) != count($close_tags))
return $matches[0];
if (isset($open_tags[0])
&& trim($open_tags[0][1]) !== trim($close_tags[0][1])
)
return $matches[0];
$uri = strip_tags($uri);
$target = ($this->strict_standards)? '' : ' target="_blank"';
return "<a href='{$uri}' class='link'{$target}>{$matches[0]}</a>";
}
/**
* Detects and links URLs
*/
protected function linkify($src) {
if (stripos($src, "http") === false && stripos($src, "www") === false)
return $src;
$chars = "0-9a-zA-Z\$\-_\.+!\*,%";
$src_ = $src;
// everyone stand back, I know regular expressions
$src = preg_replace_callback(
"@(?<![\w])
(?:(https?://(?:www[0-9]*\.)?) | (?:www\d*\.) )
# domain and tld
(?:[$chars]+)+\.[$chars]{2,}
# we don't include tags at the EOL because these are likely to be
# line-enclosing tags.
(?:[/$chars/?=\#;]+|&amp;|<[^>]+>(?!$))*
@xm",
array($this, 'linkify_cb'), $src);
// this can hit a backtracking limit, in which case it nulls our string
// FIXME: see if we can make the above regex more resiliant wrt
// backtracking
if (preg_last_error() !== PREG_NO_ERROR) {
$src = $src_;
}
return $src;
}
private function format_numbered($src) {
$lines = '<span>' .
str_replace("\n", "\n</span><span>", $src, $num_replacements) .
"\n</span>";
$num_lines = $num_replacements + 1;
$line_numbers = '<span>' . implode('</span><span>',
range($this->start_line, $this->start_line + $num_lines - 1, 1)
) . '</span>';
$format_data = array(
'line_number_digits' => strlen( (string)($this->start_line) + $num_lines ), // max number of digits in the line - this is used by the CSS
'start_line' => $this->start_line,
'height_css' => $this->height_css(),
'highlight_lines' => implode(',', $this->highlight_lines),
'code' => $lines,
'line_numbers' => $line_numbers
);
return LuminousHTMLTemplates::format(
LuminousHTMLTemplates::numbered_template,
$format_data
);
}
}
class LuminousFormatterHTMLInline extends LuminousFormatterHTML {
public function format($src) {
$this->line_numbers = false;
$this->height = 0;
$this->inline = true;
return parent::format($src);
}
}
class LuminousFormatterHTMLFullPage extends LuminousFormatterHTML {
protected $theme_css = null;
protected $css = null;
public function set_theme($css) {
$this->theme_css = $css;
}
protected function get_layout() {
// this path info shouldn't really be here
$path = luminous::root() . '/style/luminous.css';
$this->css = file_get_contents($path);
}
public function format($src) {
$this->height = 0;
$this->get_layout();
$fmted = parent::format($src);
return <<<EOF
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<style type='text/css'>
body {
margin: 0;
}
/* luminous.css */
{$this->css}
/* End luminous.css */
/* Theme CSS */
{$this->theme_css}
/* End theme CSS */
</style>
</head>
<body>
<!-- Begin luminous code //-->
$fmted
<!-- End Luminous code //-->
</body>
</html>
EOF;
}
}
/// @endcond

View File

@ -0,0 +1,14 @@
<?php
/// @cond ALL
/**
* Identity formatter. Returns what it's given. Implemented for consistency.
*/
class LuminousIdentityFormatter extends LuminousFormatter {
public function format($str) {
return $str;
}
}
/// @endcond

View File

@ -0,0 +1,200 @@
<?php
/// @cond ALL
require_once(dirname(__FILE__) . '/../utils/cssparser.class.php');
/**
* LaTeX output formatter for Luminous.
*
* \since 0.5.4
*/
class LuminousFormatterLatex extends LuminousFormatter {
private $css = null;
function __construct() { }
function set_theme($theme) {
$this->css = new LuminousCSSParser();
$this->css->convert($theme);
}
/// Converts a hexadecimal string in the form #ABCDEF to an RGB array
/// where each element is normalised to the range 0-1
static function hex2rgb($hex) {
$x = hexdec(substr($hex, 1));
$b = $x % 256;
$g = ($x >> 8) % 256;
$r = ($x >> 16) % 256;
$b /= 255.0;
$g /= 255.0;
$r /= 255.0;
$b = round($b, 2);
$g = round($g, 2);
$r = round($r, 2);
$rgb = array($r, $g, $b);
return $rgb;
}
protected function linkify($src) {
return $src;
}
/// Defines all the styling commands, these are obtained from the css parser
function define_style_commands() {
if ($this->css === null)
throw new Exception('LaTeX formatter has not been set a theme');
$cmds = array();
foreach($this->css->rules() as $name=>$properties) {
if (!preg_match('/^\w+$/', $name))
continue;
$cmd = "{#1}" ;
if ($this->css->value($name, 'bold', false) === true)
$cmd = "{\\textbf$cmd}";
if ($this->css->value($name, 'italic', false) === true)
$cmd = "{\\emph$cmd}";
if (($col = $this->css->value($name, 'color', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col)) {
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
$cmd = "{\\textcolor[rgb]{{$col_str}}$cmd}";
}
}
$name = str_replace('_', '', $name);
$name = strtoupper($name);
$cmds[] = "\\newcommand{\\lms{$name}}[1]$cmd";
}
if ($this->line_numbers &&
($col = $this->css->value('code', 'color', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col)) {
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
$cmd = "\\renewcommand{\\theFancyVerbLine}{%
\\textcolor[rgb]{{$col_str}}{\arabic{FancyVerbLine}}}";
$cmds[] = $cmd;
}
}
return implode("\n", $cmds);
}
function get_background_colour() {
if (($col = $this->css->value('code', 'bgcolor', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col))
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
return "\\pagecolor[rgb]{{$col_str}}";
}
return "";
}
function format($str) {
$out = '';
$verbcmd = "\\begin{Verbatim}[commandchars=\\\\\\{\}";
if ($this->line_numbers)
$verbcmd .= ",numbers=left,firstnumber=1,stepnumber=1";
$verbcmd .= ']';
// define the preamble
$out .= <<<EOF
\documentclass{article}
\usepackage{fullpage}
\usepackage{color}
\usepackage{fancyvrb}
\begin{document}
{$this->define_style_commands()}
{$this->get_background_colour()}
$verbcmd
EOF;
$s = '';
$str = preg_replace('%<([^/>]+)>\s*</\\1>%', '', $str);
$str = str_replace("\t", ' ', $str);
$lines = explode("\n", $str);
if ($this->wrap_length > 0) {
$str = '';
foreach($lines as $i=>$l) {
$this->wrap_line($l, $this->wrap_length);
$str .= $l;
}
}
$str_ = preg_split('/(<[^>]+>)/', $str, -1,
PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
$f1 = create_function('$matches', '
return "\\\lms" . str_replace("_", "", $matches[1]) . "{"; ');
$f2 = create_function('$matches', '
if ($matches[0][0] == "\\\")
return "{\\\textbackslash}";
return "\\\" . $matches[0];');
foreach($str_ as $s_) {
if ($s_[0] === '<') {
$s_ = preg_replace('%</[^>]+>%', '}', $s_);
$s_ = preg_replace_callback('%<([^>]+)>%', $f1
,$s_);
} else {
$s_ = str_replace('&gt;', '>', $s_);
$s_ = str_replace('&lt;', '<', $s_);
$s_ = str_replace('&amp;', '&', $s_);
$s_ = preg_replace_callback('/[#{}_$\\\&]|&(?=amp;)/', $f2, $s_);
}
$s .= $s_;
}
unset($str_);
$s = "\\lmsCODE{" . $s . '}';
/* XXX:
* hack alert: leaving newline literals (\n) inside arguments seems to
* leave them being totally ignored. This is a problem for wrapping.
*
* the current solution is to close all open lms commands before the
* newline then reopen them afterwards.
*/
$stack = array();
$pieces = preg_split('/(\\\lms[^\{]+\{|(?<!\\\)(\\\\\\\\)*[\{\}])/', $s,
-1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
// NOTE: p being a reference is probably going to necessitate a lot of
// copying to pass through all these preg_* and str* calls.
// consider rewriting.
foreach($pieces as $k=>&$p) {
if (preg_match('/^\\\lms/', $p))
$stack[] = "" . $p;
elseif(preg_match('/^(\\\\\\\\)*\}/', $p)) {
array_pop($stack);
}
elseif(preg_match('/^(\\\\\\\\)*{/', $p))
$stack [] = $p;
elseif(strpos($p, "\n") !== false) {
$before = "";
$after = "";
foreach($stack as $st_) {
$before .= $st_;
$after .= '}';
}
$p = str_replace("\n", "$after\n$before" , $p);
}
}
$s = implode('', $pieces);
$out .= $s;
$out .= <<<EOF
\end{Verbatim}
\end{document}
EOF;
return $out;
}
}
/// @endcond

140
3rdparty/luminous/src/load_scanners.php vendored Executable file
View File

@ -0,0 +1,140 @@
<?php
/*
* This is a horrible routine to register all the default
* scanners. The code is distracting at best so it's been factored into this one
* file.
*
* We include it into the main program with a require statement, which
* due to the literal way PHP includes work, when done within a function gives
* us access to that function's scope.
* We are in the scope of a method inside the Luminous_ object, so we refer to
* $this as being the $luminous_ singleton object.
*/
$language_dir = luminous::root() . '/languages/';
// this is a dummy file which includes ECMAScript dependencies in a
// non-circular way.
$this->scanners->AddScanner('ecma-includes', null, null,
"$language_dir/include/ecma.php");
$this->scanners->AddScanner(array('ada', 'adb', 'ads'),
'LuminousAdaScanner', 'Ada', "$language_dir/ada.php");
$this->scanners->AddScanner(array('as', 'actionscript'),
'LuminousActionScriptScanner', 'ActionScript', "$language_dir/as.php",
'ecma');
$this->scanners->AddScanner(array('bnf'),
'LuminousBNFScanner', 'Backus Naur Form', "$language_dir/bnf.php");
$this->scanners->AddScanner(array('bash', 'sh'),
'LuminousBashScanner', 'Bash', "$language_dir/bash.php");
$this->scanners->AddScanner(array('c', 'cpp', 'h', 'hpp', 'cxx', 'hxx'),
'LuminousCppScanner', 'C/C++', "$language_dir/cpp.php");
$this->scanners->AddScanner(array('cs', 'csharp', 'c#'),
'LuminousCSharpScanner', 'C#', "$language_dir/csharp.php");
$this->scanners->AddScanner('css',
'LuminousCSSScanner', 'CSS', "$language_dir/css.php");
$this->scanners->AddScanner(array('diff', 'patch'),
'LuminousDiffScanner', 'Diff', "$language_dir/diff.php");
$this->scanners->AddScanner(array('prettydiff', 'prettypatch',
'diffpretty', 'patchpretty'),
'LuminousPrettyDiffScanner', 'Diff-Pretty', "$language_dir/diff.php");
$this->scanners->AddScanner(array('html', 'htm'),
'LuminousHTMLScanner', 'HTML', "$language_dir/html.php",
array('js', 'css'));
$this->scanners->AddScanner(array('ecma', 'ecmascript'),
'LuminousECMAScriptScanner', 'ECMAScript',
"$language_dir/ecmascript.php", 'ecma-includes');
$this->scanners->AddScanner(array('erlang', 'erl', 'hrl'),
'LuminousErlangScanner', 'Erlang', "$language_dir/erlang.php");
$this->scanners->AddScanner('go', 'LuminousGoScanner', 'Go',
"$language_dir/go.php");
$this->scanners->AddScanner(array('groovy'),
'LuminousGroovyScanner', 'Groovy',
"$language_dir/groovy.php");
$this->scanners->AddScanner(array('haskell', 'hs'),
'LuminousHaskellScanner', 'Haskell', "$language_dir/haskell.php");
$this->scanners->AddScanner('java',
'LuminousJavaScanner', 'Java', "$language_dir/java.php");
$this->scanners->AddScanner(array('js', 'javascript'),
'LuminousJavaScriptScanner', 'JavaScript', "$language_dir/javascript.php",
array('ecma'));
$this->scanners->AddScanner('json',
'LuminousJSONScanner', 'JSON', "$language_dir/json.php");
$this->scanners->AddScanner(array('latex', 'tex'),
'LuminousLatexScanner', 'LaTeX', "$language_dir/latex.php");
$this->scanners->AddScanner(array('lolcode', 'lolc', 'lol'),
'LuminousLOLCODEScanner', 'LOLCODE', "$language_dir/lolcode.php");
$this->scanners->AddScanner(array('m', 'matlab'),
'LuminousMATLABScanner', 'MATLAB', "$language_dir/matlab.php");
$this->scanners->AddScanner(array('perl', 'pl', 'pm'),
'LuminousPerlScanner', 'Perl', "$language_dir/perl.php");
$this->scanners->AddScanner(array('rails','rhtml', 'ror'),
'LuminousRailsScanner', 'Ruby on Rails',
"$language_dir/rails.php", array('ruby', 'html'));
$this->scanners->AddScanner(array('ruby','rb'),
'LuminousRubyScanner', 'Ruby', "$language_dir/ruby.php");
$this->scanners->AddScanner(array('plain', 'text', 'txt'),
'LuminousIdentityScanner', 'Plain', "$language_dir/identity.php");
// PHP Snippet does not require an initial <?php tag to begin highlighting
$this->scanners->AddScanner('php_snippet', 'LuminousPHPSnippetScanner',
'PHP Snippet', "$language_dir/php.php", array('html'));
$this->scanners->AddScanner('php',
'LuminousPHPScanner', 'PHP', "$language_dir/php.php",
array('html'));
$this->scanners->AddScanner(array('python', 'py'),
'LuminousPythonScanner', 'Python', "$language_dir/python.php");
$this->scanners->AddScanner(array('django', 'djt'),
'LuminousDjangoScanner', 'Django', "$language_dir/python.php",
array('html')
);
$this->scanners->AddScanner(array('scala', 'scl'),
'LuminousScalaScanner', 'Scala', "$language_dir/scala.php", 'xml');
$this->scanners->AddScanner('scss',
'LuminousSCSSScanner', 'SCSS', "$language_dir/scss.php");
$this->scanners->AddScanner(array('sql', 'mysql'),
'LuminousSQLScanner', 'SQL', "$language_dir/sql.php");
$this->scanners->AddScanner(array('vim', 'vimscript'),
'LuminousVimScriptScanner', 'Vim Script', "$language_dir/vim.php");
$this->scanners->AddScanner(array('vb', 'bas'),
'LuminousVBScanner', 'Visual Basic', "$language_dir/vb.php",
'xml');
$this->scanners->AddScanner('xml', 'LuminousXMLScanner',
'XML', "$language_dir/xml.php", 'html');
$this->scanners->SetDefaultScanner('plain');

626
3rdparty/luminous/src/luminous.php vendored Executable file
View File

@ -0,0 +1,626 @@
<?php
require_once(dirname(__FILE__) . '/debug.php');
require_once(dirname(__FILE__) . '/options.class.php');
require_once(dirname(__FILE__) . '/cache/cache.class.php');
require_once(dirname(__FILE__) . '/cache/fscache.class.php');
require_once(dirname(__FILE__) . '/cache/sqlcache.class.php');
require_once(dirname(__FILE__) . '/scanners.class.php');
require_once(dirname(__FILE__) . '/formatters/formatter.class.php');
require_once(dirname(__FILE__) . '/core/scanner.class.php');
// updated automatically, use single quotes, keep single line
define('LUMINOUS_VERSION', 'v0.7.0');
/*
* This file contains the public calling interface for Luminous. It's split
* into two classes: one is basically the user-interface, the other is a
* wrapper around it. The wrapper allows a single-line function call, and is
* procedural. It's wrapped in an abstract class for a namespace.
* The real class is instantiated into a singleton which is manipulated by
* the abstract class methods.
*/
/**
* @cond ALL
* This is kind of a pseudo-UI class. It's a singleton which will be
* manipulated by a few procedural functions, for ease of use.
* It's technically supposed to be private to this class, but this is a sort
* of 'agreed' privateness, and we expose an instance of it globally.
*
* In fact, it is used by (at least) the diff scanner, which uses its
* scanner table.
*
* @internal
*
*/
class _Luminous {
/// Settings array
/// @see LuminousOptions
public $settings;
public $scanners; ///< the scanner table
public $cache = null;
/**
* The language is passed to the formatter which may choose to do something
* interesting with it. If you use the scanners table this can be figured out
* automatically, but if you pass in your own scanner, you will need to
* give a language name if you want the formatter to consider it.
*/
public $language = null;
public function __construct() {
$this->scanners = new LuminousScanners();
$this->register_default_scanners();
}
/// registers builtin scanners
private function register_default_scanners() {
$this->settings = new LuminousOptions();
require dirname(__FILE__) . '/load_scanners.php';
}
/**
* Returns an instance of the current formatter
*/
function get_formatter() {
$fmt_path = dirname(__FILE__) . '/formatters/';
$fmt = $this->settings->format;
$formatter = null;
if (!is_string($fmt) && is_subclass_of($fmt, 'LuminousFormatter')) {
$formatter = clone $fmt;
} elseif ($fmt === 'html') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTML();
} elseif ($fmt === 'html-inline') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTMLInline();
} elseif ($fmt === 'html-full') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTMLFullPage();
} elseif($fmt === 'latex') {
require_once($fmt_path . 'latexformatter.class.php');
$formatter = new LuminousFormatterLatex();
} elseif($fmt === null || $fmt === 'none') {
require_once($fmt_path . 'identityformatter.class.php');
$formatter = new LuminousIdentityFormatter();
}
if ($formatter === null) {
throw new Exception('Unknown formatter: ' . $this->settings->format);
return null;
}
$this->set_formatter_options($formatter);
return $formatter;
}
/**
* Sets up a formatter instance according to our current options/settings
*/
private function set_formatter_options(&$formatter) {
$formatter->wrap_length = $this->settings->wrap_width;
$formatter->line_numbers = $this->settings->line_numbers;
$formatter->start_line = $this->settings->start_line;
$formatter->link = $this->settings->auto_link;
$formatter->height = $this->settings->max_height;
$formatter->strict_standards = $this->settings->html_strict;
$formatter->set_theme(luminous::theme($this->settings->theme));
$formatter->highlight_lines = $this->settings->highlight_lines;
$formatter->language = $this->language;
}
/**
* calculates a 'cache_id' for the input. This is dependent upon the
* source code and the settings. This should be (near-as-feasible) unique
* for any cobmination of source, language and settings
*/
private function cache_id($scanner, $source) {
// to figure out the cache id, we mash a load of stuff together and
// md5 it. This gives us a unique (assuming no collisions) handle to
// a cache file, which depends on the input source, the relevant formatter
// settings, the version, and scanner.
$settings = array($this->settings->wrap_width,
$this->settings->line_numbers,
$this->settings->start_line,
$this->settings->auto_link,
$this->settings->max_height,
$this->settings->format,
$this->settings->theme,
$this->settings->html_strict,
LUMINOUS_VERSION,
);
$id = md5($source);
$id = md5($id . serialize($scanner));
$id = md5($id . serialize($settings));
return $id;
}
/**
* The real highlighting function
* @throw InvalidArgumentException if $scanner is not either a string or a
* LuminousScanner instance, or if $source is not a string.
*/
function highlight($scanner, $source, $settings = null) {
$old_settings = null;
if ($settings !== null) {
if (!is_array($settings)) {
throw new Exception('Luminous internal error: Settings is not an array');
}
$old_settings = clone $this->settings;
foreach($settings as $k=>$v) {
$this->settings->set($k, $v);
}
}
$should_reset_language = false;
$this->cache = null;
if (!is_string($source)) throw new InvalidArgumentException('Non-string '
. 'supplied for $source');
if (!($scanner instanceof LuminousScanner)) {
if (!is_string($scanner)) throw new InvalidArgumentException('Non-string
or LuminousScanner instance supplied for $scanner');
$code = $scanner;
$scanner = $this->scanners->GetScanner($code);
if ($scanner === null) throw new Exception("No known scanner for '$code' and no default set");
$should_reset_language = true;
$this->language = $this->scanners->GetDescription($code);
}
$cache_hit = true;
$out = null;
if ($this->settings->cache) {
$cache_id = $this->cache_id($scanner, $source);
if ($this->settings->sql_function !== null) {
$this->cache = new LuminousSQLCache($cache_id);
$this->cache->set_sql_function($this->settings->sql_function);
} else {
$this->cache = new LuminousFileSystemCache($cache_id);
}
$this->cache->set_purge_time($this->settings->cache_age);
$out = $this->cache->read();
}
if ($out === null) {
$cache_hit = false;
$out_raw = $scanner->highlight($source);
$formatter = $this->get_formatter();
$out = $formatter->format($out_raw);
}
if ($this->settings->cache && !$cache_hit) {
$this->cache->write($out);
}
if ($should_reset_language) $this->language = null;
if ($old_settings !== null) {
$this->settings = $old_settings;
}
return $out;
}
}
/// @endcond
// ends ALL
// Here's our singleton.
global $luminous_; // sometimes need this or the object seems to disappear
$luminous_ = new _Luminous();
/// @cond USER
// here's our 'real' UI class, which uses the above singleton. This is all
// static because these are actually procudural functions, we're using the
// class as a namespace.
/**
* @brief Users' API
*/
abstract class luminous {
/**
* @brief Highlights a string according to the current settings
*
* @param $scanner The scanner to use, this can either be a langauge code,
* or it can be an instance of LuminousScanner.
* @param $source The source string
* @param $cache Whether or not to use the cache
* @return the highlighted source code.
*
* To specify different output formats or other options, see set().
*/
static function highlight($scanner, $source, $cache_or_settings=null) {
global $luminous_;
try {
$settings = null;
if (is_bool($cache_or_settings)) {
$settings = array('cache' => $cache_or_settings);
} else if (is_array($cache_or_settings)) {
$settings = $cache_or_settings;
}
$h = $luminous_->highlight($scanner, $source, $settings);
if ($luminous_->settings->verbose) {
$errs = self::cache_errors();
if (!empty($errs)) {
trigger_error("Le cache de Luminous a &eacute;t&eacute; desactiv&eacute; en raison du manque de permission d'&eacute;criture. \n" .
'See luminous::cache_errors() for details.');
}
}
return $h;
} catch (InvalidArgumentException $e) {
// this is a user error, let it bubble
//FIXME how do we let it bubble without throwing? the stack trace will
// be wrong.
throw $e;
}
catch (Exception $e) {
// this is an internal error or a scanner error, or something
// it might not technically be Luminous that caused it, but let's not
// make it kill the whole page in production code
if (LUMINOUS_DEBUG) throw $e;
else {
$return = $source;
if (($t = self::setting('failure-tag')))
$return = "<$t>$return</$t>";
return $return;
}
}
}
/**
* @brief Highlights a file according to the current setings.
*
* @param $scanner The scanner to use, this can either be a langauge code,
* or it can be an instance of LuminousScanner.
* @param $file the source string
* @param $cache Whether or not to use the cache
* @return the highlighted source code.
*
* To specify different output formats or other options, see set().
*/
static function highlight_file($scanner, $file, $cache_or_settings=null) {
return self::highlight($scanner, file_get_contents($file), $cache_or_settings);
}
/**
* @brief Returns a list of cache errors encountered during the most recent highlight
*
* @return An array of errors the cache encountered (which may be empty),
* or @c FALSE if the cache is not enabled
*/
static function cache_errors() {
global $luminous_;
$c = $luminous_->cache;
if ($c === null) return FALSE;
return $c->errors();
}
/**
* @brief Registers a scanner
*
* Registers a scanner with Luminous's scanner table. Utilising this
* function means that Luminous will handle instantiation and inclusion of
* the scanner's source file in a lazy-manner.
*
* @param $language_code A string or array of strings designating the
* aliases by which this scanner may be accessed
* @param $classname The class name of the scanner, as string. If you
* leave this as 'null', it will be treated as a dummy file (you can use
* this to handle a set of non-circular include rules, if you run into
* problems).
* @param $readable_language A human readable language name
* @param $path The path to the source file containing your scanner
* @param dependencies An array of other scanners which this scanner
* depends on (as sub-scanners, or superclasses). Each item in the
* array should be a $language_code for another scanner.
*/
static function register_scanner($language_code, $classname,
$readable_language, $path, $dependencies=null) {
global $luminous_;
$luminous_->scanners->AddScanner($language_code, $classname,
$readable_language, $path, $dependencies);
}
/**
* @brief Get the full filesystem path to Luminous
* @return what Luminous thinks its location is on the filesystem
* @internal
*/
static function root() {
return realpath(dirname(__FILE__) . '/../');
}
/**
* @brief Gets a list of installed themes
*
* @return the list of theme files present in style/.
* Each theme will simply be a filename, and will end in .css, and will not
* have any directory prefix.
*/
static function themes() {
$themes_uri = self::root() . '/style/';
$themes = array();
foreach(glob($themes_uri . '/*.css') as $css) {
$fn = trim(preg_replace("%.*/%", '', $css));
switch($fn) {
// these are special, exclude these
case 'luminous.css':
case 'luminous_print.css':
case 'luminous.min.css':
continue;
default:
$themes[] = $fn;
}
}
return $themes;
}
/**
* @brief Checks whether a theme exists
* @param $theme the name of a theme, which should be suffixed with .css
* @return @c TRUE if a theme exists in style/, else @c FALSE
*/
static function theme_exists($theme) {
return in_array($theme, self::themes());
}
/**
* @brief Reads a CSS theme file
* Gets the CSS-string content of a theme file.
* Use this function for reading themes as it involves security
* checks against reading arbitrary files
*
* @param $theme The name of the theme to retrieve, which may or may not
* include the .css suffix.
* @return the content of a theme; this is the actual CSS text.
* @internal
*/
static function theme($theme) {
if (!preg_match('/\.css$/i', $theme)) $theme .= '.css';
if (self::theme_exists($theme))
return file_get_contents(self::root() . "/style/" . $theme);
else
throw new Exception('No such theme file: ' . $theme);
}
/**
* @brief Gets a setting's value
* @param $option The name of the setting (corresponds to an attribute name
* in LuminousOptions)
* @return The value of the given setting
* @throws Exception if the option is unrecognised
*
* Options are stored in LuminousOptions, which provides documentation of
* each option.
* @see LuminousOptions
*/
static function setting($option) {
global $luminous_;
$option = str_replace('-', '_', $option);
return $luminous_->settings->$option;
}
/**
* @brief Sets the given option to the given value
* @param $option The name of the setting (corresponds to an attribute name
* in LuminousOptions)
* @param $value The new value of the setting
* @throws Exception if the option is unrecognised (and in various other
* validation failures),
* @throws InvalidArgumentException if the argument fails the type-validation
* check
*
* @note This function can also accept multiple settings if $option is a
* map of option_name=>value
*
* Options are stored in LuminousOptions, which provides documentation of
* each option.
*
* @note as of 0.7 this is a thin wrapper around LuminousOptions::set()
*
* @see LuminousOptions::set
*/
static function set($option, $value=null) {
global $luminous_;
$luminous_->settings->set($option, $value);
}
/**
* @brief Gets a list of registered scanners
*
* @return a list of scanners currently registered. The list is in the
* format:
*
* language_name => codes,
*
* where language_name is a string, and codes is an array of strings.
*
* The array is sorted alphabetically by key.
*/
static function scanners() {
global $luminous_;
$scanners = $luminous_->scanners->ListScanners();
ksort($scanners);
return $scanners;
}
/**
* @brief Gets a formatter instance
*
* @return an instance of a LuminousFormatter according to the current
* format setting
*
* This shouldn't be necessary for general usage, it is only implemented
* for testing.
* @internal
*/
static function formatter() {
global $luminous_;
return $luminous_->get_formatter();
}
/**
* @internal
* Comparison function for guess_language()'s sorting
*/
static function __guess_language_cmp($a, $b) {
$c = $a['p'] - $b['p'];
if ($c === 0) return 0;
elseif ($c < 0) return -1;
else return 1;
}
/**
* @brief Attempts to guess the language of a piece of source code
* @param $src The source code whose language is to be guessed
* @param $confidence The desired confidence level: if this is 0.05 but the
* best guess has a confidence of 0.04, then $default is returned. Note
* that the confidence level returned by scanners is quite arbitrary, so
* don't set this to '1' thinking that'll give you better results.
* A realistic confidence is likely to be quite low, because a scanner will
* only return 1 if it's able to pick out a shebang (#!) line or something
* else definitive. If there exists no such identifier, a 'strong'
* confidence which is right most of the time might be as low as 0.1.
* Therefore it is recommended to keep this between 0.01 and 0.10.
* @param $default The default name to return in the event that no scanner
* thinks this source belongs to them (at the desired confidence).
*
* @return A valid code for the best scanner, or $default.
*
* This is a wrapper around luminous::guess_language_full
*/
static function guess_language($src, $confidence=0.05, $default = 'plain') {
$guess = self::guess_language_full($src);
if ($guess[0]['p'] >= $confidence)
return $guess[0]['codes'][0];
else
return $default;
}
/**
* @brief Attempts to guess the language of a piece of source code
* @param $src The source code whose language is to be guessed
* @return An array - the array is ordered by probability, with the most
* probable language coming first in the array.
* Each array element is an array which represents a language (scanner),
* and has the keys:
* \li \c 'language' => Human-readable language description,
* \li \c 'codes' => valid codes for the language (array),
* \li \c 'p' => the probability (between 0.0 and 1.0 inclusive),
*
* note that \c 'language' and \c 'codes' are the key => value pair from
* luminous::scanners()
*
* @warning Language guessing is inherently unreliable but should be right
* about 80% of the time on common languages. Bear in mind that guessing is
* going to be severely hampered in the case that one language is used to
* generate code in another language.
*
* Usage for this function will be something like this:
* @code
* $guesses = luminous::guess_language($src);
* $output = luminous::highlight($guesses[0]['codes'][0], $src);
* @endcode
*
* @see luminous::guess_language
*/
static function guess_language_full($src) {
global $luminous_;
// first we're going to make an 'info' array for the source, which
// precomputes some frequently useful things, like how many lines it
// has, etc. It prevents scanners from redundantly figuring these things
// out themselves
$lines = preg_split("/\r\n|[\r\n]/", $src);
$shebang = '';
if (preg_match('/^#!.*/', $src, $m)) $shebang = $m[0];
$info = array(
'lines' => $lines,
'num_lines' => count($lines),
'trimmed' => trim($src),
'shebang' => $shebang
);
$return = array();
foreach(self::scanners() as $lang=>$codes) {
$scanner_name = $luminous_->scanners->GetScanner($codes[0], false,
false);
assert($scanner_name !== null);
$return[] = array(
'language' => $lang,
'codes' => $codes,
'p' => call_user_func(array($scanner_name, 'guess_language'), $src,
$info)
);
}
uasort($return, array('luminous', '__guess_language_cmp'));
$return = array_reverse($return);
return $return;
}
/**
* @brief Gets the markup you need to include in your web page
* @return a string representing everything that needs to be printed in
* the \<head\> section of a website.
*
* This is influenced by the following settings:
* relative-root,
* include-javascript,
* include-jquery
* theme
*/
static function head_html() {
global $luminous_;
$theme = self::setting('theme');
$relative_root = self::setting('relative-root');
$js = self::setting('include-javascript');
$jquery = self::setting('include-jquery');
if (!preg_match('/\.css$/i', $theme)) $theme .= '.css';
if (!self::theme_exists($theme)) $theme = 'luminous_light.css';
if ($relative_root === null) {
$relative_root = str_replace($_SERVER['DOCUMENT_ROOT'], '/',
dirname(__FILE__));
$relative_root = str_replace('\\', '/', $relative_root); // bah windows
$relative_root = rtrim($relative_root, '/');
// go up one level.
$relative_root = preg_replace('%/[^/]*$%', '', $relative_root);
}
// if we ended up with any double slashes, let's zap them, and also
// trim any trailing ones
$relative_root = preg_replace('%(?<!:)//+%', '/', $relative_root);
$relative_root = rtrim($relative_root, '/');
$out = '';
$link_template = "<link rel='stylesheet' type='text/css' href='$relative_root/style/%s' id='%s'>\n";
$script_template = "<script type='text/javascript' src='$relative_root/client/%s'></script>\n";
$out .= sprintf($link_template, 'luminous.css', 'luminous-style');
$out .= sprintf($link_template, $theme, 'luminous-theme');
if ($js) {
if ($jquery)
$out .= sprintf($script_template, 'jquery-1.6.4.min.js');
$out .= sprintf($script_template, 'luminous.js');
}
return $out;
}
}
/// @endcond
// ends user

342
3rdparty/luminous/src/options.class.php vendored Executable file
View File

@ -0,0 +1,342 @@
<?php
/**
* @cond USER
*
* @brief Options class.
*
* @warning This object's structure isn't guaranteed to be stable so don't read
* or write these directly. As a user, you should be using luminous::set()
* and luminous::setting()
*
* We use a fair bit of PHP trickery in the implementation here. The keener
* among you will notice that the options are all private: don't worry about
* that. We override the __set() method to apply option specific validation.
* Options can be written to as normal.
*
* The option variable names correspond with option strings that can be passed
* through luminous::set(), however, for historical reasons, underscores can be
* replaced with dashed in the call.
*/
class LuminousOptions {
/**
* @brief Whether to use the built-in cache
*/
private $cache = true;
/**
* @brief Maximum age of cache files in seconds
*
* Cache files which have not been read for this length of time will be
* removed from the file system. The file's 'mtime' is used to calculate
* when it was last used, and a cache hit triggers a 'touch'
*
* Set to -1 or 0 to disable cache purges
*/
private $cache_age = 7776000; // 90 days
/**
* @brief Word wrapping
*
* If the formatter supports line wrapping, lines will be wrapped at
* this number of characters (0 or -1 to disable)
*/
private $wrap_width = -1;
/**
* @brief Line numbering
*
* If the formatter supports line numbering, this setting controls whether
* or not lines should be numbered
*/
private $line_numbers = true;
/**
* @brief Line number of first line
*
* If the formatter supports line numbering, this setting controls number
* of the first line
*/
private $start_line = 1;
/**
* @brief Highlighting of lines
*
* If the formatter supports highlighting lines, this setting allows
* the caller to specify the set of line numbers to highlight
*/
private $highlight_lines = array();
/**
* @brief Hyperlinking
*
* If the formatter supports hyper-linking, this setting controls whether
* or not URLs will be automatically linked
*/
private $auto_link = true;
/**
* @brief Widget height constraint
*
* If the formatter supports heigh constraint, this setting controls whether
* or not to constrain the widget's height, and to what.
*/
private $max_height = -1;
/**
* @brief Output format
*
* Chooses which output format to use. Current valid settings are:
* \li 'html' - standard HTML element, contained in a \<div\> with class 'luminous',
* CSS is not included and must be included on the page separately
* (probably with luminous::head_html())
* \li 'html-full' - A complete HTML document. CSS is included.
* \li 'html-inline' - Very similar to 'html' but geared towards inline display.
* Probably not very useful.
* \li 'latex' - A full LaTeX document
* \li 'none' or \c NULL - No formatter. Internal XML format is returned.
* You probably don't want this.
*/
private $format = 'html';
/**
* @brief Theme
*
* The default theme to use. This is observed by the HTML-full and LaTeX
* formatters, it is also read by luminous::head_html().
*
* This should be a valid theme which exists in style/
*/
private $theme = 'luminous_light.css';
/**
* @brief HTML strict standards mode
*
* The HTML4-strict doctype disallows a few things which are technically
* useful. Set this to true if you don't want Luminous to break validation
* on your HTML4-strict document. Luminous should be valid
* HTML4 loose/transitional and HTML5 without needing to enable this.
*/
private $html_strict = false;
/**
* @brief Location of Luminous relative to your document root
*
* If you use luminous::head_html(), it has to try to figure out the
* path to the style/ directory so that it can return a correct URL to the
* necessary stylesheets. Luminous may get this wrong in some situations,
* specifically it is currently impossible to get this right if Luminous
* exists on the filesystem outside of the document root, and you have used
* a symbolic link to put it inside. For this reason, this setting allows you
* to override the path.
*
* e.g. If you set this to '/extern/highlighter', the stylesheets will be
* linked with
* \<link rel='stylesheet' href='/extern/highlighter/style/luminous.css'\>
*
*/
private $relative_root = null;
/**
* @brief JavaScript extras
*
* controls whether luminous::head_html() outputs the javascript 'extras'.
*/
private $include_javascript = false;
/**
* @brief jQuery
*
* Controls whether luminous::head_html() outputs jQuery, which is required
* for the JavaScript extras. This has no effect if $include_javascript is
* false.
*/
private $include_jquery = false;
/**
* @brief Failure recovery
*
* If Luminous hits some kind of unrecoverable internal error, it should
* return the input source code back to you. If you want, it can be
* wrapped in an HTML tag. Hopefully you will never see this.
*/
private $failure_tag = 'pre';
/**
* @brief Defines an SQL function which can execute queries on a database
*
* An SQL database can be used as a replacement for the file-system cache
* database.
* This function should act similarly to the mysql_query function:
* it should take a single argument (the query string) and return:
* @li boolean @c false if the query fails
* @li boolean @c true if the query succeeds but has no return value
* @li An array of associative arrays if the query returns rows (each
* element is a row, and each row is an map keyed by field name)
*/
private $sql_function = null;
private $verbose = true;
public function LuminousOptions($opts=null) {
if (is_array($opts)) {
$this->set($opts);
}
}
public function set($nameOrArray, $value=null) {
$array = $nameOrArray;
if (!is_array($array)) {
$array = array($nameOrArray => $value);
}
foreach($array as $option => $value) {
// for backwards compatibility we need to do this here
$option = str_replace('-', '_', $option);
$this->__set($option, $value);
}
}
private static function check_type($value, $type, $nullable=false) {
if ($nullable && $value === null) return true;
$func = null;
if ($type === 'string') $func = 'is_string';
elseif($type === 'int') $func = 'is_int';
elseif($type === 'numeric') $func = 'is_numeric';
elseif($type === 'bool') $func = 'is_bool';
elseif($type === 'func') $func = 'is_callable';
elseif($type === 'array') $func = 'is_array';
else {
assert(0);
return true;
}
$test = call_user_func($func, $value);
if (!$test) {
throw new InvalidArgumentException('Argument should be type ' . $type .
($nullable? ' or null' : ''));
}
return $test;
}
public function __get($name) {
if (property_exists($this, $name))
return $this->$name;
else {
throw new Exception('Unknown property: ' . $name);
}
}
public function __set($name, $value) {
if ($name === 'auto_link')
$this->set_bool($name, $value);
else if ($name === 'cache') {
$this->set_bool($name, $value);
}
elseif($name === 'cache_age') {
if (self::check_type($value, 'int')) $this->$name = $value;
}
elseif($name === 'failure_tag') {
if (self::check_type($value, 'string', true)) $this->$name = $value;
}
elseif($name === 'format')
$this->set_format($value);
elseif($name === 'html_strict') {
if (self::check_type($value, 'bool')) $this->$name = $value;
}
elseif($name === 'include_javascript' || $name === 'include_jquery')
$this->set_bool($name, $value);
elseif($name === 'line_numbers')
$this->set_bool($name, $value);
elseif($name === 'start_line')
$this->set_start_line($value);
elseif($name === 'highlight_lines') {
if (self::check_type($value, 'array'))
$this->highlight_lines = $value;
}
elseif($name === 'max_height')
$this->set_height($value);
elseif($name === 'relative_root') {
if (self::check_type($value, 'string', true)) $this->$name = $value;
}
elseif($name === 'theme')
$this->set_theme($value);
elseif($name === 'wrap_width') {
if (self::check_type($value, 'int')) $this->$name = $value;
}
elseif($name === 'sql_function') {
if (self::check_type($value, 'func', true)) $this->$name = $value;
}
elseif ($name === 'verbose') {
$this->set_bool($name, $value);
}
else {
throw new Exception('Unknown option: ' . $name);
}
}
private function set_bool($key, $value) {
if (self::check_type($value, 'bool')) {
$this->$key = $value;
}
}
private function set_string($key, $value, $nullable=false) {
if (self::check_type($value, 'string', $nullable)) {
$this->$key = $value;
}
}
private function set_start_line($value) {
if (is_numeric($value) && $value > 0) {
$this->start_line = $value;
} else {
throw new InvalidArgumentException('Start line must be a positive number');
}
}
private function set_format($value) {
// formatter can either be an instance or an identifier (string)
$is_obj = $value instanceof LuminousFormatter;
if($is_obj || self::check_type($value, 'string', true)) {
// validate the string is a known type
if (!$is_obj && !in_array($value, array('html', 'html-full',
'html-inline', 'latex', 'none', null), true)) {
throw new Exception('Invalid formatter: ' . $value);
}
else {
$this->format = $value;
}
}
}
private function set_theme($value) {
if (self::check_type($value, 'string')) {
if (!preg_match('/\.css$/', $value)) $value .= '.css';
if (!luminous::theme_exists($value)) {
throw new Exception('No such theme: '
. luminous::root() . '/style/' . $value);
}
else $this->theme = $value;
}
}
private function set_height($value) {
// height should be either a number or a numeric string with some units at
// the end
if (is_numeric($value)
|| (is_string($value) && preg_match('/^\d+/', $value))
) {
$this->max_height = $value;
}
else {
throw new InvalidArgumentException('Unrecognised format for height');
}
}
}
/// @endcond

221
3rdparty/luminous/src/scanners.class.php vendored Executable file
View File

@ -0,0 +1,221 @@
<?php
/// \cond ALL
/**
* \file scanners.class.php
* \brief Scanner lookup table definition.
*/
/**
* \class LuminousScanners
* \author Mark Watkinson
* \brief A glorified lookup table for languages to scanners.
* One of these is instantiated in the global scope at the bottom of this source.
* The parser assumes it to exist and uses it to look up scanners.
* Users seeking to override scanners or add new scanners should add their
* scanner into '$luminous_scanners'.
*
*/
class LuminousScanners
{
private $lookup_table = array(); /**<
The language=>scanner lookup table. Scanner is an array with keys:
scanner (the string of the scanner's class name),
file (the path to the file in which its definition resides)
dependencies (the language name for any scanners it this scanner
either depends on or needs to instantiate itself)
*/
private $default_scanner = null; /**<
Language name of the default scanner to use if none is found
for a particular language */
private $descriptions = array();
private $resolved_dependencies = array();
/**
* Adds a scanner into the table, or overwrites an existing scanner.
*
* \param language_name may be either a string or an array of strings, if
* multiple languages are to use the same scanner
* \param $scanner the name of the LuminousScanner object as string, (not an
* actual instance!). If the file is actually a dummy file (say for includes),
* leave $scanner as \c null.
* \param lang_description a human-readable description of the language.
* \param file the path to the file in which the scanner is defined.
* \param dependencies optional, a string or array of strings representing
* the language names (given in another call to AddScanner, as
* language_name), on which the instantiation of this scanner depends.
* i.e. any super-classes, and any classes which this scanner instantiates
* itself.
*
*/
public function AddScanner($language_name, $scanner,
$lang_description, $file=null, $dependencies=null)
{
$dummy = $scanner === null;
$d = array();
if (is_array($dependencies))
$d = $dependencies;
elseif ($dependencies !== null)
$d = array($dependencies);
$insert = array('scanner'=>$scanner,
'file'=>$file,
'dependencies'=>$d,
'description'=>$lang_description
);
if (!is_array($language_name))
$language_name = array($language_name);
foreach($language_name as $l) {
$this->lookup_table[$l] = $insert;
if (!$dummy)
$this->AddDescription($lang_description, $l);
}
}
private function AddDescription($language_name, $language_code)
{
if (!isset($this->descriptions[$language_name]))
$this->descriptions[$language_name] = array();
$this->descriptions[$language_name][] = $language_code;
}
private function UnsetDescription($language_name)
{
foreach($this->descriptions as &$d)
{
foreach($d as $k=>$l)
{
if($l === $language_name)
unset($d[$k]);
}
}
}
/**
* Removes a scanner from the table
*
* \param language_name may be either a string or an array of strings, each of
* which will be removed from the lookup table.
*/
public function RemoveScanner($language_name)
{
if (is_array($language_name))
{
foreach($language_name as $l)
{
unset($this->lookup_table[$l]);
$this->UnsetDescription($l);
}
}
else
{
$this->UnsetDescription($language_name);
unset($this->lookup_table[$language_name]);
}
}
/**
* Sets the default scanner. This is used when none matches a lookup
* \param scanner the LuminousScanner object
*/
public function SetDefaultScanner($scanner)
{
$this->default_scanner = $scanner;
}
/**
* Method which retrives the desired scanner array, and
* recursively settles the include dependencies while doing so.
* \param language_name the name under which the gramar was originally indexed
* \param default if true: if the scanner doesn't exist, return the default
* scanner. If false, return false
* \return the scanner-array stored for the given language name
* \internal
* \see LuminousScanners::GetScanner
*/
private function GetScannerArray($language_name, $default=true)
{
$g = null;
if (array_key_exists($language_name, $this->lookup_table))
$g = $this->lookup_table[$language_name];
elseif($this->default_scanner !== null && $default === true)
$g = $this->lookup_table[$this->default_scanner];
if ($g === null)
return false;
// Break on circular dependencies.
if (!isset($this->resolved_dependencies[$language_name]))
{
$this->resolved_dependencies[$language_name] = true;
foreach($g['dependencies'] as $d)
{
$this->GetScannerArray($d, $default);
}
if ($g['file'] !== null)
require_once($g['file']);
}
return $g;
}
/**
* Returns a scanner for a language
* \param language_name the name under which the gramar was originally indexed
* \param default if true: if the scanner doesn't exist, return the default
* scanner. If false, return false
* \return The scanner, the default scanner, or null.
*/
function GetScanner($language_name, $default=true, $instance=true)
{
$resolved_dependencies = array();
$g = $this->GetScannerArray($language_name, $default);
$resolved_dependencies = array();
if ($g !== false) {
return $instance? new $g['scanner'] : $g['scanner'];
}
return null;
}
function GetDescription($language_name) {
$resolved_dependencies = array();
$g = $this->GetScannerArray($language_name, true);
$resolved_dependencies = array();
if ($g !== false) {
return $g['description'];
} else return null;
}
/**
* Returns a list of known aliases for scanners.
* \return a list, the list is such that each item is itself a list whose
* elements are aliases of the same scanner. eg:
* [
* ['c', 'cpp'],
* ['java'],
* ['py', 'python']
* ]
* etc.
*
*/
function ListScanners()
{
$l = $this->descriptions;
return $l;
}
}
/// \endcond

View File

@ -0,0 +1,386 @@
<?php
/// \cond ALL
/*
* This is a simple CSS parser, which we use to make CSS themes portable.
* The basic idea is we're going to use the CSS scanner to tokenize the
* input, then we're going to parse the tokens.
* There is some amount of redundancy here with the scanner, but this way
* means that we are 1) not dependent too much on the implementation of
* the scanner, and 2) not having to write our own with full pattern matching.
*
*/
require_once dirname(__FILE__) . '/../luminous.php';
// source: http://www.w3schools.com/css/css_colornames.asp
global $luminous_col2hex;
$luminous_col2hex = array(
'aliceblue' => '#f0f8ff',
'antiquewhite' => '#faebd7',
'aqua' => '#00ffff',
'aquamarine' => '#7fffd4',
'azure' => '#f0ffff',
'beige' => '#f5f5dc',
'bisque' => '#ffe4c4',
'black' => '#000000',
'blanchedalmond' => '#ffebcd',
'blue' => '#0000ff',
'blueviolet' => '#8a2be2',
'brown' => '#a52a2a',
'burlywood' => '#deb887',
'cadetblue' => '#5f9ea0',
'chartreuse' => '#7fff00',
'chocolate' => '#d2691e',
'coral' => '#ff7f50',
'cornflowerblue' => '#6495ed',
'cornsilk' => '#fff8dc',
'crimson' => '#dc143c',
'cyan' => '#00ffff',
'darkblue' => '#00008b',
'darkcyan' => '#008b8b',
'darkgoldenrod' => '#b8860b',
'darkgray' => '#a9a9a9',
'darkgrey' => '#a9a9a9',
'darkgreen' => '#006400',
'darkkhaki' => '#bdb76b',
'darkmagenta' => '#8b008b',
'darkolivegreen' => '#556b2f',
'darkorange' => '#ff8c00',
'darkorchid' => '#9932cc',
'darkred' => '#8b0000',
'darksalmon' => '#e9967a',
'darkseagreen' => '#8fbc8f',
'darkslateblue' => '#483d8b',
'darkslategray' => '#2f4f4f',
'darkslategrey' => '#2f4f4f',
'darkturquoise' => '#00ced1',
'darkviolet' => '#9400d3',
'deeppink' => '#ff1493',
'deepskyblue' => '#00bfff',
'dimgray' => '#696969',
'dimgrey' => '#696969',
'dodgerblue' => '#1e90ff',
'firebrick' => '#b22222',
'floralwhite' => '#fffaf0',
'forestgreen' => '#228b22',
'fuchsia' => '#ff00ff',
'gainsboro' => '#dcdcdc',
'ghostwhite' => '#f8f8ff',
'gold' => '#ffd700',
'goldenrod' => '#daa520',
'gray' => '#808080',
'grey' => '#808080',
'green' => '#008000',
'greenyellow' => '#adff2f',
'honeydew' => '#f0fff0',
'hotpink' => '#ff69b4',
'indianred' => '#cd5c5c',
'indigo' => '#4b0082',
'ivory' => '#fffff0',
'khaki' => '#f0e68c',
'lavender' => '#e6e6fa',
'lavenderblush' => '#fff0f5',
'lawngreen' => '#7cfc00',
'lemonchiffon' => '#fffacd',
'lightblue' => '#add8e6',
'lightcoral' => '#f08080',
'lightcyan' => '#e0ffff',
'lightgoldenrodyellow' => '#fafad2',
'lightgray' => '#d3d3d3',
'lightgrey' => '#d3d3d3',
'lightgreen' => '#90ee90',
'lightpink' => '#ffb6c1',
'lightsalmon' => '#ffa07a',
'lightseagreen' => '#20b2aa',
'lightskyblue' => '#87cefa',
'lightslategray' => '#778899',
'lightslategrey' => '#778899',
'lightsteelblue' => '#b0c4de',
'lightyellow' => '#ffffe0',
'lime' => '#00ff00',
'limegreen' => '#32cd32',
'linen' => '#faf0e6',
'magenta' => '#ff00ff',
'maroon' => '#800000',
'mediumaquamarine' => '#66cdaa',
'mediumblue' => '#0000cd',
'mediumorchid' => '#ba55d3',
'mediumpurple' => '#9370d8',
'mediumseagreen' => '#3cb371',
'mediumslateblue' => '#7b68ee',
'mediumspringgreen' => '#00fa9a',
'mediumturquoise' => '#48d1cc',
'mediumvioletred' => '#c71585',
'midnightblue' => '#191970',
'mintcream' => '#f5fffa',
'mistyrose' => '#ffe4e1',
'moccasin' => '#ffe4b5',
'navajowhite' => '#ffdead',
'navy' => '#000080',
'oldlace' => '#fdf5e6',
'olive' => '#808000',
'olivedrab' => '#6b8e23',
'orange' => '#ffa500',
'orangered' => '#ff4500',
'orchid' => '#da70d6',
'palegoldenrod' => '#eee8aa',
'palegreen' => '#98fb98',
'paleturquoise' => '#afeeee',
'palevioletred' => '#d87093',
'papayawhip' => '#ffefd5',
'peachpuff' => '#ffdab9',
'peru' => '#cd853f',
'pink' => '#ffc0cb',
'plum' => '#dda0dd',
'powderblue' => '#b0e0e6',
'purple' => '#800080',
'red' => '#ff0000',
'rosybrown' => '#bc8f8f',
'royalblue' => '#4169e1',
'saddlebrown' => '#8b4513',
'salmon' => '#fa8072',
'sandybrown' => '#f4a460',
'seagreen' => '#2e8b57',
'seashell' => '#fff5ee',
'sienna' => '#a0522d',
'silver' => '#c0c0c0',
'skyblue' => '#87ceeb',
'slateblue' => '#6a5acd',
'slategray' => '#708090',
'slategrey' => '#708090',
'snow' => '#fffafa',
'springgreen' => '#00ff7f',
'steelblue' => '#4682b4',
'tan' => '#d2b48c',
'teal' => '#008080',
'thistle' => '#d8bfd8',
'tomato' => '#ff6347',
'turquoise' => '#40e0d0',
'violet' => '#ee82ee',
'wheat' => '#f5deb3',
'white' => '#ffffff',
'whitesmoke' => '#f5f5f5',
'yellow' => '#ffff00',
'yellowgreen' => '#9acd32'
);
/**
* @brief Simple CSS parser to make theme files portable across output formats.
*
* This is CSS parser for making Luminous themes portable. This is not
* a general CSS parser, but could be with a bit of work!
*
* Parses CSS strings into a map of rules and values. The resulting map is
* a somewhat simplified version of CSS.
* For simplificity we re-map the following properties:
*
* background-color => bgcolor
* font-weight => bold? (bool)
* font-style => italic? (bool)
* text-decoration => underline? OR strikethrough? (bool)
*
* We also drop things like '!important'.
*
* Colours are stored as 6-digit hexadecimal strings with leading #. 3-digit
* hex strings are expanded to their 6-digit equivalents. Named colour aliases
* are replaced with their hex equivalents.
*/
class LuminousCSSParser {
private $data = array();
private static function format_property_value($prop, $value) {
global $luminous_col2hex;
// drop !important
$value = preg_replace('/\s*!important$/', '', $value);
// expand 3-digit hex
if (preg_match('/^#([a-fA-F0-9]{3})$/', $value, $m))
$value .= $m[1];
// remove quotes
$value = trim($value);
if (preg_match('/^(["\'])(.*)\\1$/', $value, $m)) $value = $m[2];
// now get it into a simpler form:
switch($prop) {
case 'color':
if (isset($luminous_col2hex[$value]))
$value = $luminous_col2hex[$value];
break;
case 'background-color':
$prop = 'bgcolor';
if (isset($luminous_col2hex[$value]))
$value = $luminous_col2hex[$value];
break;
case 'font-weight':
$prop = 'bold';
$value = in_array($value, array('bold', 'bolder', '700', '800', '900'));
break;
case 'font-style':
$prop = 'italic';
$value = in_array($value, array('italic', 'oblique'));
case 'text-decoration':
if ($value === 'line-through') {
$prop = 'strikethrough';
$value = true;
}
elseif($value === 'underline') {
$prop = 'underline';
$value = true;
}
break;
}
return array($prop, $value);
}
private static function format_css_array($css) {
$css_ = array();
// now cleanup the array, drop !important
foreach($css as $selector=>$rules) {
$rules_ = array();
foreach($rules as $prop=>$value) {
list($prop, $value) = self::format_property_value($prop, $value);
$rules_[$prop] = $value;
}
// now split selector by comma
$selectors = preg_split('/\s*,\s*/', $selector);
foreach($selectors as $s) {
// drop .luminous from the selector
$s = preg_replace('/^\.luminous\s*/', '', $s);
// now we assume that if something is in the form .classname then
// it's probably of interest, ie directly specifying a rule for a
// highlighting calss.
// and if it's not in that form them
// it's probably something else (like a rule for hyperlinks or something)
if (preg_match('/\.([\-\w]+)/', $s, $m)) $s = $m[1];
else continue;
if (!isset($css_[$s])) $css_[$s] = array();
$css_[$s] = array_merge($css_[$s], $rules_);
}
}
return $css_;
}
/**
* Returns the parsed rules. The rules are an array in the format:
*
* array(
* $rule_name => array($property => $value)
* )
*
* So, $rules['.comment']['color'] would return the color property of comment
* classes.
*/
function rules() {
return $this->data;
}
/**
* Returns the value for the given property of the given rule name, or
* returns $default.
* @param $rule_name the CSS rule name, e.g. 'a', '.luminous', etc
* @param $property the property to access, e.g. 'color'
* @param $default the value to return in the case that the rule/proeprty
* was not set. Default: null
*/
function value($rule_name, $property, $default=null) {
if (isset($this->data[$rule_name][$property]))
return $this->data[$rule_name][$property];
else return $default;
}
/**
* Converts a CSS string into a nested map of values.
* See LuminousCSSParser::rules() for the structure.
* @param $string the CSS string
* @returns the rule map
*/
function convert($string) {
$css = self::parse($string);
$data = self::format_css_array($css);
$this->data = $data;
return $data;
}
private static function parse($string) {
global $luminous_col2hex;
// singleton from usage API
global $luminous_;
$scanner = $luminous_->scanners->GetScanner('css');
$scanner->string($string);
$scanner->init();
$scanner->main();
$tokens = $scanner->token_array();
$block = false;
$expect;
$key;
$value;
$selector = '';
$css = array();
// array of selectors => rules, where rules is an array itself of (property, value)
// note this is going to get @font-face wrong, but we don't care about that.
for($i=0; $i<count($tokens); $i++) {
list($tok, $content, ) = $tokens[$i];
if ($tok === 'COMMENT') continue;
if (!$block) {
$expect = 'key';
// not in block, look for selectors.
if ($content === '{') {
$block = true;
$key = '';
$selector = trim($selector);
if (!isset($css[$selector]))
$css[$selector] = array();
}
else {
$selector .= $content;
}
continue;
}
if ($content === '}') {
$block = false;
$value = null;
$key = null;
$selector = '';
continue;
}
// expecting key, append to the key or finalise it
if ($expect === 'key') {
if ($content === ':') {
$expect = 'value';
$value = '';
}
else $key .= $content;
} elseif($expect === 'value') {
// expecting value, append to it or finalise and insert it (with the key)
if ($content === ';') {
// don't overwrite things - use the first definition
// this is for stuff like rgba which might re-define something
$key = trim($key);
$value = trim($value);
if (!isset($css[$selector][$key])) {
$css[$selector][$key] = $value;
}
$expect = 'key';
$key = '';
$value = '';
}
else $value .= $content;
}
}
return $css;
}
}
/// \endcond