1291 lines
37 KiB
PHP
1291 lines
37 KiB
PHP
<?php
|
|
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
|
|
/**
|
|
* Syntax highlighter class generator
|
|
*
|
|
* To simplify the process of creating new syntax highlighters
|
|
* for different languages, {@link Text_Highlighter_Generator} class is
|
|
* provided. It takes highlighting rules from XML file and generates
|
|
* a code of a class inherited from {@link Text_Highlighter}.
|
|
*
|
|
* PHP versions 4 and 5
|
|
*
|
|
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
|
* that is available through the world-wide-web at the following URI:
|
|
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
|
* the PHP License and are unable to obtain it through the web, please
|
|
* send a note to license@php.net so we can mail you a copy immediately.
|
|
*
|
|
* @category Text
|
|
* @package Text_Highlighter
|
|
* @author Andrey Demenev <demenev@gmail.com>
|
|
* @copyright 2004-2006 Andrey Demenev
|
|
* @license http://www.php.net/license/3_0.txt PHP License
|
|
* @version CVS: $Id$
|
|
* @link http://pear.php.net/package/Text_Highlighter
|
|
*/
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
require_once 'PEAR.php';
|
|
require_once 'XML/Parser.php';
|
|
|
|
// {{{ error codes
|
|
|
|
define ('TEXT_HIGHLIGHTER_EMPTY_RE', 1);
|
|
define ('TEXT_HIGHLIGHTER_INVALID_RE', 2);
|
|
define ('TEXT_HIGHLIGHTER_EMPTY_OR_MISSING', 3);
|
|
define ('TEXT_HIGHLIGHTER_EMPTY', 4);
|
|
define ('TEXT_HIGHLIGHTER_REGION_REGION', 5);
|
|
define ('TEXT_HIGHLIGHTER_REGION_BLOCK', 6);
|
|
define ('TEXT_HIGHLIGHTER_BLOCK_REGION', 7);
|
|
define ('TEXT_HIGHLIGHTER_KEYWORD_BLOCK', 8);
|
|
define ('TEXT_HIGHLIGHTER_KEYWORD_INHERITS', 9);
|
|
define ('TEXT_HIGHLIGHTER_PARSE', 10);
|
|
define ('TEXT_HIGHLIGHTER_FILE_WRITE', 11);
|
|
define ('TEXT_HIGHLIGHTER_FILE_READ', 12);
|
|
// }}}
|
|
|
|
/**
|
|
* Syntax highliter class generator class
|
|
*
|
|
* This class is used to generate PHP classes
|
|
* from XML files with highlighting rules
|
|
*
|
|
* Usage example
|
|
* <code>
|
|
*require_once 'Text/Highlighter/Generator.php';
|
|
*$generator = new Text_Highlighter_Generator('php.xml');
|
|
*$generator->generate();
|
|
*$generator->saveCode('PHP.php');
|
|
* </code>
|
|
*
|
|
* A command line script <b>generate</b> is provided for
|
|
* class generation (installs in scripts/Text/Highlighter).
|
|
*
|
|
* @author Andrey Demenev <demenev@gmail.com>
|
|
* @copyright 2004-2006 Andrey Demenev
|
|
* @license http://www.php.net/license/3_0.txt PHP License
|
|
* @version Release: @package_version@
|
|
* @link http://pear.php.net/package/Text_Highlighter
|
|
*/
|
|
|
|
class Text_Highlighter_Generator extends XML_Parser
|
|
{
|
|
// {{{ properties
|
|
/**
|
|
* Whether to do case folding.
|
|
* We have to declare it here, because XML_Parser
|
|
* sets case folding in constructor
|
|
*
|
|
* @var boolean
|
|
*/
|
|
var $folding = false;
|
|
|
|
/**
|
|
* Holds name of file with highlighting rules
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
var $_syntaxFile;
|
|
|
|
/**
|
|
* Current element being processed
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_element;
|
|
|
|
/**
|
|
* List of regions
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_regions = array();
|
|
|
|
/**
|
|
* List of blocks
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_blocks = array();
|
|
|
|
/**
|
|
* List of keyword groups
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_keywords = array();
|
|
|
|
/**
|
|
* List of authors
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_authors = array();
|
|
|
|
/**
|
|
* Name of language
|
|
*
|
|
* @var string
|
|
* @access public
|
|
*/
|
|
var $language = '';
|
|
|
|
/**
|
|
* Generated code
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
var $_code = '';
|
|
|
|
/**
|
|
* Default class
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
var $_defClass = 'default';
|
|
|
|
/**
|
|
* Comment
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
var $_comment = '';
|
|
|
|
/**
|
|
* Flag for comment processing
|
|
*
|
|
* @var boolean
|
|
* @access private
|
|
*/
|
|
var $_inComment = false;
|
|
|
|
/**
|
|
* Sorting order of current block/region
|
|
*
|
|
* @var integer
|
|
* @access private
|
|
*/
|
|
var $_blockOrder = 0;
|
|
|
|
/**
|
|
* Generation errors
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
var $_errors;
|
|
|
|
// }}}
|
|
// {{{ constructor
|
|
|
|
/**
|
|
* PHP4 compatable constructor
|
|
*
|
|
* @param string $syntaxFile Name of XML file
|
|
* with syntax highlighting rules
|
|
*
|
|
* @access public
|
|
*/
|
|
|
|
function Text_Highlighter_Generator($syntaxFile = '')
|
|
{
|
|
return $this->__construct($syntaxFile);
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param string $syntaxFile Name of XML file
|
|
* with syntax highlighting rules
|
|
*
|
|
* @access public
|
|
*/
|
|
|
|
function __construct($syntaxFile = '')
|
|
{
|
|
XML_Parser::XML_Parser(null, 'func');
|
|
$this->_errors = array();
|
|
$this->_declareErrorMessages();
|
|
if ($syntaxFile) {
|
|
$this->setInputFile($syntaxFile);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _formatError
|
|
|
|
/**
|
|
* Format error message
|
|
*
|
|
* @param int $code error code
|
|
* @param string $params parameters
|
|
* @param string $fileName file name
|
|
* @param int $lineNo line number
|
|
* @return array
|
|
* @access public
|
|
*/
|
|
function _formatError($code, $params, $fileName, $lineNo)
|
|
{
|
|
$template = $this->_templates[$code];
|
|
$ret = call_user_func_array('sprintf', array_merge(array($template), $params));
|
|
if ($fileName) {
|
|
$ret = '[' . $fileName . '] ' . $ret;
|
|
}
|
|
if ($lineNo) {
|
|
$ret .= ' (line ' . $lineNo . ')';
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ declareErrorMessages
|
|
|
|
/**
|
|
* Set up error message templates
|
|
*
|
|
* @access private
|
|
*/
|
|
function _declareErrorMessages()
|
|
{
|
|
$this->_templates = array (
|
|
TEXT_HIGHLIGHTER_EMPTY_RE => 'Empty regular expression',
|
|
TEXT_HIGHLIGHTER_INVALID_RE => 'Invalid regular expression : %s',
|
|
TEXT_HIGHLIGHTER_EMPTY_OR_MISSING => 'Empty or missing %s',
|
|
TEXT_HIGHLIGHTER_EMPTY => 'Empty %s',
|
|
TEXT_HIGHLIGHTER_REGION_REGION => 'Region %s refers undefined region %s',
|
|
TEXT_HIGHLIGHTER_REGION_BLOCK => 'Region %s refers undefined block %s',
|
|
TEXT_HIGHLIGHTER_BLOCK_REGION => 'Block %s refers undefined region %s',
|
|
TEXT_HIGHLIGHTER_KEYWORD_BLOCK => 'Keyword group %s refers undefined block %s',
|
|
TEXT_HIGHLIGHTER_KEYWORD_INHERITS => 'Keyword group %s inherits undefined block %s',
|
|
TEXT_HIGHLIGHTER_PARSE => '%s',
|
|
TEXT_HIGHLIGHTER_FILE_WRITE => 'Error writing file %s',
|
|
TEXT_HIGHLIGHTER_FILE_READ => '%s'
|
|
);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ setInputFile
|
|
|
|
/**
|
|
* Sets the input xml file to be parsed
|
|
*
|
|
* @param string Filename (full path)
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
function setInputFile($file)
|
|
{
|
|
$this->_syntaxFile = $file;
|
|
$ret = parent::setInputFile($file);
|
|
if (PEAR::isError($ret)) {
|
|
$this->_error(TEXT_HIGHLIGHTER_FILE_READ, $ret->message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ generate
|
|
|
|
/**
|
|
* Generates class code
|
|
*
|
|
* @access public
|
|
*/
|
|
|
|
function generate()
|
|
{
|
|
$this->_regions = array();
|
|
$this->_blocks = array();
|
|
$this->_keywords = array();
|
|
$this->language = '';
|
|
$this->_code = '';
|
|
$this->_defClass = 'default';
|
|
$this->_comment = '';
|
|
$this->_inComment = false;
|
|
$this->_authors = array();
|
|
$this->_blockOrder = 0;
|
|
$this->_errors = array();
|
|
|
|
$ret = $this->parse();
|
|
if (PEAR::isError($ret)) {
|
|
$this->_error(TEXT_HIGHLIGHTER_PARSE, $ret->message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ getCode
|
|
|
|
/**
|
|
* Returns generated code as a string.
|
|
*
|
|
* @return string Generated code
|
|
* @access public
|
|
*/
|
|
|
|
function getCode()
|
|
{
|
|
return $this->_code;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ saveCode
|
|
|
|
/**
|
|
* Saves generated class to file. Note that {@link Text_Highlighter::factory()}
|
|
* assumes that filename is uppercase (SQL.php, DTD.php, etc), and file
|
|
* is located in Text/Highlighter
|
|
*
|
|
* @param string $filename Name of file to write the code to
|
|
* @return boolean true on success, false on failure
|
|
* @access public
|
|
*/
|
|
|
|
function saveCode($filename)
|
|
{
|
|
$f = @fopen($filename, 'wb');
|
|
if (!$f) {
|
|
$this->_error(TEXT_HIGHLIGHTER_FILE_WRITE, array('outfile'=>$filename));
|
|
return false;
|
|
}
|
|
fwrite ($f, $this->_code);
|
|
fclose($f);
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ hasErrors
|
|
|
|
/**
|
|
* Reports if there were errors
|
|
*
|
|
* @return boolean
|
|
* @access public
|
|
*/
|
|
|
|
function hasErrors()
|
|
{
|
|
return count($this->_errors) > 0;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ getErrors
|
|
|
|
/**
|
|
* Returns errors
|
|
*
|
|
* @return array
|
|
* @access public
|
|
*/
|
|
|
|
function getErrors()
|
|
{
|
|
return $this->_errors;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _sortBlocks
|
|
|
|
/**
|
|
* Sorts blocks
|
|
*
|
|
* @access private
|
|
*/
|
|
|
|
function _sortBlocks($b1, $b2) {
|
|
return $b1['order'] - $b2['order'];
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _sortLookFor
|
|
/**
|
|
* Sort 'look for' list
|
|
* @return int
|
|
* @param string $b1
|
|
* @param string $b2
|
|
*/
|
|
function _sortLookFor($b1, $b2) {
|
|
$o1 = isset($this->_blocks[$b1]) ? $this->_blocks[$b1]['order'] : $this->_regions[$b1]['order'];
|
|
$o2 = isset($this->_blocks[$b2]) ? $this->_blocks[$b2]['order'] : $this->_regions[$b2]['order'];
|
|
return $o1 - $o2;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _makeRE
|
|
|
|
/**
|
|
* Adds delimiters and modifiers to regular expression if necessary
|
|
*
|
|
* @param string $text Original RE
|
|
* @return string Final RE
|
|
* @access private
|
|
*/
|
|
function _makeRE($text, $case = false)
|
|
{
|
|
if (!strlen($text)) {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_RE);
|
|
}
|
|
if (!strlen($text) || $text{0} != '/') {
|
|
$text = '/' . $text . '/';
|
|
}
|
|
if (!$case) {
|
|
$text .= 'i';
|
|
}
|
|
$php_errormsg = '';
|
|
@preg_match($text, '');
|
|
if ($php_errormsg) {
|
|
$this->_error(TEXT_HIGHLIGHTER_INVALID_RE, $php_errormsg);
|
|
}
|
|
preg_match ('#^/(.+)/(.*)$#', $text, $m);
|
|
if (@$m[2]) {
|
|
$text = '(?' . $m[2] . ')' . $m[1];
|
|
} else {
|
|
$text = $m[1];
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _exportArray
|
|
|
|
/**
|
|
* Exports array as PHP code
|
|
*
|
|
* @param array $array
|
|
* @return string Code
|
|
* @access private
|
|
*/
|
|
function _exportArray($array)
|
|
{
|
|
$array = var_export($array, true);
|
|
return trim(preg_replace('~^(\s*)~m',' \1\1',$array));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _countSubpatterns
|
|
/**
|
|
* Find number of capturing suppaterns in regular expression
|
|
* @return int
|
|
* @param string $re Regular expression (without delimiters)
|
|
*/
|
|
function _countSubpatterns($re)
|
|
{
|
|
preg_match_all('/' . $re . '/', '', $m);
|
|
return count($m)-1;
|
|
}
|
|
|
|
// }}}
|
|
|
|
/**#@+
|
|
* @access private
|
|
* @param resource $xp XML parser resource
|
|
* @param string $elem XML element name
|
|
* @param array $attribs XML element attributes
|
|
*/
|
|
|
|
// {{{ xmltag_Default
|
|
|
|
/**
|
|
* start handler for <default> element
|
|
*/
|
|
function xmltag_Default($xp, $elem, $attribs)
|
|
{
|
|
$this->_aliasAttributes($attribs);
|
|
if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
|
|
}
|
|
$this->_defClass = @$attribs['innerGroup'];
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Region
|
|
|
|
/**
|
|
* start handler for <region> element
|
|
*/
|
|
function xmltag_Region($xp, $elem, $attribs)
|
|
{
|
|
$this->_aliasAttributes($attribs);
|
|
if (!isset($attribs['name']) || $attribs['name'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'region name');
|
|
}
|
|
if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
|
|
}
|
|
$this->_element = array('name' => $attribs['name']);
|
|
$this->_element['line'] = xml_get_current_line_number($this->parser);
|
|
if (isset($attribs['case'])) {
|
|
$this->_element['case'] = $attribs['case'] == 'yes';
|
|
} else {
|
|
$this->_element['case'] = $this->_case;
|
|
}
|
|
$this->_element['innerGroup'] = $attribs['innerGroup'];
|
|
$this->_element['delimGroup'] = isset($attribs['delimGroup']) ?
|
|
$attribs['delimGroup'] :
|
|
$attribs['innerGroup'];
|
|
$this->_element['start'] = $this->_makeRE(@$attribs['start'], $this->_element['case']);
|
|
$this->_element['end'] = $this->_makeRE(@$attribs['end'], $this->_element['case']);
|
|
$this->_element['contained'] = @$attribs['contained'] == 'yes';
|
|
$this->_element['never-contained'] = @$attribs['never-contained'] == 'yes';
|
|
$this->_element['remember'] = @$attribs['remember'] == 'yes';
|
|
if (isset($attribs['startBOL']) && $attribs['startBOL'] == 'yes') {
|
|
$this->_element['startBOL'] = true;
|
|
}
|
|
if (isset($attribs['endBOL']) && $attribs['endBOL'] == 'yes') {
|
|
$this->_element['endBOL'] = true;
|
|
}
|
|
if (isset($attribs['neverAfter'])) {
|
|
$this->_element['neverafter'] = $this->_makeRE($attribs['neverAfter']);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Block
|
|
|
|
/**
|
|
* start handler for <block> element
|
|
*/
|
|
function xmltag_Block($xp, $elem, $attribs)
|
|
{
|
|
$this->_aliasAttributes($attribs);
|
|
if (!isset($attribs['name']) || $attribs['name'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'block name');
|
|
}
|
|
if (isset($attribs['innerGroup']) && $attribs['innerGroup'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY, 'innerGroup');
|
|
}
|
|
$this->_element = array('name' => $attribs['name']);
|
|
$this->_element['line'] = xml_get_current_line_number($this->parser);
|
|
if (isset($attribs['case'])) {
|
|
$this->_element['case'] = $attribs['case'] == 'yes';
|
|
} else {
|
|
$this->_element['case'] = $this->_case;
|
|
}
|
|
if (isset($attribs['innerGroup'])) {
|
|
$this->_element['innerGroup'] = @$attribs['innerGroup'];
|
|
}
|
|
$this->_element['match'] = $this->_makeRE($attribs['match'], $this->_element['case']);
|
|
$this->_element['contained'] = @$attribs['contained'] == 'yes';
|
|
$this->_element['multiline'] = @$attribs['multiline'] == 'yes';
|
|
if (isset($attribs['BOL']) && $attribs['BOL'] == 'yes') {
|
|
$this->_element['BOL'] = true;
|
|
}
|
|
if (isset($attribs['neverAfter'])) {
|
|
$this->_element['neverafter'] = $this->_makeRE($attribs['neverAfter']);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ cdataHandler
|
|
|
|
/**
|
|
* Character data handler. Used for comment
|
|
*/
|
|
function cdataHandler($xp, $cdata)
|
|
{
|
|
if ($this->_inComment) {
|
|
$this->_comment .= $cdata;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Comment
|
|
|
|
/**
|
|
* start handler for <comment> element
|
|
*/
|
|
function xmltag_Comment($xp, $elem, $attribs)
|
|
{
|
|
$this->_comment = '';
|
|
$this->_inComment = true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_PartGroup
|
|
|
|
/**
|
|
* start handler for <partgroup> element
|
|
*/
|
|
function xmltag_PartGroup($xp, $elem, $attribs)
|
|
{
|
|
$this->_aliasAttributes($attribs);
|
|
if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
|
|
}
|
|
$this->_element['partClass'][$attribs['index']] = @$attribs['innerGroup'];
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_PartClass
|
|
|
|
/**
|
|
* start handler for <partclass> element
|
|
*/
|
|
function xmltag_PartClass($xp, $elem, $attribs)
|
|
{
|
|
$this->xmltag_PartGroup($xp, $elem, $attribs);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Keywords
|
|
|
|
/**
|
|
* start handler for <keywords> element
|
|
*/
|
|
function xmltag_Keywords($xp, $elem, $attribs)
|
|
{
|
|
$this->_aliasAttributes($attribs);
|
|
if (!isset($attribs['name']) || $attribs['name'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'keyword group name');
|
|
}
|
|
if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
|
|
}
|
|
if (!isset($attribs['inherits']) || $attribs['inherits'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'inherits');
|
|
}
|
|
$this->_element = array('name'=>@$attribs['name']);
|
|
$this->_element['line'] = xml_get_current_line_number($this->parser);
|
|
$this->_element['innerGroup'] = @$attribs['innerGroup'];
|
|
if (isset($attribs['case'])) {
|
|
$this->_element['case'] = $attribs['case'] == 'yes';
|
|
} else {
|
|
$this->_element['case'] = $this->_case;
|
|
}
|
|
$this->_element['inherits'] = @$attribs['inherits'];
|
|
if (isset($attribs['otherwise'])) {
|
|
$this->_element['otherwise'] = $attribs['otherwise'];
|
|
}
|
|
if (isset($attribs['ifdef'])) {
|
|
$this->_element['ifdef'] = $attribs['ifdef'];
|
|
}
|
|
if (isset($attribs['ifndef'])) {
|
|
$this->_element['ifndef'] = $attribs['ifndef'];
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Keyword
|
|
|
|
/**
|
|
* start handler for <keyword> element
|
|
*/
|
|
function xmltag_Keyword($xp, $elem, $attribs)
|
|
{
|
|
if (!isset($attribs['match']) || $attribs['match'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'match');
|
|
}
|
|
$keyword = @$attribs['match'];
|
|
if (!$this->_element['case']) {
|
|
$keyword = strtolower($keyword);
|
|
}
|
|
$this->_element['match'][$keyword] = true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Contains
|
|
|
|
/**
|
|
* start handler for <contains> element
|
|
*/
|
|
function xmltag_Contains($xp, $elem, $attribs)
|
|
{
|
|
$this->_element['contains-all'] = @$attribs['all'] == 'yes';
|
|
if (isset($attribs['region'])) {
|
|
$this->_element['contains']['region'][$attribs['region']] =
|
|
xml_get_current_line_number($this->parser);
|
|
}
|
|
if (isset($attribs['block'])) {
|
|
$this->_element['contains']['block'][$attribs['block']] =
|
|
xml_get_current_line_number($this->parser);
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_But
|
|
|
|
/**
|
|
* start handler for <but> element
|
|
*/
|
|
function xmltag_But($xp, $elem, $attribs)
|
|
{
|
|
if (isset($attribs['region'])) {
|
|
$this->_element['not-contains']['region'][$attribs['region']] = true;
|
|
}
|
|
if (isset($attribs['block'])) {
|
|
$this->_element['not-contains']['block'][$attribs['block']] = true;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Onlyin
|
|
|
|
/**
|
|
* start handler for <onlyin> element
|
|
*/
|
|
function xmltag_Onlyin($xp, $elem, $attribs)
|
|
{
|
|
if (!isset($attribs['region']) || $attribs['region'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'region');
|
|
}
|
|
$this->_element['onlyin'][$attribs['region']] = xml_get_current_line_number($this->parser);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Author
|
|
|
|
/**
|
|
* start handler for <author> element
|
|
*/
|
|
function xmltag_Author($xp, $elem, $attribs)
|
|
{
|
|
if (!isset($attribs['name']) || $attribs['name'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'author name');
|
|
}
|
|
$this->_authors[] = array(
|
|
'name' => @$attribs['name'],
|
|
'email' => (string)@$attribs['email']
|
|
);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Highlight
|
|
|
|
/**
|
|
* start handler for <highlight> element
|
|
*/
|
|
function xmltag_Highlight($xp, $elem, $attribs)
|
|
{
|
|
if (!isset($attribs['lang']) || $attribs['lang'] === '') {
|
|
$this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'language name');
|
|
}
|
|
$this->_code = '';
|
|
$this->language = strtoupper(@$attribs['lang']);
|
|
$this->_case = @$attribs['case'] == 'yes';
|
|
}
|
|
|
|
// }}}
|
|
|
|
/**#@-*/
|
|
|
|
// {{{ _error
|
|
|
|
/**
|
|
* Add an error message
|
|
*
|
|
* @param integer $code Error code
|
|
* @param mixed $message Error message or array with error message parameters
|
|
* @param integer $lineNo Source code line number
|
|
* @access private
|
|
*/
|
|
function _error($code, $params = array(), $lineNo = 0)
|
|
{
|
|
if (!$lineNo && !empty($this->parser)) {
|
|
$lineNo = xml_get_current_line_number($this->parser);
|
|
}
|
|
$this->_errors[] = $this->_formatError($code, $params, $this->_syntaxFile, $lineNo);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ _aliasAttributes
|
|
|
|
/**
|
|
* BC trick
|
|
*
|
|
* @param array $attrs attributes
|
|
*/
|
|
function _aliasAttributes(&$attrs)
|
|
{
|
|
if (isset($attrs['innerClass']) && !isset($attrs['innerGroup'])) {
|
|
$attrs['innerGroup'] = $attrs['innerClass'];
|
|
}
|
|
if (isset($attrs['delimClass']) && !isset($attrs['delimGroup'])) {
|
|
$attrs['delimGroup'] = $attrs['delimClass'];
|
|
}
|
|
if (isset($attrs['partClass']) && !isset($attrs['partGroup'])) {
|
|
$attrs['partGroup'] = $attrs['partClass'];
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
|
|
/**#@+
|
|
* @access private
|
|
* @param resource $xp XML parser resource
|
|
* @param string $elem XML element name
|
|
*/
|
|
|
|
// {{{ xmltag_Comment_
|
|
|
|
/**
|
|
* end handler for <comment> element
|
|
*/
|
|
function xmltag_Comment_($xp, $elem)
|
|
{
|
|
$this->_inComment = false;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Region_
|
|
|
|
/**
|
|
* end handler for <region> element
|
|
*/
|
|
function xmltag_Region_($xp, $elem)
|
|
{
|
|
$this->_element['type'] = 'region';
|
|
$this->_element['order'] = $this->_blockOrder ++;
|
|
$this->_regions[$this->_element['name']] = $this->_element;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Keywords_
|
|
|
|
/**
|
|
* end handler for <keywords> element
|
|
*/
|
|
function xmltag_Keywords_($xp, $elem)
|
|
{
|
|
$this->_keywords[$this->_element['name']] = $this->_element;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Block_
|
|
|
|
/**
|
|
* end handler for <block> element
|
|
*/
|
|
function xmltag_Block_($xp, $elem)
|
|
{
|
|
$this->_element['type'] = 'block';
|
|
$this->_element['order'] = $this->_blockOrder ++;
|
|
$this->_blocks[$this->_element['name']] = $this->_element;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ xmltag_Highlight_
|
|
|
|
/**
|
|
* end handler for <highlight> element
|
|
*/
|
|
function xmltag_Highlight_($xp, $elem)
|
|
{
|
|
$conditions = array();
|
|
$toplevel = array();
|
|
foreach ($this->_blocks as $i => $current) {
|
|
if (!$current['contained'] && !isset($current['onlyin'])) {
|
|
$toplevel[] = $i;
|
|
}
|
|
foreach ((array)@$current['onlyin'] as $region => $lineNo) {
|
|
if (!isset($this->_regions[$region])) {
|
|
$this->_error(TEXT_HIGHLIGHTER_BLOCK_REGION,
|
|
array(
|
|
'block' => $current['name'],
|
|
'region' => $region
|
|
));
|
|
}
|
|
}
|
|
}
|
|
foreach ($this->_regions as $i=>$current) {
|
|
if (!$current['contained'] && !isset($current['onlyin'])) {
|
|
$toplevel[] = $i;
|
|
}
|
|
foreach ((array)@$current['contains']['region'] as $region => $lineNo) {
|
|
if (!isset($this->_regions[$region])) {
|
|
$this->_error(TEXT_HIGHLIGHTER_REGION_REGION,
|
|
array(
|
|
'region1' => $current['name'],
|
|
'region2' => $region
|
|
));
|
|
}
|
|
}
|
|
foreach ((array)@$current['contains']['block'] as $region => $lineNo) {
|
|
if (!isset($this->_blocks[$region])) {
|
|
$this->_error(TEXT_HIGHLIGHTER_REGION_BLOCK,
|
|
array(
|
|
'block' => $current['name'],
|
|
'region' => $region
|
|
));
|
|
}
|
|
}
|
|
foreach ((array)@$current['onlyin'] as $region => $lineNo) {
|
|
if (!isset($this->_regions[$region])) {
|
|
$this->_error(TEXT_HIGHLIGHTER_REGION_REGION,
|
|
array(
|
|
'region1' => $current['name'],
|
|
'region2' => $region
|
|
));
|
|
}
|
|
}
|
|
foreach ($this->_regions as $j => $region) {
|
|
if (isset($region['onlyin'])) {
|
|
$suits = isset($region['onlyin'][$current['name']]);
|
|
} elseif (isset($current['not-contains']['region'][$region['name']])) {
|
|
$suits = false;
|
|
} elseif (isset($current['contains']['region'][$region['name']])) {
|
|
$suits = true;
|
|
} else {
|
|
$suits = @$current['contains-all'] && @!$region['never-contained'];
|
|
}
|
|
if ($suits) {
|
|
$this->_regions[$i]['lookfor'][] = $j;
|
|
}
|
|
}
|
|
foreach ($this->_blocks as $j=>$region) {
|
|
if (isset($region['onlyin'])) {
|
|
$suits = isset($region['onlyin'][$current['name']]);
|
|
} elseif (isset($current['not-contains']['block'][$region['name']])) {
|
|
$suits = false;
|
|
} elseif (isset($current['contains']['block'][$region['name']])) {
|
|
$suits = true;
|
|
} else {
|
|
$suits = @$current['contains-all'] && @!$region['never-contained'];
|
|
}
|
|
if ($suits) {
|
|
$this->_regions[$i]['lookfor'][] = $j;
|
|
}
|
|
}
|
|
}
|
|
foreach ($this->_blocks as $i=>$current) {
|
|
unset ($this->_blocks[$i]['never-contained']);
|
|
unset ($this->_blocks[$i]['contained']);
|
|
unset ($this->_blocks[$i]['contains-all']);
|
|
unset ($this->_blocks[$i]['contains']);
|
|
unset ($this->_blocks[$i]['onlyin']);
|
|
unset ($this->_blocks[$i]['line']);
|
|
}
|
|
|
|
foreach ($this->_regions as $i=>$current) {
|
|
unset ($this->_regions[$i]['never-contained']);
|
|
unset ($this->_regions[$i]['contained']);
|
|
unset ($this->_regions[$i]['contains-all']);
|
|
unset ($this->_regions[$i]['contains']);
|
|
unset ($this->_regions[$i]['onlyin']);
|
|
unset ($this->_regions[$i]['line']);
|
|
}
|
|
|
|
foreach ($this->_keywords as $name => $keyword) {
|
|
if (isset($keyword['ifdef'])) {
|
|
$conditions[$keyword['ifdef']][] = array($name, true);
|
|
}
|
|
if (isset($keyword['ifndef'])) {
|
|
$conditions[$keyword['ifndef']][] = array($name, false);
|
|
}
|
|
unset($this->_keywords[$name]['line']);
|
|
if (!isset($this->_blocks[$keyword['inherits']])) {
|
|
$this->_error(TEXT_HIGHLIGHTER_KEYWORD_INHERITS,
|
|
array(
|
|
'keyword' => $keyword['name'],
|
|
'block' => $keyword['inherits']
|
|
));
|
|
}
|
|
if (isset($keyword['otherwise']) && !isset($this->_blocks[$keyword['otherwise']]) ) {
|
|
$this->_error(TEXT_HIGHLIGHTER_KEYWORD_BLOCK,
|
|
array(
|
|
'keyword' => $keyword['name'],
|
|
'block' => $keyword['inherits']
|
|
));
|
|
}
|
|
}
|
|
|
|
$syntax=array(
|
|
'keywords' => $this->_keywords,
|
|
'blocks' => array_merge($this->_blocks, $this->_regions),
|
|
'toplevel' => $toplevel,
|
|
);
|
|
uasort($syntax['blocks'], array(&$this, '_sortBlocks'));
|
|
foreach ($syntax['blocks'] as $name => $block) {
|
|
if ($block['type'] == 'block') {
|
|
continue;
|
|
}
|
|
if (is_array(@$syntax['blocks'][$name]['lookfor'])) {
|
|
usort($syntax['blocks'][$name]['lookfor'], array(&$this, '_sortLookFor'));
|
|
}
|
|
}
|
|
usort($syntax['toplevel'], array(&$this, '_sortLookFor'));
|
|
$syntax['case'] = $this->_case;
|
|
$this->_code = <<<CODE
|
|
<?php
|
|
/**
|
|
* Auto-generated class. {$this->language} syntax highlighting
|
|
CODE;
|
|
|
|
if ($this->_comment) {
|
|
$comment = preg_replace('~^~m',' * ',$this->_comment);
|
|
$this->_code .= "\n * \n" . $comment;
|
|
}
|
|
|
|
$this->_code .= <<<CODE
|
|
|
|
*
|
|
* PHP version 4 and 5
|
|
*
|
|
* LICENSE: This source file is subject to version 3.0 of the PHP license
|
|
* that is available through the world-wide-web at the following URI:
|
|
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
|
|
* the PHP License and are unable to obtain it through the web, please
|
|
* send a note to license@php.net so we can mail you a copy immediately.
|
|
*
|
|
* @copyright 2004-2006 Andrey Demenev
|
|
* @license http://www.php.net/license/3_0.txt PHP License
|
|
* @link http://pear.php.net/package/Text_Highlighter
|
|
* @category Text
|
|
* @package Text_Highlighter
|
|
* @version generated from: $this->_syntaxFile
|
|
|
|
CODE;
|
|
|
|
foreach ($this->_authors as $author) {
|
|
$this->_code .= ' * @author ' . $author['name'];
|
|
if ($author['email']) {
|
|
$this->_code .= ' <' . $author['email'] . '>';
|
|
}
|
|
$this->_code .= "\n";
|
|
}
|
|
|
|
$this->_code .= <<<CODE
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
|
|
require_once 'Text/Highlighter.php';
|
|
|
|
/**
|
|
* Auto-generated class. {$this->language} syntax highlighting
|
|
*
|
|
|
|
CODE;
|
|
foreach ($this->_authors as $author) {
|
|
$this->_code .= ' * @author ' . $author['name'];
|
|
if ($author['email']) {
|
|
$this->_code .= ' <' . $author['email']. '>';
|
|
}
|
|
$this->_code .= "\n";
|
|
}
|
|
|
|
|
|
$this->_code .= <<<CODE
|
|
* @category Text
|
|
* @package Text_Highlighter
|
|
* @copyright 2004-2006 Andrey Demenev
|
|
* @license http://www.php.net/license/3_0.txt PHP License
|
|
* @version Release: @package_version@
|
|
* @link http://pear.php.net/package/Text_Highlighter
|
|
*/
|
|
class Text_Highlighter_{$this->language} extends Text_Highlighter
|
|
{
|
|
|
|
CODE;
|
|
$this->_code .= 'var $_language = \'' . strtolower($this->language) . "';\n\n";
|
|
$array = var_export($syntax, true);
|
|
$array = trim(preg_replace('~^(\s*)~m',' \1\1',$array));
|
|
// \$this->_syntax = $array;
|
|
$this->_code .= <<<CODE
|
|
/**
|
|
* PHP4 Compatible Constructor
|
|
*
|
|
* @param array \$options
|
|
* @access public
|
|
*/
|
|
function Text_Highlighter_{$this->language}(\$options=array())
|
|
{
|
|
\$this->__construct(\$options);
|
|
}
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param array \$options
|
|
* @access public
|
|
*/
|
|
function __construct(\$options=array())
|
|
{
|
|
|
|
CODE;
|
|
$this->_code .= <<<CODE
|
|
|
|
\$this->_options = \$options;
|
|
CODE;
|
|
$states = array();
|
|
$i = 0;
|
|
foreach ($syntax['blocks'] as $name => $block) {
|
|
if ($block['type'] == 'region') {
|
|
$states[$name] = $i++;
|
|
}
|
|
}
|
|
$regs = array();
|
|
$counts = array();
|
|
$delim = array();
|
|
$inner = array();
|
|
$end = array();
|
|
$stat = array();
|
|
$keywords = array();
|
|
$parts = array();
|
|
$kwmap = array();
|
|
$subst = array();
|
|
$re = array();
|
|
$ce = array();
|
|
$rd = array();
|
|
$in = array();
|
|
$st = array();
|
|
$kw = array();
|
|
$sb = array();
|
|
foreach ($syntax['toplevel'] as $name) {
|
|
$block = $syntax['blocks'][$name];
|
|
if ($block['type'] == 'block') {
|
|
$kwm = array();
|
|
$re[] = '(' . $block['match'] . ')';
|
|
$ce[] = $this->_countSubpatterns($block['match']);
|
|
$rd[] = '';
|
|
$sb[] = false;;
|
|
$st[] = -1;
|
|
foreach ($syntax['keywords'] as $kwname => $kwgroup) {
|
|
if ($kwgroup['inherits'] != $name) {
|
|
continue;
|
|
}
|
|
$gre = implode('|', array_keys($kwgroup['match']));
|
|
if (!$kwgroup['case']) {
|
|
$gre = '(?i)' . $gre;
|
|
}
|
|
$kwm[$kwname][] = $gre;
|
|
$kwmap[$kwname] = $kwgroup['innerGroup'];
|
|
}
|
|
foreach ($kwm as $g => $ma) {
|
|
$kwm[$g] = '/^(' . implode(')|(', $ma) . ')$/';
|
|
}
|
|
$kw[] = $kwm;
|
|
} else {
|
|
$kw[] = -1;
|
|
$re[] = '(' . $block['start'] . ')';
|
|
$ce[] = $this->_countSubpatterns($block['start']);
|
|
$rd[] = $block['delimGroup'];
|
|
$st[] = $states[$name];
|
|
$sb[] = $block['remember'];
|
|
}
|
|
$in[] = $block['innerGroup'];
|
|
}
|
|
$re = implode('|', $re);
|
|
$regs[-1] = '/' . $re . '/';
|
|
$counts[-1] = $ce;
|
|
$delim[-1] = $rd;
|
|
$inner[-1] = $in;
|
|
$stat[-1] = $st;
|
|
$keywords[-1] = $kw;
|
|
$subst[-1] = $sb;
|
|
|
|
foreach ($syntax['blocks'] as $ablock) {
|
|
if ($ablock['type'] != 'region') {
|
|
continue;
|
|
}
|
|
$end[] = '/' . $ablock['end'] . '/';
|
|
$re = array();
|
|
$ce = array();
|
|
$rd = array();
|
|
$in = array();
|
|
$st = array();
|
|
$kw = array();
|
|
$pc = array();
|
|
$sb = array();
|
|
foreach ((array)@$ablock['lookfor'] as $name) {
|
|
$block = $syntax['blocks'][$name];
|
|
if (isset($block['partClass'])) {
|
|
$pc[] = $block['partClass'];
|
|
} else {
|
|
$pc[] = null;
|
|
}
|
|
if ($block['type'] == 'block') {
|
|
$kwm = array();;
|
|
$re[] = '(' . $block['match'] . ')';
|
|
$ce[] = $this->_countSubpatterns($block['match']);
|
|
$rd[] = '';
|
|
$sb[] = false;
|
|
$st[] = -1;
|
|
foreach ($syntax['keywords'] as $kwname => $kwgroup) {
|
|
if ($kwgroup['inherits'] != $name) {
|
|
continue;
|
|
}
|
|
$gre = implode('|', array_keys($kwgroup['match']));
|
|
if (!$kwgroup['case']) {
|
|
$gre = '(?i)' . $gre;
|
|
}
|
|
$kwm[$kwname][] = $gre;
|
|
$kwmap[$kwname] = $kwgroup['innerGroup'];
|
|
}
|
|
foreach ($kwm as $g => $ma) {
|
|
$kwm[$g] = '/^(' . implode(')|(', $ma) . ')$/';
|
|
}
|
|
$kw[] = $kwm;
|
|
} else {
|
|
$sb[] = $block['remember'];
|
|
$kw[] = -1;
|
|
$re[] = '(' . $block['start'] . ')';
|
|
$ce[] = $this->_countSubpatterns($block['start']);
|
|
$rd[] = $block['delimGroup'];
|
|
$st[] = $states[$name];
|
|
}
|
|
$in[] = $block['innerGroup'];
|
|
}
|
|
$re = implode('|', $re);
|
|
$regs[] = '/' . $re . '/';
|
|
$counts[] = $ce;
|
|
$delim[] = $rd;
|
|
$inner[] = $in;
|
|
$stat[] = $st;
|
|
$keywords[] = $kw;
|
|
$parts[] = $pc;
|
|
$subst[] = $sb;
|
|
}
|
|
|
|
|
|
$this->_code .= "\n \$this->_regs = " . $this->_exportArray($regs);
|
|
$this->_code .= ";\n \$this->_counts = " .$this->_exportArray($counts);
|
|
$this->_code .= ";\n \$this->_delim = " .$this->_exportArray($delim);
|
|
$this->_code .= ";\n \$this->_inner = " .$this->_exportArray($inner);
|
|
$this->_code .= ";\n \$this->_end = " .$this->_exportArray($end);
|
|
$this->_code .= ";\n \$this->_states = " .$this->_exportArray($stat);
|
|
$this->_code .= ";\n \$this->_keywords = " .$this->_exportArray($keywords);
|
|
$this->_code .= ";\n \$this->_parts = " .$this->_exportArray($parts);
|
|
$this->_code .= ";\n \$this->_subst = " .$this->_exportArray($subst);
|
|
$this->_code .= ";\n \$this->_conditions = " .$this->_exportArray($conditions);
|
|
$this->_code .= ";\n \$this->_kwmap = " .$this->_exportArray($kwmap);
|
|
$this->_code .= ";\n \$this->_defClass = '" .$this->_defClass . '\'';
|
|
$this->_code .= <<<CODE
|
|
;
|
|
\$this->_checkDefines();
|
|
}
|
|
|
|
}
|
|
CODE;
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* c-hanging-comment-ender-p: nil
|
|
* End:
|
|
*/
|
|
|
|
?>
|