2
\$\begingroup\$

I am currently using the following to convert [url=][/url] to an HTML link:

s = message.replace(/\[url=([^\]]+)\]\s*(.*?)\s*\[\/url\]/gi, "<a href='$1'>$2</a>")

That work's fine.

I then added on another replace function using a regular expression to replace www with http://www like so:

s = message.replace(/\[url=([^\]]+)\]\s*(.*?)\s*\[\/url\]/gi, "<a href='$1'>$2</a>")
.replace(/www/g, "http://www");

This is probably not the best/efficient method and also does not support https:// which is not a priority at the moment, but it is something I would like to include at some point. What could I do to improve the regular expression?

I asked this on Stack Overflow. However, I was informed that the type of question was not really fit for there, which I understand.

\$\endgroup\$
4
\$\begingroup\$

Like ojdo has pointed out, using plain regexes to parse something like BBCode is too complicated of a route to take.

It would be too long of a task to educate you on building proper parsers, but here's a short simplified example, of how one would go on parsing something like BBCode.

First tokenize

First off, we transform the input text into some higher level data structure which we can then work on more easily. For the BBCode, let's tokenize the input into begin tags, end tags, and text in between:

function tokenize(input) {
  var m;
  var tokens = [];
  while (input.length > 0) {
    if ((m = input.match(/^\[([a-z]+)(=([^\]]+))?\]/i))) {
      // start tag [foo=*]
      tokens.push({type: m[1].toLowerCase() + "_start", argument: m[3], raw: m[0]});
    }
    else if ((m = input.match(/^\[\/([a-z]+)\]/i))) {
      // end tag [/foo]
      tokens.push({type: m[1].toLowerCase() + "_end", raw: m[0]});
    }
    else {
      // text between tags (this regex always matches)
      m = input.match(/^\[?[^\[]*/);
      tokens.push({type: "text", raw: m[0]});
    }
    // skip forward
    input = input.slice(m[0].length);
  }
  return tokens;
}

Given an input like "[URL=google.com]click here[/URL]", the tokenize function produces an array of tokens like this:

[
  { type: 'url_start', argument: 'google.com', raw: '[URL=google.com]' },
  { type: 'text', raw: 'click here' },
  { type: 'url_end', raw: '[/URL]' }
]

Then produce HTML

Normally one would now transform this list of tokens into a tree structure, but as the BBCode-to-HTML conversion is very one-to-one, we don't really need to, and can pass the list of tokens directly to another function that converts it to HTML:

function tokensToHtml(tokens) {
  var output = "";
  for (var i=0; i<tokens.length; i++) {
    var tok = tokens[i];

    if (tok.type === "url_start") {
      var url = tok.argument;
      // prepend URL-s with http:// if needed
      if (!/^https?:\/\//.test(url)) {
        url = "http://" + url;
      }
      output += "<a href='" + escapeHtml(url) + "'>";
    }
    else if (tok.type === "url_end") {
      output += "</a>";
    }
    else {
      // anything else is a text token or an unknown tag
      // which we also treat just as text
      output += escapeHtml(tok.raw);
    }
  }
  return output;
}

function escapeHtml(str) {
  return str.replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

As can be see, we simply replace each token with corresponding HTML. When writing out the <a href=... we also take care of prepending the http:// as needed. Additionally we escape all HTML special chars in URL and within rest of the text - using a little escapeHtml utility function.

To tie it all together:

function bbCodeToHtml(input) {
  return tokensToHtml(tokenize(input));
}

It should be fairly obvious how to extend the tokensToHtml to support additional BBCode tags.

One missing feature is to automatically append close tags if one would input something like "[URL=google.com]bla bla". This is left as an exercise for the reader :)

\$\endgroup\$

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.