
function BBcodeParser (text, settings, extraSettings)
{
  var options = {
      allowedTags: ['b','i','u','s','font','code','quote','list','\\*','img','url','email','size','color','align','table','tr','td','th','row','cell'],
      forbiddenTags: null
  };
  $.extend(options, settings, extraSettings);
  
          var aligns= {'t': 'top', 'm': 'middle', 'b': 'bottom','l': 'left', 'c': 'center', 'r': 'right'};
            var formats= {'b': 'font-weight: bold;', 'i': 'font-style: italic;', 'u': 'text-decoration: underline;','s': 'text-decoration: line-through;'};
          
   var allowedTags= options.allowedTags;
   if (options.forbiddenTags)
   {
     allowedTags= [];
     var forbiddenTags= new RegExp('^('+(typeof(options.forbiddenTags)=='object'? options.forbiddenTags.join('|'): options.forbiddenTags)+')$', 'i');
     for (i= 0; i<options.allowedTags.length; i++)
     {
       if (!options.allowedTags[i].match(forbiddenTags)) allowedTags.push(options.allowedTags[i]);
     }     
   }
   allowedTags= allowedTags instanceof RegExp? allowedTags: new RegExp('^('+(typeof(allowedTags)=='object'? allowedTags.join('|'): allowedTags)+')$', 'i');
   console.log('BBcodeParser '+allowedTags);
   
  var pos= 0, posOpen, posClose, html= "", bbcode= text;
  var i= 0;
  
  this.buildBlock= function (text, tag, param)
  {
      
      //console.log(" * "+ tag +" "+allowedTags);
      
    if (!tag.match(allowedTags))
      return text.replace(/\n/g,"<br />");
      //console.log("** "+ tag);

    switch (tag)
    {
      case 'b':
      case 'i':
      case 'u':
      case 's':
      case 'code':
        return '<'+tag+'>'+text.replace(/\n/g,"<br />")+'</'+tag+'>';
        
      case 'table':
        var table= '<table';
        if (param)
        {
          var params= param.split(',');
          if (params[0] && params[0]!='auto') table += ' width="'+params[0]+'"';
          if (params[1] && params[1]!='auto') table += ' height="'+params[1]+'"';
        }
        table += '>'+text.replace(/\n/g,"")+'</table>';
        return table;
        /*
      case 'cols':
        return '<colgroup>'+text.replace(/\n/g,"")+'</colgroup>';
        
      case 'col':
        //return '<col'+(param && param!='auto'? ' width="'+param+'"': '')+' />';
        return '<col'+(text && text!='auto'? ' width="'+text.replace(/\n/g,"")+'"': '')+' />';*/
        
      case 'row':
      case 'tr':
        var row= '<tr';
        if (param)
        {
          var params= param.split(',');
          if (params[0] && params[0]!='auto')
          {
            row += ' height="'+params[0]+'"';
          }
          if (params[1] && params[1]!='')
          {
            row += ' style="';
            for (i= 0; i<params[1].length; i++) row += formats[params[1][i]];
            row += '"';
          }
        }
        row+='>'+text.replace(/\n/g,"<br />")+'</tr>';
        return row;
        
      case 'cell':
        var cell= '<td';
        if (param)
        {
          var params= param.split(',');
          if (params[0])
            cell += ' width="'+params[0]+'"';
          if (params[1])
            cell += ' valign="'+aligns[params[1][0]]+'" align="'+aligns[params[1][1]]+'"';
          if (params[2])
            cell += ' colspan="'+params[2]+'"';
          if (params[3])
            cell += ' rowspan="'+params[3]+'"';
          if (params[4] && params[4]!='')
          {
            cell += ' style="';
            for (i= 0; i<params[3].length; i++) cell += formats[params[3][i]];
            cell += '"';
          }
        }
        cell+='>'+text.replace(/\n/g,"<br />")+'</td>';
        return cell;
        
      case 'td':
      case 'th':
        var cell= '<'+tag;
        if (param)
        {
          var params= param.split(',');
          if (params[0])
            cell += ' width="'+params[0]+'"';
          if (params[1])
            cell += ' valign="'+aligns[params[1][0]]+'" align="'+aligns[params[1][1]]+'"';
          if (params[2])
            cell += ' colspan="'+params[2]+'"';
          if (params[3])
            cell += ' rowspan="'+params[3]+'"';
          if (params[4] && params[4]!='')
          {
            cell += ' style="';
            for (i= 0; i<params[3].length; i++) cell += formats[params[3][i]];
            cell += '"';
          }
        }
        cell+='>'+text.replace(/\n/g,"<br />")+'</'+tag+'>';
        return cell;
        
      case 'quote':
        return '<blockquote>'+text.replace(/\n/g,"<br />")+'</blockquote>';
        
      case '*':
        return '<li>'+text.replace(/\n/g,"<br />")+'</li>';
        
      case 'list':
        return param? 
        (param=='1'||param=='A'||param=='a'||param=='I'||param=='i'?'<ol type="'+param+'">'+text.replace(/\n/g,"")+'</ol>':'<ul type="'+param+'">'+text.replace(/\n/g,"")+'</ul>'): 
        '<ul type="disc">'+text.replace(/\n/g,"")+'</ul>';
        
      case 'img':        
          return '<img src="'+text+'"'+(param && 1<(size= param.split(',')).length? ' width="'+parseInt(size[0])+'" height="'+parseInt(size[1])+'"':'')+' border="0" />';

      case 'url':
        return '<a href="'+(param? param: text)+'">'+text.replace(/\n/g, "<br />")+'</a>';
        
      case 'email':
        return '<a href="mailto:'+(param? param: text)+'">'+text.replace(/\n/g,"<br />")+'</a>';
        
      case 'font':
        var bb_font= '<font';
        if (param)
        {
          var params= param.split(',');
          if (params[0] && params[0]!='')
          {
            bb_font += ' style="';
            for (i= 0; i<params[0].length; i++) bb_font += formats[params[0][i]];
            bb_font += '"';
          }
          if (params[1] && params[1]!='')
            bb_font += ' color="'+params[1]+'"';
          if (params[2] && params[2]!='')
            bb_font += ' size="'+params[2]+'"';
        }
        bb_font+='>'+text.replace(/\n/g,"<br />")+'</font>';
        return bb_font;
        
      case 'size':
      param= parseInt(param);
        return isNaN(param) || param==3? text.replace(/\n/g, "<br />"): '<font size="'+(param? parseInt(param): 3)+'">'+text.replace(/\n/g, "<br />")+'</font>';
        
      case 'color':
        return '<font color="'+(param? param: 'green')+'">'+text.replace(/\n/g, "<br />")+'</font>';
        
      case 'align':
        return '<p style="text-align:'+(param? param: 'left')+';">'+text.replace(/\n/g, "<br />")+'</p>';
        
      default:
        return text.replace(/\n/g,"<br />");
    }
  };
  
  this.parse= function (parent)
  {   
    var result= '', tagName= '', posParam, param;
    do
    {    
      posOpen=text.indexOf('[',pos); 
      // found no more tag starts?
      if (posOpen==-1)
      {
        return result + (parent?
        // close last tag
        this.buildBlock (text.substring (pos), tagName, null):
        // append reamining text
        text.substring (pos).replace (/\n/g, "<br />"));
      }
    
      // overread empty tags
      if (pos<posOpen) result += parent? text.substring (pos,posOpen):  text.substring (pos,posOpen).replace (/\n/g, "<br />");
      pos= posOpen+1;
    
      posClose=text.indexOf (']',pos); 
      // found no more tag ends?
      if (posClose==-1)
      {
        return result + (parent?
        // close last tag
        this.buildBlock (text.substring(pos),tagName,null):
        // append reamining text
        text.substring(pos).replace (/\n/g, "<br />"));
      }
    
      tagName= text.substring(pos,posClose);
      pos= posClose+1;
      
      param= null;
      // closing tag?
      if (tagName.charAt(0)=='/')
      {
        tagName= tagName.substring(1);
        if (!parent||parent==tagName)
        {
      //console.log("<"+ parent +"> "+ tagName +" / ");
          return result;
        }
        else
        {
      //console.log("<"+ parent +"> "+ tagName +" / ?? ");
          return this.buildBlock (result,tagName,null);
        }
      }/*
      else if (parent && parent==tagName)
      {
      console.log("<"+ parent +"> "+ tagName +" ???? ");
          return this.buildBlock (result,tagName,null);
      }*/
      // opening tag with params?
      else if ((posParam= tagName.indexOf('='))!=-1)
      {
        param= tagName.substring(posParam+1);
        tagName= tagName.substring(0,posParam);
      }
      
      //console.log("<"+ parent +"> "+ tagName);
      var bbcodeMatch= tagName.match(/(b|i|u|s|font|code|quote|list|\*|img|url|email|size|color|align|table|tr|th|td|row|cell)/i);
      
      // opening tag if it is a tag
      result += bbcodeMatch && bbcodeMatch.length? this.buildBlock(this.parse (tagName), tagName, param): text.substring(posOpen,posClose+1);
    
    } while (pos<text.length);
    return result;
  };
  html= this.parse (null);
  this.getHTML= function () { return html; };
  this.getText= function () { return bbcode; };
}

function bbcode2html(text, allowedTags)
{   
  var parser= new BBcodeParser (text, {'allowedTags': allowedTags});
  
  //console.log('bbcode2html');
  //console.log(text);
  var html= parser.getHTML();
  //console.log(html);
  return html;
}

function bbcode2text(text)
{   
  var parser= new BBcodeParser (text, {'allowedTags': allowedTags});
  return parser.getText();
}
cnt = 0;
var listCounter= 0;
var defaultListType= ['disc', 'circle', 'square'];
var enumCounter= 0;
var defaultEnumType= ['1', 'a', 'i'];

function dom2text(domObj)
{
  var text= "";
  
  if (domObj)
  {
  
    // Text-Node ?
    if (domObj.nodeType==3)
    {
      text= domObj.nodeValue.replace(/\n/g, '');//$.trim(domObj.nodeValue);
    }
    // Element
    else if (domObj.nodeType==1)
    {
      
      for (var i in domObj.childNodes)
      {
        text += dom2text(domObj.childNodes[i]);
      }
    
      switch (domObj.nodeName.toLowerCase())
      {        
        case 'br':
          text= '\n';
          break;
          
        default:
          //bbcode= '[?'+(domObj.nodeName||'')+'?]'+bbcode+'[/?'+(domObj.nodeName||'')+'?]';
          break;
      }
    }
  }
  return text;
}

( function($) {
 
 $.fn.bbcode = function(settings, extraSettings)
  {
    var options = $.extend({
      allowedTags: ['b','i','u','s','code','quote','list','\\*','img','url','email','size','color','align','table','tr','td','th','row','cell'],
      forbiddenTags: null,
      marginLeft: 0,
      trim: false
    }, settings, extraSettings);
    
   var bbcode= "";
   var listCounter= 0;
   var defaultListType= ['disc', 'circle', 'square'];
   var enumCounter= 0;
   var defaultEnumType= ['1', 'a', 'i'];
   var allowedTags= options.allowedTags;
   if (options.forbiddenTags)
   {
     allowedTags= [];
     var forbiddenTags= options.forbiddenTags instanceof RegExp? options.forbiddenTags: new RegExp('^('+(typeof(options.forbiddenTags)=='object'? options.forbiddenTags.join('|'): options.forbiddenTags)+')$', 'i');
     for (i= 0; i<options.allowedTags.length; i++)
     {
       if (!options.allowedTags[i].match(forbiddenTags)) allowedTags.push(options.allowedTags[i]);
     }     
   }
   allowedTags= options.allowedTags instanceof RegExp? options.allowedTags: new RegExp('^('+(typeof(allowedTags)=='object'? allowedTags.join('|'): allowedTags)+')$', 'i');
  
   $(this).each(function(i)
   {
    var node= $(this);
    // Text-Node ?
    if (this.nodeType==3)//TEXT_NODE)
    {
      bbcode= this.nodeValue.replace(/[\n\r\t ]+/g, ' ');//$.trim(this.nodeValue);
    }
    else if (this.nodeType==8)//COMMENT_NODE)
    {
    }
    // Element
    else if (this.nodeType==1)//ELEMENT_NODE)
    {
      var color= null;
      var align= align= node.attr('align') || node.css('text-align');
      var valign= valign= node.attr('valign') || node.css('vertical-align');
      var width= $(node).width() || node.attr('width') || node.css('width');
      var height= $(node).height() || node.attr('height') || node.css('height');
      
      var format_inline= '';
      var format_begin= ''; 
      var format_end= ''; 
      
      if (this.nodeName.toLowerCase() == 'p' &&
      (node.hasClass('MsoListParagraphCxSpFirst') || node.hasClass('MsoListParagraphCxSpMiddle') || node.hasClass('MsoListParagraphCxSpLast')))
      {      
        if (typeof(options.marginLeft)!='object') options.marginLeft = [options.marginLeft||0];
        var marginLeft= parseInt(this.style.marginLeft||node.css('margin-left'));
        var marginLeftLast= parseInt(options.marginLeft[options.marginLeft.length-1]);       
        
        var symbol= '';
        var i= 0;
        while (i<this.childNodes.length-1)
        {
          symbol += $(this.childNodes[i++]).bbcode(options, {'allowedTags': allowedTags, 'marginLeft': options.marginLeft, 'trim': true});          
        }  
        bbcode += $(this.childNodes[i]).bbcode(options, {'allowedTags': allowedTags, 'marginLeft': options.marginLeft, 'trim': true}); 
        symbol_= symbol;  
        if (symbol.match(/^[aAiI1]/gi)) symbol= symbol.charAt(0);
        else if (symbol.match(/^\u006f/gi)) symbol= 'circle';
        else if (symbol.match(/^\u00b7/gi)) symbol= 'disc';
        else if (symbol.match(/^[\u00a7\u00fc\u00d8]/gi)) symbol= 'square';
        else symbol= '1';
        
        var marginDiff= marginLeftLast - marginLeft;
        
        var listIndent= '';
        if (marginDiff<0)
        {
          options.marginLeft.push(marginLeft);
          listIndent= '[list='+symbol+']';
        }
        else
        {        
          while((marginDiff= marginLeftLast - marginLeft)>0)
          {
            options.marginLeft.pop(); 
            listIndent += '[/list]';
            var marginLeftLast= parseInt(options.marginLeft[options.marginLeft.length-1]);   
          }
        }
        
        settings.marginLeft=options.marginLeft;
        
        if (bbcode.length>0)
        {
          if (node.hasClass('MsoListParagraphCxSpFirst'))
          {
            if ('list'.match(allowedTags)) bbcode= '[list='+symbol+'][*]'+format_begin+$.trim(bbcode)+format_end+'[/*]';  
          }
          else if (node.hasClass('MsoListParagraphCxSpMiddle'))
          {
            if ('list'.match(allowedTags))
            {
              bbcode= listIndent+'[*]'+format_begin+$.trim(bbcode)+format_end+'[/*]';  
            }
          }
          else if (node.hasClass('MsoListParagraphCxSpLast'))
          {
            if ('list'.match(allowedTags)) bbcode= listIndent+'[*]'+format_begin+$.trim(bbcode)+format_end+'[/*][/list]';  
          }
          else
          {
            if ('align'.match(allowedTags)) bbcode= '[align='+(align||'left')+']'+format_begin+$.trim(bbcode)+format_end+'[/align]';  
          }
        }
      }
      else
      {
      
      if (this.nodeName.toLowerCase() == 'ul')
        listCounter++;
      
      else if (this.nodeName.toLowerCase() == 'ol')
        enumCounter++;
      
      for (var i; i<this.childNodes.length; i++) bbcode += $(this.childNodes[i]).bbcode(options, {'allowedTags': allowedTags});
      
      if (options.trim) bbcode= $.trim(bbcode);
    
      if (bbcode.length>0)
      {
        if (this.style.fontStyle=='italic')
        {
          if ('i'.match(allowedTags)) { format_inline += 'i'; format_begin += '[i]'; format_end= '[/i]'+format_end; }
        }
        if (this.style.fontWeight=='bold')
        {
          if ('b'.match(allowedTags)) { format_inline += 'b'; format_begin += '[b]'; format_end= '[/b]'+format_end; }
        }
        if (this.style.textDecoration=='underline')
        {
          if ('u'.match(allowedTags)) { format_inline += 'u'; format_begin += '[u]'; format_end= '[/u]'+format_end; }
        }
        else if (this.style.textDecoration=='line-through')
        {
          if ('s'.match(allowedTags)) { format_inline += 's'; format_begin += '[s]'; format_end= '[/s]'+format_end; }
        }
        if ((color= this.style.color||node.attr('color')) && 'color'.match(allowedTags))
        {
          if (color.match(/.*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/ig))
          {
            var r= new Number(RegExp.$1);
            var g= new Number(RegExp.$2);
            var b= new Number(RegExp.$3);
            color= '#'+r.toString(16)+g.toString(16)+b.toString(16);
          }    
          format_begin += '[color='+color+']'; 
          format_end= '[/color]'+format_end;
        }
      }
    
      switch (this.nodeName.toLowerCase())
      {        
        case 'br':
          bbcode= '\n';
          break;
        
        case 's':
        case 'strike':
          if ('s'.match(allowedTags))
          {
            bbcode= '[s]'+format_begin+bbcode+format_end+'[/s]';
          }
          break;
        
        case 'b':
        case 'strong':
          if ('b'.match(allowedTags))
          {          
            bbcode= '[b]'+format_begin+bbcode+format_end+'[/b]';
          }
          break;
        
        case 'i':
        case 'em':
          if ('i'.match(allowedTags))
          {
            bbcode= '[i]'+format_begin+bbcode+format_end+'[/i]';
          }
          break;
        
        case 'u':
          if ('u'.match(allowedTags))
          {
            bbcode= '[u]'+format_begin+bbcode+format_end+'[/u]';
          }
          break;
        
        case 'code':
          if ('code'.match(allowedTags))
          {
            bbcode= '[code]'+format_begin+bbcode+format_end+'[/code]';
          }
          break;
        
        case 'blockquote':
          if ('quote'.match(allowedTags))
          {
            bbcode= '[quote]'+format_begin+bbcode+format_end+'[/quote]';
          }
          break;        
          
        case 'table':
          if ('table'.match(allowedTags))
          {
          /*
            if ($(this).find('colgroup').size()==0)
            {
              var colGroup= $('<colgroup></colgroup>');
              var colWidth= [];
              $(this).find('tr').each(function(r)
              {
                var c= 0;
                $(this).find('td,th').each(function(i)
                {
                  if (!colWidth[i]) colWidth[i]= $(this).width();
                  c += this.colSpan||1;
                });
              });
              for (c= 0; c<colWidth.length; c++) $('<col width="'+colWidth[c]+'" />').appendTo(colGroup);
              colGroup.prependTo($(this));
            }
          */
            var params= '';
            if (width && width!='auto') params += width;
            if (height && height!='auto') params += ','+height;
            bbcode= '[table'+(params!=''? '='+params:'')+']'+bbcode+'[/table]';
          }
          break; 
          /*
        case 'colgroup':
          if ('cols'.match(allowedTags)) bbcode= '[cols]'+bbcode+'[/cols]';
          break; 
          
        case 'col':
          if ('col'.match(allowedTags)) bbcode= '[col]'+(width && width!='auto'? width: '')+'[/col]';
          break;
        */
        case 'td':
        case 'th':
          if (this.nodeName.toLowerCase().match(allowedTags)) 
          {
            var aligns= {'left': 'l', 'center': 'c', 'right': 'r', 'top': 't', 'middle': 'm', 'bottom': 'b'};
            var bb_align= aligns[align || 'left'];
            var bb_valign= aligns[valign || 'middle'];
            var colspan= node.attr('colspan')||1;
            var rowspan= node.attr('rowspan')||1;
            var param= width=='auto'? '': width;
            if (bb_align!='l' || bb_valign!='m' || colspan>1 || rowspan>1 || format_inline!='') param += ','+(bb_valign&&bb_valign? bb_valign+bb_align:'')+','+colspan+','+rowspan+','+format_inline;
            bbcode= '['+this.nodeName.toLowerCase()+(param!=''? '='+param:'')+']'+bbcode+'[/'+this.nodeName.toLowerCase()+']';
          }
          break;
        
        case 'tr':
          if ('tr'.match(allowedTags))
          {
            var param= '';
            if (height && height!='auto') param= height;
            if (format_inline!='') param += ','+format_inline;
            bbcode= '[tr'+(param!=''? '='+param:'')+']'+bbcode+'[/tr]\n';
          }
          break;
        
        case 'ul':      
          if ('list'.match(allowedTags)) 
          {
            listCounter--;
            bbcode= '[list='+(node.attr('type')||defaultListType[listCounter])+']\n'+bbcode+'[/list]';
          }
          break;
        
        case 'ol':
          if ('list'.match(allowedTags)) 
          {
            enumCounter--;
            bbcode= '[list='+(node.attr('type')||defaultEnumType[enumCounter])+']\n'+bbcode+'[/list]';
          }
          break;
        
        case 'li':
          if ('*'.match(allowedTags))
          {
            bbcode= '[*]'+format_begin+$.trim(bbcode)+format_end+'[/*]\n';
          }
          break;
        
        case 'img':
          if ('img'.match(allowedTags)) 
          {
          if (this.style.width && this.style.height)
            bbcode= '[img='+this.style.width+','+this.style.height+']'+(node.attr('src')||'')+'[/img]';
          if (this.style.cssText.match(/.*width:\s*(\d+).*height:\s*(\d+)/ig))
            bbcode= '[img='+RegExp.$1+','+RegExp.$2+']'+(node.attr('src')||'')+'[/img]';
          else
            bbcode= '[img]'+(node.attr('src')||'')+'[/img]';
          }
          break;
        
        case 'font':       
          var size= parseInt(node.attr('size'));
          if (isNaN(size) || size<1) size= 3;
          if (format_inline!='' || 
          size!=3 && 'size'.match(allowedTags) && color && 'color'.match(allowedTags))
          {          
            bbcode= '[font='+format_inline+','+(color||'')+','+size+']'+bbcode+'[/font]';
          }
          else if (size!=3 && 'size'.match(allowedTags))
          {
            bbcode= '[size='+size+']'+bbcode+'[/size]';
          }
          else if (color && 'color'.match(allowedTags))
          {
            bbcode= '[color='+color+']'+bbcode+'[/color]';
          }
          break;
        
        case 'a':            
          if (node.attr('href'))
          {
            if (node.attr('href').match(/mailto:(([a-z0-9&\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)?[\w]+))/ig))
            { if ('email'.match(allowedTags))  bbcode= '[email='+RegExp.$1+']'+format_begin+bbcode+format_end+'[/email]'; }
            else if (node.attr('href').match(/([\w]+):\/\/([^ \"\n\r\t<]+)/ig))
            { if ('url'.match(allowedTags))  bbcode= '[url='+RegExp.$1+'://'+RegExp.$2+']'+format_begin+bbcode+format_end+'[/url]'; }
            else if (node.attr('href').match(/([^ \"\n\r\t<]+)/ig))
            { if ('url'.match(allowedTags))  bbcode= '[url=http://'+RegExp.$1+']'+format_begin+bbcode+format_end+'[/url]'; }
          }
          break;
        
        case 'style':
        case 'script':
            bbcode= '';
          break;

        default:
            var param= format_inline;
            if (color) param += ','+color;
            bbcode= param!=''? '[font='+param+']'+bbcode+'[/font]': bbcode;  
          break;
      }
      }
    }
  });
  return bbcode;
}

})(jQuery);