if(!WYM_STRINGS) var WYM_STRINGS = new Array();

var WYM_STRINGS_EN = {
    Strong:           'Strong',
    Emphasis:         'Emphasis',
    Superscript:      'Superscript',
    Subscript:        'Subscript',
    Ordered_List:     'Ordered List',
    Unordered_List:   'Unordered List',
    Indent:           'Indent',
    Outdent:          'Outdent',
    Undo:             'Undo',
    Redo:             'Redo',
    Link:             'Link',
    Unlink:           'Unlink',
    Image:            'Image',
    Table:            'Table',
    HTML:             'HTML',
    Paragraph:        'Paragraph',
    Heading_1:        'Heading 1',
    Heading_2:        'Heading 2',
    Heading_3:        'Heading 3',
    Heading_4:        'Heading 4',
    Heading_5:        'Heading 5',
    Heading_6:        'Heading 6',
    Preformatted:     'Preformatted',
    Blockquote:       'Blockquote',
    Table_Header:     'Table Header',
    URL:              'URL',
    Title:            'Title',
    Alternative_Text: 'Alternative text',
    Caption:          'Caption',
    Number_Of_Rows:   'Number of rows',
    Number_Of_Cols:   'Number of cols',
    Submit:           'Submit',
    Cancel:           'Cancel',
    Choose:           'Choose',
    Preview:          'Preview',
    Paste_From_Word:  'Paste from Word',
    Tools:            'Tools',
    Containers:       'Containers',
    Classes:          'Classes',
    Status:           'Status',
    Source_Code:      'Source code'
};

WYM_STRINGS['en'] = WYM_STRINGS_EN;


/********** CONSTANTS **********/

    
    
    var WYM_INSTANCES        = new Array();
    var WYM_NAME             = "name";
    var WYM_INDEX            = "{Wym_Index}";
    var WYM_BASE_PATH        = "{Wym_Base_Path}";
    var WYM_CSS_PATH         = "{Wym_Css_Path}";
    var WYM_WYM_PATH         = "{Wym_Wym_Path}";
    var WYM_IFRAME_BASE_PATH = "{Wym_Iframe_Base_Path}";
    var WYM_IFRAME_DEFAULT   = "iframe/default/";
    var WYM_JQUERY_PATH      = "{Wym_Jquery_Path}";
    var WYM_TOOLS            = "{Wym_Tools}";
    var WYM_TOOLS_ITEMS      = "{Wym_Tools_Items}";
    var WYM_TOOL_NAME        = "{Wym_Tool_Name}";
    var WYM_TOOL_TITLE       = "{Wym_Tool_Title}";
    var WYM_TOOL_CLASS       = "{Wym_Tool_Class}";
    var WYM_CLASSES          = "{Wym_Classes}";
    var WYM_CLASSES_ITEMS    = "{Wym_Classes_Items}";
    var WYM_CLASS_NAME       = "{Wym_Class_Name}";
    var WYM_CLASS_TITLE      = "{Wym_Class_Title}";
    var WYM_CONTAINERS       = "{Wym_Containers}";
    var WYM_CONTAINERS_ITEMS = "{Wym_Containers_Items}";
    var WYM_CONTAINER_NAME   = "{Wym_Container_Name}";
    var WYM_CONTAINER_TITLE  = "{Wym_Containers_Title}";
    var WYM_CONTAINER_CLASS  = "{Wym_Container_Class}";
    var WYM_HTML             = "{Wym_Html}";
    var WYM_IFRAME           = "{Wym_Iframe}";
    var WYM_STATUS           = "{Wym_Status}";
    var WYM_DIALOG_TITLE     = "{Wym_Dialog_Title}";
    var WYM_DIALOG_BODY      = "{Wym_Dialog_Body}";
    var WYM_BODY             = "body";
    var WYM_STRING           = "string";
    var WYM_P                = "p";
    var WYM_H1               = "h1";
    var WYM_H2               = "h2";
    var WYM_H3               = "h3";
    var WYM_H4               = "h4";
    var WYM_H5               = "h5";
    var WYM_H6               = "h6";
    var WYM_PRE              = "pre";
    var WYM_BLOCKQUOTE       = "blockquote";
    var WYM_TD               = "td";
    var WYM_TH               = "th";
    var WYM_A                = "a";
    var WYM_BR               = "br";
    var WYM_IMG              = "img";
    var WYM_TABLE            = "table";
    var WYM_UL               = "ul";
    var WYM_OL               = "ol";
    var WYM_LI               = "li";
    var WYM_CLASS            = "class";
    var WYM_HREF             = "href";
    var WYM_SRC              = "src";
    var WYM_TITLE            = "title";
    var WYM_ALT              = "alt";
    var WYM_DIALOG_LINK      = "Link";
    var WYM_DIALOG_IMAGE     = "Image";
    var WYM_DIALOG_TABLE     = "Table";
    var WYM_DIALOG_PASTE     = "Paste_From_Word";
    var WYM_BOLD             = "Bold";
    var WYM_ITALIC           = "Italic";
    var WYM_CREATE_LINK      = "CreateLink";
    var WYM_INSERT_IMAGE     = "InsertImage";
    var WYM_INSERT_TABLE     = "InsertTable";
    var WYM_PASTE            = "Paste";
    var WYM_INDENT           = "Indent";
    var WYM_OUTDENT          = "Outdent";
    var WYM_TOGGLE_HTML      = "ToggleHtml";
    var WYM_FORMAT_BLOCK     = "FormatBlock";
    var WYM_PREVIEW          = "Preview";
    
    var WYM_DEFAULT_SKIN     = "default";

    var WYM_MAIN_CONTAINERS = new Array(WYM_P,WYM_H1,WYM_H2,WYM_H3,WYM_H4,
        WYM_H5,WYM_H6,WYM_PRE,WYM_BLOCKQUOTE);

    var WYM_BLOCKS = new Array("address", "blockquote", "div", "dl",
	   "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
	   "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt",
	   "li", "tbody", "td", "tfoot", "th", "thead", "tr");

    var WYM_KEY = {
      BACKSPACE: 8,
      ENTER: 13,
      END: 35,
      HOME: 36,
      LEFT: 37,
      UP: 38,
      RIGHT: 39,
      DOWN: 40,
      CURSOR: new Array(37, 38, 39, 40),
      DELETE: 46
    };

    var WYM_NODE = {
      ELEMENT: 1,
      ATTRIBUTE: 2,
      TEXT: 3
    };


/********** JQUERY **********/

/**
 * Replace an HTML element by WYMeditor
 *
 * @example jQuery(".wymeditor").wymeditor(
 *        {
 *
 *        }
 *      );
 * @desc Example description here
 * 
 * @name WYMeditor
 * @description WYMeditor is a web-based WYSIWYM XHTML editor
 * @param Hash hash A hash of parameters
 * @option Integer iExample Description here
 * @option String sExample Description here
 *
 * @type jQuery
 * @cat Plugins/WYMeditor
 * @author Jean-Francois Hovinne
 */
