home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / Text / Wiki.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  39.5 KB  |  1,552 lines

  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: Wiki.php,v 1.51 2007/06/09 23:17:46 justinpatrin Exp $
  13.  * @link       http://pear.php.net/package/Text_Wiki
  14.  */
  15.  
  16. /**
  17.  * The baseline abstract parser class.
  18.  */
  19. require_once 'Text/Wiki/Parse.php';
  20.  
  21. /**
  22.  * The baseline abstract render class.
  23.  */
  24. require_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: 1.2.0
  37.  * @link       http://pear.php.net/package/Text_Wiki
  38.  */
  39. class 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 = "\xFF";
  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 = 'preg';
  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.     *
  375.     * Constructor.
  376.     *
  377.     * **DEPRECATED**
  378.     * Please use the singleton() or factory() methods.
  379.     *
  380.     * @access public
  381.     *
  382.     * @param array $rules The set of rules to load for this object.  Defaults
  383.     *   to null, which will load the default ruleset for this parser.
  384.     */
  385.  
  386.     function Text_Wiki($rules = null)
  387.     {
  388.         if (is_array($rules)) {
  389.             $this->rules = array();
  390.             foreach ($rules as $rule) {
  391.                 $this->rules[] = ucfirst($rule);
  392.             }
  393.         }
  394.  
  395.         $this->addPath(
  396.             'parse',
  397.             $this->fixPath(dirname(__FILE__)) . 'Wiki/Parse/Default/'
  398.         );
  399.         $this->addPath(
  400.             'render',
  401.             $this->fixPath(dirname(__FILE__)) . 'Wiki/Render/'
  402.         );
  403.  
  404.     }
  405.  
  406.     /**
  407.     * Singleton.
  408.     *
  409.     * This avoids instantiating multiple Text_Wiki instances where a number
  410.     * of objects are required in one call, e.g. to save memory in a
  411.     * CMS invironment where several parsers are required in a single page.
  412.     *
  413.     * $single = & singleton();
  414.     *
  415.     * or
  416.     *
  417.     * $single = & singleton('Parser', array('Prefilter', 'Delimiter', 'Code', 'Function',
  418.     *   'Html', 'Raw', 'Include', 'Embed', 'Anchor', 'Heading', 'Toc', 'Horiz',
  419.     *   'Break', 'Blockquote', 'List', 'Deflist', 'Table', 'Image', 'Phplookup',
  420.     *   'Center', 'Newline', 'Paragraph', 'Url', 'Freelink', 'Interwiki', 'Wikilink',
  421.     *   'Colortext', 'Strong', 'Bold', 'Emphasis', 'Italic', 'Underline', 'Tt',
  422.     *   'Superscript', 'Subscript', 'Revise', 'Tighten'));
  423.     *
  424.     * Call using a subset of this list.  The order of passing rulesets in the
  425.     * $rules array is important!
  426.     *
  427.     * After calling this, call $single->setParseConf(), setRenderConf() or setFormatConf()
  428.     * as usual for a constructed object of this class.
  429.     *
  430.     * The internal static array of singleton objects has no index on the parser
  431.     * rules, the only index is on the parser name.  So if you call this multiple
  432.     * times with different rules but the same parser name, you will get the same
  433.     * static parser object each time.
  434.     *
  435.     * @access public
  436.     * @static
  437.     * @since Method available since Release 1.1.0
  438.     * @param string $parser The parser to be used (defaults to 'Default').
  439.     * @param array $rules   The set of rules to instantiate the object. This
  440.     *    will only be used when the first call to singleton is made, if included
  441.     *    in further calls it will be effectively ignored.
  442.     * @return &object a reference to the Text_Wiki unique instantiation.
  443.     */
  444.     function &singleton($parser = 'Default', $rules = null)
  445.     {
  446.         static $only = array();
  447.         if (!isset($only[$parser])) {
  448.             $ret = & Text_Wiki::factory($parser, $rules);
  449.             if (Text_Wiki::isError($ret)) {
  450.                 return $ret;
  451.             }
  452.             $only[$parser] =& $ret;
  453.         }
  454.         return $only[$parser];
  455.     }
  456.  
  457.     /**
  458.      * Returns a Text_Wiki Parser class for the specified parser.
  459.      *
  460.      * @access public
  461.      * @static
  462.      * @param string $parser The name of the parse to instantiate
  463.      * you need to have Text_Wiki_XXX installed to use $parser = 'XXX', it's E_FATAL
  464.      * @param array $rules The rules to pass into the constructor
  465.      *    {@see Text_Wiki::singleton} for a list of rules
  466.      * @return Text_Wiki a Parser object extended from Text_Wiki
  467.      */
  468.     function &factory($parser = 'Default', $rules = null)
  469.     {
  470.         $class = 'Text_Wiki_' . $parser;
  471.         $file = str_replace('_', '/', $class).'.php';
  472.         if (!class_exists($class)) {
  473.             require_once $file;
  474.             if (!class_exists($class)) {
  475.                 return Text_Wiki::error(
  476.                     'Class ' . $class . ' does not exist after requiring '. $file .
  477.                         ', install package ' . $class . "\n");
  478.             }
  479.         }
  480.  
  481.         $obj =& new $class($rules);
  482.         return $obj;
  483.     }
  484.  
  485.     /**
  486.     *
  487.     * Set parser configuration for a specific rule and key.
  488.     *
  489.     * @access public
  490.     *
  491.     * @param string $rule The parse rule to set config for.
  492.     *
  493.     * @param array|string $arg1 The full config array to use for the
  494.     * parse rule, or a conf key in that array.
  495.     *
  496.     * @param string $arg2 The config value for the key.
  497.     *
  498.     * @return void
  499.     *
  500.     */
  501.  
  502.     function setParseConf($rule, $arg1, $arg2 = null)
  503.     {
  504.         $rule = ucwords(strtolower($rule));
  505.  
  506.         if (! isset($this->parseConf[$rule])) {
  507.             $this->parseConf[$rule] = array();
  508.         }
  509.  
  510.         // if first arg is an array, use it as the entire
  511.         // conf array for the rule.  otherwise, treat arg1
  512.         // as a key and arg2 as a value for the rule conf.
  513.         if (is_array($arg1)) {
  514.             $this->parseConf[$rule] = $arg1;
  515.         } else {
  516.             $this->parseConf[$rule][$arg1] = $arg2;
  517.         }
  518.     }
  519.  
  520.  
  521.     /**
  522.     *
  523.     * Get parser configuration for a specific rule and key.
  524.     *
  525.     * @access public
  526.     *
  527.     * @param string $rule The parse rule to get config for.
  528.     *
  529.     * @param string $key A key in the conf array; if null,
  530.     * returns the entire conf array.
  531.     *
  532.     * @return mixed The whole conf array if no key is specified,
  533.     * or the specific conf key value.
  534.     *
  535.     */
  536.  
  537.     function getParseConf($rule, $key = null)
  538.     {
  539.         $rule = ucwords(strtolower($rule));
  540.  
  541.         // the rule does not exist
  542.         if (! isset($this->parseConf[$rule])) {
  543.             return null;
  544.         }
  545.  
  546.         // no key requested, return the whole array
  547.         if (is_null($key)) {
  548.             return $this->parseConf[$rule];
  549.         }
  550.  
  551.         // does the requested key exist?
  552.         if (isset($this->parseConf[$rule][$key])) {
  553.             // yes, return that value
  554.             return $this->parseConf[$rule][$key];
  555.         } else {
  556.             // no
  557.             return null;
  558.         }
  559.     }
  560.  
  561.  
  562.     /**
  563.     *
  564.     * Set renderer configuration for a specific format, rule, and key.
  565.     *
  566.     * @access public
  567.     *
  568.     * @param string $format The render format to set config for.
  569.     *
  570.     * @param string $rule The render rule to set config for in the format.
  571.     *
  572.     * @param array|string $arg1 The config array, or the config key
  573.     * within the render rule.
  574.     *
  575.     * @param string $arg2 The config value for the key.
  576.     *
  577.     * @return void
  578.     *
  579.     */
  580.  
  581.     function setRenderConf($format, $rule, $arg1, $arg2 = null)
  582.     {
  583.         $format = ucwords(strtolower($format));
  584.         $rule = ucwords(strtolower($rule));
  585.  
  586.         if (! isset($this->renderConf[$format])) {
  587.             $this->renderConf[$format] = array();
  588.         }
  589.  
  590.         if (! isset($this->renderConf[$format][$rule])) {
  591.             $this->renderConf[$format][$rule] = array();
  592.         }
  593.  
  594.         // if first arg is an array, use it as the entire
  595.         // conf array for the render rule.  otherwise, treat arg1
  596.         // as a key and arg2 as a value for the render rule conf.
  597.         if (is_array($arg1)) {
  598.             $this->renderConf[$format][$rule] = $arg1;
  599.         } else {
  600.             $this->renderConf[$format][$rule][$arg1] = $arg2;
  601.         }
  602.     }
  603.  
  604.  
  605.     /**
  606.     *
  607.     * Get renderer configuration for a specific format, rule, and key.
  608.     *
  609.     * @access public
  610.     *
  611.     * @param string $format The render format to get config for.
  612.     *
  613.     * @param string $rule The render format rule to get config for.
  614.     *
  615.     * @param string $key A key in the conf array; if null,
  616.     * returns the entire conf array.
  617.     *
  618.     * @return mixed The whole conf array if no key is specified,
  619.     * or the specific conf key value.
  620.     *
  621.     */
  622.  
  623.     function getRenderConf($format, $rule, $key = null)
  624.     {
  625.         $format = ucwords(strtolower($format));
  626.         $rule = ucwords(strtolower($rule));
  627.  
  628.         if (! isset($this->renderConf[$format]) ||
  629.             ! isset($this->renderConf[$format][$rule])) {
  630.             return null;
  631.         }
  632.  
  633.         // no key requested, return the whole array
  634.         if (is_null($key)) {
  635.             return $this->renderConf[$format][$rule];
  636.         }
  637.  
  638.         // does the requested key exist?
  639.         if (isset($this->renderConf[$format][$rule][$key])) {
  640.             // yes, return that value
  641.             return $this->renderConf[$format][$rule][$key];
  642.         } else {
  643.             // no
  644.             return null;
  645.         }
  646.  
  647.     }
  648.  
  649.     /**
  650.     *
  651.     * Set format configuration for a specific rule and key.
  652.     *
  653.     * @access public
  654.     *
  655.     * @param string $format The format to set config for.
  656.     *
  657.     * @param string $key The config key within the format.
  658.     *
  659.     * @param string $val The config value for the key.
  660.     *
  661.     * @return void
  662.     *
  663.     */
  664.  
  665.     function setFormatConf($format, $arg1, $arg2 = null)
  666.     {
  667.         if (! is_array($this->formatConf[$format])) {
  668.             $this->formatConf[$format] = array();
  669.         }
  670.  
  671.         // if first arg is an array, use it as the entire
  672.         // conf array for the format.  otherwise, treat arg1
  673.         // as a key and arg2 as a value for the format conf.
  674.         if (is_array($arg1)) {
  675.             $this->formatConf[$format] = $arg1;
  676.         } else {
  677.             $this->formatConf[$format][$arg1] = $arg2;
  678.         }
  679.     }
  680.  
  681.  
  682.  
  683.     /**
  684.     *
  685.     * Get configuration for a specific format and key.
  686.     *
  687.     * @access public
  688.     *
  689.     * @param string $format The format to get config for.
  690.     *
  691.     * @param mixed $key A key in the conf array; if null,
  692.     * returns the entire conf array.
  693.     *
  694.     * @return mixed The whole conf array if no key is specified,
  695.     * or the specific conf key value.
  696.     *
  697.     */
  698.  
  699.     function getFormatConf($format, $key = null)
  700.     {
  701.         // the format does not exist
  702.         if (! isset($this->formatConf[$format])) {
  703.             return null;
  704.         }
  705.  
  706.         // no key requested, return the whole array
  707.         if (is_null($key)) {
  708.             return $this->formatConf[$format];
  709.         }
  710.  
  711.         // does the requested key exist?
  712.         if (isset($this->formatConf[$format][$key])) {
  713.             // yes, return that value
  714.             return $this->formatConf[$format][$key];
  715.         } else {
  716.             // no
  717.             return null;
  718.         }
  719.     }
  720.  
  721.  
  722.     /**
  723.     *
  724.     * Inserts a rule into to the rule set.
  725.     *
  726.     * @access public
  727.     *
  728.     * @param string $name The name of the rule.  Should be different from
  729.     * all other keys in the rule set.
  730.     *
  731.     * @param string $tgt The rule after which to insert this new rule.  By
  732.     * default (null) the rule is inserted at the end; if set to '', inserts
  733.     * at the beginning.
  734.     *
  735.     * @return void
  736.     *
  737.     */
  738.  
  739.     function insertRule($name, $tgt = null)
  740.     {
  741.         $name = ucwords(strtolower($name));
  742.         if (! is_null($tgt)) {
  743.             $tgt = ucwords(strtolower($tgt));
  744.         }
  745.  
  746.         // does the rule name to be inserted already exist?
  747.         if (in_array($name, $this->rules)) {
  748.             // yes, return
  749.             return null;
  750.         }
  751.  
  752.         // the target name is not null, and not '', but does not exist
  753.         // in the list of rules. this means we're trying to insert after
  754.         // a target key, but the target key isn't there.
  755.         if (! is_null($tgt) && $tgt != '' &&
  756.             ! in_array($tgt, $this->rules)) {
  757.             return false;
  758.         }
  759.  
  760.         // if $tgt is null, insert at the end.  We know this is at the
  761.         // end (instead of resetting an existing rule) becuase we exited
  762.         // at the top of this method if the rule was already in place.
  763.         if (is_null($tgt)) {
  764.             $this->rules[] = $name;
  765.             return true;
  766.         }
  767.  
  768.         // save a copy of the current rules, then reset the rule set
  769.         // so we can insert in the proper place later.
  770.         // where to insert the rule?
  771.         if ($tgt == '') {
  772.             // insert at the beginning
  773.             array_unshift($this->rules, $name);
  774.             return true;
  775.         }
  776.  
  777.         // insert after the named rule
  778.         $tmp = $this->rules;
  779.         $this->rules = array();
  780.  
  781.         foreach ($tmp as $val) {
  782.             $this->rules[] = $val;
  783.             if ($val == $tgt) {
  784.                 $this->rules[] = $name;
  785.             }
  786.         }
  787.  
  788.         return true;
  789.  
  790.     }
  791.  
  792.  
  793.     /**
  794.     *
  795.     * Delete (remove or unset) a rule from the $rules property.
  796.     *
  797.     * @access public
  798.     *
  799.     * @param string $rule The name of the rule to remove.
  800.     *
  801.     * @return void
  802.     *
  803.     */
  804.  
  805.     function deleteRule($name)
  806.     {
  807.         $name = ucwords(strtolower($name));
  808.         $key = array_search($name, $this->rules);
  809.         if ($key !== false) {
  810.             unset($this->rules[$key]);
  811.         }
  812.     }
  813.  
  814.  
  815.     /**
  816.     *
  817.     * Change from one rule to another in-place.
  818.     *
  819.     * @access public
  820.     *
  821.     * @param string $old The name of the rule to change from.
  822.     *
  823.     * @param string $new The name of the rule to change to.
  824.     *
  825.     * @return void
  826.     *
  827.     */
  828.  
  829.     function changeRule($old, $new)
  830.     {
  831.         $old = ucwords(strtolower($old));
  832.         $new = ucwords(strtolower($new));
  833.         $key = array_search($old, $this->rules);
  834.         if ($key !== false) {
  835.             // delete the new name , case it was already there
  836.             $this->deleteRule($new);
  837.             $this->rules[$key] = $new;
  838.         }
  839.     }
  840.  
  841.  
  842.     /**
  843.     *
  844.     * Enables a rule so that it is applied when parsing.
  845.     *
  846.     * @access public
  847.     *
  848.     * @param string $rule The name of the rule to enable.
  849.     *
  850.     * @return void
  851.     *
  852.     */
  853.  
  854.     function enableRule($name)
  855.     {
  856.         $name = ucwords(strtolower($name));
  857.         $key = array_search($name, $this->disable);
  858.         if ($key !== false) {
  859.             unset($this->disable[$key]);
  860.         }
  861.     }
  862.  
  863.  
  864.     /**
  865.     *
  866.     * Disables a rule so that it is not applied when parsing.
  867.     *
  868.     * @access public
  869.     *
  870.     * @param string $rule The name of the rule to disable.
  871.     *
  872.     * @return void
  873.     *
  874.     */
  875.  
  876.     function disableRule($name)
  877.     {
  878.         $name = ucwords(strtolower($name));
  879.         $key = array_search($name, $this->disable);
  880.         if ($key === false) {
  881.             $this->disable[] = $name;
  882.         }
  883.     }
  884.  
  885.  
  886.     /**
  887.     *
  888.     * Parses and renders the text passed to it, and returns the results.
  889.     *
  890.     * First, the method parses the source text, applying rules to the
  891.     * text as it goes.  These rules will modify the source text
  892.     * in-place, replacing some text with delimited tokens (and
  893.     * populating the $this->tokens array as it goes).
  894.     *
  895.     * Next, the method renders the in-place tokens into the requested
  896.     * output format.
  897.     *
  898.     * Finally, the method returns the transformed text.  Note that the
  899.     * source text is transformed in place; once it is transformed, it is
  900.     * no longer the same as the original source text.
  901.     *
  902.     * @access public
  903.     *
  904.     * @param string $text The source text to which wiki rules should be
  905.     * applied, both for parsing and for rendering.
  906.     *
  907.     * @param string $format The target output format, typically 'xhtml'.
  908.     *  If a rule does not support a given format, the output from that
  909.     * rule is rule-specific.
  910.     *
  911.     * @return string The transformed wiki text.
  912.     *
  913.     */
  914.  
  915.     function transform($text, $format = 'Xhtml')
  916.     {
  917.         $this->parse($text);
  918.         return $this->render($format);
  919.     }
  920.  
  921.  
  922.     /**
  923.     *
  924.     * Sets the $_source text property, then parses it in place and
  925.     * retains tokens in the $_tokens array property.
  926.     *
  927.     * @access public
  928.     *
  929.     * @param string $text The source text to which wiki rules should be
  930.     * applied, both for parsing and for rendering.
  931.     *
  932.     * @return void
  933.     *
  934.     */
  935.  
  936.     function parse($text)
  937.     {
  938.         // set the object property for the source text
  939.         $this->source = $text;
  940.  
  941.         // reset the tokens.
  942.         $this->tokens = array();
  943.         $this->_countRulesTokens = array();
  944.  
  945.         // apply the parse() method of each requested rule to the source
  946.         // text.
  947.         foreach ($this->rules as $name) {
  948.             // do not parse the rules listed in $disable
  949.             if (! in_array($name, $this->disable)) {
  950.  
  951.                 // load the parsing object
  952.                 $this->loadParseObj($name);
  953.  
  954.                 // load may have failed; only parse if
  955.                 // an object is in the array now
  956.                 if (is_object($this->parseObj[$name])) {
  957.                     $this->parseObj[$name]->parse();
  958.                 }
  959.             }
  960.         }
  961.     }
  962.  
  963.  
  964.     /**
  965.     *
  966.     * Renders tokens back into the source text, based on the requested format.
  967.     *
  968.     * @access public
  969.     *
  970.     * @param string $format The target output format, typically 'xhtml'.
  971.     * If a rule does not support a given format, the output from that
  972.     * rule is rule-specific.
  973.     *
  974.     * @return string The transformed wiki text.
  975.     *
  976.     */
  977.  
  978.     function render($format = 'Xhtml')
  979.     {
  980.         // the rendering method we're going to use from each rule
  981.         $format = ucwords(strtolower($format));
  982.  
  983.         // the eventual output text
  984.         $this->output = '';
  985.  
  986.         // when passing through the parsed source text, keep track of when
  987.         // we are in a delimited section
  988.         $in_delim = false;
  989.  
  990.         // when in a delimited section, capture the token key number
  991.         $key = '';
  992.  
  993.         // load the format object, or crap out if we can't find it
  994.         $result = $this->loadFormatObj($format);
  995.         if ($this->isError($result)) {
  996.             return $result;
  997.         }
  998.  
  999.         // pre-rendering activity
  1000.         if (is_object($this->formatObj[$format])) {
  1001.             $this->output .= $this->formatObj[$format]->pre();
  1002.         }
  1003.  
  1004.         // load the render objects
  1005.         foreach (array_keys($this->_countRulesTokens) as $rule) {
  1006.             $this->loadRenderObj($format, $rule);
  1007.         }
  1008.  
  1009.         if ($this->renderingType == 'preg') {
  1010.             $this->output = preg_replace_callback('/'.$this->delim.'(\d+)'.$this->delim.'/',
  1011.                                             array(&$this, '_renderToken'),
  1012.                                             $this->source);
  1013.             /*
  1014. //Damn strtok()! Why does it "skip" empty parts of the string. It's useless now!
  1015.         } elseif ($this->renderingType == 'strtok') {
  1016.             echo '<pre>'.htmlentities($this->source).'</pre>';
  1017.             $t = strtok($this->source, $this->delim);
  1018.             $inToken = true;
  1019.             $i = 0;
  1020.             while ($t !== false) {
  1021.                 echo 'Token: '.$i.'<pre>"'.htmlentities($t).'"</pre><br/><br/>';
  1022.                 if ($inToken) {
  1023.                     //$this->output .= $this->renderObj[$this->tokens[$t][0]]->token($this->tokens[$t][1]);
  1024.                 } else {
  1025.                     $this->output .= $t;
  1026.                 }
  1027.                 $inToken = !$inToken;
  1028.                 $t = strtok($this->delim);
  1029.                 ++$i;
  1030.             }
  1031.             */
  1032.         } else {
  1033.             // pass through the parsed source text character by character
  1034.             $this->_block = '';
  1035.             $tokenStack = array();
  1036.             $k = strlen($this->source);
  1037.             for ($i = 0; $i < $k; $i++) {
  1038.  
  1039.                 // the current character
  1040.                 $char = $this->source{$i};
  1041.  
  1042.                 // are alredy in a delimited section?
  1043.                 if ($in_delim) {
  1044.  
  1045.                     // yes; are we ending the section?
  1046.                     if ($char == $this->delim) {
  1047.  
  1048.                         if (count($this->_renderCallbacks) == 0) {
  1049.                             $this->output .= $this->_block;
  1050.                             $this->_block = '';
  1051.                         }
  1052.  
  1053.                         if (isset($opts['type'])) {
  1054.                             if ($opts['type'] == 'start') {
  1055.                                 array_push($tokenStack, $rule);
  1056.                             } elseif ($opts['type'] == 'end') {
  1057.                                 if ($tokenStack[count($tokenStack) - 1] != $rule) {
  1058.                                     return Text_Wiki::error('Unbalanced tokens, check your syntax');
  1059.                                 } else {
  1060.                                     array_pop($tokenStack);
  1061.                                 }
  1062.                             }
  1063.                         }
  1064.  
  1065.                         // yes, get the replacement text for the delimited
  1066.                         // token number and unset the flag.
  1067.                         $key = (int)$key;
  1068.                         $rule = $this->tokens[$key][0];
  1069.                         $opts = $this->tokens[$key][1];
  1070.                         $this->_block .= $this->renderObj[$rule]->token($opts);
  1071.                         $in_delim = false;
  1072.  
  1073.                     } else {
  1074.  
  1075.                         // no, add to the delimited token key number
  1076.                         $key .= $char;
  1077.  
  1078.                     }
  1079.  
  1080.                 } else {
  1081.  
  1082.                     // not currently in a delimited section.
  1083.                     // are we starting into a delimited section?
  1084.                     if ($char == $this->delim) {
  1085.                         // yes, reset the previous key and
  1086.                         // set the flag.
  1087.                         $key = '';
  1088.                         $in_delim = true;
  1089.  
  1090.                     } else {
  1091.                         // no, add to the output as-is
  1092.                         $this->_block .= $char;
  1093.                     }
  1094.                 }
  1095.             }
  1096.         }
  1097.  
  1098.         if (count($this->_renderCallbacks)) {
  1099.             return $this->error('Render callbacks left over after processing finished');
  1100.         }
  1101.         /*
  1102.         while (count($this->_renderCallbacks)) {
  1103.             $this->popRenderCallback();
  1104.         }
  1105.         */
  1106.         if (strlen($this->_block)) {
  1107.             $this->output .= $this->_block;
  1108.             $this->_block = '';
  1109.         }
  1110.  
  1111.         // post-rendering activity
  1112.         if (is_object($this->formatObj[$format])) {
  1113.             $this->output .= $this->formatObj[$format]->post();
  1114.         }
  1115.  
  1116.         // return the rendered source text.
  1117.         return $this->output;
  1118.     }
  1119.  
  1120.     /**
  1121.      * Renders a token, for use only as an internal callback
  1122.      *
  1123.      * @param array Matches from preg_rpelace_callback, [1] is the token number
  1124.      * @return string The rendered text for the token
  1125.      * @access private
  1126.      */
  1127.     function _renderToken($matches) {
  1128.         return $this->renderObj[$this->tokens[$matches[1]][0]]->token($this->tokens[$matches[1]][1]);
  1129.     }
  1130.  
  1131.     function registerRenderCallback($callback) {
  1132.         $this->_blocks[] = $this->_block;
  1133.         $this->_block = '';
  1134.         $this->_renderCallbacks[] = $callback;
  1135.     }
  1136.  
  1137.     function popRenderCallback() {
  1138.         if (count($this->_renderCallbacks) == 0) {
  1139.             return Text_Wiki::error('Render callback popped when no render callbacks in stack');
  1140.         } else {
  1141.             $callback = array_pop($this->_renderCallbacks);
  1142.             $this->_block = call_user_func($callback, $this->_block);
  1143.             if (count($this->_blocks)) {
  1144.                 $parentBlock = array_pop($this->_blocks);
  1145.                 $this->_block = $parentBlock.$this->_block;
  1146.             }
  1147.             if (count($this->_renderCallbacks) == 0) {
  1148.                 $this->output .= $this->_block;
  1149.                 $this->_block = '';
  1150.             }
  1151.         }
  1152.     }
  1153.  
  1154.     /**
  1155.     *
  1156.     * Returns the parsed source text with delimited token placeholders.
  1157.     *
  1158.     * @access public
  1159.     *
  1160.     * @return string The parsed source text.
  1161.     *
  1162.     */
  1163.  
  1164.     function getSource()
  1165.     {
  1166.         return $this->source;
  1167.     }
  1168.  
  1169.  
  1170.     /**
  1171.     *
  1172.     * Returns tokens that have been parsed out of the source text.
  1173.     *
  1174.     * @access public
  1175.     *
  1176.     * @param array $rules If an array of rule names is passed, only return
  1177.     * tokens matching these rule names.  If no array is passed, return all
  1178.     * tokens.
  1179.     *
  1180.     * @return array An array of tokens.
  1181.     *
  1182.     */
  1183.  
  1184.     function getTokens($rules = null)
  1185.     {
  1186.         if (is_null($rules)) {
  1187.             return $this->tokens;
  1188.         } else {
  1189.             settype($rules, 'array');
  1190.             $result = array();
  1191.             foreach ($this->tokens as $key => $val) {
  1192.                 if (in_array($val[0], $rules)) {
  1193.                     $result[$key] = $val;
  1194.                 }
  1195.             }
  1196.             return $result;
  1197.         }
  1198.     }
  1199.  
  1200.  
  1201.     /**
  1202.     *
  1203.     * Add a token to the Text_Wiki tokens array, and return a delimited
  1204.     * token number.
  1205.     *
  1206.     * @access public
  1207.     *
  1208.     * @param array $options An associative array of options for the new
  1209.     * token array element.  The keys and values are specific to the
  1210.     * rule, and may or may not be common to other rule options.  Typical
  1211.     * options keys are 'text' and 'type' but may include others.
  1212.     *
  1213.     * @param boolean $id_only If true, return only the token number, not
  1214.     * a delimited token string.
  1215.     *
  1216.     * @return string|int By default, return the number of the
  1217.     * newly-created token array element with a delimiter prefix and
  1218.     * suffix; however, if $id_only is set to true, return only the token
  1219.     * number (no delimiters).
  1220.     *
  1221.     */
  1222.  
  1223.     function addToken($rule, $options = array(), $id_only = false)
  1224.     {
  1225.         // increment the token ID number.  note that if you parse
  1226.         // multiple times with the same Text_Wiki object, the ID number
  1227.         // will not reset to zero.
  1228.         static $id;
  1229.         if (! isset($id)) {
  1230.             $id = 0;
  1231.         } else {
  1232.             $id ++;
  1233.         }
  1234.  
  1235.         // force the options to be an array
  1236.         settype($options, 'array');
  1237.  
  1238.         // add the token
  1239.         $this->tokens[$id] = array(
  1240.             0 => $rule,
  1241.             1 => $options
  1242.         );
  1243.         if (!isset($this->_countRulesTokens[$rule])) {
  1244.             $this->_countRulesTokens[$rule] = 1;
  1245.         } else {
  1246.             ++$this->_countRulesTokens[$rule];
  1247.         }
  1248.  
  1249.         // return a value
  1250.         if ($id_only) {
  1251.             // return the last token number
  1252.             return $id;
  1253.         } else {
  1254.             // return the token number with delimiters
  1255.             return $this->delim . $id . $this->delim;
  1256.         }
  1257.     }
  1258.  
  1259.  
  1260.     /**
  1261.     *
  1262.     * Set or re-set a token with specific information, overwriting any
  1263.     * previous rule name and rule options.
  1264.     *
  1265.     * @access public
  1266.     *
  1267.     * @param int $id The token number to reset.
  1268.     *
  1269.     * @param int $rule The rule name to use.
  1270.     *
  1271.     * @param array $options An associative array of options for the
  1272.     * token array element.  The keys and values are specific to the
  1273.     * rule, and may or may not be common to other rule options.  Typical
  1274.     * options keys are 'text' and 'type' but may include others.
  1275.     *
  1276.     * @return void
  1277.     *
  1278.     */
  1279.  
  1280.     function setToken($id, $rule, $options = array())
  1281.     {
  1282.         $oldRule = $this->tokens[$id][0];
  1283.         // reset the token
  1284.         $this->tokens[$id] = array(
  1285.             0 => $rule,
  1286.             1 => $options
  1287.         );
  1288.         if ($rule != $oldRule) {
  1289.             if (!($this->_countRulesTokens[$oldRule]--)) {
  1290.                 unset($this->_countRulesTokens[$oldRule]);
  1291.             }
  1292.             if (!isset($this->_countRulesTokens[$rule])) {
  1293.                 $this->_countRulesTokens[$rule] = 1;
  1294.             } else {
  1295.                 ++$this->_countRulesTokens[$rule];
  1296.             }
  1297.         }
  1298.     }
  1299.  
  1300.  
  1301.     /**
  1302.     *
  1303.     * Load a rule parser class file.
  1304.     *
  1305.     * @access public
  1306.     *
  1307.     * @return bool True if loaded, false if not.
  1308.     *
  1309.     */
  1310.  
  1311.     function loadParseObj($rule)
  1312.     {
  1313.         $rule = ucwords(strtolower($rule));
  1314.         $file = $rule . '.php';
  1315.         $class = "Text_Wiki_Parse_$rule";
  1316.  
  1317.         if (! class_exists($class)) {
  1318.             $loc = $this->findFile('parse', $file);
  1319.             if ($loc) {
  1320.                 // found the class
  1321.                 include_once $loc;
  1322.             } else {
  1323.                 // can't find the class
  1324.                 $this->parseObj[$rule] = null;
  1325.                 // can't find the class
  1326.                 return $this->error(
  1327.                     "Parse rule '$rule' not found"
  1328.                 );
  1329.             }
  1330.         }
  1331.  
  1332.         $this->parseObj[$rule] =& new $class($this);
  1333.  
  1334.     }
  1335.  
  1336.  
  1337.     /**
  1338.     *
  1339.     * Load a rule-render class file.
  1340.     *
  1341.     * @access public
  1342.     *
  1343.     * @return bool True if loaded, false if not.
  1344.     *
  1345.     */
  1346.  
  1347.     function loadRenderObj($format, $rule)
  1348.     {
  1349.         $format = ucwords(strtolower($format));
  1350.         $rule = ucwords(strtolower($rule));
  1351.         $file = "$format/$rule.php";
  1352.         $class = "Text_Wiki_Render_$format" . "_$rule";
  1353.  
  1354.         if (! class_exists($class)) {
  1355.             // load the class
  1356.             $loc = $this->findFile('render', $file);
  1357.             if ($loc) {
  1358.                 // found the class
  1359.                 include_once $loc;
  1360.             } else {
  1361.                 // can't find the class
  1362.                 return $this->error(
  1363.                     "Render rule '$rule' in format '$format' not found"
  1364.                 );
  1365.             }
  1366.         }
  1367.  
  1368.         $this->renderObj[$rule] =& new $class($this);
  1369.     }
  1370.  
  1371.  
  1372.     /**
  1373.     *
  1374.     * Load a format-render class file.
  1375.     *
  1376.     * @access public
  1377.     *
  1378.     * @return bool True if loaded, false if not.
  1379.     *
  1380.     */
  1381.  
  1382.     function loadFormatObj($format)
  1383.     {
  1384.         $format = ucwords(strtolower($format));
  1385.         $file = $format . '.php';
  1386.         $class = "Text_Wiki_Render_$format";
  1387.  
  1388.         if (! class_exists($class)) {
  1389.             $loc = $this->findFile('render', $file);
  1390.             if ($loc) {
  1391.                 // found the class
  1392.                 include_once $loc;
  1393.             } else {
  1394.                 // can't find the class
  1395.                 return $this->error(
  1396.                     "Rendering format class '$class' not found"
  1397.                 );
  1398.             }
  1399.         }
  1400.  
  1401.         $this->formatObj[$format] =& new $class($this);
  1402.     }
  1403.  
  1404.  
  1405.     /**
  1406.     *
  1407.     * Add a path to a path array.
  1408.     *
  1409.     * @access public
  1410.     *
  1411.     * @param string $type The path-type to add (parse or render).
  1412.     *
  1413.     * @param string $dir The directory to add to the path-type.
  1414.     *
  1415.     * @return void
  1416.     *
  1417.     */
  1418.  
  1419.     function addPath($type, $dir)
  1420.     {
  1421.         $dir = $this->fixPath($dir);
  1422.         if (! isset($this->path[$type])) {
  1423.             $this->path[$type] = array($dir);
  1424.         } else {
  1425.             array_unshift($this->path[$type], $dir);
  1426.         }
  1427.     }
  1428.  
  1429.  
  1430.     /**
  1431.     *
  1432.     * Get the current path array for a path-type.
  1433.     *
  1434.     * @access public
  1435.     *
  1436.     * @param string $type The path-type to look up (plugin, filter, or
  1437.     * template).  If not set, returns all path types.
  1438.     *
  1439.     * @return array The array of paths for the requested type.
  1440.     *
  1441.     */
  1442.  
  1443.     function getPath($type = null)
  1444.     {
  1445.         if (is_null($type)) {
  1446.             return $this->path;
  1447.         } elseif (! isset($this->path[$type])) {
  1448.             return array();
  1449.         } else {
  1450.             return $this->path[$type];
  1451.         }
  1452.     }
  1453.  
  1454.  
  1455.     /**
  1456.     *
  1457.     * Searches a series of paths for a given file.
  1458.     *
  1459.     * @param array $type The type of paths to search (template, plugin,
  1460.     * or filter).
  1461.     *
  1462.     * @param string $file The file name to look for.
  1463.     *
  1464.     * @return string|bool The full path and file name for the target file,
  1465.     * or boolean false if the file is not found in any of the paths.
  1466.     *
  1467.     */
  1468.  
  1469.     function findFile($type, $file)
  1470.     {
  1471.         // get the set of paths
  1472.         $set = $this->getPath($type);
  1473.  
  1474.         // start looping through them
  1475.         foreach ($set as $path) {
  1476.             $fullname = $path . $file;
  1477.             if (file_exists($fullname) && is_readable($fullname)) {
  1478.                 return $fullname;
  1479.             }
  1480.         }
  1481.  
  1482.         // could not find the file in the set of paths
  1483.         return false;
  1484.     }
  1485.  
  1486.  
  1487.     /**
  1488.     *
  1489.     * Append a trailing '/' to paths, unless the path is empty.
  1490.     *
  1491.     * @access private
  1492.     *
  1493.     * @param string $path The file path to fix
  1494.     *
  1495.     * @return string The fixed file path
  1496.     *
  1497.     */
  1498.  
  1499.     function fixPath($path)
  1500.     {
  1501.         $len = strlen($this->_dirSep);
  1502.  
  1503.         if (! empty($path) &&
  1504.             substr($path, -1 * $len, $len) != $this->_dirSep)    {
  1505.             return $path . $this->_dirSep;
  1506.         } else {
  1507.             return $path;
  1508.         }
  1509.     }
  1510.  
  1511.  
  1512.     /**
  1513.     *
  1514.     * Simple error-object generator.
  1515.     *
  1516.     * @access public
  1517.     *
  1518.     * @param string $message The error message.
  1519.     *
  1520.     * @return object PEAR_Error
  1521.     *
  1522.     */
  1523.  
  1524.     function &error($message)
  1525.     {
  1526.         if (! class_exists('PEAR_Error')) {
  1527.             include_once 'PEAR.php';
  1528.         }
  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.