/lib/Zend/Markup/Parser/Bbcode.php
PHP | 504 lines | 276 code | 63 blank | 165 comment | 39 complexity | 989a3fb3b9f5f61a808da073142ab0ea MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
- <?php
- /**
- * Zend Framework
- *
- * LICENSE
- *
- * This source file is subject to the new BSD license that is bundled
- * with this package in the file LICENSE.txt.
- * It is also available through the world-wide-web at this URL:
- * http://framework.zend.com/license/new-bsd
- * If you did not receive a copy of the license and are unable to
- * obtain it through the world-wide-web, please send an email
- * to license@zend.com so we can send you a copy immediately.
- *
- * @category Zend
- * @package Zend_Markup
- * @subpackage Parser
- * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- * @version $Id: Bbcode.php 21127 2010-02-21 15:35:03Z kokx $
- */
- /**
- * @see Zend_Markup_TokenList
- */
- #require_once 'Zend/Markup/TokenList.php';
- /**
- * @see Zend_Markup_Parser_ParserInterface
- */
- #require_once 'Zend/Markup/Parser/ParserInterface.php';
- /**
- * @category Zend
- * @package Zend_Markup
- * @subpackage Parser
- * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
- class Zend_Markup_Parser_Bbcode implements Zend_Markup_Parser_ParserInterface
- {
- const NEWLINE = "[newline\0]";
- // there is a parsing difference between the default tags and single tags
- const TYPE_DEFAULT = 'default';
- const TYPE_SINGLE = 'single';
- const NAME_CHARSET = '^\[\]=\s';
- const STATE_SCAN = 0;
- const STATE_SCANATTRS = 1;
- const STATE_PARSEVALUE = 2;
- /**
- * Token tree
- *
- * @var Zend_Markup_TokenList
- */
- protected $_tree;
- /**
- * Current token
- *
- * @var Zend_Markup_Token
- */
- protected $_current;
- /**
- * Source to tokenize
- *
- * @var string
- */
- protected $_value = '';
- /**
- * Length of the value
- *
- * @var int
- */
- protected $_valueLen = 0;
- /**
- * Current pointer
- *
- * @var int
- */
- protected $_pointer = 0;
- /**
- * The buffer
- *
- * @var string
- */
- protected $_buffer = '';
- /**
- * Temporary tag storage
- *
- * @var array
- */
- protected $_temp;
- /**
- * Stoppers that we are searching for
- *
- * @var array
- */
- protected $_searchedStoppers = array();
- /**
- * Tag information
- *
- * @var array
- */
- protected $_tags = array(
- 'Zend_Markup_Root' => array(
- 'type' => self::TYPE_DEFAULT,
- 'stoppers' => array(),
- ),
- '*' => array(
- 'type' => self::TYPE_DEFAULT,
- 'stoppers' => array(self::NEWLINE, '[/*]', '[/]'),
- ),
- 'hr' => array(
- 'type' => self::TYPE_SINGLE,
- 'stoppers' => array(),
- ),
- 'code' => array(
- 'type' => self::TYPE_DEFAULT,
- 'stoppers' => array('[/code]', '[/]'),
- 'parse_inside' => false
- )
- );
- /**
- * Token array
- *
- * @var array
- */
- protected $_tokens = array();
- /**
- * State
- *
- * @var int
- */
- protected $_state = self::STATE_SCAN;
- /**
- * Prepare the parsing of a bbcode string, the real parsing is done in {@link _parse()}
- *
- * @param string $value
- * @return Zend_Markup_TokenList
- */
- public function parse($value)
- {
- if (!is_string($value)) {
- /**
- * @see Zend_Markup_Parser_Exception
- */
- #require_once 'Zend/Markup/Parser/Exception.php';
- throw new Zend_Markup_Parser_Exception('Value to parse should be a string.');
- }
- if (empty($value)) {
- /**
- * @see Zend_Markup_Parser_Exception
- */
- #require_once 'Zend/Markup/Parser/Exception.php';
- throw new Zend_Markup_Parser_Exception('Value to parse cannot be left empty.');
- }
- $this->_value = str_replace(array("\r\n", "\r", "\n"), self::NEWLINE, $value);
- // variable initialization for tokenizer
- $this->_valueLen = strlen($this->_value);
- $this->_pointer = 0;
- $this->_buffer = '';
- $this->_temp = array();
- $this->_state = self::STATE_SCAN;
- $this->_tokens = array();
- $this->_tokenize();
- // variable initialization for treebuilder
- $this->_searchedStoppers = array();
- $this->_tree = new Zend_Markup_TokenList();
- $this->_current = new Zend_Markup_Token(
- '',
- Zend_Markup_Token::TYPE_NONE,
- 'Zend_Markup_Root'
- );
- $this->_tree->addChild($this->_current);
- $this->_createTree();
- return $this->_tree;
- }
- /**
- * Tokenize
- *
- * @param string $input
- *
- * @return void
- */
- protected function _tokenize()
- {
- $attribute = '';
- while ($this->_pointer < $this->_valueLen) {
- switch ($this->_state) {
- case self::STATE_SCAN:
- $matches = array();
- $regex = '#\G(?<text>[^\[]*)(?<open>\[(?<name>[' . self::NAME_CHARSET . ']+)?)?#';
- preg_match($regex, $this->_value, $matches, null, $this->_pointer);
- $this->_pointer += strlen($matches[0]);
- if (!empty($matches['text'])) {
- $this->_buffer .= $matches['text'];
- }
- if (!isset($matches['open'])) {
- // great, no tag, we are ending the string
- break;
- }
- if (!isset($matches['name'])) {
- $this->_buffer .= $matches['open'];
- break;
- }
- $this->_temp = array(
- 'tag' => '[' . $matches['name'],
- 'name' => $matches['name'],
- 'attributes' => array()
- );
- if ($this->_pointer >= $this->_valueLen) {
- // damn, no tag
- $this->_buffer .= $this->_temp['tag'];
- break 2;
- }
- if ($this->_value[$this->_pointer] == '=') {
- $this->_pointer++;
- $this->_temp['tag'] .= '=';
- $this->_state = self::STATE_PARSEVALUE;
- $attribute = $this->_temp['name'];
- } else {
- $this->_state = self::STATE_SCANATTRS;
- }
- break;
- case self::STATE_SCANATTRS:
- $matches = array();
- $regex = '#\G((?<end>\s*\])|\s+(?<attribute>[' . self::NAME_CHARSET . ']+)(?<eq>=?))#';
- if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
- break 2;
- }
- $this->_pointer += strlen($matches[0]);
- if (!empty($matches['end'])) {
- if (!empty($this->_buffer)) {
- $this->_tokens[] = array(
- 'tag' => $this->_buffer,
- 'type' => Zend_Markup_Token::TYPE_NONE
- );
- $this->_buffer = '';
- }
- $this->_temp['tag'] .= $matches['end'];
- $this->_temp['type'] = Zend_Markup_Token::TYPE_TAG;
- $this->_tokens[] = $this->_temp;
- $this->_temp = array();
- $this->_state = self::STATE_SCAN;
- } else {
- // attribute name
- $attribute = $matches['attribute'];
- $this->_temp['tag'] .= $matches[0];
- $this->_temp['attributes'][$attribute] = '';
- if (empty($matches['eq'])) {
- $this->_state = self::STATE_SCANATTRS;
- } else {
- $this->_state = self::STATE_PARSEVALUE;
- }
- }
- break;
- case self::STATE_PARSEVALUE:
- $matches = array();
- $regex = '#\G((?<quote>"|\')(?<valuequote>.*?)\\2|(?<value>[^\]\s]+))#';
- if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
- $this->_state = self::STATE_SCANATTRS;
- break;
- }
- $this->_pointer += strlen($matches[0]);
- if (!empty($matches['quote'])) {
- $this->_temp['attributes'][$attribute] = $matches['valuequote'];
- } else {
- $this->_temp['attributes'][$attribute] = $matches['value'];
- }
- $this->_temp['tag'] .= $matches[0];
- $this->_state = self::STATE_SCANATTRS;
- break;
- }
- }
- if (!empty($this->_buffer)) {
- $this->_tokens[] = array(
- 'tag' => $this->_buffer,
- 'type' => Zend_Markup_Token::TYPE_NONE
- );
- }
- }
- /**
- * Parse the token array into a tree
- *
- * @param array $tokens
- *
- * @return void
- */
- public function _createTree()
- {
- foreach ($this->_tokens as $token) {
- // first we want to know if this tag is a stopper, or at least a searched one
- if ($this->_isStopper($token['tag'])) {
- // find the stopper
- $oldItems = array();
- while (!in_array($token['tag'], $this->_tags[$this->_current->getName()]['stoppers'])) {
- $oldItems[] = clone $this->_current;
- $this->_current = $this->_current->getParent();
- }
- // we found the stopper, so stop the tag
- $this->_current->setStopper($token['tag']);
- $this->_removeFromSearchedStoppers($this->_current);
- $this->_current = $this->_current->getParent();
- // add the old items again if there are any
- if (!empty($oldItems)) {
- foreach (array_reverse($oldItems) as $item) {
- /* @var $token Zend_Markup_Token */
- $this->_current->addChild($item);
- $item->setParent($this->_current);
- $this->_current = $item;
- }
- }
- } else {
- if ($token['type'] == Zend_Markup_Token::TYPE_TAG) {
- if ($token['tag'] == self::NEWLINE) {
- // this is a newline tag, add it as a token
- $this->_current->addChild(new Zend_Markup_Token(
- "\n",
- Zend_Markup_Token::TYPE_NONE,
- '',
- array(),
- $this->_current
- ));
- } elseif (isset($token['name']) && ($token['name'][0] == '/')) {
- // this is a stopper, add it as a empty token
- $this->_current->addChild(new Zend_Markup_Token(
- $token['tag'],
- Zend_Markup_Token::TYPE_NONE,
- '',
- array(),
- $this->_current
- ));
- } elseif (isset($this->_tags[$this->_current->getName()]['parse_inside'])
- && !$this->_tags[$this->_current->getName()]['parse_inside']
- ) {
- $this->_current->addChild(new Zend_Markup_Token(
- $token['tag'],
- Zend_Markup_Token::TYPE_NONE,
- '',
- array(),
- $this->_current
- ));
- } else {
- // add the tag
- $child = new Zend_Markup_Token(
- $token['tag'],
- $token['type'],
- $token['name'],
- $token['attributes'],
- $this->_current
- );
- $this->_current->addChild($child);
- // add stoppers for this tag, if its has stoppers
- if ($this->_getType($token['name']) == self::TYPE_DEFAULT) {
- $this->_current = $child;
- $this->_addToSearchedStoppers($this->_current);
- }
- }
- } else {
- // no tag, just add it as a simple token
- $this->_current->addChild(new Zend_Markup_Token(
- $token['tag'],
- Zend_Markup_Token::TYPE_NONE,
- '',
- array(),
- $this->_current
- ));
- }
- }
- }
- }
- /**
- * Check if there is a tag declaration, and if it isnt there, add it
- *
- * @param string $name
- *
- * @return void
- */
- protected function _checkTagDeclaration($name)
- {
- if (!isset($this->_tags[$name])) {
- $this->_tags[$name] = array(
- 'type' => self::TYPE_DEFAULT,
- 'stoppers' => array(
- '[/' . $name . ']',
- '[/]'
- )
- );
- }
- }
- /**
- * Check the tag's type
- *
- * @param string $name
- * @return string
- */
- protected function _getType($name)
- {
- $this->_checkTagDeclaration($name);
- return $this->_tags[$name]['type'];
- }
- /**
- * Check if the tag is a stopper
- *
- * @param string $tag
- * @return bool
- */
- protected function _isStopper($tag)
- {
- $this->_checkTagDeclaration($this->_current->getName());
- if (!empty($this->_searchedStoppers[$tag])) {
- return true;
- }
- return false;
- }
- /**
- * Add to searched stoppers
- *
- * @param Zend_Markup_Token $token
- * @return void
- */
- protected function _addToSearchedStoppers(Zend_Markup_Token $token)
- {
- $this->_checkTagDeclaration($token->getName());
- foreach ($this->_tags[$token->getName()]['stoppers'] as $stopper) {
- if (!isset($this->_searchedStoppers[$stopper])) {
- $this->_searchedStoppers[$stopper] = 0;
- }
- ++$this->_searchedStoppers[$stopper];
- }
- }
- /**
- * Remove from searched stoppers
- *
- * @param Zend_Markup_Token $token
- * @return void
- */
- protected function _removeFromSearchedStoppers(Zend_Markup_Token $token)
- {
- $this->_checkTagDeclaration($token->getName());
- foreach ($this->_tags[$token->getName()]['stoppers'] as $stopper) {
- --$this->_searchedStoppers[$stopper];
- }
- }
- }