Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "bbcode"

Symfony BBCode Parser

BBCode Parser class written specifically for use in symfony, but designed with the possibility of extension in mind. This parser does not simply use regular expressions to replace the bbcodes--it parses the entire string using a recursive parse method, and ensures that all tags that require closing tags are closed in the proper order, thereby ensuring XHTML-compliant output.

Note: The current version is now contained in a plugin at http://trac.symfony-project.com/trac/wiki/sfBBCodeParserPlugin. Anyone who wishes to use this should download the version there. It is fixed and has an easier to use tag definition system.

sfBBCodeParser.class.php:

This is the main parser class. Place it in a lib directory appropriate to your use of the parser.

<?php
 
/**
 * BBCode Parser class written specifically for use in SymfonyBB, but designed
 * with the possibility of extension in mind.  This parser does not simply use
 * regular expressions to replace the bbcodes--it parses the entire string using
 * a recursive parse method, and ensures that all tags that require closing tags
 * are closed in the proper order, thereby ensuring XHTML-compliant output.  
 * 
 * @author Colin Williams <sentineldiety@gmail.com>
 * @package SymfonyBB/BBCode
 * @copyright Copyright © 2006 Colin Williams
 * 
 * 
 */
class SBBCodeParser
{
 
  /**
   * An array containing the loaded tags.
   * @access private
   */
  private $_tags;
 
  /**
   * An array containing the options.
   * @access private
   */
  private $_options;
 
  /**
   * Holds the static instance of this class.
   * @access private
   */
  private static $instance = null;
 
  /**
   * Constructor.
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   */
    public function __construct()
  {
    $this->_loadOptions();
    //echo '<pre>'; var_dump($this->_options); echo '</pre>';
    $this->_loadCodes();
    //echo '<pre>'; var_dump($this->_tags); echo '</pre>';
  }
 
  /**
   * Parses the provided text.
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return string The parsed text.
   */
  public function parse($str)
  {
    $firstopen = $this->_getFirstOpenTagLoc($str);
    if($firstopen === false)
      return $str;
    $tag = $this->_getTagAt($str, $firstopen);
    if($tag === false)
    {
      return $str;
    }
    //Now we actually parse the tag
    $pattern = $this->_tags[$tag['tag']]['pattern'];
    $parsed = str_replace('${attr}', $tag['attr'], $pattern);
    if(!$this->_tags[$tag['tag']]['selfclose'])
      $parsed = str_replace('${content}', $this->parse($tag['content']), $parsed);
    $parsed .= $this->parse($tag['textafter']);
    return $parsed;
  }
 
  /**
   * Loads the defined codes into the private _tags class var.
   * By default, this uses a database to store the tags.  However,
   * this can be easily changed by simply modifying/overloading this
   * method.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * 
   */
  private function _loadCodes()
  {
    $tags = BBCodePeer::doSelect(new Criteria());
    foreach($tags as $tag)
    {
      $this->_tags[$tag->getTag()] = array(
        'name'      => $tag->getName(),
        'pattern'   => $tag->getPattern(),
        'help'      => $tag->getHelp(),
        'selfclose' => $tag->getSelfclose()
      );
    }
  }
 
  /**
   * Loads options into the private _options class var.  By default, this uses
   * the symfony app config file to store the options, but this can be easily
   * changed by modifying/overloading this method.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   */
  private function _loadOptions()
  {
    $this->_options['openchar'] = sbbTools::addDefault(sfConfig::get('app_bbcode_openchar'), '[');
    $this->_options['closechar'] = sbbTools::addDefault(sfConfig::get('app_bbcode_closechar'), ']');
  }
 
  /**
   * Returns the location of the first opening bbcode tag in the string.  If
   * there is no valid tag, returns false.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return int|false
   */
  private function _getFirstOpenTagLoc($str)
  {
    $openchar = $this->_options['openchar'];
    $closechar = $this->_options['closechar'];
    $startpos = 0;
    while(true) {
      $startpos = strpos($str, $openchar, $startpos);
      if($startpos === false) return false;
      $endpos = strpos($str, $closechar, $startpos);
      $tag = substr($str, $startpos + 1, ($endpos - $startpos - 1));
      // If it's a closing tag, ignore it'
      if(substr($str, $startpos + 1, 1) == '/') 
      {
        $startpos += 1;
        continue;
      }
      // Strip out the attribute, if applicable
      if(!(strpos($tag, '=') === false)) 
      {
        $arr = explode('=', $tag);
        $tag = $arr[0];
      }
      // Find out if the possible tag is a valid tag
      if(in_array($tag, array_keys($this->_tags)))
      {
        return $startpos;
      }
      else
        $startpos += 1;
    }
  }
 
