root/galaxy-central/static/scripts/jquery.wymeditor.js @ 2

リビジョン 2, 137.1 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

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