OSDN Git Service

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