setText($text);
}
/**
* Set the raw text - might include BBCode tags
*
* @param string $text The text
* @retun void
*/
public function setText($text)
{
$this->text = $text;
}
/**
* Renders only the text without any BBCode tags.
*
* @param string $text Optional: Render the passed BBCode string instead of the internally stored one
* @return string
*/
public function renderPlain($text = null)
{
if ($this->text !== null and $text === null) {
$text = $this->text;
}
return preg_replace("/\[(.*?)\]/is", '', $text);
}
/**
* Renders only the text without any BBCode tags.
* Alias for renderRaw().
*
* @deprecated Deprecated since 1.1.0
*
* @param string $text Optional: Render the passed BBCode string instead of the internally stored one
* @return string
*/
public function renderRaw($text = null)
{
return $this->renderPlain($text);
}
/**
* Renders BBCode to HTML
*
* @param string $text Optional: Render the passed BBCode string instead of the internally stored one
* @param bool $escape Escape HTML entities? (Only "<" and ">"!)
* @param bool $keepLines Keep line breaks by replacing them with
?
* @return string
*/
public function render($text = null, $escape = true, $keepLines = true)
{
if ($this->text !== null and $text === null) {
$text = $this->text;
}
$tokenizer = new Tokenizer($this->tagTypes);
$tokens = $tokenizer->tokenize($text, $escape, $keepLines);
$html = '';
$level = 0;
foreach ($tokens as $index => $token) {
switch ($token->getType()) {
case Token::TYPE_LINEBREAK:
$html .= '
';
break;
case Token::TYPE_PLAIN_TEXT:
$html .= $token->getValue();
break;
case Token::TYPE_TAG_OPENING:
case Token::TYPE_TAG_CLOSING:
$tagOpening = $token->getType() === Token::TYPE_TAG_OPENING;
$tagName = $token->getValue();
// If the tag is not known, just do not render it. We do not want to throw any exceptions.
if (isset($this->tagTypes[$tagName])) {
$tagType = $this->tagTypes[$tagName];
/** @var AbstractTagType $tag */
$tag = new $tagType;
$tag->render($html, $tagOpening);
}
if ($tagOpening) {
$level++;
} else {
$level--;
}
}
}
return $html;
$html = '';
$len = mb_strlen($text);
$inTag = false; // True if current position is inside a tag
$inName = false; // True if current pos is inside a tag name
$inStr = false; // True if current pos is inside a string
/** @var Tag|null $tag */
$tag = null;
$openTags = array();
/*
* Loop over each character of the text
*/
for ($i = 0; $i < $len; $i++) {
$char = mb_substr($text, $i, 1);
if ($keepLines) {
if ($char == "\n") {
$html .= '
';
}
if ($char == "\r") {
continue;
}
}
if (! $escape or ($char != '<' and $char != '>')) {
/*
* $inTag == true means the current position is inside a tag definition
* (= inside the brackets of a tag)
*/
if ($inTag) {
if ($char == '"') {
if ($inStr) {
$inStr = false;
} else {
if ($inName) {
$tag->valid = false;
} else {
$inStr = true;
}
}
} else {
/*
* This closes a tag
*/
if ($char == ']' and ! $inStr) {
$inTag = false;
$inName = false;
if ($tag->valid) {
$code = null;
if ($tag->opening) {
$code = $this->generateTag($tag, $html, null, $openTags);
} else {
$openingTag = $this->popTag($openTags, $tag);
if ($openingTag) {
$code = $this->generateTag($tag, $html, $openingTag, $openTags);
}
}
if ($code !== null and $tag->opening) {
$openTags[$tag->name][] = $tag;
}
$html .= $code;
}
continue;
}
if ($inName and ! $inStr) {
/*
* This makes the current tag a closing tag
*/
if ($char == '/') {
if ($tag->name) {
$tag->valid = false;
} else {
$tag->opening = false;
}
} else {
/*
* This means a property starts
*/
if ($char == '=') {
if ($tag->name) {
$inName = false;
} else {
$tag->valid = false;
}
} else {
$tag->name .= mb_strtolower($char);
}
}
} else { // If we are not inside the name we are inside a property
$tag->property .= $char;
}
}
} else {
/*
* This opens a tag
*/
if ($char == '[') {
$inTag = true;
$inName = true;
$tag = new Tag();
$tag->position = mb_strlen($html);
} else {
$html .= $char;
}
}
} else {
$html .= htmlspecialchars($char);
}
}
/*
* Check for tags that are not closed and close them.
*/
foreach ($openTags as $name => $openTagsByType) {
$closingTag = new Tag($name, false);
foreach ($openTagsByType as $openTag) {
$html .= $this->generateTag($closingTag, $html, $openTag);
}
}
return $html;
}
/**
* Generates and returns the HTML code of the current tag
*
* @param Tag $tag The current tag
* @param string $html The current HTML code passed by reference - might be altered!
* @param Tag|null $openingTag The opening tag that is linked to the tag (or null)
* @param Tag[] $openTags Array with tags that are opned but not closed
* @return string
*/
protected function generateTag(Tag $tag, &$html, Tag $openingTag = null, array $openTags = [])
{
$code = null;
if (in_array($tag->name, $this->ignoredTags)) {
return $code;
}
// Custom tags:
foreach ($this->customTagClosures as $name => $closure) {
if ($tag->name === $name) {
exit($tag->name);
$code .= $closure($tag, $html, $openingTag);
}
}
return $code;
}
/**
* Magic method __toString()
*
* @return string
*/
public function __toString()
{
return $this->render();
}
/**
* Returns the last tag of a given type and removes it from the array.
*
* @param Tag[] $tags Array of tags
* @param Tag $tag Return the last tag of the type of this tag
* @return Tag|null
*/
protected function popTag(array &$tags, $tag)
{
if (! isset($tags[$tag->name])) {
return null;
}
$size = sizeof($tags[$tag->name]);
if ($size === 0) {
return null;
} else {
return array_pop($tags[$tag->name]);
}
}
/**
* Returns true if $haystack ends with $needle
*
* @param string $haystack
* @param string $needle
* @return bool
*/
protected function endsWith($haystack, $needle)
{
return ($needle === '' or mb_substr($haystack, -mb_strlen($needle)) === $needle);
}
}