HTML_BBCodeParser
[ class tree: HTML_BBCodeParser ] [ index: HTML_BBCodeParser ] [ all elements ]

Source for file BBCodeParser.php

Documentation is available at BBCodeParser.php

  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.12 2007/06/04 21:38:35 dufuz 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')) {
  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"nullPEAR_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->_filtersas $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.      * @author Lorenzo Alberton <l.alberton@quipo.it>
  270.      */
  271.     function addFilters($filters)
  272.     {
  273.         if (is_string($filters)) {
  274.             //comma-separated list
  275.             if (strpos($filters','!== false{
  276.                 $filters explode(','$filters);
  277.             else {
  278.                 $filters = array($filters);
  279.             }
  280.         }
  281.         if (!is_array($filters)) {
  282.             //invalid format
  283.             return;
  284.         }
  285.         foreach ($filters as $filter{
  286.             if (trim($filter)){
  287.                 $this->addFilter($filter);
  288.             }
  289.         }
  290.     }
  291.  
  292.     /**
  293.      * Executes statements before the actual array building starts
  294.      *
  295.      * This method should be overwritten in a filter if you want to do
  296.      * something before the parsing process starts. This can be useful to
  297.      * allow certain short alternative tags which then can be converted into
  298.      * proper tags with preg_replace() calls.
  299.      * The main class walks through all the filters and and calls this
  300.      * method. The filters should modify their private $_preparsed
  301.      * variable, with input from $_text.
  302.      *
  303.      * @return   none 
  304.      * @access   private
  305.      * @see      $_text
  306.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  307.      */
  308.     function _preparse()
  309.     {
  310.         // default: assign _text to _preparsed, to be overwritten by filters
  311.         $this->_preparsed $this->_text;
  312.  
  313.         // return if this is a subclass
  314.         if (is_subclass_of($this'HTML_BBCodeParser')) {
  315.             return;
  316.         }
  317.  
  318.         // walk through the filters and execute _preparse
  319.         foreach ($this->_filters as $filter{
  320.             $filter->setText($this->_preparsed);
  321.             $filter->_preparse();
  322.             $this->_preparsed $filter->getPreparsed();
  323.         }
  324.     }
  325.  
  326.     /**
  327.      * Builds the tag array from the input string $_text
  328.      *
  329.      * An array consisting of tag and text elements is contructed from the
  330.      * $_preparsed variable. The method uses _buildTag() to check if a tag is
  331.      * valid and to build the actual tag to be added to the tag array.
  332.      *
  333.      * TODO: - rewrite whole method, as this one is old and probably slow
  334.      *       - see if a recursive method would be better than an iterative one
  335.      *
  336.      * @return   none 
  337.      * @access   private
  338.      * @see      _buildTag()
  339.      * @see      $_text
  340.      * @see      $_tagArray
  341.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  342.      */
  343.     function _buildTagArray()
  344.     {
  345.         $this->_tagArray = array();
  346.         $str $this->_preparsed;
  347.         $strPos = 0;
  348.         $strLength strlen($str);
  349.  
  350.         while (($strPos $strLength)) {
  351.             $tag = array();
  352.             $openPos strpos($str$this->_options['open']$strPos);
  353.             if ($openPos === false{
  354.                 $openPos $strLength;
  355.                 $nextOpenPos $strLength;
  356.             }
  357.             if ($openPos + 1 > $strLength{
  358.                 $nextOpenPos $strLength;
  359.             else {
  360.                 $nextOpenPos strpos($str$this->_options['open']$openPos + 1);
  361.                 if ($nextOpenPos === false{
  362.                     $nextOpenPos $strLength;
  363.                 }
  364.             }
  365.             $closePos strpos($str$this->_options['close']$strPos);
  366.             if ($closePos === false{
  367.                 $closePos $strLength + 1;
  368.             }
  369.  
  370.             if ($openPos == $strPos{
  371.                 if (($nextOpenPos $closePos)) {
  372.                     // new open tag before closing tag: treat as text
  373.                     $newPos $nextOpenPos;
  374.                     $tag['text'substr($str$strPos$nextOpenPos $strPos);
  375.                     $tag['type'= 0;
  376.                 else {
  377.                     // possible valid tag
  378.                     $newPos $closePos + 1;
  379.                     $newTag $this->_buildTag(substr($str$strPos$closePos $strPos + 1));
  380.                     if (($newTag !== false)) {
  381.                         $tag $newTag;
  382.                     else {
  383.                         // no valid tag after all
  384.                         $tag['text'substr($str$strPos$closePos $strPos + 1);
  385.                         $tag['type'= 0;
  386.                     }
  387.                 }
  388.             else {
  389.                 // just text
  390.                 $newPos $openPos;
  391.                 $tag['text'substr($str$strPos$openPos $strPos);
  392.                 $tag['type'= 0;
  393.             }
  394.  
  395.             // join 2 following text elements
  396.             if ($tag['type'=== 0 && isset($prev&& $prev['type'=== 0{
  397.                 $tag['text'$prev['text'].$tag['text'];
  398.                 array_pop($this->_tagArray);
  399.             }
  400.  
  401.             $this->_tagArray[$tag;
  402.             $prev $tag;
  403.             $strPos $newPos;
  404.         }
  405.     }
  406.  
  407.     /**
  408.      * Builds a tag from the input string
  409.      *
  410.      * This method builds a tag array based on the string it got as an
  411.      * argument. If the tag is invalid, <false> is returned. The tag
  412.      * attributes are extracted from the string and stored in the tag
  413.      * array as an associative array.
  414.      *
  415.      * @param    string          string to build tag from
  416.      * @return   array           tag in array format
  417.      * @access   private
  418.      * @see      _buildTagArray()
  419.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  420.      */
  421.     function _buildTag($str)
  422.     {
  423.         $tag = array('text' => $str'attributes' => array());
  424.  
  425.         if (substr($str11== '/'{        // closing tag
  426.  
  427.             $tag['tag'strtolower(substr($str2strlen($str- 3));
  428.             if (!in_array($tag['tag']array_keys($this->_definedTags))) {
  429.                 return false;                   // nope, it's not valid
  430.             else {
  431.                 $tag['type'= 2;
  432.                 return $tag;
  433.             }
  434.         else {                                // opening tag
  435.  
  436.             $tag['type'= 1;
  437.             if (strpos($str' '&& (strpos($str'='=== false)) {
  438.                 return false;                   // nope, it's not valid
  439.             }
  440.  
  441.             // tnx to Onno for the regex
  442.             // split the tag with arguments and all
  443.             $oe $this->_options['open_esc'];
  444.             $ce $this->_options['close_esc'];
  445.             $tagArray = array();
  446.             if (preg_match("!$oe([a-z0-9]+)[^$ce]*$ce!i"$str$tagArray== 0{
  447.                 return false;
  448.             }
  449.             $tag['tag'strtolower($tagArray[1]);
  450.             if (!in_array($tag['tag']array_keys($this->_definedTags))) {
  451.                 return false;                   // nope, it's not valid
  452.             }
  453.  
  454.             // tnx to Onno for the regex
  455.             // validate the arguments
  456.             $attributeArray = array();
  457.             $regex = "![\s$oe]([a-z0-9]+)=([^\s$ce]";
  458.             if ($tag['tag'!= 'url'{
  459.                 $regex .= "[^=]";
  460.             }
  461.             $regex .= "+)(?=[\s$ce])!i";
  462.             preg_match_all($regex$str$attributeArrayPREG_SET_ORDER);
  463.             foreach ($attributeArray as $attribute{
  464.                 $attNam strtolower($attribute[1]);
  465.                 if (in_array($attNamarray_keys($this->_definedTags[$tag['tag']]['attributes']))) {
  466.                     $tag['attributes'][$attNam$attribute[2];
  467.                 }
  468.             }
  469.             return $tag;
  470.         }
  471.     }
  472.  
  473.     /**
  474.      * Validates the tag array, regarding the allowed tags
  475.      *
  476.      * While looping through the tag array, two following text tags are
  477.      * joined, and it is checked that the tag is allowed inside the
  478.      * last opened tag.
  479.      * By remembering what tags have been opened it is checked that
  480.      * there is correct (xml compliant) nesting.
  481.      * In the end all still opened tags are closed.
  482.      *
  483.      * @return   none 
  484.      * @access   private
  485.      * @see      _isAllowed()
  486.      * @see      $_tagArray
  487.      * @author   Stijn de Reede  <sjr@gmx.co.uk>, Seth Price <seth@pricepages.org>
  488.      */
  489.     function _validateTagArray()
  490.     {
  491.         $newTagArray = array();
  492.         $openTags = array();
  493.         foreach ($this->_tagArray as $tag{
  494.             $prevTag end($newTagArray);
  495.             switch ($tag['type']{
  496.             case 0:
  497.                 if (($child $this->_childNeeded(end($openTags)'text')) &&
  498.                     $child !== false &&
  499.                     /*
  500.                      * No idea what to do in this case: A child is needed, but
  501.                      * no valid one is returned. We'll ignore it here and live
  502.                      * with it until someone reports a valid bug.
  503.                      */
  504.                     $child !== true ){
  505.  
  506.                     $newTagArray[$child;
  507.                     $openTags[$child['tag'];
  508.                 }
  509.                 if ($prevTag['type'=== 0{
  510.                     $tag['text'$prevTag['text'].$tag['text'];
  511.                     array_pop($newTagArray);
  512.                 }
  513.                 $newTagArray[$tag;
  514.                 break;
  515.  
  516.             case 1:
  517.                 if (!$this->_isAllowed(end($openTags)$tag['tag']||
  518.                    ($parent $this->_parentNeeded(end($openTags)$tag['tag'])) === true ||
  519.                    ($child  $this->_childNeeded(end($openTags),  $tag['tag'])) === true{
  520.                     $tag['type'= 0;
  521.                     if ($prevTag['type'=== 0{
  522.                         $tag['text'$prevTag['text'].$tag['text'];
  523.                         array_pop($newTagArray);
  524.                     }
  525.                 else {
  526.                     if ($parent{
  527.                         /*
  528.                          * Avoid use of parent if we can help it. If we are
  529.                          * trying to insert a new parent, but the current tag is
  530.                          * the same as the previous tag, then assume that the
  531.                          * previous tag structure is valid, and add this tag as
  532.                          * a sibling. To add as a sibling, we need to close the
  533.                          * current tag.
  534.                          */
  535.                         if ($tag['tag'== end($openTags)){
  536.                             $newTagArray[$this->_buildTag('[/'.$tag['tag'].']');
  537.                             array_pop($openTags);
  538.                         else {
  539.                             $newTagArray[$parent;
  540.                             $openTags[$parent['tag'];
  541.                         }
  542.                     }
  543.                     if ($child{
  544.                         $newTagArray[$child;
  545.                         $openTags[$child['tag'];
  546.                     }
  547.                     $openTags[$tag['tag'];
  548.                 }
  549.                 $newTagArray[$tag;
  550.                 break;
  551.  
  552.             case 2:
  553.                 if (($tag['tag'== end($openTags|| $this->_isAllowed(end($openTags)$tag['tag']))) {
  554.                     if (in_array($tag['tag']$openTags)) {
  555.                         $tmpOpenTags = array();
  556.                         while (end($openTags!= $tag['tag']{
  557.                             $newTagArray[$this->_buildTag('[/'.end($openTags).']');
  558.                             $tmpOpenTags[end($openTags);
  559.                             array_pop($openTags);
  560.                         }
  561.                         $newTagArray[$tag;
  562.                         array_pop($openTags);
  563.                         /* why is this here? it just seems to break things
  564.                          * (nested lists where closing tags need to be
  565.                          * generated)
  566.                         while (end($tmpOpenTags)) {
  567.                             $tmpTag = $this->_buildTag('['.end($tmpOpenTags).']');
  568.                             $newTagArray[] = $tmpTag;
  569.                             $openTags[] = $tmpTag['tag'];
  570.                             array_pop($tmpOpenTags);
  571.                         }*/
  572.                     }
  573.                 else {
  574.                     $tag['type'= 0;
  575.                     if ($prevTag['type'=== 0{
  576.                         $tag['text'$prevTag['text'].$tag['text'];
  577.                         array_pop($newTagArray);
  578.                     }
  579.                     $newTagArray[$tag;
  580.                 }
  581.                 break;
  582.             }
  583.         }
  584.         while (end($openTags)) {
  585.             $newTagArray[$this->_buildTag('[/'.end($openTags).']');
  586.             array_pop($openTags);
  587.         }
  588.         $this->_tagArray $newTagArray;
  589.     }
  590.  
  591.     /**
  592.      * Checks to see if a parent is needed
  593.      *
  594.      * Checks to see if the current $in tag has an appropriate parent. If it
  595.      * does, then it returns false. If a parent is needed, then it returns the
  596.      * first tag in the list to add to the stack.
  597.      *
  598.      * @param    array           tag that is on the outside
  599.      * @param    array           tag that is on the inside
  600.      * @return   boolean         false if not needed, tag if needed, true if out
  601.      *                            of  our minds
  602.      * @access   private
  603.      * @see      _validateTagArray()
  604.      * @author   Seth Price <seth@pricepages.org>
  605.      */
  606.     function _parentNeeded($out$in)
  607.     {
  608.         if (!isset($this->_definedTags[$in]['parent']||
  609.             ($this->_definedTags[$in]['parent'== 'all')
  610.         {
  611.             return false;
  612.         }
  613.  
  614.         $ar explode('^'$this->_definedTags[$in]['parent']);
  615.         $tags explode(','$ar[1]);
  616.         if ($ar[0== 'none'){
  617.             if ($out && in_array($out$tags)) {
  618.                 return false;
  619.             }
  620.             //Create a tag from the first one on the list
  621.             return $this->_buildTag('['.$tags[0].']');
  622.         }
  623.         if ($ar[0== 'all' && $out && !in_array($out$tags)) {
  624.             return false;
  625.         }
  626.         // Tag is needed, we don't know which one. We could make something up,
  627.         // but it would be so random, I think that it would be worthless.
  628.         return true;
  629.     }
  630.  
  631.     /**
  632.      * Checks to see if a child is needed
  633.      *
  634.      * Checks to see if the current $out tag has an appropriate child. If it
  635.      * does, then it returns false. If a child is needed, then it returns the
  636.      * first tag in the list to add to the stack.
  637.      *
  638.      * @param    array           tag that is on the outside
  639.      * @param    array           tag that is on the inside
  640.      * @return   boolean         false if not needed, tag if needed, true if out
  641.      *                            of our minds
  642.      * @access   private
  643.      * @see      _validateTagArray()
  644.      * @author   Seth Price <seth@pricepages.org>
  645.      */
  646.     function _childNeeded($out$in)
  647.     {
  648.         if (!isset($this->_definedTags[$out]['child']||
  649.            ($this->_definedTags[$out]['child'== 'all')
  650.         {
  651.             return false;
  652.         }
  653.  
  654.         $ar explode('^'$this->_definedTags[$out]['child']);
  655.         $tags explode(','$ar[1]);
  656.         if ($ar[0== 'none'){
  657.             if ($in && in_array($in$tags)) {
  658.                 return false;
  659.             }
  660.             //Create a tag from the first one on the list
  661.             return $this->_buildTag('['.$tags[0].']');
  662.         }
  663.         if ($ar[0== 'all' && $in && !in_array($in$tags)) {
  664.             return false;
  665.         }
  666.         // Tag is needed, we don't know which one. We could make something up,
  667.         // but it would be so random, I think that it would be worthless.
  668.         return true;
  669.     }
  670.  
  671.     /**
  672.      * Checks to see if a tag is allowed inside another tag
  673.      *
  674.      * The allowed tags are extracted from the private _definedTags array.
  675.      *
  676.      * @param    array           tag that is on the outside
  677.      * @param    array           tag that is on the inside
  678.      * @return   boolean         return true if the tag is allowed, false
  679.      *                            otherwise
  680.      * @access   private
  681.      * @see      _validateTagArray()
  682.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  683.      */
  684.     function _isAllowed($out$in)
  685.     {
  686.         if (!$out || ($this->_definedTags[$out]['allowed'== 'all')) {
  687.             return true;
  688.         }
  689.         if ($this->_definedTags[$out]['allowed'== 'none'{
  690.             return false;
  691.         }
  692.  
  693.         $ar explode('^'$this->_definedTags[$out]['allowed']);
  694.         $tags explode(','$ar[1]);
  695.         if ($ar[0== 'none' && in_array($in$tags)) {
  696.             return true;
  697.         }
  698.         if ($ar[0== 'all'  && in_array($in$tags)) {
  699.             return false;
  700.         }
  701.         return false;
  702.     }
  703.  
  704.     /**
  705.      * Builds a parsed string based on the tag array
  706.      *
  707.      * The correct html and attribute values are extracted from the private
  708.      * _definedTags array.
  709.      *
  710.      * @return   none 
  711.      * @access   private
  712.      * @see      $_tagArray
  713.      * @see      $_parsed
  714.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  715.      */
  716.     function _buildParsedString()
  717.     {
  718.         $this->_parsed '';
  719.         foreach ($this->_tagArray as $tag{
  720.             switch ($tag['type']{
  721.  
  722.             // just text
  723.             case 0:
  724.                 $this->_parsed .= $tag['text'];
  725.                 break;
  726.  
  727.             // opening tag
  728.             case 1:
  729.                 $this->_parsed .= '<'.$this->_definedTags[$tag['tag']]['htmlopen'];
  730.                 if ($this->_options['quotestyle'== 'single'$q "'";
  731.                 if ($this->_options['quotestyle'== 'double'$q '"';
  732.                 foreach ($tag['attributes'as $a => $v{
  733.                     //prevent XSS attacks. IMHO this is not enough, though...
  734.                     //@see http://pear.php.net/bugs/bug.php?id=5609
  735.                     $v preg_replace('#(script|about|applet|activex|chrome):#is'"\\1&#058;"$v);
  736.  
  737.                     if (($this->_options['quotewhat'== 'nothing'||
  738.                         (($this->_options['quotewhat'== 'strings'&& is_numeric($v))
  739.                     {
  740.                         $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a]$v'');
  741.                     else {
  742.                         $this->_parsed .= ' '.sprintf($this->_definedTags[$tag['tag']]['attributes'][$a]$v$q);
  743.                     }
  744.                 }
  745.                 if ($this->_definedTags[$tag['tag']]['htmlclose'== '' && $this->_options['xmlclose']{
  746.                     $this->_parsed .= ' /';
  747.                 }
  748.                 $this->_parsed .= '>';
  749.                 break;
  750.  
  751.             // closing tag
  752.             case 2:
  753.                 if ($this->_definedTags[$tag['tag']]['htmlclose'!= ''{
  754.                     $this->_parsed .= '</'.$this->_definedTags[$tag['tag']]['htmlclose'].'>';
  755.                 }
  756.                 break;
  757.             }
  758.         }
  759.     }
  760.  
  761.     /**
  762.      * Sets text in the object to be parsed
  763.      *
  764.      * @param    string          the text to set in the object
  765.      * @return   none 
  766.      * @access   public
  767.      * @see      getText()
  768.      * @see      $_text
  769.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  770.      */
  771.     function setText($str)
  772.     {
  773.         $this->_text $str;
  774.     }
  775.  
  776.     /**
  777.      * Gets the unparsed text from the object
  778.      *
  779.      * @return   string          the text set in the object
  780.      * @access   public
  781.      * @see      setText()
  782.      * @see      $_text
  783.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  784.      */
  785.     function getText()
  786.     {
  787.         return $this->_text;
  788.     }
  789.  
  790.     /**
  791.      * Gets the preparsed text from the object
  792.      *
  793.      * @return   string          the text set in the object
  794.      * @access   public
  795.      * @see      _preparse()
  796.      * @see      $_preparsed
  797.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  798.      */
  799.     function getPreparsed()
  800.     {
  801.         return $this->_preparsed;
  802.     }
  803.  
  804.     /**
  805.      * Gets the parsed text from the object
  806.      *
  807.      * @return   string          the parsed text set in the object
  808.      * @access   public
  809.      * @see      parse()
  810.      * @see      $_parsed
  811.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  812.      */
  813.     function getParsed()
  814.     {
  815.         return $this->_parsed;
  816.     }
  817.  
  818.     /**
  819.      * Parses the text set in the object
  820.      *
  821.      * @return   none 
  822.      * @access   public
  823.      * @see      _preparse()
  824.      * @see      _buildTagArray()
  825.      * @see      _validateTagArray()
  826.      * @see      _buildParsedString()
  827.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  828.      */
  829.     function parse()
  830.     {
  831.         $this->_preparse();
  832.         $this->_buildTagArray();
  833.         $this->_validateTagArray();
  834.         $this->_buildParsedString();
  835.     }
  836.  
  837.     /**
  838.      * Quick method to do setText(), parse() and getParsed at once
  839.      *
  840.      * @return   none 
  841.      * @access   public
  842.      * @see      parse()
  843.      * @see      $_text
  844.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  845.      */
  846.     function qparse($str)
  847.     {
  848.         $this->_text $str;
  849.         $this->parse();
  850.         return $this->_parsed;
  851.     }
  852.  
  853.     /**
  854.      * Quick static method to do setText(), parse() and getParsed at once
  855.      *
  856.      * @return   none 
  857.      * @access   public
  858.      * @see      parse()
  859.      * @see      $_text
  860.      * @author   Stijn de Reede  <sjr@gmx.co.uk>
  861.      */
  862.     function staticQparse($str)
  863.     {
  864.         $p = new HTML_BBCodeParser();
  865.         $str $p->qparse($str);
  866.         unset($p);
  867.         return $str;
  868.     }
  869. }
  870. ?>

Documentation generated on Tue, 05 Jun 2007 17:30:05 -0400 by phpDocumentor 1.3.2. PEAR Logo Copyright © PHP Group 2004.