Andre's Blog
Perfection is when there is nothing left to take away
BBCode parser

While most content management systems, such as blogs, allow users edit HTML directly, more specialized ones, such as discussion forums, allow users to use alternative syntax that is easier to control and adapt to particular needs. BBCode, which stands for Bulletin Board Code, is one example of such alternative.

BBCode tags are enclosed in square brackets instead of angle brackets used in HTML, which makes it easy to mix BBCode and HTML because square brackets have no significance in the latter.

In other words, if a forum page is being rendered, posts can be HTML-encoded first to avoid any HTML security issues and then BBCode tags may be converted to HTML using regular expressions. Any mismatched BBCode tags are either ignored or forced to close to generate well-formed HTML.

A typical approach to replacing BBCode tags is to use a set of regular expressions, one for each tag, similar to the one below, which replaces any sequence of [b] tags in the specified string variable with HTML equivalents:

text.replace( /\[b\](.+?)\[\/b]/gi, "<b>$1</b>" );

This regular expression guarantees that any incomplete tags missing either the start or the end tag will be ignored. It also will, however, replace any mismatched tags, such as these:

[b][i]abc[/b]def[/i]

, producing malformed HTML.

One way to control mismatched tags is to process start and end tags individually and maintain a parsing state, which can be used to detect malformed BBCode markup. The source code below is written in JavaScript and uses regular expressions to match three patterns and call the textToHtmlCB function every time a match is found. The function keeps all open tags in a stack and checks the top of the stack when closing tags.

// -----------------------------------------------------------------------
// Copyright (c) 2008, Stone Steps Inc. 
// All rights reserved
// http://www.stonesteps.ca/legal/bsd-license/
//
// This is a BBCode parser written in JavaScript. The parser is intended
// to demonstrate how to parse text containing BBCode tags in one pass 
// using regular expressions.
//
// The parser may be used as a backend component in ASP or in the browser, 
// after the text containing BBCode tags has been served to the client. 
//
// Following BBCode expressions are recognized:
//
// [b]bold[/b]
// [i]italic[/i]
// [u]underlined[/u]
// [s]strike-through[/s]
// [samp]sample[/samp]
//
// [color=red]red[/color]
// [color=#FF0000]red[/color]
// [size=1.2]1.2em[/size]
//
// [url]http://blogs.stonesteps.ca/showpost.asp?pid=33[/url]
// [url=http://blogs.stonesteps.ca/showpost.asp?pid=33][b]BBCode[/b] Parser[/url]
//
// [q=http://blogs.stonesteps.ca/showpost.asp?pid=33]inline quote[/q]
// [q]inline quote[/q]
// [blockquote=http://blogs.stonesteps.ca/showpost.asp?pid=33]block quote[/blockquote]
// [blockquote]block quote[/blockquote]
//
// [pre]formatted 
//     text[/pre]
// [code]if(a == b) 
//   print("done");[/code]
//
// text containing [noparse] [brackets][/noparse]
//
// -----------------------------------------------------------------------
var opentags;           // open tag stack
var crlf2br = true;     // convert CRLF to <br>?
var noparse = false;    // ignore BBCode tags?
var urlstart = -1;      // beginning of the URL if zero or greater (ignored if -1)

// aceptable BBcode tags, optionally prefixed with a slash
var tagname_re = /^\/?(?:b|i|u|pre|samp|code|colou?r|size|noparse|url|s|q|blockquote)$/;

// color names or hex color
var color_re = /^(:?black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua|#(?:[0-9a-f]{3})?[0-9a-f]{3})$/i;

// numbers
var number_re = /^[\\.0-9]{1,8}$/i;

// reserved, unreserved, escaped and alpha-numeric [RFC2396]
var uri_re = /^[-;\/\?:@&=\+\$,_\.!~\*'\(\)%0-9a-z]{1,512}$/i;

// main regular expression: CRLF, [tag=option], [tag] or [/tag]
var postfmt_re = /([\r\n])|(?:\[([a-z]{1,16})(?:=([^\x00-\x1F"'\(\)<>\[\]]{1,256}))?\])|(?:\[\/([a-z]{1,16})\])/ig;

// stack frame object
function taginfo_t(bbtag, etag)
{
   this.bbtag = bbtag;
   this.etag = etag;
}

// check if it's a valid BBCode tag
function isValidTag(str)
{
   if(!str || !str.length)
      return false;

   return tagname_re.test(str);
}

//
// m1 - CR or LF
// m2 - the tag of the [tag=option] expression
// m3 - the option of the [tag=option] expression
// m4 - the end tag of the [/tag] expression
//
function textToHtmlCB(mstr, m1, m2, m3, m4, offset, string)
{
   //
   // CR LF sequences
   //
   if(m1 && m1.length) {
      if(!crlf2br)
         return mstr;

      switch (m1) {
         case '\r':
            return "";
         case '\n':
            return "<br>";
      }
   }

   //
   // handle start tags
   //
   if(isValidTag(m2)) {
      // if in the noparse state, just echo the tag
      if(noparse)
         return "[" + m2 + "]";

      // ignore any tags if there's an open option-less [url] tag
      if(opentags.length && opentags[opentags.length-1].bbtag == "url" && urlstart >= 0)
         return "[" + m2 + "]";

      switch (m2) {
         case "code":
            opentags.push(new taginfo_t(m2, "</code></pre>"));
            crlf2br = false;
            return "<pre><code>";

         case "pre":
            opentags.push(new taginfo_t(m2, "</pre>"));
            crlf2br = false;
            return "<pre>";

         case "color":
         case "colour":
            if(!m3 || !color_re.test(m3))
               m3 = "inherit";
            opentags.push(new taginfo_t(m2, "</span>"));
            return "<span style=\"color: " + m3 + "\">";

         case "size":
            if(!m3 || !number_re.test(m3))
               m3 = "1";
            opentags.push(new taginfo_t(m2, "</span>"));
            return "<span style=\"font-size: " + Math.min(Math.max(m3, 0.7), 3) + "em\">";

         case "s":
            opentags.push(new taginfo_t(m2, "</span>"));
            return "<span style=\"text-decoration: line-through\">";

         case "noparse":
            noparse = true;
            return "";

         case "url":
            opentags.push(new taginfo_t(m2, "</a>"));
            
            // check if there's a valid option
            if(m3 && uri_re.test(m3)) {
               // if there is, output a complete start anchor tag
               urlstart = -1;
               return "<a href=\"" + m3 + "\">";
            }

            // otherwise, remember the URL offset 
            urlstart = mstr.length + offset;

            // and treat the text following [url] as a URL
            return "<a href=\"";

         case "q":
         case "blockquote":
            opentags.push(new taginfo_t(m2, "</" + m2 + ">"));
            return m3 && m3.length && uri_re.test(m3) ? "<" + m2 + " cite=\"" + m3 + "\">" : "<" + m2 + ">";

         default:
            // [samp], [b], [i] and [u] don't need special processing
            opentags.push(new taginfo_t(m2, "</" + m2 + ">"));
            return "<" + m2 + ">";
            
      }
   }

   //
   // process end tags
   //
   if(isValidTag(m4)) {
      if(noparse) {
         // if it's the closing noparse tag, flip the noparse state
         if(m4 == "noparse")  {
            noparse = false;
            return "";
         }
         
         // otherwise just output the original text
         return "[/" + m4 + "]";
      }
      
      // highlight mismatched end tags
      if(!opentags.length || opentags[opentags.length-1].bbtag != m4)
         return "<span style=\"color: red\">[/" + m4 + "]</span>";

      if(m4 == "url") {
         // if there was no option, use the content of the [url] tag
         if(urlstart > 0)
            return "\">" + string.substr(urlstart, offset-urlstart) + opentags.pop().etag;
         
         // otherwise just close the tag
         return opentags.pop().etag;
      }
      else if(m4 == "code" || m4 == "pre")
         crlf2br = true;

      // other tags require no special processing, just output the end tag
      return opentags.pop().etag;
   }

   return mstr;
}

//
// post must be HTML-encoded
//
function parseBBCode(post)
{
   var result, endtags, tag;

   // convert CRLF to <br> by default
   crlf2br = true;

   // create a new array for open tags
   if(opentags == null || opentags.length)
      opentags = new Array(0);

   // run the text through main regular expression matcher
   result = post.replace(postfmt_re, textToHtmlCB);

   // reset noparse, if it was unbalanced
   if(noparse)
      noparse = false;
   
   // if there are any unbalanced tags, make sure to close them
   if(opentags.length) {
      endtags = new String();
      
      // if there's an open [url] at the top, close it
      if(opentags[opentags.length-1].bbtag == "url") {
         opentags.pop();
         endtags += "\">" + post.substr(urlstart, post.length-urlstart) + "</a>";
      }
      
      // close remaining open tags
      while(opentags.length)
         endtags += opentags.pop().etag;
   }

   return endtags ? result + endtags : result;
}

The HTML below can be used to see the parser in action. Save the parser in a file called bbcode.js and save the HTML in another file in the same directory.

<html>
<head>
<title>BBCode Test</title>
<script type="text/javascript" src="bbcode.js"></script>
<script type="text/javascript">
function outputBBCode(textarea)
{
   var out = document.getElementById("out");
   var out_html = document.getElementById("out_html");
   var html = parseBBCode(textarea.value);
   
   if(!out.firstChild)
      out.appendChild(document.createTextNode(html));
   else   
      out.replaceChild(document.createTextNode(html), out.firstChild);
      
   out_html.innerHTML = html;
}
</script>
</head>
<body>
<textarea id="in" rows="12" cols="80">[b]bold[/b], [i]italic[/i], [u]underlined[/u], [s]strike-through[/s], [samp]sample[/samp] 
[url]http://blogs.stonesteps.ca/showpost.asp?pid=33[/url]
[url=http://blogs.stonesteps.ca/showpost.asp?pid=33][i]BBCode[/i] Parser[/url]
Inline [q=http://blogs.stonesteps.ca/showpost.asp?pid=33]quote[/q]
[blockquote=http://blogs.stonesteps.ca/showpost.asp?pid=33]Block quote[/blockquote][pre]formatted 
     text[/pre][code]if(a == b) 
   print("done");[/code]text containing [noparse] [brackets] [/noparse]
c[b][color=red]o[/color][/b][b][color=green]l[/color][/b][b][color=blue]o[/color][/b]rs and [size=1.2]text size[/size]
[b][i]mismatched [/b] tags[/i] 
remaining text should not affect page HTML.
</textarea>
<div>
<input type="submit" value="Submit" onclick="outputBBCode(document.getElementById(&quot;in&quot;))" style="vertical-align: top">
</div>
<div id="out_html" style="border: 1px solid #777; margin: 1em auto; padding: 5px 3px;">&nbsp;</div>
<p>This paragraph should not be formatted in any way after 
BBCode is converted to HTML, even if there are mismatched 
or mixed BBCode tags.</p>
<div id="out" style="border: 1px solid #777; margin: 1em auto; padding: 5px 3px;">&nbsp;</div>
</body>
</html>

The parser may be used with ASP, as long as the script language is identified as JScript. Alternatively, it can be used as a client script to convert BBCode tags to HTML directly in the browser.

Posted Thu Jun 30 07:11:06 EDT 2011 by fduch

url=foo.bar#id doesn't work. The following patch fixes it:

--- bbcode.js.orig 2011-06-30 14:54:18.000000000 +0400
+++ bbcode.js 2011-06-30 15:08:48.000000000 +0400
@@ -53,7 +53,7 @@
var number_re = /^[\\.0-9]{1,8}$/i;

// reserved, unreserved, escaped and alpha-numeric [RFC2396]
-var uri_re = /^[-;\/\?:@&=\+\$,_\.!~\*'\(\)%0-9a-z]{1,512}$/i;
+var uri_re = /^[-;\/\?:@&=\+\$,_\.!~\*'\(\)%0-9a-z#]{1,512}$/i;

// main regular expression: CRLF, [tag=option], [tag] or [/tag]
var postfmt_re = /([\r\n])|(?:\[([a-z]{1,16})(?:=([^\x00-\x1F"'\(\)<>\[\]]{1,256}))?\])|(?:\[\/([a-z]{1,16})\])/ig;
 

Posted Mon Jan 23 00:00:48 EST 2012 by Masonchop
http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/
Posted Mon Jan 23 00:41:41 EST 2012 by OmaomegaAttaday
http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/
Posted Mon Jan 23 04:35:48 EST 2012 by Petrinaexonna
http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/ http://translate.google.ru/1/
Posted Mon Jan 23 05:16:35 EST 2012 by Augmerolerm
I'm lonely tonight, come check out my profile http://www.localfunsex.com/
Posted Mon Jan 23 10:53:26 EST 2012 by Invegrave
No other program I am aware of takes your belief system, the most important ingredient in the successful quit smoking equation, into consideration - hence the staggering failure rate of theirs and the 90% success of ours! In addition, it promotes proper development of nerve cells and helps your cells metabolize protein, carbohydrate and fat. Fortunately, it is starting to get easier to find these dangerous trans fats -- and avoid them. Because human beings get their most efficient fuel, or energy, from carbohydrates, unrefined plant sources, if enough unrefined carbohydrates are not consumed, you will, among many other problems, feel tired. (If it's not to long, you may want to look into getting a used wheel chair or even wheel chair rental.) With that information, he or she can better help you pick out the model and brand perfect for you.
Posted Mon Jan 23 15:28:00 EST 2012 by ontopunse
Меня зовут Кристина. мне сейчас 26. А история как я похудела вот такая: В 24 года я родила и поправилась на 20кг. При моем то росте 167см я стала весить око 70кг. Это было жутко для меня. Думала как похудеть. Испробовала целую кучу методов, но особого результата так и не добилась. Мало того еще и эти "кг" возвращались. Успокайвала себя что после родов, бывает, но скоро я снова буду стройной. Да уж и это скоро наступило чут ли не через 2 года. Как обычно искала все в интернете и наткнулась на сайт по диетам. Все там внимательно почитала. Подумала и решила пройтиа какойто там тест. Вопросы по теме веса, их там не много. Я подумала зачем они мне все это спрашивают. Потом оказалось что этот тест нужен для того чтобы подобрать диету под мои потребности. Ну я и захотела 20 кг сбросить. Целый месяц я следовала тому что мне там дали. И что вы думаете? Я в конце месяца с дрожью достаю из-под кровати весы и встаю на них. Смотрю и на цифру и молча так иду и сажусь на кровать. Из меня вырвалось такое громкое "ЯХУУУ" что разбудила и ребенка и мужа. Минус 12 кг. При том что я себя не переутруждала и чувстую себя теперь отлично. Если кому стало интересно и вы тоже хотите получить диету то мой вам совет, заходите вот сюда: http://tvoya-dieta.tk
Posted Mon Jan 23 21:19:36 EST 2012 by Queefeanaerty
Как нужно узнать по этой теме? - Нужно для добротных своих сайтов акцентировать внимание данной аудитории, имею достаточно денег, готов уточнить условия расположения своей ccылочки на данном форуме. Свяжитесь по эектронной почте, в случае интереса - mailto:bestseo@bobmail.info
Posted Mon Jan 23 22:48:01 EST 2012 by Reodetthupt
Eventuell haben die Politiker doch wahr, und die Katastrophe beginnt in kurzer Zeit. Dann ist der Nutznießer dieses Armbanduhr gut dran. Im postapokalyptischen Unfall informiertverständigt der Chronographen PM1208 Wrist Gamma Indicator zuverlässig vor Strahlung. Eigentlich ist die Uhr aber für Leute gedacht, die oft mit Strahlung in Kontakt kommen können. <img>http://www.polimaster.ch/radiation/media/images/bluetech-info/PM1208M_Dosimeter.jpg</img> Spaß beiseite: Die PM1208 Wrist Gamma Indicator ist eine relativ einzigartige Uhr, die fortlaufend die Umgebungsstrahlung misst, und ab einem gefährlichen Level warnt. Bis zu 500 Datensätze zu Strahlung und Zeit kann die Uhr speichern. Diese Daten können per Infrarot-Anschluss auf einen Computer übertragen werden. Im Inneren schlägt eine RONDA 763-Mechanik mit Quartz, außen ist ein solides Stahlgehäuse verbaut. Bis 100 Meter ist die Uhr wasserdicht. Dazu gibt es einen beleuchteten Hintergrund, weil soweit unter der Wasseroberfläche das Licht auch knapp wird. Gedacht ist die ungefähr 1500 sfr günstige Uhr für alle, die berufsbedingt mit Strahlung in Kontakt kommen könnten – Piloten, Dauerflieger, Angestellte in Instituten und besorgte Privatpersonen in riskanten Gebieten. variant3
Posted Tue Jan 24 12:21:35 EST 2012 by Poker Gratis
:), I loved this project in this page, you are contributing with great information! This web page is much helpfull! My name is Patricia, I Live on Berlin, and I will be a fan of this web page, my personal details may not be in the best interest of everyone but I will tell them off course I like swimming as well as tv shows, and I also listen a lot The Cure on my bedroom, I´m single at the moment so male users....Just flirting with you guys lol :)! I already tried online dating it didn´t worked out very well.... I wrote this comment because as I previously said I really enjoy this web site I also have a board just as you, but mine is many different from this, it is about no deposit poker bonus....:) I will also apologize by my writting it is the only way I get to talk with you....Good morning to everybody, Bye bye
Posted Tue Jan 24 12:40:39 EST 2012 by SnosseHeext
Конструктор диет http://vasha-dieta.tk
Posted Tue Jan 24 14:33:50 EST 2012 by camymnoralm
Добрый день! Думаю приобрести шпильки резьбовые . Кто может что-нибуть посоветовать? Какая фирма собирает надежные шпильки? Цены кажется терпимые, но я в этом не спец, поэтому если видели дешевле, подскажите где. Где можно найти хорошее соотношение цена-качество?
Posted Tue Jan 24 21:29:40 EST 2012 by pHLMEZJ7
LThHql http://www.RUWE5gOde94HqsfDYIh3uBfJfSMdiDSG.com
Posted Wed Jan 25 15:41:51 EST 2012 by Vmdwtwys
I saw your advert in the paper
Posted Wed Jan 25 18:01:10 EST 2012 by scoobiatt
zithromax to buy
Posted Wed Jan 25 23:55:46 EST 2012 by scallCilalork
Соберем для вас базы мобильных номеров для рассылки смс, звонков, спама... нам пофиг, как вы будете их использовать. Тематики для выборки: Купля/продажа Знакомства Недвижимость Транспорт (Авто) Услуги Общество Бизнес Работа Спарсим любой регион России, а так же Украина, Беларусь и еще пара стран. Стоимость 1000 номеров = 1$ При больших заказах скидки. 1.Базы постоянно обновляются. 2.Конфиденциальность подробнее на http://xpymep.16mb.com или в аське 9079009
Posted Thu Jan 26 06:59:36 EST 2012 by ExtedsDed
Этот топик просто бесподобен :), мне интересно . http://www.tips2sports.com - сегодня футбол зенит
Posted Thu Jan 26 13:18:31 EST 2012 by dyenErete
Hello. And Bye.
Posted Thu Jan 26 14:25:20 EST 2012 by fafaloche
http://dajlajka.pl - kwejk
Name:

Comment: