I was inspired to write a BBCode to HTML converter as I'm currently learning functional programming. I wanted achieve functional cohesion. jsFiddle

I'd like feedback on:

  • structuring of the code. Should the functions getHtmlTag and getHtml be contained within bbTagToHtmlTag since they are coupled through the rules variable?
  • any regex expressions that can reduce the code base
  • any changes to the data model to reduce complexity

Below is the newer version of the code. This version is slightly less coupled compared to the original version and the functions are separated to allow for more reuse. This new version doesn't involve any recursion, so it may be more efficient.

I suspect the cost of reuse is an increase in code base. Although, I'm sure there's more regex magic that can be used to slim down the code base.

/* Escapes a string for use with regular expressions */
function escapeString(input) {
    return input.replace(/([\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,
                         function(c){return "\\" + c;});

/* replaces all */
function replaceAll(search, input, replacement, ignore) {
   return input.replace(
       new RegExp(escapeString(search), "g"+(ignore?"i":"")), 

/* Check if an index is event */
function isIndexEven(val, index, context) {
    return (index % 2 === 0);

/* Sets the attributes on an HTML string given a set of attributes */
function setHtmlAttrs(htmlCode, attrs) {
    return attrs.reduce(function(htmlCode, attr, index) {
        var re = new RegExp('\\$' + (index + 1), 'g');
        return htmlCode.replace(re, attr);
    }, htmlCode);

/* Gets the html tag(s) for a bbCode tag */
function eleFragsToHtml(isClosing, elementFragments) {
    return '<' + elementFragments.map(function(frag) {
        return (isClosing ? '/' + frag.join(' ') : frag.join(' '));
    }).join('><') + '>';

/* Converts a single bbCode Tag to Html */
function bbTagToHtmlTag(rules, tag) {
    var attrs = tag.replace(/\[\w+|\]/g, '').trim().split(' ');
    var key = tag.replace(/\[\/?| .+|\]/g, '');
    var isClosing = /\//.test(tag);
    var htmlTemplate = (isClosing ? rules[key].map(function(rule) { 
                                        return rule.filter(isIndexEven)
                        : rules[key]);
    var output = eleFragsToHtml(isClosing, htmlTemplate);    
    return setHtmlAttrs(output, attrs);

/* Converts bbCode to HTML */
function bbCodeToHtml(rules, bbCode) {
    /* Creates the Regular Expression Search */
    var regex = new RegExp( ['\\[.?[',
                             '].*?\\]'].join(''), 'g' );
    return bbCode.match(regex)
                 .reduce(function(htmlCode, tag) {
                     return replaceAll(tag, 
                                      bbTagToHtmlTag(rules, tag));
                 }, bbCode);

Below is the test setup using the same rule set as the linked post.

var rules = {
    'b': [ ['strong'] ],
    'bg': [ ['span', 'style\"background-color:$1\"'] ],
    'big': [ ['span', 'style=\"font-size: larger;\"'] ],
    'c': [ ['span', 'style=\"color:$1\"'] ],
    'f': [ ['span', 'style=\"font-family:$1\"'] ],
    'i': [ ['em'] ],
    'img': [ ['a', 'href=\"$1\"'],
             ['img', 'src=\"$1\" style=\"max-height: 200px; max-width: 200px;\"'] ],
    'p': [ ['pre'] ],
    'small': [ ['small'] ],
    's': [ ['span', 'style=\"text-decoration: line-through;\"'] ]
var sample = '[big][big][c red]WARNING[/c][/big][/big][s]test[/s][img test.jpg][/img]';
$('#console').text(bbCodeToHtml(rules, sample));
  • \$\begingroup\$ Just had a thought, if this is converting a long string of bbCode, it would have to recurse many times. However, if I recurse over the ruleSets, it would only recurse by the number of rules. Going to give that a go. It'll probably make for cleaner code anyway. \$\endgroup\$ – Pete Apr 9 '14 at 22:13
  • \$\begingroup\$ Newer version looks much much better, however your fiddle no longer works, care to update it ? Your fiddle would be more impressive if you did $('console').html(bbCodeToHtml(rules, sample)); by the way \$\endgroup\$ – konijn Apr 11 '14 at 13:34
  • \$\begingroup\$ @konijn Thanks! Updated the link and the code. \$\endgroup\$ – Pete Apr 11 '14 at 15:47
  1. Overall, it's interesting, but quite hard to read. The indentation is kinda funky, and it would be easier to parse with all those ternary branches. For instance, this:

    var htmlTemplate = (isClosing ? rules[key].map(function(rule) { 
                                        return rule.filter(isIndexEven)
                        : rules[key]);

    Could be

    var htmlTemplate = rules[key];
    if (isClosing) {
      htmlTemplate = htmlTemplate.map(function (rule) { 
        return rule.filter(isIndexEven);

    Similarly, a few more local variables wouldn't hurt. E.g.

    return input.replace(new RegExp(escapeString(search), "g"+(ignore?"i":"")), replacement);

    Could be

    var flags = "g" + (ignore ? "i" : ""),
        regex = new RegExp(escapeString(search), flags);
    return input.replace(regex, replacement);
  2. More micro-level: The pattern in escapeString can be simplified a great deal; you don't have to escape everything inside a character class, nor do you need the capture group. I get something like this


    The only things that need escaping is the backslash, the close square bracket, and the dash - unless you just put the dash at the end (if it's in the middle somewhere, it'll be interpreted as a range like 0-9).

    There's a similar thing in your rules; you don't need to escape double quotes inside single-quoted strings.

    I should add, though, that I haven't had the time to do a high-level review of all the regexp magic; I've looked a the individual patterns, not whether some can be combined, elided, or otherwise refactored.

  3. Speaking of the rules: Whenever possible use class names or simply rely on HTML tag names instead of hardcoded style attributes. For instance, the <big> tag exists in HTML, just like <small>, so you don't need a styled span element. And the img tag doesn't need a style attribute; just declare the style for img tags in a CSS file. The point is, as with all things CSS, that you won't have to change your code (behavior) to change the look (presentation).

  4. Some function names are pretty terse. E.g. eleFragsToHtml is a bit too truncated, if you ask me (especially considering the 2nd argument is spelled out as elementFragments)

  5. I'd define the unchanging regex patterns outside the functions they're in now, since they're static - or define some more functions, to make it more descriptive. For instance, this

    var attrs = tag.replace(/\[\w+|\]/g, '').trim().split(' ');
    var key = tag.replace(/\[\/?| .+|\]/g, '');
    var isClosing = /\//.test(tag);

    Could be

    var attrs     = tagAttributes(tag),
        key       = tagName(tag),
        isClosing = tagIsClosing(tag);

    (yes, I've combined the var statements).
    The tag* prefix on all those functions is of course a hint that they might benefit from being namespaced in an object. Or just have one parseTag function that returns an object with name, attributes, and closing properties.

  6. Lastly, it'd be nice to stick all of this in an IIFE, like

    var bbCodeToHtml = (function () {
      // shared variables, regex patterns here
      // various helper functions
      return function (string) {
        // the function that ties it all together

    The idea is that the global namespace isn't polluted with a lot of "niche" functions; all you'll see is a function called bbCodeToHtml.

FYI, it's a toss-up whether the function should be bbCodeToHTML or stay as it is. The DOM is inconsistent in this regard (you have the innerHTML and namespaceURI properties, but you also have functions like getElementById - and I doubt they mean the Freudian "id" but rather "ID".

  • \$\begingroup\$ Thanks for the in depth analysis. One of my goals this year is to write more readable code and all your feedback helps immensely. Definitely learned a lot from your response. Thanks for taking the time. \$\endgroup\$ – Pete Apr 12 '14 at 13:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.