web/lib/Zend/Markup/Parser/Bbcode.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
--- /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 @@
+<?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];
+        }
+    }
+
+}