PageRenderTime 43ms CodeModel.GetById 14ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/usr/plugins/Creole/Creole_Wiki.php

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