jQuery.fn.wymeditor = function(options) {

  options = jQuery.extend({

    html:       "",
    
    basePath:   false,
    
    cssPath:    false,
    
    wymPath:    false,
    
    iframeBasePath: false,
    
    jQueryPath: false,
    
    xhtmlParser: 'xhtml_parser.pack.js',
    
    cssParser: 'wym_css_parser.pack.js',
    
    styles: false,
    
    stylesheet: false,
    
    lang:       "en",

    boxHtml:   "<div class='wym_box'>"
              + "<div class='wym_area_top'>" 
              + WYM_TOOLS
              + "</div>"
              + "<div class='wym_area_left'></div>"
              + "<div class='wym_area_right'>"
              + ""/*WYM_CONTAINERS*/
              + ""/*WYM_CLASSES*/
              + "</div>"
              + "<div class='wym_area_main'>"
              + ""/*WYM_HTML*/
              + WYM_IFRAME
              + ""/*WYM_STATUS*/
              + "</div>"
              + ""/*"<div class='wym_area_bottom'>"*/
              + ""/*"<a class='wym_wymeditor_link' "*/
              + ""/*"href='http://www.wymeditor.org/'>WYMeditor</a>"*/
              + ""/*</div>"*/
              + "</div>",

    iframeHtml:"<div class='wym_iframe wym_section'>"
              + "<iframe "
              + "src='"
              + WYM_IFRAME_BASE_PATH
              + "wymiframe.html' "
              + "onload='this.contentWindow.parent.WYM_INSTANCES["
              + WYM_INDEX + "].initIframe(this)' "
              + "></iframe>"
              + "</div>",
              
    editorStyles: [],

    toolsHtml: "<div class='wym_tools wym_section'>"
              + "<h2>{Tools}</h2>"
              + "<ul>"
              + WYM_TOOLS_ITEMS
              + "</ul>"
              + "</div>",
              
    toolsItemHtml:   "<li class='"
                        + WYM_TOOL_CLASS
                        + "'><a href='#' name='"
                        + WYM_TOOL_NAME
                        + "' title='"
                        + WYM_TOOL_TITLE
                        + "'>"
                        + WYM_TOOL_TITLE
                        + "</a></li>",

    toolsItems: [
        {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}, 
        {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
        {'name': 'Superscript', 'title': 'Superscript',
            'css': 'wym_tools_superscript'},
        {'name': 'Subscript', 'title': 'Subscript',
            'css': 'wym_tools_subscript'},
        {'name': 'InsertOrderedList', 'title': 'Ordered_List',
            'css': 'wym_tools_ordered_list'},
        {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
            'css': 'wym_tools_unordered_list'},
        {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
        {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
        {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
        {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
        {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
        {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
        {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
        {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
        {'name': 'Paste', 'title': 'Paste_From_Word',
            'css': 'wym_tools_paste'},
        {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
        {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
    ],

    containersHtml:    "<div class='wym_containers wym_section'>"
                        + "<h2>{Containers}</h2>"
                        + "<ul>"
                        + WYM_CONTAINERS_ITEMS
                        + "</ul>"
                        + "</div>",
                        
    containersItemHtml:"<li class='"
                        + WYM_CONTAINER_CLASS
                        + "'>"
                        + "<a href='#' name='"
                        + WYM_CONTAINER_NAME
                        + "'>"
                        + WYM_CONTAINER_TITLE
                        + "</a></li>",
                        
    containersItems: [
        {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
        {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
        {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
        {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
        {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
        {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
        {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
        {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
        {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
            'css': 'wym_containers_blockquote'},
        {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
    ],

    classesHtml:       "<div class='wym_classes wym_section'>"
                        + "<h2>{Classes}</h2><ul>"
                        + WYM_CLASSES_ITEMS
                        + "</ul></div>",

    classesItemHtml:   "<li><a href='#' name='"
                        + WYM_CLASS_NAME
                        + "'>"
                        + WYM_CLASS_TITLE
                        + "</a></li>",

    classesItems:      [],

    statusHtml:        "<div class='wym_status wym_section'>"
                        + "<h2>{Status}</h2>"
                        + "</div>",

    htmlHtml:          "<div class='wym_html wym_section'>"
                        + "<h2>{Source_Code}</h2>"
                        + "<textarea class='wym_html_val'></textarea>"
                        + "</div>",

    boxSelector:       ".wym_box",
    toolsSelector:     ".wym_tools",
    toolsListSelector: " ul",
    containersSelector:".wym_containers",
    classesSelector:   ".wym_classes",
    htmlSelector:      ".wym_html",
    iframeSelector:    ".wym_iframe iframe",
    statusSelector:    ".wym_status",
    toolSelector:      ".wym_tools a",
    containerSelector: ".wym_containers a",
    classSelector:     ".wym_classes a",
    htmlValSelector:   ".wym_html_val",
    
    hrefSelector:      ".wym_href",
    srcSelector:       ".wym_src",
    titleSelector:     ".wym_title",
    altSelector:       ".wym_alt",
    textSelector:      ".wym_text",
    
    rowsSelector:      ".wym_rows",
    colsSelector:      ".wym_cols",
    captionSelector:   ".wym_caption",
    
    submitSelector:    ".wym_submit",
    cancelSelector:    ".wym_cancel",
    previewSelector:   "",
    
    dialogTypeSelector:    ".wym_dialog_type",
    dialogLinkSelector:    ".wym_dialog_link",
    dialogImageSelector:   ".wym_dialog_image",
    dialogTableSelector:   ".wym_dialog_table",
    dialogPasteSelector:   ".wym_dialog_paste",
    dialogPreviewSelector: ".wym_dialog_preview",
    
    updateSelector:    ".wymupdate",
    updateEvent:       "click",
    
    dialogFeatures:    "menubar=no,titlebar=no,toolbar=no,resizable=no"
                      + ",width=560,height=300,top=0,left=0",

    dialogHtml:      "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
                      + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
                      + "<html><head>"
                      + "<link rel='stylesheet' type='text/css' media='screen'"
                      + " href='"
                      + WYM_CSS_PATH
                      + "' />"
                      + "<title>"
                      + WYM_DIALOG_TITLE
                      + "</title>"
                      + "<script type='text/javascript'"
                      + " src='"
                      + WYM_JQUERY_PATH
                      + "'></script>"
                      + "<script type='text/javascript'"
                      + " src='"
                      + WYM_WYM_PATH
                      + "'></script>"
                      + "</head>"
                      + WYM_DIALOG_BODY
                      + "</html>",
                      
    dialogLinkHtml:  "<body class='wym_dialog wym_dialog_link'"
               + " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
               + ">"
               + "<form>"
               + "<fieldset>"
               + "<input type='hidden' class='wym_dialog_type' value='"
               + WYM_DIALOG_LINK
               + "' />"
               + "<legend>{Link}</legend>"
               + "<div class='row'>"
               + "<label>{URL}</label>"
               + "<input type='text' class='wym_href' value='' size='40' />"
               + "</div>"
               + "<div class='row'>"
               + "<label>{Title}</label>"
               + "<input type='text' class='wym_title' value='' size='40' />"
               + "</div>"
               + "<div class='row row-indent'>"
               + "<input class='wym_submit' type='button'"
               + " value='{Submit}' />"
               + "<input class='wym_cancel' type='button'"
               + "value='{Cancel}' />"
               + "</div>"
               + "</fieldset>"
               + "</form>"
               + "</body>",
    
    dialogImageHtml:  "<body class='wym_dialog wym_dialog_image'"
               + " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
               + ">"
               + "<form>"
               + "<fieldset>"
               + "<input type='hidden' class='wym_dialog_type' value='"
               + WYM_DIALOG_IMAGE
               + "' />"
               + "<legend>{Image}</legend>"
               + "<div class='row'>"
               + "<label>{URL}</label>"
               + "<input type='text' class='wym_src' value='' size='40' />"
               + "</div>"
               + "<div class='row'>"
               + "<label>{Alternative_Text}</label>"
               + "<input type='text' class='wym_alt' value='' size='40' />"
               + "</div>"
               + "<div class='row'>"
               + "<label>{Title}</label>"
               + "<input type='text' class='wym_title' value='' size='40' />"
               + "</div>"
               + "<div class='row row-indent'>"
               + "<input class='wym_submit' type='button'"
               + " value='{Submit}' />"
               + "<input class='wym_cancel' type='button'"
               + "value='{Cancel}' />"
               + "</div>"
               + "</fieldset>"
               + "</form>"
               + "</body>",
    
    dialogTableHtml:  "<body class='wym_dialog wym_dialog_table'"
               + " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
               + ">"
               + "<form>"
               + "<fieldset>"
               + "<input type='hidden' class='wym_dialog_type' value='"
               + WYM_DIALOG_TABLE
               + "' />"
               + "<legend>{Table}</legend>"
               + "<div class='row'>"
               + "<label>{Caption}</label>"
               + "<input type='text' class='wym_caption' value='' size='40' />"
               + "</div>"
               + "<div class='row'>"
               + "<label>{Number_Of_Rows}</label>"
               + "<input type='text' class='wym_rows' value='3' size='3' />"
               + "</div>"
               + "<div class='row'>"
               + "<label>{Number_Of_Cols}</label>"
               + "<input type='text' class='wym_cols' value='2' size='3' />"
               + "</div>"
               + "<div class='row row-indent'>"
               + "<input class='wym_submit' type='button'"
               + " value='{Submit}' />"
               + "<input class='wym_cancel' type='button'"
               + "value='{Cancel}' />"
               + "</div>"
               + "</fieldset>"
               + "</form>"
               + "</body>",

    dialogPasteHtml:  "<body class='wym_dialog wym_dialog_paste'"
               + " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
               + ">"
               + "<form>"
               + "<input type='hidden' class='wym_dialog_type' value='"
               + WYM_DIALOG_PASTE
               + "' />"
               + "<fieldset>"
               + "<legend>{Paste_From_Word}</legend>"
               + "<div class='row'>"
               + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
               + "</div>"
               + "<div class='row'>"
               + "<input class='wym_submit' type='button'"
               + " value='{Submit}' />"
               + "<input class='wym_cancel' type='button'"
               + "value='{Cancel}' />"
               + "</div>"
               + "</fieldset>"
               + "</form>"
               + "</body>",

    dialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
                      + " onload='WYM_INIT_DIALOG(" + WYM_INDEX + ")'"
                      + "></body>",
                      
    dialogStyles: [],
                      
    skin:            WYM_DEFAULT_SKIN,

    stringDelimiterLeft: "{",
    stringDelimiterRight:"}",
    
    preInit: null,
    preBind: null,
    postInit: null,
    
    preInitDialog: null,
    postInitDialog: null

  }, options);

  return this.each(function() {

    new Wymeditor(jQuery(this),options);
  });
};

/* @name extend
 * @description Returns the WYMeditor instance based on its index
 */
jQuery.extend({
  wymeditors: function(i) {
    return (WYM_INSTANCES[i]);
  },
  wymstrings: function(lang, sKey) {
    return (WYM_STRINGS[lang][sKey]);
  }
});


/********** WYMEDITOR **********/

/* @name Wymeditor
 * @description WYMeditor class
 */
function Wymeditor(elem,options) {

  this._index = WYM_INSTANCES.push(this) - 1;
  this._element = elem;
  this._options = options;
  this._html = jQuery(elem).val();
  
  if(this._options.html) this._html = this._options.html;
  this._options.basePath = this._options.basePath
    || this.computeBasePath();
  this._options.cssPath = this._options.cssPath
    || this.computeCssPath();
  this._options.wymPath = this._options.wymPath
    || this.computeWymPath();
  this._options.iframeBasePath = this._options.iframeBasePath
    || this._options.basePath + WYM_IFRAME_DEFAULT;
  this._options.jQueryPath = this._options.jQueryPath
    || this.computeJqueryPath();
    
  this.selection = new WymSelection();
  
  this.init();
  
};

/* @name init
 * @description Initializes a WYMeditor instance
 */
Wymeditor.prototype.init = function() {

  //load subclass - browser specific
  //unsupported browsers: do nothing
  if (jQuery.browser.msie) {
    var WymClass = new WymClassExplorer(this);
    var WymSel = new WymSelExplorer(this);
  }
  else if (jQuery.browser.mozilla) {
    var WymClass = new WymClassMozilla(this);
    var WymSel = new WymSelMozilla(this);
  }
  else if (jQuery.browser.opera) {
    var WymClass = new WymClassOpera(this);
    var WymSel = new WymSelOpera(this);
  }
  else if (jQuery.browser.safari) {
    //commented until supported
    var WymClass = new WymClassSafari(this);
    var WymSel = new WymSelSafari(this);
  }
  
  if(WymClass) {
  
      if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this);
  
      this.loadXhtmlParser(WymClass);
      
      if(this._options.styles || this._options.stylesheet){
        this.configureEditorUsingRawCss();
      }
      
      this.helper = new XmlHelper();
      
      //extend the Wymeditor object
      //don't use jQuery.extend since 1.1.4
      //jQuery.extend(this, WymClass);
      for (prop in WymClass) { this[prop] = WymClass[prop]; }

      //load wymbox
      this._box = jQuery(this._element).hide().after(this._options.boxHtml).next();
      
      //construct the iframe
      var iframeHtml = this._options.iframeHtml;
      iframeHtml = iframeHtml
        .replaceAll(WYM_INDEX,this._index)
        .replaceAll(WYM_IFRAME_BASE_PATH, this._options.iframeBasePath);
      
      //construct wymbox
      var boxHtml = jQuery(this._box).html();
      
      boxHtml = boxHtml.replaceAll(WYM_TOOLS, this._options.toolsHtml);
      boxHtml = boxHtml.replaceAll(WYM_CONTAINERS,this._options.containersHtml);
      boxHtml = boxHtml.replaceAll(WYM_CLASSES, this._options.classesHtml);
      boxHtml = boxHtml.replaceAll(WYM_HTML, this._options.htmlHtml);
      boxHtml = boxHtml.replaceAll(WYM_IFRAME, iframeHtml);
      boxHtml = boxHtml.replaceAll(WYM_STATUS, this._options.statusHtml);
      
      //construct tools list
      var aTools = eval(this._options.toolsItems);
      var sTools = "";

      for(var i = 0; i < aTools.length; i++) {
        var oTool = aTools[i];
        if(oTool.name && oTool.title)
          sTools += this._options.toolsItemHtml
          .replaceAll(WYM_TOOL_NAME, oTool.name)
          .replaceAll(WYM_TOOL_TITLE,
              this._options.stringDelimiterLeft
            + oTool.title
            + this._options.stringDelimiterRight)
          .replaceAll(WYM_TOOL_CLASS, oTool.css);
      }

      boxHtml = boxHtml.replaceAll(WYM_TOOLS_ITEMS, sTools);

      //construct classes list
      var aClasses = eval(this._options.classesItems);
      var sClasses = "";

      for(var i = 0; i < aClasses.length; i++) {
        var oClass = aClasses[i];
        if(oClass.name && oClass.title)
          sClasses += this._options.classesItemHtml
          .replaceAll(WYM_CLASS_NAME, oClass.name)
          .replaceAll(WYM_CLASS_TITLE, oClass.title);
      }

      boxHtml = boxHtml.replaceAll(WYM_CLASSES_ITEMS, sClasses);
      
      //construct containers list
      var aContainers = eval(this._options.containersItems);
      var sContainers = "";

      for(var i = 0; i < aContainers.length; i++) {
        var oContainer = aContainers[i];
        if(oContainer.name && oContainer.title)
          sContainers += this._options.containersItemHtml
          .replaceAll(WYM_CONTAINER_NAME, oContainer.name)
          .replaceAll(WYM_CONTAINER_TITLE,
              this._options.stringDelimiterLeft
            + oContainer.title
            + this._options.stringDelimiterRight)
          .replaceAll(WYM_CONTAINER_CLASS, oContainer.css);
      }

      boxHtml = boxHtml.replaceAll(WYM_CONTAINERS_ITEMS, sContainers);

      //l10n
      boxHtml = this.replaceStrings(boxHtml);
      
      //load html in wymbox
      jQuery(this._box).html(boxHtml);
      
      //hide the html value
      jQuery(this._box).find(this._options.htmlSelector).hide();
      
      //enable the skin
      this.skin();
      
    }
    
    if(WymSel) {
    
      //extend the selection object
      //don't use jQuery.extend since 1.1.4
      //jQuery.extend(this.selection, WymSel);
      for (prop in WymSel) { this.selection[prop] = WymSel[prop]; }
    }
};

Wymeditor.prototype.bindEvents = function() {

  //copy the instance
  var wym = this;
  
  //handle click event on tools buttons
  jQuery(this._box).find(this._options.toolSelector).click(function() {
    wym.exec(jQuery(this).attr(WYM_NAME));
    return(false);
  });
  
  //handle click event on containers buttons
  jQuery(this._box).find(this._options.containerSelector).click(function() {
    wym.container(jQuery(this).attr(WYM_NAME));
    return(false);
  });
  
  //handle keyup event on html value: set the editor value
  jQuery(this._box).find(this._options.htmlValSelector).keyup(function() {
    jQuery(wym._doc.body).html(jQuery(this).val());
  });
  
  //handle click event on classes buttons
  jQuery(this._box).find(this._options.classSelector).click(function() {
  
    var aClasses = eval(wym._options.classesItems);
    var sName = jQuery(this).attr(WYM_NAME);
    
    var oClass = aClasses.findByName(sName);
    
    if(oClass) {
      jqexpr = oClass.expr;
      wym.toggleClass(sName, jqexpr);
    }
    return(false);
  });
  
  //handle event on update element
  jQuery(this._options.updateSelector)
    .bind(this._options.updateEvent, function() {
      wym.update();
  });
};

Wymeditor.prototype.ready = function() {
  return(this._doc != null);
};


/********** METHODS **********/

/* @name box
 * @description Returns the WYMeditor container
 */
Wymeditor.prototype.box = function() {
  return(this._box);
};

/* @name html
 * @description Get/Set the html value
 */
Wymeditor.prototype.html = function(html) {

  if(html) jQuery(this._doc.body).html(html);
  else return(jQuery(this._doc.body).html());
};

/* @name xhtml
 * @description Cleans up the HTML
 */
Wymeditor.prototype.xhtml = function() {
    return this.parser.parse(this.html());
};

/* @name exec
 * @description Executes a button command
 */
Wymeditor.prototype.exec = function(cmd, param) {
  //base function for execCommand
  //open a dialog or exec
  switch(cmd) {
    case WYM_CREATE_LINK:
      var container = this.container();
      if(container || this._selected_image) this.dialog(WYM_DIALOG_LINK);
    break;
    
    case WYM_INSERT_IMAGE:
      this.dialog(WYM_DIALOG_IMAGE);
    break;
    
    case WYM_INSERT_TABLE:
      this.dialog(WYM_DIALOG_TABLE);
    break;
    
    case WYM_PASTE:
      this.dialog(WYM_DIALOG_PASTE);
    break;
    
    case WYM_TOGGLE_HTML:
      this.update();
      this.toggleHtml();
    break;
    
    case WYM_PREVIEW:
      this.dialog(WYM_PREVIEW);
    break;
    
    default:
      this._exec(cmd, param);
    break;
  }
};

/* @name container
 * @description Get/Set the selected container
 */
Wymeditor.prototype.container = function(sType) {

  if(sType) {
  
    var container = null;
    
    if(sType.toLowerCase() == WYM_TH) {
    
      container = this.container();
      
      //find the TD or TH container
      switch(container.tagName.toLowerCase()) {
      
        case WYM_TD: case WYM_TH:
          break;
        default:
          var aTypes = new Array(WYM_TD,WYM_TH);
          container = this.findUp(this.container(), aTypes);
          break;
      }
      
      //if it exists, switch
      if(container!=null) {
      
        sType = (container.tagName.toLowerCase() == WYM_TD)? WYM_TH: WYM_TD;
        this.switchTo(container,sType);
        this.update();
      }
    } else {
  
      //set the container type
      var aTypes=new Array(WYM_P,WYM_H1,WYM_H2,WYM_H3,WYM_H4,WYM_H5,
      WYM_H6,WYM_PRE,WYM_BLOCKQUOTE);
      container = this.findUp(this.container(), aTypes);
      
      if(container) {
  
        var newNode = null;
  
        //blockquotes must contain a block level element
        if(sType.toLowerCase() == WYM_BLOCKQUOTE) {
        
          var blockquote = this.findUp(this.container(), WYM_BLOCKQUOTE);
          
          if(blockquote == null) {
          
            newNode = this._doc.createElement(sType);
            container.parentNode.insertBefore(newNode,container);
            newNode.appendChild(container);
            this.setFocusToNode(newNode.firstChild);
            
          } else {
          
            var nodes = blockquote.childNodes;
            var lgt = nodes.length;
            var firstNode = null;
            
            if(lgt > 0) firstNode = nodes.item(0);
            for(var x=0; x<lgt; x++) {
              blockquote.parentNode.insertBefore(nodes.item(0),blockquote);
            }
            blockquote.parentNode.removeChild(blockquote);
            if(firstNode) this.setFocusToNode(firstNode);
          }
        }
        
        else this.switchTo(container,sType);
      
        this.update();
      }
    }
  }
  else return(this.selected());
};

/* @name toggleClass
 * @description Toggles class on selected element, or one of its parents
 */
Wymeditor.prototype.toggleClass = function(sClass, jqexpr) {

  var container = (this._selected_image
                    ? this._selected_image
                    : jQuery(this.selected()));
  container = jQuery(container).parentsOrSelf(jqexpr);
  jQuery(container).toggleClass(sClass);

  if(!jQuery(container).attr(WYM_CLASS)) jQuery(container).removeAttr(this._class);

};

/* @name findUp
 * @description Returns the first parent or self container, based on its type
 */
Wymeditor.prototype.findUp = function(node, filter) {

  //filter is a string or an array of strings

  if(node) {

      var tagname = node.tagName.toLowerCase();
      
      if(typeof(filter) == WYM_STRING) {
    
        while(tagname != filter && tagname != WYM_BODY) {
        
          node = node.parentNode;
          tagname = node.tagName.toLowerCase();
        }
      
      } else {
      
        var bFound = false;
        
        while(!bFound && tagname != WYM_BODY) {
          for(var i = 0; i < filter.length; i++) {
            if(tagname == filter[i]) {
              bFound = true;
              break;
            }
          }
          if(!bFound) {
            node = node.parentNode;
            tagname = node.tagName.toLowerCase();
          }
        }
      }
      
      if(tagname != WYM_BODY) return(node);
      else return(null);
      
  } else return(null);
};

/* @name switchTo
 * @description Switch the node's type
 */
Wymeditor.prototype.switchTo = function(node,sType) {

  var newNode = this._doc.createElement(sType);
  var html = jQuery(node).html();
  node.parentNode.replaceChild(newNode,node);
  jQuery(newNode).html(html);
  this.setFocusToNode(newNode);
};

Wymeditor.prototype.replaceStrings = function(sVal) {

  for (var key in WYM_STRINGS[this._options.lang]) {
    sVal = sVal.replaceAll(this._options.stringDelimiterLeft + key 
    + this._options.stringDelimiterRight, WYM_STRINGS[this._options.lang][key]);
  }
  return(sVal);
};

Wymeditor.prototype.encloseString = function(sVal) {

  return(this._options.stringDelimiterLeft
    + sVal
    + this._options.stringDelimiterRight);
};

/* @name status
 * @description Prints a status message
 */
Wymeditor.prototype.status = function(sMessage) {

  //print status message
  jQuery(this._box).find(this._options.statusSelector).html(sMessage);
};

/* @name update
 * @description Updates the element and textarea values
 */
Wymeditor.prototype.update = function() {

  var html = this.xhtml();
  jQuery(this._element).val(html);
  jQuery(this._box).find(this._options.htmlValSelector).val(html);
};

/* @name dialog
 * @description Opens a dialog box
 */
Wymeditor.prototype.dialog = function(sType) {
  
  var wDialog = window.open(
    '',
    'dialog',
    this._wym._options.dialogFeatures);

  if(wDialog) {

    var sBodyHtml = "";
    
    switch(sType) {

      case(WYM_DIALOG_LINK):
        sBodyHtml = this._options.dialogLinkHtml;
      break;
      case(WYM_DIALOG_IMAGE):
        sBodyHtml = this._options.dialogImageHtml;
      break;
      case(WYM_DIALOG_TABLE):
        sBodyHtml = this._options.dialogTableHtml;
      break;
      case(WYM_DIALOG_PASTE):
        sBodyHtml = this._options.dialogPasteHtml;
      break;
      case(WYM_PREVIEW):
        sBodyHtml = this._options.dialogPreviewHtml;
      break;
    }
    
    //construct the dialog
    var dialogHtml = this._options.dialogHtml;
    dialogHtml = dialogHtml
      .replaceAll(WYM_BASE_PATH, this._options.basePath)
      .replaceAll(WYM_CSS_PATH, this._options.cssPath)
      .replaceAll(WYM_WYM_PATH, this._options.wymPath)
      .replaceAll(WYM_JQUERY_PATH, this._options.jQueryPath)
      .replaceAll(WYM_DIALOG_TITLE, this.encloseString(sType))
      .replaceAll(WYM_DIALOG_BODY, sBodyHtml)
      .replaceAll(WYM_INDEX, this._index);
      
    dialogHtml = this.replaceStrings(dialogHtml);
    
    var doc = wDialog.document;
    doc.write(dialogHtml);
    doc.close();
  }
};

/* @name toggleHtml
 * @description Shows/Hides the HTML
 */
Wymeditor.prototype.toggleHtml = function() {
  jQuery(this._box).find(this._options.htmlSelector).toggle();
};

Wymeditor.prototype.uniqueStamp = function() {
	var now=new Date();
	return("wym-" + now.getTime());
};

Wymeditor.prototype.paste = function(sData) {

  var sTmp;
  var container = this.selected();
	
  //split the data, using double newlines as the separator
  var aP = sData.split(this._newLine + this._newLine);
  var rExp = new RegExp(this._newLine, "g");

  //add a P for each item
  if(container && container.tagName.toLowerCase() != WYM_BODY) {
    for(x = aP.length - 1; x >= 0; x--) {
        sTmp = aP[x];
        //simple newlines are replaced by a break
        sTmp = sTmp.replace(rExp, "<br />");
        jQuery(container).after("<p>" + sTmp + "</p>");
    }
  } else {
    for(x = 0; x < aP.length; x++) {
        sTmp = aP[x];
        //simple newlines are replaced by a break
        sTmp = sTmp.replace(rExp, "<br />");
        jQuery(this._doc.body).append("<p>" + sTmp + "</p>");
    }
  
  }
};

Wymeditor.prototype.addCssRules = function(doc, aCss) {
  var styles = doc.styleSheets[0];
  if(styles) {
    for(var i = 0; i < aCss.length; i++) {
      var oCss = aCss[i];
      if(oCss.name && oCss.css) this.addCssRule(styles, oCss);
    }
  }
};

/********** CONFIGURATION **********/

Wymeditor.prototype.computeBasePath = function() {
  return jQuery(jQuery.grep(jQuery('script'), function(s){
    return (s.src && s.src.match(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/ ))
  })).attr('src').replace(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/, '');
};

Wymeditor.prototype.computeWymPath = function() {
  return jQuery(jQuery.grep(jQuery('script'), function(s){
    return (s.src && s.src.match(/jquery\.wymeditor(\.pack){0,1}\.js(\?.*)?$/ ))
  })).attr('src');
};

Wymeditor.prototype.computeJqueryPath = function() {
  return jQuery(jQuery.grep(jQuery('script'), function(s){
    return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack){0,1}\.js(\?.*)?$/ ))
  })).attr('src');
};

Wymeditor.prototype.computeCssPath = function() {
  return jQuery(jQuery.grep(jQuery('link'), function(s){
   return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ ))
  })).attr('href');
};

Wymeditor.prototype.loadXhtmlParser = function(WymClass) {
  if(typeof XhtmlSaxListener != 'function'){
    // This is the only way to get loaded functions in the global scope until jQuery.globalEval works in safari
   eval(jQuery.ajax({url:this._options.basePath
    + this._options.xhtmlParser, async:false}).responseText);
    window.XmlHelper = XmlHelper;
    window.XhtmlValidator = XhtmlValidator;
    window.ParallelRegex = ParallelRegex;
    window.StateStack = StateStack;
    window.Lexer = Lexer;
    window.XhtmlLexer = XhtmlLexer;
    window.XhtmlParser = XhtmlParser;
    window.XhtmlSaxListener = XhtmlSaxListener;
   
  }
  var SaxListener = new XhtmlSaxListener();
  jQuery.extend(SaxListener, WymClass);
  this.parser = new XhtmlParser(SaxListener);
};

Wymeditor.prototype.configureEditorUsingRawCss = function() {
  if(typeof WymCssParser != 'function'){
    eval(jQuery.ajax({url:this._options.basePath
     + this._options.cssParser, async:false}).responseText);
    window.WymCssLexer = WymCssLexer;
    window.WymCssParser = WymCssParser;
  }
  var CssParser = new WymCssParser();
  if(this._options.stylesheet){
    CssParser.parse(jQuery.ajax({url: this._options.stylesheet,async:false}).responseText);
  }else{
    CssParser.parse(this._options.styles, false);
  }

  if(this._options.classesItems.length == 0) {
    this._options.classesItems = CssParser.css_settings.classesItems;
  }
  if(this._options.editorStyles.length == 0) {
    this._options.editorStyles = CssParser.css_settings.editorStyles;
  }
  if(this._options.dialogStyles.length == 0) {
    this._options.dialogStyles = CssParser.css_settings.dialogStyles;
  }
};

/********** EVENTS **********/

Wymeditor.prototype.listen = function() {

  //don't use jQuery.find() on the iframe body
  //because of MSIE + jQuery + expando issue (#JQ1143)
  //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);
  
  jQuery(this._doc.body).bind("mouseup", this.mouseup);
  var images = this._doc.body.getElementsByTagName("img");
  for(var i=0; i < images.length; i++) {
    jQuery(images[i]).bind("mouseup", this.mouseup);
  }
};

//mouseup handler
Wymeditor.prototype.mouseup = function(evt) {
  
  var wym = WYM_INSTANCES[this.ownerDocument.title];
  if(this.tagName.toLowerCase() == WYM_IMG) wym._selected_image = this;
  else wym._selected_image = null;
  evt.stopPropagation();
};

/********** SKINS **********/

Wymeditor.prototype.skin = function() {

  switch(this._options.skin) {
  
    case WYM_DEFAULT_SKIN:
    
      jQuery(this._box).addClass("wym_skin_default");
      
      //render following sections as panels
      jQuery(this._box).find(this._options.classesSelector)
        .addClass("wym_panel");

      //render following sections as buttons
      jQuery(this._box).find(this._options.toolsSelector)
        .addClass("wym_buttons");

      //render following sections as dropdown menus
      jQuery(this._box).find(this._options.containersSelector)
        .addClass("wym_dropdown")
        .find(WYM_H2)
        .append("<span>&nbsp;&gt;</span>");

      // auto add some margin to the main area sides if left area
      // or right area are not empty (if they contain sections)
      jQuery(this._box).find("div.wym_area_right ul")
        .parents("div.wym_area_right").show()
        .parents(this._options.boxSelector)
        .find("div.wym_area_main")
        .css({"margin-right": "155px"});

      jQuery(this._box).find("div.wym_area_left ul")
        .parents("div.wym_area_left").show()
        .parents(this._options.boxSelector)
        .find("div.wym_area_main")
        .css({"margin-left": "155px"});

      //make hover work under IE < 7
      jQuery(this._box).find(".wym_section").hover(function(){ 
        jQuery(this).addClass("hover"); 
      },function(){ 
        jQuery(this).removeClass("hover");
      });
    
    break;
  
  }

};

/********** DIALOGS **********/

function WYM_INIT_DIALOG(index) {

    var wym = window.opener.WYM_INSTANCES[index];
    var doc = window.document;
    var selected = wym.selected();
    var dialogType = jQuery(wym._options.dialogTypeSelector).val();
    var sStamp = wym.uniqueStamp();
    
    switch(dialogType) {
    
    case WYM_DIALOG_LINK:
      //ensure that we select the link to populate the fields
      if(selected && selected.tagName && selected.tagName.toLowerCase != WYM_A)
        selected = jQuery(selected).parentsOrSelf(WYM_A);
    
      //fix MSIE selection if link image has been clicked
      if(!selected && wym._selected_image)
        selected = jQuery(wym._selected_image).parentsOrSelf(WYM_A);
    break;

    }
    
    //pre-init functions
    if(jQuery.isFunction(wym._options.preInitDialog))
      wym._options.preInitDialog(wym,window);
    
    //add css rules from options
    var styles = doc.styleSheets[0];
    var aCss = eval(wym._options.dialogStyles);

    wym.addCssRules(doc, aCss);
    
    //auto populate fields if selected container (e.g. A)
    if(selected) {
      jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYM_HREF));
      jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYM_SRC));
      jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYM_TITLE));
      jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYM_ALT));
    }
    
    //auto populate image fields if selected image
    if(wym._selected_image) {
      jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
        .val(jQuery(wym._selected_image).attr(WYM_SRC));
      jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
        .val(jQuery(wym._selected_image).attr(WYM_TITLE));
      jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
        .val(jQuery(wym._selected_image).attr(WYM_ALT));
    }

    jQuery(wym._options.dialogLinkSelector + " "
        + wym._options.submitSelector).click(function() {
        
        var sUrl = jQuery(wym._options.hrefSelector).val();
        if(sUrl.length > 0) {
            wym._exec(WYM_CREATE_LINK, sStamp);
            //don't use jQuery.find() see #JQ1143
            //var link = jQuery(wym._doc.body).find("a[@href=" + sStamp + "]");
            var link = null;
            var nodes = wym._doc.body.getElementsByTagName(WYM_A);
            for(var i=0; i < nodes.length; i++) {
                if(jQuery(nodes[i]).attr(WYM_HREF) == sStamp) {
                    link = jQuery(nodes[i]);
                    break;
                }
            }
            if(link) {
                link.attr(WYM_HREF, sUrl);
                link.attr(WYM_TITLE, jQuery(wym._options.titleSelector).val());
            }
        }
        window.close();
    });
    
    jQuery(wym._options.dialogImageSelector + " "
        + wym._options.submitSelector).click(function() {
        
        var sUrl = jQuery(wym._options.srcSelector).val();
        if(sUrl.length > 0) {
            wym._exec(WYM_INSERT_IMAGE, sStamp);
            //don't use jQuery.find() see #JQ1143
            //var image = jQuery(wym._doc.body).find("img[@src=" + sStamp + "]");
            var image = null;
            var nodes = wym._doc.body.getElementsByTagName(WYM_IMG);
            for(var i=0; i < nodes.length; i++) {
                if(jQuery(nodes[i]).attr(WYM_SRC) == sStamp) {
                    image = jQuery(nodes[i]);
                    break;
                }
            }
            if(image) {
                image.attr(WYM_SRC, sUrl);
                image.attr(WYM_TITLE, jQuery(wym._options.titleSelector).val());
                image.attr(WYM_ALT, jQuery(wym._options.altSelector).val());
            }
        }
        window.close();
    });
    
    jQuery(wym._options.dialogTableSelector + " "
        + wym._options.submitSelector).click(function() {
        
        var iRows = jQuery(wym._options.rowsSelector).val();
        var iCols = jQuery(wym._options.colsSelector).val();
        
        if(iRows > 0 && iCols > 0) {
        
            var table = wym._doc.createElement(WYM_TABLE);
            var newRow = null;
        		var newCol = null;
        		
        		var sCaption = jQuery(wym._options.captionSelector).val();
        		
        		//we create the caption
        		var newCaption = table.createCaption();
        		newCaption.innerHTML = sCaption;
        		
        		//we create the rows and cells
        		for(x=0; x<iRows; x++) {
        			newRow = table.insertRow(x);
        			for(y=0; y<iCols; y++) {newRow.insertCell(y);}
        		}
        		
          //append the table after the selected container
          var node = jQuery(wym.findUp(wym.container(),WYM_MAIN_CONTAINERS)).get(0);
          if(!node || !node.parentNode) jQuery(wym._doc.body).append(table);
          else jQuery(node).after(table);
        }
        window.close();
    });
    
    jQuery(wym._options.dialogPasteSelector + " "
        + wym._options.submitSelector).click(function() {
        
        var sText = jQuery(wym._options.textSelector).val();
        wym.paste(sText);
        window.close();
    });
    
    jQuery(wym._options.dialogPreviewSelector + " "
        + wym._options.previewSelector)
        .html(wym.xhtml());
    
    //cancel button
    jQuery(wym._options.cancelSelector).mousedown(function() {
        window.close();
    });
    
    //pre-init functions
    if(jQuery.isFunction(wym._options.postInitDialog))
      wym._options.postInitDialog(wym,window);
};

/********** SELECTION API **********/

function WymSelection() {
    this.test = "test from WymSelection";
};


WymSelection.prototype = {
    /* The following properties where set in the browser specific file (in
     * getSelection()):
     * this.original
     * this.startNode
     * this.endNode
     * this.startOffset
     * this.endOffset
     * this.iscollapsed
     * this.container
     */

    /* The following methods are implemented in browser specific file:
     *  - deleteIfExpanded()
     *  - cursorToStart()
     *  - cursorToEnd()
     */


    isAtStart: function(jqexpr) {
        var parent = jQuery(this.startNode).parentsOrSelf(jqexpr);

        // jqexpr isn't a parent of the current cursor position
        if (parent.length==0)
            return false;

        var startNode = this.startNode;
        if (startNode.nodeType == WYM_NODE.TEXT) {
            // 1. startNode ist first child
            // 2. offset needs to be 0 to be at the start (or the previous
            //    characters are whitespaces)
            if ((startNode.previousSibling
                    && !isPhantomNode(startNode.previousSibling))
                        || (this.startOffset != 0 && !isPhantomString(
                            startNode.data.substring(0, this.startOffset))))
                return false;
            else
                startNode = startNode.parentNode;
        }
        // cursor can be at the start of a text node and have a startOffset > 0
        // (if the node contains trailign whitespaces)
        else if (this.startOffset != 0)
            return false;


        for (var n=jQuery(startNode); n[0]!=parent[0]; n=n.parent()) {
            var firstChild = n.parent().children(':first');

            // node isn't first child => cursor can't be at the beginning
            if (firstChild[0] != n[0]
                    || (firstChild[0].previousSibling
                        && !isPhantomNode(firstChild[0].previousSibling)))
                return false;
        }

        return true;
    },

    isAtEnd: function(jqexpr) {
        var parent = jQuery(this.endNode).parentsOrSelf(jqexpr);

        // jqexpr isn't a parent of the current cursor position
        if (parent.length==0)
            return false;
        else
            parent = parent[0];


        // This is the case if, e.g ("|" = cursor): <p>textnode|<br/></p>,
        // there the offset of endNode (endOffset) is 1 (behind the first node
        // of <p>)
        if (this.endNode == parent) {
            // NOTE I don't know if it is a good idea to delete the <br>
            // here, as "atEnd()" probably shouldn't change the dom tree,
            // but only searching it
            if (this.endNode.lastChild.nodeName == "BR")
                this.endNode.removeChild(endNode.lastChild);

            // if cursor is really at the end
            if (this.endOffset == 0)
                return false;
            else {
                for (var nNext=this.endNode.childNodes[this.endOffset-1].nextSibling;
                        nNext==null || nNext.nodeName == "BR";
                        nNext=nNext.nextSibling)

                if (nNext==null)
                    return true;
            }

        }
        else {
            var endNode = this.endNode;
            if (endNode.nodeType == WYM_NODE.TEXT) {
                if ((endNode.nextSibling
                        && !isPhantomNode(endNode.nextSibling))
                            || (this.endOffset != endNode.data.length))
                    return false;
                else
                    endNode = endNode.parentNode;
            }

            for (var n=endNode; n!=parent; n=n.parentNode) {
                var lastChild = n.parentNode.lastChild;
                // node isn't last child => cursor can't be at the end
                // (is this true?) in gecko there the last child could be a
                //     phantom node

                // sometimes also whitespacenodes which aren't phatom nodes
                // get stripped, but this is ok, as this is a wysiwym editor
                if ((lastChild != n) ||
                        (isPhantomNode(lastChild)
                        && lastChild.previousSibling != n)) {
                    return false;
                }
            }
        }

        if (this.endOffset == this.endNode.length)
            return true;
        else
            return false;
    }
};

/********** HELPERS **********/

// Returns true if it is a text node with whitespaces only
jQuery.fn.isPhantomNode = function() {
  if (this[0].nodeType == 3)
    return !(/[^\t\n\r ]/.test(this[0].data));

  return false;
};

function isPhantomNode(n) {
  if (n.nodeType == 3)
    return !(/[^\t\n\r ]/.test(n.data));

  return false;
};

function isPhantomString(str) {
    return !(/[^\t\n\r ]/.test(str));
};

// Returns the Parents or the node itself
// jqexpr = a jQuery expression
jQuery.fn.parentsOrSelf = function(jqexpr) {
  var n = this;

  if (n[0].nodeType == 3)
    n = n.parents().slice(0,1);

//  if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
  if (n.filter(jqexpr).size() == 1)
    return n;
  else
    return n.parents(jqexpr).slice(0,1);
};

String.prototype.insertAt = function(inserted, pos) {
  return(this.substr(0,pos) + inserted + this.substring(pos));
};

String.prototype.replaceAll = function(old, rep) {
  var rExp = new RegExp(old, "g");
  return(this.replace(rExp, rep));
};


// from http://forum.de.selfhtml.org/archiv/2004/3/t76079/#m438193 (2007-02-06)
Array.prototype.contains = function (elem) {
//  var i;
  for (var i = 0; i < this.length; i++) {
  if (this[i] === elem) {
    return true;
  }
  }
  return false;
};

Array.prototype.indexOf = function (item) {
	var ret=-1;
	for(var i = 0; i < this.length; i++) {
    if (this[i] == item) {
      ret=i; break;
    }
  }
	return(ret);
};

String.prototype.trim = function() {
  return this.replace(/^(\s*)|(\s*)$/gm,'');
};

Array.prototype.findByName = function (name) {
  for(var i = 0; i < this.length; i++) {
    var Item = this[i];
    if(Item.name == name) {
      return(Item);
    }
  }
  return(null);
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (C) 2007 H.O.net - http://www.honet.be/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.explorer.js
 *        MSIE specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne@wymeditor.org)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Frédéric Palluel-Lafleur (fpalluel@gmail.com)
 */

function WymClassExplorer(wym) {
    
    this._wym = wym;
    this._class = "className";
    this._newLine = "\r\n";
};

WymClassExplorer.prototype.initIframe = function(iframe) {

    //This function is executed twice, though it is called once!
    //But MSIE needs that, otherwise designMode won't work.
    //Weird.
    
    this._iframe = iframe;
    this._doc = iframe.contentWindow.document;
    
    //add css rules from options
    var styles = this._doc.styleSheets[0];
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;
    
    //init html value
    jQuery(this._doc.body).html(this._wym._html);
    
    //handle events
    var wym = this;
    
    this._doc.body.onfocus = function()
      {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;};
    this._doc.onbeforedeactivate = function() {wym.saveCaret();};
    this._doc.onkeyup = function() {
      wym.saveCaret();
      wym.keyup();
    };
    this._doc.onclick = function() {wym.saveCaret();};
    
    this._doc.body.onbeforepaste = function() {
      wym._iframe.contentWindow.event.returnValue = false;
    };
    
    this._doc.body.onpaste = function() {
      wym._iframe.contentWindow.event.returnValue = false;
      wym.paste(window.clipboardData.getData("Text"));
    };
    
    //callback can't be executed twice, so we check
    if(this._initialized) {
      
      //pre-bind functions
      if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
      
      //bind external events
      this._wym.bindEvents();
      
      // NOTE v.mische this part will be changed on event-hacking
      jQuery(this._doc).bind("keydown", this.handleKeydown);
      
      //post-init functions
      if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
      
      //add event listeners to doc elements, e.g. images
      this.listen();
    }
    
    this._initialized = true;
    
    //init designMode
    this._doc.designMode="on";
    try{
        // (bermi's note) noticed when running unit tests on IE6
        // Is this really needed, it trigger an unexisting property on IE6
        this._doc = iframe.contentWindow.document; 
    }catch(e){}
};

WymClassExplorer.prototype._exec = function(cmd,param) {
	//alert("cmd: " + cmd + " param: " + param);
    switch(cmd) {
	    case WYM_INDENT: case WYM_OUTDENT:
	    
	        var container = this.findUp(this.container(), WYM_LI);
	        if(container)
	            this._doc.execCommand(cmd);
	    break;
	    default:
	        if(param){
				//alert("if");
				this._doc.execCommand(cmd,false,param);
	        }else{
				//alert("else");
				this._doc.execCommand(cmd);
			}
	    break;
	}
    this.listen();
};

WymClassExplorer.prototype.selected = function() {

    var caretPos = this._iframe.contentWindow.document.caretPos;
        if(caretPos!=null) {
            if(caretPos.parentElement!=undefined)
              return(caretPos.parentElement());
        }
};

WymClassExplorer.prototype.saveCaret = function() {

    this._doc.caretPos = this._doc.selection.createRange();
};

WymClassExplorer.prototype.addCssRule = function(styles, oCss) {

    styles.addRule(oCss.name, oCss.css);
};

//keyup handler
WymClassExplorer.prototype.keyup = function() {
  this._selected_image = null;
};


WymClassExplorer.prototype.setFocusToNode = function(node) {
    var range = this._doc.selection.createRange();
    range.moveToElementText(node);
    range.collapse(false);
    range.move('character',-1);
    range.select();
    node.focus();
};
WymClassExplorer.prototype.handleKeydown = function(evt) {
  
  //'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  // "start" Selection API
  var sel = wym.selection.getSelection();

/*
    // some small tests for the Selection API
    var containers = WYM_MAIN_CONTAINERS.join(",");
    if (sel.isAtStart(containers))
        alert("isAtStart: "+sel.startNode.parentNode.nodeName);
    if (sel.isAtEnd(containers))
        alert("isAtEnd: "+sel.endNode.parentNode.nodeName);
    if (evt.keyCode==WYM_KEY.DELETE) {
        // if deleteIfExpanded wouldn't work, no selected text would be
        // deleted if you press del-key
        if (sel.deleteIfExpanded())
            return false;
    }
    if (evt.keyCode==WYM_KEY.HOME) {
        // if cursorToStart won't work, the cursor won't be set to start
        // if you press home-key
        sel.cursorToStart(sel.container);
        return false;
    }
    if (evt.keyCode==WYM_KEY.END)
    {
        // if cursorToEnd won't work, the cursor won't be set to the end
        // if you press end-key
        sel.cursorToEnd(sel.container);
        return false;
    }
*/
};

/********** SELECTION API **********/
/*
Class: WymSelExplorer
    Implementation of the Selection API for Microsoft Internet Explorer

Properties:
    original    - (object) Selection Object
    isCollapsed - (boolean) True if selection is collapsed to single position
    startNode   - (DOM node) Node where the selection starts
    startOffset - (integer) Offset of the position in startNode
    endNode     - (DOM node) Node where the selection ends
    endOffset   - (integer) Offset of the position in endNode
    container   - (DOM node) Node which includes the whole selection

Example:
    "|" marks the start and the end of the selection.
    (start code)
    <p>A <b>sm|all</b> examp|le.</p>
    (end code)

    isCollapsed - false
    startNode   - "small"
    startOffset - 2
    endNode     - " example"
    endOffset   - 6
    container   - <p>...</p>
*/
var WymSelExplorer = function(wym) {
    this._wym = wym;
};

WymSelExplorer.prototype = {
    /*
    Property: getSelection
        Return a Selection API object

    Example:
        (start code)
        var sel = wym.selection.getSelection();
        (end code)

    Returns:
        (object) Selection API object
    */
    getSelection: function() {
        var sel = this._wym._iframe.contentWindow.document.selection;
        var range = sel.createRange();

        this.original = sel;

        if (sel.type == "None")
            this.isCollapsed = true;
        else
            this.isCollapsed = false;

        // get start of the selection (resp. cursor position if collapsed)
        var selStart = this._getNodeAndOffset(range.duplicate(), true);
        this.startNode = selStart.node;
        this.startOffset = selStart.offset;

        if (this.isCollapsed) {
            this.endNode = this.startNode;
            this.endOffset = this.startOffset;
        }
        else {
            var selEnd = this._getNodeAndOffset(range.duplicate(), false);
            this.endNode = selEnd.node;
            this.endOffset = selEnd.offset;
        }

        this.container = jQuery(range.parentElement()).parentsOrSelf(
                WYM_MAIN_CONTAINERS.join(","))[0];

        return this;
    },


    _getNodeAndOffset: function(range, collapseToStart) {
        var parentElement = range.parentElement();

        // range from the beginning of parentElement to cursor position
        var parentRange = range.duplicate();

        // length of parentElement's text
        var parentLength;

        // offset from the beginning of parentElements to the cursor position
        var parentOffset = 0;

        // offset of the position to its node
        var offset = 0;

        // collapse to start or end
        range.collapse(collapseToStart);
        // select parent node and set range from the beginning of that node to
        // the cursor position
        parentRange.moveToElementText(parentElement);
        parentLength = parentRange.text.length;
        // XXX v.mische fix it
        var backwardRange = parentRange.duplicate();
        var searchRange = parentRange.duplicate();


        parentRange.setEndPoint("EndToStart", range);
        parentOffset = parentRange.text.length;

        // direction to search
        //  1 = forward
        // -1 = backward
        var direction = 1;

        // where to start to find the position
        var childPosition = 0;

        // ratio of the cursor position to the total parentElement text length
        var offsetRatio = parentOffset/parentLength;

        // offset from start (if search direction==-1: end) to the child node
        // we are currently at while searching the position
        // finally: position of the cursor within its node
        var offset = 0;

        var childNodes = parentElement.childNodes;

        // try to find a better start position for searching
        //if (childNodes.length>20 && offsetRatio>0.1) {
        if (childNodes.length>20 && offsetRatio>0.1) {
            // range around the appropriate node 
            var childRange = searchRange.duplicate();
            // length from start to the beginning of a node near the node
            // of the position we are looking for
            //var childOffsetRange = parentRange.duplicate();

            childPosition = Math.round(offsetRatio * childNodes.length-1);

            if (childPosition <= 0)
                childPosition = 1;

            // moveToElementText doesn't work with text nodes
            while ((childNodes[childPosition].nodeType == WYM_NODE.TEXT
                    || childNodes[childPosition].nodeName == "BR")
                    && childPosition > 0) {
                childPosition--;
            }

            if (childPosition > 0) {
                childRange.moveToElementText(childNodes[childPosition]);
                // Range from start of parent node to the start of the proposed
                // child node
                childRange.setEndPoint("EndToStart", searchRange);

                // search forward
                if (parentOffset > childRange.text.length) {
                    direction = 1;
                    searchRange.setEndPoint("StartToEnd", childRange);
                    searchRange.setEndPoint("EndToEnd", parentRange);
                }
                // search backwards
                else {
                    direction = -1;
                    searchRange.setEndPoint("StartToEnd", parentRange);
                    searchRange.setEndPoint("EndToEnd", childRange);

                    // caused by ranges
                    childPosition--;
                }

            }
            // start at the beginning
            else {
                direction = 1;
                searchRange = parentRange.duplicate();
            }
        }
        else {
            if (direction == 1)
                searchRange = parentRange.duplicate();
            else {
                searchRange = backwardRange.duplicate();
                searchRange.setEndPoint("StartToEnd", parentRange);
            }
        }

        var node=parentElement.childNodes[childPosition];


        var currentOffsetRange = searchRange.duplicate();
        if (direction==1) {
            // text length of the current node
            var nodeLength = 0;
            var br = 0;

            while (node) {
                if (node.nodeName == 'BR')
                    br++;
                nodeLength = this._getTextLength(node);
                if (currentOffsetRange.text.length > nodeLength) {
                    currentOffsetRange.moveStart('character', nodeLength + br);
                    br = 0;
                }
                else
                    break;

                node = node.nextSibling;
            }
            offset = currentOffsetRange.text.length;
        }
        // go backwards
        else {
            var nodeLength = 0;
            var br = 0;

            while (node) {
                if (node.nodeName == 'BR')
                    br++;
                nodeLength = this._getTextLength(node);

                if (currentOffsetRange.text.length > nodeLength) {
                    var length = currentOffsetRange.text.length;
                    currentOffsetRange.moveEnd('character', -(nodeLength+br));
                    // NOTE v.mische Sometimes the range isn't move as far as
                    // expected. I don't know whenwhy it happens, but it does
                    if (br == 0) {
                        var diff = (length-nodeLength) - currentOffsetRange.text.length;
                        currentOffsetRange.moveEnd('character', diff);
                    }
                    br = 0;
                }
                else
                    break;

                if (node.previousSibling)
                    node = node.previousSibling;
                else
                    break;
            }

            offset = nodeLength - currentOffsetRange.text.length;
        }

        return {'node': node, 'offset': offset};
    },

    /*
    Property: _getTextLength
        Get the length of the text of a DOM node.

    Arguments:
        node - (DOM node) [required] The node you what to know the length of.

    Example:
        (start code)
        var node = document.createTextNode("foo");
        _getTextLength(node) // returns 3
        (end code)

    Returns:
        (int) Length of a DOM node.
    */
    _getTextLength: function(node) {
        if (node.nodeType == WYM_NODE.ELEMENT)
            return node.innerText.length;
        else if (node.nodeType == WYM_NODE.TEXT)
            return node.data.length;
    },

    /*
    Property: deleteIfExpanded
        Delete contents of a selection

    Example:
        (start code)
        var sel = wym.selection.getSelection();
        sel.deleteIfExpanded();
        (end code)

    Returns:
        (boolean) True if it was expanded and thus deleted
    */
    deleteIfExpanded: function() {
        if(!this.isCollapsed) {
            this.original.clear();
            return true;
        }
        return false;
    }
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (C) 2007 H.O.net - http://www.honet.be/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.mozilla.js
 *        Gecko specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne@wymeditor.org)
 *        Volker Mische (vmx@gmx.de)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Frédéric Palluel-Lafleur (fpalluel@gmail.com)
 */

function WymClassMozilla(wym) {

    this._wym = wym;
    this._class = "class";
    this._newLine = "\n";
};

WymClassMozilla.prototype.initIframe = function(iframe) {

    this._iframe = iframe;
    this._doc = iframe.contentDocument;
    
    //add css rules from options
    
    var styles = this._doc.styleSheets[0];    
    var aCss = eval(this._options.editorStyles);
    
    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;
    
    //init html value
    this.html(this._wym._html);
    
    //init designMode
    this.enableDesignMode();
    
    //pre-bind functions
    if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
    
    //bind external events
    this._wym.bindEvents();
    
    //bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);
    
    //bind editor keyup events
    jQuery(this._doc).bind("keyup", this.keyup);
    
    //bind editor focus events (used to reset designmode - Gecko bug)
    jQuery(this._doc).bind("focus", this.enableDesignMode);
    
    //post-init functions
    if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
    
    //add event listeners to doc elements, e.g. images
    this.listen();
};

/* @name html
 * @description Get/Set the html value
 */
WymClassMozilla.prototype.html = function(html) {

  if(html) {
  
    //disable designMode
    this._doc.designMode = "off";
    
    //replace em by i and strong by bold
    //(designMode issue)
    html = html.replace(/<em([^>]*)>/gi, "<i$1>")
      .replace(/<\/em>/gi, "</i>")
      .replace(/<strong([^>]*)>/gi, "<b$1>")
      .replace(/<\/strong>/gi, "</b>");
    
    //update the html body
    jQuery(this._doc.body).html(html);
    
    //re-init designMode
    this.enableDesignMode();
  }
  else return(jQuery(this._doc.body).html());
};

WymClassMozilla.prototype._exec = function(cmd,param) {

    if(!this.selected()) return(false);

    switch(cmd) {
    
    case WYM_INDENT: case WYM_OUTDENT:
    
        var focusNode = this.selected();    
        var sel = this._iframe.contentWindow.getSelection();
        var anchorNode = sel.anchorNode;
        if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
        
        focusNode = this.findUp(focusNode, WYM_BLOCKS);
        anchorNode = this.findUp(anchorNode, WYM_BLOCKS);
        
        if(focusNode && focusNode == anchorNode
          && focusNode.tagName.toLowerCase() == WYM_LI) {

            var ancestor = focusNode.parentNode.parentNode;

            if(focusNode.parentNode.childNodes.length>1
              || ancestor.tagName.toLowerCase() == WYM_OL
              || ancestor.tagName.toLowerCase() == WYM_UL)
                this._doc.execCommand(cmd,'',null);
        }

    break;
    
    default:

        if(param) this._doc.execCommand(cmd,'',param);
        else this._doc.execCommand(cmd,'',null);
    }
    
    //set to P if parent = BODY
    var container = this.selected();
    if(container.tagName.toLowerCase() == WYM_BODY)
        this._exec(WYM_FORMAT_BLOCK, WYM_P);
    
    //add event handlers on doc elements

    this.listen();
};

/* @name selected
 * @description Returns the selected container
 */
WymClassMozilla.prototype.selected = function() {

    var sel = this._iframe.contentWindow.getSelection();
    var node = sel.focusNode;
    if(node) {
        if(node.nodeName == "#text") return(node.parentNode);
        else return(node);
    } else return(null);
};

WymClassMozilla.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
        styles.cssRules.length);
};


//keydown handler, mainly used for keyboard shortcuts
WymClassMozilla.prototype.keydown = function(evt) {
  
  //'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  // "start" Selection API
  var sel = wym.selection.getSelection();

/*
    // some small tests for the Selection API
    var containers = WYM_MAIN_CONTAINERS.join(",");
    if (sel.isAtStart(containers))
        alert("isAtStart: "+sel.startNode.parentNode.nodeName);
    if (sel.isAtEnd(containers))
        alert("isAtEnd: "+sel.endNode.parentNode.nodeName);
    if (evt.keyCode==WYM_KEY.DELETE) {
        // if deleteIfExpanded wouldn't work, no selected text would be
        // deleted if you press del-key
        if (sel.deleteIfExpanded())
            return false;
    }
    if (evt.keyCode==WYM_KEY.HOME) {
        // if cursorToStart won't work, the cursor won't be set to start
        // if you press home-key
        sel.cursorToStart(sel.container);
        return false;
    }
    if (evt.keyCode==WYM_KEY.END)
    {
        // if cursorToEnd won't work, the cursor won't be set to the end
        // if you press end-key
        sel.cursorToEnd(sel.container);
        return false;
    }
*/
  
  if(evt.ctrlKey){
    if(evt.keyCode == 66){
      //CTRL+b => STRONG
      wym._exec(WYM_BOLD);
      return false;
    }
    if(evt.keyCode == 73){
      //CTRL+i => EMPHASIS
      wym._exec(WYM_ITALIC);
      return false;
    }
  }
};

//keyup handler, mainly used for cleanups
WymClassMozilla.prototype.keyup = function(evt) {

  //'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  wym._selected_image = null;
  var container = null;

  if(evt.keyCode == 13 && !evt.shiftKey) {
  
    //RETURN key
    //cleanup <br><br> between paragraphs
    jQuery(wym._doc.body).children(WYM_BR).remove();
    
    //fix PRE bug #73
    container = wym.selected();
    if(container && container.tagName.toLowerCase() == WYM_PRE)
        wym._exec(WYM_FORMAT_BLOCK, WYM_P); //create P after PRE
  }
  
  else if(evt.keyCode != 8
       && evt.keyCode != 17
       && evt.keyCode != 46
       && evt.keyCode != 224
       && !evt.metaKey
       && !evt.ctrlKey) {
      
    //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
    //text nodes replaced by P
    
    container = wym.selected();
    var name = container.tagName.toLowerCase();

    //fix forbidden main containers
    if(
      name == "strong" ||
      name == "b" ||
      name == "em" ||
      name == "i" ||
      name == "sub" ||
      name == "sup" ||
      name == "a"

    ) name = container.parentNode.tagName.toLowerCase();

    if(name == WYM_BODY) wym._exec(WYM_FORMAT_BLOCK, WYM_P);
  }
};

WymClassMozilla.prototype.enableDesignMode = function() {
    if(this.designMode == "off") {
      try {
        this.designMode = "on";
        this.execCommand("styleWithCSS", '', false);
      } catch(e) { }
    }
};

WymClassMozilla.prototype.setFocusToNode = function(node) {
    var range = document.createRange();
    range.selectNode(node);
    var selected = this._iframe.contentWindow.getSelection();
    selected.addRange(range);
    selected.collapse(node, node.childNodes.length);
    this._iframe.contentWindow.focus();
};

WymClassMozilla.prototype.openBlockTag = function(tag, attributes)
{
  var attributes = this.validator.getValidTagAttributes(tag, attributes);

  // Handle Mozilla styled spans
  if(tag == 'span' && attributes.style){
    var new_tag = this.getTagForStyle(attributes.style);
    if(new_tag){
      this._tag_stack.pop();
      var tag = new_tag;
      this._tag_stack.push(new_tag);
      attributes.style = '';
    }else{
      return;
    }
  }
  
  this.output += this.helper.tag(tag, attributes, true);
};

WymClassMozilla.prototype.getTagForStyle = function(style) {

  if(/bold/.test(style)) return 'strong';
  if(/italic/.test(style)) return 'em';
  if(/sub/.test(style)) return 'sub';
  if(/sub/.test(style)) return 'super';
  return false;
};

/********** SELECTION API **********/

function WymSelMozilla(wym) {
    this._wym = wym;
};

WymSelMozilla.prototype = {
    getSelection: function() {
        var _sel = this._wym._iframe.contentWindow.getSelection();
        // NOTE v.mische can startNode/endNote be phantom nodes?
        this.startNode = _sel.getRangeAt(0).startContainer;
        this.endNode = _sel.getRangeAt(0).endContainer;
        this.startOffset = _sel.getRangeAt(0).startOffset;
        this.endOffset = _sel.getRangeAt(0).endOffset;
        this.isCollapsed = _sel.isCollapsed;
        this.original = _sel;
        this.container = jQuery(this.startNode).parentsOrSelf(
                WYM_MAIN_CONTAINERS.join(","))[0];

        return this;
    },

    cursorToStart: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var firstTextNode = jQuery(jqexpr)[0];

        while (firstTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!firstTextNode.hasChildNodes())
                break;
            firstTextNode = firstTextNode.firstChild;
        }

        if (isPhantomNode(firstTextNode))
            firstTextNode = firstTextNode.nextSibling;

        // e.g. an <img/>
        if (firstTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(firstTextNode.parentNode, 0);
        else
            this.original.collapse(firstTextNode, 0);
    },

    cursorToEnd: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var lastTextNode = jQuery(jqexpr)[0];

        while (lastTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!lastTextNode.hasChildNodes())
                break;
            lastTextNode = lastTextNode.lastChild;
        }

        if (isPhantomNode(lastTextNode))
            lastTextNode = lastTextNode.previousSibling;

        // e.g. an <img/>
        if (lastTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(lastTextNode.parentNode,
                lastTextNode.parentNode.childNodes.length);
        else
            this.original.collapse(lastTextNode, lastTextNode.length);
    },

    deleteIfExpanded: function() {
        if(!this.original.isCollapsed) {
            this.original.deleteFromDocument();
            return true;
        }
        return false;
    }
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (C) 2007 H.O.net - http://www.honet.be/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http://www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.opera.js
 *        Opera specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne@wymeditor.org)
 */

function WymClassOpera(wym) {

    this._wym = wym;
    this._class = "class";
    this._newLine = "\r\n";
};

WymClassOpera.prototype.initIframe = function(iframe) {

    this._iframe = iframe;
    this._doc = iframe.contentWindow.document;
    
    //add css rules from options
    var styles = this._doc.styleSheets[0];    
    var aCss = eval(this._options.editorStyles);

    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;
    
    //init designMode
    this._doc.designMode = "on";

    //init html value
    this.html(this._wym._html);
    
    //pre-bind functions
    if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
    
    //hide indent and outdent until supported
    jQuery(this._box).find(this._options.toolSelector 
      + '[@name=' + WYM_INDENT +']').hide();
    jQuery(this._box).find(this._options.toolSelector 
      + '[@name=' + WYM_OUTDENT +']').hide();
    
    //bind external events
    this._wym.bindEvents();
    
    //bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);
    
    //bind editor events
    jQuery(this._doc).bind("keyup", this.keyup);
    
    //post-init functions
    if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
    
    //add event listeners to doc elements, e.g. images
    this.listen();
};

WymClassOpera.prototype._exec = function(cmd,param) {

    switch(cmd) {
    
    case WYM_INDENT: case WYM_OUTDENT:
        //TODO: support nested lists
        //Opera creates blockquotes
        this.status("Unsupported feature.");
    break;
    default:
        if(param) this._doc.execCommand(cmd,false,param);
        else this._doc.execCommand(cmd);
    break;
	}
    
    this.listen();
};

WymClassOpera.prototype.selected = function() {

    var sel=this._iframe.contentWindow.getSelection();
    var node=sel.focusNode;
    if(node) {
        if(node.nodeName=="#text")return(node.parentNode);
        else return(node);
    } else return(null);
};

WymClassOpera.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
        styles.cssRules.length);
};

//keydown handler
WymClassOpera.prototype.keydown = function(evt) {
  
  //'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  // "start" Selection API
  var sel = wym.selection.getSelection();

/*
    // some small tests for the Selection API
    var containers = WYM_MAIN_CONTAINERS.join(",");
    if (sel.isAtStart(containers))
        alert("isAtStart: "+sel.startNode.parentNode.nodeName);
    if (sel.isAtEnd(containers))
        alert("isAtEnd: "+sel.endNode.parentNode.nodeName);
    if (evt.keyCode==WYM_KEY.DELETE) {
        // if deleteIfExpanded wouldn't work, no selected text would be
        // deleted if you press del-key
        if (sel.deleteIfExpanded())
            return false;
    }
    if (evt.keyCode==WYM_KEY.HOME) {
        // if cursorToStart won't work, the cursor won't be set to start
        // if you press home-key
        sel.cursorToStart(sel.container);
        return false;
    }
    if (evt.keyCode==WYM_KEY.END)
    {
        // if cursorToEnd won't work, the cursor won't be set to the end
        // if you press end-key
        sel.cursorToEnd(sel.container);
        return false;
    }
*/


  //Get a P instead of no container
  if(!sel.container
      && evt.keyCode != WYM_KEY.ENTER
      && evt.keyCode != WYM_KEY.LEFT
      && evt.keyCode != WYM_KEY.UP
      && evt.keyCode != WYM_KEY.RIGHT
      && evt.keyCode != WYM_KEY.DOWN
      && evt.keyCode != WYM_KEY.BACKSPACE
      && evt.keyCode != WYM_KEY.DELETE)
      wym._exec(WYM_FORMAT_BLOCK, WYM_P);

};

//keyup handler
WymClassOpera.prototype.keyup = function(evt) {

  //'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  wym._selected_image = null;
};

// TODO: implement me
WymClassOpera.prototype.setFocusToNode = function(node) {

};

/********** SELECTION API **********/

function WymSelOpera(wym) {
    this._wym = wym;
};

WymSelOpera.prototype = {
    getSelection: function() {
        var _sel = this._wym._iframe.contentWindow.getSelection();
        // NOTE v.mische can startNode/endNote be phantom nodes?
        this.startNode = _sel.getRangeAt(0).startContainer;
        this.endNode = _sel.getRangeAt(0).endContainer;
        this.startOffset = _sel.getRangeAt(0).startOffset;
        this.endOffset = _sel.getRangeAt(0).endOffset;
        this.isCollapsed = _sel.isCollapsed;
        this.original = _sel;
        this.container = jQuery(this.startNode).parentsOrSelf(
                WYM_MAIN_CONTAINERS.join(","))[0];

        return this;
    },

    cursorToStart: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var firstTextNode = jQuery(jqexpr)[0];

        while (firstTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!firstTextNode.hasChildNodes())
                break;
            firstTextNode = firstTextNode.firstChild;
        }

        if (isPhantomNode(firstTextNode))
            firstTextNode = firstTextNode.nextSibling;

        // e.g. an <img/>
        if (firstTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(firstTextNode.parentNode, 0);
        else
            this.original.collapse(firstTextNode, 0);
    },

    cursorToEnd: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var lastTextNode = jQuery(jqexpr)[0];

        while (lastTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!lastTextNode.hasChildNodes())
                break;
            lastTextNode = lastTextNode.lastChild;
        }

        if (isPhantomNode(lastTextNode))
            lastTextNode = lastTextNode.previousSibling;

        // e.g. an <img/>
        if (lastTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(lastTextNode.parentNode,
                lastTextNode.parentNode.childNodes.length);
        else
            this.original.collapse(lastTextNode, lastTextNode.length);
    },

    deleteIfExpanded: function() {
        if(!this.original.isCollapsed) {
            this.original.deleteFromDocument();
            return true;
        }
        return false;
    }
};
/*
 * WYMeditor : what you see is What You Mean web-based editor
 * Copyright (C) 2007 H.O.net - http:// www.honet.be/
 * Dual licensed under the MIT (MIT-license.txt)
 * and GPL (GPL-license.txt) licenses.
 *
 * For further information visit:
 *        http:// www.wymeditor.org/
 *
 * File Name:
 *        jquery.wymeditor.safari.js
 *        Safari specific class and functions.
 *        See the documentation for more info.
 *
 * File Authors:
 *        Jean-Francois Hovinne (jf.hovinne@wymeditor.org)
 *        Volker Mische (vmx@gmx.de)
 *        Bermi Ferrer (wymeditor a-t bermi dotorg)
 *        Frédéric Palluel-Lafleur (fpalluel@gmail.com)
 *        Scott Lewis (lewiscot@gmail.com)
 */

var WYM_UNLINK               = "Unlink";
var WYM_INSERT_UNORDEREDLIST = "InsertUnorderedList";
var WYM_INSERT_ORDEREDLIST   = "InsertOrderedList";

function WymClassSafari(wym) {
    this._wym = wym;
    this._class = "class";
    this._newLine = "\n";
    wym._options.updateEvent = "mousedown";
};

/* @name initIframe
 * @description Initializes the iframe document for editing.
 * @param document iframe An iframe document to initialize.
 * @return void
 */
WymClassSafari.prototype.initIframe = function(iframe) {

    this._iframe = iframe;
    this._doc = iframe.contentDocument;
    
    // this._doc.execCommand('useCSS', false);
    
    // add css rules from options
    
    var styles = this._doc.styleSheets[0];    
    var aCss = eval(this._options.editorStyles);
    
    this.addCssRules(this._doc, aCss);

    this._doc.title = this._wym._index;
    
    // init html value
    this.html(this._wym._html);
    
    // init designMode
    this.enableDesignMode();

    // detect when text is selected via double-click
    jQuery(this._doc).bind("dblclick", this.dblclick);
    
    // pre-bind functions
    if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
    
    // bind external events
    this._wym.bindEvents();
    
    // bind editor keydown events
    jQuery(this._doc).bind("keydown", this.keydown);
    
    // bind editor keyup events
    jQuery(this._doc).bind("keyup", this.keyup);
    
    // bind editor focus events (used to reset designmode - Gecko bug)
    jQuery(this._doc).bind("focus", this.enableDesignMode);
    
    // post-init functions
    if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
    
    // add event listeners to doc elements, e.g. images
    this.listen();
    
    _test(function() {alert(1);});
};

WymClassSafari.prototype.dblclick = function(evt) {
   window.isDblClick = true;
};

/* @name html
 * @description Get/Set the html value
 * @param string html The string representation of the html being edited.
 * @return string The html being edited
 */
WymClassSafari.prototype.html = function(html) {

  if(html) {
  
    // disable designMode
    this._doc.designMode = "off";
    
    // replace em by i and strong by bold
    // (designMode issue)
    html = html.replace(/<em([^>]*)>/gi, "<i$1>")
      .replace(/<\/em>/gi, "</i>")
      .replace(/<strong([^>]*)>/gi, "<b$1>")
      .replace(/<\/strong>/gi, "</b>");
    
    // update the html body
    jQuery(this._doc.body).html(html);
    
    // re-init designMode
    this.enableDesignMode();
  }
  else return(jQuery(this._doc.body).html());
};

/* @name _exec
 * @description Wymeditor's custom execCommand interface. Since certain commands 
 * are either un-supported or incorrectly implemented, we can re-route the command 
 * through our own custom handlers.
 *
 * @param string cmd The command to be executed
 * @param string param The parameter needed for the command. What the param is depends 
 * on the command being executed. (We need a list of commands and parameters).
 */
WymClassSafari.prototype._exec = function(cmd, param) {

    var focusNode = this.selected();    
    var sel = this.selection.getSelection();

    if (sel.anchorNode)
    {
        var anchorNode = sel.anchorNode;
        if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
    }
    
    if (focusNode && focusNode.nodeName.toLowerCase() == WYM_BR)
    {
       var _newFocusNode = focusNode.parentNode;
       jQuery(focusNode).remove();
       focusNode = _newFocusNode;
    }

    switch(cmd) {
    
        case WYM_INDENT:
            this.Outdent(focusNode, param);
            break;
            
        case WYM_OUTDENT:
            this.Indent(focusNode, param);
        break;
        
        case WYM_OUTDENT:
            this.Indent(focusNode, param);
            break;
        
        case WYM_INDENT:
            this.Indent(focusNode, param);
            break;
        
        case WYM_CREATE_LINK:
            this.CreateLink(focusNode, param);
            break;
        
        case WYM_UNLINK:
            this.Unlink(focusNode, param);
            break;
        
        case WYM_INSERT_IMAGE:
            this.InsertImage(focusNode, param);
            break;

        case WYM_INSERT_UNORDEREDLIST:
            this.InsertUnorderedList(focusNode, param);
            break;
    
        default:
    
            if(param) this._doc.execCommand(cmd,'',param);
            else this._doc.execCommand(cmd,'',null);
    }
    
    // set to P if parent = BODY
    var container = this.selected();
    if(container && container.tagName.toLowerCase() == WYM_BODY)
        this._exec(WYM_FORMAT_BLOCK, WYM_P);
    
    // Update the textarea
    this.update();

    // add event handlers on doc elements
    this.listen();
};

/* @name selected
 * @description Returns the selected container
 * @return object The currently selected container
 */
WymClassSafari.prototype.selected = function() {
    
    var sel = this._iframe.contentWindow.getSelection();
    
    var node = false;
    if (sel.focusNode)
    {
        node = sel.focusNode;
    }
    if(node) {
        if(node.nodeName == "#text") return(node.parentNode);
        else return(node);
    } else return(null); // this._iframe.contentDocument.body);
};

WymClassSafari.prototype.addCssRule = function(styles, oCss) {

    styles.insertRule(oCss.name + " {" + oCss.css + "}",
        styles.cssRules.length);
};

/* @name keydown
 * @description keydown handler, mainly used for keyboard shortcuts
 * @return bool Whether or not the calling event should be continued
 */
WymClassSafari.prototype.keydown = function(evt) {
  
  // 'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  //  "start" Selection API
  var sel = wym.selection.getSelection();

/*
    //  some small tests for the Selection API
    var containers = WYM_MAIN_CONTAINERS.join(",");
    if (sel.isAtStart(containers))
        alert("isAtStart: "+sel.startNode.parentNode.nodeName);
    if (sel.isAtEnd(containers))
        alert("isAtEnd: "+sel.endNode.parentNode.nodeName);
    if (evt.keyCode==WYM_KEY.DELETE) {
        //  if deleteIfExpanded wouldn't work, no selected text would be
        //  deleted if you press del-key
        if (sel.deleteIfExpanded())
            return false;
    }
    if (evt.keyCode==WYM_KEY.HOME) {
        //  if cursorToStart won't work, the cursor won't be set to start
        //  if you press home-key
        sel.cursorToStart(sel.container);
        return false;
    }
    if (evt.keyCode==WYM_KEY.END)
    {
        //  if cursorToEnd won't work, the cursor won't be set to the end
        //  if you press end-key
        sel.cursorToEnd(sel.container);
        return false;
    }
*/

  if(evt.ctrlKey){
    if(evt.keyCode == 66){
      // CTRL+b => STRONG
      wym._exec(WYM_BOLD);
      return false;
    }
    if(evt.keyCode == 73){
      // CTRL+i => EMPHASIS
      wym._exec(WYM_ITALIC);
      return false;
    }
  }
};

/* @name keyup
 * @description keyup handler, mainly used for cleanups
 */
WymClassSafari.prototype.keyup = function(evt) {

  // 'this' is the doc
  var wym = WYM_INSTANCES[this.title];
  
  wym._selected_image = null;

  if(evt.keyCode == 13 && !evt.shiftKey) {
  
    // RETURN key
    // cleanup <br><br> between paragraphs
    jQuery(wym._doc.body).children(WYM_BR).remove();
  }
  
  else if(evt.keyCode != 8
       && evt.keyCode != 17
       && evt.keyCode != 46
       && evt.keyCode != 224
       && !evt.metaKey
       && !evt.ctrlKey) {
      
    // NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
    // text nodes replaced by P
    
    var container = wym.selected();
    var name = container.tagName.toLowerCase();

    // fix forbidden main containers
    if(
      name == "strong" ||
      name == "b" ||
      name == "em" ||
      name == "i" ||
      name == "sub" ||
      name == "sup" ||
      name == "a"

   ) name = container.parentNode.tagName.toLowerCase();

    if(name == WYM_BODY) wym._exec(WYM_FORMAT_BLOCK, WYM_P);
  }
};

/* @name enableDesignMode
 * @description Enables live editing of the iframe document.
 */
WymClassSafari.prototype.enableDesignMode = function() {
    
    if(this._doc.designMode == "off") {
      try {
        this._doc.designMode = "on";
        this._doc.execCommand("styleWithCSS", false, false);
      } catch(e) { }
    }
};

/* @name setFocusToNode
 * @description Sets the focus to currently selected node.
 */
WymClassSafari.prototype.setFocusToNode = function(node) {
    var range = document.createRange();
    range.selectNode(node);
    var selected = this._iframe.contentWindow.getSelection();
    selected.addRange(range);
    selected.collapse(node, node.childNodes.length);
    this._iframe.contentWindow.focus();
};

/* @name openBlockTag
 * @description This function may not currently be in use. (Bermi?)
 */
WymClassSafari.prototype.openBlockTag = function(tag, attributes)
{
  var attributes = this.validator.getValidTagAttributes(tag, attributes);

  //  Handle Safari styled spans
  if(tag == 'span' && attributes.style){
    var new_tag = this.getTagForStyle(attributes.style);
    if(new_tag){
      this._tag_stack.pop();
      var tag = new_tag;
      this._tag_stack.push(new_tag);
      attributes.style = '';
    }else{
      return;
    }
  }
  
  if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){
    this.output = this.output.replace(/<\/li>$/, '');
    this.insertContentAfterClosingTag(tag, '</li>');
  }
  
  this.output += this.helper.tag(tag, attributes, true);
};

/* @name closeBlockTag
 * @description This function may not currently be in use. (Bermi?)
 */
WymClassSafari.prototype.closeBlockTag = function(tag)
{
  this.output = this.output.replace(/<br \/>$/, '')+this._getClosingTagContent('before', tag)+"</"+tag+">"+this._getClosingTagContent('after', tag);
};

/* @name getTagForStyle
 * @description Converts styled tags to the correct (i.e., syntactically valid) xhtml tag.
 * @param string style The styled tag (?)
 * @return string|bool Returns the string tag name if matched. False if not matched.
 */
WymClassSafari.prototype.getTagForStyle = function(style) {

  if(/bold/.test(style)) return 'strong';
  if(/italic/.test(style)) return 'em';
  if(/sub/.test(style)) return 'sub';
  if(/sub/.test(style)) return 'super';
  return false;
};

/********** SELECTION API **********/

/* @name WymSelSafari
 * @description Creates an instance of the SAPI
 * @param object The current wymeditor instance.
 * @return void
 */
function WymSelSafari(wym) {
    this._wym = wym;
};

/* @name WymSelSafari
 * @description The Selection Application Programming Interface (SAPI)
 */
WymSelSafari.prototype = {
    getSelection: function() {
        var _sel = this._wym._iframe.contentWindow.getSelection();
        var range = this._getRange();

        this.startNode   = this._startNode(_sel, range);
        this.endNode     = this._endNode(_sel, range);
        this.startOffset = range.startOffset;
        this.endOffset   = range.endOffset;
        this.length      = new String(_sel).length;
        
        this.isCollapsed = _sel.isCollapsed;
        this.original    = _sel;
        this.container   = jQuery(this.startNode).parentsOrSelf(WYM_MAIN_CONTAINERS.join(","))[0];
        
        return this;
    },
    
    newNode: function() {
        var _id = this._wym.uniqueStamp();
        jQuery(this._wym._iframe.contentDocument.body).append('<p id="' + _id + '"></p>');
        return this._wym._iframe.contentDocument.getElementById(_id);
    },
    
    _getRange: function()
    {
        return this._wym._iframe.contentDocument.createRange();
    },

    _startNode: function(_sel, range) {
        var node;
        if (_sel.baseNode && _sel.basNode == "#text")
        {
            node = _sel.baseNode.parentNode;
        }
        if (_sel.baseNode)
        {
            node = _sel.baseNode;
        }
        else
        {
            node = range.startNode;
        }
        return node;
    },
    
    _endNode: function(_sel, range) {
        var node;
        if (_sel.focusNode && _sel.focusNode == "#text")
        {
            node = _sel.focusNode.parentNode;
        }
        if (_sel.focusNode)
        {
            node = _sel.focusNode;
        }
        else
        {
            node = range.endNode;
        }
        return node;
    },
    
    cursorToStart: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var firstTextNode = $(jqexpr)[0];

        while (firstTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!firstTextNode.hasChildNodes())
                break;
            firstTextNode = firstTextNode.firstChild;
        }

        if (isPhantomNode(firstTextNode))
            firstTextNode = firstTextNode.nextSibling;

        //  e.g. an <img/>
        if (firstTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(firstTextNode.parentNode, 0);
        else
            this.original.collapse(firstTextNode, 0);
    },

    cursorToEnd: function(jqexpr) {
        if (jqexpr.nodeType == WYM_NODE.TEXT)
            jqexpr = jqexpr.parentNode;

        var lastTextNode = $(jqexpr)[0];

        while (lastTextNode.nodeType!=WYM_NODE.TEXT) {
            if (!lastTextNode.hasChildNodes())
                break;
            lastTextNode = lastTextNode.lastChild;
        }

        if (isPhantomNode(lastTextNode))
            lastTextNode = lastTextNode.previousSibling;

        //  e.g. an <img/>
        if (lastTextNode.nodeType == WYM_NODE.ELEMENT)
            this.original.collapse(lastTextNode.parentNode,
                lastTextNode.parentNode.childNodes.length);
        else
            this.original.collapse(lastTextNode, lastTextNode.length);
    },

    deleteIfExpanded: function() {
        if(!this.original.isCollapsed) {
            this.original.deleteFromDocument();
            return true;
        }
        return false;
    }
};

/* @name bindEvents
 * @description Binds Wymeditor events to window events.
 * @return void
 */
WymClassSafari.prototype.bindEvents = function() {

  // copy the instance
  var wym = this;
  
  // handle click event on tools buttons
  jQuery(this._box).find(this._options.toolSelector).mousedown(function() {
    wym.exec(jQuery(this).attr(WYM_NAME));
    return(false);
  });
  
  // handle click event on containers buttons
  jQuery(this._box).find(this._options.containerSelector).mousedown(function() {
    wym.container(jQuery(this).attr(WYM_NAME));
    return(false);
  });
  
  // handle keyup event on html value: set the editor value
  jQuery(this._box).find(this._options.htmlValSelector).keyup(function() {
    jQuery(wym._doc.body).html(jQuery(this).val());
    // jQuery(wym._doc.body).html(wym.addWymHacksForEditMode(jQuery(this).val()));
  });
  
  // handle click event on classes buttons
  jQuery(this._box).find(this._options.classSelector).mousedown(function() {
  
    var aClasses = eval(wym._options.classesItems);
    var sName = jQuery(this).attr(WYM_NAME);
    
    var oClass = aClasses.findByName(sName);
    
    if(oClass) {
      jqexpr = oClass.expr;
      wym.toggleClass(sName, jqexpr);
    }
    return(false);
  });
  
  // handle event on update element
  jQuery(this._options.updateSelector)
    .bind(this._options.updateEvent, function() {
      wym.update();
  });
};

/* @name toggleHtml
 * @description Toggles the display of the HTML textarea
 * @return void
 */
Wymeditor.prototype.toggleHtml = function() {
  var html = this.xhtml();
  jQuery(this._element).val(html);
  jQuery(this._box).find(this._options.htmlSelector).toggle();
  jQuery(this._box).find(this._options.htmlValSelector).val(html);
};

/* @name _debug
 * @description Opens a new window and prints the property names and values.
 * @param object An object to debug.
 * @return void
 */
_debug = function(obj)
{
    win2 = window.open();
    win2.document.write("<pre>");
    for (key in obj)
    {
      win2.document.write(key + ": " + obj[key]);
    }
    win2.document.write("</pre>");
    win2.document.close();
};

/* @name _test
 * @description Dynamically adds a 'test' link to the document so that test events can be attached 
 * to the link.onClick event.
 * @param function callback The test function to attach.
 * @return void
 */
_test = function(callback) {
    jQuery(document.body).append(
        "<p><a href=\"#\" id=\"btn-test\">Test</a></p>"
    );
    jQuery("#btn-test").click(callback);
};

/* @name nativeExecCommands
 * @description An array of execCommands natively supported by the Apple Web-Core browser engine. 
 * This list is current as of Web-Core
 */
var nativeExecCommands = [
    'BackColor',
    'Bold',
    'Copy',
    'Cut',
    'Delete',
    'FontName',
    'FontSize',
    'FontSizeDelta',
    'ForeColor',
    'ForwardDelete',
    'InsertLineBreak',
    'InsertParagraph',
    'InsertText',
    'Italic',
    'JustifyCenter',
    'JustifyFull',
    'JustifyLeft',
    'JustifyNone',
    'JustifyRight',
    'Paste',
    'PasteAndMatchStyle',
    'Print',
    'Redo',
    'SelectAll',
    'Subscript',
    'Superscript',
    'Underline',
    'Undo',
    'Unselect'
];

/* @name isNative
 * @description isNative is a Safari-only hack that checks the 'nativeExecCommands' array 
 * for a command name. It determines if a command can be executed by the native 
 * web-core code or if a custom over-ride needs to be called.
 *
 * @param string cmd The command name
 * @return bool Whether or not the command being checked is natively supported.
 */
WymClassSafari.prototype.isNative = function(cmd)
{
    for (i=0; i<nativeExecCommands.length; i++)
    {
        if (cmd.toLowerCase() == nativeExecCommands[i].toLowerCase())
        {
            return true;
        }
    }
    return false;
};

// WymClassSafari Custom execCommand Handlers

/* @name CreateLink
 * @description Wymeditor's custom handler for CreateLink
 * @param object focusNode The currently selected node
 * @param string param ...
 * @return bool Whether or not the command execution was successful
 */
WymClassSafari.prototype.CreateLink = function(focusNode, param) {
  var sel = this._iframe.contentWindow.getSelection();
  var _doc = this._iframe.contentDocument;
  
  // If nothing is selected, just exit
  if (!sel.focusNode.nodeValue && sel.focusNode.nodeName.toLowerCase() != "img") return;
  
  // Handle linking of images
  if (sel.focusNode.nodeName.toLowerCase() == "img")
  {
	var a = _doc.createElement("A");
	a.appendChild(sel.focusNode.cloneNode(false));
	var fragment = _doc.createDocumentFragment();
	fragment.appendChild(a);
	sel.focusNode.parentNode.replaceChild(fragment, sel.focusNode);
  }
  // Handle updating of an existing link
  else if (sel.focusNode.parentNode && sel.focusNode.parentNode.nodeName.toLowerCase() == "a")
  {
	sel.focusNode.parentNode.href = param;
  }
  // Link the selected text
  else
  {
	this.wrap(sel, "a", {"href":param});
  }
};

/* @name Indent
 * @description Wymeditor's custom handler for Indent
 * @param object focusNode The currently selected node
 * @param string param ...
 * @return bool Whether or not the command execution was successful
 */
WymClassSafari.prototype.Indent = function(focusNode, param) {
    var focusNode = this.selected();    
    var sel = this._iframe.contentWindow.getSelection();
    
    if (sel.anchorNode)
    {
        var anchorNode = sel.anchorNode;
        if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
    }
    
    focusNode = this.findUp(focusNode, WYM_BLOCKS);
    anchorNode = this.findUp(anchorNode, WYM_BLOCKS);
    
    if(focusNode && focusNode == anchorNode
      && focusNode.tagName.toLowerCase() == WYM_LI) {

        var ancestor = focusNode.parentNode.parentNode;

        if(focusNode.parentNode.childNodes.length>1
          || ancestor.tagName.toLowerCase() == WYM_OL
          || ancestor.tagName.toLowerCase() == WYM_UL)
            this._doc.execCommand(cmd,'',null);
    }
};

/* @name InsertImage
 * @description Inserts an image element.
 * @param object focusNode The parentNode of the current selection
 * @param string param The 'src' of the image to insert.
 * @return bool Whether or not the command was successfully executed.
 */
WymClassSafari.prototype.InsertImage = function(focusNode, param) {
  try {
    if (!focusNode)
    {
      focusNode = this.selection.newNode();
    }
    if (focusNode.nodeName.toLowerCase() == WYM_IMG)
    {
        jQuery(focusNode).attr({"src": param});
    } else {
        var opts = {src: param};
        jQuery(focusNode).append(
          this.helper.tag('img', opts)
        );
    }
  } catch(e) {
    return false;
  }
  return true;
};

/* @name InsertUnorderedList
 * @description Wymeditor's custom handler for InsertUnorderedList
 * @param object focusNode The currently selected node
 * @param string param ...
 * @return bool Whether or not the command execution was successful
 */
WymClassSafari.prototype.InsertUnorderedList = function(param, type) {
  var selected = this.selected();
  var contents = selected.innerHTML;
  
  // Last list item
  if(selected.tagName == 'LI' && selected.parentNode && selected.nextSibling == undefined) {
    this.insertTagAfter(this.deleteSelectedNode(), 'p', contents);
  
    // First list item
    } else if(selected.tagName == 'LI' && selected.parentNode && selected.previousSibling == undefined){
      var parent = selected.parentNode;
    this.deleteNode(selected);
    this.insertTagBefore(parent, 'p', contents);
    
    // inline list item
    } else if(selected.tagName == 'LI' && selected.parentNode){
      var parent = selected.parentNode;
      var before = '';
      var after = false;
      for(var i = 0; i < parent.childNodes.length; i++) {
        if(after === false){
          if(parent.childNodes[i] == selected){
            after = '';
          }else{
            before += parent.childNodes[i].outerHTML;
          }
        } else{
          after += parent.childNodes[i].outerHTML;
        }
      }
            
      $(parent).before(this.helper.contentTag(parent.tagName, before));
      var p_details = this.generateContentTagWithId('p', contents);
      $(parent).before(p_details.content);
      var p = this.getById(p_details.id);
      $(parent).before(this.helper.contentTag(parent.tagName, after));
      $(parent).remove();
      this.focusNode(p,1,1);
      
  // On a paragraph
  } else if(selected.tagName == 'P'){
    this.replaceTagWith(selected, type+'+li', contents);
  }
};

/* @name Outdent
 * @description Wymeditor's custom handler for Outdent
 * @param object focusNode The currently selected node
 * @param string param ...
 * @return bool Whether or not the command execution was successful
 */
WymClassSafari.prototype.Outdent = function(focusNode, param) {
    var focusNode = this.selected();    
    var sel = this._iframe.contentWindow.getSelection();
    
    if (sel.anchorNode)
    {
        var anchorNode = sel.anchorNode;
        if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
    }
    focusNode = this.findUp(focusNode, WYM_BLOCKS);
    anchorNode = this.findUp(anchorNode, WYM_BLOCKS);
    
    if(focusNode && focusNode == anchorNode
      && focusNode.tagName.toLowerCase() == WYM_LI) {

        var ancestor = focusNode.parentNode.parentNode;

        if(focusNode.parentNode.childNodes.length>1
          || ancestor.tagName.toLowerCase() == WYM_OL
          || ancestor.tagName.toLowerCase() == WYM_UL)
            this._doc.execCommand(cmd,'',null);
    }
};

/* @name Unlink
 * @description Wymeditor's custom handler for Unlink
 * @param object focusNode The currently selected node
 * @param string param ...
 * @return bool Whether or not the command execution was successful
 */
WymClassSafari.prototype.Unlink = function(focusNode, param) {
    alert('Unlink');
};

// Bermi's Functions

// This is a workarround for select iframe safari bug

WymClassSafari.prototype.addWymHacksForEditMode = function(xhtml) {
    return 
    '<span id="wym_safari_select_all_hack" ' 
    + 'style="height:0.01em;position:absolute;margin-top:-50px;">safari-hack</span>'+xhtml;
};

WymClassSafari.prototype.removeWymAttributesFromXhtml = function(xhtml) {
  return xhtml.replace(/<span id="wym_safari_select_all_hack"[^>]*>safari-hack<\/span>/, '');
};

WymClassSafari.prototype.removeSafarihacks = function(raw_html){
  if(true || jQuery.browser.version < 10000){
    raw_html = raw_html.replace(this.hackChar,'');
  }
  return raw_html;
};

/* @name beforeParsing
 * @description Removes Safari's place-holder BR tags in empty paragraphs.
 * @param string raw The raw HTML
 * @return string The cleaned-up HTML without place-holders
 */
WymClassSafari.prototype.beforeParsing = function(raw)
{
  this.output = '';
  return this.removeSafarihacks(raw).
  // Remove safari place holders
  replace(/([^>]*)<(\w+)><BR class\="khtml-block-placeholder"><\/\2>([^<]*)/g, "<$2>$1$3</$2>");
};

WymClassSafari.prototype.selectAll = function(param) {
  this.currentSelection.setBaseAndExtent(this._doc.body, 0, w._doc.body, w._doc.body.length);
};

/* @name update
 * @description Updates the HTML textarea with the current version of the iframe document
 * @return void
 */
WymClassSafari.prototype.update = function() {
  var html = this.cleanup(this.removeWymAttributesFromXhtml(this.xhtml()));
  jQuery(this._element).val(html);
  jQuery(this._box).find(this._options.htmlValSelector).val(html);
};

/* @name cleanup
 * @description Removes Apple-style-span tags and attributes (currently a bit buggy)
 * @param string xhtml The xhtml including Apple-span-tag tags and attributes
 * @return string The clean xhtml
 */
WymClassSafari.prototype.cleanup = function(xhtml) {
  // remove Safari style spans
  return xhtml.replace(/<span class="Apple-style-span">(.*)<\/span>/gi, "$1")
    // remove any style-span classes from valid elements (e.g., strong, em, etc.)
    .replace(/ class="Apple-style-span"/gi, "");	
};

/* @name handleEnter
 * @description Custom case-handling for when the enter key is pressed.
 * @param event evt The current window event
 * @return bool Whether or not the current event should be continued
 */
WymClassSafari.prototype.handleEnter = function(evt){
  var selected = this.selected();
  if(true || jQuery.browser.version < 10000){    
    
    if(evt.shiftKey){
      if(!this.selectedHtml && this.selectedText == selected.innerHTML){
        selected.innerHTML = this.emptyChar()+'<br />'+this.emptyChar();
      }
      return false;
    }
    this.handleEnterOnListItem(selected);
   
  }
  return true;
};

/* @name handleBackspace
 * @description Custom case-handling for when the back-space key is pressed.
 * @return bool true (allows the current event to continue)
 */
WymClassSafari.prototype.handleBackspace = function(){
  var selected = this.selected();
  if(true || jQuery.browser.version < 10000){
    if(selected.tagName == 'P' && selected.innerHTML == ''){
      // Todo: move caret to the end of previous sibling
      var parent = selected.parentNode;
      parent.removeChild(selected);
    }
  }
  return true;
};

/* @name handleEnterOnListItem
 * @description Custom case-handling for when the back-space key is pressed inside a list item 
 * (not fully implemented)
 * @return void
 */
WymClassSafari.prototype.handleEnterOnListItem = function(selected)
{
  if(selected.tagName == 'LI' && selected.parentNode) {
    // If we access the text on the right of current carret a new list item will be added
    if((this.isCollapsed && selected.innerHTML && this.selectionCopy.d ? 
        selected.innerHTML.substring(this.selectionCopy.d) : '') == ''){
      // remove the last empty list item and insert a new paragraph
      if(selected.innerHTML.trim() == ''){
        this.insertTagAfter(this.deleteSelectedNode(), 'p', '');
      // New list item
      }else if (this.isCollapsed){
        this.insertTagAfter(selected, 'li', '');

      // we are selecting all the text in a list item
      }else if(this.selectedText == selected.innerHTML || selected.innerHTML == this.selectedHtml){

        if(selected.nextSibling == undefined){ // last item
          this.insertTagAfter(this.deleteSelectedNode(), 'p', '');
        }else{
          this.insertTagAfter(this.deleteContents(selected), 'li', '');
        }
      }else{
        // New list item
        var selectedText = this.selectedHtml || this.selectedText;
        // selecting text up to the end
        if(this.selectionCopy.b+selectedText.length == selected.innerHTML.length){
            this.removeSelection();
            // replace the HTML with the left of the selection
            selected.innerHTML = selected.innerHTML.substring(0,this.selectionCopy.b);
            this.insertTagAfter(selected, 'li', '');
        }

      }
    }
  }
};

/* @name wrap
 * @description Wrap a selection in a container tag
 * @param object sel The selection object
 * @param string tag The name of the tag to wrap the selection
 * @param object options The attributes to apply to the new tag
 * @return void
 *
 * A hearty thanks to Brian Donovan (http://dev.lophty.com) for this solution. 
 * I had the right idea and had it partially working but without his Ahoy code examples 
 * and thorough explanations, it would have taken me a lot longer to solve (if at all).
 */
WymClassSafari.prototype.wrap = function(sel, tag, options) {
  var _doc = this._iframe.contentDocument;
  var fragment  = _doc.createDocumentFragment();

  // Create range before selection
  // When text is selected via double-click, Safari jacks the offsets 
  // so we need to detect the double-click and adjust accordingly.
  var preSelectionRange = this.range(
    _doc, sel, sel.anchorNode, sel.anchorNode, 0, 
    window.isDblClick ? sel.anchorOffset - 2 : sel.anchorOffset);

  // Create the range for the selection
  var range = this.range(
	_doc, sel, sel.anchorNode, sel.focusNode, 
	sel.anchorOffset, sel.focusOffset);

  // Create range after selection
  // When text is selected via double-click, Safari jacks the offsets 
  // so we need to detect the double-click and adjust accordingly.
  var postSelectionRange = this.range(
	_doc, sel, sel.focusNode, sel.focusNode, 
	window.isDblClick ? sel.focusOffset + 2 : sel.focusOffset,
	sel.focusNode.nodeValue.length);

  // Create the new 'wrap' tag
  var wrapper = this._iframe.contentDocument.createElement(tag);
  for (prop in options)
  {
    wrapper[prop] = options[prop];	
  }
  wrapper.appendChild(_doc.createTextNode(sel));

  // Append the 3 nodes to the document fragment in
  // consecutive order
  fragment.appendChild(_doc.createTextNode(preSelectionRange.toString()));
  fragment.appendChild(wrapper);
  fragment.appendChild(_doc.createTextNode(postSelectionRange.toString()));

  // Replace the text node containing the selection with
  // the document fragment that we've prepared.
  sel.anchorNode.parentNode.replaceChild(fragment, sel.anchorNode.parentNode.childNodes[0]);
};

/* @name range
 * @description Creates a new range and sets the start and end
 * @param object doc A document object
 * @param object sel The selection object
 * @param object startNode The beginning node of the selection
 * @param object endNode The end node of the selection
 * @param int startOffset The beginning offset of the selection
 * @param int endOffset The end offset of the selection
 * @return object A range object
 */
WymClassSafari.prototype.range = function(doc, sel, startNode, endNode, startOffset, endOffset) {
  var range = doc.createRange();
  range.setStart(startNode, startOffset);
  range.setEnd(endNode, endOffset);
  return range;	
};