  /**
   * Returns an array containing the tag information for the tag which starts at
   * the position given.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * @returns array
   */
  private function _getTagAt($str, $startpos)
  {
    // Very simple check to make sure that it's a somewhat valid tag
    if(substr($str, $startpos, 1) != $this->_options['openchar'])
      return false;
    $tag = array();
    $endpos = strpos($str, $this->_options['closechar'], $startpos);
    $tag['raw'] = substr($str, $startpos + 1, ($endpos - $startpos - 1));
    //Check for an attribute, and add to tag array if present
    if(strpos($tag['raw'], '=') === false)
    {
      $tag['tag'] = substr($str, $startpos + 1, ($endpos - $startpos - 1));
      $tag['attr'] = '';
    }
    else
    {
        $arr = explode('=', $tag['raw']);
      $tag['tag'] = $arr[0];
      $tag['attr'] = $arr[1];
    }
    //Ensure that the tag is a valid tag
    if(!in_array($tag['tag'], array_keys($this->_tags)))
      return false;
    // Tag has no content
    if($this->_tags[$tag['tag']]['selfclose'])
    {
        $tag['selfclose'] = true;
      $tag['textafter'] = substr($str, $endpos +1);
      return $tag;
    }
    $closepos = strripos($str, $this->_options['openchar'].'/'.$tag['tag'].$this->_options['closechar']);
    if($closepos === false)
    {
      $str .= $this->_options['openchar'].$tag['tag'].$this->_options['closechar'];
    }
    $tag['content'] = substr($str, $endpos + 1, $closepos - $endpos - 1);
    $tag['textafter'] = substr($str, $closepos + strlen($this->_options['openchar'].$tag['tag'].$this->_options['closechar']) + 1);
    return $tag;
  }
 
  /**
   * This method, along with the private class variable $instance, allows the
   * parser to be called without having to reload the codes each time (and thus
   * causing numerous unnecessary DB queries).
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return SBBCodeParser An instance of this class
   */
  public static function getInstance()
  {
    if (!isset(self::$instance))
    {
      $class = __CLASS__;
      self::$instance = new $class();
    }
 
    return self::$instance;
  }
}
 
?>

BBCodeHelper.php:

This is the helper function that allows you to parse a string for BBCode. Place it in the appropriate helper dir (it should be in the same lib dir as the main parser file).

<?php
 
function parse_bbcode($unformatted) {
    $formatted = SBBCodeParser::getInstance()->parse($unformatted);
 
    return $formatted;
}eturn $return;
}
?>

Schema:

The following is the XML schema for the BBCode table. Add it to your schema. Sorry, I don't have a YAML version.

<table name="bbcodes" phpName="BBCode">
    <column name="id" type="integer" required="true" autoIncrement="true" primaryKey="true" />
    <column name="tag" type="varchar" size="64" />
    <column name="pattern" type="longvarchar" />
    <column name="name" type="varchar" size="255" />
    <column name="help" type="varchar" size="255" />
    <column name="selfclose" type="boolean" default="false" />
  </table>

Of course, you then need to rebuild your model.

Sample BBCodes:

This is the YAML format for the BBCodes I have implemented so far. Feel free to add to them. The syntax should be fairly self-explanatory. These are only basic formatting.

BBCode:
  b1:
    name:     Bold
    help:     Make the selected text bold
    tag:      b
    pattern:  <strong>${content}</strong>
  b2:
    name:     Italic
    help:     Make the selected text italic
    tag:      i
    pattern:  <em>${content}</em>
  b3:
    name:     Underline
    help:     Underline the selected text
    tag:      u
    pattern:  <ins>${content}</ins>
  b4:
    name:     Strikethrough
    help:     Strikethrough the selected text
    tag:      s
    pattern:  <del>${content}</del>
  b5:
    name:     Color
    help:     Change the color of the selected text
    tag:      color
    pattern:  <span style="color:${attr};">${content}</span>
  b6:
    name:     Quote
    help:     Mark the selected text as a quote
    tag:      quote
    pattern:  <strong>Quote:</strong><br /><q from="${attr}">${content}</q>
  b7:
    name:     Code
    help:     Mark the selected text as code
    tag:      code
    pattern:  <strong>Code:</strong><br /><pre class="code">${content}</pre>
  b8:
    name:     Subscript
    help:     Subscript the selected text
    tag:      sub
    pattern:  <sub>${content}</sub>
  b9:
    name:     Superscript
    help:     Superscript the selected text
    tag:      sup
    pattern:  <sup>${content}</sup>

Config:

Finally, you must add the following config information to your apps/APP/config/app.yml file:

bbcode:
    openchar:   '['
    closechar:  ']'
by Colin Williams on 2006-07-18, tagged bbcode  parser 
(6 comments)