[ Previous: D. Adding Your Own Smileys | Next: F. Adding Callback Tags ]

E. Adding Enhanced Tags

Enhanced Tags, the Theory

What if simple HTML replacement isn't enough for your tags? NBBC provides two techniques for performing more sophisticated conversions; the first is enhanced tags, and the second is callback tags. We'll start with enhanced tags.

Let's say you want to add a tag that lets your users add a border around their text, a [border] tag. You want the user to be able to specify the color and thickness of the border, like this:

[border color=blue size=2]This has a blue border![/border]
This has a blue border!

Obviously, a simple replacement of HTML isn't going to be sufficient for this; we need to generate customized HTML depending on what the user supplies for the color and the size, and we need to also make sure the user supplied a color and size that makes sense: A size of "-1" is no good, and a color named "blork" is no good either.

This is where enhanced mode comes in. In enhanced mode, instead of supplying a pair of chunks of replacement HTML text, you supply a template with locations in it where the user's values should be inserted. Like this:

$bbcode->AddRule('border', Array( 'mode' => BBCODE_MODE_ENHANCED, 'template' => '<div style="border: {$size}px solid {$color}">{$_content}</div>', 'class' => 'block', 'allow_in' => Array('listitem', 'block', 'columns'), ));

There are several things different here from the last example. First, we've added the mode parameter, which controls how a tag rule is processed by NBBC. The default mode is BBCODE_MODE_SIMPLE, which we just looked at; you can also use BBCODE_MODE_ENHANCED and BBCODE_MODE_CALLBACK, and NBBC itself uses BBCODE_MODE_INTERNAL and BBCODE_MODE_LIBRARY as part of the standard BBcode library.

Second, we've switched from inline class to block class, because HTML borders tend to look a little weird when applied to individual lines of text, and because applying them to a single line or word usually isn't what the user thinks will happen with a [border] tag anyway: He'll expect that it produces a big border around all of his text. So our output will be based on <div> and not <span>, and our class is block, and because a <div> in HTML can't go inside a <span>, we've restricted the allow_in array to things that <div> elements can actually go inside: Other blocks, list items, and columns (which are table cells in disguise).

Last, we come to the meat of the rule, the template itself. Let's break that out and look at it by itself:

<div style="border: {$size}px solid {$color}">{$_content}</div>

This template includes three inserts, places where text from the user may be substituted. The first, {$size} swaps in the user's provided border size; the second, {$color} swaps in the user's provided border color; and the third, {$_content}, is a special insert that substitutes the content or body of the tag, the text between the [border] tag and the [/border] tag. You may substitute in any parameters from the tag that you want, not just color and size.

