|
|
@@ -39,6 +39,18 @@ require_once 'Zend/Markup/Parser/ParserInterface.php';
|
|
|
*/
|
|
|
class Zend_Markup_Parser_Textile implements Zend_Markup_Parser_ParserInterface
|
|
|
{
|
|
|
+
|
|
|
+ const STATE_SCAN = 0;
|
|
|
+ const STATE_NEW_PARAGRAPH = 1;
|
|
|
+ const STATE_NEWLINE = 2;
|
|
|
+
|
|
|
+ const MATCH_ATTR_CLASSID = '\((?<attr_class>[a-zA-Z0-9_]+)?(?:\#(?<attr_id>[a-zA-Z0-9_]+))?\)';
|
|
|
+ const MATCH_ATTR_STYLE = "\{(?<attr_style>[^\}\n]+)\}";
|
|
|
+ const MATCH_ATTR_LANG = '\[(?<attr_lang>[a-zA-Z_]+)\]';
|
|
|
+ const MATCH_ATTR_ALIGN = '(?<attr_align>\<\>?|\>|=)';
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* Token tree
|
|
|
*
|
|
|
@@ -103,17 +115,18 @@ class Zend_Markup_Parser_Textile implements Zend_Markup_Parser_ParserInterface
|
|
|
);
|
|
|
|
|
|
/**
|
|
|
- * The list's level
|
|
|
+ * Token array
|
|
|
*
|
|
|
- * @var int
|
|
|
+ * @var array
|
|
|
*/
|
|
|
- protected $_listLevel = 0;
|
|
|
+ protected $_tokens = array();
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Prepare the parsing of a Textile string, the real parsing is done in {@link _parse()}
|
|
|
*
|
|
|
- * @param string $value
|
|
|
+ * @param string $value
|
|
|
+ *
|
|
|
* @return array
|
|
|
*/
|
|
|
public function parse($value)
|
|
|
@@ -133,787 +146,433 @@ class Zend_Markup_Parser_Textile implements Zend_Markup_Parser_ParserInterface
|
|
|
throw new Zend_Markup_Parser_Exception('Value to parse cannot be left empty.');
|
|
|
}
|
|
|
|
|
|
- // first make we only have LF newlines
|
|
|
+ // first make we only have LF newlines, also trim the value
|
|
|
$this->_value = str_replace(array("\r\n", "\r"), "\n", $value);
|
|
|
-
|
|
|
- // trim and add a leading LF to make sure that headings on the first line
|
|
|
- // are parsed correctly
|
|
|
$this->_value = trim($this->_value);
|
|
|
|
|
|
- // initialize variables
|
|
|
- $this->_tree = new Zend_Markup_TokenList();
|
|
|
- $this->_valueLen = strlen($this->_value);
|
|
|
+ // initialize variables and tokenize
|
|
|
+ $this->_valueLen = iconv_strlen($this->_value, 'UTF-8');
|
|
|
$this->_pointer = 0;
|
|
|
$this->_buffer = '';
|
|
|
$this->_temp = array();
|
|
|
- $this->_current = new Zend_Markup_Token('', Zend_Markup_Token::TYPE_NONE, 'Zend_Markup_Root');
|
|
|
- $this->_tree->addChild($this->_current);
|
|
|
-
|
|
|
+ $this->_tokens = array();
|
|
|
|
|
|
- $info = array(
|
|
|
- 'tag' => '',
|
|
|
- 'attributes' => array()
|
|
|
- );
|
|
|
+ $this->_tokenize();
|
|
|
|
|
|
- // check if the base paragraph has some extra information
|
|
|
- if ($this->_value[$this->_pointer] == 'p') {
|
|
|
- $this->_pointer++;
|
|
|
- $info = $this->_parseTagInfo('p', '.', true);
|
|
|
-
|
|
|
- // check if the paragraph definition is correct
|
|
|
- if (substr($this->_value, $this->_pointer, 2) != '. ') {
|
|
|
- $this->_pointer = 0;
|
|
|
- } else {
|
|
|
- $this->_pointer += 2;
|
|
|
- }
|
|
|
- }
|
|
|
+ // create the tree
|
|
|
+ $this->_tree = new Zend_Markup_TokenList();
|
|
|
|
|
|
- // add the base paragraph
|
|
|
- $paragraph = new Zend_Markup_Token(
|
|
|
- $info['tag'],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- 'p',
|
|
|
- $info['attributes'],
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $this->_current->addChild($paragraph);
|
|
|
- $this->_current = $paragraph;
|
|
|
+ $this->_current = new Zend_Markup_Token('', Zend_Markup_Token::TYPE_NONE, 'Zend_Markup_Root');
|
|
|
+ $this->_tree->addChild($this->_current);
|
|
|
|
|
|
- // start the parsing process
|
|
|
- $this->_parse(true);
|
|
|
+ $this->_createTree();
|
|
|
|
|
|
return $this->_tree;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Parse a Textile string
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parse($checkStartTags = false)
|
|
|
- {
|
|
|
- // just keep looping until the parsing is done
|
|
|
- if ($checkStartTags) {
|
|
|
- // check the starter tags (newlines)
|
|
|
- switch ($this->_value[$this->_pointer]) {
|
|
|
- case 'h':
|
|
|
- $this->_parseHeading();
|
|
|
- break;
|
|
|
- case '*':
|
|
|
- case '#':
|
|
|
- $this->_parseList();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- while ($this->_pointer < $this->_valueLen) {
|
|
|
- $this->_parseTag();
|
|
|
- }
|
|
|
-
|
|
|
- $this->_processBuffer();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse an inline string
|
|
|
+ * Tokenize a textile string
|
|
|
*
|
|
|
- * @param string $value
|
|
|
- * @param Zend_Markup_Token $token
|
|
|
- * @return Zend_Markup_Token
|
|
|
- */
|
|
|
- protected function _parseInline($value, Zend_Markup_Token $token)
|
|
|
- {
|
|
|
- // save the old values
|
|
|
- $oldValue = $this->_value;
|
|
|
- $oldValueLen = $this->_valueLen;
|
|
|
- $oldPointer = $this->_pointer;
|
|
|
- $oldTree = $this->_tree;
|
|
|
- $oldCurrent = $this->_current;
|
|
|
- $oldBuffer = $this->_buffer;
|
|
|
-
|
|
|
- // set the new values
|
|
|
- $this->_value = $value;
|
|
|
- $this->_valueLen = strlen($value);
|
|
|
- $this->_pointer = 0;
|
|
|
- $this->_tree = $token;
|
|
|
- $this->_current = $token;
|
|
|
- $this->_buffer = '';
|
|
|
-
|
|
|
- // parse
|
|
|
- $this->_parse();
|
|
|
-
|
|
|
- // set the old values
|
|
|
- $this->_value = $oldValue;
|
|
|
- $this->_valueLen = $oldValueLen;
|
|
|
- $this->_pointer = $oldPointer;
|
|
|
- $this->_tree = $oldTree;
|
|
|
- $this->_current = $oldCurrent;
|
|
|
- $this->_buffer = $oldBuffer;
|
|
|
-
|
|
|
- return $token;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse a tag
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseTag()
|
|
|
- {
|
|
|
- switch ($this->_value[$this->_pointer]) {
|
|
|
- case '*':
|
|
|
- case '_':
|
|
|
- case '?':
|
|
|
- case '-':
|
|
|
- case '+':
|
|
|
- case '^':
|
|
|
- case '~':
|
|
|
- case '%':
|
|
|
- // simple tags like bold, italic
|
|
|
- $this->_parseSimpleTag($this->_value[$this->_pointer]);
|
|
|
- break;
|
|
|
- case '@':
|
|
|
- case '!':
|
|
|
- // simple tags, only they don't have anything inside them
|
|
|
- $this->_parseEmptyTag($this->_value[$this->_pointer]);
|
|
|
- break;
|
|
|
- case '"':
|
|
|
- $this->_parseLink();
|
|
|
- break;
|
|
|
- case '(':
|
|
|
- $this->_parseAcronym();
|
|
|
- break;
|
|
|
- case "\n":
|
|
|
- // this could mean multiple things, let this function check what
|
|
|
- $this->_parseNewline();
|
|
|
- break;
|
|
|
- default:
|
|
|
- // just add this token to the buffer
|
|
|
- $this->_buffer .= $this->_value[$this->_pointer++];
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse tag information
|
|
|
- *
|
|
|
- * @todo use $tagEnd property for identation
|
|
|
- * @param string $tag
|
|
|
- * @param string $tagEnd
|
|
|
- * @param bool $block
|
|
|
* @return array
|
|
|
*/
|
|
|
- protected function _parseTagInfo($tag = '', $tagEnd = '', $block = false)
|
|
|
+ protected function _tokenize()
|
|
|
{
|
|
|
- $info = array(
|
|
|
- 'attributes' => array(),
|
|
|
- 'tag' => $tag
|
|
|
- );
|
|
|
-
|
|
|
- if ($this->_pointer >= $this->_valueLen) {
|
|
|
- return $info;
|
|
|
- }
|
|
|
-
|
|
|
- // check which attribute
|
|
|
- switch ($this->_value[$this->_pointer]) {
|
|
|
- case '(':
|
|
|
- // class or id
|
|
|
- $attribute = $this->_parseAttributeEnd(')', $tagEnd);
|
|
|
-
|
|
|
- if (!$attribute) {
|
|
|
- break;
|
|
|
- }
|
|
|
+ $state = self::STATE_NEW_PARAGRAPH;
|
|
|
|
|
|
- $info['tag'] .= '(' . $attribute . ')';
|
|
|
-
|
|
|
- if ($attribute[0] == '#') {
|
|
|
- $info['attributes']['id'] = substr($attribute, 1);
|
|
|
- } elseif (($len = strpos($attribute, '#')) !== false) {
|
|
|
- $info['attributes']['class'] = substr($attribute, 0, $len);
|
|
|
- $info['attributes']['id'] = substr($attribute, $len + 1);
|
|
|
- } else {
|
|
|
- $info['attributes']['class'] = $attribute;
|
|
|
- }
|
|
|
-
|
|
|
- $this->_pointer++;
|
|
|
- break;
|
|
|
- case '{':
|
|
|
- // style
|
|
|
- $attribute = $this->_parseAttributeEnd('}', $tagEnd);
|
|
|
+ $attrsMatch = implode('|', array(
|
|
|
+ self::MATCH_ATTR_CLASSID,
|
|
|
+ self::MATCH_ATTR_STYLE,
|
|
|
+ self::MATCH_ATTR_LANG,
|
|
|
+ self::MATCH_ATTR_ALIGN
|
|
|
+ ));
|
|
|
|
|
|
- if (!$attribute) {
|
|
|
- break;
|
|
|
- }
|
|
|
+ $paragraph = '';
|
|
|
|
|
|
- $info['tag'] .= '{' . $attribute . '}';
|
|
|
+ while ($this->_pointer < $this->_valueLen) {
|
|
|
+ switch ($state) {
|
|
|
+ case self::STATE_SCAN:
|
|
|
+ $matches = array(); //[^\n*_?+~%@!-]
|
|
|
+ $acronym = '(?<acronym>[A-Z]{2,})\((?<title>[^\)]+)\)';
|
|
|
+ $regex = '#\G(?<text>.*?)(?:'
|
|
|
+ . "(?:(?<nl_paragraph>\n{2,})|(?<nl_break>\n))|"
|
|
|
+ . '(?<tag>'
|
|
|
+ . "(?<name>\*{1,2}|_{1,2}|\?{2}|\-|\+|\~|\^|%|@|!|$|{$acronym}"
|
|
|
+ . '|":(?<url>[^\s]+)|")'
|
|
|
+ . "(?:{$attrsMatch})*)"
|
|
|
+ . ')#si';
|
|
|
+ preg_match($regex, $this->_value, $matches, null, $this->_pointer);
|
|
|
+
|
|
|
+ $this->_pointer += strlen($matches[0]);
|
|
|
+
|
|
|
+ if (!empty($matches['text'])) {
|
|
|
+ $this->_buffer .= $matches['text'];
|
|
|
+ }
|
|
|
|
|
|
- $info['attributes']['style'] = $attribute;
|
|
|
+ // first add the buffer
|
|
|
+ if (!empty($this->_buffer)) {
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $this->_buffer,
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_NONE
|
|
|
+ );
|
|
|
+ $this->_buffer = '';
|
|
|
+ }
|
|
|
|
|
|
- $this->_pointer++;
|
|
|
- break;
|
|
|
- case '[':
|
|
|
- // style
|
|
|
- $attribute = $this->_parseAttributeEnd(']', $tagEnd);
|
|
|
+ if (!empty($matches['nl_paragraph'])) {
|
|
|
+ $this->_temp = array(
|
|
|
+ 'tag' => $matches['nl_paragraph'],
|
|
|
+ 'name' => 'p',
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'attributes' => array()
|
|
|
+ );
|
|
|
+
|
|
|
+ $state = self::STATE_NEW_PARAGRAPH;
|
|
|
+ } elseif (!empty($matches['nl_break'])) {
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches['nl_break'],
|
|
|
+ 'name' => 'break',
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'attributes' => array()
|
|
|
+ );
|
|
|
+
|
|
|
+ $state = self::STATE_NEWLINE;
|
|
|
+ } elseif (!empty($matches['tag'])) {
|
|
|
+ if (isset($this->_simpleTags[$matches['name']])) {
|
|
|
+ // now add the new token
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches['tag'],
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'name' => $this->_simpleTags[$matches['name']],
|
|
|
+ 'attributes' => $this->_extractAttributes($matches)
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ $attributes = $this->_extractAttributes($matches);
|
|
|
+ if ($matches['tag'][0] == '"') {
|
|
|
+ $name = 'url';
|
|
|
+ if (isset($matches['url'])) {
|
|
|
+ $attributes['url'] = $matches['url'];
|
|
|
+ }
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches['tag'],
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'name' => $name,
|
|
|
+ 'attributes' => $attributes
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ $name = 'acronym';
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => '',
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'name' => 'acronym',
|
|
|
+ 'attributes' => array(
|
|
|
+ 'title' => $matches['title']
|
|
|
+ )
|
|
|
+ );
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches['acronym'],
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_NONE
|
|
|
+ );
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => '(' . $matches['title'] . ')',
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'name' => 'acronym',
|
|
|
+ 'attributes' => array()
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $state = self::STATE_SCAN;
|
|
|
+ }
|
|
|
|
|
|
- if (!$attribute) {
|
|
|
break;
|
|
|
- }
|
|
|
-
|
|
|
- $info['tag'] .= '[' . $attribute . ']';
|
|
|
-
|
|
|
- $info['attributes']['lang'] = $attribute;
|
|
|
-
|
|
|
- $this->_pointer++;
|
|
|
- break;
|
|
|
- case '<':
|
|
|
- if ($block) {
|
|
|
- $info['tag'] .= '<';
|
|
|
- if (($this->_value[++$this->_pointer] == '>')) {
|
|
|
- $info['attributes']['align'] = 'justify';
|
|
|
- $info['tag'] .= '>';
|
|
|
- $this->_pointer++;
|
|
|
+ case self::STATE_NEW_PARAGRAPH:
|
|
|
+ if (empty($this->_temp)) {
|
|
|
+ $this->_temp = array(
|
|
|
+ 'tag' => '',
|
|
|
+ 'name' => 'p',
|
|
|
+ 'type' => Zend_Markup_token::TYPE_TAG,
|
|
|
+ 'attributes' => array()
|
|
|
+ );
|
|
|
} else {
|
|
|
- $info['attributes']['align'] = 'left';
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => "\n",
|
|
|
+ 'name' => 'p',
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'attributes' => array()
|
|
|
+ );
|
|
|
+ $this->_temp['tag'] = substr($this->_temp['tag'], 1);
|
|
|
}
|
|
|
- }
|
|
|
- break;
|
|
|
- case '>':
|
|
|
- if ($block) {
|
|
|
- $info['attributes']['align'] = 'right';
|
|
|
- $info['tag'] .= '>';
|
|
|
- $this->_pointer++;
|
|
|
- }
|
|
|
- break;
|
|
|
- case '=':
|
|
|
- if ($block) {
|
|
|
- $info['attributes']['align'] = 'center';
|
|
|
- $info['tag'] .= '=';
|
|
|
- $this->_pointer++;
|
|
|
- }
|
|
|
- break;
|
|
|
- default:
|
|
|
- // simply do nothing, there are no attributes
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- return $info;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse the attribute's end
|
|
|
- *
|
|
|
- * @todo use $tagEnd property for indentation
|
|
|
- * @param string $endToken
|
|
|
- * @param string $tagEnd
|
|
|
- * @param bool $block
|
|
|
- * @return string|bool
|
|
|
- */
|
|
|
- protected function _parseAttributeEnd($endToken, $tagEnd = '', $block = false)
|
|
|
- {
|
|
|
- $value = '';
|
|
|
-
|
|
|
- $oldPointer = $this->_pointer;
|
|
|
-
|
|
|
- while ($this->_pointer < $this->_valueLen) {
|
|
|
- if ($this->_pointer + 1 >= $this->_valueLen) {
|
|
|
- $this->_pointer = $oldPointer;
|
|
|
- return false;
|
|
|
- }
|
|
|
- if ($this->_value[++$this->_pointer] == $endToken) {
|
|
|
- return $value;
|
|
|
- }
|
|
|
- $value .= $this->_value[$this->_pointer];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse a simple markup tag
|
|
|
- *
|
|
|
- * @param string $tag
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseSimpleTag($tag)
|
|
|
- {
|
|
|
- if (++$this->_pointer >= $this->_valueLen) {
|
|
|
- // could be a stopper
|
|
|
- if ($this->_isSimpleStopper($tag)) {
|
|
|
- $this->_processBuffer();
|
|
|
- $this->_current->setStopper($tag);
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
- } else {
|
|
|
- $this->_buffer .= $tag;
|
|
|
- }
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if ($this->_value[$this->_pointer] == $tag) {
|
|
|
- $tag = $tag . $tag;
|
|
|
- $this->_pointer++;
|
|
|
- }
|
|
|
-
|
|
|
- // check if this is a stopper
|
|
|
- if ($this->_isSimpleStopper($tag)) {
|
|
|
- $this->_processBuffer();
|
|
|
- $this->_current->setStopper($tag);
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // check if this is a tag
|
|
|
- if (isset($this->_simpleTags[$tag])) {
|
|
|
- $name = $this->_simpleTags[$tag];
|
|
|
-
|
|
|
- // process the buffer and add the tag
|
|
|
- $this->_processBuffer();
|
|
|
-
|
|
|
- // parse a possible attribute
|
|
|
- $info = $this->_parseTagInfo($tag);
|
|
|
-
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- $info['tag'],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- $name,
|
|
|
- $info['attributes'],
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $this->_current->addChild($token);
|
|
|
- $this->_current = $token;
|
|
|
- } else {
|
|
|
- $this->_buffer .= $tag;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse an 'empty' markup tag
|
|
|
- *
|
|
|
- * @todo implement support for attributes
|
|
|
- * @param string $tag
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseEmptyTag($tag)
|
|
|
- {
|
|
|
- if (!isset($this->_simpleTags[$tag])) {
|
|
|
- $this->_buffer .= $tag;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // add the tag
|
|
|
- $this->_processBuffer();
|
|
|
-
|
|
|
- $this->_pointer++;
|
|
|
|
|
|
- $info = $this->_parseTagInfo($tag);
|
|
|
-
|
|
|
- $name = $this->_simpleTags[$tag];
|
|
|
+ $matches = array(); //[^\n*_?+~%@!-] (\()? [^()]+ (?(1)\))
|
|
|
+ $regex = "#\G(?<name>(h[1-6]|p)|(?:\#|\*))(?:{$attrsMatch})*(?(2)\.\s|\s)#i";
|
|
|
+ if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
|
|
|
+ $this->_tokens[] = $this->_temp;
|
|
|
+ $state = self::STATE_SCAN;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- $info['tag'],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- $name,
|
|
|
- $info['attributes'],
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $this->_current->addChild($token);
|
|
|
- $this->_current = $token;
|
|
|
+ $this->_pointer += strlen($matches[0]);
|
|
|
|
|
|
- // find the stopper
|
|
|
- while ($this->_valueLen > $this->_pointer) {
|
|
|
- if ($this->_value[$this->_pointer] == $tag) {
|
|
|
- // found the stopper, set it and return
|
|
|
- $this->_pointer++;
|
|
|
+ if ($matches['name'] == 'p') {
|
|
|
+ $this->_temp['tag'] .= $matches[0];
|
|
|
+ $this->_temp['attributes'] = $this->_extractAttributes($matches);
|
|
|
|
|
|
- $this->_processBuffer();
|
|
|
- $this->_current->setStopper($tag);
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
- return;
|
|
|
- } else {
|
|
|
- // not yet found, add the character to the buffer and go to the next one
|
|
|
- $this->_buffer .= $this->_value[$this->_pointer++];
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse a link
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseLink()
|
|
|
- {
|
|
|
- // first find the other "
|
|
|
- $len = strcspn($this->_value, '"', ++$this->_pointer);
|
|
|
- $text = substr($this->_value, $this->_pointer, $len);
|
|
|
-
|
|
|
- // not a link tag
|
|
|
- if (($this->_pointer + $len >= $this->_valueLen) || ($this->_value[$this->_pointer + $len++] != '"')) {
|
|
|
- $this->_buffer .= '"' . $text;
|
|
|
- $this->_pointer += $len;
|
|
|
- return;
|
|
|
- }
|
|
|
- // not a link tag
|
|
|
- if (($this->_pointer + $len >= $this->_valueLen) || ($this->_value[$this->_pointer + $len++] != ':')) {
|
|
|
- $this->_buffer .= '"' . $text . '"';
|
|
|
- $this->_pointer += $len;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // update the pointer
|
|
|
- $this->_pointer += $len;
|
|
|
-
|
|
|
- // now, get the URL
|
|
|
- $len = strcspn($this->_value, "\n\t ", $this->_pointer);
|
|
|
- $url = substr($this->_value, $this->_pointer, $len);
|
|
|
-
|
|
|
- $this->_pointer += $len;
|
|
|
-
|
|
|
- // gather the attributes
|
|
|
- $attributes = array(
|
|
|
- 'url' => $url,
|
|
|
- );
|
|
|
-
|
|
|
- // add the tag
|
|
|
- $this->_processBuffer();
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- '"',
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- 'url',
|
|
|
- $attributes,
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $token->addChild(new Zend_Markup_Token(
|
|
|
- $text,
|
|
|
- Zend_Markup_Token::TYPE_NONE,
|
|
|
- '',
|
|
|
- array(),
|
|
|
- $token
|
|
|
- ));
|
|
|
- $token->setStopper('":');
|
|
|
-
|
|
|
- $this->_current->addChild($token);
|
|
|
- }
|
|
|
+ $this->_tokens[] = $this->_temp;
|
|
|
+ $this->_temp = array();
|
|
|
+ } else {
|
|
|
+ $this->_tokens[] = $this->_temp;
|
|
|
+ $this->_temp = array();
|
|
|
+
|
|
|
+ $name = $matches['name'];
|
|
|
+ $attributes = $this->_extractAttributes($matches);
|
|
|
+
|
|
|
+ if ($name == '#') {
|
|
|
+ $name = 'list';
|
|
|
+ $attributes['list'] = 'decimal';
|
|
|
+ } elseif ($name == '*') {
|
|
|
+ $name = 'list';
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches[0],
|
|
|
+ 'name' => $name,
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'attributes' => $attributes
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
- * Parse a newline
|
|
|
- *
|
|
|
- * A newline could mean multiple things:
|
|
|
- * - Heading {@link _parseHeading()}
|
|
|
- * - List {@link _parseList()}
|
|
|
- * - Paragraph {@link _parseParagraph()}
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseNewline()
|
|
|
- {
|
|
|
- if (!empty($this->_buffer) && ($this->_buffer[strlen($this->_buffer) - 1] == "\n")) {
|
|
|
- $this->_parseParagraph();
|
|
|
- } else {
|
|
|
- switch ($this->_value[++$this->_pointer]) {
|
|
|
- case 'h':
|
|
|
- $this->_parseHeading();
|
|
|
+ $state = self::STATE_SCAN;
|
|
|
break;
|
|
|
- case '*':
|
|
|
- case '#':
|
|
|
- $this->_parseList();
|
|
|
- break;
|
|
|
- default:
|
|
|
- $this->_buffer .= "\n";
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Parse a paragraph declaration
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _parseParagraph()
|
|
|
- {
|
|
|
- // remove the newline from the buffer and increase the pointer
|
|
|
- $this->_buffer = substr($this->_buffer, 0, -1);
|
|
|
- $this->_pointer++;
|
|
|
-
|
|
|
- // check if we are in the current paragraph
|
|
|
- if ($this->_current->getName() == 'p') {
|
|
|
- $this->_processBuffer();
|
|
|
-
|
|
|
- $this->_current->setStopper("\n");
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
+ case self::STATE_NEWLINE:
|
|
|
+ $matches = array(); //[^\n*_?+~%@!-]
|
|
|
+ $regex = "#\G(?<name>(h[1-6])|(?:\#|\*))(?:{$attrsMatch})*(?(2)\.\s|\s)#si";
|
|
|
+ if (!preg_match($regex, $this->_value, $matches, null, $this->_pointer)) {
|
|
|
+ $state = self::STATE_SCAN;
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- $info = array(
|
|
|
- 'tag' => "\n",
|
|
|
- 'attributes' => array()
|
|
|
- );
|
|
|
+ $this->_pointer += strlen($matches[0]);
|
|
|
|
|
|
- $oldPointer = $this->_pointer;
|
|
|
+ $name = $matches['name'];
|
|
|
+ $attributes = $this->_extractAttributes($matches);
|
|
|
|
|
|
- if ($this->_value[$this->_pointer] == 'p') {
|
|
|
- $this->_pointer++;
|
|
|
- $info = $this->_parseTagInfo("\np", '.', true);
|
|
|
+ if ($name == '#') {
|
|
|
+ $name = 'list';
|
|
|
+ $attributes['list'] = 'decimal';
|
|
|
+ } elseif ($name == '*') {
|
|
|
+ $name = 'list';
|
|
|
+ }
|
|
|
|
|
|
- if (substr($this->_value, $this->_pointer, 2) == '. ') {
|
|
|
- $this->_pointer += 2;
|
|
|
- } else {
|
|
|
- // incorrect declaration of paragraph, reset the pointer and use default info
|
|
|
- $this->_pointer = $oldPointer;
|
|
|
- $info = array(
|
|
|
- 'tag' => "\n",
|
|
|
- 'attributes' => array()
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $matches[0],
|
|
|
+ 'name' => $name,
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'attributes' => $attributes
|
|
|
);
|
|
|
- }
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // create a new one and jump onto it
|
|
|
- $paragraph = new Zend_Markup_Token(
|
|
|
- $info['tag'],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- 'p',
|
|
|
- $info['attributes'],
|
|
|
- $this->_current
|
|
|
+ if (!empty($buffer)) {
|
|
|
+ $this->_tokens[] = array(
|
|
|
+ 'tag' => $buffer,
|
|
|
+ 'type' => Zend_Markup_Token::TYPE_NONE
|
|
|
);
|
|
|
- $this->_current->addChild($paragraph);
|
|
|
- $this->_current = $paragraph;
|
|
|
- } else {
|
|
|
- /**
|
|
|
- * @todo Go down in the tree until you find the paragraph
|
|
|
- * while remembering every step. After that, close the
|
|
|
- * paragraph, add a new one and climb back up by re-adding
|
|
|
- * every step
|
|
|
- */
|
|
|
+ $buffer = '';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Parse a heading
|
|
|
+ * Create a tree from the tokenized text
|
|
|
*
|
|
|
- * @todo implement support for attributes
|
|
|
* @return void
|
|
|
*/
|
|
|
- protected function _parseHeading()
|
|
|
+ protected function _createTree()
|
|
|
{
|
|
|
- // check if it is a valid heading
|
|
|
- if (in_array($this->_value[++$this->_pointer], range(1, 6))) {
|
|
|
- $name = 'h' . $this->_value[$this->_pointer++];
|
|
|
+ $inside = true;
|
|
|
+
|
|
|
+ foreach ($this->_tokens as $key => $token) {
|
|
|
+ // first check if the token is a stopper
|
|
|
+ if ($this->_isStopper($token, $this->_current)) {
|
|
|
+ if ($this->_current->getName() == 'li') {
|
|
|
+ // list items are handled differently
|
|
|
+ if (isset($this->_tokens[$key + 1])
|
|
|
+ && ($this->_tokens[$key + 1]['type'] == Zend_Markup_Token::TYPE_TAG)
|
|
|
+ && ($this->_tokens[$key + 1]['name'] == 'list')
|
|
|
+ ) {
|
|
|
+ // the next item is a correct tag
|
|
|
+ $this->_current->setStopper($token['tag']);
|
|
|
+
|
|
|
+ $this->_current = $this->_current->getParent();
|
|
|
+ } else {
|
|
|
+ // close the list
|
|
|
+ $this->_current->setStopper($token['tag']);
|
|
|
|
|
|
- $info = $this->_parseTagInfo($name, '.', true);
|
|
|
+ $this->_current = $this->_current->getParent()->getParent();
|
|
|
|
|
|
- // now, the next char should be a dot
|
|
|
- if ($this->_value[$this->_pointer] == '.') {
|
|
|
- $info['tag'] .= '.';
|
|
|
+ // go up in the tree until we found the end
|
|
|
+ while ($this->_isStopper($token, $this->_current)) {
|
|
|
+ $this->_current->setStopper($token['tag']);
|
|
|
|
|
|
- // add the tag
|
|
|
- $this->_processBuffer();
|
|
|
+ $this->_current = $this->_current->getParent();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // go up in the tree until we found the end of stoppers
|
|
|
+ while ($this->_isStopper($token, $this->_current)) {
|
|
|
+ $this->_current->setStopper($token['tag']);
|
|
|
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- $info['tag'],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- $name,
|
|
|
- $info['attributes'],
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $this->_current->addChild($token);
|
|
|
- $this->_current = $token;
|
|
|
+ if (!empty($token['attributes'])) {
|
|
|
+ foreach ($token['attributes'] as $name => $value) {
|
|
|
+ $this->_current->addAttribute($name, $value);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if ($this->_value[++$this->_pointer] != ' ') {
|
|
|
- $this->_buffer .= $this->_value[$this->_pointer];
|
|
|
+ $this->_current = $this->_current->getParent();
|
|
|
+ }
|
|
|
}
|
|
|
+ $inside = true;
|
|
|
+ } elseif (($token['type'] == Zend_Markup_Token::TYPE_TAG) && $inside) {
|
|
|
+ if ($token['name'] == 'break') {
|
|
|
+ // add the newline and continue parsing
|
|
|
+ $this->_current->addChild(new Zend_Markup_Token(
|
|
|
+ $token['tag'],
|
|
|
+ Zend_Markup_Token::TYPE_NONE,
|
|
|
+ '',
|
|
|
+ array(),
|
|
|
+ $this->_current
|
|
|
+ ));
|
|
|
+ } else {
|
|
|
+ // handle a list item
|
|
|
+ if ($token['name'] == 'list') {
|
|
|
+ $attributes = array();
|
|
|
+ if (isset($token['attributes']['list'])) {
|
|
|
+ $attributes['list'] = $token['attributes']['list'];
|
|
|
+ unset($token['attributes']['list']);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($this->_current->getName() != 'list') {
|
|
|
+ // the list isn't started yet, create it
|
|
|
+ $child = new Zend_Markup_Token(
|
|
|
+ '',
|
|
|
+ Zend_Markup_Token::TYPE_TAG,
|
|
|
+ 'list',
|
|
|
+ $attributes,
|
|
|
+ $this->_current
|
|
|
+ );
|
|
|
+
|
|
|
+ $this->_current->addChild($child);
|
|
|
+
|
|
|
+ $this->_current = $child;
|
|
|
+ }
|
|
|
+ $token['name'] = 'li';
|
|
|
+ } elseif (($token['name'] == 'img') || ($token['name'] == 'url')) {
|
|
|
+ $inside = false;
|
|
|
+ }
|
|
|
|
|
|
- // find the end
|
|
|
- $len = strcspn($this->_value, "\n", ++$this->_pointer);
|
|
|
-
|
|
|
- $this->_buffer.= substr($this->_value, $this->_pointer, $len);
|
|
|
-
|
|
|
- $this->_pointer += $len;
|
|
|
+ // add the token
|
|
|
+ $child = new Zend_Markup_Token(
|
|
|
+ $token['tag'],
|
|
|
+ Zend_Markup_Token::TYPE_TAG,
|
|
|
+ $token['name'],
|
|
|
+ $token['attributes'],
|
|
|
+ $this->_current
|
|
|
+ );
|
|
|
|
|
|
- // end the tag and return
|
|
|
- $this->_processBuffer();
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
+ $this->_current->addChild($child);
|
|
|
|
|
|
- return;
|
|
|
+ $this->_current = $child;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // simply add the token as text
|
|
|
+ $this->_current->addChild(new Zend_Markup_Token(
|
|
|
+ $token['tag'],
|
|
|
+ Zend_Markup_Token::TYPE_NONE,
|
|
|
+ '',
|
|
|
+ array(),
|
|
|
+ $this->_current
|
|
|
+ ));
|
|
|
}
|
|
|
- $this->_buffer .= "\n" . $name;
|
|
|
- return;
|
|
|
}
|
|
|
-
|
|
|
- // not a valid heading
|
|
|
- $this->_buffer .= "\nh";
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Parse a list
|
|
|
+ * Check if a tag is a stopper
|
|
|
*
|
|
|
- * @todo allow a deeper list level
|
|
|
- * @todo add support for markup inside the list items
|
|
|
- * @return void
|
|
|
+ * @param array $token
|
|
|
+ * @param Zend_Markup_Token $current
|
|
|
+ *
|
|
|
+ * @return bool
|
|
|
*/
|
|
|
- protected function _parseList()
|
|
|
+ protected function _isStopper(array $token, Zend_Markup_Token $current)
|
|
|
{
|
|
|
- // for this operation, we need the entire line
|
|
|
- $len = strcspn($this->_value, "\n", $this->_pointer);
|
|
|
- $line = substr($this->_value, $this->_pointer, $len);
|
|
|
-
|
|
|
- // add the list tag
|
|
|
- $this->_processBuffer();
|
|
|
-
|
|
|
- // maybe we have to rewind
|
|
|
- $oldPointer = $this->_pointer;
|
|
|
-
|
|
|
- // attributes array
|
|
|
- $attrs = array();
|
|
|
- if ($line[0] == '#') {
|
|
|
- $attrs['list'] = 'decimal';
|
|
|
- }
|
|
|
-
|
|
|
- if ((strlen($line) <= 1) || ($line[1] != ' ')) {
|
|
|
- // rewind and return
|
|
|
- unset($list);
|
|
|
- $this->_pointer = $oldPointer;
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // add the token
|
|
|
- $list = new Zend_Markup_Token('', Zend_Markup_Token::TYPE_TAG, 'list', $attrs, $this->_current);
|
|
|
-
|
|
|
- // loop through every next line, until there are no list items any more
|
|
|
- while ($this->_valueLen > $this->_pointer) {
|
|
|
- // add the li-tag with contents
|
|
|
- $item = new Zend_Markup_Token(
|
|
|
- $line[0],
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- 'li',
|
|
|
- array(),
|
|
|
- $list
|
|
|
- );
|
|
|
-
|
|
|
- // parse and add the content
|
|
|
- $this->_parseInline(substr($line, 2), $item);
|
|
|
-
|
|
|
- $list->addChild($item);
|
|
|
-
|
|
|
- $this->_pointer += $len;
|
|
|
-
|
|
|
- // check if the next line is a list item too
|
|
|
- if (($this->_pointer + 1 >= $this->_valueLen) || $this->_value[++$this->_pointer] != $line[0]) {
|
|
|
- // there is no new list item coming
|
|
|
+ switch ($current->getName()) {
|
|
|
+ case 'h1':
|
|
|
+ case 'h2':
|
|
|
+ case 'h3':
|
|
|
+ case 'h4':
|
|
|
+ case 'h5':
|
|
|
+ case 'h6':
|
|
|
+ case 'list':
|
|
|
+ case 'li':
|
|
|
+ if (($token['type'] == Zend_Markup_Token::TYPE_TAG)
|
|
|
+ && (($token['name'] == 'break') || ($token['name'] == 'p'))
|
|
|
+ ) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'break':
|
|
|
+ return false;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (($token['type'] == Zend_Markup_Token::TYPE_TAG) && ($token['name'] == $current->getName())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
break;
|
|
|
- }
|
|
|
-
|
|
|
- // get the next line
|
|
|
- $len = strcspn($this->_value, "\n", $this->_pointer);
|
|
|
- $line = substr($this->_value, $this->_pointer, $len);
|
|
|
}
|
|
|
-
|
|
|
- // end the list tag
|
|
|
- $this->_current->addChild($list);
|
|
|
- $this->_current = $this->_current->getParent();
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Parse an acronym
|
|
|
+ * Extract the attributes
|
|
|
*
|
|
|
- * @return void
|
|
|
+ * @param array $matches
|
|
|
+ *
|
|
|
+ * @return array
|
|
|
*/
|
|
|
- protected function _parseAcronym()
|
|
|
+ protected function _extractAttributes(array $matches)
|
|
|
{
|
|
|
- $this->_pointer++;
|
|
|
-
|
|
|
- // first find the acronym itself
|
|
|
- $acronym = '';
|
|
|
- $pointer = 0;
|
|
|
-
|
|
|
- if (empty($this->_buffer)) {
|
|
|
- $this->_buffer .= '(';
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $bufferLen = strlen($this->_buffer);
|
|
|
+ $attributes = array();
|
|
|
|
|
|
- while (($bufferLen > $pointer) && ctype_upper($this->_buffer[$bufferLen - ++$pointer])) {
|
|
|
- $acronym = $this->_buffer[strlen($this->_buffer) - $pointer] . $acronym;
|
|
|
+ if (!empty($matches['attr_class'])) {
|
|
|
+ $attributes['class'] = $matches['attr_class'];
|
|
|
}
|
|
|
-
|
|
|
- if (strlen($acronym) < 3) {
|
|
|
- // just add the '(' to the buffer, this isn't an acronym
|
|
|
- $this->_buffer .= '(';
|
|
|
- return;
|
|
|
+ if (!empty($matches['attr_id'])) {
|
|
|
+ $attributes['id'] = $matches['attr_id'];
|
|
|
}
|
|
|
-
|
|
|
- // now, find the closing ')'
|
|
|
- $title = '';
|
|
|
- while ($this->_pointer < $this->_valueLen) {
|
|
|
- if ($this->_value[$this->_pointer] == ')') {
|
|
|
- break;
|
|
|
- } else {
|
|
|
- $title .= $this->_value[$this->_pointer];
|
|
|
- }
|
|
|
- $this->_pointer++;
|
|
|
+ if (!empty($matches['attr_style'])) {
|
|
|
+ $attributes['style'] = $matches['attr_style'];
|
|
|
}
|
|
|
-
|
|
|
- if ($this->_pointer >= $this->_valueLen) {
|
|
|
- $this->_buffer .= '(';
|
|
|
- return;
|
|
|
+ if (!empty($matches['attr_lang'])) {
|
|
|
+ $attributes['lang'] = $matches['attr_lang'];
|
|
|
}
|
|
|
-
|
|
|
- $this->_pointer++;
|
|
|
-
|
|
|
- if (empty($title)) {
|
|
|
- $this->_buffer .= '()';
|
|
|
- return;
|
|
|
+ if (!empty($matches['attr_align'])) {
|
|
|
+ switch ($matches['attr_align']) {
|
|
|
+ case '=':
|
|
|
+ $attributes['align'] = 'center';
|
|
|
+ break;
|
|
|
+ case '>':
|
|
|
+ $attributes['align'] = 'right';
|
|
|
+ break;
|
|
|
+ case '<>':
|
|
|
+ $attributes['align'] = 'justify';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ case '<':
|
|
|
+ $attributes['align'] = 'left';
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- $this->_buffer = substr($this->_buffer, 0, -$this->_pointer);
|
|
|
- $this->_processBuffer();
|
|
|
-
|
|
|
- // now add the tag
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- '',
|
|
|
- Zend_Markup_Token::TYPE_TAG,
|
|
|
- 'acronym',
|
|
|
- array('title' => $title),
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $token->setStopper('(' . $title . ')');
|
|
|
- $token->addChild(new Zend_Markup_Token(
|
|
|
- $acronym,
|
|
|
- Zend_Markup_Token::TYPE_NONE,
|
|
|
- '',
|
|
|
- array(),
|
|
|
- $token
|
|
|
- ));
|
|
|
- $this->_current->addChild($token);
|
|
|
+ return $attributes;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Check if the tag is a simple stopper
|
|
|
- *
|
|
|
- * @param string $tag
|
|
|
- * @return bool
|
|
|
- */
|
|
|
- protected function _isSimpleStopper($tag)
|
|
|
- {
|
|
|
- if ($tag == substr($this->_current->getTag(), 0, strlen($tag))) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Process the current buffer
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _processBuffer()
|
|
|
- {
|
|
|
- if (!empty($this->_buffer)) {
|
|
|
- // no tag start found, add the buffer to the current tag and stop parsing
|
|
|
- $token = new Zend_Markup_Token(
|
|
|
- $this->_buffer,
|
|
|
- Zend_Markup_Token::TYPE_NONE,
|
|
|
- '',
|
|
|
- array(),
|
|
|
- $this->_current
|
|
|
- );
|
|
|
- $this->_current->addChild($token);
|
|
|
- $this->_buffer = '';
|
|
|
- }
|
|
|
- }
|
|
|
}
|