Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /*!
- @name: BBCode Library
- @description: There is bbcode library based on finite automaton.
- O(|s|) {s - source string}
- @version: 0.9.2 Beta
- @author: Ivan Udovin
- */
- class BBCode extends Tag {
- public $tags = array(
- 'p' => array(
- 'class' => 'Tag_P',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 's', 'sub', 'sup', 'url', 'img', 'align')
- ),
- 'b' => array(
- 'class' => 'Tag_B',
- 'closed' => false,
- 'children' => array('i', 'u', 's', 'sub', 'sup', 'url')
- ),
- 'i' => array(
- 'class' => 'Tag_I',
- 'closed' => false,
- 'children' => array('b', 'u', 's', 'sub', 'sup', 'url')
- ),
- 'u' => array(
- 'class' => 'Tag_U',
- 'closed' => false,
- 'children' => array('b', 'i', 's', 'sub', 'sup', 'url')
- ),
- 's' => array(
- 'class' => 'Tag_S',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 'sub', 'sup', 'url')
- ),
- 'sub' => array(
- 'class' => 'Tag_SUB',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 's', 'url')
- ),
- 'sup' => array(
- 'class' => 'Tag_SUP',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 's', 'url')
- ),
- 'url' => array(
- 'class' => 'Tag_URL',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 's', 'img')
- ),
- 'img' => array(
- 'class' => 'Tag_IMG',
- 'closed' => true,
- 'children' => array()
- ),
- 'align' => array(
- 'class' => 'Tag_ALIGN',
- 'closed' => false,
- 'children' => array('b', 'i', 'u', 's', 'sub', 'sup', 'url')
- )
- );
- public $root_tags = array('p', 'b', 'i', 'u', 's', 'sub', 'sup', 'url', 'img', 'align');
- private $source = '';
- private $cursor = 0;
- private $syntax = array();
- public function __construct($source) {
- parent::__construct(null, null);
- $this->source = $source;
- $this->parse();
- $this->normalize();
- $this->createDOM();
- }
- private function getToken() {
- $token = '';
- $token_type = false;
- $char_type = false;
- while (true) {
- $token_type = $char_type;
- if (!isset($this->source[$this->cursor])) {
- if ($char_type === false) {
- return false;
- } else {
- break;
- }
- }
- $char = $this->source[$this->cursor];
- switch ($char) {
- case '[':
- $char_type = 1;
- break;
- case ']':
- $char_type = 2;
- break;
- case '"':
- $char_type = 3;
- break;
- case "'":
- $char_type = 4;
- break;
- case '=':
- $char_type = 5;
- break;
- case '/':
- $char_type = 6;
- break;
- case ' ':
- $char_type = 7;
- break;
- case "\n":
- $char_type = 8;
- break;
- case "\r":
- $char_type = 8;
- break;
- case "\0":
- $char_type = 8;
- break;
- case "\x0B":
- $char_type = 8;
- break;
- default:
- $char_type = 0;
- break;
- }
- if ($token_type === false) {
- $token = $char;
- } else if ($token_type > 0) {
- break;
- } else if ($token_type == $char_type) {
- $token .= $char;
- } else {
- break;
- }
- $this->cursor++;
- }
- if ($token_type == 0 && isset($this->tags[$token])) {
- $token_type = 9;
- }
- return array($token_type, $token);
- }
- private function parse() {
- $this->cursor = 0;
- $this->syntax = array();
- $finite_automaton = array(
- 0 => array( 0, 1, 0, 0, 0, 0, 0, 0, 0, 0),
- 1 => array( 7, 20, 7, 7, 7, 7, 3, 7, 7, 2),
- 2 => array( 7, 20, 5, 7, 7, 19, 6, 8, 7, 7),
- 3 => array( 7, 20, 7, 7, 7, 7, 7, 7, 7, 4),
- 4 => array( 7, 20, 5, 7, 7, 7, 7, 7, 7, 7),
- 5 => array( 0, 1, 0, 0, 0, 0, 0, 0, 0, 0),
- 6 => array( 7, 20, 5, 7, 7, 7, 7, 7, 7, 7),
- 7 => array( 0, 1, 0, 0, 0, 0, 0, 0, 0, 0),
- 8 => array( 9, 20, 7, 7, 7, 19, 6, 7, 7, 9),
- 9 => array( 7, 20, 7, 7, 7, 10, 6, 17, 7, 7),
- 10 => array(11, 20, 7, 12, 15, 7, 7, 18, 7, 11),
- 11 => array( 7, 20, 5, 7, 7, 7, 6, 8, 7, 7),
- 12 => array(13, 20, 7, 14, 13, 13, 13, 13, 13, 13),
- 13 => array(13, 20, 7, 14, 13, 13, 13, 13, 13, 13),
- 14 => array( 7, 20, 5, 7, 7, 7, 6, 8, 7, 7),
- 15 => array(16, 20, 7, 16, 14, 16, 16, 16, 16, 16),
- 16 => array(16, 20, 7, 16, 14, 16, 16, 16, 16, 16),
- 17 => array( 7, 20, 7, 7, 7, 10, 6, 7, 7, 7),
- 18 => array(11, 20, 7, 12, 15, 7, 7, 7, 7, 11),
- 19 => array(11, 20, 7, 12, 15, 7, 7, 18, 7, 11),
- 20 => array( 7, 20, 7, 7, 7, 7, 3, 7, 7, 2)
- );
- $mode = 0;
- $pointer = -1;
- $last_tag = null;
- $last_attrib = null;
- while ($token = $this->getToken()) {
- $mode = $finite_automaton[$mode][$token[0]];
- switch ($mode) {
- case 0:
- if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
- $this->syntax[$pointer]['text'] .= $token[1];
- } else {
- $this->syntax[++$pointer] = array('type' => 'text', 'text' => $token[1]);
- }
- break;
- case 1:
- $last_tag = array('type' => 'open', 'text' => $token[1], 'attrib' => array());
- break;
- case 2:
- $last_tag['tag'] = $token[1];
- $last_tag['text'] .= $token[1];
- $last_attrib = $token[1];
- break;
- case 3:
- $last_tag['type'] = 'close';
- $last_tag['text'] .= $token[1];
- break;
- case 4:
- $last_tag['tag'] = $token[1];
- $last_tag['text'] .= $token[1];
- break;
- case 5:
- $last_tag['text'] .= $token[1];
- $pointer++;
- $this->syntax[$pointer] = $last_tag;
- $last_tag = null;
- break;
- case 6:
- $last_tag['type'] = 'open/close';
- $last_tag['text'] .= $token[1];
- break;
- case 7:
- $last_tag['text'] .= $token[1];
- if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
- $this->syntax[$pointer]['text'] .= $last_tag['text'];
- } else {
- $this->syntax[++$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
- }
- $last_tag = null;
- break;
- case 8:
- $last_tag['text'] .= $token[1];
- break;
- case 9:
- $last_tag['text'] .= $token[1];
- $last_tag['attrib'][$token[1]] = '';
- $last_attrib = $token[1];
- break;
- case 10:
- $last_tag['text'] .= $token[1];
- break;
- case 11:
- $last_tag['text'] .= $token[1];
- $last_tag['attrib'][$last_attrib] = $token[1];
- break;
- case 12:
- $last_tag['text'] .= $token[1];
- break;
- case 13:
- $last_tag['text'] .= $token[1];
- $last_tag['attrib'][$last_attrib] .= $token[1];
- break;
- case 14:
- $last_tag['text'] .= $token[1];
- break;
- case 15:
- $last_tag['text'] .= $token[1];
- break;
- case 16:
- $last_tag['text'] .= $token[1];
- $last_tag['attrib'][$last_attrib] .= $token[1];
- break;
- case 17:
- $last_tag['text'] .= $token[1];
- break;
- case 18:
- $last_tag['text'] .= $token[1];
- break;
- case 19:
- $last_tag['text'] .= $token[1];
- $last_attrib = $last_tag['tag'];
- break;
- case 20:
- if ($this->syntax[$pointer]['type'] == 'text') {
- $this->syntax[$pointer]['text'] .= $last_tag['text'];
- } else {
- $this->syntax[++$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
- }
- $last_tag = array('type' => 'open', 'text' => $token[1], 'attrib' => array());
- break;
- }
- }
- if (isset($last_tag)) {
- if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
- $this->syntax[$pointer]['text'] .= $last_tag['text'];
- } else {
- $pointer++;
- $this->syntax[$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
- }
- }
- }
- private function normalize() {
- $stack = array();
- foreach ($this->syntax as $key => $node) {
- switch ($node['type']) {
- case 'open':
- if (count($stack) == 0) {
- $allowed = $this->root_tags;
- } else {
- $allowed = $this->tags[$stack[count($stack)-1]]['children'];
- }
- if (array_search($node['tag'], $allowed) !== false) {
- if ($this->tags[$node['tag']]['closed']) {
- $this->syntax[$key]['type'] = 'open/close';
- } else {
- array_push($stack, $node['tag']);
- }
- } else {
- $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
- }
- break;
- case 'close':
- if (count($stack) > 0 && $node['tag'] == $stack[count($stack)-1]){
- array_pop($stack);
- } else {
- $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
- }
- break;
- case 'open/close':
- if (count($stack) <= 0) {
- $allowed = $this->root_tags;
- } else {
- $allowed = $this->tags[$stack[count($stack)-1]]['children'];
- }
- if (array_search($node['tag'], $allowed) === false) {
- $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
- }
- break;
- }
- }
- }
- private function createDOM() {
- $current = $this;
- foreach ($this->syntax as $node) {
- switch ($node['type']) {
- case 'text':
- $child = new Text($current, $node['text']);
- array_push($current->children, $child);
- break;
- case 'open':
- $tag_class = $this->tags[$node['tag']]['class'];
- $class = (class_exists($tag_class, false)) ? $tag_class : 'Tag';
- $child = new $class($current, $node['attrib']);
- array_push($current->children, $child);
- $current = $child;
- break;
- case 'close':
- $current = $current->parent;
- break;
- case 'open/close':
- $tag_class = $this->tags[$node['tag']]['class'];
- $class = (class_exists($tag_class, false)) ? $tag_class : 'Tag';
- $child = new $class($current, $node['attrib']);
- array_push($current->children, $child);
- break;
- }
- }
- }
- }
- abstract class Node {
- abstract public function getHTML();
- protected function specialchars($string) {
- $chars = array(
- '[' => '@l;',
- ']' => '@r;',
- '"' => '@q;',
- "'" => '@a;',
- '@' => '@at;'
- );
- return strtr($string, $chars);
- }
- protected function unspecialchars($string) {
- $chars = array(
- '@l;' => '[',
- '@r;' => ']',
- '@q;' => '"',
- '@a;' => "'",
- '@at;' => '@'
- );
- return strtr($string, $chars);
- }
- }
- class Tag extends Node {
- public $parent = null;
- public $children = array();
- public $attrib = array();
- public function __construct($parent, $attrib) {
- $this->parent = $parent;
- $this->attrib = (array) $attrib;
- }
- public function getHTML() {
- $html = '';
- foreach ($this->children as $child) {
- $html .= $child->getHTML();
- }
- return $html;
- }
- public function getAttrib($name) {
- return $this->unspecialchars($this->attrib[$name]);
- }
- public function hasAttrib($name) {
- return isset($this->attrib[$name]);
- }
- }
- class Text extends Node {
- public $parent = null;
- public $text = '';
- public function __construct($parent, $text) {
- $this->parent = $parent;
- $this->text = (string) $text;
- }
- public function getHTML() {
- return htmlspecialchars($this->unspecialchars($this->text));
- }
- }
- /* ==================== */
- class Tag_P extends Tag {
- public function getHTML() {
- return '<p class="bbcode">'.parent::getHTML().'</p>';
- }
- }
- class Tag_B extends Tag {
- public function getHTML() {
- return '<b class="bbcode">'.parent::getHTML().'</b>';
- }
- }
- class Tag_I extends Tag {
- public function getHTML() {
- return '<i class="bbcode">'.parent::getHTML().'</i>';
- }
- }
- class Tag_U extends Tag {
- public function getHTML() {
- return '<u class="bbcode">'.parent::getHTML().'</u>';
- }
- }
- class Tag_S extends Tag {
- public function getHTML() {
- return '<s class="bbcode">'.parent::getHTML().'</s>';
- }
- }
- class Tag_SUB extends Tag {
- public function getHTML() {
- return '<sub class="bbcode">'.parent::getHTML().'</sub>';
- }
- }
- class Tag_SUP extends Tag {
- public function getHTML() {
- return '<sup class="bbcode">'.parent::getHTML().'</sup>';
- }
- }
- class Tag_URL extends Tag {
- public function getHTML() {
- return '<a href="'.htmlspecialchars($this->getAttrib('url')).'" class="bbcode">'.parent::getHTML().'</a>';
- }
- }
- class Tag_IMG extends Tag {
- public function getHTML() {
- $alt = ($this->hasAttrib('alt')) ? $this->getAttrib('alt') : '';
- switch ($this->getAttrib('float')) {
- case 'left': $float = 'left'; break;
- case 'right': $float = 'right'; break;
- default: $float = 'none'; break;
- }
- return '<img src="'.htmlspecialchars($this->getAttrib('img')).'" alt="'.htmlspecialchars($alt).'" style="float:'.$float.';" class="bbcode" />';
- }
- }
- class Tag_ALIGN extends Tag {
- public function getHTML() {
- switch($this->getAttrib('align')) {
- case 'left': $align = 'left'; break;
- case 'right': $align = 'right'; break;
- case 'center': $align = 'center'; break;
- case 'justify': $align = 'justify'; break;
- default: $align = 'inherit'; break;
- }
- return '<div style="text-align:'.$align.'" class="bbcode">'.parent::getHTML().'</div>';
- }
- }
- ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement