I decided to try my hand at implementing a BBCode parser in JavaScript. You can play around with it online here, and download the source here.
I had looked around a little bit and noticed that the existing JavaScript BBCode parsers had at least a few of the following issues:
- They didn’t report errors on misaligned tags (e.g., [b][u]test[/b][/u]).
- They couldn’t handle tags of the same type that were nested within each other (e.g., [color=red]red[color=blue]blue[/color]red again[/color]). This happens because their regex will look for the first closing tag it can find.
- They couldn’t handle BBCode’s list format (e.g., [list][*]item 1[*]item 2[/list]).
- They didn’t report errors on incorrect parent-child relationships (e.g., [list][td]item 1?[/td][/list]).
- They weren’t easily extendible.
I naively thought it’d be easy to quickly whip up a parser, and at first it was. Most BBCode tags can be implemented with a simple find and replace. However, I quickly ran into the issues of dealing with nested tags of the same type, the noparse tag, and the list tag’s annoying [*] tag (which doesn’t have a closing tag). Luckily, I came across a neat blog post on finding nested patterns in JavaScript, which came in handy for isolating tag pairs, from the inner-most on up. Taking the idea from that post, one can do something like this to process the inner tags first and avoid the nested tag problem:
var str = "[list][list]test[/list][/list]", re = /\[([^\]]*?)\](.*?)\[\/\1\]/gi; while (str !== (str = str.replace(re, function(strMatch, subM1, subM2) { return "" + subM2 + " "; }))); // str = "" test
That idea works well, though you can’t implement a noparse tag if you process the inner-most tags first. So I decided to pre-process the BBCode with something similar to the idea above and add in nested-depth information to each open and close tag. Once all of the tags had that, I could parse the processed code with a regex that could easily match-up the correct open and close tags.
To get around the issue of the [*] tag having no closing tag, I wrote code that inserted [/*] tags where they were supposed to go during the pre-processing period. I wont go into the algorithm here, but you can dig into the code if you’re interested.
Also, I should note that the fact that JavaScript allows you to use a function as the second parameter to the replace method makes processing the tags really easy. Once you match a set of tags, you can recursively call the parse function on that tag’s contents from inside of the function you passed to replace.
Using the parser
To use the use the parser, you’d simply include xbbcode.js and xbbcode.css files somewhere on your page (which are contained in the zip file linked above), and then call the XBBCODE object from somewhere in your JavaScript:
var result = XBBCODE.process({ text: "Some bbcode to process here", removeMisalignedTags: false, addInLineBreaks: false }); console.log("Errors: " + result.error); console.dir(result.errorQueue); console.log(result.html);// the HTML form of your BBCode
Adding new tags
To add a new tag to your BBCode, add properties to the “tags” object inside of the XBBCODE object. For example, say you wanted to add a tag called [googleit] which would change its contents into a link of its google search results. You’d implement that by adding this to the tags object:
"googleit": { openTag: function(params,content) { var website = "\"http://www.google.com/#q=" + content + '"'; return '<a href=' + website + '>'; }, closeTag: function(params,content) { return '</a>'; } }
Then you could have BBCode like this: “[googleit]ta-da![/googleit]” which would be transformed into this: “<a href=”http://www.google.com/#q=ta-da!”>ta-da!</a>”
If you have any suggestions or find any bugs let me know.