(There are two additional possible built-in "special" inserts, like {$_content}. They are {$_name}, which is always the name of the tag itself; and {$_default} which is the default value provided by the user. The default value is the value attached to the name of the tag and not any specific other parameter; for example, in [font=Times], the default value is "Times". Most of the tags in the standard library use and support default values whenever possible, since they're easiest for the user to work with.)

Input Validation

So now we have a tag that wraps our text in a border. What's wrong with that?

The answer is that we haven't done any kind of validation: What if, for example, the user wrote this malicious BBCode?

[border color="blue;font-size:40pt" size=2]This has a blue border![/border]
This has a blue border!

Oops! That's not supposed to happen! The user's not supposed to be able to use the [border] tag to change the font size! We have a problem here because we haven't checked the user's input, and that's dangerous: The whole point behind a validating BBCode parser is that it ensures that the user can't do things like this. Imagine what would happen if the user inserted some Javascript? Your page could be made to unknowingly distribute viruses! Since that's bad --- very bad --- let's look at how to fix this problem.

The solution is to specify some restrictions on what the user is allowed to use for attributes. To do this, we use regular expressions to limit the possible values of {$size} and {$color} to things that we know are safe, like "1" and "red," and to prohibit all other values. When NBBC sees a prohibited value, it rejects the tag in its entirety, thus ensuring that your output will still be safe.

Tech Tip

Don't know what regular expressions are? They're used pretty frequently in NBBC, but we won't introduce them here: They're documented very well in other places, and you can even buy whole books on them if you want to learn them in depth. Online, there's a good manual documenting NBBC's Perl-compatible regular expressions (PCRE) in detail, and there are also good introductions/tutorials to regular expressions, both short intros and long tutorials.

In this manual, though, we're going to use just the following basic syntax:

  • a - the letter "a"; a letter or number or symbol usually matches itself
  • [a-z] - a character class, matching "a" through "z", inclusive
  • [a-f0-9z] - a character class, matching "a" through "f", and "0" through "9", and "z"
  • ^ - anchor to the beginning of the input
  • $ - anchor to the end of the input
  • r* - zero or more of "r"
  • r+ - one or more of "r"
  • r? - zero or one of "r", i.e., an optional "r"
  • r|s - "r" or "s"
  • (r) - parentheses control precedence, just like in arithmetic

That's it: We're going to use just the basics, and not going to touch advanced syntax like (?=...) or r{n,m} in the NBBC manual. If you need to learn or brush up on regexes to follow this, there are plenty of other documents that will teach you how to do so.

These are the regular expressions we'll use for {$size} and {$color}, and we'll err on the side of being too restrictive: That's generally safer than being too permissive. In this case, we'll allow any positive integer for {$size}, and any hex value or alphabetic string for {$color}. This leaves out CSS-style rgb(r,g,b) colors and the new CSS3 hsv(h,s,v) colors, but it's simple, usable, and reasonably flexible:

Regex for validating a {$size}:
Regex for validating a {$color}:

So how do we use these expressions? They get added to the BBCode rule for [border] under its 'allow' property, like this:

$bbcode->AddRule('border', Array( 'mode' => BBCODE_MODE_ENHANCED, 'template' => '<div style="border: {$size}px solid {$color}">{$_content}</div>', 'allow' => Array( 'color' => '/^#[0-9a-fA-F]+|[a-zA-Z]+$/', 'size' => '/^[1-9][0-9]*$/', ), 'class' => 'block', 'allow_in' => Array('listitem', 'block', 'columns'), ));

Now any time NBBC sees a tag whose parameters are valid, it'll accept that tag; but any time it sees a tag whose parameters don't match the 'allow' regexes, that tag will be rejected, like this:

[border color="blue;font-size:40pt" size=2]This has a blue border![/border] [border color="green" size=2]This has a green border![/border]
[border color="blue;font-size:40pt" size=2]This has a blue border![/border]
This has a green border!

While this output isn't perfect (for example, it might be better when given a bad color or size to try to clean it up than to outright ignore it), it's at least safe output; the user typed garbage input, and got garbage output, which is much better than what we had before.

Default Values

Okay, one more question: What happens if the user doesn't provide a color or size for the border? The 'allow' property is only checked for the parameters that get included, so it's entirely possible for the user to still produce bad output like this:

[border color=blue]This has a blue border![/border]
This has a blue border!

Obviously, that's not what you want, and whether it'll work at all depends on how the browser treats a damaged CSS style (most will ignore it). Instead, it would be good to be able to say that if, say, a size is not included, use a size of "1" so that the output is valid and useful even if not all of the information is included. We can do this using default values:

$bbcode->AddRule('border', Array( 'mode' => BBCODE_MODE_ENHANCED, 'template' => '<div style="border: {$size}px solid {$color}">{$_content}</div>', 'allow' => Array( 'color' => '/^#[0-9a-fA-F]+|[a-zA-Z]+$/', 'size' => '/^[1-9][0-9]*$/', ), 'default' => Array( 'color' => 'blue', 'size' => '1', ), 'class' => 'block', 'allow_in' => Array('listitem', 'block', 'columns'), ));

Now whenever the user leaves out the color parameter, NBBC will pretend that the user included color=blue; and likewise, when size is omitted, NBBC will pretend that the user included size=1. This is certainly much more in keeping with the user's expectations (that if he uses [border], he'll get a border), and it makes the enhanced tag useful in pretty much all circumstances:

[border]This has a blue border![/border]
This has a blue border!

[ Previous: D. Adding Your Own Smileys | Next: F. Adding Callback Tags ]

Copyright © 2010, the Phantom Inker. All rights reserved.