diff -r 5b37998e522e -r 162c1de6545a web/lib/Zend/Markup/Parser/Bbcode.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/Zend/Markup/Parser/Bbcode.php Fri Mar 11 15:05:35 2011 +0100 @@ -0,0 +1,504 @@ + 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(?[^\[]*)(?\[(?[' . 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((?\s*\])|\s+(?[' . self::NAME_CHARSET . ']+)(?=?))#'; + 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((?"|\')(?.*?)\\2|(?[^\]\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]; + } + } + +}