1<?php 2// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: 3/** 4 * Parse structured wiki text and render into arbitrary formats such as XHTML. 5 * 6 * PHP versions 4 and 5 7 * 8 * @category Text 9 * @package Text_Wiki 10 * @author Paul M. Jones <pmjones@php.net> 11 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 12 * @version CVS: $Id$ 13 * @link http://pear.php.net/package/Text_Wiki 14 */ 15 16/** 17 * The baseline abstract parser class. 18 */ 19require_once 'Text/Wiki/Parse.php'; 20 21/** 22 * The baseline abstract render class. 23 */ 24require_once 'Text/Wiki/Render.php'; 25 26/** 27 * Parse structured wiki text and render into arbitrary formats such as XHTML. 28 * 29 * This is the "master" class for handling the management and convenience 30 * functions to transform Wiki-formatted text. 31 * 32 * @category Text 33 * @package Text_Wiki 34 * @author Paul M. Jones <pmjones@php.net> 35 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 36 * @version Release: @package_version@ 37 * @link http://pear.php.net/package/Text_Wiki 38 */ 39class Text_Wiki { 40 41 /** 42 * 43 * The default list of rules, in order, to apply to the source text. 44 * 45 * @access public 46 * 47 * @var array 48 * 49 */ 50 51 var $rules = array( 52 'Prefilter', 53 'Delimiter', 54 'Code', 55 'Function', 56 'Html', 57 'Raw', 58 'Include', 59 'Embed', 60 'Anchor', 61 'Heading', 62 'Toc', 63 'Horiz', 64 'Break', 65 'Blockquote', 66 'List', 67 'Deflist', 68 'Table', 69 'Image', 70 'Phplookup', 71 'Center', 72 'Newline', 73 'Paragraph', 74 'Url', 75 'Freelink', 76 'Interwiki', 77 'Wikilink', 78 'Colortext', 79 'Strong', 80 'Bold', 81 'Emphasis', 82 'Italic', 83 'Underline', 84 'Tt', 85 'Superscript', 86 'Subscript', 87 'Revise', 88 'Tighten' 89 ); 90 91 92 /** 93 * 94 * The list of rules to not-apply to the source text. 95 * 96 * @access public 97 * 98 * @var array 99 * 100 */ 101 102 var $disable = array( 103 'Html', 104 'Include', 105 'Embed' 106 ); 107 108 109 /** 110 * 111 * Custom configuration for rules at the parsing stage. 112 * 113 * In this array, the key is the parsing rule name, and the value is 114 * an array of key-value configuration pairs corresponding to the $conf 115 * property in the target parsing rule. 116 * 117 * For example: 118 * 119 * <code> 120 * $parseConf = array( 121 * 'Include' => array( 122 * 'base' => '/path/to/scripts/' 123 * ) 124 * ); 125 * </code> 126 * 127 * Note that most default rules do not need any parsing configuration. 128 * 129 * @access public 130 * 131 * @var array 132 * 133 */ 134 135 var $parseConf = array(); 136 137 138 /** 139 * 140 * Custom configuration for rules at the rendering stage. 141 * 142 * Because rendering may be different for each target format, the 143 * first-level element in this array is always a format name (e.g., 144 * 'Xhtml'). 145 * 146 * Within that first level element, the subsequent elements match the 147 * $parseConf format. That is, the sub-key is the rendering rule name, 148 * and the sub-value is an array of key-value configuration pairs 149 * corresponding to the $conf property in the target rendering rule. 150 * 151 * @access public 152 * 153 * @var array 154 * 155 */ 156 157 var $renderConf = array( 158 'Docbook' => array(), 159 'Latex' => array(), 160 'Pdf' => array(), 161 'Plain' => array(), 162 'Rtf' => array(), 163 'Xhtml' => array() 164 ); 165 166 167 /** 168 * 169 * Custom configuration for the output format itself. 170 * 171 * Even though Text_Wiki will render the tokens from parsed text, 172 * the format itself may require some configuration. For example, 173 * RTF needs to know font names and sizes, PDF requires page layout 174 * information, and DocBook needs a section hierarchy. This array 175 * matches the $conf property of the the format-level renderer 176 * (e.g., Text_Wiki_Render_Xhtml). 177 * 178 * In this array, the key is the rendering format name, and the value is 179 * an array of key-value configuration pairs corresponding to the $conf 180 * property in the rendering format rule. 181 * 182 * @access public 183 * 184 * @var array 185 * 186 */ 187 188 var $formatConf = array( 189 'Docbook' => array(), 190 'Latex' => array(), 191 'Pdf' => array(), 192 'Plain' => array(), 193 'Rtf' => array(), 194 'Xhtml' => array() 195 ); 196 197 198 /** 199 * 200 * The delimiter for token numbers of parsed elements in source text. 201 * 202 * @access public 203 * 204 * @var string 205 * 206 */ 207 208 var $delim = "\31"; 209 210 211 /** 212 * 213 * The tokens generated by rules as the source text is parsed. 214 * 215 * As Text_Wiki applies rule classes to the source text, it will 216 * replace portions of the text with a delimited token number. This 217 * is the array of those tokens, representing the replaced text and 218 * any options set by the parser for that replaced text. 219 * 220 * The tokens array is sequential; each element is itself a sequential 221 * array where element 0 is the name of the rule that generated the 222 * token, and element 1 is an associative array where the key is an 223 * option name and the value is an option value. 224 * 225 * @access private 226 * 227 * @var array 228 * 229 */ 230 231 var $tokens = array(); 232 233 /** 234 * How many tokens generated pro rules. 235 * 236 * Intended to load only necessary render objects 237 * 238 * @access private 239 * @var array 240 */ 241 var $_countRulesTokens = array(); 242 243 244 /** 245 * 246 * The source text to which rules will be applied. 247 * 248 * This text will be transformed in-place, which means that it will 249 * change as the rules are applied. 250 * 251 * @access private 252 * 253 * @var string 254 * 255 */ 256 257 var $source = ''; 258 259 /** 260 * The output text 261 * 262 * @var string 263 */ 264 var $output = ''; 265 266 267 /** 268 * 269 * Array of rule parsers. 270 * 271 * Text_Wiki creates one instance of every rule that is applied to 272 * the source text; this array holds those instances. The array key 273 * is the rule name, and the array value is an instance of the rule 274 * class. 275 * 276 * @access private 277 * 278 * @var array 279 * 280 */ 281 282 var $parseObj = array(); 283 284 285 /** 286 * 287 * Array of rule renderers. 288 * 289 * Text_Wiki creates one instance of every rule that is applied to 290 * the source text; this array holds those instances. The array key 291 * is the rule name, and the array value is an instance of the rule 292 * class. 293 * 294 * @access private 295 * 296 * @var array 297 * 298 */ 299 300 var $renderObj = array(); 301 302 303 /** 304 * 305 * Array of format renderers. 306 * 307 * @access private 308 * 309 * @var array 310 * 311 */ 312 313 var $formatObj = array(); 314 315 316 /** 317 * 318 * Array of paths to search, in order, for parsing and rendering rules. 319 * 320 * @access private 321 * 322 * @var array 323 * 324 */ 325 326 var $path = array( 327 'parse' => array(), 328 'render' => array() 329 ); 330 331 332 333 /** 334 * 335 * The directory separator character. 336 * 337 * @access private 338 * 339 * @var string 340 * 341 */ 342 343 var $_dirSep = DIRECTORY_SEPARATOR; 344 345 /** 346 * Temporary configuration variable 347 * 348 * @var string 349 */ 350 var $renderingType = 'normal'; 351 352 /** 353 * Stack of rendering callbacks 354 * 355 * @var Array 356 */ 357 var $_renderCallbacks = array(); 358 359 /** 360 * Current output block 361 * 362 * @var string 363 */ 364 var $_block; 365 366 /** 367 * A stack of blocks 368 * 369 * @param Array 370 */ 371 var $_blocks; 372 373 /** 374 * A fix for PHP5. 375 * 376 * **DEPRECATED** 377 * Please use the singleton() or factory() methods. 378 * 379 * @param mixed $rules null or an array. 380 * 381 * @return $this 382 * @uses self::Text_Wiki() 383 */ 384 function __construct($rules = null) 385 { 386 if (is_array($rules)) { 387 $this->rules = array(); 388 foreach ($rules as $rule) { 389 $this->rules[] = ucfirst($rule); 390 } 391 } 392 393 $this->addPath( 394 'parse', 395 $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/' 396 ); 397 $this->addPath( 398 'render', 399 $this->fixPath(dirname(__FILE__)) . 'Wiki/Render/' 400 ); 401 } 402 403 /** 404 * Singleton. 405 * 406 * This avoids instantiating multiple Text_Wiki instances where a number 407 * of objects are required in one call, e.g. to save memory in a 408 * CMS invironment where several parsers are required in a single page. 409 * 410 * $single = singleton(); 411 * 412 * or 413 * 414 * $single = singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function', 415 * 'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz', 416 * 'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup', 417 * 'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink', 418 * 'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt', 419 * 'Superscript', 'Subscript', 'Revise', 'Tighten')); 420 * 421 * Call using a subset of this list. The order of passing rulesets in the 422 * $rules array is important! 423 * 424 * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf() 425 * as usual for a constructed object of this class. 426 * 427 * The internal static array of singleton objects has no index on the parser 428 * rules, the only index is on the parser name. So if you call this multiple 429 * times with different rules but the same parser name, you will get the same 430 * static parser object each time. 431 * 432 * @since Method available since Release 1.1.0 433 * @param string $parser The parser to be used (defaults to 'Default'). 434 * @param array $rules The set of rules to instantiate the object. This 435 * will only be used when the first call to singleton is made, if included 436 * in further calls it will be effectively ignored. 437 * @return &object a reference to the Text_Wiki unique instantiation. 438 */ 439 public static function singleton($parser = 'Default', $rules = null) 440 { 441 static $only = array(); 442 if (!isset($only[$parser])) { 443 $ret = Text_Wiki::factory($parser, $rules); 444 if (Text_Wiki::isError($ret)) { 445 return $ret; 446 } 447 $only[$parser] = $ret; 448 } 449 return $only[$parser]; 450 } 451 452 /** 453 * Returns a Text_Wiki Parser class for the specified parser. 454 * 455 * @param string $parser The name of the parse to instantiate 456 * you need to have Text_Wiki_XXX installed to use $parser = 'XXX', it's E_FATAL 457 * @param array $rules The rules to pass into the constructor 458 * {@see Text_Wiki::singleton} for a list of rules 459 * @return Text_Wiki a Parser object extended from Text_Wiki 460 */ 461 public static function factory($parser = 'Default', $rules = null) 462 { 463 $class = 'Text_Wiki_' . $parser; 464 $file = str_replace('_', '/', $class).'.php'; 465 if (!class_exists($class)) { 466 require_once $file; 467 if (!class_exists($class)) { 468 return Text_Wiki::error( 469 'Class ' . $class . ' does not exist after requiring '. $file . 470 ', install package ' . $class . "\n"); 471 } 472 } 473 474 return new $class($rules); 475 } 476 477 /** 478 * 479 * Set parser configuration for a specific rule and key. 480 * 481 * @access public 482 * 483 * @param string $rule The parse rule to set config for. 484 * 485 * @param array|string $arg1 The full config array to use for the 486 * parse rule, or a conf key in that array. 487 * 488 * @param string $arg2 The config value for the key. 489 * 490 * @return void 491 * 492 */ 493 494 function setParseConf($rule, $arg1, $arg2 = null) 495 { 496 $rule = ucwords(strtolower($rule)); 497 498 if (! isset($this->parseConf[$rule])) { 499 $this->parseConf[$rule] = array(); 500 } 501 502 // if first arg is an array, use it as the entire 503 // conf array for the rule. otherwise, treat arg1 504 // as a key and arg2 as a value for the rule conf. 505 if (is_array($arg1)) { 506 $this->parseConf[$rule] = $arg1; 507 } else { 508 $this->parseConf[$rule][$arg1] = $arg2; 509 } 510 } 511 512 513 /** 514 * 515 * Get parser configuration for a specific rule and key. 516 * 517 * @access public 518 * 519 * @param string $rule The parse rule to get config for. 520 * 521 * @param string $key A key in the conf array; if null, 522 * returns the entire conf array. 523 * 524 * @return mixed The whole conf array if no key is specified, 525 * or the specific conf key value. 526 * 527 */ 528 529 function getParseConf($rule, $key = null) 530 { 531 $rule = ucwords(strtolower($rule)); 532 533 // the rule does not exist 534 if (! isset($this->parseConf[$rule])) { 535 return null; 536 } 537 538 // no key requested, return the whole array 539 if (is_null($key)) { 540 return $this->parseConf[$rule]; 541 } 542 543 // does the requested key exist? 544 if (isset($this->parseConf[$rule][$key])) { 545 // yes, return that value 546 return $this->parseConf[$rule][$key]; 547 } else { 548 // no 549 return null; 550 } 551 } 552 553 554 /** 555 * 556 * Set renderer configuration for a specific format, rule, and key. 557 * 558 * @access public 559 * 560 * @param string $format The render format to set config for. 561 * 562 * @param string $rule The render rule to set config for in the format. 563 * 564 * @param array|string $arg1 The config array, or the config key 565 * within the render rule. 566 * 567 * @param string $arg2 The config value for the key. 568 * 569 * @return void 570 * 571 */ 572 573 function setRenderConf($format, $rule, $arg1, $arg2 = null) 574 { 575 $format = ucwords(strtolower($format)); 576 $rule = ucwords(strtolower($rule)); 577 578 if (! isset($this->renderConf[$format])) { 579 $this->renderConf[$format] = array(); 580 } 581 582 if (! isset($this->renderConf[$format][$rule])) { 583 $this->renderConf[$format][$rule] = array(); 584 } 585 586 // if first arg is an array, use it as the entire 587 // conf array for the render rule. otherwise, treat arg1 588 // as a key and arg2 as a value for the render rule conf. 589 if (is_array($arg1)) { 590 $this->renderConf[$format][$rule] = $arg1; 591 } else { 592 $this->renderConf[$format][$rule][$arg1] = $arg2; 593 } 594 } 595 596 597 /** 598 * 599 * Get renderer configuration for a specific format, rule, and key. 600 * 601 * @access public 602 * 603 * @param string $format The render format to get config for. 604 * 605 * @param string $rule The render format rule to get config for. 606 * 607 * @param string $key A key in the conf array; if null, 608 * returns the entire conf array. 609 * 610 * @return mixed The whole conf array if no key is specified, 611 * or the specific conf key value. 612 * 613 */ 614 615 function getRenderConf($format, $rule, $key = null) 616 { 617 $format = ucwords(strtolower($format)); 618 $rule = ucwords(strtolower($rule)); 619 620 if (! isset($this->renderConf[$format]) || 621 ! isset($this->renderConf[$format][$rule])) { 622 return null; 623 } 624 625 // no key requested, return the whole array 626 if (is_null($key)) { 627 return $this->renderConf[$format][$rule]; 628 } 629 630 // does the requested key exist? 631 if (isset($this->renderConf[$format][$rule][$key])) { 632 // yes, return that value 633 return $this->renderConf[$format][$rule][$key]; 634 } else { 635 // no 636 return null; 637 } 638 639 } 640 641 /** 642 * 643 * Set format configuration for a specific rule and key. 644 * 645 * @access public 646 * 647 * @param string $format The format to set config for. 648 * 649 * @param string $key The config key within the format. 650 * 651 * @param string $val The config value for the key. 652 * 653 * @return void 654 * 655 */ 656 657 function setFormatConf($format, $arg1, $arg2 = null) 658 { 659 if (! isset($this->formatConf[$format]) || ! is_array($this->formatConf[$format])) { 660 $this->formatConf[$format] = array(); 661 } 662 663 // if first arg is an array, use it as the entire 664 // conf array for the format. otherwise, treat arg1 665 // as a key and arg2 as a value for the format conf. 666 if (is_array($arg1)) { 667 $this->formatConf[$format] = $arg1; 668 } else { 669 $this->formatConf[$format][$arg1] = $arg2; 670 } 671 } 672 673 674 675 /** 676 * 677 * Get configuration for a specific format and key. 678 * 679 * @access public 680 * 681 * @param string $format The format to get config for. 682 * 683 * @param mixed $key A key in the conf array; if null, 684 * returns the entire conf array. 685 * 686 * @return mixed The whole conf array if no key is specified, 687 * or the specific conf key value. 688 * 689 */ 690 691 function getFormatConf($format, $key = null) 692 { 693 // the format does not exist 694 if (! isset($this->formatConf[$format])) { 695 return null; 696 } 697 698 // no key requested, return the whole array 699 if (is_null($key)) { 700 return $this->formatConf[$format]; 701 } 702 703 // does the requested key exist? 704 if (isset($this->formatConf[$format][$key])) { 705 // yes, return that value 706 return $this->formatConf[$format][$key]; 707 } else { 708 // no 709 return null; 710 } 711 } 712 713 714 /** 715 * 716 * Inserts a rule into to the rule set. 717 * 718 * @access public 719 * 720 * @param string $name The name of the rule. Should be different from 721 * all other keys in the rule set. 722 * 723 * @param string $tgt The rule after which to insert this new rule. By 724 * default (null) the rule is inserted at the end; if set to '', inserts 725 * at the beginning. 726 * 727 * @return void 728 * 729 */ 730 731 function insertRule($name, $tgt = null) 732 { 733 $name = ucwords(strtolower($name)); 734 if (! is_null($tgt)) { 735 $tgt = ucwords(strtolower($tgt)); 736 } 737 738 // does the rule name to be inserted already exist? 739 if (in_array($name, $this->rules)) { 740 // yes, return 741 return null; 742 } 743 744 // the target name is not null, and not '', but does not exist 745 // in the list of rules. this means we're trying to insert after 746 // a target key, but the target key isn't there. 747 if (! is_null($tgt) && $tgt != '' && 748 ! in_array($tgt, $this->rules)) { 749 return false; 750 } 751 752 // if $tgt is null, insert at the end. We know this is at the 753 // end (instead of resetting an existing rule) becuase we exited 754 // at the top of this method if the rule was already in place. 755 if (is_null($tgt)) { 756 $this->rules[] = $name; 757 return true; 758 } 759 760 // save a copy of the current rules, then reset the rule set 761 // so we can insert in the proper place later. 762 // where to insert the rule? 763 if ($tgt == '') { 764 // insert at the beginning 765 array_unshift($this->rules, $name); 766 return true; 767 } 768 769 // insert after the named rule 770 $tmp = $this->rules; 771 $this->rules = array(); 772 773 foreach ($tmp as $val) { 774 $this->rules[] = $val; 775 if ($val == $tgt) { 776 $this->rules[] = $name; 777 } 778 } 779 780 return true; 781 782 } 783 784 785 /** 786 * 787 * Delete (remove or unset) a rule from the $rules property. 788 * 789 * @access public 790 * 791 * @param string $rule The name of the rule to remove. 792 * 793 * @return void 794 * 795 */ 796 797 function deleteRule($name) 798 { 799 $name = ucwords(strtolower($name)); 800 $key = array_search($name, $this->rules); 801 if ($key !== false) { 802 unset($this->rules[$key]); 803 } 804 } 805 806 807 /** 808 * 809 * Change from one rule to another in-place. 810 * 811 * @access public 812 * 813 * @param string $old The name of the rule to change from. 814 * 815 * @param string $new The name of the rule to change to. 816 * 817 * @return void 818 * 819 */ 820 821 function changeRule($old, $new) 822 { 823 $old = ucwords(strtolower($old)); 824 $new = ucwords(strtolower($new)); 825 $key = array_search($old, $this->rules); 826 if ($key !== false) { 827 // delete the new name , case it was already there 828 $this->deleteRule($new); 829 $this->rules[$key] = $new; 830 } 831 } 832 833 834 /** 835 * 836 * Enables a rule so that it is applied when parsing. 837 * 838 * @access public 839 * 840 * @param string $rule The name of the rule to enable. 841 * 842 * @return void 843 * 844 */ 845 846 function enableRule($name) 847 { 848 $name = ucwords(strtolower($name)); 849 $key = array_search($name, $this->disable); 850 if ($key !== false) { 851 unset($this->disable[$key]); 852 } 853 } 854 855 856 /** 857 * 858 * Disables a rule so that it is not applied when parsing. 859 * 860 * @access public 861 * 862 * @param string $rule The name of the rule to disable. 863 * 864 * @return void 865 * 866 */ 867 868 function disableRule($name) 869 { 870 $name = ucwords(strtolower($name)); 871 $key = array_search($name, $this->disable); 872 if ($key === false) { 873 $this->disable[] = $name; 874 } 875 } 876 877 878 /** 879 * 880 * Parses and renders the text passed to it, and returns the results. 881 * 882 * First, the method parses the source text, applying rules to the 883 * text as it goes. These rules will modify the source text 884 * in-place, replacing some text with delimited tokens (and 885 * populating the $this->tokens array as it goes). 886 * 887 * Next, the method renders the in-place tokens into the requested 888 * output format. 889 * 890 * Finally, the method returns the transformed text. Note that the 891 * source text is transformed in place; once it is transformed, it is 892 * no longer the same as the original source text. 893 * 894 * @access public 895 * 896 * @param string $text The source text to which wiki rules should be 897 * applied, both for parsing and for rendering. 898 * 899 * @param string $format The target output format, typically 'xhtml'. 900 * If a rule does not support a given format, the output from that 901 * rule is rule-specific. 902 * 903 * @return string The transformed wiki text. 904 * 905 */ 906 907 function transform($text, $format = 'Xhtml') 908 { 909 $this->parse($text); 910 return $this->render($format); 911 } 912 913 914 /** 915 * 916 * Sets the $_source text property, then parses it in place and 917 * retains tokens in the $_tokens array property. 918 * 919 * @access public 920 * 921 * @param string $text The source text to which wiki rules should be 922 * applied, both for parsing and for rendering. 923 * 924 * @return void 925 * 926 */ 927 928 function parse($text) 929 { 930 // set the object property for the source text 931 $this->source = $text; 932 933 // reset the tokens. 934 $this->tokens = array(); 935 $this->_countRulesTokens = array(); 936 937 // apply the parse() method of each requested rule to the source 938 // text. 939 foreach ($this->rules as $name) { 940 // do not parse the rules listed in $disable 941 if (! in_array($name, $this->disable)) { 942 943 // load the parsing object 944 $this->loadParseObj($name); 945 946 // load may have failed; only parse if 947 // an object is in the array now 948 if (is_object($this->parseObj[$name])) { 949 $this->parseObj[$name]->parse(); 950 } 951 } 952 } 953 } 954 955 956 /** 957 * 958 * Renders tokens back into the source text, based on the requested format. 959 * 960 * @access public 961 * 962 * @param string $format The target output format, typically 'xhtml'. 963 * If a rule does not support a given format, the output from that 964 * rule is rule-specific. 965 * 966 * @return string The transformed wiki text. 967 * 968 */ 969 970 function render($format = 'Xhtml') 971 { 972 // the rendering method we're going to use from each rule 973 $format = ucwords(strtolower($format)); 974 975 // the eventual output text 976 $this->output = ''; 977 978 // when passing through the parsed source text, keep track of when 979 // we are in a delimited section 980 $in_delim = false; 981 982 // when in a delimited section, capture the token key number 983 $key = ''; 984 985 // load the format object, or crap out if we can't find it 986 $result = $this->loadFormatObj($format); 987 if ($this->isError($result)) { 988 return $result; 989 } 990 991 // pre-rendering activity 992 if (is_object($this->formatObj[$format])) { 993 $this->output .= $this->formatObj[$format]->pre(); 994 } 995 996 // load the render objects 997 foreach (array_keys($this->_countRulesTokens) as $rule) { 998 $this->loadRenderObj($format, $rule); 999 } 1000 1001 if ($this->renderingType == 'preg') { 1002 $this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/', 1003 array(&$this, '_renderToken'), 1004 $this->source); 1005 /* 1006//Damn strtok()! Why does it "skip" empty parts of the string. It's useless now! 1007 } elseif ($this->renderingType == 'strtok') { 1008 echo '<pre>'.htmlentities($this->source).'</pre>'; 1009 $t = strtok($this->source, $this->delim); 1010 $inToken = true; 1011 $i = 0; 1012 while ($t !== false) { 1013 echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>'; 1014 if ($inToken) { 1015 //$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]); 1016 } else { 1017 $this->output .= $t; 1018 } 1019 $inToken = !$inToken; 1020 $t = strtok($this->delim); 1021 ++$i; 1022 } 1023 */ 1024 } else { 1025 // pass through the parsed source text character by character 1026 $this->_block = ''; 1027 $tokenStack = array(); 1028 $k = strlen($this->source); 1029 for ($i = 0; $i < $k; $i++) { 1030 1031 // the current character 1032 $char = $this->source{$i}; 1033 1034 // are alredy in a delimited section? 1035 if ($in_delim) { 1036 1037 // yes; are we ending the section? 1038 if ($char == $this->delim) { 1039 1040 if (count($this->_renderCallbacks) == 0) { 1041 $this->output .= $this->_block; 1042 $this->_block = ''; 1043 } 1044 if (isset($opts['type'])) { 1045 if ($opts['type'] == 'start') { 1046 array_push($tokenStack, $rule); 1047 } elseif ($opts['type'] == 'end') { 1048 if ($tokenStack[count($tokenStack) - 1] != $rule) { 1049 return Text_Wiki::error('Unbalanced tokens, check your syntax'); 1050 } else { 1051 array_pop($tokenStack); 1052 } 1053 } 1054 } 1055 1056 // yes, get the replacement text for the delimited 1057 // token number and unset the flag. 1058 $key = (int)$key; 1059 $rule = $this->tokens[$key][0]; 1060 $opts = $this->tokens[$key][1]; 1061 $this->_block .= $this->renderObj[$rule]->token($opts); 1062 $in_delim = false; 1063 1064 } else { 1065 1066 // no, add to the delimited token key number 1067 $key .= $char; 1068 1069 } 1070 1071 } else { 1072 1073 // not currently in a delimited section. 1074 // are we starting into a delimited section? 1075 if ($char == $this->delim) { 1076 // yes, reset the previous key and 1077 // set the flag. 1078 $key = ''; 1079 $in_delim = true; 1080 1081 } else { 1082 // no, add to the output as-is 1083 $this->_block .= $char; 1084 } 1085 } 1086 } 1087 } 1088 1089 if (count($this->_renderCallbacks)) { 1090 return $this->error('Render callbacks left over after processing finished'); 1091 } 1092 /* 1093 while (count($this->_renderCallbacks)) { 1094 $this->popRenderCallback(); 1095 } 1096 */ 1097 if (strlen($this->_block)) { 1098 $this->output .= $this->_block; 1099 $this->_block = ''; 1100 } 1101 1102 // post-rendering activity 1103 if (is_object($this->formatObj[$format])) { 1104 $this->output .= $this->formatObj[$format]->post(); 1105 } 1106 1107 // return the rendered source text. 1108 return $this->output; 1109 } 1110 1111 /** 1112 * Renders a token, for use only as an internal callback 1113 * 1114 * @param array Matches from preg_rpelace_callback, [1] is the token number 1115 * @return string The rendered text for the token 1116 * @access private 1117 */ 1118 function _renderToken($matches) { 1119 return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]); 1120 } 1121 1122 function registerRenderCallback($callback) { 1123 $this->_blocks[] = $this->_block; 1124 $this->_block = ''; 1125 $this->_renderCallbacks[] = $callback; 1126 } 1127 1128 function popRenderCallback() { 1129 if (count($this->_renderCallbacks) == 0) { 1130 return Text_Wiki::error('Render callback popped when no render callbacks in stack'); 1131 } else { 1132 $callback = array_pop($this->_renderCallbacks); 1133 $this->_block = call_user_func($callback, $this->_block); 1134 if (count($this->_blocks)) { 1135 $parentBlock = array_pop($this->_blocks); 1136 $this->_block = $parentBlock.$this->_block; 1137 } 1138 if (count($this->_renderCallbacks) == 0) { 1139 $this->output .= $this->_block; 1140 $this->_block = ''; 1141 } 1142 } 1143 } 1144 1145 /** 1146 * 1147 * Returns the parsed source text with delimited token placeholders. 1148 * 1149 * @access public 1150 * 1151 * @return string The parsed source text. 1152 * 1153 */ 1154 1155 function getSource() 1156 { 1157 return $this->source; 1158 } 1159 1160 1161 /** 1162 * 1163 * Returns tokens that have been parsed out of the source text. 1164 * 1165 * @access public 1166 * 1167 * @param array $rules If an array of rule names is passed, only return 1168 * tokens matching these rule names. If no array is passed, return all 1169 * tokens. 1170 * 1171 * @return array An array of tokens. 1172 * 1173 */ 1174 1175 function getTokens($rules = null) 1176 { 1177 if (is_null($rules)) { 1178 return $this->tokens; 1179 } else { 1180 settype($rules, 'array'); 1181 $result = array(); 1182 foreach ($this->tokens as $key => $val) { 1183 if (in_array($val[0], $rules)) { 1184 $result[$key] = $val; 1185 } 1186 } 1187 return $result; 1188 } 1189 } 1190 1191 1192 /** 1193 * 1194 * Add a token to the Text_Wiki tokens array, and return a delimited 1195 * token number. 1196 * 1197 * @access public 1198 * 1199 * @param array $options An associative array of options for the new 1200 * token array element. The keys and values are specific to the 1201 * rule, and may or may not be common to other rule options. Typical 1202 * options keys are 'text' and 'type' but may include others. 1203 * 1204 * @param boolean $id_only If true, return only the token number, not 1205 * a delimited token string. 1206 * 1207 * @return string|int By default, return the number of the 1208 * newly-created token array element with a delimiter prefix and 1209 * suffix; however, if $id_only is set to true, return only the token 1210 * number (no delimiters). 1211 * 1212 */ 1213 1214 function addToken($rule, $options = array(), $id_only = false) 1215 { 1216 // increment the token ID number. note that if you parse 1217 // multiple times with the same Text_Wiki object, the ID number 1218 // will not reset to zero. 1219 static $id; 1220 if (! isset($id)) { 1221 $id = 0; 1222 } else { 1223 $id ++; 1224 } 1225 1226 // force the options to be an array 1227 settype($options, 'array'); 1228 1229 // add the token 1230 $this->tokens[$id] = array( 1231 0 => $rule, 1232 1 => $options 1233 ); 1234 if (!isset($this->_countRulesTokens[$rule])) { 1235 $this->_countRulesTokens[$rule] = 1; 1236 } else { 1237 ++$this->_countRulesTokens[$rule]; 1238 } 1239 1240 // return a value 1241 if ($id_only) { 1242 // return the last token number 1243 return $id; 1244 } else { 1245 // return the token number with delimiters 1246 return $this->delim . $id . $this->delim; 1247 } 1248 } 1249 1250 1251 /** 1252 * 1253 * Set or re-set a token with specific information, overwriting any 1254 * previous rule name and rule options. 1255 * 1256 * @access public 1257 * 1258 * @param int $id The token number to reset. 1259 * 1260 * @param int $rule The rule name to use. 1261 * 1262 * @param array $options An associative array of options for the 1263 * token array element. The keys and values are specific to the 1264 * rule, and may or may not be common to other rule options. Typical 1265 * options keys are 'text' and 'type' but may include others. 1266 * 1267 * @return void 1268 * 1269 */ 1270 1271 function setToken($id, $rule, $options = array()) 1272 { 1273 $oldRule = isset($this->tokens[$id]) ? $this->tokens[$id][0] : null; 1274 // reset the token 1275 $this->tokens[$id] = array( 1276 0 => $rule, 1277 1 => $options 1278 ); 1279 if ($rule != $oldRule) { 1280 if (isset($oldRule) && !($this->_countRulesTokens[$oldRule]--)) { 1281 unset($this->_countRulesTokens[$oldRule]); 1282 } 1283 if (!isset($this->_countRulesTokens[$rule])) { 1284 $this->_countRulesTokens[$rule] = 1; 1285 } else { 1286 ++$this->_countRulesTokens[$rule]; 1287 } 1288 } 1289 } 1290 1291 1292 /** 1293 * 1294 * Load a rule parser class file. 1295 * 1296 * @access public 1297 * 1298 * @return bool True if loaded, false if not. 1299 * 1300 */ 1301 1302 function loadParseObj($rule) 1303 { 1304 $rule = ucwords(strtolower($rule)); 1305 $file = $rule . '.php'; 1306 $class = "Text_Wiki_Parse_$rule"; 1307 1308 if (! class_exists($class)) { 1309 $loc = $this->findFile('parse', $file); 1310 if ($loc) { 1311 // found the class 1312 include_once $loc; 1313 } else { 1314 // can't find the class 1315 $this->parseObj[$rule] = null; 1316 // can't find the class 1317 return $this->error( 1318 "Parse rule '$rule' not found" 1319 ); 1320 } 1321 } 1322 1323 $this->parseObj[$rule] = new $class($this); 1324 1325 } 1326 1327 1328 /** 1329 * 1330 * Load a rule-render class file. 1331 * 1332 * @access public 1333 * 1334 * @return bool True if loaded, false if not. 1335 * 1336 */ 1337 1338 function loadRenderObj($format, $rule) 1339 { 1340 $format = ucwords(strtolower($format)); 1341 $rule = ucwords(strtolower($rule)); 1342 $file = "$format/$rule.php"; 1343 $class = "Text_Wiki_Render_$format" . "_$rule"; 1344 1345 if (! class_exists($class)) { 1346 // load the class 1347 $loc = $this->findFile('render', $file); 1348 if ($loc) { 1349 // found the class 1350 include_once $loc; 1351 } else { 1352 // can't find the class 1353 return $this->error( 1354 "Render rule '$rule' in format '$format' not found" 1355 ); 1356 } 1357 } 1358 1359 $this->renderObj[$rule] = new $class($this); 1360 } 1361 1362 1363 /** 1364 * 1365 * Load a format-render class file. 1366 * 1367 * @access public 1368 * 1369 * @return bool True if loaded, false if not. 1370 * 1371 */ 1372 1373 function loadFormatObj($format) 1374 { 1375 $format = ucwords(strtolower($format)); 1376 $file = $format . '.php'; 1377 $class = "Text_Wiki_Render_$format"; 1378 1379 if (! class_exists($class)) { 1380 $loc = $this->findFile('render', $file); 1381 if ($loc) { 1382 // found the class 1383 include_once $loc; 1384 } else { 1385 // can't find the class 1386 return $this->error( 1387 "Rendering format class '$class' not found" 1388 ); 1389 } 1390 } 1391 1392 $this->formatObj[$format] = new $class($this); 1393 } 1394 1395 1396 /** 1397 * 1398 * Add a path to a path array. 1399 * 1400 * @access public 1401 * 1402 * @param string $type The path-type to add (parse or render). 1403 * 1404 * @param string $dir The directory to add to the path-type. 1405 * 1406 * @return void 1407 * 1408 */ 1409 1410 function addPath($type, $dir) 1411 { 1412 $dir = $this->fixPath($dir); 1413 if (! isset($this->path[$type])) { 1414 $this->path[$type] = array($dir); 1415 } else { 1416 array_unshift($this->path[$type], $dir); 1417 } 1418 } 1419 1420 1421 /** 1422 * 1423 * Get the current path array for a path-type. 1424 * 1425 * @access public 1426 * 1427 * @param string $type The path-type to look up (plugin, filter, or 1428 * template). If not set, returns all path types. 1429 * 1430 * @return array The array of paths for the requested type. 1431 * 1432 */ 1433 1434 function getPath($type = null) 1435 { 1436 if (is_null($type)) { 1437 return $this->path; 1438 } elseif (! isset($this->path[$type])) { 1439 return array(); 1440 } else { 1441 return $this->path[$type]; 1442 } 1443 } 1444 1445 1446 /** 1447 * 1448 * Searches a series of paths for a given file. 1449 * 1450 * @param array $type The type of paths to search (template, plugin, 1451 * or filter). 1452 * 1453 * @param string $file The file name to look for. 1454 * 1455 * @return string|bool The full path and file name for the target file, 1456 * or boolean false if the file is not found in any of the paths. 1457 * 1458 */ 1459 1460 function findFile($type, $file) 1461 { 1462 // get the set of paths 1463 $set = $this->getPath($type); 1464 1465 // start looping through them 1466 foreach ($set as $path) { 1467 $fullname = $path . $file; 1468 if (file_exists($fullname) && is_readable($fullname)) { 1469 return $fullname; 1470 } 1471 } 1472 1473 // could not find the file in the set of paths 1474 return false; 1475 } 1476 1477 1478 /** 1479 * 1480 * Append a trailing '/' to paths, unless the path is empty. 1481 * 1482 * @access private 1483 * 1484 * @param string $path The file path to fix 1485 * 1486 * @return string The fixed file path 1487 * 1488 */ 1489 1490 function fixPath($path) 1491 { 1492 $len = strlen($this->_dirSep); 1493 1494 if (! empty($path) && 1495 substr($path, -1 * $len, $len) != $this->_dirSep) { 1496 return $path . $this->_dirSep; 1497 } else { 1498 return $path; 1499 } 1500 } 1501 1502 1503 /** 1504 * 1505 * Simple error-object generator. 1506 * 1507 * @access public 1508 * 1509 * @param string $message The error message. 1510 * 1511 * @return object PEAR_Error 1512 * 1513 */ 1514 1515 function error($message) 1516 { 1517 if (! class_exists('PEAR_Error')) { 1518 include_once 'PEAR.php'; 1519 } 1520 $pear = new PEAR(); 1521 return $pear->throwError($message); 1522 } 1523 1524 1525 /** 1526 * 1527 * Simple error checker. 1528 * 1529 * @param mixed $obj Check if this is a PEAR_Error object or not. 1530 * 1531 * @return bool True if a PEAR_Error, false if not. 1532 * 1533 */ 1534 1535 public static function isError(&$obj) 1536 { 1537 return is_a($obj, 'PEAR_Error'); 1538 } 1539} 1540