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