Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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