You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2010/03/29 03:14:49 UTC

svn commit: r928555 [5/16] - in /myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources: oam.custom.calendar.DB/ oam.custom.calendar.WH/ oam.custom.calendar.images/ oam.custom.inputHtml.kupudrawers/ oam.custom.inputHtml.kupuimages/ oam.cus...

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupubeforeunload.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupubeforeunload.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupubeforeunload.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupubeforeunload.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,158 @@
+/* BeforeUnload form processing */
+if (!window.beforeunload) (function() {
+    var BeforeUnloadHandler = function() {
+        var self = this;
+
+        this.message = "Discard changes? If you click OK, any changes you have made will be lost.";
+        if (window._) {
+            this.message = _("Discard changes? If you click OK, any changes you have made will be lost.");
+        };
+        this.forms = [];
+        this.chkId = [];
+        this.chkType = new this.CheckType();
+        this.handlers = [this.isAnyFormChanged];
+        this.submitting = false;
+
+        this.execute = function(event) {
+            if (self.submitting) return;
+            if (!event) event = window.event;
+
+            for (var i = 0; i < self.handlers.length; i++) {
+                var fn = self.handlers[i];
+                var message = message || fn.apply(self);
+            }
+            if (message===true) message = self.message;
+            if (message===false) message = undefined;
+            if (event && message) event.returnValue = message;
+            return message;
+        };
+        this.execute.tool = this;
+    };
+    var Class = BeforeUnloadHandler.prototype;
+
+    // form checking code
+    Class.isAnyFormChanged = function() {
+        for (var i=0; i < this.forms.length; i++) {
+            var form = this.forms[i];
+            if (this.isElementChanged(form)) {
+                return true;
+            }
+        }
+        return false;
+    };
+    Class.addHandler = function(fn) {
+        this.handlers.push(fn);
+    };
+    Class.onsubmit = function() {
+        var tool = window.onbeforeunload && window.onbeforeunload.tool;
+        tool.submitting = true;
+    };
+    Class.addForm = function(form) {
+        for (var i = 0; i < this.forms.length; i++) {
+            if (this.forms[i]==form) return;
+        }
+        this.forms.push(form);
+        form.onsubmit = this.onsubmit;
+        var elements = form.getElementsByTagName('input');
+        for (var j = 0; j < elements.length; j++) {
+            var ele = elements[j];
+            if (ele.type=='hidden') {
+                ele.setAttribute('originalValue', ele.defaultValue);
+            }
+        }
+    };
+    Class.addForms = function() {
+        for (var i = 0; i < arguments.length; i++) {
+            var element = arguments[i];
+            if (!element) continue;
+            if (element.tagName=='FORM') {
+                this.addForm(element);
+            }
+            else {
+                var forms = element.getElementsByTagName('form');
+                for (var j = 0; j < forms.length; j++) {
+                    this.addForm(forms[j]);
+                }
+            }
+        }
+    };
+    Class.removeForms = function() {
+        for (var i = 0; i < arguments.length; i++) {
+            var element = arguments[i];
+            if (!element) continue;
+            if (element.tagName=='FORM') {
+                for (var j = 0; j < arguments.length; j++) {
+                    if (this.forms[j] == element) {
+                        this.forms.splice(j--, 1);
+                        element.onsubmit=null;
+                    }
+                }
+            } else {
+                var forms = element.getElementsByTagName('form');
+                for (var j = 0; j < forms.length; j++) {
+                    this.removeForms(forms[j]);
+                }
+            }
+        }
+    };
+
+    Class.CheckType = function() {};
+    var c = Class.CheckType.prototype;
+    c.checkbox = c.radio = function(ele) {
+        return ele.checked != ele.defaultChecked;
+    };
+    c.password = c.textarea = c.text = function(ele) {
+        return ele.value != ele.defaultValue;
+    };
+    // hidden: cannot tell on Mozilla without special treatment
+    c.hidden = function(ele) {
+        var orig = ele.getAttribute("originalValue");
+        return orig && (ele.value != orig);
+    };
+
+    c['select-one'] = function(ele) {
+        for (var i=0 ; i < ele.length; i++) {
+            var opt = ele.options[i];
+            if ( opt.selected != opt.defaultSelected) {
+                if (i===0 && opt.selected) continue; /* maybe no default */
+                return true;
+            }
+        }
+        return false;
+    };
+
+    c['select-multiple'] = function(ele) {
+        for (var i=0 ; i < ele.length; i++) {
+            var opt = ele.options[i];
+            if ( opt.selected != opt.defaultSelected) {
+                return true;
+            }
+        }
+        return false;
+    };
+
+    Class.chk_form = function(form) {
+        var elements = form.elements;
+        for (var i=0; i < elements.length; i++ ) {
+            var element = elements[i];
+            if (this.isElementChanged(element)) {
+                return true;
+            }
+        }
+        return false;
+    };
+
+    Class.isElementChanged = function(ele) {
+        var method = (ele.id && this.chkId.hasOwnProperty(ele.id))?this.chkId[ele.id]:null;
+        if (!method && ele.type && ele.name) {
+            method = this.chkType[ele.type];
+        }
+        if (!method && ele.tagName) {
+            method = this['chk_'+ele.tagName.toLowerCase()];
+        }
+
+        return method? method.apply(this, [ele]) : false;
+    };
+
+    window.onbeforeunload = new BeforeUnloadHandler().execute;
+})();

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupublank.html
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupublank.html?rev=928555&view=auto
==============================================================================
    (empty)

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucleanupexpressions.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucleanupexpressions.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucleanupexpressions.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucleanupexpressions.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,186 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2004 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id: kupucleanupexpressions.js 928511 2010-03-28 22:53:14Z lu4242 $
+
+// WARNING: this file can contain non-ascii characters, *always* make sure your
+// text-editor uses 'UTF-8' as the character encoding!!
+
+function CleanupExpressionsTool(actionselectid, performactionbuttonid) {
+    /* tool that allows global replace actions on the text contents
+
+        the actions will be presented to the user as a list of some
+        sort (e.g. select) of which the user can choose one, when (s)he
+        does the system will use a set of regular expressions and 
+        replacements on the code, when a match of the expression is
+        encountered the match will be replaced with the replacement
+
+        matches and replacements can be configured from the XML, the
+        format is:
+
+        <config>
+          <cleanup_expressions>
+            <set>
+              <name>Convert single quotes to curly ones</name>
+              <expression>
+                <reg>
+                  (\W)'
+                </reg>
+                <replacement>
+                  \1‘
+                </replacement>
+              </expression>
+              <expression>
+                <reg>
+                  '
+                </reg>
+                <replacement>
+                  ’
+                </replacement>
+              </expression>
+            </set>
+            <set>
+              <name>Reduce whitespace</name>
+              <expression>
+                <reg>
+                  [ ]{2}
+                </reg>
+                <replacement>
+                  &#x20;
+                </replacement>
+              </expression>
+            </set>
+          </cleanup_expressions>
+        </config>
+        
+    */
+    this.actionselect = document.getElementById(actionselectid);
+    this.performactionbutton = document.getElementById(performactionbuttonid);
+};
+
+CleanupExpressionsTool.prototype = new KupuTool;
+
+CleanupExpressionsTool.prototype.initialize = function(editor) {
+    /* initialize the tool, read the regexp sets into a mapping */
+    this.editor = editor;
+    // mapping name -> [[regexp, replacement], ...]
+    this.expressions = this.generateExpressionsMapping();
+    // populate action select
+    this.populateActionSelect(this.expressions);
+    // add the event handler to the button
+    addEventHandler(this.performactionbutton, 'click', this.performAction, 
+                    this);
+};
+
+CleanupExpressionsTool.prototype.generateExpressionsMapping = function() {
+    /* convert the config struct to a somewhat simpler mapping */
+    var ret = {};
+    var expressions = this.editor.config.cleanup_expressions;
+    if (!expressions) {
+        // no expressions in the XML config, bail out
+        alert('no cleanup expressions configured');
+        return ret;
+    };
+    var sets = expressions.set;
+    for (var i=0; i < sets.length; i++) {
+        var set = sets[i];
+        var name = set.name;
+        ret[name] = [];
+        var exprs = set.expression;
+        // may be list type as well as object
+        if (exprs.length) {
+          for (var j=0; j < exprs.length; j++) {
+              var expr = exprs[j];
+              var regexp = expr.reg.strip();
+              var replacement = this._prepareReplacement(expr.replacement);
+              ret[name].push([regexp, replacement]);
+          };
+        } else {
+            var regexp = exprs.reg.strip();
+            var replacement = this._prepareReplacement(exprs.replacement);
+            ret[name].push([regexp, replacement]);
+        };
+    };
+    return ret;
+};
+
+CleanupExpressionsTool.prototype._prepareReplacement = function(data) {
+    /* replace \x([0-9a-f]{2}) escapes with the unicode value of \1 */
+    data = data.strip();
+    var reg = /\\x([0-9a-f]{2})/g;
+    while (true) {
+        var match = reg.exec(data);
+        if (!match || !match.length) {
+            return data;
+        };
+        data = data.replace(match[0], String.fromCharCode(parseInt(match[1], 16)));
+    };
+};
+
+CleanupExpressionsTool.prototype.populateActionSelect = function(mapping) {
+    /* populate the select with which the user can choose actions */
+    for (var name in mapping) {
+        var option = document.createElement('option');
+        option.value = name;
+        option.appendChild(document.createTextNode(name));
+        this.actionselect.appendChild(option);
+    };
+    this.actionselect.style.width = '100%';
+};
+
+CleanupExpressionsTool.prototype.performAction = function() {
+    /* perform a single action (set of regexps/replacements) to the whole body */
+    var action = this.actionselect.options[
+                  this.actionselect.selectedIndex].value;
+    var sets = this.expressions[action];
+    for (var i=0; i < sets.length; i++) {
+        var body = this.editor.getInnerDocument().getElementsByTagName('body')[0];
+        var nodeiterator = new NodeIterator(body);
+        while (true) {
+            var current = nodeiterator.next();
+            if (!current) {
+                break;
+            };
+            if (current.nodeType == 3) {
+                var value = current.nodeValue;
+                if (value.strip()) {
+                    this.performReplaceOnNode(current, sets[i][0], sets[i][1]);
+                };
+            };
+        };
+    };
+    alert('Cleanup done');
+};
+
+CleanupExpressionsTool.prototype.performReplaceOnNode = function(node, regexp, replacement) {
+    /* perform the replacement to the contents of a single node */
+    var value = node.nodeValue;
+    while (true) {
+        var reg = new RegExp(regexp, 'g');
+        var match = reg.exec(value);
+        if (!match || !match.length) {
+            node.nodeValue = value;
+            return;
+        };
+        value = value.replace(match[0], this.createReplacement(replacement, match));
+    };
+};
+
+CleanupExpressionsTool.prototype.createReplacement = function(pattern, interpolations) {
+    /* interpolate '\[0-9]' escapes, they will be replaced with interpolations[n] where
+        n is the number behind the backslash */
+    var reg = /\\([0-9])/g;
+    while (true) {
+        var match = reg.exec(pattern);
+        if (!match || !match.length) {
+            return pattern;
+        };
+        pattern = pattern.replace(match[0], interpolations[parseInt(match[1])]);
+    };
+};

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucnftable.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucnftable.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucnftable.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucnftable.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,131 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id$
+
+TableTool.prototype.setTableRowRepeat = function() {
+    var selNode = this.editor.getSelectedNode();
+    var row = this.editor.getNearestParentOfType(selNode, 'tr');
+    if (!row) {
+        this.editor.logMessage(_('Not inside a row!'), 1);
+        return;
+    };
+    row.setAttribute('repeatable', 'repeatable');
+    row.className = 'repeatable';
+    this.updateState(selNode);
+};
+
+TableTool.prototype.delTableRowRepeat = function() {
+    var selNode = this.editor.getSelectedNode();
+    var row = this.editor.getNearestParentOfType(selNode, 'tr');
+    if (!row) {
+        this.editor.logMessage(_('Not inside a row!'), 1);
+        return;
+    };
+    row.removeAttribute('repeatable');
+    row.className = '';
+    row.removeAttribute('class');
+    this.updateState(selNode);
+};
+
+function CNFTableToolBox(addtabledivid, edittabledivid, newrowsinputid, 
+                    newcolsinputid, makeheaderinputid, classselectid, alignselectid, addtablebuttonid,
+                    addrowbuttonid, delrowbuttonid, setrowrepeatbuttonid, delrowrepeatbuttonid,
+                    addcolbuttonid, delcolbuttonid, fixbuttonid,
+                    fixallbuttonid, toolboxid, plainclass, activeclass) {
+
+    this.addtablediv = getFromSelector(addtabledivid);
+    this.edittablediv = getFromSelector(edittabledivid);
+    this.newrowsinput = getFromSelector(newrowsinputid);
+    this.newcolsinput = getFromSelector(newcolsinputid);
+    this.makeheaderinput = getFromSelector(makeheaderinputid);
+    this.classselect = getFromSelector(classselectid);
+    this.alignselect = getFromSelector(alignselectid);
+    this.addtablebutton = getFromSelector(addtablebuttonid);
+    this.addrowbutton = getFromSelector(addrowbuttonid);
+    this.delrowbutton = getFromSelector(delrowbuttonid);
+    this.setrowrepeatbutton = getFromSelector(setrowrepeatbuttonid);
+    this.delrowrepeatbutton = getFromSelector(delrowrepeatbuttonid);
+    this.addcolbutton = getFromSelector(addcolbuttonid);
+    this.delcolbutton = getFromSelector(delcolbuttonid);
+    this.fixbutton = getFromSelector(fixbuttonid);
+    this.fixallbutton = getFromSelector(fixallbuttonid);
+    this.toolboxel = getFromSelector(toolboxid);
+    this.plainclass = plainclass;
+    this.activeclass = activeclass;
+
+    this.initialize = function(tool, editor) {
+        /* attach the event handlers */
+        this.tool = tool;
+        this.editor = editor;
+        // build the select list of table classes if configured
+        if (this.editor.config.table_classes) {
+            var classes = this.editor.config.table_classes['class'];
+            while (this.classselect.hasChildNodes()) {
+                this.classselect.removeChild(this.classselect.firstChild);
+            };
+            for (var i=0; i < classes.length; i++) {
+                var classname = classes[i];
+                var option = document.createElement('option');
+                var content = document.createTextNode(classname);
+                option.appendChild(content);
+                option.setAttribute('value', classname);
+                this.classselect.appendChild(option);
+            };
+        };
+        addEventHandler(this.addtablebutton, "click", this.addTable, this);
+        addEventHandler(this.addrowbutton, "click", this.tool.addTableRow, this.tool);
+        addEventHandler(this.delrowbutton, "click", this.tool.delTableRow, this.tool);
+        addEventHandler(this.setrowrepeatbutton, "click", this.tool.setTableRowRepeat, this.tool);
+        addEventHandler(this.delrowrepeatbutton, "click", this.tool.delTableRowRepeat, this.tool);
+        addEventHandler(this.addcolbutton, "click", this.tool.addTableColumn, this.tool);
+        addEventHandler(this.delcolbutton, "click", this.tool.delTableColumn, this.tool);
+        addEventHandler(this.alignselect, "change", this.setColumnAlign, this);
+        addEventHandler(this.classselect, "change", this.setTableClass, this);
+        addEventHandler(this.fixbutton, "click", this.tool.fixTable, this.tool);
+        addEventHandler(this.fixallbutton, "click", this.tool.fixAllTables, this.tool);
+        this.addtablediv.style.display = "block";
+        this.edittablediv.style.display = "none";
+    };
+
+    this.updateState = function(selNode) {
+        /* update the state (add/edit) and update the pulldowns (if required) */
+        var table = this.editor.getNearestParentOfType(selNode, 'table');
+        if (table) {
+            this.addtablediv.style.display = "none";
+            this.edittablediv.style.display = "block";
+
+            var align = this.tool._getColumnAlign(selNode);
+            selectSelectItem(this.alignselect, align);
+            selectSelectItem(this.classselect, table.className);
+            if (this.toolboxel) {
+                this.toolboxel.className = this.activeclass;
+            };
+            var row = this.editor.getNearestParentOfType(selNode, 'tr');
+            var isRepeatable = row.getAttribute('repeatable');
+            if (isRepeatable) {
+                this.setrowrepeatbutton.style.display = 'none';
+                this.delrowrepeatbutton.style.display = 'inline';
+            } else {
+                this.setrowrepeatbutton.style.display = 'inline';
+                this.delrowrepeatbutton.style.display = 'none';
+            };
+        } else {
+            this.edittablediv.style.display = "none";
+            this.addtablediv.style.display = "block";
+            this.alignselect.selectedIndex = 0;
+            this.classselect.selectedIndex = 0;
+            if (this.toolboxel) {
+                this.toolboxel.className = this.plainclass;
+            };
+        };
+    };
+};
+
+CNFTableToolBox.prototype = new TableToolBox;

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentfilters.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentfilters.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentfilters.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentfilters.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,800 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2004 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+
+// $Id: kupucontentfilters.js 928511 2010-03-28 22:53:14Z lu4242 $
+
+
+//----------------------------------------------------------------------------
+// 
+// ContentFilters
+//
+//  These are (or currently 'this is') filters for HTML cleanup and 
+//  conversion. Kupu filters should be classes that should get registered to
+//  the editor using the registerFilter method with 2 methods: 'initialize'
+//  and 'filter'. The first will be called with the editor as its only
+//  argument and the latter with a reference to the ownerdoc (always use 
+//  that to create new nodes and such) and the root node of the HTML DOM as 
+//  its arguments.
+//
+//----------------------------------------------------------------------------
+
+function NonXHTMLTagFilter() {
+    /* filter out non-XHTML tags*/
+    
+    // A mapping from element name to whether it should be left out of the 
+    // document entirely. If you want an element to reappear in the resulting 
+    // document *including* it's contents, add it to the mapping with a 1 value.
+    // If you want an element not to appear but want to leave it's contents in 
+    // tact, add it to the mapping with a 0 value. If you want an element and
+    // it's contents to be removed from the document, don't add it.
+    if (arguments.length) {
+        // allow an optional filterdata argument
+        this.filterdata = arguments[0];
+    } else {
+        // provide a default filterdata dict
+        this.filterdata = {'html': 1,
+                            'body': 1,
+                            'head': 1,
+                            'title': 1,
+                            
+                            'a': 1,
+                            'abbr': 1,
+                            'acronym': 1,
+                            'address': 1,
+                            'b': 1,
+                            'base': 1,
+                            'big': 1,
+                            'blockquote': 1,
+                            'br': 1,
+                            'caption': 1,
+                            'cite': 1,
+                            'code': 1,
+                            'col': 1,
+                            'colgroup': 1,
+                            'dd': 1,
+                            'dfn': 1,
+                            'div': 1,
+                            'dl': 1,
+                            'dt': 1,
+                            'em': 1,
+                            'h1': 1,
+                            'h2': 1,
+                            'h3': 1,
+                            'h4': 1,
+                            'h5': 1,
+                            'h6': 1,
+                            'i': 1,
+                            'img': 1,
+                            'kbd': 1,
+                            'li': 1,
+                            'link': 1,
+                            'meta': 1,
+                            'ol': 1,
+                            'p': 1,
+                            'pre': 1,
+                            'q': 1,
+                            'samp': 1,
+                            'script': 1,
+                            'small': 1,
+                            'span': 1,
+                            'strong': 1,
+                            'style': 1,
+                            'sub': 1,
+                            'sup': 1,
+                            'table': 1,
+                            'tbody': 1,
+                            'td': 1,
+                            'tfoot': 1,
+                            'th': 1,
+                            'thead': 1,
+                            'tr': 1,
+                            'tt': 1,
+                            'ul': 1,
+                            'u': 1,
+                            'var': 1,
+
+                            // even though they're deprecated we should leave
+                            // font tags as they are, since Kupu sometimes
+                            // produces them itself.
+                            'font': 1,
+                            'center': 0
+                            };
+    };
+                        
+    this.initialize = function(editor) {
+        /* init */
+        this.editor = editor;
+    };
+
+    this.filter = function(ownerdoc, htmlnode) {
+        return this._filterHelper(ownerdoc, htmlnode);
+    };
+
+    this._filterHelper = function(ownerdoc, node) {
+        /* filter unwanted elements */
+        if (node.nodeType == 3) {
+            return ownerdoc.createTextNode(node.nodeValue);
+        } else if (node.nodeType == 4) {
+            return ownerdoc.createCDATASection(node.nodeValue);
+        };
+        // create a new node to place the result into
+        // XXX this can be severely optimized by doing stuff inline rather 
+        // than on creating new elements all the time!
+        var newnode = ownerdoc.createElement(node.nodeName);
+        // copy the attributes
+        for (var i=0; i < node.attributes.length; i++) {
+            var attr = node.attributes[i];
+            newnode.setAttribute(attr.nodeName, attr.nodeValue);
+        };
+        for (var i=0; i < node.childNodes.length; i++) {
+            var child = node.childNodes[i];
+            var nodeType = child.nodeType;
+            var nodeName = child.nodeName.toLowerCase();
+            if (nodeType == 3 || nodeType == 4) {
+                newnode.appendChild(this._filterHelper(ownerdoc, child));
+            };
+            if (nodeName in this.filterdata && this.filterdata[nodeName]) {
+                newnode.appendChild(this._filterHelper(ownerdoc, child));
+            } else if (nodeName in this.filterdata) {
+                for (var j=0; j < child.childNodes.length; j++) {
+                    newnode.appendChild(this._filterHelper(ownerdoc, 
+                        child.childNodes[j]));
+                };
+            };
+        };
+        return newnode;
+    };
+};
+
+//-----------------------------------------------------------------------------
+//
+// XHTML validation support
+//
+// This class is the XHTML 1.0 transitional DTD expressed as Javascript
+// data structures.
+//
+function XhtmlValidation(editor) {
+    // Support functions
+    function asList(s) {
+        if (typeof(s)==typeof('') || !s.length) s = [s];
+        return s;
+    }
+
+    this.Set = function(ary) {
+        if (typeof(ary)==typeof('')) ary = [ary];
+        if (ary instanceof Array) {
+            for (var i = 0; i < ary.length; i++) {
+                this[ary[i]] = 1;
+            }
+        }
+        else {
+            for (var v in ary) { // already a set?
+                this[v] = 1;
+            }
+        }
+    };
+    
+    this._exclude = function(array, exceptions) {
+        var ex;
+        if (exceptions.split) {
+            ex = exceptions.split("|");
+        } else {
+            ex = exceptions;
+        }
+        var exclude = new this.Set(ex);
+        var res = [];
+        for (var k=0; k < array.length;k++) {
+            if (!exclude[array[k]]) res.push(array[k]);
+        }
+        return res;
+    };
+    this.setAttrFilter = function(attributes, filter) {
+        for (var j = 0; j < attributes.length; j++) {
+            var attr = attributes[j];
+            this.attrFilters[attr] = filter || this._defaultCopyAttribute;
+        }
+    };
+
+    this.setTagAttributes = function(tags, attributes) {
+        for (var j = 0; j < tags.length; j++) {
+            this.tagAttributes[tags[j]] = attributes;
+        }
+    };
+
+    // define some new attributes for existing tags
+    this.includeTagAttributes = function(tags, attributes) {
+        for (var j = 0; j < tags.length; j++) {
+            var tag = tags[j];
+            this.tagAttributes[tag] = this.tagAttributes[tag].concat(attributes);
+        }
+    };
+
+    this.excludeTagAttributes = function(tags, attributes) {
+        var bad = new this.Set(attributes);
+        var tagset = new this.Set(tags);
+        for (var tag in tagset) {
+            var val = this.tagAttributes[tag];
+            if (val) {
+                for (var i = val.length; i >= 0; i--) {
+                    if (bad[val[i]]) {
+                        val = val.concat(); // Copy
+                        val.splice(i,1);
+                    }
+                }
+            }
+            this.tagAttributes[tag] = val;
+            // have to store this to allow filtering for nodes on which
+            // '*' is set as allowed, this allows using '*' for the attributes
+            // but also filtering some out
+            this.badTagAttributes[tag] = attributes;
+        }
+    };
+
+    this.excludeTags = function(badtags) {
+        if (typeof(badtags)==typeof('')) badtags = [badtags];
+        for (var i = 0; i < badtags.length; i++) {
+            delete this.tagAttributes[badtags[i]];
+        }
+    };
+
+    this.excludeAttributes = function(badattrs) {
+        this.excludeTagAttributes(this.tagAttributes, badattrs);
+        for (var i = 0; i < badattrs.length; i++) {
+            delete this.attrFilters[badattrs[i]];
+        }
+    };
+    var replaceNodes = { 'b': 'strong', 'i': 'em' };
+    if (editor.getBrowserName()=="IE") {
+        this._getTagName = function(htmlnode) {
+            var nodename = htmlnode.nodeName.toLowerCase();
+            if (htmlnode.scopeName && htmlnode.scopeName != "HTML") {
+                nodename = htmlnode.scopeName+':'+nodename;
+            }
+            return replaceNodes[nodename]||nodename;
+        };
+    } else {
+        this._getTagName = function(htmlnode) {
+            var nodename = htmlnode.nodeName.toLowerCase();
+            return replaceNodes[nodename]||nodename;
+        };
+    };
+
+    // Supporting declarations
+    this.elements = new function(validation) {
+        // A list of all attributes
+        this.attributes = [
+            'abbr','accept','accept-charset','accesskey','action','align','alink',
+            'alt','archive','axis','background','bgcolor','border','cellpadding',
+            'cellspacing','char','charoff','charset','checked','cite','class',
+            'classid','clear','code','codebase','codetype','color','cols','colspan',
+            'compact','content','coords','data','datetime','declare','defer','dir',
+            'disabled','enctype','face','for','frame','frameborder','headers',
+            'height','href','hreflang','hspace','http-equiv','id','ismap','label',
+            'lang','language','link','longdesc','marginheight','marginwidth',
+            'maxlength','media','method','multiple','name','nohref','noshade','nowrap',
+            'object','onblur','onchange','onclick','ondblclick','onfocus','onkeydown',
+            'onkeypress','onkeyup','onload','onmousedown','onmousemove','onmouseout',
+            'onmouseover','onmouseup','onreset','onselect','onsubmit','onunload',
+            'profile','prompt','readonly','rel','rev','rows','rowspan','rules',
+            'scheme','scope','scrolling','selected','shape','size','span','src',
+            'standby','start','style','summary','tabindex','target','text','title',
+            'type','usemap','valign','value','valuetype','vlink','vspace','width',
+            'xml:lang','xml:space','xmlns'];
+
+        // Core attributes
+        this.coreattrs = ['id', 'title', 'style', 'class'];
+        this.i18n = ['lang', 'dir', 'xml:lang'];
+        // All event attributes are here but commented out so we don't
+        // have to remove them later.
+        this.events = []; // 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup'.split('|');
+        this.focusevents = []; // ['onfocus','onblur']
+        this.loadevents = []; // ['onload', 'onunload']
+        this.formevents = []; // ['onsubmit','onreset']
+        this.inputevents = [] ; // ['onselect', 'onchange']
+        this.focus = ['accesskey', 'tabindex'].concat(this.focusevents);
+        this.attrs = [].concat(this.coreattrs, this.i18n, this.events);
+
+        // entities
+        this.special_extra = ['object','applet','img','map','iframe', 'embed'];
+        this.special_basic=['br','span','bdo'];
+        this.special = [].concat(this.special_basic, this.special_extra);
+        this.fontstyle_extra = ['big','small','font','basefont'];
+        this.fontstyle_basic = ['tt','i','b','u','s','strike'];
+        this.fontstyle = [].concat(this.fontstyle_basic, this.fontstyle_extra);
+        this.phrase_extra = ['sub','sup'];
+        this.phrase_basic = ['em','strong','dfn','code','q',
+                             'samp','kbd','var','cite','abbr','acronym'];
+        this.phrase = [].concat(this.phrase_basic, this.phrase_extra);
+        this.inline_forms = ['input','select','textarea','label','button'];
+        this.misc_inline = ['ins','del', 'script'];
+        this.misc = ['noscript'].concat(this.misc_inline);
+        this.inline = ['a'].concat(this.special, this.fontstyle, this.phrase, this.inline_forms);
+
+        this.Inline = ['#text', '#comment'].concat(this.inline, this.misc_inline);
+
+        this.heading = ['h1','h2','h3','h4','h5','h6'];
+        this.lists = ['ul','ol','dl','menu','dir'];
+        this.blocktext = ['pre','hr','blockquote','address','center','noframes'];
+        this.block = ['p','div','isindex','fieldset','table'].concat(
+                     this.heading, this.lists, this.blocktext);
+
+        this.Flow = ['#text','form'].concat(this.block, this.inline, this.misc);
+    }(this);
+
+    this._commonsetting = function(self, names, value) {
+        for (var n = 0; n < names.length; n++) {
+            self[names[n]] = value;
+        }
+    };
+    
+    // The tagAttributes class returns all valid attributes for a tag,
+    // e.g. a = this.tagAttributes.head
+    // a.head -> [ 'lang', 'xml:lang', 'dir', 'id', 'profile' ]
+    this.tagAttributes = new function(el, validation) {
+        this.title = el.i18n.concat('id');
+        this.html = this.title.concat('xmlns');
+        this.head = this.title.concat('profile');
+        this.base = ['id', 'href', 'target'];
+        this.meta =  this.title.concat('http-equiv','name','content', 'scheme');
+        this.link = el.attrs.concat('charset','href','hreflang','type', 'rel','rev','media','target');
+        this.style = this.title.concat('type','media','title', 'xml:space');
+        this.script = ['id','charset','type','language','src','defer', 'xml:space'];
+        this.iframe = [
+                      'longdesc','name','src','frameborder','marginwidth',
+                      'marginheight','scrolling','align','height','width'].concat(el.coreattrs);
+        this.body = ['background','bgcolor','text','link','vlink','alink'].concat(el.attrs, el.loadevents);
+        validation._commonsetting(this,
+                                  ['p','div'].concat(el.heading),
+                                  ['align'].concat(el.attrs));
+        this.dl = this.dir = this.menu = el.attrs.concat('compact');
+        this.ul = this.menu.concat('type');
+        this.ol = this.ul.concat('start');
+        this.li = el.attrs.concat('type','value');
+        this.hr = el.attrs.concat('align','noshade','size','width');
+        this.pre = el.attrs.concat('width','xml:space');
+        this.blockquote = this.q = el.attrs.concat('cite');
+        this.ins = this.del = this.blockquote.concat('datetime');
+        this.a = el.attrs.concat(el.focus,'charset','type','name','href','hreflang','rel','rev','shape','coords','target');
+        this.bdo = el.coreattrs.concat(el.events, 'lang','xml:lang','dir');
+        this.br = el.coreattrs.concat('clear');
+        validation._commonsetting(this,
+                                  ['noscript','noframes','dt', 'dd', 'address','center','span','em', 'strong', 'dfn','code',
+                                  'samp','kbd','var','cite','abbr','acronym','sub','sup','tt',
+                                  'i','b','big','small','u','s','strike', 'fieldset'],
+                                  el.attrs);
+
+        this.basefont = ['id','size','color','face'];
+        this.font = el.coreattrs.concat(el.i18n, 'size','color','face');
+        this.object = el.attrs.concat('declare','classid','codebase','data','type','codetype','archive','standby','height','width','usemap','name','tabindex','align','border','hspace','vspace');
+        this.embed=['*'];
+        this.param = ['id','name','value','valuetype','type'];
+        this.applet = el.coreattrs.concat('codebase','archive','code','object','alt','name','width','height','align','hspace','vspace');
+        this.img = el.attrs.concat('src','alt','name','longdesc','height','width','usemap','ismap','align','border','hspace','vspace');
+        this.map = this.title.concat('title','name', 'style', 'class', el.events);
+        this.area = el.attrs.concat('shape','coords','href','nohref','alt','target', el.focus);
+        this.form = el.attrs.concat('action','method','name','enctype',el.formevents,'accept','accept-charset','target');
+        this.label = el.attrs.concat('for','accesskey', el.focusevents);
+        this.input = el.attrs.concat('type','name','value','checked','disabled','readonly','size','maxlength','src','alt','usemap',el.input,'accept','align', el.focus);
+        this.select = el.attrs.concat('name','size','multiple','disabled','tabindex', el.focusevents,el.input);
+        this.optgroup = el.attrs.concat('disabled','label');
+        this.option = el.attrs.concat('selected','disabled','label','value');
+        this.textarea = el.attrs.concat('name','rows','cols','disabled','readonly', el.inputevents, el.focus);
+        this.legend = el.attrs.concat('accesskey','align');
+        this.button = el.attrs.concat('name','value','type','disabled',el.focus);
+        this.isindex = el.coreattrs.concat('prompt', el.i18n);
+        this.table = el.attrs.concat('summary','width','border','frame','rules','cellspacing','cellpadding','align','bgcolor');
+        this.caption = el.attrs.concat('align');
+        this.col = this.colgroup = el.attrs.concat('span','width','align','char','charoff','valign');
+        this.thead =  el.attrs.concat('align','char','charoff','valign');
+        this.tfoot = this.tbody = this.thead;
+        this.tr = this.thead.concat('bgcolor');
+        this.td = this.th = this.tr.concat('abbr','axis','headers','scope','rowspan','colspan','nowrap','width','height');
+    }(this.elements, this);
+
+    this.badTagAttributes = new this.Set({});
+
+    // Nasty tags should be initialised from Plone's HTML control panel
+    // but we have a few tags we know for sure aren't going to work
+    // so we can put them in whatever.
+    this.nastyTags = new this.Set({'script':1, 'style':1, 'meta':1, 'title':1}); 
+
+    // State array. For each tag identifies what it can contain.
+    // I'm not attempting to check the order or number of contained
+    // tags (yet).
+    this.States = new function(el, validation) {
+
+        var here = this;
+        function setStates(tags, value) {
+            var valset = new validation.Set(value);
+
+            for (var i = 0; i < tags.length; i++) {
+                here[tags[i]] = valset;
+            }
+        }
+        
+        setStates(['html'], ['head','body']);
+        setStates(['head'], ['title','base','script','style', 'meta','link','object','isindex']);
+        setStates([
+            'base', 'meta', 'link', 'hr', 'param', 'img', 'area', 'input',
+            'br', 'basefont', 'isindex', 'col'], []);
+
+        setStates(['title','style','script','option','textarea'], ['#text']);
+        setStates([ 'noscript', 'iframe', 'noframes', 'body', 'div',
+            'li', 'dd', 'blockquote', 'center', 'ins', 'del', 'td', 'th'], el.Flow);
+
+        setStates(el.heading, el.Inline);
+        setStates([ 'p', 'dt', 'address', 'span', 'bdo', 'caption',
+            'em', 'strong', 'dfn','code','samp','kbd','var',
+            'cite','abbr','acronym','q','sub','sup','tt','i',
+            'b','big','small','u','s','strike','font','label',
+            'legend'], el.Inline);
+
+        setStates(['ul', 'ol', 'menu', 'dir', 'ul'], ['li']);
+        setStates(['dl'], ['dt','dd']);
+        setStates(['pre'], validation._exclude(el.Inline, "img|object|embed|applet|big|small|sub|sup|font|basefont"));
+        setStates(['a'], validation._exclude(el.Inline, "a"));
+        setStates(['applet', 'object','embed'], ['#text', 'param','form'].concat(el.block, el.inline, el.misc));
+        setStates(['map'], ['form', 'area'].concat(el.block, el.misc));
+        setStates(['form'], validation._exclude(el.Flow, ['form']));
+        setStates(['select'], ['optgroup','option']);
+        setStates(['optgroup'], ['option']);
+        setStates(['fieldset'], ['#text','legend','form'].concat(el.block,el.inline,el.misc));
+        setStates(['button'], validation._exclude(el.Flow, ['a','form','iframe'].concat(el.inline_forms)));
+        setStates(['table'], ['caption','col','colgroup','thead','tfoot','tbody','tr']);
+        setStates(['thead', 'tfoot', 'tbody'], ['tr']);
+        setStates(['colgroup'], ['col']);
+        setStates(['tr'], ['th','td']);
+    }(this.elements, this);
+
+    // Permitted elements for style.
+    this.styleWhitelist = new this.Set(['text-align', 'list-style-type', 'float']);
+    this.classBlacklist = new this.Set(['MsoNormal', 'MsoTitle', 'MsoHeader', 'MsoFootnoteText',
+        'Bullet1', 'Bullet2', 'Apple-span-style']);
+
+    this.classFilter = function(value) {
+        var classes = value.split(' ');
+        var filtered = [];
+        for (var i = 0; i < classes.length; i++) {
+            var c = classes[i];
+            if (c && !this.classBlacklist[c]) {
+                filtered.push(c);
+            }
+        }
+        return filtered.join(' ');
+    };
+    this._defaultCopyAttribute = function(name, htmlnode, xhtmlnode) {
+        var val = htmlnode.getAttribute(name);
+        if (val) xhtmlnode.setAttribute(name, val);
+        return !!val;
+    };
+    // Set up filters for attributes.
+    // Filters may return false if nothing was copied, true or
+    // undefined if an attribute was copied.
+    var filter = this;
+    this.attrFilters = new function(validation, editor) {
+        var attrs = validation.elements.attributes;
+        for (var i=0; i < attrs.length; i++) {
+            this[attrs[i]] = validation._defaultCopyAttribute;
+        }
+        this['class'] = function(name, htmlnode, xhtmlnode) {
+            var val = htmlnode.getAttribute('class');
+            if (val) val = validation.classFilter(val);
+            if (val) xhtmlnode.setAttribute('class', val);
+            return !!val;
+        };
+        // allow a * wildcard to make all attributes valid in the filter
+        // note that this is pretty slow on IE
+        this['*'] = function(name, htmlnode, xhtmlnode) {
+            var res = false;
+            var nodeName = filter._getTagName(htmlnode);
+            var bad = filter.badTagAttributes[nodeName];
+            for (var i=0; i < htmlnode.attributes.length; i++) {
+                var attr = htmlnode.attributes[i];
+                var name = attr.name;
+                if (bad && bad.contains(name)) {
+                    continue;
+                };
+                if (attr.specified) {
+                    xhtmlnode.setAttribute(name, attr.value);
+                    res = true;
+                };
+            };
+            return res;
+        };
+        if (editor.getBrowserName()=="IE") {
+            this['class'] = function(name, htmlnode, xhtmlnode) {
+                var val = htmlnode.className;
+                if (val) {
+                    val = validation.classFilter(val); 
+                    if (val) xhtmlnode.setAttribute('class', val);
+                } else {
+                    val = htmlnode.getAttribute("class");
+                    if (val) val = validation.classFilter(val);                        
+                    if (val) xhtmlnode.setAttribute('class', val);
+                }
+                return !!val;
+            };
+            this['http-equiv'] = function(name, htmlnode, xhtmlnode) {
+                var val = htmlnode.httpEquiv;
+                if (val) xhtmlnode.setAttribute('http-equiv', val);
+                return !!val;
+            };
+            this['xml:lang'] = this['xml:space'] = function(name, htmlnode, xhtmlnode) {
+                try {
+                    var val = htmlnode.getAttribute(name);
+                    if (val) xhtmlnode.setAttribute(name, val);
+                    return !!val;
+                } catch(e) {
+                }
+            };
+        }
+        this.alt = function(name, htmlnode, xhtmlnode) {
+            var val = htmlnode.getAttribute(name);
+            var ok = val || xhtmlnode.tagName=='img';
+            if (ok) xhtmlnode.setAttribute(name, val);
+            return ok;
+        };
+        this.rowspan = this.colspan = function(name, htmlnode, xhtmlnode) {
+            var val = htmlnode.getAttribute(name);
+            var ok = val && val != '1';
+            if (ok) xhtmlnode.setAttribute(name, val);
+            return ok;
+        };
+        this.style = function(name, htmlnode, xhtmlnode) {
+            var val = htmlnode.style.cssText;
+            if (val) {
+                var styles = val.split(/; */);
+                for (var i = styles.length; i >= 0; i--) if (styles[i]) {
+                    var parts = /^([^:]+): *(.*)$/.exec(styles[i]);
+                    var name = parts[1].toLowerCase();
+                    // myFaces : Remove this right now as the white list is really too small
+                    //if (validation.styleWhitelist[name]) {
+                        styles[i] = name+': '+parts[2];
+                    //} else {
+                    //    styles.splice(i,1); // delete
+                    //}
+                }
+                if (styles[styles.length-1]) styles.push('');
+                val = styles.join('; ').strip();
+            };
+            if (val) xhtmlnode.setAttribute('style', val);
+            return !!val;
+        };
+    }(this, editor);
+
+    // Exclude unwanted tags.
+    this.excludeTags(['center', 'meta', 'title']);
+
+    if (editor.config && editor.config.htmlfilter) {
+        this.filterStructure = editor.config.htmlfilter.filterstructure;
+        
+        var exclude = editor.config.htmlfilter;
+        if (exclude.a) {
+            this.excludeAttributes(exclude.a);
+        }
+        if (exclude.t) {
+            this.excludeTags(exclude.t);
+        }
+        if (exclude.c) {
+            var c = asList(exclude.c);
+            for (var i = 0; i < c.length; i++) {
+                this.excludeTagAttributes(c[i].t, c[i].a);
+            }
+        }
+        if (exclude.xstyle) {
+            var s = asList(exclude.xstyle);
+            for (var i = 0; i < s.length; i++) {
+                this.styleWhitelist[s[i]] = 1;
+            }
+        }
+        if (exclude['class']) {
+            var c = asList(exclude['class']);
+            for (var i = 0; i < c.length; i++) {
+                this.classBlacklist[c[i]] = 1;
+            }
+        }
+    };
+
+    // Copy all valid attributes from htmlnode to xhtmlnode.
+    // Returns true if at least one attribute was copied.
+    this._copyAttributes = function(htmlnode, xhtmlnode, valid) {
+        var name;
+        var res = false;
+        if (valid.contains('*')) {
+            // allow all attributes on this tag
+            res = this.attrFilters['*'](name, htmlnode, xhtmlnode);
+            if (res===undefined) res = true;
+        } else {
+            for (var i = 0; i < valid.length; i++) {
+                name = valid[i];
+                var filter = this.attrFilters[name];
+                if (filter) {
+                    var f = filter(name, htmlnode, xhtmlnode);
+                    res|=(f||f===undefined);
+                }
+            };
+        }
+        return res;
+    };
+    this._xmlCopyAttr = function(srcnode, target) {
+        var valid = this.tagAttributes[srcnode.nodeName];
+        for (var i = 0; i < valid.length; i++) {
+            var val = srcnode.getAttribute(valid[i]);
+            if (val) {
+                target.setAttribute(valid[i], val);
+            }
+        };
+    };
+
+    this._convertToSarissaNode = function(ownerdoc, htmlnode) {
+        var root = this._convertNodes(ownerdoc, htmlnode, null, new this.Set(['html']));
+        this._cleanupBr(ownerdoc, root);
+        this._cleanupParas(ownerdoc, root);
+        return root;
+    };
+
+    // Clean up a paragraph. Any direct child which is not allowed in
+    // the paragraph is moved to the parent. This may involved
+    // splitting the paragraph, or if it is at the beginning or end it
+    // may simply mean moving it out of the paragraph.
+    this._cleanupPara = function(ownerdoc, para) {
+        var permitted = this.States.p;
+        var nodes = [[]];
+        var idx = 0;
+        for (var child = para.firstChild; child; child = child.nextSibling) {
+            var nn = child.nodeName.toLowerCase();
+            if (permitted[nn] && (nn != 'img' || !(/\bcaptioned\b/i.test(child.getAttribute('class'))))) {
+                nodes[idx].push(child);
+            } else {
+                if (nodes[idx].length) {
+                    nodes.push(child);
+                } else {
+                    nodes[idx] = child;
+                }
+                nodes.push([]);
+                idx = nodes.length-1;
+            }
+        }
+        if (!nodes[idx].length) {
+            nodes.splice(idx,1);
+        };
+        if (nodes.length > 0 && nodes[0] instanceof Array && !nodes[0].length) {
+            nodes.splice(0,1);
+        }
+        if (nodes.length==0 || (nodes.length==1 && nodes[0] instanceof Array)) {
+            return; /* No change */
+        }
+        /* Need to cleanup this paragraph */
+        var parentnode = para.parentNode;
+        for (var idx = 0; idx < nodes.length; idx++) {
+            var n = nodes[idx];
+            if (n instanceof Array) {
+                var newp = ownerdoc.createElement('p');
+                this._xmlCopyAttr(para, newp);
+                for (var ln = n.length-1; ln >= 0; ln--) {
+                    var nn = n[ln].nodeName.toLowerCase();
+                    if (nn=='br' || (nn=='#text' && (/^\s*$/.test(n[ln].nodeValue)))) {
+                        n.splice(ln,1);
+                    } else { break; }
+                }
+                if (n.length==0) {
+                    continue;
+                }
+                for (var j = 0; j < n.length; j++) {
+                    newp.appendChild(n[j]);
+                }
+                n = newp;
+            }
+            parentnode.insertBefore(n,para);
+        }
+        parentnode.removeChild(para);
+    };
+
+    this._cleanupParas = function(ownerdoc, root) {
+        var paras = root.getElementsByTagName('p');
+        for (var i = paras.length-1; i >= 0; i--) {
+            this._cleanupPara(ownerdoc, paras[i]);
+        }
+    };
+    /* Cleanup br tags: br at top level is replaced by a paragraph,
+     * br at end of p|div is dropped.
+     */
+    this._cleanupBr = function(ownerdoc, root) {
+        var breaks = root.getElementsByTagName('br');
+        // Iterate backwards: removeChild removes node from breaks.
+        for (var i = breaks.length-1; i >= 0; i--) {
+            var node = breaks[i];
+            var parentNode = node.parentNode;
+            if (parentNode.tagName=='body') {
+                var p = ownerdoc.createElement('p');
+                var prev = node.previousSibling;
+                if (prev && prev.nodeType==3) {
+                    p.appendChild(prev);
+                }
+                parentNode.insertBefore(p,node);
+                parentNode.removeChild(node);
+            } else if (!node.nextSibling && (/(p|div)\b/i.test(parentNode.nodeName) && !(node.previousSibling&&node.previousSibling.nodeName=='br'))) {
+                parentNode.removeChild(node);
+            }
+        }
+    };
+
+    this._convertNodes = function(ownerdoc, htmlnode, xhtmlparent, permitted) {
+        var parentnode = xhtmlparent;
+        var nodename = this._getTagName(htmlnode);
+        var nostructure = !this.filterstructure;
+
+        // TODO: This permits valid tags anywhere. it should use the state
+        // table in xhtmlvalid to only permit tags where the XHTML DTD
+        // says they are valid.
+        var validattrs = this.tagAttributes[nodename];
+        if (validattrs && (nostructure || permitted[nodename])) {
+            try {
+                var xhtmlnode = ownerdoc.createElement(nodename);
+                parentnode = xhtmlnode;
+            } catch (e) { };
+            
+            if (validattrs && xhtmlnode) {
+                if (!this._copyAttributes(htmlnode, xhtmlnode, validattrs) && nodename=='span') {
+                    parentnode = xhtmlparent;
+                    xhtmlnode = null;
+                }
+            }
+        } else {
+            // Stripping this tag, maybe we also want to strip the
+            // content of the tag.
+            if (this.nastyTags[nodename]) { return null; }
+        }
+
+        var kids = htmlnode.childNodes;
+        var permittedChildren = this.States[parentnode.tagName] || permitted;
+
+        if (kids.length == 0) {
+            // TOMAHAWK-1307 #000000 is displayed in inputHtml
+            // changed htmlnode.text to htmlnode.textContent
+            if (htmlnode.textContent && htmlnode.textContent != "" &&
+                (nostructure || permittedChildren['#text'])) {
+                var text = htmlnode.textContent;
+                var tnode = ownerdoc.createTextNode(text);
+                parentnode.appendChild(tnode);
+            }
+        } else {
+            for (var i = 0; i < kids.length; i++) {
+                var kid = kids[i];
+
+                if (kid.parentNode !== htmlnode) {
+                    if (kid.tagName.toLowerCase()=='body') {
+                        if (nodename != 'html') continue;
+                    } else if (kid.parentNode.tagName === htmlnode.tagName) {
+                        continue; // IE bug: nodes appear multiple places
+                    }
+                }
+                
+                if (kid.nodeType == 1) {
+                    var newkid = this._convertNodes(ownerdoc, kid, parentnode, permittedChildren);
+                    if (newkid != null) {
+                        parentnode.appendChild(newkid);
+                    };
+                } else if (kid.nodeType == 3) {
+                    if (nostructure || permittedChildren['#text']) {
+                        parentnode.appendChild(ownerdoc.createTextNode(kid.nodeValue));
+                    }
+                } else if (kid.nodeType == 4) {
+                    if (nostructure || permittedChildren['#text']) {
+                        parentnode.appendChild(ownerdoc.createCDATASection(kid.nodeValue));
+                    }
+                } else if (kid.nodeType == 8) {
+                    parentnode.appendChild(ownerdoc.createComment(kid.nodeValue));
+                }
+            }
+        } 
+        return xhtmlnode;
+    };
+}

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentstyles.css
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentstyles.css?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentstyles.css (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontentstyles.css Mon Mar 29 01:14:43 2010
@@ -0,0 +1,98 @@
+/*****************************************************************************
+ *
+ * Kupu content styles
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. See CREDITS.txt
+ *
+ * Instead of customizing this file, it is recommended to add your own
+ * CSS file.  Feel free to use whole or parts of this for your own
+ * designs, but give credit where credit is due.
+ *
+ *****************************************************************************/
+
+/* $Id: kupucontentstyles.css 9984 2005-03-21 14:29:21Z yuppie $ */
+
+/* this is an example CSS that defines styles for all classes set by kupu. of
+   course customizations can include completely different styles, and also for
+   different elements (changeable or not) */
+
+.image-left {
+   float: left;
+   clear: both;
+}
+
+.image-inline {
+   float: none;
+}
+
+.image-right {
+   float: right;
+   clear: both;
+}
+
+table {
+    border-width: 0px;
+    padding: 3px;
+}
+
+table.plain td {
+    border-width: 1px;
+    border-style: solid;
+    border-color: black;
+}
+
+table.listing {
+    border-width: 0px;
+}
+
+table.listing td {
+    border-style: solid;
+    border-width: 1px;
+    border-color: black;
+}
+
+table.listing th {
+    border-style: solid;
+    border-width: 1px;
+    border-color: black;
+    background-color: gray;
+    color: white;
+}
+
+table.grid {
+    border-width: 0px;
+}
+
+table.grid td {
+    border-style: solid;
+    border-width: 1px;
+    border-color: black;
+}
+
+table.grid th {
+    border-style: solid;
+    border-width: 1px;
+    border-color: black;
+}
+
+table.data {
+    border-style: solid;
+    border-width: 1px;
+    border-color: black;
+}
+
+table.data td {
+    border-style: solid;
+    border-width: 1px;
+    border-color: gray;
+}
+
+table.data th {
+    border-style: solid;
+    border-width: 1px;
+    border-color: gray;
+}
+
+.repeatable {
+    background-color: pink;
+}

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontextmenu.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontextmenu.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontextmenu.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupucontextmenu.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,186 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id: kupucontextmenu.js 928511 2010-03-28 22:53:14Z lu4242 $
+
+
+//----------------------------------------------------------------------------
+// ContextMenu
+//----------------------------------------------------------------------------
+
+function ContextMenu() {
+    /* the contextmenu */
+    this.contextmenu = null;
+    this.seperator = 1;
+
+    this.initialize = function(editor) {
+        /* set the event handlers and such */
+        this.editor = editor;
+        // needs some work since it won't work for more than one editor
+        addEventHandler(this.editor.getInnerDocument(), "contextmenu", this.createContextMenu, this);
+        //addEventHandler(editor.getInnerDocument(), "focus", this.hideContextMenu, this);
+        addEventHandler(document, "focus", this.hideContextMenu, this);
+        addEventHandler(editor.getInnerDocument(), "mousedown", this.hideContextMenu, this);
+        addEventHandler(document, "mousedown", this.hideContextMenu, this);
+    };
+
+    this.createContextMenu = function(event) {
+        /* Create and show the context menu 
+        
+            The method will ask all tools for any (optional) elements they
+            want to add the menu and when done render it
+        */
+        if (event.stopPropagation) {
+            event.stopPropagation();
+        };
+        event.returnValue = false;
+        if (this.editor.getBrowserName() == 'IE') {
+            this.editor._saveSelection();
+        };
+        // somehow Mozilla on Windows seems to generate the oncontextmenu event
+        // several times on each rightclick, here's a workaround
+        if (this.editor.getBrowserName() == 'Mozilla' && this.contextmenu) {
+            return false;
+        };
+        this.hideContextMenu();
+        var selNode = this.editor.getSelectedNode();
+        var elements = [];
+        for (var id in this.editor.tools) {
+            var tool = this.editor.tools[id];
+            // alas, some people seem to want backward compatibility ;)
+            if (tool.createContextMenuElements) {
+                var els = tool.createContextMenuElements(selNode, event);
+                elements = elements.concat(els);
+            };
+        };
+        // remove the last seperator
+        this._createNewContextMenu(elements, event);
+        this.last_event = event;
+        return false;
+    };
+
+    this.hideContextMenu = function(event) {
+        /* remove the context menu from view */
+        if (this.contextmenu) {
+            try {
+                window.document.getElementsByTagName('body')[0].removeChild(this.contextmenu);
+            } catch (e) {
+                // after some commands, the contextmenu will be removed by 
+                // the browser, ignore those cases
+            };
+            this.contextmenu = null;
+        };
+    };
+
+    this._createNewContextMenu = function(elements, event) {
+        /* add the elements to the contextmenu and show it */
+        var doc = window.document;
+        var menu = doc.createElement('div');
+        menu.contentEditable = false;
+        menu.designMode = 'Off';
+        this._setMenuStyle(menu);
+        for (var i=0; i < elements.length; i++) {
+            var element = elements[i];
+            if (element !== this.seperator) {
+                var div = doc.createElement('div');
+                div.style.width = '100%';
+                var label = doc.createTextNode('\u00a0' + element.label);
+                div.appendChild(label);
+                menu.appendChild(div);
+                // set a reference to the div on the element
+                element.element = div;
+                addEventHandler(div, "mousedown", element.action, element.context);
+                addEventHandler(div, "mouseover", element.changeOverStyle, element);
+                addEventHandler(div, "mouseout", element.changeNormalStyle, element);
+                addEventHandler(div, "mouseup", this.hideContextMenu, this);
+            } else {
+                var hr = doc.createElement('hr');
+                menu.appendChild(hr);
+            };
+        };
+        // now move the menu to the right position
+        var iframe = this.editor.getDocument().getEditable();
+        var left = event.clientX;
+        var top = event.clientY;
+        var currnode = iframe;
+        if (this.editor.getBrowserName() == 'IE') {
+            while (currnode) {
+                left += currnode.offsetLeft + currnode.clientLeft;
+                top += currnode.offsetTop + currnode.clientTop;
+                currnode = currnode.offsetParent;
+            };
+        } else {
+            while (currnode) {
+                left += currnode.offsetLeft;
+                top += currnode.offsetTop;
+                currnode = currnode.offsetParent;
+            };
+        };
+        menu.style.left = left + 'px';
+        menu.style.top = top + 'px';
+        menu.style.visibility = 'visible';
+        addEventHandler(menu, 'focus', function() {this.blur();}, menu);
+        doc.getElementsByTagName('body')[0].appendChild(menu);
+        this.contextmenu = menu;
+    };
+    
+    this._setMenuStyle = function(menu) {
+        /* set the styles for the menu
+
+            to change the menu style, override this method
+        */
+        menu.style.position = 'absolute';
+        menu.style.backgroundColor = 'white';
+        menu.style.fontFamily = 'Verdana, Arial, Helvetica, sans-serif';
+        menu.style.fontSize = '12px';
+        menu.style.lineHeight = '16px';
+        menu.style.borderWidth = '1px';
+        menu.style.borderStyle = 'solid';
+        menu.style.borderColor = 'black';
+        menu.style.cursor = 'default';
+        menu.style.width = "8em";
+    };
+
+    this._showOriginalMenu = function(event) {
+        window.document.dispatchEvent(this._last_event);
+    };
+};
+
+function ContextMenuElement(label, action, context) {
+    /* context menu element struct
+    
+        should be returned (optionally in a list) by the tools' 
+        createContextMenuElements methods
+    */
+    this.label = label; // the text shown in the menu
+    this.action = action; // a reference to the method that should be called
+    this.context = context; // a reference to the object on which the method
+                            //  is defined
+    this.element = null; // the contextmenu machinery will add a reference 
+                            // to the element here
+
+    this.changeOverStyle = function(event) {
+        /* set the background of the element to 'mouseover' style
+
+            override only for the prototype, not for individual elements
+            so every element looks the same
+        */
+        this.element.style.backgroundColor = 'blue';
+    };
+
+    this.changeNormalStyle = function(event) {
+        /* set the background of the element back to 'normal' style
+
+            override only for the prototype, not for individual elements
+            so every element looks the same
+        */
+        this.element.style.backgroundColor = 'white';
+    };
+}
+