array('type' => TT_TRIVIAL, 'args' => 1),
'i' => array('type' => TT_TRIVIAL, 'args' => 1),
'u' => array('type' => TT_TRIVIAL, 'args' => 1),
'hr' => array('type' => TT_TRIVIAL, 'args' => 0),
'img' => array(
'type' => TT_INTERLEAVE,
'args' => 1,
'html' => array('
')
),
'url' => array(
'type' => TT_CALLBACK,
'args' => 2,
'func' => function($txt, $url) {
return '' . $txt . '';
}
),
'bubble' => array(
'type' => TT_CALLBACK,
'args' => 4,
'func' => function($txt, $args) {
$br = isset($args['radius'])? $args['radius'] : 6;
$col = isset($args['color'])? $args['color'] : "000";
return '' . $txt . '';
}
),
'grad' => array(
'type' => TT_CALLBACK,
'args' => 3,
'func' => function($txt, $arg0, $arg1) {
if (empty($arg0)) $arg0="pink";
if (empty($arg1)) $arg1="green";
return ""
. $txt . '';
}
)
);
function notice($s) { echo "\n"; }
// This is a recursive function; call it with i = 0, and it will
// call itself recursively until all tags are parsed. It returns the
// parsed string, with $i set to the position of the first-read closing tag.
function parse_sub($str, &$i)
{
global $bbtags;
$outstr = "";
$bookmark = $i;
for ($i = strpos($str, '[', $i); $i !== FALSE; $i = strpos($str, '[', $i))
{
$close = strpos($str, ']', $i + 1);
if ($close == FALSE) {
$i = FALSE;
return $outstr . substr($str, $bookmark, $i - $bookmark);
}
// Look inside our tag, now
$stag = substr($str, $i+1, $close - $i - 1);
// If it's a closing tag, return and let the parent handle that
if ($stag[0] == '/')
return $outstr . substr($str, $bookmark, $i - $bookmark);
// Doplegänger
if ($stag[0] == '[') {
++$i;
continue;
}
// Make sure we're safe if args=1; we don't know, yet
$tagc = preg_split('/\s*[\s=]\s*/', $stag, 2);
if (count($tagc) == 2)
$arg1 = $tagc[1]; // This technically allows [tag x]y[/tag]
else
$arg1 = NULL; // Don't reuse old arg values
$tname = strtolower($tagc[0]); // Deliberately not trimmed
// Look up tag
$tstart = $i;
$i = $close + 1;
if (!array_key_exists($tname, $bbtags)) {
notice("No bbtag called [$tname] ($stag)");
continue;
}
// Tag found
$bbtag = $bbtags[$tname];
if ($bbtag['args'] > 0)
{
$tlen = strlen($tname) + 2;
// Handle associative tags
if ($bbtag['args'] > 3)
{
$i = $tstart + $tlen;
if (ctype_space($str[$i-1])) {
$args = read_attr_list($str, $i);
if ($args === NULL) { // Bail on failure
$i = FALSE;
return $outstr . substr($str, $bookmark);
}
++$i;
}
else if ($str[$i-1] == ']')
$args = array();
else continue;
}
else {
$args = NULL;
if ($bbtag['args'] == 1 && $str[$tstart + $tlen - 1] != ']') {
notice("$str [$tstart+$tlen-1] != ']'");
continue;
}
}
$arg0 = '';
for (;;) {
// This is where shit gets interesting
$arg0 .= parse_sub($str, $i);
// Make sure we arrived at our own closing tag
if ($i == FALSE)
return $outstr . substr($str, $bookmark);
$close = strpos($str, ']', $i);
$ctag = substr($str, $i, $close - $i);
if (strncasecmp($ctag, '[/'.$tname, $tlen) != 0) {
notice("strncasecmp('$ctag', '[/'.'$tname', '$tlen') != 0");
$arg0 .= $str[$i++];
continue; // If not, just keep looking
}
break;
}
// Now we have a little more parsing to do for ternary tags
if ($bbtag['args'] == 3) {
$arg2 = trim(substr($str, $i + $tlen, $close - $i - $tlen));
if (strlen($arg2) > 0 && $arg2[0] == '=')
$arg2 = trim(substr($arg2, 1));
}
else
$arg2 = NULL;
$i = $close + 1;
}
else
$arg0 = $arg1 = $arg2 = $args = NULL;
$outstr .= substr($str, $bookmark, $tstart - $bookmark);
$outstr .= evaluate_tag($tname, $bbtag, $arg0, $arg1, $arg2, $args);
$bookmark = $i;
}
$outstr .= substr($str, $bookmark);
return $outstr;
}
// This function might as well be in that last block of code,
// but it was getting frighteningly big, so I moved it here.
function evaluate_tag($tname, $bbtag, $arg0, $arg1, $arg2, $args) {
switch ($bbtag['type'])
{
case TT_TRIVIAL: switch ($bbtag['args']) {
case 0: return "<$tname />";
case 1: return "<$tname>$arg0$tname>";
case 2: return "";
case 3: return "";
default: return "";
}
case TT_INTERLEAVE: $h = $bbtag['html']; switch ($bbtag['args']) {
case 0: return $h[0];
case 1: return $h[0] . $arg0 . $h[1];
case 2: return $h[0] . $arg1 . $h[1] . $arg0 . $h[2];
case 3: return $h[0] . $arg1 . $h[1] . $arg0 . $h[2] . $arg2 . $h[3];
default: return "";
}
case TT_CALLBACK: $f = $bbtag['func']; switch ($bbtag['args']) {
case 0: return $f();
case 1: return $f($arg0);
case 2: return $f($arg0, $arg1);
case 3: return $f($arg0, $arg1, $arg2);
default: return $f($arg0, $args);
}
}
}
// And then all this nonsense is to parse associative tags.
function read_attr_list($str, &$i) {
$len = strlen($str);
$attrs = array();
while ($i < $len && $str[$i] != ']')
{
if (ctype_space($str[$i]))
continue;
// Read an attribute name
$attr_start = $i;
while (++$i < $len && $str[$i] != '='
&& !ctype_space($str[$i-1]));
$attr_name = substr($str, $attr_start, $i-$attr_start);
// Read past the attribute name
while ($i < $len && ctype_space($str[$i]))
++$i;
if ($i >= $len) // Bail if out of bounds
{ notice("OOB1"); return NULL; }
if ($str[$i] == '=')
{
while (++$i < $len && ctype_space($str[$i]));
if ($str[$i] == '"' || $str[$i] == '\'')
{
$val_start = $i + 1;
$ochar = $str[$i];
while (++$i < $len && $str[$i] != $ochar)
if ($str[$i] == '\\') ++$i;
if ($i >= $len)
{ notice("OOB2"); return NULL; }
$val = str_replace(
array("\\\\", "\\\'", "\\\"", "\\r", "\\n", "\\t"),
array("\\", "\'", "\"", "\r", "\n", "\t"),
substr($str, $val_start, $i - $val_start)
);
}
else {
$val_start = $i;
while (++$i < $len && $str[$i] != ']' && !ctype_space($str[$i]));
if ($i >= $len)
{ notice("OOB3"); return NULL; }
$val = substr($str, $val_start, $i - $val_start);
}
}
else $val = NULL;
$attrs[$attr_name] = $val;
}
return $attrs;
}
function parse_bbcode($str) {
$i = 0;
$res = "";
while ($i !== false) {
$res .= parse_sub($str, $i);
if ($i !== false)
$res .= $str[$i++];
}
return $res;
}
echo "Result:
\n";
echo parse_bbcode(
"This is some [b]cool shit[/b], I'm sure you'll agree.
Italics: [i]check[/i].
Bold italics: [b][i]check[/i] and [i]double check[/i][/b]
Now, let's try [url=asses]some urls[/url].
And now, let's try [url = asses in thongs]urls with gaps[/url].
For good measure, [grad=red]graded spans[/grad=blue].
To fuck shit up, [grad=red]graded spans[/grad].
To really fuck shit up, [grad]graded spans[/grad].
Okay, [bubble]complicated tag time[/bubble]!
Now, [bubble radius='5' color=255]more complicated tag time[/bubble]!
Finally, [bubble radius='5[b]ha[/b][/bubble]' color=255]extremely complicated tag time[/bubble]!
This is an unmatched closing italic tag: [/i]
Here's one in a bold tag: [b]wat[/[/i][/[[/b]
[b]This is an unmatched bold tag.
This concludes the BBCode portion of your exam.
");
?>