public function visitElementNode(\JBBCode\ElementNode $elementNode) { $attrs = $elementNode->getAttribute(); if (is_array($attrs)) { foreach ($attrs as &$el) { $el = $this->htmlSafe($el); } $elementNode->setAttribute($attrs); } foreach ($elementNode->getChildren() as $child) { $child->accept($this); } }
/** * This is the next step in parsing a tag. It's possible for it to still be invalid at this * point but many of the basic invalid tag name conditions have already been handled. * * @param $parent the current parent element * @param $tokenizer the tokenizer we're using * @param $tagContent the text between the [ and the ], assuming there is actually a ] * * @return the new parent element */ protected function parseTag(ElementNode $parent, Tokenizer $tokenizer, $tagContent) { $next; if (!$tokenizer->hasNext() || ($next = $tokenizer->next()) != ']') { /* This is a malformed tag. Both the previous [ and the tagContent * is really just plain text. */ $this->createTextNode($parent, '['); $this->createTextNode($parent, $tagContent); return $parent; } /* This is a well-formed tag consisting of [something] or [/something], but * we still need to ensure that 'something' is a valid tag name. Additionally, * if it's a closing tag, we need to ensure that there was a previous matching * opening tag. */ /* There could be an attribute. */ $tagPieces = explode('=', $tagContent); $tmpTagName = $tagPieces[0]; $actualTagName; if ('/' == $tmpTagName[0]) { /* This is a closing tag name. */ $actualTagName = substr($tmpTagName, 1); } else { $actualTagName = $tmpTagName; } if ('/' == $tmpTagName[0]) { /* This is attempting to close an open tag. We must verify that there exists an * open tag of the same type and that there is no option (options on closing * tags don't make any sense). */ $elToClose = $parent->closestParentOfType($actualTagName); if (null == $elToClose || count($tagPieces) > 1) { /* Closing an unopened tag or has an option. Treat everything as plain text. */ $this->createTextNode($parent, '['); $this->createTextNode($parent, $tagContent); $this->createTextNode($parent, ']'); return $parent; } else { /* We're closing $elToClose. In order to do that, we just need to return * $elToClose's parent, since that will change our effective parent to be * elToClose's parent. */ return $elToClose->getParent(); } } /* Verify that this is a known bbcode tag name. */ if ('' == $actualTagName || !$this->codeExists($actualTagName, count($tagPieces) > 1)) { /* This is an invalid tag name! Treat everything we've seen as plain text. */ $this->createTextNode($parent, '['); $this->createTextNode($parent, $tagContent); $this->createTextNode($parent, ']'); return $parent; } /* If we're here, this is a valid opening tag. Let's make a new node for it. */ $el = new ElementNode(); $el->setNodeId(++$this->nextNodeid); $code = $this->getCode($actualTagName, count($tagPieces) > 1); $el->setCodeDefinition($code); if (count($tagPieces) > 1) { /* We have an attribute we should save. */ unset($tagPieces[0]); $el->setAttribute(implode('=', $tagPieces)); } $parent->addChild($el); return $el; }
/** * Constructs the parse tree from a string of bbcode markup. * * @param string $str the bbcode markup to parse */ public function parse($str) { $this->reset(); $parent = $this->treeRoot; $tokenManager = new TokenManager($str); $nodeid = 1; $inTag = false; while ($tokenManager->hasCurrent()) { // tokens are either "[", "]" or a string that contains neither a opening bracket nor a closing bracket if ($inTag) { // this token should be a tag name // explode by = in case there's an attribute $pieces = explode('=', $tokenManager->getCurrent(), 2); // check if it's a closing tag if (substr($pieces[0], 0, 1) == "/") { $tagName = substr($pieces[0], 1); $closing = true; } else { $tagName = $pieces[0]; $closing = false; } if (($this->codeExists($tagName, isset($pieces[1])) || $closing && $this->codeExists($tagName, true)) && $tokenManager->hasNext() && $tokenManager->next() == "]") { if ($closing) { $closestParent = $parent->closestParentOfType($tagName); if ($closestParent != null && $closestParent->hasParent()) { // closing an element... move to this element's parent $parent->getCodeDefinition()->decrementCounter(); $parent = $closestParent->getParent(); $tokenManager->advance(); $tokenManager->advance(); $inTag = false; continue; } } else { // new element $el = new ElementNode(); $code = $this->getCode($tagName, isset($pieces[1])); $code->incrementCounter(); $el->setNestDepth($code->getCounter()); $el->setCodeDefinition($code); $el->setTagName($tagName); $el->setNodeId($nodeid++); if (isset($pieces[1])) { $el->setAttribute($pieces[1]); } $parent->addChild($el); $parent = $el; $tokenManager->advance(); $tokenManager->advance(); $inTag = false; continue; } } // the opening bracket that sent us in here was really just plain text $node = new TextNode("["); $node->setNodeId($nodeid++); $parent->addChild($node); $inTag = false; // treat this token as regular text, and let the next if...else structure handle it as regular text } if ($tokenManager->getCurrent() == "[") { $inTag = true; } else { $node = new TextNode($tokenManager->getCurrent()); $node->setNodeId($nodeid++); $parent->addChild($node); } $tokenManager->advance(); } }