OSDN Git Service

b13e2d02f66f8995b4a5c3ba7ab4e0c2e8190e91
[feedblog/feedgenerator.git] / wymeditor / jquery.wymeditor.back.js
1 /**
2  * @version 0.5-rc1
3  *
4  * WYMeditor : what you see is What You Mean web-based editor
5  * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
6  * Dual licensed under the MIT (MIT-license.txt)
7  * and GPL (GPL-license.txt) licenses.
8  *
9  * For further information visit:
10  *        http://www.wymeditor.org/
11  *
12  * File: jquery.wymeditor.js
13  *
14  *        Main JS file with core classes and functions.
15  *        See the documentation for more info.
16  *
17  * About: authors
18  *
19  *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
20  *        Volker Mische (vmx a-t gmx dotde)
21  *        Scott Lewis (lewiscot a-t gmail dotcom)
22  *        Bermi Ferrer (wymeditor a-t bermi dotorg)
23  *        Daniel Reszka (d.reszka a-t wymeditor dotorg)
24  *        Jonatan Lundin (jonatan.lundin _at_ gmail.com)
25  */
26
27 /*
28    Namespace: WYMeditor
29    Global WYMeditor namespace.
30 */
31 if(!WYMeditor) var WYMeditor = {};
32
33 //Wrap the Firebug console in WYMeditor.console
34 (function() {
35     if ( !window.console || !console.firebug ) {
36         var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
37         "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
38
39         WYMeditor.console = {};
40         for (var i = 0; i < names.length; ++i)
41             WYMeditor.console[names[i]] = function() {}
42
43     } else WYMeditor.console = window.console;
44 })();
45
46 jQuery.extend(WYMeditor, {
47
48 /*
49     Constants: Global WYMeditor constants.
50
51     VERSION             - Defines WYMeditor version.
52     INSTANCES           - An array of loaded WYMeditor.editor instances.
53     STRINGS             - An array of loaded WYMeditor language pairs/values.
54     SKINS               - An array of loaded WYMeditor skins.
55     NAME                - The "name" attribute.
56     INDEX               - A string replaced by the instance index.
57     WYM_INDEX           - A string used to get/set the instance index.
58     BASE_PATH           - A string replaced by WYMeditor's base path.
59     SKIN_PATH           - A string replaced by WYMeditor's skin path.
60     WYM_PATH            - A string replaced by WYMeditor's main JS file path.
61     SKINS_DEFAULT_PATH  - The skins default base path.
62     SKINS_DEFAULT_CSS   - The skins default CSS file.
63     LANG_DEFAULT_PATH   - The language files default path.
64     IFRAME_BASE_PATH    - A string replaced by the designmode iframe's base path.
65     IFRAME_DEFAULT      - The iframe's default base path.
66     JQUERY_PATH         - A string replaced by the computed jQuery path.
67     DIRECTION           - A string replaced by the text direction (rtl or ltr).
68     LOGO                - A string replaced by WYMeditor logo.
69     TOOLS               - A string replaced by the toolbar's HTML.
70     TOOLS_ITEMS         - A string replaced by the toolbar items.
71     TOOL_NAME           - A string replaced by a toolbar item's name.
72     TOOL_TITLE          - A string replaced by a toolbar item's title.
73     TOOL_CLASS          - A string replaced by a toolbar item's class.
74     CLASSES             - A string replaced by the classes panel's HTML.
75     CLASSES_ITEMS       - A string replaced by the classes items.
76     CLASS_NAME          - A string replaced by a class item's name.
77     CLASS_TITLE         - A string replaced by a class item's title.
78     CONTAINERS          - A string replaced by the containers panel's HTML.
79     CONTAINERS_ITEMS    - A string replaced by the containers items.
80     CONTAINER_NAME      - A string replaced by a container item's name.
81     CONTAINER_TITLE     - A string replaced by a container item's title.
82     CONTAINER_CLASS     - A string replaced by a container item's class.
83     HTML                - A string replaced by the HTML view panel's HTML.
84     IFRAME              - A string replaced by the designmode iframe.
85     STATUS              - A string replaced by the status panel's HTML.
86     DIALOG_TITLE        - A string replaced by a dialog's title.
87     DIALOG_BODY         - A string replaced by a dialog's HTML body.
88     BODY                - The BODY element.
89     STRING              - The "string" type.
90     BODY,DIV,P,
91     H1,H2,H3,H4,H5,H6,
92     PRE,BLOCKQUOTE,
93     A,BR,IMG,
94     TABLE,TD,TH,
95     UL,OL,LI            - HTML elements string representation.
96     CLASS,HREF,SRC,
97     TITLE,ALT           - HTML attributes string representation.
98     DIALOG_LINK         - A link dialog type.
99     DIALOG_IMAGE        - An image dialog type.
100     DIALOG_TABLE        - A table dialog type.
101     DIALOG_PASTE        - A 'Paste from Word' dialog type.
102     BOLD                - Command: (un)set selection to <strong>.
103     ITALIC              - Command: (un)set selection to <em>.
104     CREATE_LINK         - Command: open the link dialog or (un)set link.
105     INSERT_IMAGE        - Command: open the image dialog or insert an image.
106     INSERT_TABLE        - Command: open the table dialog.
107     PASTE               - Command: open the paste dialog.
108     INDENT              - Command: nest a list item.
109     OUTDENT             - Command: unnest a list item.
110     TOGGLE_HTML         - Command: display/hide the HTML view.
111     FORMAT_BLOCK        - Command: set a block element to another type.
112     PREVIEW             - Command: open the preview dialog.
113     UNLINK              - Command: unset a link.
114     INSERT_UNORDEREDLIST- Command: insert an unordered list.
115     INSERT_ORDEREDLIST  - Command: insert an ordered list.
116     MAIN_CONTAINERS     - An array of the main HTML containers used in WYMeditor.
117     BLOCKS              - An array of the HTML block elements.
118     KEY                 - Standard key codes.
119     NODE                - Node types.
120
121 */
122
123     VERSION             : "0.5-rc1",
124     INSTANCES           : [],
125     STRINGS             : [],
126     SKINS               : [],
127     NAME                : "name",
128     INDEX               : "{Wym_Index}",
129     WYM_INDEX           : "wym_index",
130     BASE_PATH           : "{Wym_Base_Path}",
131     CSS_PATH            : "{Wym_Css_Path}",
132     WYM_PATH            : "{Wym_Wym_Path}",
133     SKINS_DEFAULT_PATH  : "skins/",
134     SKINS_DEFAULT_CSS   : "skin.css",
135     SKINS_DEFAULT_JS    : "skin.js",
136     LANG_DEFAULT_PATH   : "lang/",
137     IFRAME_BASE_PATH    : "{Wym_Iframe_Base_Path}",
138     IFRAME_DEFAULT      : "iframe/default/",
139     JQUERY_PATH         : "{Wym_Jquery_Path}",
140     DIRECTION           : "{Wym_Direction}",
141     LOGO                : "{Wym_Logo}",
142     TOOLS               : "{Wym_Tools}",
143     TOOLS_ITEMS         : "{Wym_Tools_Items}",
144     TOOL_NAME           : "{Wym_Tool_Name}",
145     TOOL_TITLE          : "{Wym_Tool_Title}",
146     TOOL_CLASS          : "{Wym_Tool_Class}",
147     CLASSES             : "{Wym_Classes}",
148     CLASSES_ITEMS       : "{Wym_Classes_Items}",
149     CLASS_NAME          : "{Wym_Class_Name}",
150     CLASS_TITLE         : "{Wym_Class_Title}",
151     CONTAINERS          : "{Wym_Containers}",
152     CONTAINERS_ITEMS    : "{Wym_Containers_Items}",
153     CONTAINER_NAME      : "{Wym_Container_Name}",
154     CONTAINER_TITLE     : "{Wym_Containers_Title}",
155     CONTAINER_CLASS     : "{Wym_Container_Class}",
156     HTML                : "{Wym_Html}",
157     IFRAME              : "{Wym_Iframe}",
158     STATUS              : "{Wym_Status}",
159     DIALOG_TITLE        : "{Wym_Dialog_Title}",
160     DIALOG_BODY         : "{Wym_Dialog_Body}",
161     STRING              : "string",
162     BODY                : "body",
163     DIV                 : "div",
164     P                   : "p",
165     H1                  : "h1",
166     H2                  : "h2",
167     H3                  : "h3",
168     H4                  : "h4",
169     H5                  : "h5",
170     H6                  : "h6",
171     PRE                 : "pre",
172     BLOCKQUOTE          : "blockquote",
173     A                   : "a",
174     BR                  : "br",
175     IMG                 : "img",
176     TABLE               : "table",
177     TD                  : "td",
178     TH                  : "th",
179     UL                  : "ul",
180     OL                  : "ol",
181     LI                  : "li",
182     CLASS               : "class",
183     HREF                : "href",
184     SRC                 : "src",
185     TITLE               : "title",
186     ALT                 : "alt",
187     DIALOG_LINK         : "Link",
188     DIALOG_IMAGE        : "Image",
189     DIALOG_TABLE        : "Table",
190     DIALOG_PASTE        : "Paste_From_Word",
191     BOLD                : "Bold",
192     ITALIC              : "Italic",
193     CREATE_LINK         : "CreateLink",
194     INSERT_IMAGE        : "InsertImage",
195     INSERT_TABLE        : "InsertTable",
196     INSERT_HTML         : "InsertHTML",
197     PASTE               : "Paste",
198     INDENT              : "Indent",
199     OUTDENT             : "Outdent",
200     TOGGLE_HTML         : "ToggleHtml",
201     FORMAT_BLOCK        : "FormatBlock",
202     PREVIEW             : "Preview",
203     UNLINK                              : "Unlink",
204     INSERT_UNORDEREDLIST: "InsertUnorderedList",
205     INSERT_ORDEREDLIST  : "InsertOrderedList",
206
207     MAIN_CONTAINERS : new Array("p","h1","h2","h3","h4","h5","h6","pre","blockquote"),
208
209     BLOCKS : new Array("address", "blockquote", "div", "dl",
210            "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
211            "noscript", "ol", "p", "pre", "table", "ul", "dd", "dt",
212            "li", "tbody", "td", "tfoot", "th", "thead", "tr"),
213
214     KEY : {
215       BACKSPACE: 8,
216       ENTER: 13,
217       END: 35,
218       HOME: 36,
219       LEFT: 37,
220       UP: 38,
221       RIGHT: 39,
222       DOWN: 40,
223       CURSOR: new Array(37, 38, 39, 40),
224       DELETE: 46
225     },
226
227     NODE : {
228       ELEMENT: 1,
229       ATTRIBUTE: 2,
230       TEXT: 3
231     },
232         
233     /*
234         Class: WYMeditor.editor
235         WYMeditor editor main class, instanciated for each editor occurrence.
236     */
237
238         editor : function(elem, options) {
239
240         /*
241             Constructor: WYMeditor.editor
242
243             Initializes main values (index, elements, paths, ...)
244             and call WYMeditor.editor.init which initializes the editor.
245
246             Parameters:
247
248                 elem - The HTML element to be replaced by the editor.
249                 options - The hash of options.
250
251             Returns:
252
253                 Nothing.
254
255             See Also:
256
257                 <WYMeditor.editor.init>
258         */
259
260         //store the instance in the INSTANCES array and store the index
261         this._index = WYMeditor.INSTANCES.push(this) - 1;
262         //store the element replaced by the editor
263         this._element = elem;
264         //store the options
265         this._options = options;
266         //store the element's inner value
267         this._html = jQuery(elem).val();
268
269         //store the HTML option, if any
270         if(this._options.html) this._html = this._options.html;
271         //get or compute the base path (where the main JS file is located)
272         this._options.basePath = this._options.basePath
273         || this.computeBasePath();
274         //get or set the skin path (where the skin files are located)
275         this._options.skinPath = this._options.skinPath
276         || this._options.basePath + WYMeditor.SKINS_DEFAULT_PATH
277            + this._options.skin + '/';
278         //get or compute the main JS file location
279         this._options.wymPath = this._options.wymPath
280         || this.computeWymPath();
281         //get or set the language files path
282         this._options.langPath = this._options.langPath
283         || this._options.basePath + WYMeditor.LANG_DEFAULT_PATH;
284         //get or set the designmode iframe's base path
285         this._options.iframeBasePath = this._options.iframeBasePath
286         || this._options.basePath + WYMeditor.IFRAME_DEFAULT;
287         //get or compute the jQuery JS file location
288         this._options.jQueryPath = this._options.jQueryPath
289         || this.computeJqueryPath();
290
291         //initialize the editor instance
292         this.init();
293         
294         }
295
296 });
297
298
299 /********** JQUERY **********/
300
301 /**
302  * Replace an HTML element by WYMeditor
303  *
304  * @example jQuery(".wymeditor").wymeditor(
305  *        {
306  *
307  *        }
308  *      );
309  * @desc Example description here
310  * 
311  * @name WYMeditor
312  * @description WYMeditor is a web-based WYSIWYM XHTML editor
313  * @param Hash hash A hash of parameters
314  * @option Integer iExample Description here
315  * @option String sExample Description here
316  *
317  * @type jQuery
318  * @cat Plugins/WYMeditor
319  * @author Jean-Francois Hovinne
320  */
321 jQuery.fn.wymeditor = function(options) {
322
323   options = jQuery.extend({
324
325     html:       "",
326     
327     basePath:   false,
328     
329     skinPath:    false,
330     
331     wymPath:    false,
332     
333     iframeBasePath: false,
334     
335     jQueryPath: false,
336     
337     styles: false,
338     
339     stylesheet: false,
340     
341     skin:       "default",
342     initSkin:   true,
343     loadSkin:   true,
344
345     lang:       "en",
346
347     direction:  "ltr",
348
349     boxHtml:   "<div class='wym_box'>"
350               + "<div class='wym_area_top'>" 
351               + WYMeditor.TOOLS
352               + "</div>"
353               + "<div class='wym_area_left'></div>"
354               + "<div class='wym_area_right'>"
355               + WYMeditor.CONTAINERS
356               + WYMeditor.CLASSES
357               + "</div>"
358               + "<div class='wym_area_main'>"
359               + WYMeditor.HTML
360               + WYMeditor.IFRAME
361               + WYMeditor.STATUS
362               + "</div>"
363               + "<div class='wym_area_bottom'>"
364               + WYMeditor.LOGO
365               + "</div>"
366               + "</div>",
367
368     logoHtml:  "<a class='wym_wymeditor_link' "
369               + "href='http://www.wymeditor.org/'>WYMeditor</a>",
370
371     iframeHtml:"<div class='wym_iframe wym_section'>"
372               + "<iframe "
373               + "src='"
374               + WYMeditor.IFRAME_BASE_PATH
375               + "wymiframe.html' "
376               + "onload='this.contentWindow.parent.WYMeditor.INSTANCES["
377               + WYMeditor.INDEX + "].initIframe(this)'"
378               + "></iframe>"
379               + "</div>",
380               
381     editorStyles: [],
382
383     toolsHtml: "<div class='wym_tools wym_section'>"
384               + "<h2>{Tools}</h2>"
385               + "<ul>"
386               + WYMeditor.TOOLS_ITEMS
387               + "</ul>"
388               + "</div>",
389               
390     toolsItemHtml:   "<li class='"
391                         + WYMeditor.TOOL_CLASS
392                         + "'><a href='#' name='"
393                         + WYMeditor.TOOL_NAME
394                         + "' title='"
395                         + WYMeditor.TOOL_TITLE
396                         + "'>"
397                         + WYMeditor.TOOL_TITLE
398                         + "</a></li>",
399
400     toolsItems: [
401         {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}, 
402         {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},
403         {'name': 'Superscript', 'title': 'Superscript',
404             'css': 'wym_tools_superscript'},
405         {'name': 'Subscript', 'title': 'Subscript',
406             'css': 'wym_tools_subscript'},
407         {'name': 'InsertOrderedList', 'title': 'Ordered_List',
408             'css': 'wym_tools_ordered_list'},
409         {'name': 'InsertUnorderedList', 'title': 'Unordered_List',
410             'css': 'wym_tools_unordered_list'},
411         {'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'},
412         {'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'},
413         {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},
414         {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},
415         {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},
416         {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
417         {'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
418         {'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
419         {'name': 'Paste', 'title': 'Paste_From_Word',
420             'css': 'wym_tools_paste'},
421         {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'},
422         {'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}
423     ],
424
425     containersHtml:    "<div class='wym_containers wym_section'>"
426                         + "<h2>{Containers}</h2>"
427                         + "<ul>"
428                         + WYMeditor.CONTAINERS_ITEMS
429                         + "</ul>"
430                         + "</div>",
431                         
432     containersItemHtml:"<li class='"
433                         + WYMeditor.CONTAINER_CLASS
434                         + "'>"
435                         + "<a href='#' name='"
436                         + WYMeditor.CONTAINER_NAME
437                         + "'>"
438                         + WYMeditor.CONTAINER_TITLE
439                         + "</a></li>",
440                         
441     containersItems: [
442         {'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'},
443         {'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'},
444         {'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'},
445         {'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'},
446         {'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'},
447         {'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'},
448         {'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'},
449         {'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'},
450         {'name': 'BLOCKQUOTE', 'title': 'Blockquote',
451             'css': 'wym_containers_blockquote'},
452         {'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}
453     ],
454
455     classesHtml:       "<div class='wym_classes wym_section'>"
456                         + "<h2>{Classes}</h2><ul>"
457                         + WYMeditor.CLASSES_ITEMS
458                         + "</ul></div>",
459
460     classesItemHtml:   "<li><a href='#' name='"
461                         + WYMeditor.CLASS_NAME
462                         + "'>"
463                         + WYMeditor.CLASS_TITLE
464                         + "</a></li>",
465
466     classesItems:      [],
467
468     statusHtml:        "<div class='wym_status wym_section'>"
469                         + "<h2>{Status}</h2>"
470                         + "</div>",
471
472     htmlHtml:          "<div class='wym_html wym_section'>"
473                         + "<h2>{Source_Code}</h2>"
474                         + "<textarea class='wym_html_val'></textarea>"
475                         + "</div>",
476
477     boxSelector:       ".wym_box",
478     toolsSelector:     ".wym_tools",
479     toolsListSelector: " ul",
480     containersSelector:".wym_containers",
481     classesSelector:   ".wym_classes",
482     htmlSelector:      ".wym_html",
483     iframeSelector:    ".wym_iframe iframe",
484     iframeBodySelector:".wym_iframe",
485     statusSelector:    ".wym_status",
486     toolSelector:      ".wym_tools a",
487     containerSelector: ".wym_containers a",
488     classSelector:     ".wym_classes a",
489     htmlValSelector:   ".wym_html_val",
490     
491     hrefSelector:      ".wym_href",
492     srcSelector:       ".wym_src",
493     titleSelector:     ".wym_title",
494     altSelector:       ".wym_alt",
495     textSelector:      ".wym_text",
496     
497     rowsSelector:      ".wym_rows",
498     colsSelector:      ".wym_cols",
499     captionSelector:   ".wym_caption",
500     summarySelector:   ".wym_summary",
501     
502     submitSelector:    ".wym_submit",
503     cancelSelector:    ".wym_cancel",
504     previewSelector:   "",
505     
506     dialogTypeSelector:    ".wym_dialog_type",
507     dialogLinkSelector:    ".wym_dialog_link",
508     dialogImageSelector:   ".wym_dialog_image",
509     dialogTableSelector:   ".wym_dialog_table",
510     dialogPasteSelector:   ".wym_dialog_paste",
511     dialogPreviewSelector: ".wym_dialog_preview",
512     
513     updateSelector:    ".wymupdate",
514     updateEvent:       "click",
515     
516     dialogFeatures:    "menubar=no,titlebar=no,toolbar=no,resizable=no"
517                       + ",width=560,height=300,top=0,left=0",
518     dialogFeaturesPreview: "menubar=no,titlebar=no,toolbar=no,resizable=no"
519                       + ",scrollbars=yes,width=560,height=300,top=0,left=0",
520
521     dialogHtml:      "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'"
522                       + " 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>"
523                       + "<html dir='"
524                       + WYMeditor.DIRECTION
525                       + "'><head>"
526                       + "<link rel='stylesheet' type='text/css' media='screen'"
527                       + " href='"
528                       + WYMeditor.CSS_PATH
529                       + "' />"
530                       + "<title>"
531                       + WYMeditor.DIALOG_TITLE
532                       + "</title>"
533                       + "<script type='text/javascript'"
534                       + " src='"
535                       + WYMeditor.JQUERY_PATH
536                       + "'></script>"
537                       + "<script type='text/javascript'"
538                       + " src='"
539                       + WYMeditor.WYM_PATH
540                       + "'></script>"
541                       + "</head>"
542                       + WYMeditor.DIALOG_BODY
543                       + "</html>",
544                       
545     dialogLinkHtml:  "<body class='wym_dialog wym_dialog_link'"
546                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
547                + ">"
548                + "<form>"
549                + "<fieldset>"
550                + "<input type='hidden' class='wym_dialog_type' value='"
551                + WYMeditor.DIALOG_LINK
552                + "' />"
553                + "<legend>{Link}</legend>"
554                + "<div class='row'>"
555                + "<label>{URL}</label>"
556                + "<input type='text' class='wym_href' value='' size='40' />"
557                + "</div>"
558                + "<div class='row'>"
559                + "<label>{Title}</label>"
560                + "<input type='text' class='wym_title' value='' size='40' />"
561                + "</div>"
562                + "<div class='row row-indent'>"
563                + "<input class='wym_submit' type='button'"
564                + " value='{Submit}' />"
565                + "<input class='wym_cancel' type='button'"
566                + "value='{Cancel}' />"
567                + "</div>"
568                + "</fieldset>"
569                + "</form>"
570                + "</body>",
571     
572     dialogImageHtml:  "<body class='wym_dialog wym_dialog_image'"
573                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
574                + ">"
575                + "<form>"
576                + "<fieldset>"
577                + "<input type='hidden' class='wym_dialog_type' value='"
578                + WYMeditor.DIALOG_IMAGE
579                + "' />"
580                + "<legend>{Image}</legend>"
581                + "<div class='row'>"
582                + "<label>{URL}</label>"
583                + "<input type='text' class='wym_src' value='' size='40' />"
584                + "</div>"
585                + "<div class='row'>"
586                + "<label>{Alternative_Text}</label>"
587                + "<input type='text' class='wym_alt' value='' size='40' />"
588                + "</div>"
589                + "<div class='row'>"
590                + "<label>{Title}</label>"
591                + "<input type='text' class='wym_title' value='' size='40' />"
592                + "</div>"
593                + "<div class='row row-indent'>"
594                + "<input class='wym_submit' type='button'"
595                + " value='{Submit}' />"
596                + "<input class='wym_cancel' type='button'"
597                + "value='{Cancel}' />"
598                + "</div>"
599                + "</fieldset>"
600                + "</form>"
601                + "</body>",
602     
603     dialogTableHtml:  "<body class='wym_dialog wym_dialog_table'"
604                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
605                + ">"
606                + "<form>"
607                + "<fieldset>"
608                + "<input type='hidden' class='wym_dialog_type' value='"
609                + WYMeditor.DIALOG_TABLE
610                + "' />"
611                + "<legend>{Table}</legend>"
612                + "<div class='row'>"
613                + "<label>{Caption}</label>"
614                + "<input type='text' class='wym_caption' value='' size='40' />"
615                + "</div>"
616                + "<div class='row'>"
617                + "<label>{Summary}</label>"
618                + "<input type='text' class='wym_summary' value='' size='40' />"
619                + "</div>"
620                + "<div class='row'>"
621                + "<label>{Number_Of_Rows}</label>"
622                + "<input type='text' class='wym_rows' value='3' size='3' />"
623                + "</div>"
624                + "<div class='row'>"
625                + "<label>{Number_Of_Cols}</label>"
626                + "<input type='text' class='wym_cols' value='2' size='3' />"
627                + "</div>"
628                + "<div class='row row-indent'>"
629                + "<input class='wym_submit' type='button'"
630                + " value='{Submit}' />"
631                + "<input class='wym_cancel' type='button'"
632                + "value='{Cancel}' />"
633                + "</div>"
634                + "</fieldset>"
635                + "</form>"
636                + "</body>",
637
638     dialogPasteHtml:  "<body class='wym_dialog wym_dialog_paste'"
639                + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
640                + ">"
641                + "<form>"
642                + "<input type='hidden' class='wym_dialog_type' value='"
643                + WYMeditor.DIALOG_PASTE
644                + "' />"
645                + "<fieldset>"
646                + "<legend>{Paste_From_Word}</legend>"
647                + "<div class='row'>"
648                + "<textarea class='wym_text' rows='10' cols='50'></textarea>"
649                + "</div>"
650                + "<div class='row'>"
651                + "<input class='wym_submit' type='button'"
652                + " value='{Submit}' />"
653                + "<input class='wym_cancel' type='button'"
654                + "value='{Cancel}' />"
655                + "</div>"
656                + "</fieldset>"
657                + "</form>"
658                + "</body>",
659
660     dialogPreviewHtml: "<body class='wym_dialog wym_dialog_preview'"
661                       + " onload='WYMeditor.INIT_DIALOG(" + WYMeditor.INDEX + ")'"
662                       + "></body>",
663                       
664     dialogStyles: [],
665
666     stringDelimiterLeft: "{",
667     stringDelimiterRight:"}",
668     
669     preInit: null,
670     preBind: null,
671     postInit: null,
672     
673     preInitDialog: null,
674     postInitDialog: null
675
676   }, options);
677
678   return this.each(function() {
679
680     new WYMeditor.editor(jQuery(this),options);
681   });
682 };
683
684 /* @name extend
685  * @description Returns the WYMeditor instance based on its index
686  */
687 jQuery.extend({
688   wymeditors: function(i) {
689     return (WYMeditor.INSTANCES[i]);
690   }
691 });
692
693
694 /********** WYMeditor **********/
695
696 /* @name Wymeditor
697  * @description WYMeditor class
698  */
699
700 /* @name init
701  * @description Initializes a WYMeditor instance
702  */
703 WYMeditor.editor.prototype.init = function() {
704
705   //load subclass - browser specific
706   //unsupported browsers: do nothing
707   if (jQuery.browser.msie) {
708     var WymClass = new WYMeditor.WymClassExplorer(this);
709   }
710   else if (jQuery.browser.mozilla) {
711     var WymClass = new WYMeditor.WymClassMozilla(this);
712   }
713   else if (jQuery.browser.opera) {
714     var WymClass = new WYMeditor.WymClassOpera(this);
715   }
716   else if (jQuery.browser.safari) {
717     var WymClass = new WYMeditor.WymClassSafari(this);
718   }
719   
720   if(WymClass) {
721   
722       if(jQuery.isFunction(this._options.preInit)) this._options.preInit(this);
723
724       var SaxListener = new WYMeditor.XhtmlSaxListener();
725       jQuery.extend(SaxListener, WymClass);
726       this.parser = new WYMeditor.XhtmlParser(SaxListener);
727       
728       if(this._options.styles || this._options.stylesheet){
729         this.configureEditorUsingRawCss();
730       }
731       
732       this.helper = new WYMeditor.XmlHelper();
733       
734       //extend the Wymeditor object
735       //don't use jQuery.extend since 1.1.4
736       //jQuery.extend(this, WymClass);
737       for (var prop in WymClass) { this[prop] = WymClass[prop]; }
738
739       //load wymbox
740       this._box = jQuery(this._element).hide().after(this._options.boxHtml).next().addClass('wym_box_' + this._index);
741
742       //store the instance index in wymbox and element replaced by editor instance
743       //but keep it compatible with jQuery < 1.2.3, see #122
744       if( jQuery.isFunction( jQuery.fn.data ) ) {
745         jQuery.data(this._box.get(0), WYMeditor.WYM_INDEX, this._index);
746         jQuery.data(this._element.get(0), WYMeditor.WYM_INDEX, this._index);
747       }
748       
749       var h = WYMeditor.Helper;
750
751       //construct the iframe
752       var iframeHtml = this._options.iframeHtml;
753       iframeHtml = h.replaceAll(iframeHtml, WYMeditor.INDEX, this._index);
754       iframeHtml = h.replaceAll(iframeHtml, WYMeditor.IFRAME_BASE_PATH, this._options.iframeBasePath);
755       
756       //construct wymbox
757       var boxHtml = jQuery(this._box).html();
758       
759       boxHtml = h.replaceAll(boxHtml, WYMeditor.LOGO, this._options.logoHtml);
760       boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS, this._options.toolsHtml);
761       boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS,this._options.containersHtml);
762       boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES, this._options.classesHtml);
763       boxHtml = h.replaceAll(boxHtml, WYMeditor.HTML, this._options.htmlHtml);
764       boxHtml = h.replaceAll(boxHtml, WYMeditor.IFRAME, iframeHtml);
765       boxHtml = h.replaceAll(boxHtml, WYMeditor.STATUS, this._options.statusHtml);
766       
767       //construct tools list
768       var aTools = eval(this._options.toolsItems);
769       var sTools = "";
770
771       for(var i = 0; i < aTools.length; i++) {
772         var oTool = aTools[i];
773         if(oTool.name && oTool.title)
774           var sTool = this._options.toolsItemHtml;
775           var sTool = h.replaceAll(sTool, WYMeditor.TOOL_NAME, oTool.name);
776           sTool = h.replaceAll(sTool, WYMeditor.TOOL_TITLE, this._options.stringDelimiterLeft
777             + oTool.title
778             + this._options.stringDelimiterRight);
779           sTool = h.replaceAll(sTool, WYMeditor.TOOL_CLASS, oTool.css);
780           sTools += sTool;
781       }
782
783       boxHtml = h.replaceAll(boxHtml, WYMeditor.TOOLS_ITEMS, sTools);
784
785       //construct classes list
786       var aClasses = eval(this._options.classesItems);
787       var sClasses = "";
788
789       for(var i = 0; i < aClasses.length; i++) {
790         var oClass = aClasses[i];
791         if(oClass.name && oClass.title)
792           var sClass = this._options.classesItemHtml;
793           sClass = h.replaceAll(sClass, WYMeditor.CLASS_NAME, oClass.name);
794           sClass = h.replaceAll(sClass, WYMeditor.CLASS_TITLE, oClass.title);
795           sClasses += sClass;
796       }
797
798       boxHtml = h.replaceAll(boxHtml, WYMeditor.CLASSES_ITEMS, sClasses);
799       
800       //construct containers list
801       var aContainers = eval(this._options.containersItems);
802       var sContainers = "";
803
804       for(var i = 0; i < aContainers.length; i++) {
805         var oContainer = aContainers[i];
806         if(oContainer.name && oContainer.title)
807           var sContainer = this._options.containersItemHtml;
808           sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_NAME, oContainer.name);
809           sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_TITLE,
810               this._options.stringDelimiterLeft
811             + oContainer.title
812             + this._options.stringDelimiterRight);
813           sContainer = h.replaceAll(sContainer, WYMeditor.CONTAINER_CLASS, oContainer.css);
814           sContainers += sContainer;
815       }
816
817       boxHtml = h.replaceAll(boxHtml, WYMeditor.CONTAINERS_ITEMS, sContainers);
818
819       //l10n
820       boxHtml = this.replaceStrings(boxHtml);
821       
822       //load html in wymbox
823       jQuery(this._box).html(boxHtml);
824       
825       //hide the html value
826       jQuery(this._box).find(this._options.htmlSelector).hide();
827       
828       //enable the skin
829       this.loadSkin();
830       
831     }
832 };
833
834 WYMeditor.editor.prototype.bindEvents = function() {
835
836   //copy the instance
837   var wym = this;
838   
839   //handle click event on tools buttons
840   jQuery(this._box).find(this._options.toolSelector).click(function() {
841     wym._iframe.contentWindow.focus(); //See #154
842     wym.exec(jQuery(this).attr(WYMeditor.NAME));    
843     return(false);
844   });
845   
846   //handle click event on containers buttons
847   jQuery(this._box).find(this._options.containerSelector).click(function() {
848     wym.container(jQuery(this).attr(WYMeditor.NAME));
849     return(false);
850   });
851   
852   //handle keyup event on html value: set the editor value
853   //handle focus/blur events to check if the element has focus, see #147
854   jQuery(this._box).find(this._options.htmlValSelector)
855     .keyup(function() { jQuery(wym._doc.body).html(jQuery(this).val());})
856     .focus(function() { jQuery(this).toggleClass('hasfocus'); })
857     .blur(function() { jQuery(this).toggleClass('hasfocus'); });
858
859   //handle click event on classes buttons
860   jQuery(this._box).find(this._options.classSelector).click(function() {
861   
862     var aClasses = eval(wym._options.classesItems);
863     var sName = jQuery(this).attr(WYMeditor.NAME);
864     
865     var oClass = WYMeditor.Helper.findByName(aClasses, sName);
866     
867     if(oClass) {
868       var jqexpr = oClass.expr;
869       wym.toggleClass(sName, jqexpr);
870     }
871     wym._iframe.contentWindow.focus(); //See #154
872     return(false);
873   });
874   
875   //handle event on update element
876   jQuery(this._options.updateSelector)
877     .bind(this._options.updateEvent, function() {
878       wym.update();
879   });
880 };
881
882 WYMeditor.editor.prototype.ready = function() {
883   return(this._doc != null);
884 };
885
886
887 /********** METHODS **********/
888
889 /* @name box
890  * @description Returns the WYMeditor container
891  */
892 WYMeditor.editor.prototype.box = function() {
893   return(this._box);
894 };
895
896 /* @name html
897  * @description Get/Set the html value
898  */
899 WYMeditor.editor.prototype.html = function(html) {
900
901   if(typeof html === 'string') jQuery(this._doc.body).html(html);
902   else return(jQuery(this._doc.body).html());
903 };
904
905 /* @name xhtml
906  * @description Cleans up the HTML
907  */
908 WYMeditor.editor.prototype.xhtml = function() {
909     return this.parser.parse(this.html());
910 };
911
912 /* @name exec
913  * @description Executes a button command
914  */
915 WYMeditor.editor.prototype.exec = function(cmd) {
916   
917   //base function for execCommand
918   //open a dialog or exec
919   switch(cmd) {
920     case WYMeditor.CREATE_LINK:
921       var container = this.container();
922       if(container || this._selected_image) this.dialog(WYMeditor.DIALOG_LINK);
923     break;
924     
925     case WYMeditor.INSERT_IMAGE:
926       this.dialog(WYMeditor.DIALOG_IMAGE);
927     break;
928     
929     case WYMeditor.INSERT_TABLE:
930       this.dialog(WYMeditor.DIALOG_TABLE);
931     break;
932     
933     case WYMeditor.PASTE:
934       this.dialog(WYMeditor.DIALOG_PASTE);
935     break;
936     
937     case WYMeditor.TOGGLE_HTML:
938       this.update();
939       this.toggleHtml();
940
941       //partially fixes #121 when the user manually inserts an image
942       if(!jQuery(this._box).find(this._options.htmlSelector).is(':visible'))
943         this.listen();
944     break;
945     
946     case WYMeditor.PREVIEW:
947       this.dialog(WYMeditor.PREVIEW, this._options.dialogFeaturesPreview);
948     break;
949     
950     default:
951       this._exec(cmd);
952     break;
953   }
954 };
955
956 /* @name container
957  * @description Get/Set the selected container
958  */
959 WYMeditor.editor.prototype.container = function(sType) {
960
961   if(sType) {
962   
963     var container = null;
964     
965     if(sType.toLowerCase() == WYMeditor.TH) {
966     
967       container = this.container();
968       
969       //find the TD or TH container
970       switch(container.tagName.toLowerCase()) {
971       
972         case WYMeditor.TD: case WYMeditor.TH:
973           break;
974         default:
975           var aTypes = new Array(WYMeditor.TD,WYMeditor.TH);
976           container = this.findUp(this.container(), aTypes);
977           break;
978       }
979       
980       //if it exists, switch
981       if(container!=null) {
982       
983         sType = (container.tagName.toLowerCase() == WYMeditor.TD)? WYMeditor.TH: WYMeditor.TD;
984         this.switchTo(container,sType);
985         this.update();
986       }
987     } else {
988   
989       //set the container type
990       var aTypes=new Array(WYMeditor.P,WYMeditor.H1,WYMeditor.H2,WYMeditor.H3,WYMeditor.H4,WYMeditor.H5,
991       WYMeditor.H6,WYMeditor.PRE,WYMeditor.BLOCKQUOTE);
992       container = this.findUp(this.container(), aTypes);
993       
994       if(container) {
995   
996         var newNode = null;
997   
998         //blockquotes must contain a block level element
999         if(sType.toLowerCase() == WYMeditor.BLOCKQUOTE) {
1000         
1001           var blockquote = this.findUp(this.container(), WYMeditor.BLOCKQUOTE);
1002           
1003           if(blockquote == null) {
1004           
1005             newNode = this._doc.createElement(sType);
1006             container.parentNode.insertBefore(newNode,container);
1007             newNode.appendChild(container);
1008             this.setFocusToNode(newNode.firstChild);
1009             
1010           } else {
1011           
1012             var nodes = blockquote.childNodes;
1013             var lgt = nodes.length;
1014             var firstNode = null;
1015             
1016             if(lgt > 0) firstNode = nodes.item(0);
1017             for(var x=0; x<lgt; x++) {
1018               blockquote.parentNode.insertBefore(nodes.item(0),blockquote);
1019             }
1020             blockquote.parentNode.removeChild(blockquote);
1021             if(firstNode) this.setFocusToNode(firstNode);
1022           }
1023         }
1024         
1025         else this.switchTo(container,sType);
1026       
1027         this.update();
1028       }
1029     }
1030   }
1031   else return(this.selected());
1032 };
1033
1034 /* @name toggleClass
1035  * @description Toggles class on selected element, or one of its parents
1036  */
1037 WYMeditor.editor.prototype.toggleClass = function(sClass, jqexpr) {
1038
1039   var container = (this._selected_image
1040                     ? this._selected_image
1041                     : jQuery(this.selected()));
1042   container = jQuery(container).parentsOrSelf(jqexpr);
1043   jQuery(container).toggleClass(sClass);
1044
1045   if(!jQuery(container).attr(WYMeditor.CLASS)) jQuery(container).removeAttr(this._class);
1046
1047 };
1048
1049 /* @name findUp
1050  * @description Returns the first parent or self container, based on its type
1051  */
1052 WYMeditor.editor.prototype.findUp = function(node, filter) {
1053
1054   //filter is a string or an array of strings
1055
1056   if(node) {
1057
1058       var tagname = node.tagName.toLowerCase();
1059       
1060       if(typeof(filter) == WYMeditor.STRING) {
1061     
1062         while(tagname != filter && tagname != WYMeditor.BODY) {
1063         
1064           node = node.parentNode;
1065           tagname = node.tagName.toLowerCase();
1066         }
1067       
1068       } else {
1069       
1070         var bFound = false;
1071         
1072         while(!bFound && tagname != WYMeditor.BODY) {
1073           for(var i = 0; i < filter.length; i++) {
1074             if(tagname == filter[i]) {
1075               bFound = true;
1076               break;
1077             }
1078           }
1079           if(!bFound) {
1080             node = node.parentNode;
1081             tagname = node.tagName.toLowerCase();
1082           }
1083         }
1084       }
1085       
1086       if(tagname != WYMeditor.BODY) return(node);
1087       else return(null);
1088       
1089   } else return(null);
1090 };
1091
1092 /* @name switchTo
1093  * @description Switch the node's type
1094  */
1095 WYMeditor.editor.prototype.switchTo = function(node,sType) {
1096
1097   var newNode = this._doc.createElement(sType);
1098   var html = jQuery(node).html();
1099   node.parentNode.replaceChild(newNode,node);
1100   jQuery(newNode).html(html);
1101   this.setFocusToNode(newNode);
1102 };
1103
1104 WYMeditor.editor.prototype.replaceStrings = function(sVal) {
1105   //check if the language file has already been loaded
1106   //if not, get it via a synchronous ajax call
1107   if(!WYMeditor.STRINGS[this._options.lang]) {
1108     try {
1109       eval(jQuery.ajax({url:this._options.langPath
1110         + this._options.lang + '.js', async:false}).responseText);
1111     } catch(e) {
1112         WYMeditor.console.error("WYMeditor: error while parsing language file.");
1113         return sVal;
1114     }
1115   }
1116
1117   //replace all the strings in sVal and return it
1118   for (var key in WYMeditor.STRINGS[this._options.lang]) {
1119     sVal = WYMeditor.Helper.replaceAll(sVal, this._options.stringDelimiterLeft + key 
1120     + this._options.stringDelimiterRight,
1121     WYMeditor.STRINGS[this._options.lang][key]);
1122   };
1123   return(sVal);
1124 };
1125
1126 WYMeditor.editor.prototype.encloseString = function(sVal) {
1127
1128   return(this._options.stringDelimiterLeft
1129     + sVal
1130     + this._options.stringDelimiterRight);
1131 };
1132
1133 /* @name status
1134  * @description Prints a status message
1135  */
1136 WYMeditor.editor.prototype.status = function(sMessage) {
1137
1138   //print status message
1139   jQuery(this._box).find(this._options.statusSelector).html(sMessage);
1140 };
1141
1142 /* @name update
1143  * @description Updates the element and textarea values
1144  */
1145 WYMeditor.editor.prototype.update = function() {
1146
1147   var html = this.xhtml();
1148   jQuery(this._element).val(html);
1149   jQuery(this._box).find(this._options.htmlValSelector).not('.hasfocus').val(html); //#147
1150 };
1151
1152 /* @name dialog
1153  * @description Opens a dialog box
1154  */
1155 WYMeditor.editor.prototype.dialog = function( dialogType, dialogFeatures, bodyHtml ) {
1156   
1157   var features = dialogFeatures || this._wym._options.dialogFeatures;
1158   var wDialog = window.open('', 'dialog', features);
1159
1160   if(wDialog) {
1161
1162     var sBodyHtml = "";
1163     
1164     switch( dialogType ) {
1165
1166       case(WYMeditor.DIALOG_LINK):
1167         sBodyHtml = this._options.dialogLinkHtml;
1168       break;
1169       case(WYMeditor.DIALOG_IMAGE):
1170         sBodyHtml = this._options.dialogImageHtml;
1171       break;
1172       case(WYMeditor.DIALOG_TABLE):
1173         sBodyHtml = this._options.dialogTableHtml;
1174       break;
1175       case(WYMeditor.DIALOG_PASTE):
1176         sBodyHtml = this._options.dialogPasteHtml;
1177       break;
1178       case(WYMeditor.PREVIEW):
1179         sBodyHtml = this._options.dialogPreviewHtml;
1180       break;
1181
1182       default:
1183         sBodyHtml = bodyHtml;
1184     }
1185     
1186     var h = WYMeditor.Helper;
1187
1188     //construct the dialog
1189     var dialogHtml = this._options.dialogHtml;
1190     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.BASE_PATH, this._options.basePath);
1191     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIRECTION, this._options.direction);
1192     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.CSS_PATH, this._options.skinPath + WYMeditor.SKINS_DEFAULT_CSS);
1193     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.WYM_PATH, this._options.wymPath);
1194     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.JQUERY_PATH, this._options.jQueryPath);
1195     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_TITLE, this.encloseString( dialogType ));
1196     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.DIALOG_BODY, sBodyHtml);
1197     dialogHtml = h.replaceAll(dialogHtml, WYMeditor.INDEX, this._index);
1198       
1199     dialogHtml = this.replaceStrings(dialogHtml);
1200     
1201     var doc = wDialog.document;
1202     doc.write(dialogHtml);
1203     doc.close();
1204   }
1205 };
1206
1207 /* @name toggleHtml
1208  * @description Shows/Hides the HTML
1209  */
1210 WYMeditor.editor.prototype.toggleHtml = function() {
1211   jQuery(this._box).find(this._options.htmlSelector).toggle();
1212 };
1213
1214 WYMeditor.editor.prototype.uniqueStamp = function() {
1215         var now = new Date();
1216         return("wym-" + now.getTime());
1217 };
1218
1219 WYMeditor.editor.prototype.paste = function(sData) {
1220
1221   var sTmp;
1222   var container = this.selected();
1223         
1224   //split the data, using double newlines as the separator
1225   var aP = sData.split(this._newLine + this._newLine);
1226   var rExp = new RegExp(this._newLine, "g");
1227
1228   //add a P for each item
1229   if(container && container.tagName.toLowerCase() != WYMeditor.BODY) {
1230     for(x = aP.length - 1; x >= 0; x--) {
1231         sTmp = aP[x];
1232         //simple newlines are replaced by a break
1233         sTmp = sTmp.replace(rExp, "<br />");
1234         jQuery(container).after("<p>" + sTmp + "</p>");
1235     }
1236   } else {
1237     for(x = 0; x < aP.length; x++) {
1238         sTmp = aP[x];
1239         //simple newlines are replaced by a break
1240         sTmp = sTmp.replace(rExp, "<br />");
1241         jQuery(this._doc.body).append("<p>" + sTmp + "</p>");
1242     }
1243   
1244   }
1245 };
1246
1247 WYMeditor.editor.prototype.insert = function(html) {
1248     // Do we have a selection?
1249     if (this._iframe.contentWindow.getSelection().focusNode != null) {
1250         // Overwrite selection with provided html
1251         this._exec( WYMeditor.INSERT_HTML, html);
1252     } else {
1253         // Fall back to the internal paste function if there's no selection
1254         this.paste(html)
1255     }
1256 };
1257
1258 WYMeditor.editor.prototype.wrap = function(left, right) {
1259     // Do we have a selection?
1260     if (this._iframe.contentWindow.getSelection().focusNode != null) {
1261         // Wrap selection with provided html
1262         this._exec( WYMeditor.INSERT_HTML, left + this._iframe.contentWindow.getSelection().toString() + right);
1263     }
1264 };
1265
1266 WYMeditor.editor.prototype.unwrap = function() {
1267     // Do we have a selection?
1268     if (this._iframe.contentWindow.getSelection().focusNode != null) {
1269         // Unwrap selection
1270         this._exec( WYMeditor.INSERT_HTML, this._iframe.contentWindow.getSelection().toString() );
1271     }
1272 };
1273
1274 WYMeditor.editor.prototype.addCssRules = function(doc, aCss) {
1275   var styles = doc.styleSheets[0];
1276   if(styles) {
1277     for(var i = 0; i < aCss.length; i++) {
1278       var oCss = aCss[i];
1279       if(oCss.name && oCss.css) this.addCssRule(styles, oCss);
1280     }
1281   }
1282 };
1283
1284 /********** CONFIGURATION **********/
1285
1286 WYMeditor.editor.prototype.computeBasePath = function() {
1287   return jQuery(jQuery.grep(jQuery('script'), function(s){
1288     return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1289   })).attr('src').replace(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/, '');
1290 };
1291
1292 WYMeditor.editor.prototype.computeWymPath = function() {
1293   return jQuery(jQuery.grep(jQuery('script'), function(s){
1294     return (s.src && s.src.match(/jquery\.wymeditor(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1295   })).attr('src');
1296 };
1297
1298 WYMeditor.editor.prototype.computeJqueryPath = function() {
1299   return jQuery(jQuery.grep(jQuery('script'), function(s){
1300     return (s.src && s.src.match(/jquery(-(.*)){0,1}(\.pack|\.min|\.packed)?\.js(\?.*)?$/ ))
1301   })).attr('src');
1302 };
1303
1304 WYMeditor.editor.prototype.computeCssPath = function() {
1305   return jQuery(jQuery.grep(jQuery('link'), function(s){
1306    return (s.href && s.href.match(/wymeditor\/skins\/(.*)screen\.css(\?.*)?$/ ))
1307   })).attr('href');
1308 };
1309
1310 WYMeditor.editor.prototype.configureEditorUsingRawCss = function() {
1311
1312   var CssParser = new WYMeditor.WymCssParser();
1313   if(this._options.stylesheet){
1314     CssParser.parse(jQuery.ajax({url: this._options.stylesheet,async:false}).responseText);
1315   }else{
1316     CssParser.parse(this._options.styles, false);
1317   }
1318
1319   if(this._options.classesItems.length == 0) {
1320     this._options.classesItems = CssParser.css_settings.classesItems;
1321   }
1322   if(this._options.editorStyles.length == 0) {
1323     this._options.editorStyles = CssParser.css_settings.editorStyles;
1324   }
1325   if(this._options.dialogStyles.length == 0) {
1326     this._options.dialogStyles = CssParser.css_settings.dialogStyles;
1327   }
1328 };
1329
1330 /********** EVENTS **********/
1331
1332 WYMeditor.editor.prototype.listen = function() {
1333
1334   //don't use jQuery.find() on the iframe body
1335   //because of MSIE + jQuery + expando issue (#JQ1143)
1336   //jQuery(this._doc.body).find("*").bind("mouseup", this.mouseup);
1337   
1338   jQuery(this._doc.body).bind("mousedown", this.mousedown);
1339   var images = this._doc.body.getElementsByTagName("img");
1340   for(var i=0; i < images.length; i++) {
1341     jQuery(images[i]).bind("mousedown", this.mousedown);
1342   }
1343 };
1344
1345 WYMeditor.editor.prototype.mousedown = function(evt) {
1346   
1347   var wym = WYMeditor.INSTANCES[this.ownerDocument.title];
1348   wym._selected_image = (this.tagName.toLowerCase() == WYMeditor.IMG) ? this : null;
1349   evt.stopPropagation();
1350 };
1351
1352 /********** SKINS **********/
1353
1354 /*
1355  * Function: WYMeditor.loadCss
1356  *      Loads a stylesheet in the document.
1357  *
1358  * Parameters:
1359  *      href - The CSS path.
1360  */
1361 WYMeditor.loadCss = function(href) {
1362     
1363     var link = document.createElement('link');
1364     link.rel = 'stylesheet';
1365     link.href = href;
1366
1367     var head = jQuery('head').get(0);
1368     head.appendChild(link);
1369 };
1370
1371 /*
1372  *  Function: WYMeditor.editor.loadSkin
1373  *      Loads the skin CSS and initialization script (if needed).
1374  */
1375 WYMeditor.editor.prototype.loadSkin = function() {
1376
1377     //does the user want to automatically load the CSS (default: yes)?
1378     //we also test if it hasn't been already loaded by another instance
1379     //see below for a better (second) test
1380     if(this._options.loadSkin && !WYMeditor.SKINS[this._options.skin]) {
1381
1382         //check if it hasn't been already loaded
1383         //so we don't load it more than once
1384         //(we check the existing <link> elements)
1385
1386         var found = false;
1387         var rExp = new RegExp(this._options.skin
1388              + '\/' + WYMeditor.SKINS_DEFAULT_CSS + '$');
1389
1390         jQuery('link').each( function() {
1391             if(this.href.match(rExp)) found = true;
1392         });
1393
1394         //load it, using the skin path
1395         if(!found) WYMeditor.loadCss( this._options.skinPath
1396             + WYMeditor.SKINS_DEFAULT_CSS );
1397     }
1398
1399     //put the classname (ex. wym_skin_default) on wym_box
1400     jQuery(this._box).addClass( "wym_skin_" + this._options.skin );
1401
1402     //does the user want to use some JS to initialize the skin (default: yes)?
1403     //also check if it hasn't already been loaded by another instance
1404     if(this._options.initSkin && !WYMeditor.SKINS[this._options.skin]) {
1405
1406         eval(jQuery.ajax({url:this._options.skinPath
1407             + WYMeditor.SKINS_DEFAULT_JS, async:false}).responseText);
1408     }
1409
1410     //init the skin, if needed
1411     if(WYMeditor.SKINS[this._options.skin]
1412     && WYMeditor.SKINS[this._options.skin].init)
1413        WYMeditor.SKINS[this._options.skin].init(this);
1414
1415 };
1416
1417
1418 /********** DIALOGS **********/
1419
1420 WYMeditor.INIT_DIALOG = function(index) {
1421
1422   var wym = window.opener.WYMeditor.INSTANCES[index];
1423   var doc = window.document;
1424   var selected = wym.selected();
1425   var dialogType = jQuery(wym._options.dialogTypeSelector).val();
1426   var sStamp = wym.uniqueStamp();
1427
1428   switch(dialogType) {
1429
1430   case WYMeditor.DIALOG_LINK:
1431     //ensure that we select the link to populate the fields
1432     if(selected && selected.tagName && selected.tagName.toLowerCase != WYMeditor.A)
1433       selected = jQuery(selected).parentsOrSelf(WYMeditor.A);
1434
1435     //fix MSIE selection if link image has been clicked
1436     if(!selected && wym._selected_image)
1437       selected = jQuery(wym._selected_image).parentsOrSelf(WYMeditor.A);
1438   break;
1439
1440   }
1441
1442   //pre-init functions
1443   if(jQuery.isFunction(wym._options.preInitDialog))
1444     wym._options.preInitDialog(wym,window);
1445
1446   //add css rules from options
1447   var styles = doc.styleSheets[0];
1448   var aCss = eval(wym._options.dialogStyles);
1449
1450   wym.addCssRules(doc, aCss);
1451
1452   //auto populate fields if selected container (e.g. A)
1453   if(selected) {
1454     jQuery(wym._options.hrefSelector).val(jQuery(selected).attr(WYMeditor.HREF));
1455     jQuery(wym._options.srcSelector).val(jQuery(selected).attr(WYMeditor.SRC));
1456     jQuery(wym._options.titleSelector).val(jQuery(selected).attr(WYMeditor.TITLE));
1457     jQuery(wym._options.altSelector).val(jQuery(selected).attr(WYMeditor.ALT));
1458   }
1459
1460   //auto populate image fields if selected image
1461   if(wym._selected_image) {
1462     jQuery(wym._options.dialogImageSelector + " " + wym._options.srcSelector)
1463       .val(jQuery(wym._selected_image).attr(WYMeditor.SRC));
1464     jQuery(wym._options.dialogImageSelector + " " + wym._options.titleSelector)
1465       .val(jQuery(wym._selected_image).attr(WYMeditor.TITLE));
1466     jQuery(wym._options.dialogImageSelector + " " + wym._options.altSelector)
1467       .val(jQuery(wym._selected_image).attr(WYMeditor.ALT));
1468   }
1469
1470   jQuery(wym._options.dialogLinkSelector + " "
1471     + wym._options.submitSelector).click(function() {
1472
1473       var sUrl = jQuery(wym._options.hrefSelector).val();
1474       if(sUrl.length > 0) {
1475
1476         wym._exec(WYMeditor.CREATE_LINK, sStamp);
1477
1478         jQuery("a[href=" + sStamp + "]", wym._doc.body)
1479             .attr(WYMeditor.HREF, sUrl)
1480             .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val());
1481
1482       }
1483       window.close();
1484   });
1485
1486   jQuery(wym._options.dialogImageSelector + " "
1487     + wym._options.submitSelector).click(function() {
1488
1489       var sUrl = jQuery(wym._options.srcSelector).val();
1490       if(sUrl.length > 0) {
1491
1492         wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
1493
1494         jQuery("img[src$=" + sStamp + "]", wym._doc.body)
1495             .attr(WYMeditor.SRC, sUrl)
1496             .attr(WYMeditor.TITLE, jQuery(wym._options.titleSelector).val())
1497             .attr(WYMeditor.ALT, jQuery(wym._options.altSelector).val());
1498       }
1499       window.close();
1500   });
1501
1502   jQuery(wym._options.dialogTableSelector + " "
1503     + wym._options.submitSelector).click(function() {
1504
1505       var iRows = jQuery(wym._options.rowsSelector).val();
1506       var iCols = jQuery(wym._options.colsSelector).val();
1507
1508       if(iRows > 0 && iCols > 0) {
1509
1510         var table = wym._doc.createElement(WYMeditor.TABLE);
1511         var newRow = null;
1512                 var newCol = null;
1513
1514                 var sCaption = jQuery(wym._options.captionSelector).val();
1515
1516                 //we create the caption
1517                 var newCaption = table.createCaption();
1518                 newCaption.innerHTML = sCaption;
1519
1520                 //we create the rows and cells
1521                 for(x=0; x<iRows; x++) {
1522                         newRow = table.insertRow(x);
1523                         for(y=0; y<iCols; y++) {newRow.insertCell(y);}
1524                 }
1525
1526         //set the summary attr
1527         jQuery(table).attr('summary',
1528             jQuery(wym._options.summarySelector).val());
1529
1530         //append the table after the selected container
1531         var node = jQuery(wym.findUp(wym.container(),
1532           WYMeditor.MAIN_CONTAINERS)).get(0);
1533         if(!node || !node.parentNode) jQuery(wym._doc.body).append(table);
1534         else jQuery(node).after(table);
1535       }
1536       window.close();
1537   });
1538
1539   jQuery(wym._options.dialogPasteSelector + " "
1540     + wym._options.submitSelector).click(function() {
1541
1542       var sText = jQuery(wym._options.textSelector).val();
1543       wym.paste(sText);
1544       window.close();
1545   });
1546
1547   jQuery(wym._options.dialogPreviewSelector + " "
1548     + wym._options.previewSelector)
1549     .html(wym.xhtml());
1550
1551   //cancel button
1552   jQuery(wym._options.cancelSelector).mousedown(function() {
1553     window.close();
1554   });
1555
1556   //pre-init functions
1557   if(jQuery.isFunction(wym._options.postInitDialog))
1558     wym._options.postInitDialog(wym,window);
1559
1560 };
1561
1562 /********** XHTML LEXER/PARSER **********/
1563
1564 /*
1565 * @name xml
1566 * @description Use these methods to generate XML and XHTML compliant tags and
1567 * escape tag attributes correctly
1568 * @author Bermi Ferrer - http://bermi.org
1569 * @author David Heinemeier Hansson http://loudthinking.com
1570 */
1571 WYMeditor.XmlHelper = function()
1572 {
1573   this._entitiesDiv = document.createElement('div');
1574   return this;
1575 };
1576
1577
1578 /*
1579 * @name tag
1580 * @description
1581 * Returns an empty HTML tag of type *name* which by default is XHTML
1582 * compliant. Setting *open* to true will create an open tag compatible
1583 * with HTML 4.0 and below. Add HTML attributes by passing an attributes
1584 * array to *options*. For attributes with no value like (disabled and
1585 * readonly), give it a value of true in the *options* array.
1586 *
1587 * Examples:
1588 *
1589 *   this.tag('br')
1590 *    # => <br />
1591 *   this.tag ('br', false, true)
1592 *    # => <br>
1593 *   this.tag ('input', jQuery({type:'text',disabled:true }) )
1594 *    # => <input type="text" disabled="disabled" />
1595 */
1596 WYMeditor.XmlHelper.prototype.tag = function(name, options, open)
1597 {
1598   options = options || false;
1599   open = open || false;
1600   return '<'+name+(options ? this.tagOptions(options) : '')+(open ? '>' : ' />');
1601 };
1602
1603 /*
1604 * @name contentTag
1605 * @description
1606 * Returns a XML block tag of type *name* surrounding the *content*. Add
1607 * XML attributes by passing an attributes array to *options*. For attributes
1608 * with no value like (disabled and readonly), give it a value of true in
1609 * the *options* array. You can use symbols or strings for the attribute names.
1610 *
1611 *   this.contentTag ('p', 'Hello world!' )
1612 *    # => <p>Hello world!</p>
1613 *   this.contentTag('div', this.contentTag('p', "Hello world!"), jQuery({class : "strong"}))
1614 *    # => <div class="strong"><p>Hello world!</p></div>
1615 *   this.contentTag("select", options, jQuery({multiple : true}))
1616 *    # => <select multiple="multiple">...options...</select>
1617 */
1618 WYMeditor.XmlHelper.prototype.contentTag = function(name, content, options)
1619 {
1620   options = options || false;
1621   return '<'+name+(options ? this.tagOptions(options) : '')+'>'+content+'</'+name+'>';
1622 };
1623
1624 /*
1625 * @name cdataSection
1626 * @description
1627 * Returns a CDATA section for the given +content+.  CDATA sections
1628 * are used to escape blocks of text containing characters which would
1629 * otherwise be recognized as markup. CDATA sections begin with the string
1630 * <tt>&lt;![CDATA[</tt> and } with (and may not contain) the string
1631 * <tt>]]></tt>.
1632 */
1633 WYMeditor.XmlHelper.prototype.cdataSection = function(content)
1634 {
1635   return '<![CDATA['+content+']]>';
1636 };
1637
1638
1639 /*
1640 * @name escapeOnce
1641 * @description
1642 * Returns the escaped +xml+ without affecting existing escaped entities.
1643 *
1644 *  this.escapeOnce( "1 > 2 &amp; 3")
1645 *    # => "1 &gt; 2 &amp; 3"
1646 */
1647 WYMeditor.XmlHelper.prototype.escapeOnce = function(xml)
1648 {
1649   return this._fixDoubleEscape(this.escapeEntities(xml));
1650 };
1651
1652 /*
1653 * @name _fixDoubleEscape
1654 * @description
1655 * Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc.
1656 */
1657 WYMeditor.XmlHelper.prototype._fixDoubleEscape = function(escaped)
1658 {
1659   return escaped.replace(/&amp;([a-z]+|(#\d+));/ig, "&$1;");
1660 };
1661
1662 /*
1663 * @name tagOptions
1664 * @description
1665 * Takes an array like the one generated by Tag.parseAttributes
1666 *  [["src", "http://www.editam.com/?a=b&c=d&amp;f=g"], ["title", "Editam, <Simplified> CMS"]]
1667 * or an object like {src:"http://www.editam.com/?a=b&c=d&amp;f=g", title:"Editam, <Simplified> CMS"}
1668 * and returns a string properly escaped like
1669 * ' src = "http://www.editam.com/?a=b&amp;c=d&amp;f=g" title = "Editam, &lt;Simplified&gt; CMS"'
1670 * which is valid for strict XHTML
1671 */
1672 WYMeditor.XmlHelper.prototype.tagOptions = function(options)
1673 {
1674   var xml = this;
1675   xml._formated_options = '';
1676
1677   for (var key in options) {
1678     var formated_options = '';
1679     var value = options[key];
1680     if(typeof value != 'function' && value.length > 0) {
1681
1682       if(parseInt(key) == key && typeof value == 'object'){
1683         key = value.shift();
1684         value = value.pop();
1685       }
1686       if(key != '' && value != ''){
1687         xml._formated_options += ' '+key+'="'+xml.escapeOnce(value)+'"';
1688       }
1689     }
1690   }
1691   return xml._formated_options;
1692 };
1693
1694 /*
1695 * @name escapeEntities
1696 * @description
1697 * Escapes XML/HTML entities <, >, & and ". If seccond parameter is set to false it
1698 * will not escape ". If set to true it will also escape '
1699 */
1700 WYMeditor.XmlHelper.prototype.escapeEntities = function(string, escape_quotes)
1701 {
1702   this._entitiesDiv.innerHTML = string;
1703   this._entitiesDiv.textContent = string;
1704   var result = this._entitiesDiv.innerHTML;
1705   if(typeof escape_quotes == 'undefined'){
1706     if(escape_quotes != false) result = result.replace('"', '&quot;');
1707     if(escape_quotes == true)  result = result.replace('"', '&#039;');
1708   }
1709   return result;
1710 };
1711
1712 /*
1713 * Parses a string conatining tag attributes and values an returns an array formated like
1714 *  [["src", "http://www.editam.com"], ["title", "Editam, Simplified CMS"]]
1715 */
1716 WYMeditor.XmlHelper.prototype.parseAttributes = function(tag_attributes)
1717 {
1718   // Use a compounded regex to match single quoted, double quoted and unquoted attribute pairs
1719   var result = [];
1720   var matches = tag_attributes.split(/((=\s*")(")("))|((=\s*\')(\')(\'))|((=\s*[^>\s]*))/g);
1721   if(matches.toString() != tag_attributes){
1722     for (var k in matches) {
1723       var v = matches[k];
1724       if(typeof v != 'function' && v.length != 0){
1725         var re = new RegExp('(\\w+)\\s*'+v);
1726         if(match = tag_attributes.match(re) ){
1727           var value = v.replace(/^[\s=]+/, "");
1728           var delimiter = value.charAt(0);
1729           delimiter = delimiter == '"' ? '"' : (delimiter=="'"?"'":'');
1730           if(delimiter != ''){
1731             value = delimiter == '"' ? value.replace(/^"|"+$/g, '') :  value.replace(/^'|'+$/g, '');
1732           }
1733           tag_attributes = tag_attributes.replace(match[0],'');
1734           result.push([match[1] , value]);
1735         }
1736       }
1737     }
1738   }
1739   return result;
1740 };
1741
1742 /**
1743 * XhtmlValidator for validating tag attributes
1744 *
1745 * @author Bermi Ferrer - http://bermi.org
1746 */
1747 WYMeditor.XhtmlValidator = {
1748   "_attributes":
1749   {
1750     "core":
1751     {
1752       "except":[
1753       "base",
1754       "head",
1755       "html",
1756       "meta",
1757       "param",
1758       "script",
1759       "style",
1760       "title"
1761       ],
1762       "attributes":[
1763       "class",
1764       "id",
1765       "style",
1766       "title",
1767       "accesskey",
1768       "tabindex"
1769       ]
1770     },
1771     "language":
1772     {
1773       "except":[
1774       "base",
1775       "br",
1776       "hr",
1777       "iframe",
1778       "param",
1779       "script"
1780       ],
1781       "attributes":
1782       {
1783         "dir":[
1784         "ltr",
1785         "rtl"
1786         ],
1787         "0":"lang",
1788         "1":"xml:lang"
1789       }
1790     },
1791     "keyboard":
1792     {
1793       "attributes":
1794       {
1795         "accesskey":/^(\w){1}$/,
1796         "tabindex":/^(\d)+$/
1797       }
1798     }
1799   },
1800   "_events":
1801   {
1802     "window":
1803     {
1804       "only":[
1805       "body"
1806       ],
1807       "attributes":[
1808       "onload",
1809       "onunload"
1810       ]
1811     },
1812     "form":
1813     {
1814       "only":[
1815       "form",
1816       "input",
1817       "textarea",
1818       "select",
1819       "a",
1820       "label",
1821       "button"
1822       ],
1823       "attributes":[
1824       "onchange",
1825       "onsubmit",
1826       "onreset",
1827       "onselect",
1828       "onblur",
1829       "onfocus"
1830       ]
1831     },
1832     "keyboard":
1833     {
1834       "except":[
1835       "base",
1836       "bdo",
1837       "br",
1838       "frame",
1839       "frameset",
1840       "head",
1841       "html",
1842       "iframe",
1843       "meta",
1844       "param",
1845       "script",
1846       "style",
1847       "title"
1848       ],
1849       "attributes":[
1850       "onkeydown",
1851       "onkeypress",
1852       "onkeyup"
1853       ]
1854     },
1855     "mouse":
1856     {
1857       "except":[
1858       "base",
1859       "bdo",
1860       "br",
1861       "head",
1862       "html",
1863       "meta",
1864       "param",
1865       "script",
1866       "style",
1867       "title"
1868       ],
1869       "attributes":[
1870       "onclick",
1871       "ondblclick",
1872       "onmousedown",
1873       "onmousemove",
1874       "onmouseover",
1875       "onmouseout",
1876       "onmouseup"
1877       ]
1878     }
1879   },
1880   "_tags":
1881   {
1882     "a":
1883     {
1884       "attributes":
1885       {
1886         "0":"charset",
1887         "1":"coords",
1888         "2":"href",
1889         "3":"hreflang",
1890         "4":"name",
1891         "rel":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
1892         "rev":/^(alternate|designates|stylesheet|start|next|prev|contents|index|glossary|copyright|chapter|section|subsection|appendix|help|bookmark| |shortcut|icon)+$/,
1893         "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/,
1894         "5":"type"
1895       }
1896     },
1897     "0":"abbr",
1898     "1":"acronym",
1899     "2":"address",
1900     "area":
1901     {
1902       "attributes":
1903       {
1904         "0":"alt",
1905         "1":"coords",
1906         "2":"href",
1907         "nohref":/^(true|false)$/,
1908         "shape":/^(rect|rectangle|circ|circle|poly|polygon)$/
1909       },
1910       "required":[
1911       "alt"
1912       ]
1913     },
1914     "3":"b",
1915     "base":
1916     {
1917       "attributes":[
1918       "href"
1919       ],
1920       "required":[
1921       "href"
1922       ]
1923     },
1924     "bdo":
1925     {
1926       "attributes":
1927       {
1928         "dir":/^(ltr|rtl)$/
1929       },
1930       "required":[
1931       "dir"
1932       ]
1933     },
1934     "4":"big",
1935     "blockquote":
1936     {
1937       "attributes":[
1938       "cite"
1939       ]
1940     },
1941     "5":"body",
1942     "6":"br",
1943     "button":
1944     {
1945       "attributes":
1946       {
1947         "disabled":/^(disabled)$/,
1948         "type":/^(button|reset|submit)$/,
1949         "0":"value"
1950       },
1951       "inside":"form"
1952     },
1953     "7":"caption",
1954     "8":"cite",
1955     "9":"code",
1956     "col":
1957     {
1958       "attributes":
1959       {
1960         "align":/^(right|left|center|justify)$/,
1961         "0":"char",
1962         "1":"charoff",
1963         "span":/^(\d)+$/,
1964         "valign":/^(top|middle|bottom|baseline)$/,
1965         "2":"width"
1966       },
1967       "inside":"colgroup"
1968     },
1969     "colgroup":
1970     {
1971       "attributes":
1972       {
1973         "align":/^(right|left|center|justify)$/,
1974         "0":"char",
1975         "1":"charoff",
1976         "span":/^(\d)+$/,
1977         "valign":/^(top|middle|bottom|baseline)$/,
1978         "2":"width"
1979       }
1980     },
1981     "10":"dd",
1982     "del":
1983     {
1984       "attributes":
1985       {
1986         "0":"cite",
1987         "datetime":/^([0-9]){8}/
1988       }
1989     },
1990     "11":"div",
1991     "12":"dfn",
1992     "13":"dl",
1993     "14":"dt",
1994     "15":"em",
1995     "fieldset":
1996     {
1997       "inside":"form"
1998     },
1999     "form":
2000     {
2001       "attributes":
2002       {
2003         "0":"action",
2004         "1":"accept",
2005         "2":"accept-charset",
2006         "3":"enctype",
2007         "method":/^(get|post)$/
2008       },
2009       "required":[
2010       "action"
2011       ]
2012     },
2013     "head":
2014     {
2015       "attributes":[
2016       "profile"
2017       ]
2018     },
2019     "16":"h1",
2020     "17":"h2",
2021     "18":"h3",
2022     "19":"h4",
2023     "20":"h5",
2024     "21":"h6",
2025     "22":"hr",
2026     "html":
2027     {
2028       "attributes":[
2029       "xmlns"
2030       ]
2031     },
2032     "23":"i",
2033     "img":
2034     {
2035       "attributes":[
2036       "alt",
2037       "src",
2038       "height",
2039       "ismap",
2040       "longdesc",
2041       "usemap",
2042       "width"
2043       ],
2044       "required":[
2045       "alt",
2046       "src"
2047       ]
2048     },
2049     "input":
2050     {
2051       "attributes":
2052       {
2053         "0":"accept",
2054         "1":"alt",
2055         "checked":/^(checked)$/,
2056         "disabled":/^(disabled)$/,
2057         "maxlength":/^(\d)+$/,
2058         "2":"name",
2059         "readonly":/^(readonly)$/,
2060         "size":/^(\d)+$/,
2061         "3":"src",
2062         "type":/^(button|checkbox|file|hidden|image|password|radio|reset|submit|text)$/,
2063         "4":"value"
2064       },
2065       "inside":"form"
2066     },
2067     "ins":
2068     {
2069       "attributes":
2070       {
2071         "0":"cite",
2072         "datetime":/^([0-9]){8}/
2073       }
2074     },
2075     "24":"kbd",
2076     "label":
2077     {
2078       "attributes":[
2079       "for"
2080       ],
2081       "inside":"form"
2082     },
2083     "25":"legend",
2084     "26":"li",
2085     "link":
2086     {
2087       "attributes":
2088       {
2089         "0":"charset",
2090         "1":"href",
2091         "2":"hreflang",
2092         "media":/^(all|braille|print|projection|screen|speech|,|;| )+$/i,
2093         //next comment line required by Opera!
2094         /*"rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,*/
2095         "rel":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
2096         "rev":/^(alternate|appendix|bookmark|chapter|contents|copyright|glossary|help|home|index|next|prev|section|start|stylesheet|subsection| |shortcut|icon)+$/i,
2097         "3":"type"
2098       },
2099       "inside":"head"
2100     },
2101     "map":
2102     {
2103       "attributes":[
2104       "id",
2105       "name"
2106       ],
2107       "required":[
2108       "id"
2109       ]
2110     },
2111     "meta":
2112     {
2113       "attributes":
2114       {
2115         "0":"content",
2116         "http-equiv":/^(content\-type|expires|refresh|set\-cookie)$/i,
2117         "1":"name",
2118         "2":"scheme"
2119       },
2120       "required":[
2121       "content"
2122       ]
2123     },
2124     "27":"noscript",
2125     "object":
2126     {
2127       "attributes":[
2128       "archive",
2129       "classid",
2130       "codebase",
2131       "codetype",
2132       "data",
2133       "declare",
2134       "height",
2135       "name",
2136       "standby",
2137       "type",
2138       "usemap",
2139       "width"
2140       ]
2141     },
2142     "28":"ol",
2143     "optgroup":
2144     {
2145       "attributes":
2146       {
2147         "0":"label",
2148         "disabled": /^(disabled)$/
2149       },
2150       "required":[
2151       "label"
2152       ]
2153     },
2154     "option":
2155     {
2156       "attributes":
2157       {
2158         "0":"label",
2159         "disabled":/^(disabled)$/,
2160         "selected":/^(selected)$/,
2161         "1":"value"
2162       },
2163       "inside":"select"
2164     },
2165     "29":"p",
2166     "param":
2167     {
2168       "attributes":
2169       {
2170         "0":"type",
2171         "valuetype":/^(data|ref|object)$/,
2172         "1":"valuetype",
2173         "2":"value"
2174       },
2175       "required":[
2176       "name"
2177       ]
2178     },
2179     "30":"pre",
2180     "q":
2181     {
2182       "attributes":[
2183       "cite"
2184       ]
2185     },
2186     "31":"samp",
2187     "script":
2188     {
2189       "attributes":
2190       {
2191         "type":/^(text\/ecmascript|text\/javascript|text\/jscript|text\/vbscript|text\/vbs|text\/xml)$/,
2192         "0":"charset",
2193         "defer":/^(defer)$/,
2194         "1":"src"
2195       },
2196       "required":[
2197       "type"
2198       ]
2199     },
2200     "select":
2201     {
2202       "attributes":
2203       {
2204         "disabled":/^(disabled)$/,
2205         "multiple":/^(multiple)$/,
2206         "0":"name",
2207         "1":"size"
2208       },
2209       "inside":"form"
2210     },
2211     "32":"small",
2212     "33":"span",
2213     "34":"strong",
2214     "style":
2215     {
2216       "attributes":
2217       {
2218         "0":"type",
2219         "media":/^(screen|tty|tv|projection|handheld|print|braille|aural|all)$/
2220       },
2221       "required":[
2222       "type"
2223       ]
2224     },
2225     "35":"sub",
2226     "36":"sup",
2227     "table":
2228     {
2229       "attributes":
2230       {
2231         "0":"border",
2232         "1":"cellpadding",
2233         "2":"cellspacing",
2234         "frame":/^(void|above|below|hsides|lhs|rhs|vsides|box|border)$/,
2235         "rules":/^(none|groups|rows|cols|all)$/,
2236         "3":"summary",
2237         "4":"width"
2238       }
2239     },
2240     "tbody":
2241     {
2242       "attributes":
2243       {
2244         "align":/^(right|left|center|justify)$/,
2245         "0":"char",
2246         "1":"charoff",
2247         "valign":/^(top|middle|bottom|baseline)$/
2248       }
2249     },
2250     "td":
2251     {
2252       "attributes":
2253       {
2254         "0":"abbr",
2255         "align":/^(left|right|center|justify|char)$/,
2256         "1":"axis",
2257         "2":"char",
2258         "3":"charoff",
2259         "colspan":/^(\d)+$/,
2260         "4":"headers",
2261         "rowspan":/^(\d)+$/,
2262         "scope":/^(col|colgroup|row|rowgroup)$/,
2263         "valign":/^(top|middle|bottom|baseline)$/
2264       }
2265     },
2266     "textarea":
2267     {
2268       "attributes":[
2269       "cols",
2270       "rows",
2271       "disabled",
2272       "name",
2273       "readonly"
2274       ],
2275       "required":[
2276       "cols",
2277       "rows"
2278       ],
2279       "inside":"form"
2280     },
2281     "tfoot":
2282     {
2283       "attributes":
2284       {
2285         "align":/^(right|left|center|justify)$/,
2286         "0":"char",
2287         "1":"charoff",
2288         "valign":/^(top|middle|bottom)$/,
2289         "2":"baseline"
2290       }
2291     },
2292     "th":
2293     {
2294       "attributes":
2295       {
2296         "0":"abbr",
2297         "align":/^(left|right|center|justify|char)$/,
2298         "1":"axis",
2299         "2":"char",
2300         "3":"charoff",
2301         "colspan":/^(\d)+$/,
2302         "4":"headers",
2303         "rowspan":/^(\d)+$/,
2304         "scope":/^(col|colgroup|row|rowgroup)$/,
2305         "valign":/^(top|middle|bottom|baseline)$/
2306       }
2307     },
2308     "thead":
2309     {
2310       "attributes":
2311       {
2312         "align":/^(right|left|center|justify)$/,
2313         "0":"char",
2314         "1":"charoff",
2315         "valign":/^(top|middle|bottom|baseline)$/
2316       }
2317     },
2318     "37":"title",
2319     "tr":
2320     {
2321       "attributes":
2322       {
2323         "align":/^(right|left|center|justify|char)$/,
2324         "0":"char",
2325         "1":"charoff",
2326         "valign":/^(top|middle|bottom|baseline)$/
2327       }
2328     },
2329     "38":"tt",
2330     "39":"ul",
2331     "40":"var"
2332   },
2333
2334   // Temporary skiped attributes
2335   skiped_attributes : [],
2336   skiped_attribute_values : [],
2337
2338   getValidTagAttributes: function(tag, attributes)
2339   {
2340     var valid_attributes = {};
2341     var possible_attributes = this.getPossibleTagAttributes(tag);
2342     for(var attribute in attributes) {
2343       var value = attributes[attribute];
2344       var h = WYMeditor.Helper;
2345       if(!h.contains(this.skiped_attributes, attribute) && !h.contains(this.skiped_attribute_values, value)){
2346         if (typeof value != 'function' && h.contains(possible_attributes, attribute)) {
2347           if (this.doesAttributeNeedsValidation(tag, attribute)) {
2348             if(this.validateAttribute(tag, attribute, value)){
2349               valid_attributes[attribute] = value;
2350             }
2351           }else{
2352             valid_attributes[attribute] = value;
2353           }
2354         }
2355       }
2356     }
2357     return valid_attributes;
2358   },
2359   getUniqueAttributesAndEventsForTag : function(tag)
2360   {
2361     var result = [];
2362
2363     if (this._tags[tag] && this._tags[tag]['attributes']) {
2364       for (k in this._tags[tag]['attributes']) {
2365         result.push(parseInt(k) == k ? this._tags[tag]['attributes'][k] : k);
2366       }
2367     }
2368     return result;
2369   },
2370   getDefaultAttributesAndEventsForTags : function()
2371   {
2372     var result = [];
2373     for (var key in this._events){
2374       result.push(this._events[key]);
2375     }
2376     for (var key in this._attributes){
2377       result.push(this._attributes[key]);
2378     }
2379     return result;
2380   },
2381   isValidTag : function(tag)
2382   {
2383     if(this._tags[tag]){
2384       return true;
2385     }
2386     for(var key in this._tags){
2387       if(this._tags[key] == tag){
2388         return true;
2389       }
2390     }
2391     return false;
2392   },
2393   getDefaultAttributesAndEventsForTag : function(tag)
2394   {
2395     var default_attributes = [];
2396     if (this.isValidTag(tag)) {
2397       var default_attributes_and_events = this.getDefaultAttributesAndEventsForTags();
2398
2399       for(var key in default_attributes_and_events) {
2400         var defaults = default_attributes_and_events[key];
2401         if(typeof defaults == 'object'){
2402           var h = WYMeditor.Helper;
2403           if ((defaults['except'] && h.contains(defaults['except'], tag)) || (defaults['only'] && !h.contains(defaults['only'], tag))) {
2404             continue;
2405           }
2406
2407           var tag_defaults = defaults['attributes'] ? defaults['attributes'] : defaults['events'];
2408           for(var k in tag_defaults) {
2409             default_attributes.push(typeof tag_defaults[k] != 'string' ? k : tag_defaults[k]);
2410           }
2411         }
2412       }
2413     }
2414     return default_attributes;
2415   },
2416   doesAttributeNeedsValidation: function(tag, attribute)
2417   {
2418     return this._tags[tag] && ((this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute]) || (this._tags[tag]['required'] &&
2419      WYMeditor.Helper.contains(this._tags[tag]['required'], attribute)));
2420   },
2421   validateAttribute : function(tag, attribute, value)
2422   {
2423     if ( this._tags[tag] &&
2424       (this._tags[tag]['attributes'] && this._tags[tag]['attributes'][attribute] && value.length > 0 && !value.match(this._tags[tag]['attributes'][attribute])) || // invalid format
2425       (this._tags[tag] && this._tags[tag]['required'] && WYMeditor.Helper.contains(this._tags[tag]['required'], attribute) && value.length == 0) // required attribute
2426     ) {
2427       return false;
2428     }
2429     return typeof this._tags[tag] != 'undefined';
2430   },
2431   getPossibleTagAttributes : function(tag)
2432   {
2433     if (!this._possible_tag_attributes) {
2434       this._possible_tag_attributes = {};
2435     }
2436     if (!this._possible_tag_attributes[tag]) {
2437       this._possible_tag_attributes[tag] = this.getUniqueAttributesAndEventsForTag(tag).concat(this.getDefaultAttributesAndEventsForTag(tag));
2438     }
2439     return this._possible_tag_attributes[tag];
2440   }
2441 };
2442
2443
2444 /**
2445 *    Compounded regular expression. Any of
2446 *    the contained patterns could match and
2447 *    when one does, it's label is returned.
2448 *
2449 *    Constructor. Starts with no patterns.
2450 *    @param boolean case    True for case sensitive, false
2451 *                            for insensitive.
2452 *    @access public
2453 *    @author Marcus Baker (http://lastcraft.com)
2454 *    @author Bermi Ferrer (http://bermi.org)
2455 */
2456 WYMeditor.ParallelRegex = function(case_sensitive)
2457 {
2458   this._case = case_sensitive;
2459   this._patterns = [];
2460   this._labels = [];
2461   this._regex = null;
2462   return this;
2463 };
2464
2465
2466 /**
2467 *    Adds a pattern with an optional label.
2468 *    @param string pattern      Perl style regex, but ( and )
2469 *                                lose the usual meaning.
2470 *    @param string label        Label of regex to be returned
2471 *                                on a match.
2472 *    @access public
2473 */
2474 WYMeditor.ParallelRegex.prototype.addPattern = function(pattern, label)
2475 {
2476   label = label || true;
2477   var count = this._patterns.length;
2478   this._patterns[count] = pattern;
2479   this._labels[count] = label;
2480   this._regex = null;
2481 };
2482
2483 /**
2484 *    Attempts to match all patterns at once against
2485 *    a string.
2486 *    @param string subject      String to match against.
2487 *
2488 *    @return boolean             True on success.
2489 *    @return string match         First matched portion of
2490 *                                subject.
2491 *    @access public
2492 */
2493 WYMeditor.ParallelRegex.prototype.match = function(subject)
2494 {
2495   if (this._patterns.length == 0) {
2496     return [false, ''];
2497   }
2498   var matches = subject.match(this._getCompoundedRegex());
2499
2500   if(!matches){
2501     return [false, ''];
2502   }
2503   var match = matches[0];
2504   for (var i = 1; i < matches.length; i++) {
2505     if (matches[i]) {
2506       return [this._labels[i-1], match];
2507     }
2508   }
2509   return [true, matches[0]];
2510 };
2511
2512 /**
2513 *    Compounds the patterns into a single
2514 *    regular expression separated with the
2515 *    "or" operator. Caches the regex.
2516 *    Will automatically escape (, ) and / tokens.
2517 *    @param array patterns    List of patterns in order.
2518 *    @access private
2519 */
2520 WYMeditor.ParallelRegex.prototype._getCompoundedRegex = function()
2521 {
2522   if (this._regex == null) {
2523     for (var i = 0, count = this._patterns.length; i < count; i++) {
2524       this._patterns[i] = '(' + this._untokenizeRegex(this._tokenizeRegex(this._patterns[i]).replace(/([\/\(\)])/g,'\\$1')) + ')';
2525     }
2526     this._regex = new RegExp(this._patterns.join("|") ,this._getPerlMatchingFlags());
2527   }
2528   return this._regex;
2529 };
2530
2531 /**
2532 * Escape lookahead/lookbehind blocks
2533 */
2534 WYMeditor.ParallelRegex.prototype._tokenizeRegex = function(regex)
2535 {
2536   return regex.
2537   replace(/\(\?(i|m|s|x|U)\)/,     '~~~~~~Tk1\$1~~~~~~').
2538   replace(/\(\?(\-[i|m|s|x|U])\)/, '~~~~~~Tk2\$1~~~~~~').
2539   replace(/\(\?\=(.*)\)/,          '~~~~~~Tk3\$1~~~~~~').
2540   replace(/\(\?\!(.*)\)/,          '~~~~~~Tk4\$1~~~~~~').
2541   replace(/\(\?\<\=(.*)\)/,        '~~~~~~Tk5\$1~~~~~~').
2542   replace(/\(\?\<\!(.*)\)/,        '~~~~~~Tk6\$1~~~~~~').
2543   replace(/\(\?\:(.*)\)/,          '~~~~~~Tk7\$1~~~~~~');
2544 };
2545
2546 /**
2547 * Unscape lookahead/lookbehind blocks
2548 */
2549 WYMeditor.ParallelRegex.prototype._untokenizeRegex = function(regex)
2550 {
2551   return regex.
2552   replace(/~~~~~~Tk1(.{1})~~~~~~/,    "(?\$1)").
2553   replace(/~~~~~~Tk2(.{2})~~~~~~/,    "(?\$1)").
2554   replace(/~~~~~~Tk3(.*)~~~~~~/,      "(?=\$1)").
2555   replace(/~~~~~~Tk4(.*)~~~~~~/,      "(?!\$1)").
2556   replace(/~~~~~~Tk5(.*)~~~~~~/,      "(?<=\$1)").
2557   replace(/~~~~~~Tk6(.*)~~~~~~/,      "(?<!\$1)").
2558   replace(/~~~~~~Tk7(.*)~~~~~~/,      "(?:\$1)");
2559 };
2560
2561
2562 /**
2563 *    Accessor for perl regex mode flags to use.
2564 *    @return string       Perl regex flags.
2565 *    @access private
2566 */
2567 WYMeditor.ParallelRegex.prototype._getPerlMatchingFlags = function()
2568 {
2569   return (this._case ? "m" : "mi");
2570 };
2571
2572
2573
2574 /**
2575 *    States for a stack machine.
2576 *
2577 *    Constructor. Starts in named state.
2578 *    @param string start        Starting state name.
2579 *    @access public
2580 *    @author Marcus Baker (http://lastcraft.com)
2581 *    @author Bermi Ferrer (http://bermi.org)
2582 */
2583 WYMeditor.StateStack = function(start)
2584 {
2585   this._stack = [start];
2586   return this;
2587 };
2588
2589 /**
2590 *    Accessor for current state.
2591 *    @return string       State.
2592 *    @access public
2593 */
2594 WYMeditor.StateStack.prototype.getCurrent = function()
2595 {
2596   return this._stack[this._stack.length - 1];
2597 };
2598
2599 /**
2600 *    Adds a state to the stack and sets it
2601 *    to be the current state.
2602 *    @param string state        New state.
2603 *    @access public
2604 */
2605 WYMeditor.StateStack.prototype.enter = function(state)
2606 {
2607   this._stack.push(state);
2608 };
2609
2610 /**
2611 *    Leaves the current state and reverts
2612 *    to the previous one.
2613 *    @return boolean    False if we drop off
2614 *                       the bottom of the list.
2615 *    @access public
2616 */
2617 WYMeditor.StateStack.prototype.leave = function()
2618 {
2619   if (this._stack.length == 1) {
2620     return false;
2621   }
2622   this._stack.pop();
2623   return true;
2624 };
2625
2626
2627 // GLOBALS
2628 WYMeditor.LEXER_ENTER = 1;
2629 WYMeditor.LEXER_MATCHED = 2;
2630 WYMeditor.LEXER_UNMATCHED = 3;
2631 WYMeditor.LEXER_EXIT = 4;
2632 WYMeditor.LEXER_SPECIAL = 5;
2633
2634
2635 /**
2636 *    Accepts text and breaks it into tokens.
2637 *    Some optimisation to make the sure the
2638 *    content is only scanned by the PHP regex
2639 *    parser once. Lexer modes must not start
2640 *    with leading underscores.
2641 *
2642 *    Sets up the lexer in case insensitive matching
2643 *    by default.
2644 *    @param Parser parser  Handling strategy by reference.
2645 *    @param string start            Starting handler.
2646 *    @param boolean case            True for case sensitive.
2647 *    @access public
2648 *    @author Marcus Baker (http://lastcraft.com)
2649 *    @author Bermi Ferrer (http://bermi.org)
2650 */
2651 WYMeditor.Lexer = function(parser, start, case_sensitive)
2652 {
2653   start = start || 'accept';
2654   this._case = case_sensitive || false;
2655   this._regexes = {};
2656   this._parser = parser;
2657   this._mode = new WYMeditor.StateStack(start);
2658   this._mode_handlers = {};
2659   this._mode_handlers[start] = start;
2660   return this;
2661 };
2662
2663 /**
2664 *    Adds a token search pattern for a particular
2665 *    parsing mode. The pattern does not change the
2666 *    current mode.
2667 *    @param string pattern      Perl style regex, but ( and )
2668 *                                lose the usual meaning.
2669 *    @param string mode         Should only apply this
2670 *                                pattern when dealing with
2671 *                                this type of input.
2672 *    @access public
2673 */
2674 WYMeditor.Lexer.prototype.addPattern = function(pattern, mode)
2675 {
2676   var mode = mode || "accept";
2677   if (typeof this._regexes[mode] == 'undefined') {
2678     this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2679   }
2680   this._regexes[mode].addPattern(pattern);
2681   if (typeof this._mode_handlers[mode] == 'undefined') {
2682     this._mode_handlers[mode] = mode;
2683   }
2684 };
2685
2686 /**
2687 *    Adds a pattern that will enter a new parsing
2688 *    mode. Useful for entering parenthesis, strings,
2689 *    tags, etc.
2690 *    @param string pattern      Perl style regex, but ( and )
2691 *                                lose the usual meaning.
2692 *    @param string mode         Should only apply this
2693 *                                pattern when dealing with
2694 *                                this type of input.
2695 *    @param string new_mode     Change parsing to this new
2696 *                                nested mode.
2697 *    @access public
2698 */
2699 WYMeditor.Lexer.prototype.addEntryPattern = function(pattern, mode, new_mode)
2700 {
2701   if (typeof this._regexes[mode] == 'undefined') {
2702     this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2703   }
2704   this._regexes[mode].addPattern(pattern, new_mode);
2705   if (typeof this._mode_handlers[new_mode] == 'undefined') {
2706     this._mode_handlers[new_mode] = new_mode;
2707   }
2708 };
2709
2710 /**
2711 *    Adds a pattern that will exit the current mode
2712 *    and re-enter the previous one.
2713 *    @param string pattern      Perl style regex, but ( and )
2714 *                                lose the usual meaning.
2715 *    @param string mode         Mode to leave.
2716 *    @access public
2717 */
2718 WYMeditor.Lexer.prototype.addExitPattern = function(pattern, mode)
2719 {
2720   if (typeof this._regexes[mode] == 'undefined') {
2721     this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2722   }
2723   this._regexes[mode].addPattern(pattern, "__exit");
2724   if (typeof this._mode_handlers[mode] == 'undefined') {
2725     this._mode_handlers[mode] = mode;
2726   }
2727 };
2728
2729 /**
2730 *    Adds a pattern that has a special mode. Acts as an entry
2731 *    and exit pattern in one go, effectively calling a special
2732 *    parser handler for this token only.
2733 *    @param string pattern      Perl style regex, but ( and )
2734 *                                lose the usual meaning.
2735 *    @param string mode         Should only apply this
2736 *                                pattern when dealing with
2737 *                                this type of input.
2738 *    @param string special      Use this mode for this one token.
2739 *    @access public
2740 */
2741 WYMeditor.Lexer.prototype.addSpecialPattern =  function(pattern, mode, special)
2742 {
2743   if (typeof this._regexes[mode] == 'undefined') {
2744     this._regexes[mode] = new WYMeditor.ParallelRegex(this._case);
2745   }
2746   this._regexes[mode].addPattern(pattern, '_'+special);
2747   if (typeof this._mode_handlers[special] == 'undefined') {
2748     this._mode_handlers[special] = special;
2749   }
2750 };
2751
2752 /**
2753 *    Adds a mapping from a mode to another handler.
2754 *    @param string mode        Mode to be remapped.
2755 *    @param string handler     New target handler.
2756 *    @access public
2757 */
2758 WYMeditor.Lexer.prototype.mapHandler = function(mode, handler)
2759 {
2760   this._mode_handlers[mode] = handler;
2761 };
2762
2763 /**
2764 *    Splits the page text into tokens. Will fail
2765 *    if the handlers report an error or if no
2766 *    content is consumed. If successful then each
2767 *    unparsed and parsed token invokes a call to the
2768 *    held listener.
2769 *    @param string raw        Raw HTML text.
2770 *    @return boolean           True on success, else false.
2771 *    @access public
2772 */
2773 WYMeditor.Lexer.prototype.parse = function(raw)
2774 {
2775   if (typeof this._parser == 'undefined') {
2776     return false;
2777   }
2778
2779   var length = raw.length;
2780   var parsed;
2781   while (typeof (parsed = this._reduce(raw)) == 'object') {
2782     var raw = parsed[0];
2783     var unmatched = parsed[1];
2784     var matched = parsed[2];
2785     var mode = parsed[3];
2786
2787     if (! this._dispatchTokens(unmatched, matched, mode)) {
2788       return false;
2789     }
2790
2791     if (raw == '') {
2792       return true;
2793     }
2794     if (raw.length == length) {
2795       return false;
2796     }
2797     length = raw.length;
2798   }
2799   if (! parsed ) {
2800     return false;
2801   }
2802
2803   return this._invokeParser(raw, WYMeditor.LEXER_UNMATCHED);
2804 };
2805
2806 /**
2807 *    Sends the matched token and any leading unmatched
2808 *    text to the parser changing the lexer to a new
2809 *    mode if one is listed.
2810 *    @param string unmatched    Unmatched leading portion.
2811 *    @param string matched      Actual token match.
2812 *    @param string mode         Mode after match. A boolean
2813 *                                false mode causes no change.
2814 *    @return boolean             False if there was any error
2815 *                                from the parser.
2816 *    @access private
2817 */
2818 WYMeditor.Lexer.prototype._dispatchTokens = function(unmatched, matched, mode)
2819 {
2820   mode = mode || false;
2821
2822   if (! this._invokeParser(unmatched, WYMeditor.LEXER_UNMATCHED)) {
2823     return false;
2824   }
2825
2826   if (typeof mode == 'boolean') {
2827     return this._invokeParser(matched, WYMeditor.LEXER_MATCHED);
2828   }
2829   if (this._isModeEnd(mode)) {
2830     if (! this._invokeParser(matched, WYMeditor.LEXER_EXIT)) {
2831       return false;
2832     }
2833     return this._mode.leave();
2834   }
2835   if (this._isSpecialMode(mode)) {
2836     this._mode.enter(this._decodeSpecial(mode));
2837     if (! this._invokeParser(matched, WYMeditor.LEXER_SPECIAL)) {
2838       return false;
2839     }
2840     return this._mode.leave();
2841   }
2842   this._mode.enter(mode);
2843
2844   return this._invokeParser(matched, WYMeditor.LEXER_ENTER);
2845 };
2846
2847 /**
2848 *    Tests to see if the new mode is actually to leave
2849 *    the current mode and pop an item from the matching
2850 *    mode stack.
2851 *    @param string mode    Mode to test.
2852 *    @return boolean        True if this is the exit mode.
2853 *    @access private
2854 */
2855 WYMeditor.Lexer.prototype._isModeEnd = function(mode)
2856 {
2857   return (mode === "__exit");
2858 };
2859
2860 /**
2861 *    Test to see if the mode is one where this mode
2862 *    is entered for this token only and automatically
2863 *    leaves immediately afterwoods.
2864 *    @param string mode    Mode to test.
2865 *    @return boolean        True if this is the exit mode.
2866 *    @access private
2867 */
2868 WYMeditor.Lexer.prototype._isSpecialMode = function(mode)
2869 {
2870   return (mode.substring(0,1) == "_");
2871 };
2872
2873 /**
2874 *    Strips the magic underscore marking single token
2875 *    modes.
2876 *    @param string mode    Mode to decode.
2877 *    @return string         Underlying mode name.
2878 *    @access private
2879 */
2880 WYMeditor.Lexer.prototype._decodeSpecial = function(mode)
2881 {
2882   return mode.substring(1);
2883 };
2884
2885 /**
2886 *    Calls the parser method named after the current
2887 *    mode. Empty content will be ignored. The lexer
2888 *    has a parser handler for each mode in the lexer.
2889 *    @param string content        Text parsed.
2890 *    @param boolean is_match      Token is recognised rather
2891 *                                  than unparsed data.
2892 *    @access private
2893 */
2894 WYMeditor.Lexer.prototype._invokeParser = function(content, is_match)
2895 {
2896
2897   if (!/ +/.test(content) && ((content === '') || (content == false))) {
2898     return true;
2899   }
2900   var current = this._mode.getCurrent();
2901   var handler = this._mode_handlers[current];
2902   var result;
2903   eval('result = this._parser.' + handler + '(content, is_match);');
2904   return result;
2905 };
2906
2907 /**
2908 *    Tries to match a chunk of text and if successful
2909 *    removes the recognised chunk and any leading
2910 *    unparsed data. Empty strings will not be matched.
2911 *    @param string raw         The subject to parse. This is the
2912 *                               content that will be eaten.
2913 *    @return array/boolean      Three item list of unparsed
2914 *                               content followed by the
2915 *                               recognised token and finally the
2916 *                               action the parser is to take.
2917 *                               True if no match, false if there
2918 *                               is a parsing error.
2919 *    @access private
2920 */
2921 WYMeditor.Lexer.prototype._reduce = function(raw)
2922 {
2923   var matched = this._regexes[this._mode.getCurrent()].match(raw);
2924   var match = matched[1];
2925   var action = matched[0];
2926   if (action) {
2927     var unparsed_character_count = raw.indexOf(match);
2928     var unparsed = raw.substr(0, unparsed_character_count);
2929     raw = raw.substring(unparsed_character_count + match.length);
2930     return [raw, unparsed, match, action];
2931   }
2932   return true;
2933 };
2934
2935
2936
2937 /**
2938 * This are the rules for breaking the XHTML code into events
2939 * handled by the provided parser.
2940 *
2941 *    @author Marcus Baker (http://lastcraft.com)
2942 *    @author Bermi Ferrer (http://bermi.org)
2943 */
2944 WYMeditor.XhtmlLexer = function(parser)
2945 {
2946   jQuery.extend(this, new WYMeditor.Lexer(parser, 'Text'));
2947
2948   this.mapHandler('Text', 'Text');
2949
2950   this.addTokens();
2951
2952   this.init();
2953
2954   return this;
2955 };
2956
2957
2958 WYMeditor.XhtmlLexer.prototype.init = function()
2959 {
2960 };
2961
2962 WYMeditor.XhtmlLexer.prototype.addTokens = function()
2963 {
2964   this.addCommentTokens('Text');
2965   this.addScriptTokens('Text');
2966   this.addCssTokens('Text');
2967   this.addTagTokens('Text');
2968 };
2969
2970 WYMeditor.XhtmlLexer.prototype.addCommentTokens = function(scope)
2971 {
2972   this.addEntryPattern("<!--", scope, 'Comment');
2973   this.addExitPattern("-->", 'Comment');
2974 };
2975
2976 WYMeditor.XhtmlLexer.prototype.addScriptTokens = function(scope)
2977 {
2978   this.addEntryPattern("<script", scope, 'Script');
2979   this.addExitPattern("</script>", 'Script');
2980 };
2981
2982 WYMeditor.XhtmlLexer.prototype.addCssTokens = function(scope)
2983 {
2984   this.addEntryPattern("<style", scope, 'Css');
2985   this.addExitPattern("</style>", 'Css');
2986 };
2987
2988 WYMeditor.XhtmlLexer.prototype.addTagTokens = function(scope)
2989 {
2990   this.addSpecialPattern("<\\s*[a-z0-9:\-]+\\s*>", scope, 'OpeningTag');
2991   this.addEntryPattern("<[a-z0-9:\-]+"+'[\\\/ \\\>]+', scope, 'OpeningTag');
2992   this.addInTagDeclarationTokens('OpeningTag');
2993
2994   this.addSpecialPattern("</\\s*[a-z0-9:\-]+\\s*>", scope, 'ClosingTag');
2995
2996 };
2997
2998 WYMeditor.XhtmlLexer.prototype.addInTagDeclarationTokens = function(scope)
2999 {
3000   this.addSpecialPattern('\\s+', scope, 'Ignore');
3001
3002   this.addAttributeTokens(scope);
3003
3004   this.addExitPattern('/>', scope);
3005   this.addExitPattern('>', scope);
3006
3007 };
3008
3009 WYMeditor.XhtmlLexer.prototype.addAttributeTokens = function(scope)
3010 {
3011   this.addSpecialPattern("\\s*[a-z-_0-9]*:?[a-z-_0-9]+\\s*(?=\=)\\s*", scope, 'TagAttributes');
3012
3013   this.addEntryPattern('=\\s*"', scope, 'DoubleQuotedAttribute');
3014   this.addPattern("\\\\\"", 'DoubleQuotedAttribute');
3015   this.addExitPattern('"', 'DoubleQuotedAttribute');
3016
3017   this.addEntryPattern("=\\s*'", scope, 'SingleQuotedAttribute');
3018   this.addPattern("\\\\'", 'SingleQuotedAttribute');
3019   this.addExitPattern("'", 'SingleQuotedAttribute');
3020
3021   this.addSpecialPattern('=\\s*[^>\\s]*', scope, 'UnquotedAttribute');
3022 };
3023
3024
3025
3026 /**
3027 * XHTML Parser.
3028 *
3029 * This XHTML parser will trigger the events available on on
3030 * current SaxListener
3031 *
3032 *    @author Bermi Ferrer (http://bermi.org)
3033 */
3034 WYMeditor.XhtmlParser = function(Listener, mode)
3035 {
3036   var mode = mode || 'Text';
3037   this._Lexer = new WYMeditor.XhtmlLexer(this);
3038   this._Listener = Listener;
3039   this._mode = mode;
3040   this._matches = [];
3041   this._last_match = '';
3042   this._current_match = '';
3043
3044   return this;
3045 };
3046
3047 WYMeditor.XhtmlParser.prototype.parse = function(raw)
3048 {
3049   this._Lexer.parse(this.beforeParsing(raw));
3050   return this.afterParsing(this._Listener.getResult());
3051 };
3052
3053 WYMeditor.XhtmlParser.prototype.beforeParsing = function(raw)
3054 {
3055   if(raw.match(/class="MsoNormal"/) || raw.match(/ns = "urn:schemas-microsoft-com/)){
3056     // Usefull for cleaning up content pasted from other sources (MSWord)
3057     this._Listener.avoidStylingTagsAndAttributes();
3058   }
3059   return this._Listener.beforeParsing(raw);
3060 };
3061
3062 WYMeditor.XhtmlParser.prototype.afterParsing = function(parsed)
3063 {
3064   if(this._Listener._avoiding_tags_implicitly){
3065     this._Listener.allowStylingTagsAndAttributes();
3066   }
3067   return this._Listener.afterParsing(parsed);
3068 };
3069
3070
3071 WYMeditor.XhtmlParser.prototype.Ignore = function(match, state)
3072 {
3073   return true;
3074 };
3075
3076 WYMeditor.XhtmlParser.prototype.Text = function(text)
3077 {
3078   this._Listener.addContent(text);
3079   return true;
3080 };
3081
3082 WYMeditor.XhtmlParser.prototype.Comment = function(match, status)
3083 {
3084   return this._addNonTagBlock(match, status, 'addComment');
3085 };
3086
3087 WYMeditor.XhtmlParser.prototype.Script = function(match, status)
3088 {
3089   return this._addNonTagBlock(match, status, 'addScript');
3090 };
3091
3092 WYMeditor.XhtmlParser.prototype.Css = function(match, status)
3093 {
3094   return this._addNonTagBlock(match, status, 'addCss');
3095 };
3096
3097 WYMeditor.XhtmlParser.prototype._addNonTagBlock = function(match, state, type)
3098 {
3099   switch (state){
3100     case WYMeditor.LEXER_ENTER:
3101     this._non_tag = match;
3102     break;
3103     case WYMeditor.LEXER_UNMATCHED:
3104     this._non_tag += match;
3105     break;
3106     case WYMeditor.LEXER_EXIT:
3107     switch(type) {
3108       case 'addComment':
3109       this._Listener.addComment(this._non_tag+match);
3110       break;
3111       case 'addScript':
3112       this._Listener.addScript(this._non_tag+match);
3113       break;
3114       case 'addCss':
3115       this._Listener.addCss(this._non_tag+match);
3116       break;
3117     }
3118   }
3119   return true;
3120 };
3121
3122 WYMeditor.XhtmlParser.prototype.OpeningTag = function(match, state)
3123 {
3124   switch (state){
3125     case WYMeditor.LEXER_ENTER:
3126     this._tag = this.normalizeTag(match);
3127     this._tag_attributes = {};
3128     break;
3129     case WYMeditor.LEXER_SPECIAL:
3130     this._callOpenTagListener(this.normalizeTag(match));
3131     break;
3132     case WYMeditor.LEXER_EXIT:
3133     this._callOpenTagListener(this._tag, this._tag_attributes);
3134   }
3135   return true;
3136 };
3137
3138 WYMeditor.XhtmlParser.prototype.ClosingTag = function(match, state)
3139 {
3140   this._callCloseTagListener(this.normalizeTag(match));
3141   return true;
3142 };
3143
3144 WYMeditor.XhtmlParser.prototype._callOpenTagListener = function(tag, attributes)
3145 {
3146   var  attributes = attributes || {};
3147   this.autoCloseUnclosedBeforeNewOpening(tag);
3148
3149   if(this._Listener.isBlockTag(tag)){
3150     this._Listener._tag_stack.push(tag);
3151     this._Listener.fixNestingBeforeOpeningBlockTag(tag, attributes);
3152     this._Listener.openBlockTag(tag, attributes);
3153     this._increaseOpenTagCounter(tag);
3154   }else if(this._Listener.isInlineTag(tag)){
3155     this._Listener.inlineTag(tag, attributes);
3156   }else{
3157     this._Listener.openUnknownTag(tag, attributes);
3158     this._increaseOpenTagCounter(tag);
3159   }
3160   this._Listener.last_tag = tag;
3161   this._Listener.last_tag_opened = true;
3162   this._Listener.last_tag_attributes = attributes;
3163 };
3164
3165 WYMeditor.XhtmlParser.prototype._callCloseTagListener = function(tag)
3166 {
3167   if(this._decreaseOpenTagCounter(tag)){
3168     this.autoCloseUnclosedBeforeTagClosing(tag);
3169
3170     if(this._Listener.isBlockTag(tag)){
3171       var expected_tag = this._Listener._tag_stack.pop();
3172       if(expected_tag == false){
3173         return;
3174       }else if(expected_tag != tag){
3175         tag = expected_tag;
3176       }
3177       this._Listener.closeBlockTag(tag);
3178     }else{
3179       this._Listener.closeUnknownTag(tag);
3180     }
3181   }else{
3182     this._Listener.closeUnopenedTag(tag);
3183   }
3184   this._Listener.last_tag = tag;
3185   this._Listener.last_tag_opened = false;
3186 };
3187
3188 WYMeditor.XhtmlParser.prototype._increaseOpenTagCounter = function(tag)
3189 {
3190   this._Listener._open_tags[tag] = this._Listener._open_tags[tag] || 0;
3191   this._Listener._open_tags[tag]++;
3192 };
3193
3194 WYMeditor.XhtmlParser.prototype._decreaseOpenTagCounter = function(tag)
3195 {
3196   if(this._Listener._open_tags[tag]){
3197     this._Listener._open_tags[tag]--;
3198     if(this._Listener._open_tags[tag] == 0){
3199       this._Listener._open_tags[tag] = undefined;
3200     }
3201     return true;
3202   }
3203   return false;
3204 };
3205
3206 WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeNewOpening = function(new_tag)
3207 {
3208   this._autoCloseUnclosed(new_tag, false);
3209 };
3210
3211 WYMeditor.XhtmlParser.prototype.autoCloseUnclosedBeforeTagClosing = function(tag)
3212 {
3213   this._autoCloseUnclosed(tag, true);
3214 };
3215
3216 WYMeditor.XhtmlParser.prototype._autoCloseUnclosed = function(new_tag, closing)
3217 {
3218   var closing = closing || false;
3219   if(this._Listener._open_tags){
3220     for (var tag in this._Listener._open_tags) {
3221       var counter = this._Listener._open_tags[tag];
3222       if(counter > 0 && this._Listener.shouldCloseTagAutomatically(tag, new_tag, closing)){
3223         this._callCloseTagListener(tag, true);
3224       }
3225     }
3226   }
3227 };
3228
3229 WYMeditor.XhtmlParser.prototype.getTagReplacements = function()
3230 {
3231   return this._Listener.getTagReplacements();
3232 };
3233
3234 WYMeditor.XhtmlParser.prototype.normalizeTag = function(tag)
3235 {
3236   tag = tag.replace(/^([\s<\/>]*)|([\s<\/>]*)$/gm,'').toLowerCase();
3237   var tags = this._Listener.getTagReplacements();
3238   if(tags[tag]){
3239     return tags[tag];
3240   }
3241   return tag;
3242 };
3243
3244 WYMeditor.XhtmlParser.prototype.TagAttributes = function(match, state)
3245 {
3246   if(WYMeditor.LEXER_SPECIAL == state){
3247     this._current_attribute = match;
3248   }
3249   return true;
3250 };
3251
3252 WYMeditor.XhtmlParser.prototype.DoubleQuotedAttribute = function(match, state)
3253 {
3254   if(WYMeditor.LEXER_UNMATCHED == state){
3255     this._tag_attributes[this._current_attribute] = match;
3256   }
3257   return true;
3258 };
3259
3260 WYMeditor.XhtmlParser.prototype.SingleQuotedAttribute = function(match, state)
3261 {
3262   if(WYMeditor.LEXER_UNMATCHED == state){
3263     this._tag_attributes[this._current_attribute] = match;
3264   }
3265   return true;
3266 };
3267
3268 WYMeditor.XhtmlParser.prototype.UnquotedAttribute = function(match, state)
3269 {
3270   this._tag_attributes[this._current_attribute] = match.replace(/^=/,'');
3271   return true;
3272 };
3273
3274
3275
3276 /**
3277 * XHTML Sax parser.
3278 *
3279 *    @author Bermi Ferrer (http://bermi.org)
3280 */
3281 WYMeditor.XhtmlSaxListener = function()
3282 {
3283   this.output = '';
3284   this.helper = new WYMeditor.XmlHelper();
3285   this._open_tags = {};
3286   this.validator = WYMeditor.XhtmlValidator;
3287   this._tag_stack = [];
3288   this.avoided_tags = [];
3289
3290   this.entities = {
3291     '&nbsp;':'&#160;','&iexcl;':'&#161;','&cent;':'&#162;',
3292     '&pound;':'&#163;','&curren;':'&#164;','&yen;':'&#165;',
3293     '&brvbar;':'&#166;','&sect;':'&#167;','&uml;':'&#168;',
3294     '&copy;':'&#169;','&ordf;':'&#170;','&laquo;':'&#171;',
3295     '&not;':'&#172;','&shy;':'&#173;','&reg;':'&#174;',
3296     '&macr;':'&#175;','&deg;':'&#176;','&plusmn;':'&#177;',
3297     '&sup2;':'&#178;','&sup3;':'&#179;','&acute;':'&#180;',
3298     '&micro;':'&#181;','&para;':'&#182;','&middot;':'&#183;',
3299     '&cedil;':'&#184;','&sup1;':'&#185;','&ordm;':'&#186;',
3300     '&raquo;':'&#187;','&frac14;':'&#188;','&frac12;':'&#189;',
3301     '&frac34;':'&#190;','&iquest;':'&#191;','&Agrave;':'&#192;',
3302     '&Aacute;':'&#193;','&Acirc;':'&#194;','&Atilde;':'&#195;',
3303     '&Auml;':'&#196;','&Aring;':'&#197;','&AElig;':'&#198;',
3304     '&Ccedil;':'&#199;','&Egrave;':'&#200;','&Eacute;':'&#201;',
3305     '&Ecirc;':'&#202;','&Euml;':'&#203;','&Igrave;':'&#204;',
3306     '&Iacute;':'&#205;','&Icirc;':'&#206;','&Iuml;':'&#207;',
3307     '&ETH;':'&#208;','&Ntilde;':'&#209;','&Ograve;':'&#210;',
3308     '&Oacute;':'&#211;','&Ocirc;':'&#212;','&Otilde;':'&#213;',
3309     '&Ouml;':'&#214;','&times;':'&#215;','&Oslash;':'&#216;',
3310     '&Ugrave;':'&#217;','&Uacute;':'&#218;','&Ucirc;':'&#219;',
3311     '&Uuml;':'&#220;','&Yacute;':'&#221;','&THORN;':'&#222;',
3312     '&szlig;':'&#223;','&agrave;':'&#224;','&aacute;':'&#225;',
3313     '&acirc;':'&#226;','&atilde;':'&#227;','&auml;':'&#228;',
3314     '&aring;':'&#229;','&aelig;':'&#230;','&ccedil;':'&#231;',
3315     '&egrave;':'&#232;','&eacute;':'&#233;','&ecirc;':'&#234;',
3316     '&euml;':'&#235;','&igrave;':'&#236;','&iacute;':'&#237;',
3317     '&icirc;':'&#238;','&iuml;':'&#239;','&eth;':'&#240;',
3318     '&ntilde;':'&#241;','&ograve;':'&#242;','&oacute;':'&#243;',
3319     '&ocirc;':'&#244;','&otilde;':'&#245;','&ouml;':'&#246;',
3320     '&divide;':'&#247;','&oslash;':'&#248;','&ugrave;':'&#249;',
3321     '&uacute;':'&#250;','&ucirc;':'&#251;','&uuml;':'&#252;',
3322     '&yacute;':'&#253;','&thorn;':'&#254;','&yuml;':'&#255;',
3323     '&OElig;':'&#338;','&oelig;':'&#339;','&Scaron;':'&#352;',
3324     '&scaron;':'&#353;','&Yuml;':'&#376;','&fnof;':'&#402;',
3325     '&circ;':'&#710;','&tilde;':'&#732;','&Alpha;':'&#913;',
3326     '&Beta;':'&#914;','&Gamma;':'&#915;','&Delta;':'&#916;',
3327     '&Epsilon;':'&#917;','&Zeta;':'&#918;','&Eta;':'&#919;',
3328     '&Theta;':'&#920;','&Iota;':'&#921;','&Kappa;':'&#922;',
3329     '&Lambda;':'&#923;','&Mu;':'&#924;','&Nu;':'&#925;',
3330     '&Xi;':'&#926;','&Omicron;':'&#927;','&Pi;':'&#928;',
3331     '&Rho;':'&#929;','&Sigma;':'&#931;','&Tau;':'&#932;',
3332     '&Upsilon;':'&#933;','&Phi;':'&#934;','&Chi;':'&#935;',
3333     '&Psi;':'&#936;','&Omega;':'&#937;','&alpha;':'&#945;',
3334     '&beta;':'&#946;','&gamma;':'&#947;','&delta;':'&#948;',
3335     '&epsilon;':'&#949;','&zeta;':'&#950;','&eta;':'&#951;',
3336     '&theta;':'&#952;','&iota;':'&#953;','&kappa;':'&#954;',
3337     '&lambda;':'&#955;','&mu;':'&#956;','&nu;':'&#957;',
3338     '&xi;':'&#958;','&omicron;':'&#959;','&pi;':'&#960;',
3339     '&rho;':'&#961;','&sigmaf;':'&#962;','&sigma;':'&#963;',
3340     '&tau;':'&#964;','&upsilon;':'&#965;','&phi;':'&#966;',
3341     '&chi;':'&#967;','&psi;':'&#968;','&omega;':'&#969;',
3342     '&thetasym;':'&#977;','&upsih;':'&#978;','&piv;':'&#982;',
3343     '&ensp;':'&#8194;','&emsp;':'&#8195;','&thinsp;':'&#8201;',
3344     '&zwnj;':'&#8204;','&zwj;':'&#8205;','&lrm;':'&#8206;',
3345     '&rlm;':'&#8207;','&ndash;':'&#8211;','&mdash;':'&#8212;',
3346     '&lsquo;':'&#8216;','&rsquo;':'&#8217;','&sbquo;':'&#8218;',
3347     '&ldquo;':'&#8220;','&rdquo;':'&#8221;','&bdquo;':'&#8222;',
3348     '&dagger;':'&#8224;','&Dagger;':'&#8225;','&bull;':'&#8226;',
3349     '&hellip;':'&#8230;','&permil;':'&#8240;','&prime;':'&#8242;',
3350     '&Prime;':'&#8243;','&lsaquo;':'&#8249;','&rsaquo;':'&#8250;',
3351     '&oline;':'&#8254;','&frasl;':'&#8260;','&euro;':'&#8364;',
3352     '&image;':'&#8465;','&weierp;':'&#8472;','&real;':'&#8476;',
3353     '&trade;':'&#8482;','&alefsym;':'&#8501;','&larr;':'&#8592;',
3354     '&uarr;':'&#8593;','&rarr;':'&#8594;','&darr;':'&#8595;',
3355     '&harr;':'&#8596;','&crarr;':'&#8629;','&lArr;':'&#8656;',
3356     '&uArr;':'&#8657;','&rArr;':'&#8658;','&dArr;':'&#8659;',
3357     '&hArr;':'&#8660;','&forall;':'&#8704;','&part;':'&#8706;',
3358     '&exist;':'&#8707;','&empty;':'&#8709;','&nabla;':'&#8711;',
3359     '&isin;':'&#8712;','&notin;':'&#8713;','&ni;':'&#8715;',
3360     '&prod;':'&#8719;','&sum;':'&#8721;','&minus;':'&#8722;',
3361     '&lowast;':'&#8727;','&radic;':'&#8730;','&prop;':'&#8733;',
3362     '&infin;':'&#8734;','&ang;':'&#8736;','&and;':'&#8743;',
3363     '&or;':'&#8744;','&cap;':'&#8745;','&cup;':'&#8746;',
3364     '&int;':'&#8747;','&there4;':'&#8756;','&sim;':'&#8764;',
3365     '&cong;':'&#8773;','&asymp;':'&#8776;','&ne;':'&#8800;',
3366     '&equiv;':'&#8801;','&le;':'&#8804;','&ge;':'&#8805;',
3367     '&sub;':'&#8834;','&sup;':'&#8835;','&nsub;':'&#8836;',
3368     '&sube;':'&#8838;','&supe;':'&#8839;','&oplus;':'&#8853;',
3369     '&otimes;':'&#8855;','&perp;':'&#8869;','&sdot;':'&#8901;',
3370     '&lceil;':'&#8968;','&rceil;':'&#8969;','&lfloor;':'&#8970;',
3371     '&rfloor;':'&#8971;','&lang;':'&#9001;','&rang;':'&#9002;',
3372     '&loz;':'&#9674;','&spades;':'&#9824;','&clubs;':'&#9827;',
3373     '&hearts;':'&#9829;','&diams;':'&#9830;'};
3374
3375     this.block_tags = ["a", "abbr", "acronym", "address", "area", "b",
3376     "base", "bdo", "big", "blockquote", "body", "button",
3377     "caption", "cite", "code", "col", "colgroup", "dd", "del", "div",
3378     "dfn", "dl", "dt", "em", "fieldset", "form", "head", "h1", "h2",
3379     "h3", "h4", "h5", "h6", "html", "i", "ins",
3380     "kbd", "label", "legend", "li", "map", "noscript",
3381     "object", "ol", "optgroup", "option", "p", "param", "pre", "q",
3382     "samp", "script", "select", "small", "span", "strong", "style",
3383     "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th",
3384     "thead", "title", "tr", "tt", "ul", "var", "extends"];
3385
3386
3387     this.inline_tags = ["br", "hr", "img", "input"];
3388
3389     return this;
3390 };
3391
3392 WYMeditor.XhtmlSaxListener.prototype.shouldCloseTagAutomatically = function(tag, now_on_tag, closing)
3393 {
3394   var closing = closing || false;
3395   if(tag == 'td'){
3396     if((closing && now_on_tag == 'tr') || (!closing && now_on_tag == 'td')){
3397       return true;
3398     }
3399   }
3400   if(tag == 'option'){
3401     if((closing && now_on_tag == 'select') || (!closing && now_on_tag == 'option')){
3402       return true;
3403     }
3404   }
3405   return false;
3406 };
3407
3408 WYMeditor.XhtmlSaxListener.prototype.beforeParsing = function(raw)
3409 {
3410   this.output = '';
3411   return raw;
3412 };
3413
3414 WYMeditor.XhtmlSaxListener.prototype.afterParsing = function(xhtml)
3415 {
3416   xhtml = this.replaceNamedEntities(xhtml);
3417   xhtml = this.joinRepeatedEntities(xhtml);
3418   xhtml = this.removeEmptyTags(xhtml);
3419   xhtml = this.removeBrInPre(xhtml);
3420   return xhtml;
3421 };
3422
3423 WYMeditor.XhtmlSaxListener.prototype.replaceNamedEntities = function(xhtml)
3424 {
3425   for (var entity in this.entities) {
3426     xhtml = xhtml.replace(new RegExp(entity, 'g'), this.entities[entity]);
3427   }
3428   return xhtml;
3429 };
3430
3431 WYMeditor.XhtmlSaxListener.prototype.joinRepeatedEntities = function(xhtml)
3432 {
3433   var tags = 'em|strong|sub|sup|acronym|pre|del|address';
3434   return xhtml.replace(new RegExp('<\/('+tags+')><\\1>' ,''),'').
3435   replace(new RegExp('(\s*<('+tags+')>\s*){2}(.*)(\s*<\/\\2>\s*){2}' ,''),'<\$2>\$3<\$2>');
3436 };
3437
3438 WYMeditor.XhtmlSaxListener.prototype.removeEmptyTags = function(xhtml)
3439 {
3440   return xhtml.replace(new RegExp('<('+this.block_tags.join("|").replace(/\|td/,'').replace(/\|th/, '')+')>(<br \/>|&#160;|&nbsp;|\\s)*<\/\\1>' ,'g'),'');
3441 };
3442
3443 WYMeditor.XhtmlSaxListener.prototype.removeBrInPre = function(xhtml)
3444 {
3445   var matches = xhtml.match(new RegExp('<pre[^>]*>(.*?)<\/pre>','gmi'));
3446   if(matches) {
3447     for(var i=0; i<matches.length; i++) {
3448       xhtml = xhtml.replace(matches[i], matches[i].replace(new RegExp('<br \/>', 'g'), String.fromCharCode(13,10)));
3449     }
3450   }
3451   return xhtml;
3452 };
3453
3454 WYMeditor.XhtmlSaxListener.prototype.getResult = function()
3455 {
3456   return this.output;
3457 };
3458
3459 WYMeditor.XhtmlSaxListener.prototype.getTagReplacements = function()
3460 {
3461   return {'b':'strong', 'i':'em'};
3462 };
3463
3464 WYMeditor.XhtmlSaxListener.prototype.addContent = function(text)
3465 {
3466   this.output += text;
3467 };
3468
3469 WYMeditor.XhtmlSaxListener.prototype.addComment = function(text)
3470 {
3471   if(this.remove_comments){
3472     this.output += text;
3473   }
3474 };
3475
3476 WYMeditor.XhtmlSaxListener.prototype.addScript = function(text)
3477 {
3478   if(!this.remove_scripts){
3479     this.output += text;
3480   }
3481 };
3482
3483 WYMeditor.XhtmlSaxListener.prototype.addCss = function(text)
3484 {
3485   if(!this.remove_embeded_styles){
3486     this.output += text;
3487   }
3488 };
3489
3490 WYMeditor.XhtmlSaxListener.prototype.openBlockTag = function(tag, attributes)
3491 {
3492   this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes), true);
3493 };
3494
3495 WYMeditor.XhtmlSaxListener.prototype.inlineTag = function(tag, attributes)
3496 {
3497   this.output += this.helper.tag(tag, this.validator.getValidTagAttributes(tag, attributes));
3498 };
3499
3500 WYMeditor.XhtmlSaxListener.prototype.openUnknownTag = function(tag, attributes)
3501 {
3502   //this.output += this.helper.tag(tag, attributes, true);
3503 };
3504
3505 WYMeditor.XhtmlSaxListener.prototype.closeBlockTag = function(tag)
3506 {
3507   this.output = this.output.replace(/<br \/>$/, '')+this._getClosingTagContent('before', tag)+"</"+tag+">"+this._getClosingTagContent('after', tag);
3508 };
3509
3510 WYMeditor.XhtmlSaxListener.prototype.closeUnknownTag = function(tag)
3511 {
3512   //this.output += "</"+tag+">";
3513 };
3514
3515 WYMeditor.XhtmlSaxListener.prototype.closeUnopenedTag = function(tag)
3516 {
3517   this.output += "</"+tag+">";
3518 };
3519
3520 WYMeditor.XhtmlSaxListener.prototype.avoidStylingTagsAndAttributes = function()
3521 {
3522   this.avoided_tags = ['div','span'];
3523   this.validator.skiped_attributes = ['style'];
3524   this.validator.skiped_attribute_values = ['MsoNormal','main1']; // MS Word attributes for class
3525   this._avoiding_tags_implicitly = true;
3526 };
3527
3528 WYMeditor.XhtmlSaxListener.prototype.allowStylingTagsAndAttributes = function()
3529 {
3530   this.avoided_tags = [];
3531   this.validator.skiped_attributes = [];
3532   this.validator.skiped_attribute_values = [];
3533   this._avoiding_tags_implicitly = false;
3534 };
3535
3536 WYMeditor.XhtmlSaxListener.prototype.isBlockTag = function(tag)
3537 {
3538   return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.block_tags, tag);
3539 };
3540
3541 WYMeditor.XhtmlSaxListener.prototype.isInlineTag = function(tag)
3542 {
3543   return !WYMeditor.Helper.contains(this.avoided_tags, tag) && WYMeditor.Helper.contains(this.inline_tags, tag);
3544 };
3545
3546 WYMeditor.XhtmlSaxListener.prototype.insertContentAfterClosingTag = function(tag, content)
3547 {
3548   this._insertContentWhenClosingTag('after', tag, content);
3549 };
3550
3551 WYMeditor.XhtmlSaxListener.prototype.insertContentBeforeClosingTag = function(tag, content)
3552 {
3553   this._insertContentWhenClosingTag('before', tag, content);
3554 };
3555
3556 WYMeditor.XhtmlSaxListener.prototype.fixNestingBeforeOpeningBlockTag = function(tag, attributes)
3557 {
3558     if(tag != 'li' && (tag == 'ul' || tag == 'ol') && this.last_tag && !this.last_tag_opened && this.last_tag == 'li'){
3559       this.output = this.output.replace(/<\/li>$/, '');
3560       this.insertContentAfterClosingTag(tag, '</li>');
3561     }
3562 };
3563
3564 WYMeditor.XhtmlSaxListener.prototype._insertContentWhenClosingTag = function(position, tag, content)
3565 {
3566   if(!this['_insert_'+position+'_closing']){
3567     this['_insert_'+position+'_closing'] = [];
3568   }
3569   if(!this['_insert_'+position+'_closing'][tag]){
3570     this['_insert_'+position+'_closing'][tag] = [];
3571   }
3572   this['_insert_'+position+'_closing'][tag].push(content);
3573 };
3574
3575 WYMeditor.XhtmlSaxListener.prototype._getClosingTagContent = function(position, tag)
3576 {
3577   if( this['_insert_'+position+'_closing'] &&
3578       this['_insert_'+position+'_closing'][tag] &&
3579       this['_insert_'+position+'_closing'][tag].length > 0){
3580         return this['_insert_'+position+'_closing'][tag].pop();
3581   }
3582   return '';
3583 };
3584
3585
3586 /********** CSS PARSER **********/
3587
3588
3589 WYMeditor.WymCssLexer = function(parser, only_wym_blocks)
3590 {
3591   var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? true : only_wym_blocks);
3592
3593   jQuery.extend(this, new WYMeditor.Lexer(parser, (only_wym_blocks?'Ignore':'WymCss')));
3594
3595   this.mapHandler('WymCss', 'Ignore');
3596
3597   if(only_wym_blocks == true){
3598     this.addEntryPattern("/\\\x2a[<\\s]*WYMeditor[>\\s]*\\\x2a/", 'Ignore', 'WymCss');
3599     this.addExitPattern("/\\\x2a[<\/\\s]*WYMeditor[>\\s]*\\\x2a/", 'WymCss');
3600   }
3601
3602   this.addSpecialPattern("[\\sa-z1-6]*\\\x2e[a-z-_0-9]+", 'WymCss', 'WymCssStyleDeclaration');
3603
3604   this.addEntryPattern("/\\\x2a", 'WymCss', 'WymCssComment');
3605   this.addExitPattern("\\\x2a/", 'WymCssComment');
3606
3607   this.addEntryPattern("\x7b", 'WymCss', 'WymCssStyle');
3608   this.addExitPattern("\x7d", 'WymCssStyle');
3609
3610   this.addEntryPattern("/\\\x2a", 'WymCssStyle', 'WymCssFeedbackStyle');
3611   this.addExitPattern("\\\x2a/", 'WymCssFeedbackStyle');
3612
3613   return this;
3614 };
3615
3616 WYMeditor.WymCssParser = function()
3617 {
3618   this._in_style = false;
3619   this._has_title = false;
3620   this.only_wym_blocks = true;
3621   this.css_settings = {'classesItems':[], 'editorStyles':[], 'dialogStyles':[]};
3622   return this;
3623 };
3624
3625 WYMeditor.WymCssParser.prototype.parse = function(raw, only_wym_blocks)
3626 {
3627   var only_wym_blocks = (typeof only_wym_blocks == 'undefined' ? this.only_wym_blocks : only_wym_blocks);
3628   this._Lexer = new WYMeditor.WymCssLexer(this, only_wym_blocks);
3629   this._Lexer.parse(raw);
3630 };
3631
3632 WYMeditor.WymCssParser.prototype.Ignore = function(match, state)
3633 {
3634   return true;
3635 };
3636
3637 WYMeditor.WymCssParser.prototype.WymCssComment = function(text, status)
3638 {
3639   if(text.match(/end[a-z0-9\s]*wym[a-z0-9\s]*/mi)){
3640     return false;
3641   }
3642   if(status == WYMeditor.LEXER_UNMATCHED){
3643     if(!this._in_style){
3644       this._has_title = true;
3645       this._current_item = {'title':WYMeditor.Helper.trim(text)};
3646     }else{
3647       if(this._current_item[this._current_element]){
3648         if(!this._current_item[this._current_element].expressions){
3649           this._current_item[this._current_element].expressions = [text];
3650         }else{
3651           this._current_item[this._current_element].expressions.push(text);
3652         }
3653       }
3654     }
3655     this._in_style = true;
3656   }
3657   return true;
3658 };
3659
3660 WYMeditor.WymCssParser.prototype.WymCssStyle = function(match, status)
3661 {
3662   if(status == WYMeditor.LEXER_UNMATCHED){
3663     match = WYMeditor.Helper.trim(match);
3664     if(match != ''){
3665       this._current_item[this._current_element].style = match;
3666     }
3667   }else if (status == WYMeditor.LEXER_EXIT){
3668     this._in_style = false;
3669     this._has_title = false;
3670     this.addStyleSetting(this._current_item);
3671   }
3672   return true;
3673 };
3674
3675 WYMeditor.WymCssParser.prototype.WymCssFeedbackStyle = function(match, status)
3676 {
3677   if(status == WYMeditor.LEXER_UNMATCHED){
3678     this._current_item[this._current_element].feedback_style = match.replace(/^([\s\/\*]*)|([\s\/\*]*)$/gm,'');
3679   }
3680   return true;
3681 };
3682
3683 WYMeditor.WymCssParser.prototype.WymCssStyleDeclaration = function(match)
3684 {
3685   match = match.replace(/^([\s\.]*)|([\s\.*]*)$/gm, '');
3686
3687   var tag = '';
3688   if(match.indexOf('.') > 0){
3689     var parts = match.split('.');
3690     this._current_element = parts[1];
3691     var tag = parts[0];
3692   }else{
3693     this._current_element = match;
3694   }
3695
3696   if(!this._has_title){
3697     this._current_item = {'title':(!tag?'':tag.toUpperCase()+': ')+this._current_element};
3698     this._has_title = true;
3699   }
3700
3701   if(!this._current_item[this._current_element]){
3702     this._current_item[this._current_element] = {'name':this._current_element};
3703   }
3704   if(tag){
3705     if(!this._current_item[this._current_element].tags){
3706       this._current_item[this._current_element].tags = [tag];
3707     }else{
3708       this._current_item[this._current_element].tags.push(tag);
3709     }
3710   }
3711   return true;
3712 };
3713
3714 WYMeditor.WymCssParser.prototype.addStyleSetting = function(style_details)
3715 {
3716   for (var name in style_details){
3717     var details = style_details[name];
3718     if(typeof details == 'object' && name != 'title'){
3719
3720       this.css_settings.classesItems.push({
3721         'name': WYMeditor.Helper.trim(details.name),
3722         'title': style_details.title,
3723         'expr' : WYMeditor.Helper.trim((details.expressions||details.tags).join(', '))
3724       });
3725       if(details.feedback_style){
3726         this.css_settings.editorStyles.push({
3727           'name': '.'+ WYMeditor.Helper.trim(details.name),
3728           'css': details.feedback_style
3729         });
3730       }
3731       if(details.style){
3732         this.css_settings.dialogStyles.push({
3733           'name': '.'+ WYMeditor.Helper.trim(details.name),
3734           'css': details.style
3735         });
3736       }
3737     }
3738   }
3739 };
3740
3741 /********** HELPERS **********/
3742
3743 // Returns true if it is a text node with whitespaces only
3744 jQuery.fn.isPhantomNode = function() {
3745   if (this[0].nodeType == 3)
3746     return !(/[^\t\n\r ]/.test(this[0].data));
3747
3748   return false;
3749 };
3750
3751 WYMeditor.isPhantomNode = function(n) {
3752   if (n.nodeType == 3)
3753     return !(/[^\t\n\r ]/.test(n.data));
3754
3755   return false;
3756 };
3757
3758 WYMeditor.isPhantomString = function(str) {
3759     return !(/[^\t\n\r ]/.test(str));
3760 };
3761
3762 // Returns the Parents or the node itself
3763 // jqexpr = a jQuery expression
3764 jQuery.fn.parentsOrSelf = function(jqexpr) {
3765   var n = this;
3766
3767   if (n[0].nodeType == 3)
3768     n = n.parents().slice(0,1);
3769
3770 //  if (n.is(jqexpr)) // XXX should work, but doesn't (probably a jQuery bug)
3771   if (n.filter(jqexpr).size() == 1)
3772     return n;
3773   else
3774     return n.parents(jqexpr).slice(0,1);
3775 };
3776
3777 // String & array helpers
3778
3779 WYMeditor.Helper = {
3780
3781     //replace all instances of 'old' by 'rep' in 'str' string
3782     replaceAll: function(str, old, rep) {
3783         var rExp = new RegExp(old, "g");
3784         return(str.replace(rExp, rep));
3785     },
3786
3787     //insert 'inserted' at position 'pos' in 'str' string
3788     insertAt: function(str, inserted, pos) {
3789         return(str.substr(0,pos) + inserted + str.substring(pos));
3790     },
3791
3792     //trim 'str' string
3793     trim: function(str) {
3794         return str.replace(/^(\s*)|(\s*)$/gm,'');
3795     },
3796
3797     //return true if 'arr' array contains 'elem', or false
3798     contains: function(arr, elem) {
3799         for (var i = 0; i < arr.length; i++) {
3800             if (arr[i] === elem) return true;
3801         }
3802         return false;
3803     },
3804
3805     //return 'item' position in 'arr' array, or -1
3806     indexOf: function(arr, item) {
3807         var ret=-1;
3808         for(var i = 0; i < arr.length; i++) {
3809             if (arr[i] == item) {
3810                 ret = i;
3811                 break;
3812             }
3813         }
3814             return(ret);
3815     },
3816
3817     //return 'item' object in 'arr' array, checking its 'name' property, or null
3818     findByName: function(arr, name) {
3819         for(var i = 0; i < arr.length; i++) {
3820             var item = arr[i];
3821             if(item.name == name) return(item);
3822         }
3823         return(null);
3824     }
3825 };
3826
3827
3828 /*
3829  * WYMeditor : what you see is What You Mean web-based editor
3830  * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
3831  * Dual licensed under the MIT (MIT-license.txt)
3832  * and GPL (GPL-license.txt) licenses.
3833  *
3834  * For further information visit:
3835  *        http://www.wymeditor.org/
3836  *
3837  * File Name:
3838  *        jquery.wymeditor.explorer.js
3839  *        MSIE specific class and functions.
3840  *        See the documentation for more info.
3841  *
3842  * File Authors:
3843  *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
3844  *        Bermi Ferrer (wymeditor a-t bermi dotorg)
3845  *        Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
3846  *        Jonatan Lundin (jonatan.lundin _at_ gmail.com)
3847  */
3848
3849 WYMeditor.WymClassExplorer = function(wym) {
3850
3851     this._wym = wym;
3852     this._class = "className";
3853     this._newLine = "\r\n";
3854
3855 };
3856
3857 WYMeditor.WymClassExplorer.prototype.initIframe = function(iframe) {
3858
3859     //This function is executed twice, though it is called once!
3860     //But MSIE needs that, otherwise designMode won't work.
3861     //Weird.
3862     
3863     this._iframe = iframe;
3864     this._doc = iframe.contentWindow.document;
3865     
3866     //add css rules from options
3867     var styles = this._doc.styleSheets[0];
3868     var aCss = eval(this._options.editorStyles);
3869
3870     this.addCssRules(this._doc, aCss);
3871
3872     this._doc.title = this._wym._index;
3873
3874     //set the text direction
3875     jQuery('html', this._doc).attr('dir', this._options.direction);
3876     
3877     //init html value
3878     jQuery(this._doc.body).html(this._wym._html);
3879     
3880     //handle events
3881     var wym = this;
3882     
3883     this._doc.body.onfocus = function()
3884       {wym._doc.designMode = "on"; wym._doc = iframe.contentWindow.document;};
3885     this._doc.onbeforedeactivate = function() {wym.saveCaret();};
3886     this._doc.onkeyup = function() {
3887       wym.saveCaret();
3888       wym.keyup();
3889     };
3890     this._doc.onclick = function() {wym.saveCaret();};
3891     
3892     this._doc.body.onbeforepaste = function() {
3893       wym._iframe.contentWindow.event.returnValue = false;
3894     };
3895     
3896     this._doc.body.onpaste = function() {
3897       wym._iframe.contentWindow.event.returnValue = false;
3898       wym.paste(window.clipboardData.getData("Text"));
3899     };
3900     
3901     //callback can't be executed twice, so we check
3902     if(this._initialized) {
3903       
3904       //pre-bind functions
3905       if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
3906       
3907       //bind external events
3908       this._wym.bindEvents();
3909       
3910       //post-init functions
3911       if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
3912       
3913       //add event listeners to doc elements, e.g. images
3914       this.listen();
3915     }
3916     
3917     this._initialized = true;
3918     
3919     //init designMode
3920     this._doc.designMode="on";
3921     try{
3922         // (bermi's note) noticed when running unit tests on IE6
3923         // Is this really needed, it trigger an unexisting property on IE6
3924         this._doc = iframe.contentWindow.document; 
3925     }catch(e){}
3926 };
3927
3928 WYMeditor.WymClassExplorer.prototype._exec = function(cmd,param) {
3929
3930     switch(cmd) {
3931     
3932     case WYMeditor.INDENT: case WYMeditor.OUTDENT:
3933     
3934         var container = this.findUp(this.container(), WYMeditor.LI);
3935         if(container) {
3936             var ancestor = container.parentNode.parentNode;
3937             if(container.parentNode.childNodes.length>1
3938               || ancestor.tagName.toLowerCase() == WYMeditor.OL
3939               || ancestor.tagName.toLowerCase() == WYMeditor.UL)
3940               this._doc.execCommand(cmd);
3941         }
3942     break;
3943     default:
3944         if(param) this._doc.execCommand(cmd,false,param);
3945         else this._doc.execCommand(cmd);
3946     break;
3947         }
3948     
3949     this.listen();
3950 };
3951
3952 WYMeditor.WymClassExplorer.prototype.selected = function() {
3953
3954     var caretPos = this._iframe.contentWindow.document.caretPos;
3955         if(caretPos!=null) {
3956             if(caretPos.parentElement!=undefined)
3957               return(caretPos.parentElement());
3958         }
3959 };
3960
3961 WYMeditor.WymClassExplorer.prototype.saveCaret = function() {
3962
3963     this._doc.caretPos = this._doc.selection.createRange();
3964 };
3965
3966 WYMeditor.WymClassExplorer.prototype.addCssRule = function(styles, oCss) {
3967
3968     styles.addRule(oCss.name, oCss.css);
3969 };
3970
3971 WYMeditor.WymClassExplorer.prototype.insert = function(html) {
3972
3973     // Get the current selection
3974     var range = this._doc.selection.createRange();
3975
3976     // Check if the current selection is inside the editor
3977     if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
3978         try {
3979             // Overwrite selection with provided html
3980             range.pasteHTML(html);
3981         } catch (e) { }
3982     } else {
3983         // Fall back to the internal paste function if there's no selection
3984         this.paste(html);
3985     }
3986 };
3987
3988 WYMeditor.WymClassExplorer.prototype.wrap = function(left, right) {
3989
3990     // Get the current selection
3991     var range = this._doc.selection.createRange();
3992
3993     // Check if the current selection is inside the editor
3994     if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
3995         try {
3996             // Overwrite selection with provided html
3997             range.pasteHTML(left + range.text + right);
3998         } catch (e) { }
3999     }
4000 };
4001
4002 WYMeditor.WymClassExplorer.prototype.unwrap = function() {
4003
4004     // Get the current selection
4005     var range = this._doc.selection.createRange();
4006
4007     // Check if the current selection is inside the editor
4008     if ( jQuery(range.parentElement()).parents( this._options.iframeBodySelector ).is('*') ) {
4009         try {
4010             // Unwrap selection
4011             var text = range.text;
4012             this._exec( 'Cut' );
4013             range.pasteHTML( text );
4014         } catch (e) { }
4015     }
4016 };
4017
4018 //keyup handler
4019 WYMeditor.WymClassExplorer.prototype.keyup = function() {
4020   this._selected_image = null;
4021 };
4022
4023 WYMeditor.WymClassExplorer.prototype.setFocusToNode = function(node) {
4024     var range = this._doc.selection.createRange();
4025     range.moveToElementText(node);
4026     range.collapse(false);
4027     range.move('character',-1);
4028     range.select();
4029     node.focus();
4030 };
4031
4032 /*
4033  * WYMeditor : what you see is What You Mean web-based editor
4034  * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4035  * Dual licensed under the MIT (MIT-license.txt)
4036  * and GPL (GPL-license.txt) licenses.
4037  *
4038  * For further information visit:
4039  *        http://www.wymeditor.org/
4040  *
4041  * File Name:
4042  *        jquery.wymeditor.mozilla.js
4043  *        Gecko specific class and functions.
4044  *        See the documentation for more info.
4045  *
4046  * File Authors:
4047  *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4048  *        Volker Mische (vmx a-t gmx dotde)
4049  *        Bermi Ferrer (wymeditor a-t bermi dotorg)
4050  *        Frédéric Palluel-Lafleur (fpalluel a-t gmail dotcom)
4051  */
4052
4053 WYMeditor.WymClassMozilla = function(wym) {
4054
4055     this._wym = wym;
4056     this._class = "class";
4057     this._newLine = "\n";
4058 };
4059
4060 WYMeditor.WymClassMozilla.prototype.initIframe = function(iframe) {
4061
4062     this._iframe = iframe;
4063     this._doc = iframe.contentDocument;
4064     
4065     //add css rules from options
4066     
4067     var styles = this._doc.styleSheets[0];    
4068     var aCss = eval(this._options.editorStyles);
4069     
4070     this.addCssRules(this._doc, aCss);
4071
4072     this._doc.title = this._wym._index;
4073
4074     //set the text direction
4075     jQuery('html', this._doc).attr('dir', this._options.direction);
4076     
4077     //init html value
4078     this.html(this._wym._html);
4079     
4080     //init designMode
4081     this.enableDesignMode();
4082     
4083     //pre-bind functions
4084     if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4085     
4086     //bind external events
4087     this._wym.bindEvents();
4088     
4089     //bind editor keydown events
4090     jQuery(this._doc).bind("keydown", this.keydown);
4091     
4092     //bind editor keyup events
4093     jQuery(this._doc).bind("keyup", this.keyup);
4094     
4095     //bind editor focus events (used to reset designmode - Gecko bug)
4096     jQuery(this._doc).bind("focus", this.enableDesignMode);
4097     
4098     //post-init functions
4099     if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4100     
4101     //add event listeners to doc elements, e.g. images
4102     this.listen();
4103 };
4104
4105 /* @name html
4106  * @description Get/Set the html value
4107  */
4108 WYMeditor.WymClassMozilla.prototype.html = function(html) {
4109
4110   if(typeof html === 'string') {
4111   
4112     //disable designMode
4113     try { this._doc.designMode = "off"; } catch(e) { };
4114     
4115     //replace em by i and strong by bold
4116     //(designMode issue)
4117     html = html.replace(/<em(\b[^>]*)>/gi, "<i$1>")
4118       .replace(/<\/em>/gi, "</i>")
4119       .replace(/<strong(\b[^>]*)>/gi, "<b$1>")
4120       .replace(/<\/strong>/gi, "</b>");
4121
4122     //update the html body
4123     jQuery(this._doc.body).html(html);
4124     
4125     //re-init designMode
4126     this.enableDesignMode();
4127   }
4128   else return(jQuery(this._doc.body).html());
4129 };
4130
4131 WYMeditor.WymClassMozilla.prototype._exec = function(cmd,param) {
4132
4133     if(!this.selected()) return(false);
4134
4135     switch(cmd) {
4136     
4137     case WYMeditor.INDENT: case WYMeditor.OUTDENT:
4138     
4139         var focusNode = this.selected();    
4140         var sel = this._iframe.contentWindow.getSelection();
4141         var anchorNode = sel.anchorNode;
4142         if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
4143         
4144         focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
4145         anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
4146         
4147         if(focusNode && focusNode == anchorNode
4148           && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
4149
4150             var ancestor = focusNode.parentNode.parentNode;
4151
4152             if(focusNode.parentNode.childNodes.length>1
4153               || ancestor.tagName.toLowerCase() == WYMeditor.OL
4154               || ancestor.tagName.toLowerCase() == WYMeditor.UL)
4155                 this._doc.execCommand(cmd,'',null);
4156         }
4157
4158     break;
4159     
4160     default:
4161
4162         if(param) this._doc.execCommand(cmd,'',param);
4163         else this._doc.execCommand(cmd,'',null);
4164     }
4165     
4166     //set to P if parent = BODY
4167     var container = this.selected();
4168     if(container.tagName.toLowerCase() == WYMeditor.BODY)
4169         this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4170     
4171     //add event handlers on doc elements
4172
4173     this.listen();
4174 };
4175
4176 /* @name selected
4177  * @description Returns the selected container
4178  */
4179 WYMeditor.WymClassMozilla.prototype.selected = function() {
4180
4181     var sel = this._iframe.contentWindow.getSelection();
4182     var node = sel.focusNode;
4183     if(node) {
4184         if(node.nodeName == "#text") return(node.parentNode);
4185         else return(node);
4186     } else return(null);
4187 };
4188
4189 WYMeditor.WymClassMozilla.prototype.addCssRule = function(styles, oCss) {
4190
4191     styles.insertRule(oCss.name + " {" + oCss.css + "}",
4192         styles.cssRules.length);
4193 };
4194
4195
4196 //keydown handler, mainly used for keyboard shortcuts
4197 WYMeditor.WymClassMozilla.prototype.keydown = function(evt) {
4198   
4199   //'this' is the doc
4200   var wym = WYMeditor.INSTANCES[this.title];
4201   var container = null;  
4202
4203   if(evt.ctrlKey){
4204     if(evt.keyCode == 66){
4205       //CTRL+b => STRONG
4206       wym._exec(WYMeditor.BOLD);
4207       return false;
4208     }
4209     if(evt.keyCode == 73){
4210       //CTRL+i => EMPHASIS
4211       wym._exec(WYMeditor.ITALIC);
4212       return false;
4213     }
4214   }
4215
4216   else if(evt.keyCode == 13) {
4217     if(!evt.shiftKey){
4218       //fix PRE bug #73
4219       container = wym.selected();
4220       if(container && container.tagName.toLowerCase() == WYMeditor.PRE) {
4221         evt.preventDefault();
4222         wym.insert('<p></p>');
4223       }
4224     }
4225   }
4226 };
4227
4228 //keyup handler, mainly used for cleanups
4229 WYMeditor.WymClassMozilla.prototype.keyup = function(evt) {
4230
4231   //'this' is the doc
4232   var wym = WYMeditor.INSTANCES[this.title];
4233   
4234   wym._selected_image = null;
4235   var container = null;
4236
4237   if(evt.keyCode == 13 && !evt.shiftKey) {
4238   
4239     //RETURN key
4240     //cleanup <br><br> between paragraphs
4241     jQuery(wym._doc.body).children(WYMeditor.BR).remove();
4242   }
4243   
4244   else if(evt.keyCode != 8
4245        && evt.keyCode != 17
4246        && evt.keyCode != 46
4247        && evt.keyCode != 224
4248        && !evt.metaKey
4249        && !evt.ctrlKey) {
4250       
4251     //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
4252     //text nodes replaced by P
4253     
4254     container = wym.selected();
4255     var name = container.tagName.toLowerCase();
4256
4257     //fix forbidden main containers
4258     if(
4259       name == "strong" ||
4260       name == "b" ||
4261       name == "em" ||
4262       name == "i" ||
4263       name == "sub" ||
4264       name == "sup" ||
4265       name == "a"
4266
4267     ) name = container.parentNode.tagName.toLowerCase();
4268
4269     if(name == WYMeditor.BODY) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4270   }
4271 };
4272
4273 WYMeditor.WymClassMozilla.prototype.enableDesignMode = function() {
4274     if(this.designMode == "off") {
4275       try {
4276         this.designMode = "on";
4277         this.execCommand("styleWithCSS", '', false);
4278       } catch(e) { }
4279     }
4280 };
4281
4282 WYMeditor.WymClassMozilla.prototype.setFocusToNode = function(node) {
4283     var range = document.createRange();
4284     range.selectNode(node);
4285     var selected = this._iframe.contentWindow.getSelection();
4286     selected.addRange(range);
4287     selected.collapse(node, node.childNodes.length);
4288     this._iframe.contentWindow.focus();
4289 };
4290
4291 WYMeditor.WymClassMozilla.prototype.openBlockTag = function(tag, attributes)
4292 {
4293   var attributes = this.validator.getValidTagAttributes(tag, attributes);
4294
4295   // Handle Mozilla styled spans
4296   if(tag == 'span' && attributes.style){
4297     var new_tag = this.getTagForStyle(attributes.style);
4298     if(new_tag){
4299       this._tag_stack.pop();
4300       var tag = new_tag;
4301       this._tag_stack.push(new_tag);
4302       attributes.style = '';
4303     }else{
4304       return;
4305     }
4306   }
4307   
4308   this.output += this.helper.tag(tag, attributes, true);
4309 };
4310
4311 WYMeditor.WymClassMozilla.prototype.getTagForStyle = function(style) {
4312
4313   if(/bold/.test(style)) return 'strong';
4314   if(/italic/.test(style)) return 'em';
4315   if(/sub/.test(style)) return 'sub';
4316   if(/sub/.test(style)) return 'super';
4317   return false;
4318 };
4319
4320 /*
4321  * WYMeditor : what you see is What You Mean web-based editor
4322  * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4323  * Dual licensed under the MIT (MIT-license.txt)
4324  * and GPL (GPL-license.txt) licenses.
4325  *
4326  * For further information visit:
4327  *        http://www.wymeditor.org/
4328  *
4329  * File Name:
4330  *        jquery.wymeditor.opera.js
4331  *        Opera specific class and functions.
4332  *        See the documentation for more info.
4333  *
4334  * File Authors:
4335  *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4336  */
4337
4338 WYMeditor.WymClassOpera = function(wym) {
4339
4340     this._wym = wym;
4341     this._class = "class";
4342     this._newLine = "\r\n";
4343 };
4344
4345 WYMeditor.WymClassOpera.prototype.initIframe = function(iframe) {
4346
4347     this._iframe = iframe;
4348     this._doc = iframe.contentWindow.document;
4349     
4350     //add css rules from options
4351     var styles = this._doc.styleSheets[0];    
4352     var aCss = eval(this._options.editorStyles);
4353
4354     this.addCssRules(this._doc, aCss);
4355
4356     this._doc.title = this._wym._index;
4357
4358     //set the text direction
4359     jQuery('html', this._doc).attr('dir', this._options.direction);
4360     
4361     //init designMode
4362     this._doc.designMode = "on";
4363
4364     //init html value
4365     this.html(this._wym._html);
4366     
4367     //pre-bind functions
4368     if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4369     
4370     //bind external events
4371     this._wym.bindEvents();
4372     
4373     //bind editor keydown events
4374     jQuery(this._doc).bind("keydown", this.keydown);
4375     
4376     //bind editor events
4377     jQuery(this._doc).bind("keyup", this.keyup);
4378     
4379     //post-init functions
4380     if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4381     
4382     //add event listeners to doc elements, e.g. images
4383     this.listen();
4384 };
4385
4386 WYMeditor.WymClassOpera.prototype._exec = function(cmd,param) {
4387
4388     if(param) this._doc.execCommand(cmd,false,param);
4389     else this._doc.execCommand(cmd);
4390     
4391     this.listen();
4392 };
4393
4394 WYMeditor.WymClassOpera.prototype.selected = function() {
4395
4396     var sel=this._iframe.contentWindow.getSelection();
4397     var node=sel.focusNode;
4398     if(node) {
4399         if(node.nodeName=="#text")return(node.parentNode);
4400         else return(node);
4401     } else return(null);
4402 };
4403
4404 WYMeditor.WymClassOpera.prototype.addCssRule = function(styles, oCss) {
4405
4406     styles.insertRule(oCss.name + " {" + oCss.css + "}",
4407         styles.cssRules.length);
4408 };
4409
4410 //keydown handler
4411 WYMeditor.WymClassOpera.prototype.keydown = function(evt) {
4412   
4413   //'this' is the doc
4414   var wym = WYMeditor.INSTANCES[this.title];
4415   var sel = wym._iframe.contentWindow.getSelection();
4416   startNode = sel.getRangeAt(0).startContainer;
4417
4418   //Get a P instead of no container
4419   if(!jQuery(startNode).parentsOrSelf(
4420                 WYMeditor.MAIN_CONTAINERS.join(","))[0]
4421       && !jQuery(startNode).parentsOrSelf('li')
4422       && evt.keyCode != WYMeditor.KEY.ENTER
4423       && evt.keyCode != WYMeditor.KEY.LEFT
4424       && evt.keyCode != WYMeditor.KEY.UP
4425       && evt.keyCode != WYMeditor.KEY.RIGHT
4426       && evt.keyCode != WYMeditor.KEY.DOWN
4427       && evt.keyCode != WYMeditor.KEY.BACKSPACE
4428       && evt.keyCode != WYMeditor.KEY.DELETE)
4429       wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4430
4431 };
4432
4433 //keyup handler
4434 WYMeditor.WymClassOpera.prototype.keyup = function(evt) {
4435
4436   //'this' is the doc
4437   var wym = WYMeditor.INSTANCES[this.title];
4438   wym._selected_image = null;
4439 };
4440
4441 // TODO: implement me
4442 WYMeditor.WymClassOpera.prototype.setFocusToNode = function(node) {
4443
4444 };
4445
4446 /*
4447  * WYMeditor : what you see is What You Mean web-based editor
4448  * Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
4449  * Dual licensed under the MIT (MIT-license.txt)
4450  * and GPL (GPL-license.txt) licenses.
4451  *
4452  * For further information visit:
4453  *        http://www.wymeditor.org/
4454  *
4455  * File Name:
4456  *        jquery.wymeditor.safari.js
4457  *        Safari specific class and functions.
4458  *        See the documentation for more info.
4459  *
4460  * File Authors:
4461  *        Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
4462  *        Scott Lewis (lewiscot a-t gmail dotcom)
4463  */
4464
4465 WYMeditor.WymClassSafari = function(wym) {
4466
4467     this._wym = wym;
4468     this._class = "class";
4469     this._newLine = "\n";
4470 };
4471
4472 WYMeditor.WymClassSafari.prototype.initIframe = function(iframe) {
4473
4474     this._iframe = iframe;
4475     this._doc = iframe.contentDocument;
4476     
4477     //add css rules from options
4478     
4479     var styles = this._doc.styleSheets[0];    
4480     var aCss = eval(this._options.editorStyles);
4481     
4482     this.addCssRules(this._doc, aCss);
4483
4484     this._doc.title = this._wym._index;
4485
4486     //set the text direction
4487     jQuery('html', this._doc).attr('dir', this._options.direction);
4488
4489     //init designMode
4490     this._doc.designMode = "on";
4491     
4492     //init html value
4493     this.html(this._wym._html);
4494     
4495     //pre-bind functions
4496     if(jQuery.isFunction(this._options.preBind)) this._options.preBind(this);
4497     
4498     //bind external events
4499     this._wym.bindEvents();
4500     
4501     //bind editor keydown events
4502     jQuery(this._doc).bind("keydown", this.keydown);
4503     
4504     //bind editor keyup events
4505     jQuery(this._doc).bind("keyup", this.keyup);
4506     
4507     //post-init functions
4508     if(jQuery.isFunction(this._options.postInit)) this._options.postInit(this);
4509     
4510     //add event listeners to doc elements, e.g. images
4511     this.listen();
4512 };
4513
4514 WYMeditor.WymClassSafari.prototype._exec = function(cmd,param) {
4515
4516     if(!this.selected()) return(false);
4517
4518     switch(cmd) {
4519     
4520     case WYMeditor.INDENT: case WYMeditor.OUTDENT:
4521     
4522         var focusNode = this.selected();    
4523         var sel = this._iframe.contentWindow.getSelection();
4524         var anchorNode = sel.anchorNode;
4525         if(anchorNode.nodeName == "#text") anchorNode = anchorNode.parentNode;
4526         
4527         focusNode = this.findUp(focusNode, WYMeditor.BLOCKS);
4528         anchorNode = this.findUp(anchorNode, WYMeditor.BLOCKS);
4529         
4530         if(focusNode && focusNode == anchorNode
4531           && focusNode.tagName.toLowerCase() == WYMeditor.LI) {
4532
4533             var ancestor = focusNode.parentNode.parentNode;
4534
4535             if(focusNode.parentNode.childNodes.length>1
4536               || ancestor.tagName.toLowerCase() == WYMeditor.OL
4537               || ancestor.tagName.toLowerCase() == WYMeditor.UL)
4538                 this._doc.execCommand(cmd,'',null);
4539         }
4540
4541     break;
4542
4543     case WYMeditor.INSERT_ORDEREDLIST: case WYMeditor.INSERT_UNORDEREDLIST:
4544
4545         this._doc.execCommand(cmd,'',null);
4546
4547         //Safari creates lists in e.g. paragraphs.
4548         //Find the container, and remove it.
4549         var focusNode = this.selected();
4550         var container = this.findUp(focusNode, WYMeditor.MAIN_CONTAINERS);
4551         if(container) jQuery(container).replaceWith(jQuery(container).html());
4552
4553     break;
4554     
4555     default:
4556
4557         if(param) this._doc.execCommand(cmd,'',param);
4558         else this._doc.execCommand(cmd,'',null);
4559     }
4560     
4561     //set to P if parent = BODY
4562     var container = this.selected();
4563     if(container && container.tagName.toLowerCase() == WYMeditor.BODY)
4564         this._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P);
4565
4566     //add event handlers on doc elements
4567     this.listen();
4568 };
4569
4570 /* @name selected
4571  * @description Returns the selected container
4572  */
4573 WYMeditor.WymClassSafari.prototype.selected = function() {
4574
4575     var sel = this._iframe.contentWindow.getSelection();
4576     var node = sel.focusNode;
4577     if(node) {
4578         if(node.nodeName == "#text") return(node.parentNode);
4579         else return(node);
4580     } else return(null);
4581 };
4582
4583 WYMeditor.WymClassSafari.prototype.addCssRule = function(styles, oCss) {
4584
4585     styles.insertRule(oCss.name + " {" + oCss.css + "}",
4586         styles.cssRules.length);
4587 };
4588
4589
4590 //keydown handler, mainly used for keyboard shortcuts
4591 WYMeditor.WymClassSafari.prototype.keydown = function(evt) {
4592   
4593   //'this' is the doc
4594   var wym = WYMeditor.INSTANCES[this.title];
4595   
4596   if(evt.ctrlKey){
4597     if(evt.keyCode == 66){
4598       //CTRL+b => STRONG
4599       wym._exec(WYMeditor.BOLD);
4600       return false;
4601     }
4602     if(evt.keyCode == 73){
4603       //CTRL+i => EMPHASIS
4604       wym._exec(WYMeditor.ITALIC);
4605       return false;
4606     }
4607   }
4608 };
4609
4610 //keyup handler, mainly used for cleanups
4611 WYMeditor.WymClassSafari.prototype.keyup = function(evt) {
4612
4613   //'this' is the doc
4614   var wym = WYMeditor.INSTANCES[this.title];
4615   
4616   wym._selected_image = null;
4617   var container = null;
4618
4619   if(evt.keyCode == 13 && !evt.shiftKey) {
4620   
4621     //RETURN key
4622     //cleanup <br><br> between paragraphs
4623     jQuery(wym._doc.body).children(WYMeditor.BR).remove();
4624     
4625     //fix PRE bug #73
4626     container = wym.selected();
4627     if(container && container.tagName.toLowerCase() == WYMeditor.PRE)
4628         wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //create P after PRE
4629   }
4630
4631   //fix #112
4632   if(evt.keyCode == 13 && evt.shiftKey) {
4633     wym._exec('InsertLineBreak');
4634   }
4635   
4636   if(evt.keyCode != 8
4637        && evt.keyCode != 17
4638        && evt.keyCode != 46
4639        && evt.keyCode != 224
4640        && !evt.metaKey
4641        && !evt.ctrlKey) {
4642       
4643     //NOT BACKSPACE, NOT DELETE, NOT CTRL, NOT COMMAND
4644     //text nodes replaced by P
4645     
4646     container = wym.selected();
4647     var name = container.tagName.toLowerCase();
4648
4649     //fix forbidden main containers
4650     if(
4651       name == "strong" ||
4652       name == "b" ||
4653       name == "em" ||
4654       name == "i" ||
4655       name == "sub" ||
4656       name == "sup" ||
4657       name == "a" ||
4658       name == "span" //fix #110
4659
4660     ) name = container.parentNode.tagName.toLowerCase();
4661
4662     if(name == WYMeditor.BODY || name == WYMeditor.DIV) wym._exec(WYMeditor.FORMAT_BLOCK, WYMeditor.P); //fix #110 for DIV
4663   }
4664 };
4665
4666 WYMeditor.WymClassSafari.prototype.setFocusToNode = function(node) {
4667     var range = this._iframe.contentDocument.createRange();
4668     range.selectNode(node);
4669     var selected = this._iframe.contentWindow.getSelection();
4670     selected.addRange(range);
4671     selected.collapse(node, node.childNodes.length);
4672     this._iframe.contentWindow.focus();
4673 };
4674
4675 WYMeditor.WymClassSafari.prototype.openBlockTag = function(tag, attributes)
4676 {
4677   var attributes = this.validator.getValidTagAttributes(tag, attributes);
4678
4679   // Handle Safari styled spans
4680   if(tag == 'span' && attributes.style) {
4681     var new_tag = this.getTagForStyle(attributes.style);
4682     if(new_tag){
4683       this._tag_stack.pop();
4684       var tag = new_tag;
4685       this._tag_stack.push(new_tag);
4686       attributes.style = '';
4687       
4688       //should fix #125 - also removed the xhtml() override
4689       if(typeof attributes['class'] == 'string')
4690         attributes['class'] = attributes['class'].replace(/apple-style-span/gi, '');
4691     
4692     } else {
4693       return;
4694     }
4695   }
4696   
4697   this.output += this.helper.tag(tag, attributes, true);
4698 };
4699
4700 WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) {
4701
4702   if(/bold/.test(style)) return 'strong';
4703   if(/italic/.test(style)) return 'em';
4704   if(/sub/.test(style)) return 'sub';
4705   if(/super/.test(style)) return 'sup';
4706   return false;
4707 };