1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3/**
4* Syntax highlighter class generator
5*
6* To simplify the process of creating new syntax highlighters
7* for different languages, {@link Text_Highlighter_Generator} class is
8* provided. It takes highlighting rules from XML file and generates
9* a code of a class inherited from {@link Text_Highlighter}.
10*
11* PHP versions 4 and 5
12*
13* LICENSE: This source file is subject to version 3.0 of the PHP license
14* that is available through the world-wide-web at the following URI:
15* http://www.php.net/license/3_0.txt.  If you did not receive a copy of
16* the PHP License and are unable to obtain it through the web, please
17* send a note to license@php.net so we can mail you a copy immediately.
18*
19* @category   Text
20* @package    Text_Highlighter
21* @author     Andrey Demenev <demenev@gmail.com>
22* @copyright  2004-2006 Andrey Demenev
23* @license    http://www.php.net/license/3_0.txt  PHP License
24* @version    CVS: $Id$
25* @link       http://pear.php.net/package/Text_Highlighter
26*/
27
28/**
29* @ignore
30*/
31require_once 'PEAR.php';
32require_once 'XML/Parser.php';
33
34// {{{ error codes
35
36define ('TEXT_HIGHLIGHTER_EMPTY_RE',          1);
37define ('TEXT_HIGHLIGHTER_INVALID_RE',        2);
38define ('TEXT_HIGHLIGHTER_EMPTY_OR_MISSING',  3);
39define ('TEXT_HIGHLIGHTER_EMPTY',             4);
40define ('TEXT_HIGHLIGHTER_REGION_REGION',     5);
41define ('TEXT_HIGHLIGHTER_REGION_BLOCK',      6);
42define ('TEXT_HIGHLIGHTER_BLOCK_REGION',      7);
43define ('TEXT_HIGHLIGHTER_KEYWORD_BLOCK',     8);
44define ('TEXT_HIGHLIGHTER_KEYWORD_INHERITS',  9);
45define ('TEXT_HIGHLIGHTER_PARSE',            10);
46define ('TEXT_HIGHLIGHTER_FILE_WRITE',       11);
47define ('TEXT_HIGHLIGHTER_FILE_READ',        12);
48// }}}
49
50/**
51* Syntax highliter class generator class
52*
53* This class is used to generate PHP classes
54* from XML files with highlighting rules
55*
56* Usage example
57* <code>
58*require_once 'Text/Highlighter/Generator.php';
59*$generator = new Text_Highlighter_Generator('php.xml');
60*$generator->generate();
61*$generator->saveCode('PHP.php');
62* </code>
63*
64* A command line script <b>generate</b> is provided for
65* class generation (installs in scripts/Text/Highlighter).
66*
67* @author     Andrey Demenev <demenev@gmail.com>
68* @copyright  2004-2006 Andrey Demenev
69* @license    http://www.php.net/license/3_0.txt  PHP License
70* @version    Release: @package_version@
71* @link       http://pear.php.net/package/Text_Highlighter
72*/
73
74class Text_Highlighter_Generator extends  XML_Parser
75{
76    // {{{ properties
77    /**
78    * Whether to do case folding.
79    * We have to declare it here, because XML_Parser
80    * sets case folding in constructor
81    *
82    * @var  boolean
83    */
84    var $folding = false;
85
86    /**
87    * Holds name of file with highlighting rules
88    *
89    * @var string
90    * @access private
91    */
92    var $_syntaxFile;
93
94    /**
95    * Current element being processed
96    *
97    * @var array
98    * @access private
99    */
100    var $_element;
101
102    /**
103    * List of regions
104    *
105    * @var array
106    * @access private
107    */
108    var $_regions = array();
109
110    /**
111    * List of blocks
112    *
113    * @var array
114    * @access private
115    */
116    var $_blocks = array();
117
118    /**
119    * List of keyword groups
120    *
121    * @var array
122    * @access private
123    */
124    var $_keywords = array();
125
126    /**
127    * List of authors
128    *
129    * @var array
130    * @access private
131    */
132    var $_authors = array();
133
134    /**
135    * Name of language
136    *
137    * @var string
138    * @access public
139    */
140    var $language = '';
141
142    /**
143    * Generated code
144    *
145    * @var string
146    * @access private
147    */
148    var $_code = '';
149
150    /**
151    * Default class
152    *
153    * @var string
154    * @access private
155    */
156    var $_defClass = 'default';
157
158    /**
159    * Comment
160    *
161    * @var string
162    * @access private
163    */
164    var $_comment = '';
165
166    /**
167    * Flag for comment processing
168    *
169    * @var boolean
170    * @access private
171    */
172    var $_inComment = false;
173
174    /**
175    * Sorting order of current block/region
176    *
177    * @var integer
178    * @access private
179    */
180    var $_blockOrder = 0;
181
182    /**
183    * Generation errors
184    *
185    * @var array
186    * @access private
187    */
188    var $_errors;
189
190    // }}}
191    // {{{ constructor
192
193    /**
194    * PHP4 compatable constructor
195    *
196    * @param string $syntaxFile Name of XML file
197    * with syntax highlighting rules
198    *
199    * @access public
200    */
201
202    function Text_Highlighter_Generator($syntaxFile = '')
203    {
204        return $this->__construct($syntaxFile);
205    }
206
207    /**
208    * Constructor
209    *
210    * @param string $syntaxFile Name of XML file
211    * with syntax highlighting rules
212    *
213    * @access public
214    */
215
216    function __construct($syntaxFile = '')
217    {
218        XML_Parser::XML_Parser(null, 'func');
219        $this->_errors = array();
220        $this->_declareErrorMessages();
221        if ($syntaxFile) {
222            $this->setInputFile($syntaxFile);
223        }
224    }
225
226    // }}}
227    // {{{ _formatError
228
229    /**
230    * Format error message
231    *
232    * @param int $code error code
233    * @param string $params parameters
234    * @param string $fileName file name
235    * @param int $lineNo line number
236    * @return  array
237    * @access  public
238    */
239    function _formatError($code, $params, $fileName, $lineNo)
240    {
241        $template = $this->_templates[$code];
242        $ret = call_user_func_array('sprintf', array_merge(array($template), $params));
243        if ($fileName) {
244            $ret = '[' . $fileName . '] ' . $ret;
245        }
246        if ($lineNo) {
247            $ret .= ' (line ' . $lineNo . ')';
248        }
249        return $ret;
250    }
251
252    // }}}
253    // {{{ declareErrorMessages
254
255    /**
256    * Set up error message templates
257    *
258    * @access  private
259    */
260    function _declareErrorMessages()
261    {
262        $this->_templates = array (
263        TEXT_HIGHLIGHTER_EMPTY_RE => 'Empty regular expression',
264        TEXT_HIGHLIGHTER_INVALID_RE => 'Invalid regular expression : %s',
265        TEXT_HIGHLIGHTER_EMPTY_OR_MISSING => 'Empty or missing %s',
266        TEXT_HIGHLIGHTER_EMPTY  => 'Empty %s',
267        TEXT_HIGHLIGHTER_REGION_REGION => 'Region %s refers undefined region %s',
268        TEXT_HIGHLIGHTER_REGION_BLOCK => 'Region %s refers undefined block %s',
269        TEXT_HIGHLIGHTER_BLOCK_REGION => 'Block %s refers undefined region %s',
270        TEXT_HIGHLIGHTER_KEYWORD_BLOCK => 'Keyword group %s refers undefined block %s',
271        TEXT_HIGHLIGHTER_KEYWORD_INHERITS => 'Keyword group %s inherits undefined block %s',
272        TEXT_HIGHLIGHTER_PARSE => '%s',
273        TEXT_HIGHLIGHTER_FILE_WRITE => 'Error writing file %s',
274        TEXT_HIGHLIGHTER_FILE_READ => '%s'
275        );
276    }
277
278    // }}}
279    // {{{ setInputFile
280
281    /**
282    * Sets the input xml file to be parsed
283    *
284    * @param    string      Filename (full path)
285    * @return   boolean
286    * @access   public
287    */
288    function setInputFile($file)
289    {
290        $this->_syntaxFile = $file;
291        $ret = parent::setInputFile($file);
292        if (PEAR::isError($ret)) {
293            $this->_error(TEXT_HIGHLIGHTER_FILE_READ, $ret->message);
294            return false;
295        }
296        return true;
297    }
298
299    // }}}
300    // {{{ generate
301
302    /**
303    * Generates class code
304    *
305    * @access public
306    */
307
308    function generate()
309    {
310        $this->_regions    = array();
311        $this->_blocks     = array();
312        $this->_keywords   = array();
313        $this->language    = '';
314        $this->_code       = '';
315        $this->_defClass   = 'default';
316        $this->_comment    = '';
317        $this->_inComment  = false;
318        $this->_authors    = array();
319        $this->_blockOrder = 0;
320        $this->_errors   = array();
321
322        $ret = $this->parse();
323        if (PEAR::isError($ret)) {
324            $this->_error(TEXT_HIGHLIGHTER_PARSE, $ret->message);
325            return false;
326        }
327        return true;
328    }
329
330    // }}}
331    // {{{ getCode
332
333    /**
334    * Returns generated code as a string.
335    *
336    * @return string Generated code
337    * @access public
338    */
339
340    function getCode()
341    {
342        return $this->_code;
343    }
344
345    // }}}
346    // {{{ saveCode
347
348    /**
349    * Saves generated class to file. Note that {@link Text_Highlighter::factory()}
350    * assumes that filename is uppercase (SQL.php, DTD.php, etc), and file
351    * is located in Text/Highlighter
352    *
353    * @param string $filename Name of file to write the code to
354    * @return boolean true on success, false on failure
355    * @access public
356    */
357
358    function saveCode($filename)
359    {
360        $f = @fopen($filename, 'wb');
361        if (!$f) {
362            $this->_error(TEXT_HIGHLIGHTER_FILE_WRITE, array('outfile'=>$filename));
363            return false;
364        }
365        fwrite ($f, $this->_code);
366        fclose($f);
367        return true;
368    }
369
370    // }}}
371    // {{{ hasErrors
372
373    /**
374    * Reports if there were errors
375    *
376    * @return boolean
377    * @access public
378    */
379
380    function hasErrors()
381    {
382        return count($this->_errors) > 0;
383    }
384
385    // }}}
386    // {{{ getErrors
387
388    /**
389    * Returns errors
390    *
391    * @return array
392    * @access public
393    */
394
395    function getErrors()
396    {
397        return $this->_errors;
398    }
399
400    // }}}
401    // {{{ _sortBlocks
402
403    /**
404    * Sorts blocks
405    *
406    * @access private
407    */
408
409    function _sortBlocks($b1, $b2) {
410        return $b1['order'] - $b2['order'];
411    }
412
413    // }}}
414    // {{{ _sortLookFor
415    /**
416    * Sort 'look for' list
417    * @return int
418    * @param string $b1
419    * @param string $b2
420    */
421    function _sortLookFor($b1, $b2) {
422        $o1 = isset($this->_blocks[$b1]) ? $this->_blocks[$b1]['order'] : $this->_regions[$b1]['order'];
423        $o2 = isset($this->_blocks[$b2]) ? $this->_blocks[$b2]['order'] : $this->_regions[$b2]['order'];
424        return $o1 - $o2;
425    }
426
427    // }}}
428    // {{{ _makeRE
429
430    /**
431    * Adds delimiters and modifiers to regular expression if necessary
432    *
433    * @param string $text Original RE
434    * @return string Final RE
435    * @access private
436    */
437    function _makeRE($text, $case = false)
438    {
439        if (!strlen($text)) {
440            $this->_error(TEXT_HIGHLIGHTER_EMPTY_RE);
441        }
442        if (!strlen($text) || $text{0} != '/') {
443            $text = '/' . $text . '/';
444        }
445        if (!$case) {
446            $text .= 'i';
447        }
448        $php_errormsg = '';
449        @preg_match($text, '');
450        if ($php_errormsg) {
451            $this->_error(TEXT_HIGHLIGHTER_INVALID_RE, $php_errormsg);
452        }
453        preg_match ('#^/(.+)/(.*)$#', $text, $m);
454        if (@$m[2]) {
455            $text = '(?' . $m[2] . ')' . $m[1];
456        } else {
457            $text = $m[1];
458        }
459        return $text;
460    }
461
462    // }}}
463    // {{{ _exportArray
464
465    /**
466    * Exports array as PHP code
467    *
468    * @param array $array
469    * @return string Code
470    * @access private
471    */
472    function _exportArray($array)
473    {
474        $array = var_export($array, true);
475        return trim(preg_replace('~^(\s*)~m','        \1\1',$array));
476    }
477
478    // }}}
479    // {{{ _countSubpatterns
480    /**
481    * Find number of capturing suppaterns in regular expression
482    * @return int
483    * @param string $re Regular expression (without delimiters)
484    */
485    function _countSubpatterns($re)
486    {
487        preg_match_all('/' . $re . '/', '', $m);
488        return count($m)-1;
489    }
490
491    // }}}
492
493    /**#@+
494    * @access private
495    * @param resource $xp      XML parser resource
496    * @param string   $elem    XML element name
497    * @param array    $attribs XML element attributes
498    */
499
500    // {{{ xmltag_Default
501
502    /**
503    * start handler for <default> element
504    */
505    function xmltag_Default($xp, $elem, $attribs)
506    {
507        $this->_aliasAttributes($attribs);
508        if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
509            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
510        }
511        $this->_defClass = @$attribs['innerGroup'];
512    }
513
514    // }}}
515    // {{{ xmltag_Region
516
517    /**
518    * start handler for <region> element
519    */
520    function xmltag_Region($xp, $elem, $attribs)
521    {
522        $this->_aliasAttributes($attribs);
523        if (!isset($attribs['name']) || $attribs['name'] === '') {
524            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'region name');
525        }
526        if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
527            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
528        }
529        $this->_element = array('name' => $attribs['name']);
530        $this->_element['line'] = xml_get_current_line_number($this->parser);
531        if (isset($attribs['case'])) {
532            $this->_element['case'] = $attribs['case'] == 'yes';
533        } else {
534            $this->_element['case'] = $this->_case;
535        }
536        $this->_element['innerGroup'] = $attribs['innerGroup'];
537        $this->_element['delimGroup'] = isset($attribs['delimGroup']) ?
538        $attribs['delimGroup'] :
539        $attribs['innerGroup'];
540        $this->_element['start'] = $this->_makeRE(@$attribs['start'], $this->_element['case']);
541        $this->_element['end'] = $this->_makeRE(@$attribs['end'], $this->_element['case']);
542        $this->_element['contained'] = @$attribs['contained'] == 'yes';
543        $this->_element['never-contained'] = @$attribs['never-contained'] == 'yes';
544        $this->_element['remember'] = @$attribs['remember'] == 'yes';
545        if (isset($attribs['startBOL']) && $attribs['startBOL'] == 'yes') {
546            $this->_element['startBOL'] = true;
547        }
548        if (isset($attribs['endBOL']) && $attribs['endBOL'] == 'yes') {
549            $this->_element['endBOL'] = true;
550        }
551        if (isset($attribs['neverAfter'])) {
552            $this->_element['neverafter'] = $this->_makeRE($attribs['neverAfter']);
553        }
554    }
555
556    // }}}
557    // {{{ xmltag_Block
558
559    /**
560    * start handler for <block> element
561    */
562    function xmltag_Block($xp, $elem, $attribs)
563    {
564        $this->_aliasAttributes($attribs);
565        if (!isset($attribs['name']) || $attribs['name'] === '') {
566            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'block name');
567        }
568        if (isset($attribs['innerGroup']) && $attribs['innerGroup'] === '') {
569            $this->_error(TEXT_HIGHLIGHTER_EMPTY, 'innerGroup');
570        }
571        $this->_element = array('name' => $attribs['name']);
572        $this->_element['line'] = xml_get_current_line_number($this->parser);
573        if (isset($attribs['case'])) {
574            $this->_element['case'] = $attribs['case'] == 'yes';
575        } else {
576            $this->_element['case'] = $this->_case;
577        }
578        if (isset($attribs['innerGroup'])) {
579            $this->_element['innerGroup'] = @$attribs['innerGroup'];
580        }
581        $this->_element['match'] = $this->_makeRE($attribs['match'], $this->_element['case']);
582        $this->_element['contained'] = @$attribs['contained'] == 'yes';
583        $this->_element['multiline'] = @$attribs['multiline'] == 'yes';
584        if (isset($attribs['BOL']) && $attribs['BOL'] == 'yes') {
585            $this->_element['BOL'] = true;
586        }
587        if (isset($attribs['neverAfter'])) {
588            $this->_element['neverafter'] = $this->_makeRE($attribs['neverAfter']);
589        }
590    }
591
592    // }}}
593    // {{{ cdataHandler
594
595    /**
596    * Character data handler. Used for comment
597    */
598    function cdataHandler($xp, $cdata)
599    {
600        if ($this->_inComment) {
601            $this->_comment .= $cdata;
602        }
603    }
604
605    // }}}
606    // {{{ xmltag_Comment
607
608    /**
609    * start handler for <comment> element
610    */
611    function xmltag_Comment($xp, $elem, $attribs)
612    {
613        $this->_comment = '';
614        $this->_inComment = true;
615    }
616
617    // }}}
618    // {{{ xmltag_PartGroup
619
620    /**
621    * start handler for <partgroup> element
622    */
623    function xmltag_PartGroup($xp, $elem, $attribs)
624    {
625        $this->_aliasAttributes($attribs);
626        if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
627            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
628        }
629        $this->_element['partClass'][$attribs['index']] = @$attribs['innerGroup'];
630    }
631
632    // }}}
633    // {{{ xmltag_PartClass
634
635    /**
636    * start handler for <partclass> element
637    */
638    function xmltag_PartClass($xp, $elem, $attribs)
639    {
640        $this->xmltag_PartGroup($xp, $elem, $attribs);
641    }
642
643    // }}}
644    // {{{ xmltag_Keywords
645
646    /**
647    * start handler for <keywords> element
648    */
649    function xmltag_Keywords($xp, $elem, $attribs)
650    {
651        $this->_aliasAttributes($attribs);
652        if (!isset($attribs['name']) || $attribs['name'] === '') {
653            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'keyword group name');
654        }
655        if (!isset($attribs['innerGroup']) || $attribs['innerGroup'] === '') {
656            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'innerGroup');
657        }
658        if (!isset($attribs['inherits']) || $attribs['inherits'] === '') {
659            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'inherits');
660        }
661        $this->_element = array('name'=>@$attribs['name']);
662        $this->_element['line'] = xml_get_current_line_number($this->parser);
663        $this->_element['innerGroup'] = @$attribs['innerGroup'];
664        if (isset($attribs['case'])) {
665            $this->_element['case'] = $attribs['case'] == 'yes';
666        } else {
667            $this->_element['case'] = $this->_case;
668        }
669        $this->_element['inherits'] = @$attribs['inherits'];
670        if (isset($attribs['otherwise'])) {
671            $this->_element['otherwise'] = $attribs['otherwise'];
672        }
673        if (isset($attribs['ifdef'])) {
674            $this->_element['ifdef'] = $attribs['ifdef'];
675        }
676        if (isset($attribs['ifndef'])) {
677            $this->_element['ifndef'] = $attribs['ifndef'];
678        }
679    }
680
681    // }}}
682    // {{{ xmltag_Keyword
683
684    /**
685    * start handler for <keyword> element
686    */
687    function xmltag_Keyword($xp, $elem, $attribs)
688    {
689        if (!isset($attribs['match']) || $attribs['match'] === '') {
690            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'match');
691        }
692        $keyword = @$attribs['match'];
693        if (!$this->_element['case']) {
694            $keyword = strtolower($keyword);
695        }
696        $this->_element['match'][$keyword] = true;
697    }
698
699    // }}}
700    // {{{ xmltag_Contains
701
702    /**
703    * start handler for <contains> element
704    */
705    function xmltag_Contains($xp, $elem, $attribs)
706    {
707        $this->_element['contains-all'] = @$attribs['all'] == 'yes';
708        if (isset($attribs['region'])) {
709            $this->_element['contains']['region'][$attribs['region']] =
710            xml_get_current_line_number($this->parser);
711        }
712        if (isset($attribs['block'])) {
713            $this->_element['contains']['block'][$attribs['block']] =
714            xml_get_current_line_number($this->parser);
715        }
716    }
717
718    // }}}
719    // {{{ xmltag_But
720
721    /**
722    * start handler for <but> element
723    */
724    function xmltag_But($xp, $elem, $attribs)
725    {
726        if (isset($attribs['region'])) {
727            $this->_element['not-contains']['region'][$attribs['region']] = true;
728        }
729        if (isset($attribs['block'])) {
730            $this->_element['not-contains']['block'][$attribs['block']] = true;
731        }
732    }
733
734    // }}}
735    // {{{ xmltag_Onlyin
736
737    /**
738    * start handler for <onlyin> element
739    */
740    function xmltag_Onlyin($xp, $elem, $attribs)
741    {
742        if (!isset($attribs['region']) || $attribs['region'] === '') {
743            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'region');
744        }
745        $this->_element['onlyin'][$attribs['region']] = xml_get_current_line_number($this->parser);
746    }
747
748    // }}}
749    // {{{ xmltag_Author
750
751    /**
752    * start handler for <author> element
753    */
754    function xmltag_Author($xp, $elem, $attribs)
755    {
756        if (!isset($attribs['name']) || $attribs['name'] === '') {
757            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'author name');
758        }
759        $this->_authors[] = array(
760        'name'  => @$attribs['name'],
761        'email' => (string)@$attribs['email']
762        );
763    }
764
765    // }}}
766    // {{{ xmltag_Highlight
767
768    /**
769    * start handler for <highlight> element
770    */
771    function xmltag_Highlight($xp, $elem, $attribs)
772    {
773        if (!isset($attribs['lang']) || $attribs['lang'] === '') {
774            $this->_error(TEXT_HIGHLIGHTER_EMPTY_OR_MISSING, 'language name');
775        }
776        $this->_code = '';
777        $this->language = strtoupper(@$attribs['lang']);
778        $this->_case = @$attribs['case'] == 'yes';
779    }
780
781    // }}}
782
783    /**#@-*/
784
785    // {{{ _error
786
787    /**
788    * Add an error message
789    *
790    * @param integer $code Error code
791    * @param mixed   $message Error message or array with error message parameters
792    * @param integer $lineNo Source code line number
793    * @access private
794    */
795    function _error($code, $params = array(), $lineNo = 0)
796    {
797        if (!$lineNo && !empty($this->parser)) {
798            $lineNo = xml_get_current_line_number($this->parser);
799        }
800        $this->_errors[] = $this->_formatError($code, $params, $this->_syntaxFile, $lineNo);
801    }
802
803    // }}}
804    // {{{ _aliasAttributes
805
806    /**
807    * BC trick
808    *
809    * @param array $attrs attributes
810    */
811    function _aliasAttributes(&$attrs)
812    {
813        if (isset($attrs['innerClass']) && !isset($attrs['innerGroup'])) {
814            $attrs['innerGroup'] = $attrs['innerClass'];
815        }
816        if (isset($attrs['delimClass']) && !isset($attrs['delimGroup'])) {
817            $attrs['delimGroup'] = $attrs['delimClass'];
818        }
819        if (isset($attrs['partClass']) && !isset($attrs['partGroup'])) {
820            $attrs['partGroup'] = $attrs['partClass'];
821        }
822    }
823
824    // }}}
825
826    /**#@+
827    * @access private
828    * @param resource $xp      XML parser resource
829    * @param string   $elem    XML element name
830    */
831
832    // {{{ xmltag_Comment_
833
834    /**
835    * end handler for <comment> element
836    */
837    function xmltag_Comment_($xp, $elem)
838    {
839        $this->_inComment = false;
840    }
841
842    // }}}
843    // {{{ xmltag_Region_
844
845    /**
846    * end handler for <region> element
847    */
848    function xmltag_Region_($xp, $elem)
849    {
850        $this->_element['type'] = 'region';
851        $this->_element['order'] = $this->_blockOrder ++;
852        $this->_regions[$this->_element['name']] = $this->_element;
853    }
854
855    // }}}
856    // {{{ xmltag_Keywords_
857
858    /**
859    * end handler for <keywords> element
860    */
861    function xmltag_Keywords_($xp, $elem)
862    {
863        $this->_keywords[$this->_element['name']] = $this->_element;
864    }
865
866    // }}}
867    // {{{ xmltag_Block_
868
869    /**
870    * end handler for <block> element
871    */
872    function xmltag_Block_($xp, $elem)
873    {
874        $this->_element['type'] = 'block';
875        $this->_element['order'] = $this->_blockOrder ++;
876        $this->_blocks[$this->_element['name']] = $this->_element;
877    }
878
879    // }}}
880    // {{{ xmltag_Highlight_
881
882    /**
883    * end handler for <highlight> element
884    */
885    function xmltag_Highlight_($xp, $elem)
886    {
887        $conditions = array();
888        $toplevel = array();
889        foreach ($this->_blocks as $i => $current) {
890            if (!$current['contained'] && !isset($current['onlyin'])) {
891                $toplevel[] = $i;
892            }
893            foreach ((array)@$current['onlyin'] as $region => $lineNo) {
894                if (!isset($this->_regions[$region])) {
895                    $this->_error(TEXT_HIGHLIGHTER_BLOCK_REGION,
896                    array(
897                    'block' => $current['name'],
898                    'region' => $region
899                    ));
900                }
901            }
902        }
903        foreach ($this->_regions as $i=>$current) {
904            if (!$current['contained'] && !isset($current['onlyin'])) {
905                $toplevel[] = $i;
906            }
907            foreach ((array)@$current['contains']['region'] as $region => $lineNo) {
908                if (!isset($this->_regions[$region])) {
909                    $this->_error(TEXT_HIGHLIGHTER_REGION_REGION,
910                    array(
911                    'region1' => $current['name'],
912                    'region2' => $region
913                    ));
914                }
915            }
916            foreach ((array)@$current['contains']['block'] as $region => $lineNo) {
917                if (!isset($this->_blocks[$region])) {
918                    $this->_error(TEXT_HIGHLIGHTER_REGION_BLOCK,
919                    array(
920                    'block' => $current['name'],
921                    'region' => $region
922                    ));
923                }
924            }
925            foreach ((array)@$current['onlyin'] as $region => $lineNo) {
926                if (!isset($this->_regions[$region])) {
927                    $this->_error(TEXT_HIGHLIGHTER_REGION_REGION,
928                    array(
929                    'region1' => $current['name'],
930                    'region2' => $region
931                    ));
932                }
933            }
934            foreach ($this->_regions as $j => $region) {
935                if (isset($region['onlyin'])) {
936                    $suits = isset($region['onlyin'][$current['name']]);
937                } elseif (isset($current['not-contains']['region'][$region['name']])) {
938                    $suits = false;
939                } elseif (isset($current['contains']['region'][$region['name']])) {
940                    $suits = true;
941                } else {
942                    $suits = @$current['contains-all'] && @!$region['never-contained'];
943                }
944                if ($suits) {
945                    $this->_regions[$i]['lookfor'][] = $j;
946                }
947            }
948            foreach ($this->_blocks as $j=>$region) {
949                if (isset($region['onlyin'])) {
950                    $suits = isset($region['onlyin'][$current['name']]);
951                } elseif (isset($current['not-contains']['block'][$region['name']])) {
952                    $suits = false;
953                } elseif (isset($current['contains']['block'][$region['name']])) {
954                    $suits = true;
955                } else {
956                    $suits = @$current['contains-all'] && @!$region['never-contained'];
957                }
958                if ($suits) {
959                    $this->_regions[$i]['lookfor'][] = $j;
960                }
961            }
962        }
963        foreach ($this->_blocks as $i=>$current) {
964            unset ($this->_blocks[$i]['never-contained']);
965            unset ($this->_blocks[$i]['contained']);
966            unset ($this->_blocks[$i]['contains-all']);
967            unset ($this->_blocks[$i]['contains']);
968            unset ($this->_blocks[$i]['onlyin']);
969            unset ($this->_blocks[$i]['line']);
970        }
971
972        foreach ($this->_regions as $i=>$current) {
973            unset ($this->_regions[$i]['never-contained']);
974            unset ($this->_regions[$i]['contained']);
975            unset ($this->_regions[$i]['contains-all']);
976            unset ($this->_regions[$i]['contains']);
977            unset ($this->_regions[$i]['onlyin']);
978            unset ($this->_regions[$i]['line']);
979        }
980
981        foreach ($this->_keywords as $name => $keyword) {
982            if (isset($keyword['ifdef'])) {
983                $conditions[$keyword['ifdef']][] = array($name, true);
984            }
985            if (isset($keyword['ifndef'])) {
986                $conditions[$keyword['ifndef']][] = array($name, false);
987            }
988            unset($this->_keywords[$name]['line']);
989            if (!isset($this->_blocks[$keyword['inherits']])) {
990                $this->_error(TEXT_HIGHLIGHTER_KEYWORD_INHERITS,
991                array(
992                'keyword' => $keyword['name'],
993                'block' => $keyword['inherits']
994                ));
995            }
996            if (isset($keyword['otherwise']) && !isset($this->_blocks[$keyword['otherwise']]) ) {
997                $this->_error(TEXT_HIGHLIGHTER_KEYWORD_BLOCK,
998                array(
999                'keyword' => $keyword['name'],
1000                'block' => $keyword['inherits']
1001                ));
1002            }
1003        }
1004
1005        $syntax=array(
1006        'keywords'   => $this->_keywords,
1007        'blocks'     => array_merge($this->_blocks, $this->_regions),
1008        'toplevel'   => $toplevel,
1009        );
1010        uasort($syntax['blocks'], array(&$this, '_sortBlocks'));
1011        foreach ($syntax['blocks'] as $name => $block) {
1012            if ($block['type'] == 'block') {
1013                continue;
1014            }
1015            if (is_array(@$syntax['blocks'][$name]['lookfor'])) {
1016                usort($syntax['blocks'][$name]['lookfor'], array(&$this, '_sortLookFor'));
1017            }
1018        }
1019        usort($syntax['toplevel'], array(&$this, '_sortLookFor'));
1020        $syntax['case'] = $this->_case;
1021        $this->_code = <<<CODE
1022<?php
1023/**
1024 * Auto-generated class. {$this->language} syntax highlighting
1025CODE;
1026
1027        if ($this->_comment) {
1028            $comment = preg_replace('~^~m',' * ',$this->_comment);
1029            $this->_code .= "\n * \n" . $comment;
1030        }
1031
1032        $this->_code .= <<<CODE
1033
1034 *
1035 * PHP version 4 and 5
1036 *
1037 * LICENSE: This source file is subject to version 3.0 of the PHP license
1038 * that is available through the world-wide-web at the following URI:
1039 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
1040 * the PHP License and are unable to obtain it through the web, please
1041 * send a note to license@php.net so we can mail you a copy immediately.
1042 *
1043 * @copyright  2004-2006 Andrey Demenev
1044 * @license    http://www.php.net/license/3_0.txt  PHP License
1045 * @link       http://pear.php.net/package/Text_Highlighter
1046 * @category   Text
1047 * @package    Text_Highlighter
1048 * @version    generated from: $this->_syntaxFile
1049
1050CODE;
1051
1052        foreach ($this->_authors as $author) {
1053            $this->_code .= ' * @author ' . $author['name'];
1054            if ($author['email']) {
1055                $this->_code .= ' <' . $author['email'] . '>';
1056            }
1057            $this->_code .= "\n";
1058        }
1059
1060        $this->_code .= <<<CODE
1061 *
1062 */
1063
1064/**
1065 * @ignore
1066 */
1067
1068require_once 'Text/Highlighter.php';
1069
1070/**
1071 * Auto-generated class. {$this->language} syntax highlighting
1072 *
1073
1074CODE;
1075        foreach ($this->_authors as $author) {
1076            $this->_code .= ' * @author ' . $author['name'];
1077            if ($author['email']) {
1078                $this->_code .= ' <' . $author['email']. '>';
1079            }
1080            $this->_code .= "\n";
1081        }
1082
1083
1084        $this->_code .= <<<CODE
1085 * @category   Text
1086 * @package    Text_Highlighter
1087 * @copyright  2004-2006 Andrey Demenev
1088 * @license    http://www.php.net/license/3_0.txt  PHP License
1089 * @version    Release: @package_version@
1090 * @link       http://pear.php.net/package/Text_Highlighter
1091 */
1092class  Text_Highlighter_{$this->language} extends Text_Highlighter
1093{
1094
1095CODE;
1096        $this->_code .= 'var $_language = \'' . strtolower($this->language) . "';\n\n";
1097        $array = var_export($syntax, true);
1098        $array = trim(preg_replace('~^(\s*)~m','        \1\1',$array));
1099        //        \$this->_syntax = $array;
1100        $this->_code .= <<<CODE
1101    /**
1102     * PHP4 Compatible Constructor
1103     *
1104     * @param array  \$options
1105     * @access public
1106     */
1107    function Text_Highlighter_{$this->language}(\$options=array())
1108    {
1109        \$this->__construct(\$options);
1110    }
1111
1112
1113    /**
1114     *  Constructor
1115     *
1116     * @param array  \$options
1117     * @access public
1118     */
1119    function __construct(\$options=array())
1120    {
1121
1122CODE;
1123        $this->_code .= <<<CODE
1124
1125        \$this->_options = \$options;
1126CODE;
1127        $states = array();
1128        $i = 0;
1129        foreach ($syntax['blocks'] as $name => $block) {
1130            if ($block['type'] == 'region') {
1131                $states[$name] = $i++;
1132            }
1133        }
1134        $regs = array();
1135        $counts = array();
1136        $delim = array();
1137        $inner = array();
1138        $end = array();
1139        $stat = array();
1140        $keywords = array();
1141        $parts = array();
1142        $kwmap = array();
1143        $subst = array();
1144        $re = array();
1145        $ce = array();
1146        $rd = array();
1147        $in = array();
1148        $st = array();
1149        $kw = array();
1150        $sb = array();
1151        foreach ($syntax['toplevel'] as $name) {
1152            $block = $syntax['blocks'][$name];
1153            if ($block['type'] == 'block') {
1154                $kwm = array();
1155                $re[] = '(' . $block['match'] . ')';
1156                $ce[] = $this->_countSubpatterns($block['match']);
1157                $rd[] = '';
1158                $sb[] = false;;
1159                $st[] = -1;
1160                foreach ($syntax['keywords'] as $kwname => $kwgroup) {
1161                    if ($kwgroup['inherits'] != $name) {
1162                        continue;
1163                    }
1164                    $gre = implode('|', array_keys($kwgroup['match']));
1165                    if (!$kwgroup['case']) {
1166                        $gre = '(?i)' . $gre;
1167                    }
1168                    $kwm[$kwname][] =  $gre;
1169                    $kwmap[$kwname] = $kwgroup['innerGroup'];
1170                }
1171                foreach ($kwm as $g => $ma) {
1172                    $kwm[$g] = '/^(' . implode(')|(', $ma) . ')$/';
1173                }
1174                $kw[] = $kwm;
1175            } else {
1176                $kw[] = -1;
1177                $re[] = '(' . $block['start'] . ')';
1178                $ce[] = $this->_countSubpatterns($block['start']);
1179                $rd[] = $block['delimGroup'];
1180                $st[] = $states[$name];
1181                $sb[] = $block['remember'];
1182            }
1183            $in[] = $block['innerGroup'];
1184        }
1185        $re = implode('|', $re);
1186        $regs[-1] = '/' . $re . '/';
1187        $counts[-1] = $ce;
1188        $delim[-1] = $rd;
1189        $inner[-1] = $in;
1190        $stat[-1] = $st;
1191        $keywords[-1] = $kw;
1192        $subst[-1] = $sb;
1193
1194        foreach ($syntax['blocks'] as $ablock) {
1195            if ($ablock['type'] != 'region') {
1196                continue;
1197            }
1198            $end[] = '/' . $ablock['end'] . '/';
1199            $re = array();
1200            $ce = array();
1201            $rd = array();
1202            $in = array();
1203            $st = array();
1204            $kw = array();
1205            $pc = array();
1206            $sb = array();
1207            foreach ((array)@$ablock['lookfor'] as $name) {
1208                $block = $syntax['blocks'][$name];
1209                if (isset($block['partClass'])) {
1210                    $pc[] = $block['partClass'];
1211                } else {
1212                    $pc[] = null;
1213                }
1214                if ($block['type'] == 'block') {
1215                    $kwm = array();;
1216                    $re[] = '(' . $block['match'] . ')';
1217                    $ce[] = $this->_countSubpatterns($block['match']);
1218                    $rd[] = '';
1219                    $sb[] = false;
1220                    $st[] = -1;
1221                    foreach ($syntax['keywords'] as $kwname => $kwgroup) {
1222                        if ($kwgroup['inherits'] != $name) {
1223                            continue;
1224                        }
1225                        $gre = implode('|', array_keys($kwgroup['match']));
1226                        if (!$kwgroup['case']) {
1227                            $gre = '(?i)' . $gre;
1228                        }
1229                        $kwm[$kwname][] =  $gre;
1230                        $kwmap[$kwname] = $kwgroup['innerGroup'];
1231                    }
1232                    foreach ($kwm as $g => $ma) {
1233                        $kwm[$g] = '/^(' . implode(')|(', $ma) . ')$/';
1234                    }
1235                    $kw[] = $kwm;
1236                } else {
1237                    $sb[] = $block['remember'];
1238                    $kw[] = -1;
1239                    $re[] = '(' . $block['start'] . ')';
1240                    $ce[] = $this->_countSubpatterns($block['start']);
1241                    $rd[] = $block['delimGroup'];
1242                    $st[] = $states[$name];
1243                }
1244                $in[] = $block['innerGroup'];
1245            }
1246            $re = implode('|', $re);
1247            $regs[] = '/' . $re . '/';
1248            $counts[] = $ce;
1249            $delim[] = $rd;
1250            $inner[] = $in;
1251            $stat[] = $st;
1252            $keywords[] = $kw;
1253            $parts[] = $pc;
1254            $subst[] = $sb;
1255        }
1256
1257
1258        $this->_code .= "\n        \$this->_regs = " . $this->_exportArray($regs);
1259        $this->_code .= ";\n        \$this->_counts = " .$this->_exportArray($counts);
1260        $this->_code .= ";\n        \$this->_delim = " .$this->_exportArray($delim);
1261        $this->_code .= ";\n        \$this->_inner = " .$this->_exportArray($inner);
1262        $this->_code .= ";\n        \$this->_end = " .$this->_exportArray($end);
1263        $this->_code .= ";\n        \$this->_states = " .$this->_exportArray($stat);
1264        $this->_code .= ";\n        \$this->_keywords = " .$this->_exportArray($keywords);
1265        $this->_code .= ";\n        \$this->_parts = " .$this->_exportArray($parts);
1266        $this->_code .= ";\n        \$this->_subst = " .$this->_exportArray($subst);
1267        $this->_code .= ";\n        \$this->_conditions = " .$this->_exportArray($conditions);
1268        $this->_code .= ";\n        \$this->_kwmap = " .$this->_exportArray($kwmap);
1269        $this->_code .= ";\n        \$this->_defClass = '" .$this->_defClass . '\'';
1270        $this->_code .= <<<CODE
1271;
1272        \$this->_checkDefines();
1273    }
1274
1275}
1276CODE;
1277}
1278
1279// }}}
1280}
1281
1282
1283/*
1284* Local variables:
1285* tab-width: 4
1286* c-basic-offset: 4
1287* c-hanging-comment-ender-p: nil
1288* End:
1289*/
1290
1291?>
1292