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 / HTML / BBCodeParser.php < prev    next >
Encoding:
PHP Script  |  2008-07-02  |  30.7 KB  |  882 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Stijn de Reede <sjr@gmx.co.uk>                               |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: BBCodeParser.php,v 1.17 2007/07/02 18:46:30 cweiske Exp $
  20. //
  21.  
  22. /**
  23. * @package  HTML_BBCodeParser
  24. * @author   Stijn de Reede  <sjr@gmx.co.uk>
  25. *
  26. *
  27. * This is a parser to replace UBB style tags with their html equivalents. It
  28. * does not simply do some regex calls, but is complete stack based
  29. * parse engine. This ensures that all tags are properly nested, if not,
  30. * extra tags are added to maintain the nesting. This parser should only produce
  31. * xhtml 1.0 compliant code. All tags are validated and so are all their attributes.
  32. * It should be easy to extend this parser with your own tags, see the _definedTags
  33. * format description below.
  34. *
  35. *
  36. * Usage:
  37. * $parser = new HTML_BBCodeParser();
  38. * $parser->setText('normal [b]bold[/b] and normal again');
  39. * $parser->parse();
  40. * echo $parser->getParsed();
  41. * or:
  42. * $parser = new HTML_BBCodeParser();
  43. * echo $parser->qparse('normal [b]bold[/b] and normal again');
  44. * or:
  45. * echo HTML_BBCodeParser::staticQparse('normal [b]bold[/b] and normal again');
  46. *
  47. *
  48. * Setting the options from the ini file:
  49. * $config = parse_ini_file('BBCodeParser.ini', true);
  50. * $options = &PEAR::getStaticProperty('HTML_BBCodeParser', '_options');
  51. * $options = $config['HTML_BBCodeParser'];
  52. * unset($options);
  53. *
  54. *
  55. * The _definedTags variables should be in this format:
  56. * array('tag'                                // the actual tag used
  57. *           => array('htmlopen'  => 'open',  // the opening tag in html
  58. *                    'htmlclose' => 'close', // the closing tag in html,
  59. *                                               can be set to an empty string
  60. *                                               if no closing tag is present
  61. *                                               in html (like <img>)
  62. *                    'allowed'   => 'allow', // tags that are allowed inside
  63. *                                               this tag. Values can be all
  64. *                                               or none, or either of these
  65. *                                               two, followed by a ^ and then
  66. *                                               followed by a comma seperated
  67. *                                               list of exceptions on this
  68. *                    'attributes' => array() // an associative array containing
  69. *                                               the tag attributes and their
  70. *                                               printf() html equivalents, to
  71. *                                               which the first argument is
  72. *                                               the value, and the second is
  73. *                                               the quote. Default would be
  74. *                                               something like this:
  75. *                                               'attr' => 'attr=%2$s%1$s%2$s'
  76. *                   ),
  77. *       'etc'
  78. *           => (...)
  79. *       )
  80. */
  81. require_once 'PEAR.php';
  82.  
  83. class HTML_BBCodeParser
  84. {
  85.     /**
  86.      * An array of tags parsed by the engine, should be overwritten by filters
  87.      *
  88.      * @access   private
  89.      * @var      array
  90.      */
  91.     var $_definedTags  = array();
  92.  
  93.     /**
  94.      * A string containing the input
  95.      *
  96.      * @access   private
  97.      * @var      string
  98.      */
  99.     var $_text          = '';
  100.  
  101.     /**
  102.      * A string containing the preparsed input
  103.      *
  104.      * @access   private
  105.      * @var      string
  106.      */
  107.     var $_preparsed     = '';
  108.  
  109.     /**
  110.      * An array tags and texts build from the input text
  111.      *
  112.      * @access   private
  113.      * @var      array
  114.      */
  115.     var $_tagArray      = array();
  116.  
  117.     /**
  118.      * A string containing the parsed version of the text
  119.      *
  120.      * @access   private
  121.      * @var      string
  122.      */
  123.     var $_parsed        = '';
  124.  
  125.     /**
  126.      * An array of options, filled by an ini file or through the contructor
  127.      *
  128.      * @access   private
  129.      * @var      array
  130.      */
  131.     var $_options = array(
  132.         'quotestyle'    => 'double',
  133.         'quotewhat'     => 'all',
  134.         'open'          => '[',
  135.         'close'         => ']',
  136.         'xmlclose'      => true,
  137.         'filters'       => 'Basic'
  138.     );
  139.  
  140.     /**
  141.      * An array of filters used for parsing
  142.      *
  143.      * @access   private
  144.      * @var      array
  145.      */
  146.     var $_filters       = array();
  147.  
  148.     /**
  149.      * Constructor, initialises the options and filters
  150.      *
  151.      * Sets the private variable _options with base options defined with
  152.      * &PEAR::getStaticProperty(), overwriting them with (if present)
  153.      * the argument to this method.
  154.      * Then it sets the extra options to properly escape the tag
  155.      * characters in preg_replace() etc. The set options are
  156.      * then stored back with &PEAR::getStaticProperty(), so that the filter
  157.      * classes can use them.
  158.      * All the filters in the options are initialised and their defined tags
  159.      * are copied into the private variable _definedTags.
  160.      *
  161.      * @param    array           options to use, can be left out
  162.      * @return   none
  163.      * @access   public
  164.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  165.      */
  166.     function HTML_BBCodeParser($options = array())
  167.     {
  168.         // set the already set options
  169.         $baseoptions = &PEAR::getStaticProperty('HTML_BBCodeParser', '_options');
  170.         if (is_array($baseoptions)) {
  171.             foreach ($baseoptions as  $k => $v)  {
  172.                 $this->_options[$k] = $v;
  173.             }
  174.         }
  175.  
  176.         // set the options passed as an argument
  177.         foreach ($options as $k => $v )  {
  178.             $this->_options[$k] = $v;
  179.         }
  180.  
  181.         // add escape open and close chars to the options for preg escaping
  182.         $preg_escape = '\^$.[]|()?*+{}';
  183.         if ($this->_options['open'] != '' && strpos($preg_escape, $this->_options['open'])) {
  184.             $this->_options['open_esc'] = "\\".$this->_options['open'];
  185.         } else {
  186.             $this->_options['open_esc'] = $this->_options['open'];
  187.         }
  188.         if ($this->_options['close'] != '' && strpos($preg_escape, $this->_options['close'])) {
  189.             $this->_options['close_esc'] = "\\".$this->_options['close'];
  190.         } else {
  191.             $this->_options['close_esc'] = $this->_options['close'];
  192.         }
  193.  
  194.         // set the options back so that child classes can use them */
  195.         $baseoptions = $this->_options;
  196.         unset($baseoptions);
  197.  
  198.         // return if this is a subclass
  199.         if (is_subclass_of($this, 'HTML_BBCodeParser_Filter')) {
  200.             return;
  201.         }
  202.  
  203.         // extract the definedTags from subclasses */
  204.         $this->addFilters($this->_options['filters']);
  205.     }
  206.  
  207.     /**
  208.      * Option setter
  209.      *
  210.      * @param string option name
  211.      * @param mixed  option value
  212.      * @author Lorenzo Alberton <l.alberton@quipo.it>
  213.      */
  214.     function setOption($name, $value)
  215.     {
  216.         $this->_options[$name] = $value;
  217.     }
  218.  
  219.     /**
  220.      * Add a new filter
  221.      *
  222.      * @param string filter
  223.      * @author Lorenzo Alberton <l.alberton@quipo.it>
  224.      */
  225.     function addFilter($filter)
  226.     {
  227.         $filter = ucfirst($filter);
  228.         if (!array_key_exists($filter, $this->_filters)) {
  229.             $class = 'HTML_BBCodeParser_Filter_'.$filter;
  230.             @include_once 'HTML/BBCodeParser/Filter/'.$filter.'.php';
  231.             if (!class_exists($class)) {
  232.                 PEAR::raiseError("Failed to load filter $filter", null, PEAR_ERROR_DIE);
  233.             }
  234.             $this->_filters[$filter] = new $class;
  235.             $this->_definedTags = array_merge(
  236.                 $this->_definedTags,
  237.                 $this->_filters[$filter]->_definedTags
  238.             );
  239.         }
  240.     }
  241.  
  242.     /**
  243.      * Remove an existing filter
  244.      *
  245.      * @param string $filter
  246.      * @author Lorenzo Alberton <l.alberton@quipo.it>
  247.      */
  248.     function removeFilter($filter)
  249.     {
  250.         $filter = ucfirst(trim($filter));
  251.         if (!empty($filter) && array_key_exists($filter, $this->_filters)) {
  252.             unset($this->_filters[$filter]);
  253.         }
  254.         // also remove the related $this->_definedTags for this filter,
  255.         // preserving the others
  256.         $this->_definedTags = array();
  257.         foreach (array_keys($this->_filters) as $filter) {
  258.             $this->_definedTags = array_merge(
  259.                 $this->_definedTags,
  260.                 $this->_filters[$filter]->_definedTags
  261.             );
  262.         }
  263.     }
  264.  
  265.     /**
  266.      * Add new filters
  267.      *
  268.      * @param mixed (array or string)
  269.      * @return boolean true if all ok, false if not.
  270.      * @author Lorenzo Alberton <l.alberton@quipo.it>
  271.      */
  272.     function addFilters($filters)
  273.     {
  274.         if (is_string($filters)) {
  275.             //comma-separated list
  276.             if (strpos($filters, ',') !== false) {
  277.                 $filters = explode(',', $filters);
  278.             } else {
  279.                 $filters = array($filters);
  280.             }
  281.         }
  282.         if (!is_array($filters)) {
  283.             //invalid format
  284.             return false;
  285.         }
  286.         foreach ($filters as $filter) {
  287.             if (trim($filter)){
  288.                 $this->addFilter($filter);
  289.             }
  290.         }
  291.         return true;
  292.     }
  293.  
  294.     /**
  295.      * Executes statements before the actual array building starts
  296.      *
  297.      * This method should be overwritten in a filter if you want to do
  298.      * something before the parsing process starts. This can be useful to
  299.      * allow certain short alternative tags which then can be converted into
  300.      * proper tags with preg_replace() calls.
  301.      * The main class walks through all the filters and and calls this
  302.      * method. The filters should modify their private $_preparsed
  303.      * variable, with input from $_text.
  304.      *
  305.      * @return   none
  306.      * @access   private
  307.      * @see      $_text
  308.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  309.      */
  310.     function _preparse()
  311.     {
  312.         // default: assign _text to _preparsed, to be overwritten by filters
  313.         $this->_preparsed = $this->_text;
  314.  
  315.         // return if this is a subclass
  316.         if (is_subclass_of($this, 'HTML_BBCodeParser')) {
  317.             return;
  318.         }
  319.  
  320.         // walk through the filters and execute _preparse
  321.         foreach ($this->_filters as $filter) {
  322.             $filter->setText($this->_preparsed);
  323.             $filter->_preparse();
  324.             $this->_preparsed = $filter->getPreparsed();
  325.         }
  326.     }
  327.  
  328.     /**
  329.      * Builds the tag array from the input string $_text
  330.      *
  331.      * An array consisting of tag and text elements is contructed from the
  332.      * $_preparsed variable. The method uses _buildTag() to check if a tag is
  333.      * valid and to build the actual tag to be added to the tag array.
  334.      *
  335.      * TODO: - rewrite whole method, as this one is old and probably slow
  336.      *       - see if a recursive method would be better than an iterative one
  337.      *
  338.      * @return   none
  339.      * @access   private
  340.      * @see      _buildTag()
  341.      * @see      $_text
  342.      * @see      $_tagArray
  343.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  344.      */
  345.     function _buildTagArray()
  346.     {
  347.         $this->_tagArray = array();
  348.         $str = $this->_preparsed;
  349.         $strPos = 0;
  350.         $strLength = strlen($str);
  351.  
  352.         while (($strPos < $strLength)) {
  353.             $tag = array();
  354.             $openPos = strpos($str, $this->_options['open'], $strPos);
  355.             if ($openPos === false) {
  356.                 $openPos = $strLength;
  357.                 $nextOpenPos = $strLength;
  358.             }
  359.             if ($openPos + 1 > $strLength) {
  360.                 $nextOpenPos = $strLength;
  361.             } else {
  362.                 $nextOpenPos = strpos($str, $this->_options['open'], $openPos + 1);
  363.                 if ($nextOpenPos === false) {
  364.                     $nextOpenPos = $strLength;
  365.                 }
  366.             }
  367.             $closePos = strpos($str, $this->_options['close'], $strPos);
  368.             if ($closePos === false) {
  369.                 $closePos = $strLength + 1;
  370.             }
  371.  
  372.             if ($openPos == $strPos) {
  373.                 if (($nextOpenPos < $closePos)) {
  374.                     // new open tag before closing tag: treat as text
  375.                     $newPos = $nextOpenPos;
  376.                     $tag['text'] = substr($str, $strPos, $nextOpenPos - $strPos);
  377.                     $tag['type'] = 0;
  378.                 } else {
  379.                     // possible valid tag
  380.                     $newPos = $closePos + 1;
  381.                     $newTag = $this->_buildTag(substr($str, $strPos, $closePos - $strPos + 1));
  382.                     if (($newTag !== false)) {
  383.                         $tag = $newTag;
  384.                     } else {
  385.                         // no valid tag after all
  386.                         $tag['text'] = substr($str, $strPos, $closePos - $strPos + 1);
  387.                         $tag['type'] = 0;
  388.                     }
  389.                 }
  390.             } else {
  391.                 // just text
  392.                 $newPos = $openPos;
  393.                 $tag['text'] = substr($str, $strPos, $openPos - $strPos);
  394.                 $tag['type'] = 0;
  395.             }
  396.  
  397.             // join 2 following text elements
  398.             if ($tag['type'] === 0 && isset($prev) && $prev['type'] === 0) {
  399.                 $tag['text'] = $prev['text'].$tag['text'];
  400.                 array_pop($this->_tagArray);
  401.             }
  402.  
  403.             $this->_tagArray[] = $tag;
  404.             $prev = $tag;
  405.             $strPos = $newPos;
  406.         }
  407.     }
  408.  
  409.     /**
  410.      * Builds a tag from the input string
  411.      *
  412.      * This method builds a tag array based on the string it got as an
  413.      * argument. If the tag is invalid, <false> is returned. The tag
  414.      * attributes are extracted from the string and stored in the tag
  415.      * array as an associative array.
  416.      *
  417.      * @param    string          string to build tag from
  418.      * @return   array           tag in array format
  419.      * @access   private
  420.      * @see      _buildTagArray()
  421.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  422.      */
  423.     function _buildTag($str)
  424.     {
  425.         $tag = array('text' => $str, 'attributes' => array());
  426.  
  427.         if (substr($str, 1, 1) == '/') {        // closing tag
  428.  
  429.             $tag['tag'] = strtolower(substr($str, 2, strlen($str) - 3));
  430.             if (!in_array($tag['tag'], array_keys($this->_definedTags))) {
  431.                 return false;                   // nope, it's not valid
  432.             } else {
  433.                 $tag['type'] = 2;
  434.                 return $tag;
  435.             }
  436.         } else {                                // opening tag
  437.  
  438.             $tag['type'] = 1;
  439.             if (strpos($str, ' ') && (strpos($str, '=') === false)) {
  440.                 return false;                   // nope, it's not valid
  441.             }
  442.  
  443.             // tnx to Onno for the regex
  444.             // split the tag with arguments and all
  445.             $oe = $this->_options['open_esc'];
  446.             $ce = $this->_options['close_esc'];
  447.             $tagArray = array();
  448.             if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i", $str, $tagArray) == 0) {
  449.                 return false;
  450.             }
  451.             $tag['tag'] = strtolower($tagArray[1]);
  452.             if (!in_array($tag['tag'], array_keys($this->_definedTags))) {
  453.                 return false;                   // nope, it's not valid
  454.             }
  455.  
  456.             // tnx to Onno for the regex
  457.             // validate the arguments
  458.             $attributeArray = array();
  459.             $regex = "![\s$oe]([a-z0-9]+)=(\"[^\s$ce]+\"|[^\s$ce]";
  460.             if ($tag['tag'] != 'url') {
  461.                 $regex .= "[^=]";
  462.             }
  463.             $regex .= "+)(?=[\s$ce])!i";
  464.             preg_match_all($regex, $str, $attributeArray, PREG_SET_ORDER);
  465.             foreach ($attributeArray as $attribute) {
  466.                 $attNam = strtolower($attribute[1]);
  467.                 if (in_array($attNam, array_keys($this->_definedTags[$tag['tag']]['attributes']))) {
  468.                     if ($attribute[2][0] == '"' && $attribute[2][strlen($attribute[2])-1] == '"') {
  469.                         $tag['attributes'][$attNam] = substr($attribute[2], 1, -1);
  470.                     } else {
  471.                         $tag['attributes'][$attNam] = $attribute[2];
  472.                     }
  473.                 }
  474.             }
  475.             return $tag;
  476.         }
  477.     }
  478.  
  479.     /**
  480.      * Validates the tag array, regarding the allowed tags
  481.      *
  482.      * While looping through the tag array, two following text tags are
  483.      * joined, and it is checked that the tag is allowed inside the
  484.      * last opened tag.
  485.      * By remembering what tags have been opened it is checked that
  486.      * there is correct (xml compliant) nesting.
  487.      * In the end all still opened tags are closed.
  488.      *
  489.      * @return   none
  490.      * @access   private
  491.      * @see      _isAllowed()
  492.      * @see      $_tagArray
  493.      * @author   Stijn de Reede  <sjr@gmx.co.uk>, Seth Price <seth@pricepages.org>
  494.      */
  495.     function _validateTagArray()
  496.     {
  497.         $newTagArray = array();
  498.         $openTags = array();
  499.         foreach ($this->_tagArray as $tag) {
  500.             $prevTag = end($newTagArray);
  501.             switch ($tag['type']) {
  502.             case 0:
  503.                 if (($child = $this->_childNeeded(end($openTags), 'text')) &&
  504.                     $child !== false &&
  505.                     /*
  506.                      * No idea what to do in this case: A child is needed, but
  507.                      * no valid one is returned. We'll ignore it here and live
  508.                      * with it until someone reports a valid bug.
  509.                      */
  510.                     $child !== true )
  511.                 {
  512.                     if (trim($tag['text']) == '') {
  513.                         //just an empty indentation or newline without value?
  514.                         continue;
  515.                     }
  516.                     $newTagArray[] = $child;
  517.                     $openTags[] = $child['tag'];
  518.                 }
  519.                 if ($prevTag['type'] === 0) {
  520.                     $tag['text'] = $prevTag['text'].$tag['text'];
  521.                     array_pop($newTagArray);
  522.                 }
  523.                 $newTagArray[] = $tag;
  524.                 break;
  525.  
  526.             case 1:
  527.                 if (!$this->_isAllowed(end($openTags), $tag['tag']) ||
  528.                    ($parent = $this->_parentNeeded(end($openTags), $tag['tag'])) === true ||
  529.                    ($child  = $this->_childNeeded(end($openTags),  $tag['tag'])) === true) {
  530.                     $tag['type'] = 0;
  531.                     if ($prevTag['type'] === 0) {
  532.                         $tag['text'] = $prevTag['text'].$tag['text'];
  533.                         array_pop($newTagArray);
  534.                     }
  535.                 } else {
  536.                     if ($parent) {
  537.                         /*
  538.                          * Avoid use of parent if we can help it. If we are
  539.                          * trying to insert a new parent, but the current tag is
  540.                          * the same as the previous tag, then assume that the
  541.                          * previous tag structure is valid, and add this tag as
  542.                          * a sibling. To add as a sibling, we need to close the
  543.                          * current tag.
  544.                          */
  545.                         if ($tag['tag'] == end($openTags)){
  546.                             $newTagArray[] = $this->_buildTag('[/'.$tag['tag'].']');
  547.                             array_pop($openTags);
  548.                         } else {
  549.                             $newTagArray[] = $parent;
  550.                             $openTags[] = $parent['tag'];
  551.                         }
  552.                     }
  553.                     if ($child) {
  554.                         $newTagArray[] = $child;
  555.                         $openTags[] = $child['tag'];
  556.                     }
  557.                     $openTags[] = $tag['tag'];
  558.                 }
  559.                 $newTagArray[] = $tag;
  560.                 break;
  561.  
  562.             case 2:
  563.                 if (($tag['tag'] == end($openTags) || $this->_isAllowed(end($openTags), $tag['tag']))) {
  564.                     if (in_array($tag['tag'], $openTags)) {
  565.                         $tmpOpenTags = array();
  566.                         while (end($openTags) != $tag['tag']) {
  567.                             $newTagArray[] = $this->_buildTag('[/'.end($openTags).']');
  568.                             $tmpOpenTags[] = end($openTags);
  569.                             array_pop($openTags);
  570.                         }
  571.                         $newTagArray[] = $tag;
  572.                         array_pop($openTags);
  573.                         /* why is this here? it just seems to break things
  574.                          * (nested lists where closing tags need to be
  575.                          * generated)
  576.                         while (end($tmpOpenTags)) {
  577.                             $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']');
  578.                             $newTagArray[] = $tmpTag;
  579.                             $openTags[] = $tmpTag['tag'];
  580.                             array_pop($tmpOpenTags);
  581.                         }*/
  582.                     }
  583.                 } else {
  584.                     $tag['type'] = 0;
  585.                     if ($prevTag['type'] === 0) {
  586.                         $tag['text'] = $prevTag['text'].$tag['text'];
  587.                         array_pop($newTagArray);
  588.                     }
  589.                     $newTagArray[] = $tag;
  590.                 }
  591.                 break;
  592.             }
  593.         }
  594.         while (end($openTags)) {
  595.             $newTagArray[] = $this->_buildTag('[/'.end($openTags).']');
  596.             array_pop($openTags);
  597.         }
  598.         $this->_tagArray = $newTagArray;
  599.     }
  600.  
  601.     /**
  602.      * Checks to see if a parent is needed
  603.      *
  604.      * Checks to see if the current $in tag has an appropriate parent. If it
  605.      * does, then it returns false. If a parent is needed, then it returns the
  606.      * first tag in the list to add to the stack.
  607.      *
  608.      * @param    array           tag that is on the outside
  609.      * @param    array           tag that is on the inside
  610.      * @return   boolean         false if not needed, tag if needed, true if out
  611.      *                           of  our minds
  612.      * @access   private
  613.      * @see      _validateTagArray()
  614.      * @author   Seth Price <seth@pricepages.org>
  615.      */
  616.     function _parentNeeded($out, $in)
  617.     {
  618.         if (!isset($this->_definedTags[$in]['parent']) ||
  619.             ($this->_definedTags[$in]['parent'] == 'all')
  620.         ) {
  621.             return false;
  622.         }
  623.  
  624.         $ar = explode('^', $this->_definedTags[$in]['parent']);
  625.         $tags = explode(',', $ar[1]);
  626.         if ($ar[0] == 'none'){
  627.             if ($out && in_array($out, $tags)) {
  628.                 return false;
  629.             }
  630.             //Create a tag from the first one on the list
  631.             return $this->_buildTag('['.$tags[0].']');
  632.         }
  633.         if ($ar[0] == 'all' && $out && !in_array($out, $tags)) {
  634.             return false;
  635.         }
  636.         // Tag is needed, we don't know which one. We could make something up,
  637.         // but it would be so random, I think that it would be worthless.
  638.         return true;
  639.     }
  640.  
  641.     /**
  642.      * Checks to see if a child is needed
  643.      *
  644.      * Checks to see if the current $out tag has an appropriate child. If it
  645.      * does, then it returns false. If a child is needed, then it returns the
  646.      * first tag in the list to add to the stack.
  647.      *
  648.      * @param    array           tag that is on the outside
  649.      * @param    array           tag that is on the inside
  650.      * @return   boolean         false if not needed, tag if needed, true if out
  651.      *                           of our minds
  652.      * @access   private
  653.      * @see      _validateTagArray()
  654.      * @author   Seth Price <seth@pricepages.org>
  655.      */
  656.     function _childNeeded($out, $in)
  657.     {
  658.         if (!isset($this->_definedTags[$out]['child']) ||
  659.            ($this->_definedTags[$out]['child'] == 'all')
  660.         ) {
  661.             return false;
  662.         }
  663.  
  664.         $ar = explode('^', $this->_definedTags[$out]['child']);
  665.         $tags = explode(',', $ar[1]);
  666.         if ($ar[0] == 'none'){
  667.             if ($in && in_array($in, $tags)) {
  668.                 return false;
  669.             }
  670.             //Create a tag from the first one on the list
  671.             return $this->_buildTag('['.$tags[0].']');
  672.         }
  673.         if ($ar[0] == 'all' && $in && !in_array($in, $tags)) {
  674.             return false;
  675.         }
  676.         // Tag is needed, we don't know which one. We could make something up,
  677.         // but it would be so random, I think that it would be worthless.
  678.         return true;
  679.     }
  680.  
  681.     /**
  682.      * Checks to see if a tag is allowed inside another tag
  683.      *
  684.      * The allowed tags are extracted from the private _definedTags array.
  685.      *
  686.      * @param    array           tag that is on the outside
  687.      * @param    array           tag that is on the inside
  688.      * @return   boolean         return true if the tag is allowed, false
  689.      *                           otherwise
  690.      * @access   private
  691.      * @see      _validateTagArray()
  692.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  693.      */
  694.     function _isAllowed($out, $in)
  695.     {
  696.         if (!$out || ($this->_definedTags[$out]['allowed'] == 'all')) {
  697.             return true;
  698.         }
  699.         if ($this->_definedTags[$out]['allowed'] == 'none') {
  700.             return false;
  701.         }
  702.  
  703.         $ar = explode('^', $this->_definedTags[$out]['allowed']);
  704.         $tags = explode(',', $ar[1]);
  705.         if ($ar[0] == 'none' && in_array($in, $tags)) {
  706.             return true;
  707.         }
  708.         if ($ar[0] == 'all'  && in_array($in, $tags)) {
  709.             return false;
  710.         }
  711.         return false;
  712.     }
  713.  
  714.     /**
  715.      * Builds a parsed string based on the tag array
  716.      *
  717.      * The correct html and attribute values are extracted from the private
  718.      * _definedTags array.
  719.      *
  720.      * @return   none
  721.      * @access   private
  722.      * @see      $_tagArray
  723.      * @see      $_parsed
  724.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  725.      */
  726.     function _buildParsedString()
  727.     {
  728.         $this->_parsed = '';
  729.         foreach ($this->_tagArray as $tag) {
  730.             switch ($tag['type']) {
  731.  
  732.             // just text
  733.             case 0:
  734.                 $this->_parsed .= $tag['text'];
  735.                 break;
  736.  
  737.             // opening tag
  738.             case 1:
  739.                 $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen'];
  740.                 if ($this->_options['quotestyle'] == 'single') $q = "'";
  741.                 if ($this->_options['quotestyle'] == 'double') $q = '"';
  742.                 foreach ($tag['attributes'] as $a => $v) {
  743.                     //prevent XSS attacks. IMHO this is not enough, though...
  744.                     //@see http://pear.php.net/bugs/bug.php?id=5609
  745.                     $v = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $v);
  746.                     $v = htmlspecialchars($v);
  747.                     $v = str_replace('&amp;', '&', $v);
  748.  
  749.                     if (($this->_options['quotewhat'] == 'nothing') ||
  750.                         (($this->_options['quotewhat'] == 'strings') && is_numeric($v))
  751.                     ) {
  752.                         $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, '');
  753.                     } else {
  754.                         $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a], $v, $q);
  755.                     }
  756.                 }
  757.                 if ($this->_definedTags[$tag['tag']]['htmlclose'] == '' && $this->_options['xmlclose']) {
  758.                     $this->_parsed .= ' /';
  759.                 }
  760.                 $this->_parsed .= '>';
  761.                 break;
  762.  
  763.             // closing tag
  764.             case 2:
  765.                 if ($this->_definedTags[$tag['tag']]['htmlclose'] != '') {
  766.                     $this->_parsed .= '</'.$this->_definedTags[$tag['tag']]['htmlclose'].'>';
  767.                 }
  768.                 break;
  769.             }
  770.         }
  771.     }
  772.  
  773.     /**
  774.      * Sets text in the object to be parsed
  775.      *
  776.      * @param    string          the text to set in the object
  777.      * @return   none
  778.      * @access   public
  779.      * @see      getText()
  780.      * @see      $_text
  781.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  782.      */
  783.     function setText($str)
  784.     {
  785.         $this->_text = $str;
  786.     }
  787.  
  788.     /**
  789.      * Gets the unparsed text from the object
  790.      *
  791.      * @return   string          the text set in the object
  792.      * @access   public
  793.      * @see      setText()
  794.      * @see      $_text
  795.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  796.      */
  797.     function getText()
  798.     {
  799.         return $this->_text;
  800.     }
  801.  
  802.     /**
  803.      * Gets the preparsed text from the object
  804.      *
  805.      * @return   string          the text set in the object
  806.      * @access   public
  807.      * @see      _preparse()
  808.      * @see      $_preparsed
  809.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  810.      */
  811.     function getPreparsed()
  812.     {
  813.         return $this->_preparsed;
  814.     }
  815.  
  816.     /**
  817.      * Gets the parsed text from the object
  818.      *
  819.      * @return   string          the parsed text set in the object
  820.      * @access   public
  821.      * @see      parse()
  822.      * @see      $_parsed
  823.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  824.      */
  825.     function getParsed()
  826.     {
  827.         return $this->_parsed;
  828.     }
  829.  
  830.     /**
  831.      * Parses the text set in the object
  832.      *
  833.      * @return   none
  834.      * @access   public
  835.      * @see      _preparse()
  836.      * @see      _buildTagArray()
  837.      * @see      _validateTagArray()
  838.      * @see      _buildParsedString()
  839.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  840.      */
  841.     function parse()
  842.     {
  843.         $this->_preparse();
  844.         $this->_buildTagArray();
  845.         $this->_validateTagArray();
  846.         $this->_buildParsedString();
  847.     }
  848.  
  849.     /**
  850.      * Quick method to do setText(), parse() and getParsed at once
  851.      *
  852.      * @return   none
  853.      * @access   public
  854.      * @see      parse()
  855.      * @see      $_text
  856.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  857.      */
  858.     function qparse($str)
  859.     {
  860.         $this->_text = $str;
  861.         $this->parse();
  862.         return $this->_parsed;
  863.     }
  864.  
  865.     /**
  866.      * Quick static method to do setText(), parse() and getParsed at once
  867.      *
  868.      * @return   none
  869.      * @access   public
  870.      * @see      parse()
  871.      * @see      $_text
  872.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  873.      */
  874.     function staticQparse($str)
  875.     {
  876.         $p = new HTML_BBCodeParser();
  877.         $str = $p->qparse($str);
  878.         unset($p);
  879.         return $str;
  880.     }
  881. }
  882. ?>