Advertisement
Guest User

Untitled

a guest
Aug 4th, 2015
232
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 22.61 KB | None | 0 0
  1. <?php
  2. /*!
  3. @name:                  BBCode Library
  4. @description:   There is bbcode library based on finite automaton.
  5.                                 O(|s|) {s - source string}
  6. @version:               0.9.2 Beta
  7. @author:                Ivan Udovin
  8. */
  9. class BBCode extends Tag {
  10.        
  11.         public $tags = array(
  12.                 'p' => array(
  13.                         'class' => 'Tag_P',
  14.                         'closed' => false,
  15.                         'children' => array('b', 'i', 'u', 's', 'sub', 'sup', 'url', 'img', 'align')
  16.                 ),
  17.                 'b' => array(
  18.                         'class' => 'Tag_B',
  19.                         'closed' => false,
  20.                         'children' => array('i', 'u', 's', 'sub', 'sup', 'url')        
  21.                 ),
  22.                 'i' => array(
  23.                         'class' => 'Tag_I',
  24.                         'closed' => false,
  25.                         'children' => array('b', 'u', 's', 'sub', 'sup', 'url')
  26.                 ),
  27.                 'u' => array(
  28.                         'class' => 'Tag_U',
  29.                         'closed' => false,
  30.                         'children' => array('b', 'i', 's', 'sub', 'sup', 'url')
  31.                 ),
  32.                 's' => array(
  33.                         'class' => 'Tag_S',
  34.                         'closed' => false,
  35.                         'children' => array('b', 'i', 'u', 'sub', 'sup', 'url')
  36.                 ),
  37.                 'sub' => array(
  38.                         'class' => 'Tag_SUB',
  39.                         'closed' => false,
  40.                         'children' => array('b', 'i', 'u', 's', 'url')
  41.                 ),
  42.                 'sup' => array(
  43.                         'class' => 'Tag_SUP',
  44.                         'closed' => false,
  45.                         'children' => array('b', 'i', 'u', 's', 'url')
  46.                 ),
  47.                 'url' => array(
  48.                         'class' => 'Tag_URL',
  49.                         'closed' => false,
  50.                         'children' => array('b', 'i', 'u', 's', 'img')
  51.                 ),
  52.                 'img' => array(
  53.                         'class' => 'Tag_IMG',
  54.                         'closed' => true,
  55.                         'children' => array()
  56.                 ),
  57.                 'align' => array(
  58.                         'class' => 'Tag_ALIGN',
  59.                         'closed' => false,
  60.                         'children' => array('b', 'i', 'u', 's', 'sub', 'sup', 'url')
  61.                 )
  62.         );
  63.         public $root_tags = array('p', 'b', 'i', 'u', 's', 'sub', 'sup', 'url', 'img', 'align');
  64.        
  65.         private $source = '';
  66.         private $cursor = 0;
  67.         private $syntax = array();
  68.        
  69.         public function __construct($source) {
  70.                 parent::__construct(null, null);
  71.                
  72.                 $this->source = $source;
  73.                
  74.                 $this->parse();
  75.                 $this->normalize();
  76.                 $this->createDOM();
  77.         }
  78.        
  79.         private function getToken() {
  80.                 $token = '';
  81.                 $token_type = false;
  82.                 $char_type = false;
  83.                 while (true) {
  84.                         $token_type = $char_type;
  85.                         if (!isset($this->source[$this->cursor])) {
  86.                                 if ($char_type === false) {
  87.                                         return false;
  88.                                 } else {
  89.                                         break;
  90.                                 }
  91.                         }
  92.                         $char = $this->source[$this->cursor];
  93.                         switch ($char) {
  94.                                 case '[':
  95.                                         $char_type = 1;
  96.                                 break;
  97.                                 case ']':
  98.                                         $char_type = 2;
  99.                                 break;
  100.                                 case '"':
  101.                                         $char_type = 3;
  102.                                 break;
  103.                                 case "'":
  104.                                         $char_type = 4;
  105.                                 break;
  106.                                 case '=':
  107.                                         $char_type = 5;
  108.                                 break;
  109.                                 case '/':
  110.                                         $char_type = 6;
  111.                                 break;
  112.                                 case ' ':
  113.                                         $char_type = 7;
  114.                                 break;
  115.                                 case "\n":
  116.                                         $char_type = 8;
  117.                                 break;
  118.                                 case "\r":
  119.                                         $char_type = 8;
  120.                                 break;
  121.                                 case "\0":
  122.                                         $char_type = 8;
  123.                                 break;
  124.                                 case "\x0B":
  125.                                         $char_type = 8;
  126.                                 break;
  127.                                 default:
  128.                                         $char_type = 0;
  129.                                 break;
  130.                         }
  131.                         if ($token_type === false) {
  132.                                 $token = $char;
  133.                         } else if ($token_type > 0) {
  134.                                 break;
  135.                         } else if ($token_type == $char_type) {
  136.                                 $token .= $char;
  137.                         } else {
  138.                                 break;
  139.                         }
  140.                         $this->cursor++;
  141.                 }
  142.                 if ($token_type == 0 && isset($this->tags[$token])) {
  143.                         $token_type = 9;
  144.                 }
  145.                 return array($token_type, $token);
  146.         }
  147.        
  148.         private function parse() {
  149.                 $this->cursor = 0;
  150.                 $this->syntax = array();
  151.                 $finite_automaton = array(
  152.                         0  => array( 0,  1,  0,  0,  0,  0,  0,  0,  0,  0),
  153.                         1  => array( 7, 20,  7,  7,  7,  7,  3,  7,  7,  2),
  154.                         2  => array( 7, 20,  5,  7,  7, 19,  6,  8,  7,  7),
  155.                         3  => array( 7, 20,  7,  7,  7,  7,  7,  7,  7,  4),
  156.                         4  => array( 7, 20,  5,  7,  7,  7,  7,  7,  7,  7),
  157.                         5  => array( 0,  1,  0,  0,  0,  0,  0,  0,  0,  0),
  158.                         6  => array( 7, 20,  5,  7,  7,  7,  7,  7,  7,  7),
  159.                         7  => array( 0,  1,  0,  0,  0,  0,  0,  0,  0,  0),
  160.                         8  => array( 9, 20,  7,  7,  7, 19,  6,  7,  7,  9),
  161.                         9  => array( 7, 20,  7,  7,  7, 10,  6, 17,  7,  7),
  162.                         10 => array(11, 20,  7, 12, 15,  7,  7, 18,  7, 11),
  163.                         11 => array( 7, 20,  5,  7,  7,  7,  6,  8,  7,  7),
  164.                         12 => array(13, 20,  7, 14, 13, 13, 13, 13, 13, 13),
  165.                         13 => array(13, 20,  7, 14, 13, 13, 13, 13, 13, 13),
  166.                         14 => array( 7, 20,  5,  7,  7,  7,  6,  8,  7,  7),
  167.                         15 => array(16, 20,  7, 16, 14, 16, 16, 16, 16, 16),
  168.                         16 => array(16, 20,  7, 16, 14, 16, 16, 16, 16, 16),
  169.                         17 => array( 7, 20,  7,  7,  7, 10,  6,  7,  7,  7),
  170.                         18 => array(11, 20,  7, 12, 15,  7,  7,  7,  7, 11),
  171.                         19 => array(11, 20,  7, 12, 15,  7,  7, 18,  7, 11),
  172.                         20 => array( 7, 20,  7,  7,  7,  7,  3,  7,  7,  2)
  173.                 );
  174.                 $mode = 0;
  175.                 $pointer = -1;
  176.                 $last_tag = null;
  177.                 $last_attrib = null;
  178.                 while ($token = $this->getToken()) {
  179.                         $mode = $finite_automaton[$mode][$token[0]];
  180.                         switch ($mode) {
  181.                                 case 0:
  182.                                         if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
  183.                                                 $this->syntax[$pointer]['text'] .= $token[1];
  184.                                         } else {
  185.                                                 $this->syntax[++$pointer] = array('type' => 'text', 'text' => $token[1]);
  186.                                         }
  187.                                 break;
  188.                                 case 1:
  189.                                         $last_tag = array('type' => 'open', 'text' => $token[1], 'attrib' => array());
  190.                                 break;
  191.                                 case 2:
  192.                                         $last_tag['tag'] = $token[1];
  193.                                         $last_tag['text'] .= $token[1];
  194.                                         $last_attrib = $token[1];
  195.                                 break;
  196.                                 case 3:
  197.                                         $last_tag['type'] = 'close';
  198.                                         $last_tag['text'] .= $token[1];
  199.                                 break;
  200.                                 case 4:
  201.                                         $last_tag['tag'] = $token[1];
  202.                                         $last_tag['text'] .= $token[1];
  203.                                 break;
  204.                                 case 5:
  205.                                         $last_tag['text'] .= $token[1];
  206.                                         $pointer++;
  207.                                         $this->syntax[$pointer] = $last_tag;
  208.                                         $last_tag = null;
  209.                                 break;
  210.                                 case 6:
  211.                                         $last_tag['type'] = 'open/close';
  212.                                         $last_tag['text'] .= $token[1];
  213.                                 break;
  214.                                 case 7:
  215.                                         $last_tag['text'] .= $token[1];
  216.                                         if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
  217.                                                 $this->syntax[$pointer]['text'] .= $last_tag['text'];
  218.                                         } else {
  219.                                                 $this->syntax[++$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
  220.                                         }
  221.                                         $last_tag = null;
  222.                                 break;
  223.                                 case 8:
  224.                                         $last_tag['text'] .= $token[1];
  225.                                 break;
  226.                                 case 9:
  227.                                         $last_tag['text'] .= $token[1];
  228.                                         $last_tag['attrib'][$token[1]] = '';
  229.                                         $last_attrib = $token[1];
  230.                                 break;
  231.                                 case 10:
  232.                                         $last_tag['text'] .= $token[1];
  233.                                 break;
  234.                                 case 11:
  235.                                         $last_tag['text'] .= $token[1];
  236.                                         $last_tag['attrib'][$last_attrib] = $token[1];
  237.                                 break;
  238.                                 case 12:
  239.                                         $last_tag['text'] .= $token[1];
  240.                                 break;
  241.                                 case 13:
  242.                                         $last_tag['text'] .= $token[1];
  243.                                         $last_tag['attrib'][$last_attrib] .= $token[1];
  244.                                 break;
  245.                                 case 14:
  246.                                         $last_tag['text'] .= $token[1];
  247.                                 break;
  248.                                 case 15:
  249.                                         $last_tag['text'] .= $token[1];
  250.                                 break;
  251.                                 case 16:
  252.                                         $last_tag['text'] .= $token[1];
  253.                                         $last_tag['attrib'][$last_attrib] .= $token[1];
  254.                                 break;
  255.                                 case 17:
  256.                                         $last_tag['text'] .= $token[1];
  257.                                 break;
  258.                                 case 18:
  259.                                         $last_tag['text'] .= $token[1];
  260.                                 break;
  261.                                 case 19:
  262.                                         $last_tag['text'] .= $token[1];
  263.                                         $last_attrib = $last_tag['tag'];
  264.                                 break;
  265.                                 case 20:
  266.                                         if ($this->syntax[$pointer]['type'] == 'text') {
  267.                                                 $this->syntax[$pointer]['text'] .= $last_tag['text'];
  268.                                         } else {
  269.                                                 $this->syntax[++$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
  270.                                         }
  271.                                         $last_tag = array('type' => 'open', 'text' => $token[1], 'attrib' => array());
  272.                                 break;
  273.                         }
  274.                 }
  275.                 if (isset($last_tag)) {
  276.                         if (isset($this->syntax[$pointer]) && $this->syntax[$pointer]['type'] == 'text') {
  277.                                 $this->syntax[$pointer]['text'] .= $last_tag['text'];
  278.                         } else {
  279.                                 $pointer++;
  280.                                 $this->syntax[$pointer] = array('type' => 'text', 'text' => $last_tag['text']);
  281.                         }
  282.                 }
  283.         }
  284.        
  285.         private function normalize() {
  286.                 $stack = array();
  287.                 foreach ($this->syntax as $key => $node) {
  288.                         switch ($node['type']) {
  289.                                 case 'open':
  290.                                         if (count($stack) == 0) {
  291.                                                 $allowed = $this->root_tags;
  292.                                         } else {
  293.                                                 $allowed = $this->tags[$stack[count($stack)-1]]['children'];
  294.                                         }
  295.                                         if (array_search($node['tag'], $allowed) !== false) {
  296.                                                 if ($this->tags[$node['tag']]['closed']) {
  297.                                                         $this->syntax[$key]['type'] = 'open/close';
  298.                                                 } else {
  299.                                                         array_push($stack, $node['tag']);
  300.                                                 }
  301.                                         } else {
  302.                                                 $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
  303.                                         }
  304.                                 break;
  305.                                 case 'close':
  306.                                         if (count($stack) > 0 && $node['tag'] == $stack[count($stack)-1]){
  307.                                                 array_pop($stack);
  308.                                         } else {
  309.                                                 $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
  310.                                         }
  311.                                 break;
  312.                                 case 'open/close':
  313.                                         if (count($stack) <= 0) {
  314.                                                 $allowed = $this->root_tags;
  315.                                         } else {
  316.                                                 $allowed = $this->tags[$stack[count($stack)-1]]['children'];
  317.                                         }
  318.                                         if (array_search($node['tag'], $allowed) === false) {
  319.                                                 $this->syntax[$key] = array('type' => 'text', 'text' => $node['text']);
  320.                                         }
  321.                                 break;
  322.                         }
  323.                 }
  324.         }
  325.        
  326.         private function createDOM() {
  327.                 $current = $this;
  328.                 foreach ($this->syntax as $node) {
  329.                         switch ($node['type']) {
  330.                                 case 'text':
  331.                                         $child = new Text($current, $node['text']);
  332.                                         array_push($current->children, $child);
  333.                                 break;
  334.                                 case 'open':
  335.                                         $tag_class = $this->tags[$node['tag']]['class'];
  336.                                         $class = (class_exists($tag_class, false)) ? $tag_class : 'Tag';
  337.                                         $child = new $class($current, $node['attrib']);
  338.                                         array_push($current->children, $child);
  339.                                         $current = $child;
  340.                                 break;
  341.                                 case 'close':
  342.                                         $current = $current->parent;
  343.                                 break;
  344.                                 case 'open/close':
  345.                                         $tag_class = $this->tags[$node['tag']]['class'];
  346.                                         $class = (class_exists($tag_class, false)) ? $tag_class : 'Tag';
  347.                                         $child = new $class($current, $node['attrib']);
  348.                                         array_push($current->children, $child);
  349.                                 break;
  350.                         }
  351.                 }
  352.         }
  353.        
  354. }
  355.  
  356. abstract class Node {
  357.        
  358.         abstract public function getHTML();
  359.        
  360.         protected function specialchars($string) {
  361.         $chars = array(
  362.             '[' => '@l;',
  363.             ']' => '@r;',
  364.             '"' => '@q;',
  365.             "'" => '@a;',
  366.             '@' => '@at;'
  367.         );
  368.         return strtr($string, $chars);
  369.     }
  370.  
  371.     protected function unspecialchars($string) {
  372.         $chars = array(
  373.             '@l;'  => '[',
  374.             '@r;'  => ']',
  375.             '@q;'  => '"',
  376.             '@a;'  => "'",
  377.             '@at;' => '@'
  378.         );
  379.         return strtr($string, $chars);
  380.     }
  381.        
  382. }
  383.  
  384. class Tag extends Node {
  385.        
  386.         public $parent = null;
  387.         public $children = array();
  388.         public $attrib = array();
  389.        
  390.         public function __construct($parent, $attrib) {
  391.                 $this->parent = $parent;
  392.                 $this->attrib = (array) $attrib;
  393.         }
  394.        
  395.         public function getHTML() {
  396.                 $html = '';
  397.                 foreach ($this->children as $child) {
  398.                         $html .= $child->getHTML();
  399.                 }
  400.                 return $html;
  401.         }
  402.        
  403.         public function getAttrib($name) {
  404.                 return $this->unspecialchars($this->attrib[$name]);
  405.         }
  406.        
  407.         public function hasAttrib($name) {
  408.                 return isset($this->attrib[$name]);
  409.         }
  410.        
  411. }
  412.  
  413. class Text extends Node {
  414.        
  415.         public $parent = null;
  416.         public $text = '';
  417.        
  418.         public function __construct($parent, $text) {
  419.                 $this->parent = $parent;
  420.                 $this->text = (string) $text;
  421.         }
  422.        
  423.         public function getHTML() {
  424.                 return htmlspecialchars($this->unspecialchars($this->text));
  425.         }
  426.        
  427. }
  428.  
  429. /* ==================== */
  430.  
  431. class Tag_P extends Tag {
  432.        
  433.         public function getHTML() {
  434.                 return '<p class="bbcode">'.parent::getHTML().'</p>';
  435.         }
  436.        
  437. }
  438.  
  439. class Tag_B extends Tag {
  440.        
  441.         public function getHTML() {
  442.                 return '<b class="bbcode">'.parent::getHTML().'</b>';
  443.         }
  444.        
  445. }
  446.  
  447. class Tag_I extends Tag {
  448.        
  449.         public function getHTML() {
  450.                 return '<i class="bbcode">'.parent::getHTML().'</i>';
  451.         }
  452.        
  453. }
  454.  
  455. class Tag_U extends Tag {
  456.        
  457.         public function getHTML() {
  458.                 return '<u class="bbcode">'.parent::getHTML().'</u>';
  459.         }
  460.        
  461. }
  462.  
  463. class Tag_S extends Tag {
  464.        
  465.         public function getHTML() {
  466.                 return '<s class="bbcode">'.parent::getHTML().'</s>';
  467.         }
  468.        
  469. }
  470.  
  471. class Tag_SUB extends Tag {
  472.        
  473.         public function getHTML() {
  474.                 return '<sub class="bbcode">'.parent::getHTML().'</sub>';
  475.         }
  476.        
  477. }
  478.  
  479. class Tag_SUP extends Tag {
  480.        
  481.         public function getHTML() {
  482.                 return '<sup class="bbcode">'.parent::getHTML().'</sup>';
  483.         }
  484.        
  485. }
  486.  
  487. class Tag_URL extends Tag {
  488.        
  489.         public function getHTML() {
  490.                 return '<a href="'.htmlspecialchars($this->getAttrib('url')).'" class="bbcode">'.parent::getHTML().'</a>';
  491.         }
  492.        
  493. }
  494.  
  495. class Tag_IMG extends Tag {
  496.        
  497.         public function getHTML() {
  498.                 $alt = ($this->hasAttrib('alt')) ? $this->getAttrib('alt') : '';
  499.                 switch ($this->getAttrib('float')) {
  500.                         case 'left': $float = 'left'; break;
  501.                         case 'right': $float = 'right'; break;
  502.                         default: $float = 'none'; break;
  503.                 }
  504.                 return '<img src="'.htmlspecialchars($this->getAttrib('img')).'" alt="'.htmlspecialchars($alt).'" style="float:'.$float.';" class="bbcode" />';
  505.         }
  506.        
  507. }
  508.  
  509. class Tag_ALIGN extends Tag {
  510.        
  511.         public function getHTML() {
  512.                 switch($this->getAttrib('align')) {
  513.                         case 'left': $align = 'left'; break;
  514.                         case 'right': $align = 'right'; break;
  515.                         case 'center': $align = 'center'; break;
  516.                         case 'justify': $align = 'justify'; break;
  517.                         default: $align = 'inherit'; break;
  518.                 }
  519.                 return '<div style="text-align:'.$align.'" class="bbcode">'.parent::getHTML().'</div>';
  520.         }
  521.        
  522. }
  523. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement