/** * @version 0.5-rc1 * * WYMeditor : what you see is What You Mean web-based editor * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/ * Dual licensed under the MIT (MIT-license.txt) * and GPL (GPL-license.txt) licenses. * * For further information visit: * http://www.wymeditor.org/ * * File: jquery.wymeditor.js * * Main JS file with core classes and functions. * See the documentation for more info. * * About: authors * * Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg) * Volker Mische (vmx a-t gmx dotde) * Scott Lewis (lewiscot a-t gmail dotcom) * Bermi Ferrer (wymeditor a-t bermi dotorg) * Daniel Reszka (d.reszka a-t wymeditor dotorg) * Jonatan Lundin (jonatan.lundin a-t gmail dotcom) */ /* Namespace: WYMeditor Global WYMeditor namespace. */ if(!WYMeditor) var WYMeditor = {}; //Wrap the Firebug console in WYMeditor.console (function() { if ( !window.console || !console.firebug ) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; WYMeditor.console = {}; for (var i = 0; i < names.length; ++i) WYMeditor.console[names[i]] = function() {} } else WYMeditor.console = window.console; })(); jQuery.extend(WYMeditor, { /* Constants: Global WYMeditor constants. VERSION - Defines WYMeditor version. INSTANCES - An array of loaded WYMeditor.editor instances. STRINGS - An array of loaded WYMeditor language pairs/values. SKINS - An array of loaded WYMeditor skins. NAME - The "name" attribute. INDEX - A string replaced by the instance index. WYM_INDEX - A string used to get/set the instance index. BASE_PATH - A string replaced by WYMeditor's base path. SKIN_PATH - A string replaced by WYMeditor's skin path. WYM_PATH - A string replaced by WYMeditor's main JS file path. SKINS_DEFAULT_PATH - The skins default base path. SKINS_DEFAULT_CSS - The skins default CSS file. LANG_DEFAULT_PATH - The language files default path. IFRAME_BASE_PATH - A string replaced by the designmode iframe's base path. IFRAME_DEFAULT - The iframe's default base path. JQUERY_PATH - A string replaced by the computed jQuery path. DIRECTION - A string replaced by the text direction (rtl or ltr). LOGO - A string replaced by WYMeditor logo. TOOLS - A string replaced by the toolbar's HTML. TOOLS_ITEMS - A string replaced by the toolbar items. TOOL_NAME - A string replaced by a toolbar item's name. TOOL_TITLE - A string replaced by a toolbar item's title. TOOL_CLASS - A string replaced by a toolbar item's class. CLASSES - A string replaced by the classes panel's HTML. CLASSES_ITEMS - A string replaced by the classes items. CLASS_NAME - A string replaced by a class item's name. CLASS_TITLE - A string replaced by a class item's title. CONTAINERS - A string replaced by the containers panel's HTML. CONTAINERS_ITEMS - A string replaced by the containers items. CONTAINER_NAME - A string replaced by a container item's name. CONTAINER_TITLE - A string replaced by a container item's title. CONTAINER_CLASS - A string replaced by a container item's class. HTML - A string replaced by the HTML view panel's HTML. IFRAME - A string replaced by the designmode iframe. STATUS - A string replaced by the status panel's HTML. DIALOG_TITLE - A string replaced by a dialog's title. DIALOG_BODY - A string replaced by a dialog's HTML body. BODY - The BODY element. STRING - The "string" type. BODY,DIV,P, H1,H2,H3,H4,H5,H6, PRE,BLOCKQUOTE, A,BR,IMG, TABLE,TD,TH, UL,OL,LI - HTML elements string representation. CLASS,HREF,SRC, TITLE,REL,ALT - HTML attributes string representation. DIALOG_LINK - A link dialog type. DIALOG_IMAGE - An image dialog type. DIALOG_TABLE - A table dialog type. DIALOG_PASTE - A 'Paste from Word' dialog type. BOLD - Command: (un)set selection to . ITALIC - Command: (un)set selection to . CREATE_LINK - Command: open the link dialog or (un)set link. INSERT_IMAGE - Command: open the image dialog or insert an image. INSERT_TABLE - Command: open the table dialog. PASTE - Command: open the paste dialog. INDENT - Command: nest a list item. OUTDENT - Command: unnest a list item. TOGGLE_HTML - Command: display/hide the HTML view. FORMAT_BLOCK - Command: set a block element to another type. PREVIEW - Command: open the preview dialog. UNLINK - Command: unset a link. INSERT_UNORDEREDLIST- Command: insert an unordered list. INSERT_ORDEREDLIST - Command: insert an ordered list. MAIN_CONTAINERS - An array of the main HTML containers used in WYMeditor. BLOCKS - An array of the HTML block elements. KEY - Standard key codes. NODE - Node types. */ VERSION : "0.5-rc1", INSTANCES : [], STRINGS : [], SKINS : [], NAME : "name", INDEX : "{Wym_Index}", WYM_INDEX : "wym_index", BASE_PATH : "{Wym_Base_Path}", CSS_PATH : "{Wym_Css_Path}", WYM_PATH : "{Wym_Wym_Path}", SKINS_DEFAULT_PATH : "skins/", SKINS_DEFAULT_CSS : "skin.css", SKINS_DEFAULT_JS : "skin.js", LANG_DEFAULT_PATH : "lang/", IFRAME_BASE_PATH : "{Wym_Iframe_Base_Path}", IFRAME_DEFAULT : "iframe/default/", JQUERY_PATH : "{Wym_Jquery_Path}", DIRECTION : "{Wym_Direction}", LOGO : "{Wym_Logo}", TOOLS : "{Wym_Tools}", TOOLS_ITEMS : "{Wym_Tools_Items}", TOOL_NAME : "{Wym_Tool_Name}", TOOL_TITLE : "{Wym_Tool_Title}", TOOL_CLASS : "{Wym_Tool_Class}", CLASSES : "{Wym_Classes}", CLASSES_ITEMS : "{Wym_Classes_Items}", CLASS_NAME : "{Wym_Class_Name}", CLASS_TITLE : "{Wym_Class_Title}", CONTAINERS : "{Wym_Containers}", CONTAINERS_ITEMS : "{Wym_Containers_Items}", CONTAINER_NAME : "{Wym_Container_Name}", CONTAINER_TITLE : "{Wym_Containers_Title}", CONTAINER_CLASS : "{Wym_Container_Class}", HTML : "{Wym_Html}", IFRAME : "{Wym_Iframe}", STATUS : "{Wym_Status}", DIALOG_TITLE : "{Wym_Dialog_Title}", DIALOG_BODY : "{Wym_Dialog_Body}", STRING : "string", BODY : "body", DIV : "div", P : "p", H1 : "h1", H2 : "h2", H3 : "h3", H4 : "h4", H5 : "h5", H6 : "h6", PRE : "pre", BLOCKQUOTE : "blockquote", A : "a", BR : "br", IMG : "img", TABLE : "table", TD : "td", TH : "th", UL : "ul", OL : "ol", LI : "li", CLASS : "class", HREF : "href", SRC : "src", TITLE : "title", REL : "rel", ALT : "alt", DIALOG_LINK : "Link", DIALOG_IMAGE : "Image", DIALOG_TABLE : "Table", DIALOG_PASTE : "Paste_From_Word", BOLD : "Bold", ITALIC : "Italic", CREATE_LINK : "CreateLink", INSERT_IMAGE : "InsertImage", INSERT_TABLE : "InsertTable", INSERT_HTML : "InsertHTML", PASTE : "Paste", INDENT : "Indent", OUTDENT : "Outdent", TOGGLE_HTML : "ToggleHtml", FORMAT_BLOCK : "FormatBlock", PREVIEW : "Preview", UNLINK : "Unlink", INSERT_UNORDEREDLIST: "InsertUnorderedList", INSERT_ORDEREDLIST : "InsertOrderedList", MAIN_CONTAINERS : new Array("p","h1","h2","h3","h4","h5","h6","pre","blockquote"), 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"), 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 }, NODE : { ELEMENT: 1, ATTRIBUTE: 2, TEXT: 3 }, /* Class: WYMeditor.editor WYMeditor editor main class, instanciated for each editor occurrence. */ editor : function(elem, options) { /* Constructor: WYMeditor.editor Initializes main values (index, elements, paths, ...) and call WYMeditor.editor.init which initializes the editor. Parameters: elem - The HTML element to be replaced by the editor. options - The hash of options. Returns: Nothing. See Also: */ //store the instance in the INSTANCES array and store the index this._index = WYMeditor.INSTANCES.push(this) - 1; //store the element replaced by the editor this._element = elem; //store the options this._options = options; //store the element's inner value this._html = jQuery(elem).val(); //store the HTML option, if any if(this._options.html) this._html = this._options.html; //get or compute the base path (where the main JS file is located) this._options.basePath = this._options.basePath || this.computeBasePath(); //get or set the skin path (where the skin files are located) this._options.skinPath = this._options.skinPath || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH + this._options.skin + '/'; //get or compute the main JS file location this._options.wymPath = this._options.wymPath || this.computeWymPath(); //get or set the language files path this._options.langPath = this._options.langPath || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH; //get or set the designmode iframe's base path this._options.iframeBasePath = this._options.iframeBasePath || this._options.basePath + WYMeditor.IFRAME_DEFAULT; //get or compute the jQuery JS file location this._options.jQueryPath = this._options.jQueryPath || this.computeJqueryPath(); //initialize the editor instance this.init(); } }); /********** 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, skinPath: false, wymPath: false, iframeBasePath: false, jQueryPath: false, styles: false, stylesheet: false, skin: "default", initSkin: true, loadSkin: true, lang: "en", direction: "ltr", boxHtml: "
" + "
" + WYMeditor.TOOLS + "
" + "
" + "
" + WYMeditor.CONTAINERS + WYMeditor.CLASSES + "
" + "
" + WYMeditor.HTML + WYMeditor.IFRAME + WYMeditor.STATUS + "
" + "
" + WYMeditor.LOGO + "
" + "
", logoHtml: "WYMeditor", iframeHtml:"
" + "" + "
", editorStyles: [], toolsHtml: "
" + "

{Tools}

" + "
    " + WYMeditor.TOOLS_ITEMS + "
" + "
", toolsItemHtml: "
  • " + WYMeditor.TOOL_TITLE + "
  • ", 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: "
    " + "

    {Containers}

    " + "
      " + WYMeditor.CONTAINERS_ITEMS + "
    " + "
    ", containersItemHtml:"
  • " + "" + WYMeditor.CONTAINER_TITLE + "
  • ", 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: "
    " + "

    {Classes}

      " + WYMeditor.CLASSES_ITEMS + "
    ", classesItemHtml: "
  • " + WYMeditor.CLASS_TITLE + "
  • ", classesItems: [], statusHtml: "
    " + "

    {Status}

    " + "
    ", htmlHtml: "
    " + "

    {Source_Code}

    " + "" + "
    ", boxSelector: ".wym_box", toolsSelector: ".wym_tools", toolsListSelector: " ul", containersSelector:".wym_containers", classesSelector: ".wym_classes", htmlSelector: ".wym_html", iframeSelector: ".wym_iframe iframe", iframeBodySelector:".wym_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", relSelector: ".wym_rel", altSelector: ".wym_alt", textSelector: ".wym_text", rowsSelector: ".wym_rows", colsSelector: ".wym_cols", captionSelector: ".wym_caption", summarySelector: ".wym_summary", submitSelector: "form", 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", dialogFeaturesPreview: "menubar=no,titlebar=no,toolbar=no,resizable=no" + ",scrollbars=yes,width=560,height=300,top=0,left=0", dialogHtml: "" + "" + "" + "" + WYMeditor.DIALOG_TITLE + "" + "" + "" + "" + WYMeditor.DIALOG_BODY + "", dialogLinkHtml: "" + "
    " + "
    " + "" + "{Link}" + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "", dialogImageHtml: "" + "
    " + "
    " + "" + "{Image}" + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "", dialogTableHtml: "" + "
    " + "
    " + "" + "{Table}" + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "", dialogPasteHtml: "" + "
    " + "" + "
    " + "{Paste_From_Word}" + "
    " + "" + "
    " + "
    " + "" + "" + "
    " + "
    " + "
    " + "", dialogPreviewHtml: "", dialogStyles: [], stringDelimiterLeft: "{", stringDelimiterRight:"}", preInit: null, preBind: null, postInit: null, preInitDialog: null, postInitDialog: null }, options); return this.each(function() { new WYMeditor.editor(jQuery(this),options); }); }; /* @name extend * @description Returns the WYMeditor instance based on its index */ jQuery.extend({ wymeditors: function(i) { return (WYMeditor.INSTANCES[i]); } }); /********** WYMeditor **********/ /* @name Wymeditor * @description WYMeditor class */ /* @name init * @description Initializes a WYMeditor instance */ WYMeditor.editor.prototype.init = function() { //load subclass - browser specific //unsupported browsers: do nothing if (jQuery.browser.msie) { var WymClass = new WYMeditor.WymClassExplorer(this); } else if (jQuery.browser.mozilla) { var WymClass = new WYMeditor.WymClassMozilla(this); } else if (jQuery.browser.opera) { var WymClass = new WYMeditor.WymClassOpera(this); } else if (jQuery.browser.safari) { var WymClass = new WYMeditor.WymClassSafari(this); } if(WymClass) { if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this); var SaxListener = new WYMeditor.XhtmlSaxListener(); jQuery.extend(SaxListener, WymClass); this.parser = new WYMeditor.XhtmlParser(SaxListener); if(this._options.styles || this._options.stylesheet){ this.configureEditorUsingRawCss(); } this.helper = new WYMeditor.XmlHelper(); //extend the Wymeditor object //don't use jQuery.extend since 1.1.4 //jQuery.extend(this, WymClass); for (var prop in WymClass) { this[prop] = WymClass[prop]; } //load wymbox this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index); //store the instance index in wymbox and element replaced by editor instance //but keep it compatible with jQuery < 1.2.3, see #122 if( jQuery.isFunction( jQuery.fn.data ) ) { jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index); jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index); } var h = WYMeditor.Helper; //construct the iframe var iframeHtml = this._options.iframeHtml; iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index); iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath); //construct wymbox var boxHtml = jQuery(this._box).html(); boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml); boxHtml = h.replaceAll(boxHtml, WYMeditor.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) var sTool = this._options.toolsItemHtml; var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name); sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft + oTool.title + this._options.stringDelimiterRight); sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css); sTools += sTool; } boxHtml = h.replaceAll(boxHtml, WYMeditor.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) var sClass = this._options.classesItemHtml; sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name); sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title); sClasses += sClass; } boxHtml = h.replaceAll(boxHtml, WYMeditor.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) var sContainer = this._options.containersItemHtml; sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name); sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE, this._options.stringDelimiterLeft + oContainer.title + this._options.stringDelimiterRight); sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css); sContainers += sContainer; } boxHtml = h.replaceAll(boxHtml, WYMeditor.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.loadSkin(); } }; WYMeditor.editor.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._iframe.contentWindow.focus(); //See #154 wym.exec(jQuery(this).attr(WYMeditor.NAME)); return(false); }); //handle click event on containers buttons jQuery(this._box).find(this._options.containerSelector).click(function() { wym.container(jQuery(this).attr(WYMeditor.NAME)); return(false); }); //handle keyup event on html value: set the editor value //handle focus/blur events to check if the element has focus, see #147 jQuery(this._box).find(this._options.htmlValSelector) .keyup(function() { jQuery(wym._doc.body).html(jQuery(this).val());}) .focus(function() { jQuery(this).toggleClass('hasfocus'); }) .blur(function() { jQuery(this).toggleClass('hasfocus'); }); //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(WYMeditor.NAME); var oClass = WYMeditor.Helper.findByName(aClasses, sName); if(oClass) { var jqexpr = oClass.expr; wym.toggleClass(sName, jqexpr); } wym._iframe.contentWindow.focus(); //See #154 return(false); }); //handle event on update element jQuery(this._options.updateSelector) .bind(this._options.updateEvent, function() { wym.update(); }); }; WYMeditor.editor.prototype.ready = function() { return(this._doc != null); }; /********** METHODS **********/ /* @name box * @description Returns the WYMeditor container */ WYMeditor.editor.prototype.box = function() { return(this._box); }; /* @name html * @description Get/Set the html value */ WYMeditor.editor.prototype.html = function(html) { if(typeof html === 'string') jQuery(this._doc.body).html(html); else return(jQuery(this._doc.body).html()); }; /* @name xhtml * @description Cleans up the HTML */ WYMeditor.editor.prototype.xhtml = function() { return this.parser.parse(this.html()); }; /* @name exec * @description Executes a button command */ WYMeditor.editor.prototype.exec = function(cmd) { //base function for execCommand //open a dialog or exec switch(cmd) { case WYMeditor.CREATE_LINK: var container = this.container(); if(container || this._selected_image) this.dialog(WYMeditor.DIALOG_LINK); break; case WYMeditor.INSERT_IMAGE: this.dialog(WYMeditor.DIALOG_IMAGE); break; case WYMeditor.INSERT_TABLE: this.dialog(WYMeditor.DIALOG_TABLE); break; case WYMeditor.PASTE: this.dialog(WYMeditor.DIALOG_PASTE); break; case WYMeditor.TOGGLE_HTML: this.update(); this.toggleHtml(); break; case WYMeditor.PREVIEW: this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview); break; default: this._exec(cmd); break; } }; /* @name container * @description Get/Set the selected container */ WYMeditor.editor.prototype.container = function(sType) { if(sType) { var container = null; if(sType.toLowerCase() == WYMeditor.TH) { container = this.container(); //find the TD or TH container switch(container.tagName.toLowerCase()) { case WYMeditor.TD: case WYMeditor.TH: break; default: var aTypes = new Array(WYMeditor.TD,WYMeditor.TH); container = this.findUp(this.container(), aTypes); break; } //if it exists, switch if(container!=null) { sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD; this.switchTo(container,sType); this.update(); } } else { //set the container type var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5, WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE); container = this.findUp(this.container(), aTypes); if(container) { var newNode = null; //blockquotes must contain a block level element if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) { var blockquote = this.findUp(this.container(), WYMeditor.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') ) + '

    '; } // Insert where appropriate if (container && container.tagName.toLowerCase() != WYMeditor.BODY) { // No .last() pre jQuery 1.4 //focusNode = jQuery(html).insertAfter(container).last()[0]; paragraphs = jQuery(html, this._doc).insertAfter(container); focusNode = paragraphs[paragraphs.length - 1]; } else { paragraphs = jQuery(html, this._doc).appendTo(this._doc.body); focusNode = paragraphs[paragraphs.length - 1]; } // Do some minor cleanup (#131) if (jQuery(container).text() == '') { jQuery(container).remove(); } // And remove br (if editor was empty) jQuery('body > br', this._doc).remove(); // Restore focus this.setFocusToNode(focusNode); }; WYMeditor.editor.prototype.insert = function(html) { // Do we have a selection? var selection = this._iframe.contentWindow.getSelection(), range, node; if (selection.focusNode != null) { // Overwrite selection with provided html range = selection.getRangeAt(0); node = range.createContextualFragment(html); range.deleteContents(); range.insertNode(node); } else { // Fall back to the internal paste function if there's no selection this.paste(html) } }; WYMeditor.editor.prototype.wrap = function(left, right) { this.insert(left + this._iframe.contentWindow.getSelection().toString() + right); }; WYMeditor.editor.prototype.unwrap = function() { this.insert(this._iframe.contentWindow.getSelection().toString()); }; WYMeditor.editor.prototype.setFocusToNode = function(node, toStart) { var range = this._doc.createRange(), selection = this._iframe.contentWindow.getSelection(); toStart = toStart ? 0 : 1; range.selectNodeContents(node); selection.addRange(range); selection.collapse(node, toStart); this._iframe.contentWindow.focus(); }; WYMeditor.editor.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.editor.prototype.computeBasePath = function() { return jQuery(jQuery.grep(jQuery('script'), function(s){ return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) })).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, ''); }; WYMeditor.editor.prototype.computeWymPath = function() { return jQuery(jQuery.grep(jQuery('script'), function(s){ return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) })).attr('src'); }; WYMeditor.editor.prototype.computeJqueryPath = function() { return jQuery(jQuery.grep(jQuery('script'), function(s){ return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ )) })).attr('src'); }; WYMeditor.editor.prototype.computeCssPath = function() { return jQuery(jQuery.grep(jQuery('link'), function(s){ return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ )) })).attr('href'); }; WYMeditor.editor.prototype.configureEditorUsingRawCss = function() { var CssParser = new WYMeditor.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.editor.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("mousedown", this.mousedown); }; WYMeditor.editor.prototype.mousedown = function(evt) { var wym = WYMeditor.INSTANCES[this.ownerDocument.title]; wym._selected_image = (evt.target.tagName.toLowerCase() == WYMeditor.IMG) ? evt.target : null; }; /********** SKINS **********/ /* * Function: WYMeditor.loadCss * Loads a stylesheet in the document. * * Parameters: * href - The CSS path. */ WYMeditor.loadCss = function(href) { var link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; var head = jQuery('head').get(0); head.appendChild(link); }; /* * Function: WYMeditor.editor.loadSkin * Loads the skin CSS and initialization script (if needed). */ WYMeditor.editor.prototype.loadSkin = function() { //does the user want to automatically load the CSS (default: yes)? //we also test if it hasn't been already loaded by another instance //see below for a better (second) test if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) { //check if it hasn't been already loaded //so we don't load it more than once //(we check the existing elements) var found = false; var rExp = new RegExp(this._options.skin + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$'); jQuery('link').each( function() { if(this.href.match(rExp)) found = true; }); //load it, using the skin path if(!found) WYMeditor.loadCss( this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS ); } //put the classname (ex. wym_skin_default) on wym_box jQuery(this._box).addClass( "wym_skin_" + this._options.skin ); //does the user want to use some JS to initialize the skin (default: yes)? //also check if it hasn't already been loaded by another instance if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) { eval(jQuery.ajax({url:this._options.skinPath + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText); } //init the skin, if needed if(WYMeditor.SKINS[this._options.skin] && WYMeditor.SKINS[this._options.skin].init) WYMeditor.SKINS[this._options.skin].init(this); }; /********** DIALOGS **********/ WYMeditor.INIT_DIALOG = function(index) { var wym = window.opener.WYMeditor.INSTANCES[index]; var doc = window.document; var selected = wym.selected(); var dialogType = jQuery(wym._options.dialogTypeSelector).val(); var sStamp = wym.uniqueStamp(); switch(dialogType) { case WYMeditor.DIALOG_LINK: //ensure that we select the link to populate the fields if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A) selected = jQuery(selected).parentsOrSelf(WYMeditor.A); //fix MSIE selection if link image has been clicked if(!selected && wym._selected_image) selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.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(WYMeditor.HREF)); jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC)); jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE)); jQuery(wym._options.relSelector).val(jQuery(selected).attr(WYMeditor.REL)); jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.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(WYMeditor.SRC)); jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector) .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE)); jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector) .val(jQuery(wym._selected_image).attr(WYMeditor.ALT)); } jQuery(wym._options.dialogLinkSelector + " " + wym._options.submitSelector).submit(function() { var sUrl = jQuery(wym._options.hrefSelector).val(); if(sUrl.length > 0) { var link; if (selected[0] && selected[0].tagName.toLowerCase() == WYMeditor.A) { link = selected; } else { wym._exec(WYMeditor.CREATE_LINK, sStamp); link = jQuery("a[href=" + sStamp + "]", wym._doc.body); } link.attr(WYMeditor.HREF, sUrl) .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()) .attr(WYMeditor.REL, jQuery(wym._options.relSelector).val()); } window.close(); }); jQuery(wym._options.dialogImageSelector + " " + wym._options.submitSelector).submit(function() { var sUrl = jQuery(wym._options.srcSelector).val(); if(sUrl.length > 0) { wym._exec(WYMeditor.INSERT_IMAGE, sStamp); jQuery("img[src$=" + sStamp + "]", wym._doc.body) .attr(WYMeditor.SRC, sUrl) .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val()) .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val()); } window.close(); }); jQuery(wym._options.dialogTableSelector + " " + wym._options.submitSelector).submit(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(WYMeditor.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
    * this.tag ('br', false, true) * # =>
    * this.tag ('input', jQuery({type:'text',disabled:true }) ) * # => */ WYMeditor.XmlHelper.prototype.tag = function(name, options, open) { options = options || false; open = open || false; return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />'); }; /* * @name contentTag * @description * Returns a XML block tag of type *name* surrounding the *content*. Add * XML attributes by passing an attributes array to *options*. For attributes * with no value like (disabled and readonly), give it a value of true in * the *options* array. You can use symbols or strings for the attribute names. * * this.contentTag ('p', 'Hello world!' ) * # =>

    Hello world!

    * this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"})) * # =>

    Hello world!

    * this.contentTag("select", options, jQuery({multiple : true})) * # => */ WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options) { options = options || false; return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+''; }; /* * @name cdataSection * @description * Returns a CDATA section for the given +content+. CDATA sections * are used to escape blocks of text containing characters which would * otherwise be recognized as markup. CDATA sections begin with the string * <![CDATA[ and } with (and may not contain) the string * ]]>. */ WYMeditor.XmlHelper.prototype.cdataSection = function(content) { return ''; }; /* * @name escapeOnce * @description * Returns the escaped +xml+ without affecting existing escaped entities. * * this.escapeOnce( "1 > 2 & 3") * # => "1 > 2 & 3" */ WYMeditor.XmlHelper.prototype.escapeOnce = function(xml) { return this._fixDoubleEscape(this.escapeEntities(xml)); }; /* * @name _fixDoubleEscape * @description * Fix double-escaped entities, such as &amp;, &#123;, etc. */ WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped) { return escaped.replace(/&([a-z]+|(#\d+));/ig, "&$1;"); }; /* * @name tagOptions * @description * Takes an array like the one generated by Tag.parseAttributes * [["src", "http://www.editam.com/?a=b&c=d&f=g"], ["title", "Editam, CMS"]] * or an object like {src:"http://www.editam.com/?a=b&c=d&f=g", title:"Editam, CMS"} * and returns a string properly escaped like * ' src = "http://www.editam.com/?a=b&c=d&f=g" title = "Editam, <Simplified> CMS"' * which is valid for strict XHTML */ WYMeditor.XmlHelper.prototype.tagOptions = function(options) { var xml = this; xml._formated_options = ''; for (var key in options) { var formated_options = ''; var value = options[key]; if(typeof value != 'function' && value.length > 0) { if(parseInt(key) == key && typeof value == 'object'){ key = value.shift(); value = value.pop(); } if(key != '' && value != ''){ xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"'; } } } return xml._formated_options; }; /* * @name escapeEntities * @description * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it * will not escape ". If set to true it will also escape ' */ WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes) { this._entitiesDiv.innerHTML = string; this._entitiesDiv.textContent = string; var result = this._entitiesDiv.innerHTML; if(typeof escape_quotes == 'undefined'){ if(escape_quotes != false) result = result.replace('"', '"'); if(escape_quotes == true) result = result.replace('"', '''); } return result; }; /* * Parses a string conatining tag attributes and values an returns an array formated like * [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]] */ WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes) { // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs var result = []; var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g); if(matches.toString() != tag_attributes){ for (var k in matches) { var v = matches[k]; if(typeof v != 'function' && v.length != 0){ var re = new RegExp('(\\w+)\\s*'+v); if(match = tag_attributes.match(re) ){ var value = v.replace(/^[\s=]+/, ""); var delimiter = value.charAt(0); delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":''); if(delimiter != ''){ value = delimiter == '"' ? value.replace(/^"|"+$/g, '') : value.replace(/^'|'+$/g, ''); } tag_attributes = tag_attributes.replace(match[0],''); result.push([match[1] , value]); } } } } return result; }; /** * XhtmlValidator for validating tag attributes * * @author Bermi Ferrer - http://bermi.org */ WYMeditor.XhtmlValidator = { "_attributes": { "core": { "except":[ "base", "head", "html", "meta", "param", "script", "style", "title" ], "attributes":[ "class", "id", "style", "title", "accesskey", "tabindex" ] }, "language": { "except":[ "base", "br", "hr", "iframe", "param", "script" ], "attributes": { "dir":[ "ltr", "rtl" ], "0":"lang", "1":"xml:lang" } }, "keyboard": { "attributes": { "accesskey":/^(\w){1}$/, "tabindex":/^(\d)+$/ } } }, "_events": { "window": { "only":[ "body" ], "attributes":[ "onload", "onunload" ] }, "form": { "only":[ "form", "input", "textarea", "select", "a", "label", "button" ], "attributes":[ "onchange", "onsubmit", "onreset", "onselect", "onblur", "onfocus" ] }, "keyboard": { "except":[ "base", "bdo", "br", "frame", "frameset", "head", "html", "iframe", "meta", "param", "script", "style", "title" ], "attributes":[ "onkeydown", "onkeypress", "onkeyup" ] }, "mouse": { "except":[ "base", "bdo", "br", "head", "html", "meta", "param", "script", "style", "title" ], "attributes":[ "onclick", "ondblclick", "onmousedown", "onmousemove", "onmouseover", "onmouseout", "onmouseup" ] } }, "_tags": { "a": { "attributes": { "0":"charset", "1":"coords", "2":"href", "3":"hreflang", "4":"name", "5":"rel", "6":"rev", "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/, "7":"type" } }, "0":"abbr", "1":"acronym", "2":"address", "area": { "attributes": { "0":"alt", "1":"coords", "2":"href", "nohref":/^(true|false)$/, "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/ }, "required":[ "alt" ] }, "3":"b", "base": { "attributes":[ "href" ], "required":[ "href" ] }, "bdo": { "attributes": { "dir":/^(ltr|rtl)$/ }, "required":[ "dir" ] }, "4":"big", "blockquote": { "attributes":[ "cite" ] }, "5":"body", "6":"br", "button": { "attributes": { "disabled":/^(disabled)$/, "type":/^(button|reset|submit)$/, "0":"value" }, "inside":"form" }, "7":"caption", "8":"cite", "9":"code", "col": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "span":/^(\d)+$/, "valign":/^(top|middle|bottom|baseline)$/, "2":"width" }, "inside":"colgroup" }, "colgroup": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "span":/^(\d)+$/, "valign":/^(top|middle|bottom|baseline)$/, "2":"width" } }, "10":"dd", "del": { "attributes": { "0":"cite", "datetime":/^([0-9]){8}/ } }, "11":"div", "12":"dfn", "13":"dl", "14":"dt", "15":"em", "fieldset": { "inside":"form" }, "form": { "attributes": { "0":"action", "1":"accept", "2":"accept-charset", "3":"enctype", "method":/^(get|post)$/ }, "required":[ "action" ] }, "head": { "attributes":[ "profile" ] }, "16":"h1", "17":"h2", "18":"h3", "19":"h4", "20":"h5", "21":"h6", "22":"hr", "html": { "attributes":[ "xmlns" ] }, "23":"i", "img": { "attributes":[ "alt", "src", "height", "ismap", "longdesc", "usemap", "width" ], "required":[ "alt", "src" ] }, "input": { "attributes": { "0":"accept", "1":"alt", "checked":/^(checked)$/, "disabled":/^(disabled)$/, "maxlength":/^(\d)+$/, "2":"name", "readonly":/^(readonly)$/, "size":/^(\d)+$/, "3":"src", "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/, "4":"value" }, "inside":"form" }, "ins": { "attributes": { "0":"cite", "datetime":/^([0-9]){8}/ } }, "24":"kbd", "label": { "attributes":[ "for" ], "inside":"form" }, "25":"legend", "26":"li", "link": { "attributes": { "0":"charset", "1":"href", "2":"hreflang", "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i, //next comment line required by Opera! /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/ "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i, "3":"type" }, "inside":"head" }, "map": { "attributes":[ "id", "name" ], "required":[ "id" ] }, "meta": { "attributes": { "0":"content", "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i, "1":"name", "2":"scheme" }, "required":[ "content" ] }, "27":"noscript", "object": { "attributes":[ "archive", "classid", "codebase", "codetype", "data", "declare", "height", "name", "standby", "type", "usemap", "width" ] }, "28":"ol", "optgroup": { "attributes": { "0":"label", "disabled": /^(disabled)$/ }, "required":[ "label" ] }, "option": { "attributes": { "0":"label", "disabled":/^(disabled)$/, "selected":/^(selected)$/, "1":"value" }, "inside":"select" }, "29":"p", "param": { "attributes": { "0":"type", "valuetype":/^(data|ref|object)$/, "1":"valuetype", "2":"value" }, "required":[ "name" ] }, "30":"pre", "q": { "attributes":[ "cite" ] }, "31":"samp", "script": { "attributes": { "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/, "0":"charset", "defer":/^(defer)$/, "1":"src" }, "required":[ "type" ] }, "select": { "attributes": { "disabled":/^(disabled)$/, "multiple":/^(multiple)$/, "0":"name", "1":"size" }, "inside":"form" }, "32":"small", "33":"span", "34":"strong", "style": { "attributes": { "0":"type", "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/ }, "required":[ "type" ] }, "35":"sub", "36":"sup", "table": { "attributes": { "0":"border", "1":"cellpadding", "2":"cellspacing", "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/, "rules":/^(none|groups|rows|cols|all)$/, "3":"summary", "4":"width" } }, "tbody": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "td": { "attributes": { "0":"abbr", "align":/^(left|right|center|justify|char)$/, "1":"axis", "2":"char", "3":"charoff", "colspan":/^(\d)+$/, "4":"headers", "rowspan":/^(\d)+$/, "scope":/^(col|colgroup|row|rowgroup)$/, "valign":/^(top|middle|bottom|baseline)$/ } }, "textarea": { "attributes":[ "cols", "rows", "disabled", "name", "readonly" ], "required":[ "cols", "rows" ], "inside":"form" }, "tfoot": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom)$/, "2":"baseline" } }, "th": { "attributes": { "0":"abbr", "align":/^(left|right|center|justify|char)$/, "1":"axis", "2":"char", "3":"charoff", "colspan":/^(\d)+$/, "4":"headers", "rowspan":/^(\d)+$/, "scope":/^(col|colgroup|row|rowgroup)$/, "valign":/^(top|middle|bottom|baseline)$/ } }, "thead": { "attributes": { "align":/^(right|left|center|justify)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "37":"title", "tr": { "attributes": { "align":/^(right|left|center|justify|char)$/, "0":"char", "1":"charoff", "valign":/^(top|middle|bottom|baseline)$/ } }, "38":"tt", "39":"ul", "40":"var" }, // Temporary skiped attributes skiped_attributes : [], skiped_attribute_values : [], getValidTagAttributes: function(tag, attributes) { var valid_attributes = {}; var possible_attributes = this.getPossibleTagAttributes(tag); for(var attribute in attributes) { var value = attributes[attribute]; var h = WYMeditor.Helper; if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){ if (typeof value != 'function' && h.contains(possible_attributes, attribute)) { if (this.doesAttributeNeedsValidation(tag, attribute)) { if(this.validateAttribute(tag, attribute, value)){ valid_attributes[attribute] = value; } }else{ valid_attributes[attribute] = value; } } } } return valid_attributes; }, getUniqueAttributesAndEventsForTag : function(tag) { var result = []; if (this._tags[tag] && this._tags[tag]['attributes']) { for (k in this._tags[tag]['attributes']) { result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k); } } return result; }, getDefaultAttributesAndEventsForTags : function() { var result = []; for (var key in this._events){ result.push(this._events[key]); } for (var key in this._attributes){ result.push(this._attributes[key]); } return result; }, isValidTag : function(tag) { if(this._tags[tag]){ return true; } for(var key in this._tags){ if(this._tags[key] == tag){ return true; } } return false; }, getDefaultAttributesAndEventsForTag : function(tag) { var default_attributes = []; if (this.isValidTag(tag)) { var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags(); for(var key in default_attributes_and_events) { var defaults = default_attributes_and_events[key]; if(typeof defaults == 'object'){ var h = WYMeditor.Helper; if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) { continue; } var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events']; for(var k in tag_defaults) { default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]); } } } } return default_attributes; }, doesAttributeNeedsValidation: function(tag, attribute) { return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute))); }, validateAttribute : function(tag, attribute, value) { if ( this._tags[tag] && (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute ) { return false; } return typeof this._tags[tag] != 'undefined'; }, getPossibleTagAttributes : function(tag) { if (!this._possible_tag_attributes) { this._possible_tag_attributes = {}; } if (!this._possible_tag_attributes[tag]) { this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag)); } return this._possible_tag_attributes[tag]; } }; /** * Compounded regular expression. Any of * the contained patterns could match and * when one does, it's label is returned. * * Constructor. Starts with no patterns. * @param boolean case True for case sensitive, false * for insensitive. * @access public * @author Marcus Baker (http://lastcraft.com) * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.ParallelRegex = function(case_sensitive) { this._case = case_sensitive; this._patterns = []; this._labels = []; this._regex = null; return this; }; /** * Adds a pattern with an optional label. * @param string pattern Perl style regex, but ( and ) * lose the usual meaning. * @param string label Label of regex to be returned * on a match. * @access public */ WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label) { label = label || true; var count = this._patterns.length; this._patterns[count] = pattern; this._labels[count] = label; this._regex = null; }; /** * Attempts to match all patterns at once against * a string. * @param string subject String to match against. * * @return boolean True on success. * @return string match First matched portion of * subject. * @access public */ WYMeditor.ParallelRegex.prototype.match = function(subject) { if (this._patterns.length == 0) { return [false, '']; } var matches = subject.match(this._getCompoundedRegex()); if(!matches){ return [false, '']; } var match = matches[0]; for (var i = 1; i < matches.length; i++) { if (matches[i]) { return [this._labels[i-1], match]; } } return [true, matches[0]]; }; /** * Compounds the patterns into a single * regular expression separated with the * "or" operator. Caches the regex. * Will automatically escape (, ) and / tokens. * @param array patterns List of patterns in order. * @access private */ WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function() { if (this._regex == null) { for (var i = 0, count = this._patterns.length; i < count; i++) { this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')'; } this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags()); } return this._regex; }; /** * Escape lookahead/lookbehind blocks */ WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex) { return regex. replace(/\(\?(i|m|s|x|U)\)/, '~~~~~~Tk1\$1~~~~~~'). replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~'). replace(/\(\?\=(.*)\)/, '~~~~~~Tk3\$1~~~~~~'). replace(/\(\?\!(.*)\)/, '~~~~~~Tk4\$1~~~~~~'). replace(/\(\?\<\=(.*)\)/, '~~~~~~Tk5\$1~~~~~~'). replace(/\(\?\<\!(.*)\)/, '~~~~~~Tk6\$1~~~~~~'). replace(/\(\?\:(.*)\)/, '~~~~~~Tk7\$1~~~~~~'); }; /** * Unscape lookahead/lookbehind blocks */ WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex) { return regex. replace(/~~~~~~Tk1(.{1})~~~~~~/, "(?\$1)"). replace(/~~~~~~Tk2(.{2})~~~~~~/, "(?\$1)"). replace(/~~~~~~Tk3(.*)~~~~~~/, "(?=\$1)"). replace(/~~~~~~Tk4(.*)~~~~~~/, "(?!\$1)"). replace(/~~~~~~Tk5(.*)~~~~~~/, "(?<=\$1)"). replace(/~~~~~~Tk6(.*)~~~~~~/, "(?", 'Comment'); }; WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope) { this.addEntryPattern("", 'Script'); }; WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope) { this.addEntryPattern("", 'Css'); }; WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope) { this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag'); this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag'); this.addInTagDeclarationTokens('OpeningTag'); this.addSpecialPattern("", scope, 'ClosingTag'); }; WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope) { this.addSpecialPattern('\\s+', scope, 'Ignore'); this.addAttributeTokens(scope); this.addExitPattern('/>', scope); this.addExitPattern('>', scope); }; WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope) { this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes'); this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute'); this.addPattern("\\\\\"", 'DoubleQuotedAttribute'); this.addExitPattern('"', 'DoubleQuotedAttribute'); this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute'); this.addPattern("\\\\'", 'SingleQuotedAttribute'); this.addExitPattern("'", 'SingleQuotedAttribute'); this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute'); }; /** * XHTML Parser. * * This XHTML parser will trigger the events available on on * current SaxListener * * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.XhtmlParser = function(Listener, mode) { var mode = mode || 'Text'; this._Lexer = new WYMeditor.XhtmlLexer(this); this._Listener = Listener; this._mode = mode; this._matches = []; this._last_match = ''; this._current_match = ''; return this; }; WYMeditor.XhtmlParser.prototype.parse = function(raw) { this._Lexer.parse(this.beforeParsing(raw)); return this.afterParsing(this._Listener.getResult()); }; WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw) { if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){ // Usefull for cleaning up content pasted from other sources (MSWord) this._Listener.avoidStylingTagsAndAttributes(); } return this._Listener.beforeParsing(raw); }; WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed) { if(this._Listener._avoiding_tags_implicitly){ this._Listener.allowStylingTagsAndAttributes(); } return this._Listener.afterParsing(parsed); }; WYMeditor.XhtmlParser.prototype.Ignore = function(match, state) { return true; }; WYMeditor.XhtmlParser.prototype.Text = function(text) { this._Listener.addContent(text); return true; }; WYMeditor.XhtmlParser.prototype.Comment = function(match, status) { return this._addNonTagBlock(match, status, 'addComment'); }; WYMeditor.XhtmlParser.prototype.Script = function(match, status) { return this._addNonTagBlock(match, status, 'addScript'); }; WYMeditor.XhtmlParser.prototype.Css = function(match, status) { return this._addNonTagBlock(match, status, 'addCss'); }; WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type) { switch (state){ case WYMeditor.LEXER_ENTER: this._non_tag = match; break; case WYMeditor.LEXER_UNMATCHED: this._non_tag += match; break; case WYMeditor.LEXER_EXIT: switch(type) { case 'addComment': this._Listener.addComment(this._non_tag+match); break; case 'addScript': this._Listener.addScript(this._non_tag+match); break; case 'addCss': this._Listener.addCss(this._non_tag+match); break; } } return true; }; WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state) { switch (state){ case WYMeditor.LEXER_ENTER: this._tag = this.normalizeTag(match); this._tag_attributes = {}; break; case WYMeditor.LEXER_SPECIAL: this._callOpenTagListener(this.normalizeTag(match)); break; case WYMeditor.LEXER_EXIT: this._callOpenTagListener(this._tag, this._tag_attributes); } return true; }; WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state) { this._callCloseTagListener(this.normalizeTag(match)); return true; }; WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes) { var attributes = attributes || {}; this.autoCloseUnclosedBeforeNewOpening(tag); if(this._Listener.isBlockTag(tag)){ this._Listener._tag_stack.push(tag); this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes); this._Listener.openBlockTag(tag, attributes); this._increaseOpenTagCounter(tag); }else if(this._Listener.isInlineTag(tag)){ this._Listener.inlineTag(tag, attributes); }else{ this._Listener.openUnknownTag(tag, attributes); this._increaseOpenTagCounter(tag); } this._Listener.last_tag = tag; this._Listener.last_tag_opened = true; this._Listener.last_tag_attributes = attributes; }; WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag) { if(this._decreaseOpenTagCounter(tag)){ this.autoCloseUnclosedBeforeTagClosing(tag); if(this._Listener.isBlockTag(tag)){ var expected_tag = this._Listener._tag_stack.pop(); if(expected_tag == false){ return; }else if(expected_tag != tag){ tag = expected_tag; } this._Listener.closeBlockTag(tag); }else{ this._Listener.closeUnknownTag(tag); } }else{ this._Listener.closeUnopenedTag(tag); } this._Listener.last_tag = tag; this._Listener.last_tag_opened = false; }; WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag) { this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0; this._Listener._open_tags[tag]++; }; WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag) { if(this._Listener._open_tags[tag]){ this._Listener._open_tags[tag]--; if(this._Listener._open_tags[tag] == 0){ this._Listener._open_tags[tag] = undefined; } return true; } return false; }; WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag) { this._autoCloseUnclosed(new_tag, false); }; WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag) { this._autoCloseUnclosed(tag, true); }; WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing) { var closing = closing || false; if(this._Listener._open_tags){ for (var tag in this._Listener._open_tags) { var counter = this._Listener._open_tags[tag]; if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){ this._callCloseTagListener(tag, true); } } } }; WYMeditor.XhtmlParser.prototype.getTagReplacements = function() { return this._Listener.getTagReplacements(); }; WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag) { tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase(); var tags = this._Listener.getTagReplacements(); if(tags[tag]){ return tags[tag]; } return tag; }; WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state) { if(WYMeditor.LEXER_SPECIAL == state){ this._current_attribute = match; } return true; }; WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state) { if(WYMeditor.LEXER_UNMATCHED == state){ this._tag_attributes[this._current_attribute] = match; } return true; }; WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state) { if(WYMeditor.LEXER_UNMATCHED == state){ this._tag_attributes[this._current_attribute] = match; } return true; }; WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state) { this._tag_attributes[this._current_attribute] = match.replace(/^=/,''); return true; }; /** * XHTML Sax parser. * * @author Bermi Ferrer (http://bermi.org) */ WYMeditor.XhtmlSaxListener = function() { this.output = ''; this.helper = new WYMeditor.XmlHelper(); this._open_tags = {}; this.validator = WYMeditor.XhtmlValidator; this._tag_stack = []; this.avoided_tags = []; this.entities = { ' ':' ','¡':'¡','¢':'¢', '£':'£','¤':'¤','¥':'¥', '¦':'¦','§':'§','¨':'¨', '©':'©','ª':'ª','«':'«', '¬':'¬','­':'­','®':'®', '¯':'¯','°':'°','±':'±', '²':'²','³':'³','´':'´', 'µ':'µ','¶':'¶','·':'·', '¸':'¸','¹':'¹','º':'º', '»':'»','¼':'¼','½':'½', '¾':'¾','¿':'¿','À':'À', 'Á':'Á','Â':'Â','Ã':'Ã', 'Ä':'Ä','Å':'Å','Æ':'Æ', 'Ç':'Ç','È':'È','É':'É', 'Ê':'Ê','Ë':'Ë','Ì':'Ì', 'Í':'Í','Î':'Î','Ï':'Ï', 'Ð':'Ð','Ñ':'Ñ','Ò':'Ò', 'Ó':'Ó','Ô':'Ô','Õ':'Õ', 'Ö':'Ö','×':'×','Ø':'Ø', 'Ù':'Ù','Ú':'Ú','Û':'Û', 'Ü':'Ü','Ý':'Ý','Þ':'Þ', 'ß':'ß','à':'à','á':'á', 'â':'â','ã':'ã','ä':'ä', 'å':'å','æ':'æ','ç':'ç', 'è':'è','é':'é','ê':'ê', 'ë':'ë','ì':'ì','í':'í', 'î':'î','ï':'ï','ð':'ð', 'ñ':'ñ','ò':'ò','ó':'ó', 'ô':'ô','õ':'õ','ö':'ö', '÷':'÷','ø':'ø','ù':'ù', 'ú':'ú','û':'û','ü':'ü', 'ý':'ý','þ':'þ','ÿ':'ÿ', 'Œ':'Œ','œ':'œ','Š':'Š', 'š':'š','Ÿ':'Ÿ','ƒ':'ƒ', 'ˆ':'ˆ','˜':'˜','Α':'Α', 'Β':'Β','Γ':'Γ','Δ':'Δ', 'Ε':'Ε','Ζ':'Ζ','Η':'Η', 'Θ':'Θ','Ι':'Ι','Κ':'Κ', 'Λ':'Λ','Μ':'Μ','Ν':'Ν', 'Ξ':'Ξ','Ο':'Ο','Π':'Π', 'Ρ':'Ρ','Σ':'Σ','Τ':'Τ', 'Υ':'Υ','Φ':'Φ','Χ':'Χ', 'Ψ':'Ψ','Ω':'Ω','α':'α', 'β':'β','γ':'γ','δ':'δ', 'ε':'ε','ζ':'ζ','η':'η', 'θ':'θ','ι':'ι','κ':'κ', 'λ':'λ','μ':'μ','ν':'ν', 'ξ':'ξ','ο':'ο','π':'π', 'ρ':'ρ','ς':'ς','σ':'σ', 'τ':'τ','υ':'υ','φ':'φ', 'χ':'χ','ψ':'ψ','ω':'ω', 'ϑ':'ϑ','ϒ':'ϒ','ϖ':'ϖ', ' ':' ',' ':' ',' ':' ', '‌':'‌','‍':'‍','‎':'‎', '‏':'‏','–':'–','—':'—', '‘':'‘','’':'’','‚':'‚', '“':'“','”':'”','„':'„', '†':'†','‡':'‡','•':'•', '…':'…','‰':'‰','′':'′', '″':'″','‹':'‹','›':'›', '‾':'‾','⁄':'⁄','€':'€', 'ℑ':'ℑ','℘':'℘','ℜ':'ℜ', '™':'™','ℵ':'ℵ','←':'←', '↑':'↑','→':'→','↓':'↓', '↔':'↔','↵':'↵','⇐':'⇐', '⇑':'⇑','⇒':'⇒','⇓':'⇓', '⇔':'⇔','∀':'∀','∂':'∂', '∃':'∃','∅':'∅','∇':'∇', '∈':'∈','∉':'∉','∋':'∋', '∏':'∏','∑':'∑','−':'−', '∗':'∗','√':'√','∝':'∝', '∞':'∞','∠':'∠','∧':'∧', '∨':'∨','∩':'∩','∪':'∪', '∫':'∫','∴':'∴','∼':'∼', '≅':'≅','≈':'≈','≠':'≠', '≡':'≡','≤':'≤','≥':'≥', '⊂':'⊂','⊃':'⊃','⊄':'⊄', '⊆':'⊆','⊇':'⊇','⊕':'⊕', '⊗':'⊗','⊥':'⊥','⋅':'⋅', '⌈':'⌈','⌉':'⌉','⌊':'⌊', '⌋':'⌋','⟨':'〈','⟩':'〉', '◊':'◊','♠':'♠','♣':'♣', '♥':'♥','♦':'♦'}; this.block_tags = ["a", "abbr", "acronym", "address", "area", "b", "base", "bdo", "big", "blockquote", "body", "button", "caption", "cite", "code", "col", "colgroup", "dd", "del", "div", "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2", "h3", "h4", "h5", "h6", "html", "i", "ins", "kbd", "label", "legend", "li", "map", "noscript", "object", "ol", "optgroup", "option", "p", "param", "pre", "q", "samp", "script", "select", "small", "span", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "title", "tr", "tt", "ul", "var", "extends"]; this.inline_tags = ["br", "hr", "img", "input"]; return this; }; WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing) { var closing = closing || false; if(tag == 'td'){ if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){ return true; } } if(tag == 'option'){ if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){ return true; } } return false; }; WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw) { this.output = ''; return raw; }; WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml) { xhtml = this.replaceNamedEntities(xhtml); xhtml = this.joinRepeatedEntities(xhtml); xhtml = this.removeEmptyTags(xhtml); xhtml = this.removeBrInPre(xhtml); return xhtml; }; WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml) { for (var entity in this.entities) { xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]); } return xhtml; }; WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml) { var tags = 'em|strong|sub|sup|acronym|pre|del|address'; return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),''). replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>'); }; WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml) { return xhtml.replace(new RegExp('<('+this.block_tags.join("|").replace(/\|td/,'').replace(/\|th/, '')+')>(
    | | |\\s)*<\/\\1>' ,'g'),''); }; WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml) { var matches = xhtml.match(new RegExp(']*>(.*?)<\/pre>','gmi')); if(matches) { for(var i=0; i', 'g'), String.fromCharCode(13,10))); } } return xhtml; }; WYMeditor.XhtmlSaxListener.prototype.getResult = function() { return this.output; }; WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function() { return {'b':'strong', 'i':'em'}; }; WYMeditor.XhtmlSaxListener.prototype.addContent = function(text) { this.output += text; }; WYMeditor.XhtmlSaxListener.prototype.addComment = function(text) { if(this.remove_comments){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.addScript = function(text) { if(!this.remove_scripts){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.addCss = function(text) { if(!this.remove_embeded_styles){ this.output += text; } }; WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes) { this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true); }; WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes) { this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes)); }; WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes) { //this.output += this.helper.tag(tag, attributes, true); }; WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag) { this.output = this.output.replace(/
    $/, '')+this._getClosingTagContent('before', tag)+""+this._getClosingTagContent('after', tag); }; WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag) { //this.output += ""; }; WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag) { this.output += ""; }; WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function() { this.avoided_tags = ['div','span']; this.validator.skiped_attributes = ['style']; this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class this._avoiding_tags_implicitly = true; }; WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function() { this.avoided_tags = []; this.validator.skiped_attributes = []; this.validator.skiped_attribute_values = []; this._avoiding_tags_implicitly = false; }; WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag) { return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag); }; WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag) { return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag); }; WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content) { this._insertContentWhenClosingTag('after', tag, content); }; WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content) { this._insertContentWhenClosingTag('before', tag, content); }; WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes) { 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, ''); } }; WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content) { if(!this['_insert_'+position+'_closing']){ this['_insert_'+position+'_closing'] = []; } if(!this['_insert_'+position+'_closing'][tag]){ this['_insert_'+position+'_closing'][tag] = []; } this['_insert_'+position+'_closing'][tag].push(content); }; WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag) { if( this['_insert_'+position+'_closing'] && this['_insert_'+position+'_closing'][tag] && this['_insert_'+position+'_closing'][tag].length > 0){ return this['_insert_'+position+'_closing'][tag].pop(); } return ''; }; /********** CSS PARSER **********/ WYMeditor.WymCssLexer = function(parser, only_wym_blocks) { var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks); jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss'))); this.mapHandler('WymCss', 'Ignore'); if(only_wym_blocks == true){ this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss'); this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss'); } this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration'); this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment'); this.addExitPattern("\\\x2a/", 'WymCssComment'); this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle'); this.addExitPattern("\x7d", 'WymCssStyle'); this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle'); this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle'); return this; }; WYMeditor.WymCssParser = function() { this._in_style = false; this._has_title = false; this.only_wym_blocks = true; this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]}; return this; }; WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks) { var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks); this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks); this._Lexer.parse(raw); }; WYMeditor.WymCssParser.prototype.Ignore = function(match, state) { return true; }; WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status) { if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){ return false; } if(status == WYMeditor.LEXER_UNMATCHED){ if(!this._in_style){ this._has_title = true; this._current_item = {'title':WYMeditor.Helper.trim(text)}; }else{ if(this._current_item[this._current_element]){ if(!this._current_item[this._current_element].expressions){ this._current_item[this._current_element].expressions = [text]; }else{ this._current_item[this._current_element].expressions.push(text); } } } this._in_style = true; } return true; }; WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status) { if(status == WYMeditor.LEXER_UNMATCHED){ match = WYMeditor.Helper.trim(match); if(match != ''){ this._current_item[this._current_element].style = match; } }else if (status == WYMeditor.LEXER_EXIT){ this._in_style = false; this._has_title = false; this.addStyleSetting(this._current_item); } return true; }; WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status) { if(status == WYMeditor.LEXER_UNMATCHED){ this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,''); } return true; }; WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match) { match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, ''); var tag = ''; if(match.indexOf('.') > 0){ var parts = match.split('.'); this._current_element = parts[1]; var tag = parts[0]; }else{ this._current_element = match; } if(!this._has_title){ this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element}; this._has_title = true; } if(!this._current_item[this._current_element]){ this._current_item[this._current_element] = {'name':this._current_element}; } if(tag){ if(!this._current_item[this._current_element].tags){ this._current_item[this._current_element].tags = [tag]; }else{ this._current_item[this._current_element].tags.push(tag); } } return true; }; WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details) { for (var name in style_details){ var details = style_details[name]; if(typeof details == 'object' && name != 'title'){ this.css_settings.classesItems.push({ 'name': WYMeditor.Helper.trim(details.name), 'title': style_details.title, 'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', ')) }); if(details.feedback_style){ this.css_settings.editorStyles.push({ 'name': '.'+ WYMeditor.Helper.trim(details.name), 'css': details.feedback_style }); } if(details.style){ this.css_settings.dialogStyles.push({ 'name': '.'+ WYMeditor.Helper.trim(details.name), 'css': details.style }); } } } }; /********** 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; }; WYMeditor.isPhantomNode = function(n) { if (n.nodeType == 3) return !(/[^\t\n\r ]/.test(n.data)); return false; }; WYMeditor.isPhantomString = function(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 & array helpers WYMeditor.Helper = { //replace all instances of 'old' by 'rep' in 'str' string replaceAll: function(str, old, rep) { var rExp = new RegExp(old, "g"); return(str.replace(rExp, rep)); }, //insert 'inserted' at position 'pos' in 'str' string insertAt: function(str, inserted, pos) { return(str.substr(0,pos) + inserted + str.substring(pos)); }, //trim 'str' string trim: function(str) { return str.replace(/^(\s*)|(\s*)$/gm,''); }, //return true if 'arr' array contains 'elem', or false contains: function(arr, elem) { for (var i = 0; i < arr.length; i++) { if (arr[i] === elem) return true; } return false; }, //return 'item' position in 'arr' array, or -1 indexOf: function(arr, item) { var ret=-1; for(var i = 0; i < arr.length; i++) { if (arr[i] == item) { ret = i; break; } } return(ret); }, //return 'item' object in 'arr' array, checking its 'name' property, or null findByName: function(arr, name) { for(var i = 0; i < arr.length; i++) { var item = arr[i]; if(item.name == name) return(item); } return(null); } };