Skip to content

Instantly share code, notes, and snippets.

@daqing
Created February 21, 2009 09:12
Show Gist options
  • Select an option

  • Save daqing/67964 to your computer and use it in GitHub Desktop.

Select an option

Save daqing/67964 to your computer and use it in GitHub Desktop.
/*
* BBCode.js - javascript BBCode module by Zhang Qingcheng <kinch.zhang@gmail.com>
*
* usage:
*
* 1. load this module
* <script type="text/javascript" src="<?php echo get_domain_name_by_app('js', true);?>/c/BBCode.js"></script>
*
* 2. get an instance to use it
* <script type="text/javascript">
* var bbcode = new BBCode();
*
* if (! bbcode.isValid(bbcode))
* {
* alert('bbcode is invalid!');
*
* return false;
* }
* </script>
*
*/
(function(){
var BBCode = window.BBCode = function() {}
BBCode.prototype = {
/**
* check if the given bbcode is valid
*
* @param String bbcode
* @returns Boolean true/false
* @description this function returns true if the given bbcode in well-formed
*/
'isValid' : function(content) {
/*
* 检查一段内容中包含的UBB的格式是否正确
*
*/
if (content.length == 0)
return false;
var stack = [];
var depth = 0;
var errors = 0;
var unmatched = 0;
var options = {
'b' : 'no_arg',
'i' : 'no_arg',
'u' : 'no_arg',
'url' : 'opt_arg',
'img' : 'no_arg',
'swf' : 'no_arg',
'wmv' : 'no_arg',
'rm' : 'no_arg',
'media' : 'arg',
'size' : 'arg',
'quote' : 'no_arg',
'list' : 'opt_arg',
'color' : 'arg',
'dstaic' : 'no_arg',
'attach' : 'no_arg',
'align' : 'arg',
'p' : 'no_arg',
'font': 'arg',
'color' : 'arg',
'table' : 'opt_arg',
'tr' : 'opt_arg',
'td' : 'opt_arg',
'desc' : 'no_arg',
'hide' : 'no_arg',
'fly' : 'no_arg',
'code' : 'no_arg',
'email' : 'no_arg',
'*': 'opt_arg',
'tempfile': 'no_arg'
};
var strictRule = ['img', 'swf', 'wmv', 'media', 'rm', 'dstatic', 'email'];
// strip faces and br if any
if (content.indexOf('[face_') != -1)
content = content.replace(/\[face_[0-9]+\]/g, '');
if (content.indexOf('[br]') != -1)
content = content.replace(/\[br\]/g, '');
parseBBCode(content);
console.info('all errer count: ' + errors);
return (errors == 0) && (unmatched == 0);
function in_array(needle, haystack, isStrict)
{
var found = false, key;
for (key in haystack) {
if ((isStrict && haystack[key] === needle)
||
(!isStrict && haystack[key] == needle)
)
{
found = true;
break;
}
}
return found;
}
function hasQuotes(str)
{
return (str.indexOf('"') != -1) ||
(str.indexOf("'") != -1);
}
function checkParam(param)
{
console.log('checking param: -> ' + param);
if ((param[0] == '"' || param[0] == "'"))
{
var last = param.length - 1;
if (param[0] != param[last])
{
console.log('quote not match: (' + param[0]);
return 1;
}
else if (hasQuotes(param.substring(1, last - 1)))
{
console.log('extra quotes found');
return 1;
}
}
else if (hasQuotes(param))
{
return 1;
}
return 0;
}
function parseBBCode(content)
{
if (content.length == 0)
return true;
if (depth < 1000)
depth++;
else
throw "Max depth of 1000 recursion reached";
var head = content.indexOf('[');
var tail = content.indexOf(']');
if ( head == -1 && tail == -1)
// no bbcode
return true;
if ((head == -1 && tail > -1) ||
(head > -1 && tail == -1) ||
(head > -1 && tail > -1 && head > tail)
)
{
errors++;
console.info('error count: ' + errors + '::head and tail not match');
return;
}
tag = content.substring(head + 1, tail);
if (tag.length == 0) {
errors++;
console.info('error count: ' + errors + '::empty tag');
return;
}
if (tag[0] != '/') {
// open tag
console.info('enter opening tag: ' + tag);
// check paramters
var pos = tag.indexOf('=');
if (pos == -1) {
if (!options[tag]) {
// invalid tag
errors++;
console.info('error count: ' + errors + '::invalid tag name -> ' + tag);
return;
}
if (options[tag] == 'arg') {
errors++;
console.info('error count: ' + errors + '::tag -> ' + tag_name + ' should have arg');
return;
}
stack.push(tag);
} else {
var tag_name = tag.substring(0, pos);
if (!options[tag_name]) {
// invalid tag
errors++;
console.info('error count: ' + errors + '::invalid tag name -> ' + tag_name);
return;
}
if (options[tag_name] == 'no_arg') {
errors++;
console.info('error count: ' + errors + '::tag -> ' + tag_name + ' should have no arg');
return;
}
errors += checkParam(tag.substring(pos + 1));
if (errors > 0)
return;
stack.push(tag_name);
}
unmatched ++;
console.info('unmatch count: ' + unmatched);
} else {
// close tag
tag_name = tag.substring(1);
if (!options[tag_name]) {
// invalid tag
errors++;
console.info('error count: ' + errors + '::invalid tag name or empty tag -> ' + tag_name);
return;
}
console.info('enter close tag: ' + tag);
unmatched --;
console.info('unmatch count: ' + unmatched);
if (stack.length == 0) {
errors++;
console.info('error count: ' + errors + '::stack is empty, too many close tag');
return;
}
// check if the open tag and closing tag is match
open = stack.pop();
if (open != tag_name)
{
if (in_array(open, strictRule, true) || in_array(tag_name, strictRule, true))
{
errors++;
console.info('error count: ' + errors + '::have strict tag but not match -> open=' + open + ' and close=' + tag_name);
return;
}
}
}
parseBBCode(content.substring(tail + 1));
}
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment