Advertisement
JoshDreamland

BB Parser — FSX Friendly!

Jan 16th, 2014
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 11.20 KB | None | 0 0
  1. <?php
  2. /*!
  3.  * @file bbcode.php
  4.  *
  5.  * A BBCode tag is defined in terms of two parameters, a parse type,
  6.  * and an argument count. The parse type, "type", is given by one of
  7.  * the TT_ constants.
  8.  *
  9.  * The argument count, "args", is in the range [0,4]. A zero-argument tag
  10.  * does not have an end tag. A one-argument tag has its argument between
  11.  * the start and end tag. A two-element tag is identical to a one-argument
  12.  * tag with the advent of an =arg1 in the opening tag, eg,
  13.  *   [url=arg1]arg0[/url]
  14.  * A three-argument tag is given an additional parameter at tag close.
  15.  * A four-element tag actually has unlimited arguments and operates as
  16.  * an HTML tag. The parameters are passed in an associative array to
  17.  * a callback, which is the only valid type for this count.
  18.  *
  19.  * From an implementation perspective, the argument count controls how
  20.  * the tag is decomposed into memory, ie, parsed. The type determines
  21.  * how the tag is evaluated after parse finishes. The argument count is
  22.  * used in evaluation to pass the correct number of parameters, but the
  23.  * type is not considered during parse, at all.
  24.  *
  25.  * Other notes: Tag names may not contain spaces or "=".
  26. */
  27.  
  28. const TT_TRIVIAL = 0;    ///< This tag is a direct HTML wrapper tag
  29. const TT_INTERLEAVE = 1; ///< This tag becomes "html"[0] . arg0 . "html"[1] . arg1 ...
  30. const TT_CALLBACK = 2;   ///< This tag gets evaluated by a callback function
  31.  
  32. $bbtags = array(
  33.   'b'   => array('type' => TT_TRIVIAL, 'args' => 1),
  34.   'i'   => array('type' => TT_TRIVIAL, 'args' => 1),
  35.   'u'   => array('type' => TT_TRIVIAL, 'args' => 1),
  36.   'hr'  => array('type' => TT_TRIVIAL, 'args' => 0),
  37.   'img' => array(
  38.     'type' => TT_INTERLEAVE,
  39.     'args' => 1,
  40.     'html' => array('<img src="', '" alt="User image" />')
  41.   ),
  42.   'url' => array(
  43.     'type' => TT_CALLBACK,
  44.     'args' => 2,
  45.     'func' => function($txt, $url) {
  46.        return '<a href="' . $url . '">' . $txt . '</a>';
  47.     }
  48.    
  49.   ),
  50.   'bubble' => array(
  51.     'type' => TT_CALLBACK,
  52.     'args' => 4,
  53.     'func' => function($txt, $args) {
  54.        $br = isset($args['radius'])? $args['radius'] : 6;
  55.        $col = isset($args['color'])? $args['color']  : "000";
  56.        return '<span class="bubble" style="'
  57.          . "border-radius:".$br."px;"
  58.          . "color:#".$col.";"
  59.          . '">' . $txt . '</span>';
  60.     }
  61.   ),
  62.   'grad' => array(
  63.     'type' => TT_CALLBACK,
  64.     'args' => 3,
  65.     'func' => function($txt, $arg0, $arg1) {
  66.        if (empty($arg0)) $arg0="pink";
  67.        if (empty($arg1)) $arg1="green";
  68.        return "<span style=\"linear-gradient(to right,$arg0,$arg1)\">"
  69.               . $txt . '</span>';
  70.     }
  71.   )
  72. );
  73.  
  74. function notice($s) { echo "<!-- Notice: $s -->\n"; }
  75.  
  76. /*!
  77.  * This is a recursive function; call it with i = 0, and it will
  78.  * call itself recursively until all tags are parsed. It returns the
  79.  * parsed string, with $i set to the position of the first-read closing tag.
  80.  * @param  $str  The string to parse.
  81.  * @param  $i    [in/out] The position from which to start parsing.
  82.  *               Set at the end of the function to denote the first
  83.  *               unparsed character, or FALSE if all characters have
  84.  *               been consumed.
  85.  * @return Returns the HTML parsed substring of the given input.
  86. */
  87. function parse_sub($str, &$i)
  88. {
  89.   global $bbtags;
  90.   $outstr = "";
  91.   $bookmark = $i;
  92.   for ($i = strpos($str, '[', $i); $i !== FALSE; $i = strpos($str, '[', $i))
  93.   {
  94.     $close = strpos($str, ']', $i + 1);
  95.     if ($close == FALSE) {
  96.       $i = FALSE;
  97.       return $outstr . substr($str, $bookmark, $i - $bookmark);
  98.     }
  99.    
  100.     // Look inside our tag, now
  101.     $stag = substr($str, $i+1, $close - $i - 1);
  102.    
  103.     // If it's a closing tag, return and let the parent handle that
  104.     if ($stag[0] == '/')
  105.       return $outstr . substr($str, $bookmark, $i - $bookmark);
  106.    
  107.     // Doplegänger
  108.     if ($stag[0] == '[') {
  109.       ++$i;
  110.       continue;
  111.     }
  112.    
  113.     // Make sure we're safe if args=1; we don't know, yet
  114.     $tagc = preg_split('/\s*[\s=]\s*/', $stag, 2);
  115.     if (count($tagc) == 2)
  116.       $arg1 = $tagc[1]; // This technically allows [tag x]y[/tag]
  117.     else
  118.       $arg1 = NULL; // Don't reuse old arg values
  119.     $tname = strtolower($tagc[0]); // Deliberately not trimmed
  120.    
  121.     // Look up tag
  122.     $tstart = $i;
  123.     $i = $close + 1;
  124.     if (!array_key_exists($tname, $bbtags)) {
  125.       notice("No bbtag called [$tname] ($stag)");
  126.       continue;
  127.     }
  128.    
  129.     // Tag found
  130.     $bbtag = $bbtags[$tname];
  131.     if ($bbtag['args'] > 0)
  132.     {
  133.       $tlen = strlen($tname) + 2;
  134.      
  135.       // Handle associative tags
  136.       if ($bbtag['args'] > 3)
  137.       {
  138.         $i = $tstart + $tlen;
  139.         if (ctype_space($str[$i-1])) {
  140.           $args = read_attr_list($str, $i);
  141.           if ($args === NULL) { // Bail on failure
  142.             $i = FALSE;
  143.             return $outstr . substr($str, $bookmark);
  144.           }
  145.           ++$i;
  146.         }
  147.         else if ($str[$i-1] == ']')
  148.           $args = array();
  149.         else continue;
  150.       }
  151.       else {
  152.         $args = NULL;
  153.         if ($bbtag['args'] == 1 && $str[$tstart + $tlen - 1] != ']') {
  154.           notice("$str [$tstart+$tlen-1] != ']'");
  155.           continue;
  156.         }
  157.       }
  158.      
  159.       $arg0 = '';
  160.       for (;;) {
  161.         // This is where shit gets interesting
  162.         $arg0 .= parse_sub($str, $i);
  163.        
  164.         // Make sure we arrived at our own closing tag
  165.         if ($i == FALSE)
  166.           return $outstr . substr($str, $bookmark);
  167.        
  168.         $close = strpos($str, ']', $i);
  169.         $ctag = substr($str, $i, $close - $i);
  170.         if (strncasecmp($ctag, '[/'.$tname, $tlen) != 0) {
  171.           notice("strncasecmp('$ctag', '[/'.'$tname', '$tlen') != 0");
  172.           $arg0 .= $str[$i++];
  173.           continue; // If not, just keep looking
  174.         }
  175.        
  176.         break;
  177.       }
  178.      
  179.       // Now we have a little more parsing to do for ternary tags
  180.       if ($bbtag['args'] == 3) {
  181.         $arg2 = trim(substr($str, $i + $tlen, $close - $i - $tlen));
  182.         if (strlen($arg2) > 0 && $arg2[0] == '=')
  183.           $arg2 = trim(substr($arg2, 1));
  184.       }
  185.       else
  186.         $arg2 = NULL;
  187.      
  188.       $i = $close + 1;
  189.     }
  190.     else
  191.       $arg0 = $arg1 = $arg2 = $args = NULL;
  192.    
  193.     $outstr .= substr($str, $bookmark, $tstart - $bookmark);
  194.     $outstr .= evaluate_tag($tname, $bbtag, $arg0, $arg1, $arg2, $args);
  195.     $bookmark = $i;
  196.   }
  197.   $outstr .= substr($str, $bookmark);
  198.   return $outstr;
  199. }
  200.  
  201.  
  202. /*! This function might as well be in that last block of code,
  203.  * but it was getting frighteningly big, so I moved it here.
  204.  * @note Unused tag arguments should be NULL; \p $arg1 and \p $arg2 should
  205.  *       never be non-null at the same time as \p $args.
  206.  * @param $tname  The name of the tag.
  207.  * @param $bbtag  The tag array from the \c bbtags array.
  208.  * @param $arg0   The first argument, the text between the tags, if applicable.
  209.  * @param $arg1   The second argument, the = value in the opening tag, if applicable.
  210.  * @param $arg2   The third argument, the = value in the closing tag, if applicable.
  211.  * @param $args   An associative array of arguments, if applicable.
  212.  * @return Returns the result of evaluating the tag;
  213.  *         a string with which to replace the tag.
  214. */
  215. function evaluate_tag($tname, $bbtag, $arg0, $arg1, $arg2, $args) {
  216.   switch ($bbtag['type'])
  217.   {
  218.     case TT_TRIVIAL: switch ($bbtag['args']) {
  219.       case 0:  return "<$tname />";
  220.       case 1:  return "<$tname>$arg0</$tname>";
  221.       case 2:  return "<!-- This tag is invalid -->";
  222.       case 3:  return "<!-- This tag is invalid -->";
  223.       default: return "<!-- This tag would be a security risk -->";
  224.     }
  225.     case TT_INTERLEAVE: $h = $bbtag['html']; switch ($bbtag['args']) {
  226.       case 0:  return $h[0];
  227.       case 1:  return $h[0] . $arg0 . $h[1];
  228.       case 2:  return $h[0] . $arg1 . $h[1] . $arg0 . $h[2];
  229.       case 3:  return $h[0] . $arg1 . $h[1] . $arg0 . $h[2] . $arg2 . $h[3];
  230.       default: return "<!-- This tag cannot be automated -->";
  231.     }
  232.     case TT_CALLBACK: $f = $bbtag['func']; switch ($bbtag['args']) {
  233.       case 0:  return $f();
  234.       case 1:  return $f($arg0);
  235.       case 2:  return $f($arg0, $arg1);
  236.       case 3:  return $f($arg0, $arg1, $arg2);
  237.       default: return $f($arg0, $args);
  238.     }
  239.   }
  240. }
  241.  
  242.  
  243. /*! This nonsense is to parse associative tags.
  244.  * @param $str The string from which to read attributes.
  245.  * @param $i   [in/out] The position from which to start reading.
  246.  *             Set at end of function call to denote the end of the list.
  247.  * @return  Returns the associative array read in from the string.
  248. */
  249. function read_attr_list($str, &$i) {
  250.   $len = strlen($str);
  251.   $attrs = array();
  252.   while ($i < $len && $str[$i] != ']')
  253.   {
  254.     if (ctype_space($str[$i]))
  255.       continue;
  256.    
  257.     // Read an attribute name
  258.     $attr_start = $i;
  259.     while (++$i < $len && $str[$i] != '='
  260.        && !ctype_space($str[$i-1]));
  261.     $attr_name = substr($str, $attr_start, $i-$attr_start);
  262.    
  263.     // Read past the attribute name
  264.     while ($i < $len && ctype_space($str[$i]))
  265.       ++$i;
  266.     if ($i >= $len) // Bail if out of bounds
  267.       { notice("OOB1"); return NULL; }
  268.    
  269.     if ($str[$i] == '=')
  270.     {
  271.       while (++$i < $len && ctype_space($str[$i]));
  272.       if ($str[$i] == '"' || $str[$i] == '\'')
  273.       {
  274.         $val_start = $i + 1;
  275.         $ochar = $str[$i];
  276.         while (++$i < $len && $str[$i] != $ochar)
  277.           if ($str[$i] == '\\') ++$i;
  278.         if ($i >= $len)
  279.           { notice("OOB2"); return NULL; }
  280.         $val = str_replace(
  281.           array("\\\\", "\\\'", "\\\"", "\\r", "\\n", "\\t"),
  282.           array("\\",     "\'",   "\"",   "\r",  "\n", "\t"),
  283.           substr($str, $val_start, $i - $val_start)
  284.         );
  285.       }
  286.       else {
  287.         $val_start = $i;
  288.         while (++$i < $len && $str[$i] != ']' && !ctype_space($str[$i]));
  289.         if ($i >= $len)
  290.           { notice("OOB3"); return NULL; }
  291.         $val = substr($str, $val_start, $i - $val_start);
  292.       }
  293.     }
  294.     else $val = NULL;
  295.     $attrs[$attr_name] = $val;
  296.   }
  297.   return $attrs;
  298. }
  299.  
  300. /*! Main BBCode parser call.
  301.  * @param  $str  The BBCode string to parse.
  302.  * @return Returns the HTML parsed version of the input.
  303. */
  304. function parse_bbcode($str) {
  305.   $i = 0;
  306.   $res = "";
  307.   while ($i !== false) {
  308.     $res .= parse_sub($str, $i);
  309.     if ($i !== false)
  310.       $res .= $str[$i++];
  311.   }
  312.   return $res;
  313. }
  314.  
  315. echo "Result:<br/>\n";
  316. echo parse_bbcode(
  317. "This is some [b]cool shit[/b], I'm sure you'll agree.
  318. Italics: [i]check[/i].
  319. Bold italics: [b][i]check[/i] and [i]double check[/i][/b]
  320. Now, let's try [url=asses]some urls[/url].
  321. And now, let's try [url = asses in thongs]urls with gaps[/url].
  322. For good measure, [grad=red]graded spans[/grad=blue].
  323. To fuck shit up, [grad=red]graded spans[/grad].
  324. To really fuck shit up, [grad]graded spans[/grad].
  325. Okay, [bubble]complicated tag time[/bubble]!
  326. Now, [bubble radius='5' color=255]more complicated tag time[/bubble]!
  327. Finally, [bubble radius='5[b]ha[/b][/bubble]' color=255]extremely complicated tag time[/bubble]!
  328.  
  329. This is an unmatched closing italic tag: [/i]
  330. Here's one in a bold tag: [b]wat[/[/i][/[[/b]
  331. [b]This is an unmatched bold tag.
  332.  
  333. This concludes the BBCode portion of your exam.
  334. ");
  335.  
  336. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement