[kune-commits] r1074 - in trunk: . src/main/java src/main/java/com src/main/java/com/xpn src/main/java/com/xpn/xwiki src/main/java/com/xpn/xwiki/wysiwyg src/main/java/com/xpn/xwiki/wysiwyg/client src/main/java/com/xpn/xwiki/wysiwyg/client/diff src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers src/main/java/com/xpn/xwiki/wysiwyg/client/dom src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla src/main/java/org/ourproject/kune/platf src/main/java/org/ourproject/kune/platf/client/actions src/main/java/org/ourproject/kune/platf/client/ui/rte/basic

vjrj vjrj at ourproject.org
Fri Mar 13 00:47:02 CET 2009


Author: vjrj
Date: 2009-03-13 00:46:56 +0100 (Fri, 13 Mar 2009)
New Revision: 1074

Added:
   trunk/src/main/java/com/
   trunk/src/main/java/com/xpn/
   trunk/src/main/java/com/xpn/xwiki/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/Wysiwyg.gwt.xml
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/AddDelta.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ChangeDelta.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Chunk.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DeleteDelta.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Delta.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Diff.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffAlgorithm.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffException.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DifferentiationFailedException.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/PatchFailedException.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Revision.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/RevisionVisitor.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/SimpleDiff.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ToString.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/DiffNode.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/MyersDiff.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/PathNode.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/Snake.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DOMUtils.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DepthFirstPreOrderIterator.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Document.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DocumentFragment.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Element.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Event.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/InnerHTMLListener.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/JavaScriptObject.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Range.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeCompare.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeFactory.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Selection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/SelectionManager.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Style.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableCellElement.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableRowElement.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Text.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TextFragment.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/AbstractSelection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultNativeRangeWrapper.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRange.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRangeFactory.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelectionManager.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/NativeRangeWrapper.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/ControlRange.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IEDOMUtils.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelectionManager.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeRange.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeSelection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/TextRange.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaDOMUtils.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelection.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelectionManager.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeRange.java
   trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeSelection.java
Modified:
   trunk/COPYRIGHT
   trunk/src/main/java/org/ourproject/kune/platf/Kune-Platform.gwt.xml
   trunk/src/main/java/org/ourproject/kune/platf/client/actions/ActionShortcut.java
   trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPanel.java
   trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPresenter.java
   trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorView.java
Log:
Incomplete - task Better RTE (Rich Text Editor) support. Region library added thanks to xwiki (see Alt-I action for info in RTE).


Modified: trunk/COPYRIGHT
===================================================================
--- trunk/COPYRIGHT	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/COPYRIGHT	2009-03-12 23:46:56 UTC (rev 1074)
@@ -81,13 +81,13 @@
 others
 --------------------------------------------------------------------------------
 suco
-	 http://suco.googlecode.com
-	 Copyright: under GNU Lesser General Public License
+     http://suco.googlecode.com
+     Copyright: under GNU Lesser General Public License
    http://www.gnu.org/licenses/lgpl.html
 
 emite
-	 http://emite.googlecode.com
-	 Copyright: under GNU Lesser General Public License
+     http://emite.googlecode.com
+     Copyright: under GNU Lesser General Public License
    http://www.gnu.org/licenses/lgpl.html
 
 fonts-min.css and reset.css
@@ -109,6 +109,12 @@
    Copyright: under GNU Lesser General Public License
    http://www.gnu.org/licenses/lgpl.html
 
+dom region and diff libraries:
+   XWiki Platform
+   http://www.xwiki.org/
+   Copyright: under GNU Lesser General Public License
+   http://www.gnu.org/licenses/lgpl.html
+
 some license description:
     from Wikipedia
     Copyright: under GFDL http://www.gnu.org/copyleft/fdl.html

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/Wysiwyg.gwt.xml
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/Wysiwyg.gwt.xml	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/Wysiwyg.gwt.xml	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,56 @@
+<module>
+  <inherits name='com.google.gwt.user.User'/>
+  <inherits name="com.google.gwt.i18n.I18N"/>
+  <inherits name="com.google.gwt.http.HTTP"/>
+  <inherits name="com.google.gwt.junit.JUnit"/>
+
+  <stylesheet src="Wysiwyg.css"/>
+
+  <define-property name="old.user.agent" values="yes,no"/>
+
+  <property-provider name="old.user.agent"><![CDATA[
+    var ua = navigator.userAgent.toLowerCase();
+    var makeVersion = function(result) {
+      return (parseInt(result[1]) * 1000) + parseInt(result[2]);
+    };
+    if (ua.indexOf("gecko") != -1) {
+      var result = /rv:([0-9]+)\.([0-9]+)/.exec(ua);
+      if (result && result.length == 3) {
+        if (makeVersion(result) >= 1009) {
+          return "no";
+        }
+      }
+    }
+    return "yes";
+  ]]></property-provider>
+
+  <!-- SelectionManager -->
+  <replace-with class="com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelectionManager">
+    <when-type-is class="com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelectionManager"/>
+  </replace-with>
+
+  <replace-with class="com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla.MozillaSelectionManager">
+    <when-type-is class="com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelectionManager"/>
+    <when-property-is name="old.user.agent" value="yes"/>
+    <any>
+      <when-property-is name="user.agent" value="gecko1_8"/>
+      <when-property-is name="user.agent" value="gecko"/>
+    </any>
+  </replace-with>
+
+  <replace-with class="com.xpn.xwiki.wysiwyg.client.dom.internal.ie.IESelectionManager">
+    <when-type-is class="com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelectionManager"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
+
+  <!-- DOMUtils -->
+  <replace-with class="com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla.MozillaDOMUtils">
+    <when-type-is class="com.xpn.xwiki.wysiwyg.client.dom.DOMUtils"/>
+  </replace-with>
+
+  <replace-with class="com.xpn.xwiki.wysiwyg.client.dom.internal.ie.IEDOMUtils">
+    <when-type-is class="com.xpn.xwiki.wysiwyg.client.dom.DOMUtils"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
+
+</module>

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/AddDelta.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/AddDelta.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/AddDelta.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,125 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.List;
+
+/**
+ * Holds an add-delta between to revisions of a text.
+ * 
+ * @version $Id: AddDelta.java,v 1.1 2006/03/12 00:24:21 juanca Exp $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see Diff
+ * @see Chunk
+ */
+public class AddDelta extends Delta
+{
+
+    public AddDelta()
+    {
+        super();
+    }
+
+    public AddDelta(int origpos, Chunk rev)
+    {
+        init(new Chunk(origpos, 0), rev);
+    }
+
+    public void verify(List target) throws PatchFailedException
+    {
+        if (original.first() > target.size())
+        {
+            throw new PatchFailedException("original.first() > target.size()");
+        }
+    }
+
+    public void applyTo(List target)
+    {
+        revised.applyAdd(original.first(), target);
+    }
+
+    public void toString(StringBuffer s)
+    {
+        s.append(original.anchor());
+        s.append("a");
+        s.append(revised.rangeString());
+        s.append(Diff.NL);
+        revised.toString(s, "> ", Diff.NL);
+    }
+
+    public void toRCSString(StringBuffer s, String EOL)
+    {
+        s.append("a");
+        s.append(original.anchor());
+        s.append(" ");
+        s.append(revised.size());
+        s.append(EOL);
+        revised.toString(s, "", EOL);
+    }
+
+    public void Accept(RevisionVisitor visitor)
+    {
+        visitor.visit(this);
+    }
+
+    public void accept(RevisionVisitor visitor)
+    {
+        visitor.visit(this);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ChangeDelta.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ChangeDelta.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ChangeDelta.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,133 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.List;
+
+/**
+ * Holds an change-delta between to revisions of a text.
+ * 
+ * @version $Id: ChangeDelta.java,v 1.1 2006/03/12 00:24:21 juanca Exp $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see Diff
+ * @see Chunk
+ */
+public class ChangeDelta extends Delta
+{
+
+    public ChangeDelta()
+    {
+        super();
+    }
+
+    public ChangeDelta(Chunk orig, Chunk rev)
+    {
+        init(orig, rev);
+    }
+
+    public void verify(List target) throws PatchFailedException
+    {
+        if (!original.verify(target))
+        {
+            throw new PatchFailedException();
+        }
+        if (original.first() > target.size())
+        {
+            throw new PatchFailedException("original.first() > target.size()");
+        }
+    }
+
+    public void applyTo(List target)
+    {
+        original.applyDelete(target);
+        revised.applyAdd(original.first(), target);
+    }
+
+    public void toString(StringBuffer s)
+    {
+        original.rangeString(s);
+        s.append("c");
+        revised.rangeString(s);
+        s.append(Diff.NL);
+        original.toString(s, "< ", "\n");
+        s.append("---");
+        s.append(Diff.NL);
+        revised.toString(s, "> ", "\n");
+    }
+
+    public void toRCSString(StringBuffer s, String EOL)
+    {
+        s.append("d");
+        s.append(original.rcsfrom());
+        s.append(" ");
+        s.append(original.size());
+        s.append(EOL);
+        s.append("a");
+        s.append(original.rcsto());
+        s.append(" ");
+        s.append(revised.size());
+        s.append(EOL);
+        revised.toString(s, "", EOL);
+    }
+
+    public void accept(RevisionVisitor visitor)
+    {
+        visitor.visit(this);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Chunk.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Chunk.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Chunk.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,417 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Holds a information about a parrt of the text involved in a differencing or
+ * patching operation.
+ * 
+ * @version $Id: Chunk.java,v 1.1 2006/03/12 00:24:21 juanca Exp $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Diff
+ * @see Delta
+ */
+public class Chunk extends ToString
+{
+
+    protected int anchor;
+
+    protected int count;
+
+    protected List chunk;
+
+    public Chunk() {        
+    }
+
+    /**
+     * Creates a chunk that doesn't copy the original text.
+     * 
+     * @param pos
+     *            the start position in the text.
+     * @param count
+     *            the size of the chunk.
+     */
+    public Chunk(int pos, int count)
+    {
+        this.anchor = pos;
+        this.count = (count >= 0 ? count : 0);
+    }
+
+    /**
+     * Creates a chunk and saves a copy the original chunk's text.
+     * 
+     * @param iseq
+     *            the original text.
+     * @param pos
+     *            the start position in the text.
+     * @param count
+     *            the size of the chunk.
+     */
+    public Chunk(Object[] iseq, int pos, int count)
+    {
+        this(pos, count);
+        chunk = slice(iseq, pos, count);
+    }
+
+    /**
+     * Creates a chunk that will be displaced in the resulting text, and saves a
+     * copy the original chunk's text.
+     * 
+     * @param iseq
+     *            the original text.
+     * @param pos
+     *            the start position in the text.
+     * @param count
+     *            the size of the chunk.
+     * @param offset
+     *            the position the chunk should have in the resulting text.
+     */
+    public Chunk(Object[] iseq, int pos, int count, int offset)
+    {
+        this(offset, count);
+        chunk = slice(iseq, pos, count);
+    }
+
+    /**
+     * Creates a chunk and saves a copy the original chunk's text.
+     * 
+     * @param iseq
+     *            the original text.
+     * @param pos
+     *            the start position in the text.
+     * @param count
+     *            the size of the chunk.
+     */
+    public Chunk(List iseq, int pos, int count)
+    {
+        this(pos, count);
+        chunk = slice(iseq, pos, count);
+    }
+
+    /**
+     * Creates a chunk that will be displaced in the resulting text, and saves a
+     * copy the original chunk's text.
+     * 
+     * @param iseq
+     *            the original text.
+     * @param pos
+     *            the start position in the text.
+     * @param count
+     *            the size of the chunk.
+     * @param offset
+     *            the position the chunk should have in the resulting text.
+     */
+    public Chunk(List iseq, int pos, int count, int offset)
+    {
+        this(offset, count);
+        chunk = slice(iseq, pos, count);
+    }
+
+    /**
+     * Returns the anchor position of the chunk.
+     * 
+     * @return the anchor position.
+     */
+    public int anchor()
+    {
+        return anchor;
+    }
+
+    public void moveAnchor(int deltaPosition) {
+        anchor += deltaPosition;
+    }
+
+    /**
+     * Returns the size of the chunk.
+     * 
+     * @return the size.
+     */
+    public int size()
+    {
+        return count;
+    }
+
+    /**
+     * Returns the index of the first line of the chunk.
+     */
+    public int first()
+    {
+        return anchor();
+    }
+
+    /**
+     * Returns the index of the last line of the chunk.
+     */
+    public int last()
+    {
+        return anchor() + size() - 1;
+    }
+
+    /**
+     * Returns the <i>from</i> index of the chunk in RCS terms.
+     */
+    public int rcsfrom()
+    {
+        return anchor + 1;
+    }
+
+    /**
+     * Returns the <i>to</i> index of the chunk in RCS terms.
+     */
+    public int rcsto()
+    {
+        return anchor + count;
+    }
+
+    /**
+     * Returns the text saved for this chunk.
+     * 
+     * @return the text.
+     */
+    public List chunk()
+    {
+        return chunk;
+    }
+
+    /**
+     * Verifies that this chunk's saved text matches the corresponding text in
+     * the given sequence.
+     * 
+     * @param target
+     *            the sequence to verify against.
+     * @return true if the texts match.
+     */
+    public boolean verify(List target)
+    {
+        if (chunk == null)
+        {
+            return true;
+        }
+        if (last() > target.size())
+        {
+            return false;
+        }
+        for (int i = 0; i < count; i++)
+        {
+            if (!target.get(anchor + i).equals(chunk.get(i)))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Delete this chunk from he given text.
+     * 
+     * @param target
+     *            the text to delete from.
+     */
+    public void applyDelete(List target)
+    {
+        for (int i = last(); i >= first(); i--)
+        {
+            target.remove(i);
+        }
+    }
+
+    /**
+     * Add the text of this chunk to the target at the given position.
+     * 
+     * @param start
+     *            where to add the text.
+     * @param target
+     *            the text to add to.
+     */
+    public void applyAdd(int start, List target)
+    {
+        Iterator i = chunk.iterator();
+        while (i.hasNext())
+        {
+            target.add(start++, i.next());
+        }
+    }
+
+    /**
+     * Provide a string image of the chunk using the an empty prefix and
+     * postfix.
+     */
+    public void toString(StringBuffer s)
+    {
+        toString(s, "", "");
+    }
+
+    /**
+     * Provide a string image of the chunk using the given prefix and postfix.
+     * 
+     * @param s
+     *            where the string image should be appended.
+     * @param prefix
+     *            the text thatshould prefix each line.
+     * @param postfix
+     *            the text that should end each line.
+     */
+    public StringBuffer toString(StringBuffer s, String prefix, String postfix)
+    {
+        if (chunk != null)
+        {
+            Iterator i = chunk.iterator();
+            while (i.hasNext())
+            {
+                s.append(prefix);
+                s.append(i.next());
+                s.append(postfix);
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Retreives the specified part from a {@link List List}.
+     * 
+     * @param seq
+     *            the list to retreive a slice from.
+     * @param pos
+     *            the start position.
+     * @param count
+     *            the number of items in the slice.
+     * @return a {@link List List} containing the specified items.
+     */
+    public static List slice(List seq, int pos, int count)
+    {
+        ArrayList list = new ArrayList();
+        if (count <= 0)
+        {
+            if (pos<seq.size())
+             list.add(seq.get(pos));
+            return list;
+        }
+        else
+        {
+            for (int i=pos;i<pos+count;i++) {
+                if (i<seq.size())
+                 list.add(seq.get(i));
+            }
+            return list;
+         }
+    }
+
+    /**
+     * Retrieves a slice from an {@link Object Object} array.
+     * 
+     * @param seq
+     *            the list to retreive a slice from.
+     * @param pos
+     *            the start position.
+     * @param count
+     *            the number of items in the slice.
+     * @return a {@link List List} containing the specified items.
+     */
+    public static List slice(Object[] seq, int pos, int count)
+    {
+        return slice(Arrays.asList(seq), pos, count);
+    }
+
+    /**
+     * Provide a string representation of the numeric range of this chunk.
+     */
+    public String rangeString()
+    {
+        StringBuffer result = new StringBuffer();
+        rangeString(result);
+        return result.toString();
+    }
+
+    /**
+     * Provide a string representation of the numeric range of this chunk.
+     * 
+     * @param s
+     *            where the string representation should be appended.
+     */
+    public void rangeString(StringBuffer s)
+    {
+        rangeString(s, ",");
+    }
+
+    /**
+     * Provide a string representation of the numeric range of this chunk.
+     * 
+     * @param s
+     *            where the string representation should be appended.
+     * @param separ
+     *            what to use as line separator.
+     */
+    public void rangeString(StringBuffer s, String separ)
+    {
+        if (size() <= 1)
+        {
+            s.append(Integer.toString(rcsfrom()));
+        }
+        else
+        {
+            s.append(Integer.toString(rcsfrom()));
+            s.append(separ);
+            s.append(Integer.toString(rcsto()));
+        }
+    }
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DeleteDelta.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DeleteDelta.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DeleteDelta.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,119 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.List;
+
+/**
+ * Holds a delete-delta between to revisions of a text.
+ * 
+ * @version $Id: DeleteDelta.java,v 1.1 2006/03/12 00:24:21 juanca Exp $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see Diff
+ * @see Chunk
+ */
+public class DeleteDelta extends Delta
+{
+
+    public DeleteDelta()
+    {
+        super();
+    }
+
+    public DeleteDelta(Chunk orig)
+    {
+        init(orig, null);
+    }
+
+    public void verify(List target) throws PatchFailedException
+    {
+        if (!original.verify(target))
+        {
+            throw new PatchFailedException();
+        }
+    }
+
+    public void applyTo(List target)
+    {
+        original.applyDelete(target);
+    }
+
+    public void toString(StringBuffer s)
+    {
+        s.append(original.rangeString());
+        s.append("d");
+        s.append(revised.rcsto());
+        s.append(Diff.NL);
+        original.toString(s, "< ", Diff.NL);
+    }
+
+    public void toRCSString(StringBuffer s, String EOL)
+    {
+        s.append("d");
+        s.append(original.rcsfrom());
+        s.append(" ");
+        s.append(original.size());
+        s.append(EOL);
+    }
+
+    public void accept(RevisionVisitor visitor)
+    {
+        visitor.visit(this);
+    }
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Delta.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Delta.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Delta.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,257 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.List;
+
+/**
+ * Holds a "delta" difference between to revisions of a text.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * 
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @author <a href="mailto:bwm at hplb.hpl.hp.com">Brian McBride</a>
+ * @see Diff
+ * @see Chunk
+ * @see Revision
+ * 
+ * modifications
+ * 
+ * 27 Apr 2003 bwm
+ * 
+ * Added getOriginal() and getRevised() accessor methods Added visitor pattern
+ * accept() method
+ */
+
+public abstract class Delta extends ToString
+{
+
+    protected Chunk original;
+
+    protected Chunk revised;
+
+   
+    /**
+     * Returns a Delta that corresponds to the given chunks in the original and
+     * revised text respectively.
+     * 
+     * @param orig
+     *            the chunk in the original text.
+     * @param rev
+     *            the chunk in the revised text.
+     */
+    public static Delta newDelta(Chunk orig, Chunk rev)
+    {
+        Delta result;
+        if ((orig.size()>0)&&(rev.size()>0))
+            result = new ChangeDelta();
+        else if ((orig.size()==0)&&(rev.size()==0))
+            result = new ChangeDelta();
+        else if ((orig.size()>0)&&(rev.size()==0))
+            result = new DeleteDelta();
+        else if ((orig.size()==0)&&(rev.size()>0))
+                 result = new AddDelta();
+        else
+             return null;
+
+        result.init(orig, rev);
+        return result;
+    }
+
+    /**
+     * Creates an uninitialized delta.
+     */
+    public Delta()
+    {
+    }
+
+    /**
+     * Creates a delta object with the given chunks from the original and
+     * revised texts.
+     */
+    public  Delta(Chunk orig, Chunk rev)
+    {
+        init(orig, rev);
+    }
+
+    /**
+     * Initializaes the delta with the given chunks from the original and
+     * revised texts.
+     */
+    public void init(Chunk orig, Chunk rev)
+    {
+        original = orig;
+        revised = rev;
+    }
+
+    /**
+     * Verifies that this delta can be used to patch the given text.
+     * 
+     * @param target
+     *            the text to patch.
+     * @throws PatchFailedException
+     *             if the patch cannot be applied.
+     */
+    public abstract void verify(List target) throws PatchFailedException;
+
+    /**
+     * Applies this delta as a patch to the given text.
+     * 
+     * @param target
+     *            the text to patch.
+     * @throws PatchFailedException
+     *             if the patch cannot be applied.
+     */
+    public final void patch(List target) throws PatchFailedException
+    {
+        verify(target);
+        try
+        {
+            applyTo(target);
+        }
+        catch (Exception e)
+        {
+            throw new PatchFailedException(e.getMessage());
+        }
+    }
+
+    /**
+     * Applies this delta as a patch to the given text.
+     * 
+     * @param target
+     *            the text to patch.
+     * @throws PatchFailedException
+     *             if the patch cannot be applied.
+     */
+    public abstract void applyTo(List target);
+
+    /**
+     * Converts this delta into its Unix diff style string representation.
+     * 
+     * @param s
+     *            a {@link StringBuffer StringBuffer} to which the string
+     *            representation will be appended.
+     */
+    public void toString(StringBuffer s)
+    {
+        original.rangeString(s);
+        s.append("x");
+        revised.rangeString(s);
+        s.append(Diff.NL);
+        original.toString(s, "> ", "\n");
+        s.append("---");
+        s.append(Diff.NL);
+        revised.toString(s, "< ", "\n");
+    }
+
+    /**
+     * Converts this delta into its RCS style string representation.
+     * 
+     * @param s
+     *            a {@link StringBuffer StringBuffer} to which the string
+     *            representation will be appended.
+     * @param EOL
+     *            the string to use as line separator.
+     */
+    public abstract void toRCSString(StringBuffer s, String EOL);
+
+    /**
+     * Converts this delta into its RCS style string representation.
+     * 
+     * @param EOL
+     *            the string to use as line separator.
+     */
+    public String toRCSString(String EOL)
+    {
+        StringBuffer s = new StringBuffer();
+        toRCSString(s, EOL);
+        return s.toString();
+    }
+
+    /**
+     * Accessor method to return the chunk representing the original sequence of
+     * items
+     * 
+     * @return the original sequence
+     */
+    public Chunk getOriginal()
+    {
+        return original;
+    }
+
+    /**
+     * Accessor method to return the chunk representing the updated sequence of
+     * items.
+     * 
+     * @return the updated sequence
+     */
+    public Chunk getRevised()
+    {
+        return revised;
+    }
+
+    /**
+     * Accepts a visitor.
+     * <p>
+     * See the Visitor pattern in "Design Patterns" by the GOF4.
+     * 
+     * @param visitor
+     *            The visitor.
+     */
+    public abstract void accept(RevisionVisitor visitor);
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Diff.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Diff.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Diff.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,390 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.*;
+
+import com.xpn.xwiki.wysiwyg.client.diff.myers.MyersDiff;
+
+/**
+ * Implements a differencing engine that works on arrays of
+ * {@link Object Object}.
+ * 
+ * <p>
+ * Within this library, the word <i>text</i> means a unit of information
+ * subject to version control.
+ * 
+ * <p>
+ * Text is represented as <code>Object[]</code> because the diff engine is
+ * capable of handling more than plain ascci. In fact, arrays of any type that
+ * implements {@link java.lang.Object#hashCode hashCode()} and
+ * {@link java.lang.Object#equals equals()} correctly can be subject to
+ * differencing using this library.
+ * </p>
+ * 
+ * <p>
+ * This library provides a framework in which different differencing algorithms
+ * may be used. If no algorithm is specififed, a default algorithm is used.
+ * </p>
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see DiffAlgorithm
+ * 
+ * modifications:
+ * 
+ * 27 Apr 2003 bwm
+ * 
+ * Added some comments whilst trying to figure out the algorithm
+ * 
+ * 03 May 2003 bwm
+ * 
+ * Factored out the algorithm implementation into a separate difference
+ * algorithm class to allow pluggable algorithms.
+ */
+
+public class Diff extends ToString
+{
+    /** The standard line separator. */
+    public static final String NL = " "; // System.getProperty("line.separator");
+
+    /** The line separator to use in RCS format output. */
+    public static final String RCS_EOL = " ";
+
+    /** The original sequence. */ 
+    protected Object[] orig;
+
+    /** The differencing algorithm to use. */
+    protected DiffAlgorithm algorithm;
+
+    /**
+     * Create a differencing object for an empty text, using the default algorithm. This has been
+     * added to stop GWT's complains about not having a default constructor. You should use the
+     * other two constructors instead.
+     */
+    public Diff()
+    {
+        this(new Object[] {});
+    }
+
+    /**
+     * Create a differencing object using the default algorithm
+     * 
+     * @param original
+     *            original text that will be compared
+     */
+    public Diff(Object[] original)
+    {
+        this(original, null);
+    }
+
+    /**
+     * Create a differencing object using the given algorithm
+     * 
+     * @param original
+     *            the original text which will be compared against
+     * @param algorithm
+     *            the difference algorithm to use.
+     */
+    public Diff(Object[] original, DiffAlgorithm algorithm)
+    {
+        if (original == null)
+        {
+            throw new IllegalArgumentException();
+        }
+
+        this.orig = original;
+        if (algorithm != null)
+            this.algorithm = algorithm;
+        else
+            this.algorithm = defaultAlgorithm();
+    }
+
+    protected DiffAlgorithm defaultAlgorithm()
+    {
+        return new MyersDiff();
+    }
+
+    /**
+     * compute the difference between an original and a revision.
+     * 
+     * @param orig
+     *            the original
+     * @param rev
+     *            the revision to compare with the original.
+     * @return a Revision describing the differences
+     */
+    public static Revision diff(Object[] orig, Object[] rev)
+            throws DifferentiationFailedException
+    {
+        if (orig == null || rev == null)
+        {
+            throw new IllegalArgumentException();
+        }
+
+        return diff(orig, rev, null);
+    }
+
+    /**
+     * compute the difference between an original and a revision.
+     * 
+     * @param orig
+     *            the original
+     * @param rev
+     *            the revision to compare with the original.
+     * @param algorithm
+     *            the difference algorithm to use
+     * @return a Revision describing the differences
+     */
+    public static Revision diff(Object[] orig, Object[] rev,
+            DiffAlgorithm algorithm) throws DifferentiationFailedException
+    {
+        if (orig == null || rev == null)
+        {
+            throw new IllegalArgumentException();
+        }
+
+        return new Diff(orig, algorithm).diff(rev);
+    }
+
+    /**
+     * compute the difference between the original and a revision.
+     * 
+     * @param rev
+     *            the revision to compare with the original.
+     * @return a Revision describing the differences
+     */
+    public Revision diff(Object[] rev) throws DifferentiationFailedException
+    {
+        if (orig.length == 0 && rev.length == 0)
+            return new Revision();
+        else
+            return algorithm.diff(orig, rev);
+    }
+
+    /**
+     * Compares the two input sequences.
+     * 
+     * @param orig
+     *            The original sequence.
+     * @param rev
+     *            The revised sequence.
+     * @return true if the sequences are identical. False otherwise.
+     */
+    public static boolean compare(Object[] orig, Object[] rev)
+    {
+        if (orig.length != rev.length)
+        {
+            return false;
+        }
+        else
+        {
+            for (int i = 0; i < orig.length; i++)
+            {
+                if (!orig[i].equals(rev[i]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Converts an array of {@link Object Object} to a string using
+     * {@link Diff#NL Diff.NL} as the line separator.
+     * 
+     * @param o
+     *            the array of objects.
+     */
+    /* public static String arrayToString(Object[] o)
+    {
+        return arrayToString(o, Diff.NL);
+    }  */
+
+    /**
+     * Edits all of the items in the input sequence.
+     * 
+     * @param text
+     *            The input sequence.
+     * @return A sequence of the same length with all the lines differing from
+     *         the corresponding ones in the input.
+     */
+    public static Object[] editAll(Object[] text)
+    {
+        Object[] result = new String[text.length];
+
+        for (int i = 0; i < text.length; i++)
+            result[i] = text[i] + " <edited>";
+
+        return result;
+    }
+
+    /**
+     * Performs random edits on the input sequence. Useful for testing.
+     * 
+     * @param text
+     *            The input sequence.
+     * @return The sequence with random edits performed.
+     */
+    public static Object[] randomEdit(Object[] text)
+    {
+        return randomEdit(text, text.length);
+    }
+
+    /**
+     * Performs random edits on the input sequence. Useful for testing.
+     * 
+     * @param text
+     *            The input sequence.
+     * @param seed
+     *            A seed value for the randomizer.
+     * @return The sequence with random edits performed.
+     */
+    public static Object[] randomEdit(Object[] text, long seed)
+    {
+        /* List result = new ArrayList(Arrays.asList(text));
+        Random r = new Random(seed);
+        int nops = r.nextInt(10);
+        for (int i = 0; i < nops; i++)
+        {
+            boolean del = r.nextBoolean();
+            int pos = r.nextInt(result.size() + 1);
+            int len = Math.min(result.size() - pos, 1 + r.nextInt(4));
+            if (del && result.size() > 0)
+            { // delete
+                result.subList(pos, pos + len).clear();
+            }
+            else
+            {
+                for (int k = 0; k < len; k++, pos++)
+                {
+                    result.add(pos, "[" + i + "] random edit[" + i + "][" + i
+                            + "]");
+                }
+            }
+        }
+        return result.toArray();
+        */
+        return text;
+    }
+
+    /**
+     * Shuffles around the items in the input sequence.
+     * 
+     * @param text
+     *            The input sequence.
+     * @return The shuffled sequence.
+     */
+    public static Object[] shuffle(Object[] text)
+    {
+        return shuffle(text, text.length);
+    }
+
+    /**
+     * Shuffles around the items in the input sequence.
+     * 
+     * @param text
+     *            The input sequence.
+     * @param seed
+     *            A seed value for randomizing the suffle.
+     * @return The shuffled sequence.
+     */
+    public static Object[] shuffle(Object[] text, long seed)
+    {
+        List result = new ArrayList(Arrays.asList(text));
+        // Collections.shuffle(result);
+        return result.toArray();
+    }
+
+    /**
+     * Generate a random sequence of the given size.
+     * 
+     * @param The
+     *            size of the sequence to generate.
+     * @return The generated sequence.
+     */
+    /*
+    public static Object[] randomSequence(int size)
+    {
+        return randomSequence(size, size);
+    } */
+
+    /**
+     * Generate a random sequence of the given size.
+     * 
+     * @param The
+     *            size of the sequence to generate.
+     * @param seed
+     *            A seed value for randomizing the generation.
+     * @return The generated sequence.
+     */
+    /*
+    public static Object[] randomSequence(int size, long seed)
+    {
+        Integer[] result = new Integer[size];
+        Random r = new Random(seed);
+        for (int i = 0; i < result.length; i++)
+        {
+            result[i] = new Integer(r.nextInt(size));
+        }
+        return result;
+    } */
+
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffAlgorithm.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffAlgorithm.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffAlgorithm.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+/**
+ * A simple interface for implementations of differencing algorithms.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * 
+ * @author <a href="mailto:bwm at hplb.hpl.hp.com">Brian McBride</a>
+ */
+public interface DiffAlgorithm
+{
+    /**
+     * Computes the difference between the original sequence and the revised
+     * sequence and returns it as a
+     * {@link com.xpn.xwiki.wysiwyg.client.diff.Revision Revision} object.
+     * <p>
+     * The revision can be used to construct the revised sequence from the
+     * original sequence.
+     * 
+     * @param rev
+     *            the revised text
+     * @return the revision script.
+     * @throws DifferentiationFailedException
+     *             if the diff could not be computed.
+     */
+    public abstract Revision diff(Object[] orig, Object[] rev)
+            throws DifferentiationFailedException;
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffException.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffException.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DiffException.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,78 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+/**
+ * Base class for all exceptions emanating from this package.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * 
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ */
+public class DiffException extends Exception
+{
+
+    public DiffException()
+    {
+    }
+
+    public DiffException(String msg)
+    {
+        super(msg);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DifferentiationFailedException.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DifferentiationFailedException.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/DifferentiationFailedException.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,81 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+/**
+ * Thrown whenever the differencing engine cannot produce the differences
+ * between two revisions of ta text.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * 
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Diff
+ * @see DiffAlgorithm
+ */
+public class DifferentiationFailedException extends DiffException
+{
+
+    public DifferentiationFailedException()
+    {
+    }
+
+    public DifferentiationFailedException(String msg)
+    {
+        super(msg);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/PatchFailedException.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/PatchFailedException.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/PatchFailedException.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,79 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+/**
+ * Thrown whenever a delta cannot be applied as a patch to a given text.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see Diff
+ */
+public class PatchFailedException extends DiffException
+{
+
+    public PatchFailedException()
+    {
+    }
+
+    public PatchFailedException(String msg)
+    {
+        super(msg);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Revision.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Revision.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/Revision.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,269 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * A Revision holds the series of deltas that describe the differences between
+ * two sequences.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * 
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @author <a href="mailto:bwm at hplb.hpl.hp.com">Brian McBride</a>
+ * 
+ * @see Delta
+ * @see Diff
+ * @see Chunk
+ * @see Revision
+ * 
+ * modifications 27 Apr 2003 bwm
+ * 
+ * Added visitor pattern Visitor interface and accept() method.
+ */
+
+public class Revision extends ToString
+{
+
+    List deltas_ = new ArrayList();
+
+    /**
+     * Creates an empty Revision.
+     */
+    public Revision()
+    {
+    }
+
+    /**
+     * Adds a delta to this revision.
+     * 
+     * @param delta
+     *            the {@link Delta Delta} to add.
+     */
+    public synchronized void addDelta(Delta delta)
+    {
+        if (delta == null)
+        {
+            throw new IllegalArgumentException("new delta is null");
+        }
+        deltas_.add(delta);
+    }
+
+    /**
+     * Adds a delta to the start of this revision.
+     * 
+     * @param delta
+     *            the {@link Delta Delta} to add.
+     */
+    public synchronized void insertDelta(Delta delta)
+    {
+        if (delta == null)
+        {
+            throw new IllegalArgumentException("new delta is null");
+        }
+        deltas_.add(0, delta);
+    }
+
+    /**
+     * Retrieves a delta from this revision by position.
+     * 
+     * @param i
+     *            the position of the delta to retrieve.
+     * @return the specified delta
+     */
+    public Delta getDelta(int i)
+    {
+        return (Delta) deltas_.get(i);
+    }
+
+    /**
+     * Returns the number of deltas in this revision.
+     * 
+     * @return the number of deltas.
+     */
+    public int size()
+    {
+        return deltas_.size();
+    }
+
+    /**
+     * Applies the series of deltas in this revision as patches to the given
+     * text.
+     * 
+     * @param src
+     *            the text to patch, which the method doesn't change.
+     * @return the resulting text after the patches have been applied.
+     * @throws PatchFailedException
+     *             if any of the patches cannot be applied.
+     */
+    public Object[] patch(Object[] src) throws PatchFailedException
+    {
+        List target = new ArrayList(Arrays.asList(src));
+        applyTo(target);
+        return target.toArray();
+    }
+
+    /**
+     * Applies the series of deltas in this revision as patches to the given
+     * text.
+     * 
+     * @param target
+     *            the text to patch.
+     * @throws PatchFailedException
+     *             if any of the patches cannot be applied.
+     */
+    public synchronized void applyTo(List target) throws PatchFailedException
+    {
+        ListIterator i = deltas_.listIterator(deltas_.size());
+        while (i.hasPrevious())
+        {
+            Delta delta = (Delta) i.previous();
+            delta.patch(target);
+        }
+    }
+
+    /**
+     * Converts this revision into its Unix diff style string representation.
+     * 
+     * @param s
+     *            a {@link StringBuffer StringBuffer} to which the string
+     *            representation will be appended.
+     */
+    public synchronized void toString(StringBuffer s)
+    {
+        Iterator i = deltas_.iterator();
+        while (i.hasNext())
+        {
+            ((Delta) i.next()).toString(s);
+        }
+    }
+
+    /**
+     * Converts this revision into its RCS style string representation.
+     * 
+     * @param s
+     *            a {@link StringBuffer StringBuffer} to which the string
+     *            representation will be appended.
+     * @param EOL
+     *            the string to use as line separator.
+     */
+    public synchronized void toRCSString(StringBuffer s, String EOL)
+    {
+        Iterator i = deltas_.iterator();
+        while (i.hasNext())
+        {
+            ((Delta) i.next()).toRCSString(s, EOL);
+        }
+    }
+
+    /**
+     * Converts this revision into its RCS style string representation.
+     * 
+     * @param s
+     *            a {@link StringBuffer StringBuffer} to which the string
+     *            representation will be appended.
+     */
+    public void toRCSString(StringBuffer s)
+    {
+        toRCSString(s, Diff.NL);
+    }
+
+    /**
+     * Converts this delta into its RCS style string representation.
+     * 
+     * @param EOL
+     *            the string to use as line separator.
+     */
+    public String toRCSString(String EOL)
+    {
+        StringBuffer s = new StringBuffer();
+        toRCSString(s, EOL);
+        return s.toString();
+    }
+
+    /**
+     * Converts this delta into its RCS style string representation using the
+     * default line separator.
+     */
+    public String toRCSString()
+    {
+        return toRCSString(Diff.NL);
+    }
+
+    /**
+     * Accepts a visitor.
+     * 
+     * @param visitor
+     *            the visitor visiting this instance
+     */
+    public void accept(RevisionVisitor visitor)
+    {
+        visitor.visit(this);
+        Iterator iter = deltas_.iterator();
+        while (iter.hasNext())
+        {
+            ((Delta) iter.next()).accept(visitor);
+        }
+    }
+
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/RevisionVisitor.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/RevisionVisitor.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/RevisionVisitor.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,73 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+/**
+ * Definition of a Visitor interface for {@link Revision Revisions} See "Design
+ * Patterns" by the Gang of Four
+ */
+public interface RevisionVisitor
+{
+    public void visit(Revision revision);
+
+    public void visit(DeleteDelta delta);
+
+    public void visit(ChangeDelta delta);
+
+    public void visit(AddDelta delta);
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/SimpleDiff.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/SimpleDiff.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/SimpleDiff.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,335 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import java.util.*;
+
+/**
+ * Implements a simple differencing algortithm.
+ * <p>
+ * 
+ * @date $Date: 2006/03/12 00:24:21 $
+ * @version $Revision: 1.1 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * 
+ * <p>
+ * <b>Overview of Algorithm</b>
+ * </p>
+ * 
+ * <p>
+ * <i>by <a href='http://www.topmeadow.net/bwm'> bwm</a>
+ * </p>
+ * 
+ * <p>
+ * The algorithm is optimised for situations where the input sequences have few
+ * repeated objects. If it is given input with many repeated objects it will
+ * report sub-optimal changes. However, given appropriate input, it is fast, and
+ * linear in memory usage.
+ * </p>
+ * 
+ * <p>
+ * The algorithm consists of the following steps:
+ * </p>
+ * <ul>
+ * <li>compute an equivalence set for the input data</li>
+ * <li>translate each element of the orginal and revised input sequences to a
+ * member of the equivalence set </li>
+ * <li>match the the input sequences to determine the deltas, i.e. the
+ * differences between the original and revised sequences.</li>
+ * </ul>
+ * 
+ * <p>
+ * The first step is to compute a an equivalence set for the input data. The
+ * equivalence set is computed from objects that are in the original input
+ * sequence
+ * </p>
+ * 
+ * <pre>
+ *    eq(x) = the index of the first occurence of x in the original sequence.
+ * </pre>
+ * 
+ * <p>
+ * With this equivalence function, the algorithm can compare integers rather
+ * than strings, which is considerably more efficient.
+ * </p>
+ * 
+ * <p>
+ * The second step is to compute the datastructure on which the algorithm will
+ * operate. Having computed the equivalence function in the previous step, we
+ * can compute two arrays where indx[i] = eqs(orig[i]) and jndx[i] =
+ * eqs(rev[i]). The algorithm can now operate on indx and jndx instead of orig
+ * and rev. Thus, comparisons are then on O(int == int) instead of
+ * O(Object.equals(Object)).
+ * </p>
+ * 
+ * <p>
+ * The algorithm now matches indx and jndx. Whilst indx[i] == jndx[i] it skips
+ * matching objects in the sequence. In seeking to match objects in the input
+ * sequence it assumes that each object is likely to be unique. It uses the
+ * known characteristics of the unique equivalence function. It can tell from
+ * the eq value if this object appeared in the other sequence at all. If it did
+ * not, there is no point in searching for a match.
+ * </p>
+ * 
+ * <p>
+ * Recall that the eq function value is the index earliest occurrence in the
+ * orig sequence. This information is used to search efficiently for the next
+ * match. The algorithm is perfect when all input objects are unique, but
+ * degrades when input objects are not unique. When input objects are not unique
+ * an optimal match may not be found, but a correct match will be.
+ * </p>
+ * 
+ * <p>
+ * Having identified common matching objects in the orig and revised sequences,
+ * the differences between them are easily computed.
+ * </p>
+ * 
+ * @see Delta
+ * @see Revision Modifications:
+ * 
+ * 27/Apr/2003 bwm Added some comments whilst trying to figure out the algorithm
+ * 
+ * 03 May 2003 bwm Created this implementation class by refactoring it out of
+ * the Diff class to enable plug in difference algorithms
+ * 
+ */
+public class SimpleDiff implements DiffAlgorithm
+{
+
+    static final int NOT_FOUND_i = -2;
+    static final int NOT_FOUND_j = -1;
+    static final int EOS = Integer.MAX_VALUE;
+
+    public SimpleDiff()
+    {
+    }
+
+    protected int scan(int[] ndx, int i, int target)
+    {
+        while (ndx[i] < target)
+        {
+            i++;
+        }
+        return i;
+    }
+
+    /**
+     * Compute the difference between original and revised sequences.
+     * 
+     * @param orig
+     *            The original sequence.
+     * @param rev
+     *            The revised sequence to be compared with the original.
+     * @return A Revision object describing the differences.
+     * @throws DifferenciationFailedException
+     *             if the diff could not be computed.
+     */
+    public Revision diff(Object[] orig, Object[] rev)
+            throws DifferentiationFailedException
+    {
+        // create map eqs, such that for each item in both orig and rev
+        // eqs(item) = firstOccurrence(item, orig);
+        Map eqs = buildEqSet(orig, rev);
+
+        // create an array such that
+        // indx[i] = NOT_FOUND_i if orig[i] is not in rev
+        // indx[i] = firstOccurrence(orig[i], orig)
+        int[] indx = buildIndex(eqs, orig, NOT_FOUND_i);
+
+        // create an array such that
+        // jndx[j] = NOT_FOUND_j if orig[j] is not in rev
+        // jndx[j] = firstOccurrence(rev[j], orig)
+        int[] jndx = buildIndex(eqs, rev, NOT_FOUND_j);
+
+        // what in effect has been done is to build a unique hash
+        // for each item that is in both orig and rev
+        // and to label each item in orig and new with that hash value
+        // or a marker that the item is not common to both.
+
+        eqs = null; // let gc know we're done with this
+
+        Revision deltas = new Revision(); // !!! new Revision()
+        int i = 0;
+        int j = 0;
+
+        // skip matching
+        // skip leading items that are equal
+        // could be written
+        // for (i=0; indx[i] != EOS && indx[i] == jndx[i]; i++);
+        // j = i;
+        for (; indx[i] != EOS && indx[i] == jndx[j]; i++, j++)
+        {
+            /* void */
+        }
+
+        while (indx[i] != jndx[j])
+        { // only equal if both == EOS
+            // they are different
+            int ia = i;
+            int ja = j;
+
+            // size of this delta
+            do
+            {
+                // look down rev for a match
+                // stop at a match
+                // or if the FO(rev[j]) > FO(orig[i])
+                // or at the end
+                while (jndx[j] < 0 || jndx[j] < indx[i])
+                {
+                    j++;
+                }
+                // look down orig for a match
+                // stop at a match
+                // or if the FO(orig[i]) > FO(rev[j])
+                // or at the end
+                while (indx[i] < 0 || indx[i] < jndx[j])
+                {
+                    i++;
+                }
+
+                // this doesn't do a compare each line with each other line
+                // so it won't find all matching lines
+            }
+            while (indx[i] != jndx[j]);
+
+            // on exit we have a match
+
+            // they are equal, reverse any exedent matches
+            // it is possible to overshoot, so count back matching items
+            while (i > ia && j > ja && indx[i - 1] == jndx[j - 1])
+            {
+                --i;
+                --j;
+            }
+
+            deltas.addDelta(Delta.newDelta(new Chunk(orig, ia, i - ia),
+                    new Chunk(rev, ja, j - ja)));
+            // skip matching
+            for (; indx[i] != EOS && indx[i] == jndx[j]; i++, j++)
+            {
+                /* void */
+            }
+        }
+        return deltas;
+    }
+
+    /**
+     * create a <code>Map</code> from each common item in orig and rev to the
+     * index of its first occurrence in orig
+     * 
+     * @param orig
+     *            the original sequence of items
+     * @param rev
+     *            the revised sequence of items
+     */
+    protected Map buildEqSet(Object[] orig, Object[] rev)
+    {
+        // construct a set of the objects that orig and rev have in common
+
+        // first construct a set containing all the elements in orig
+        Set items = new HashSet(Arrays.asList(orig));
+
+        // then remove all those not in rev
+        items.retainAll(Arrays.asList(rev));
+
+        Map eqs = new HashMap();
+        for (int i = 0; i < orig.length; i++)
+        {
+            // if its a common item and hasn't been found before
+            if (items.contains(orig[i]))
+            {
+                // add it to the map
+                eqs.put(orig[i], new Integer(i));
+                // and make sure its not considered again
+                items.remove(orig[i]);
+            }
+        }
+        return eqs;
+    }
+
+    /**
+     * build a an array such each a[i] = eqs([i]) or NF if eqs([i]) undefined
+     * 
+     * @param eqs
+     *            a mapping from Object to Integer
+     * @param seq
+     *            a sequence of objects
+     * @param NF
+     *            the not found marker
+     */
+    protected int[] buildIndex(Map eqs, Object[] seq, int NF)
+    {
+        int[] result = new int[seq.length + 1];
+        for (int i = 0; i < seq.length; i++)
+        {
+            Integer value = (Integer) eqs.get(seq[i]);
+            if (value == null || value.intValue() < 0)
+            {
+                result[i] = NF;
+            }
+            else
+            {
+                result[i] = value.intValue();
+            }
+        }
+        result[seq.length] = EOS;
+        return result;
+    }
+
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ToString.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ToString.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/ToString.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,197 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * This class delegates handling of the to a StringBuffer based version.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ */
+public class ToString implements IsSerializable
+{
+    public ToString()
+    {
+    }
+
+    /**
+     * Default implementation of the
+     * {@link java.lang.Object#toString toString() } method that delegates work
+     * to a {@link java.lang.StringBuffer StringBuffer} base version.
+     */
+    public String toString()
+    {
+        StringBuffer s = new StringBuffer();
+        toString(s);
+        return s.toString();
+    }
+
+    /**
+     * Place a string image of the object in a StringBuffer.
+     * 
+     * @param s
+     *            the string buffer.
+     */
+    public void toString(StringBuffer s)
+    {
+        s.append(super.toString());
+    }
+
+    /**
+     * Breaks a string into an array of strings. Use the value of the
+     * <code>line.separator</code> system property as the linebreak character.
+     * 
+     * @param value
+     *            the string to convert.
+     */
+    /*
+    public static String[] stringToArray(String value)
+    {
+        ArrayList list = new ArrayList();
+        String[] lines = value.replaceAll("\r", "").split("\n");
+        for (int i=0;i<lines.length;i++) {
+           splitLine(list, lines[i]);
+        }
+        String[] result = new String[list.size() + 2];
+        for (int i=0;i<list.size();i++) {
+            result[i+1] = (String) list.get(i);
+        }
+        // Add placeholder strings at the beginning and end
+        result[0] = "BEGIN";
+        result[list.size()+1] = "END";
+        return result;
+
+    }
+
+    public static void splitLine(ArrayList list, String line) {
+        boolean isTag = false;
+        StringBuffer currentToken = new StringBuffer();
+        for (int i=0;i<line.length();i++) {
+            char c = line.charAt(i);
+            if (!isTag)  {
+                if (c=='<') {
+                    list.add(currentToken.toString());
+                    currentToken = new StringBuffer();
+                    currentToken.append(c);
+                    isTag = true;
+                } else if (c==' ') {
+                    list.add(currentToken.toString());
+                    list.add(" ");
+                    currentToken = new StringBuffer();
+                } else {
+                    currentToken.append(c);
+                }
+            } else if (isTag && (c == '>')) {
+               currentToken.append(c);
+               list.add(currentToken.toString());
+               currentToken = new StringBuffer();
+               isTag = false;
+            } else {
+                currentToken.append(c);
+            }
+        }
+        if (currentToken.length()>0) {
+            list.add(currentToken.toString());
+        }
+        list.add("\n");
+    }
+
+    /**
+     * Converts an array of {@link Object Object} to a string using the given
+     * line separator.
+     * 
+     * @param o                   
+     *            the array of objects.
+     */
+    /*
+    public static String arrayToString(Object[] o)
+    {
+        StringBuffer buf = new StringBuffer();
+        // ignore first and last element
+        for (int i = 1; i < o.length - 1; i++)
+        {
+            buf.append(o[i]);
+        }
+        return buf.toString().replaceAll("\r", "");
+    } */
+
+    public static String[] stringToArray(String value)
+    {
+      value = value.replaceAll("\r", "");
+      String[] result = new String[value.length() + 2];
+      result[0] = "BEGIN";
+      for (int i=0;i<value.length();i++) {
+          result[i+1] = "" + value.charAt(i);
+      }
+      result[result.length-1] = "END";
+      return result;
+    }
+
+    public static String arrayToString(Object[] o)
+    {
+        StringBuffer buf = new StringBuffer();
+        // ignore first and last element
+        for (int i = 1; i < o.length - 1; i++)
+        {
+            buf.append(o[i]);
+        }
+        return buf.toString().replaceAll("\r", "");
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/DiffNode.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/DiffNode.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/DiffNode.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,59 @@
+package com.xpn.xwiki.wysiwyg.client.diff.myers;
+
+/**
+ * <p>Title: </p>
+ * <p>Description: </p>
+ * <p>Copyright: Copyright (c) 2002</p>
+ * <p>Company: </p>
+ * @author not attributable
+ * @version 1.0
+ */
+
+/**
+ * A diffnode in a diffpath.
+ * <p>
+ * A DiffNode and its previous node mark a delta between two input sequences,
+ * that is, two differing subsequences between (possibly zero length) matching
+ * sequences.
+ * 
+ * {@link DiffNode DiffNodes} and {@link Snake Snakes} allow for compression of
+ * diffpaths, as each snake is represented by a single {@link Snake Snake} node
+ * and each contiguous series of insertions and deletions is represented by a
+ * single {@link DiffNode DiffNodes}.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * 
+ */
+public final class DiffNode extends PathNode
+{
+    /**
+     * Constructs a DiffNode.
+     * <p>
+     * DiffNodes are compressed. That means that the path pointed to by the
+     * <code>prev</code> parameter will be followed using
+     * {@link PathNode#previousSnake} until a non-diff node is found.
+     * 
+     * @param the
+     *            position in the original sequence
+     * @param the
+     *            position in the revised sequence
+     * @param prev
+     *            the previous node in the path.
+     */
+    public DiffNode(int i, int j, PathNode prev)
+    {
+        super(i, j, (prev == null ? null : prev.previousSnake()));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @return false, always
+     */
+    public boolean isSnake()
+    {
+        return false;
+    }
+
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/MyersDiff.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/MyersDiff.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/MyersDiff.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,235 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff.myers;
+
+import com.xpn.xwiki.wysiwyg.client.diff.*;
+
+/**
+ * A clean-room implementation of <a
+ * href="http://www.cs.arizona.edu/people/gene/"> Eugene Myers</a> differencing
+ * algorithm.
+ * <p>
+ * See the paper at <a
+ * href="http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps">
+ * http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps</a>
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * @see Delta
+ * @see Revision
+ * @see Diff
+ */
+public class MyersDiff implements DiffAlgorithm
+{
+    /**
+     * Constructs an instance of the Myers differencing algorithm.
+     */
+    public MyersDiff()
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Revision diff(Object[] orig, Object[] rev)
+            throws DifferentiationFailedException
+    {
+        PathNode path = buildPath(orig, rev);
+        return buildRevision(path, orig, rev);
+    }
+
+    /**
+     * Computes the minimum diffpath that expresses de differences between the
+     * original and revised sequences, according to Gene Myers differencing
+     * algorithm.
+     * 
+     * @param orig
+     *            The original sequence.
+     * @param rev
+     *            The revised sequence.
+     * @return A minimum {@link PathNode Path} accross the differences graph.
+     * @throws DifferentiationFailedException
+     *             if a diff path could not be found.
+     */
+    public static PathNode buildPath(Object[] orig, Object[] rev)
+            throws DifferentiationFailedException
+    {
+        if (orig == null)
+            throw new IllegalArgumentException("original sequence is null");
+        if (rev == null)
+            throw new IllegalArgumentException("revised sequence is null");
+
+        // these are local constants
+        final int N = orig.length;
+        final int M = rev.length;
+
+        final int MAX = N + M + 1;
+        final int size = 1 + 2 * MAX;
+        final int middle = (size + 1) / 2;
+        final PathNode diagonal[] = new PathNode[size];
+
+        diagonal[middle + 1] = new Snake(0, -1, null);
+        for (int d = 0; d < MAX; d++)
+        {
+            for (int k = -d; k <= d; k += 2)
+            {
+                final int kmiddle = middle + k;
+                final int kplus = kmiddle + 1;
+                final int kminus = kmiddle - 1;
+                PathNode prev = null;
+
+                int i;
+                if ((k == -d)
+                        || (k != d && diagonal[kminus].i < diagonal[kplus].i))
+                {
+                    i = diagonal[kplus].i;
+                    prev = diagonal[kplus];
+                }
+                else
+                {
+                    i = diagonal[kminus].i + 1;
+                    prev = diagonal[kminus];
+                }
+
+                diagonal[kminus] = null; // no longer used
+
+                int j = i - k;
+
+                PathNode node = new DiffNode(i, j, prev);
+
+                // orig and rev are zero-based
+                // but the algorithm is one-based
+                // that's why there's no +1 when indexing the sequences
+                while (i < N && j < M && orig[i].equals(rev[j]))
+                {
+                    i++;
+                    j++;
+                }
+                if (i > node.i)
+                    node = new Snake(i, j, node);
+
+                diagonal[kmiddle] = node;
+
+                if (i >= N && j >= M)
+                {
+                    return diagonal[kmiddle];
+                }
+            }
+            diagonal[middle + d - 1] = null;
+
+        }
+        // According to Myers, this cannot happen
+        throw new DifferentiationFailedException("could not find a diff path");
+    }
+
+    /**
+     * Constructs a {@link Revision} from a difference path.
+     * 
+     * @param path
+     *            The path.
+     * @param orig
+     *            The original sequence.
+     * @param rev
+     *            The revised sequence.
+     * @return A {@link Revision} script corresponding to the path.
+     * @throws DifferentiationFailedException
+     *             if a {@link Revision} could not be built from the given path.
+     */
+    public static Revision buildRevision(PathNode path, Object[] orig,
+            Object[] rev)
+    {
+        if (path == null)
+            throw new IllegalArgumentException("path is null");
+        if (orig == null)
+            throw new IllegalArgumentException("original sequence is null");
+        if (rev == null)
+            throw new IllegalArgumentException("revised sequence is null");
+
+        Revision revision = new Revision();
+        if (path.isSnake())
+            path = path.prev;
+        while (path != null && path.prev != null && path.prev.j >= 0)
+        {
+            if (path.isSnake())
+                throw new IllegalStateException(
+                        "bad diffpath: found snake when looking for diff");
+            int i = path.i + 1;
+            int j = path.j + 1;
+            if (i>orig.length)
+                i--;
+            if (j>rev.length)
+                j--;
+
+            path = path.prev;
+            int ianchor = path.i - 1;
+            if (ianchor<0)
+             ianchor = 0;
+            int janchor = path.j - 1;
+            if (janchor<0)
+             janchor = 0;
+
+            Delta delta = Delta.newDelta(new Chunk(orig, ianchor, i - ianchor),
+                    new Chunk(rev, janchor, j - janchor));
+            revision.insertDelta(delta);
+            if (path.isSnake())
+                path = path.prev;
+        }
+        return revision;
+    }
+
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/PathNode.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/PathNode.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/PathNode.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,150 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff.myers;
+
+/**
+ * A node in a diffpath.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * 
+ * @see DiffNode
+ * @see Snake
+ * 
+ */
+public abstract class PathNode
+{
+    /** Position in the original sequence. */
+    public final int i;
+    /** Position in the revised sequence. */
+    public final int j;
+    /** The previous node in the path. */
+    public final PathNode prev;
+
+    /**
+     * Concatenates a new path node with an existing diffpath.
+     * 
+     * @param i
+     *            The position in the original sequence for the new node.
+     * @param j
+     *            The position in the revised sequence for the new node.
+     * @param prev
+     *            The previous node in the path.
+     */
+    public PathNode(int i, int j, PathNode prev)
+    {
+        this.i = i;
+        this.j = j;
+        this.prev = prev;
+    }
+
+    /**
+     * Is this node a {@link Snake Snake node}?
+     * 
+     * @return true if this is a {@link Snake Snake node}
+     */
+    public abstract boolean isSnake();
+
+    /**
+     * Is this a bootstrap node?
+     * <p>
+     * In bottstrap nodes one of the two corrdinates is less than zero.
+     * 
+     * @return tru if this is a bootstrap node.
+     */
+    public boolean isBootstrap()
+    {
+        return i < 0 || j < 0;
+    }
+
+    /**
+     * Skips sequences of {@link DiffNode DiffNodes} until a {@link Snake} or
+     * bootstrap node is found, or the end of the path is reached.
+     * 
+     * @return The next first {@link Snake} or bootstrap node in the path, or
+     *         <code>null</code> if none found.
+     */
+    public final PathNode previousSnake()
+    {
+        if (isBootstrap())
+            return null;
+        if (!isSnake() && prev != null)
+            return prev.previousSnake();
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer("[");
+        PathNode node = this;
+        while (node != null)
+        {
+            buf.append("(");
+            buf.append(Integer.toString(node.i));
+            buf.append(",");
+            buf.append(Integer.toString(node.j));
+            buf.append(")");
+            node = node.prev;
+        }
+        buf.append("]");
+        return buf.toString();
+    }
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/Snake.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/Snake.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/diff/myers/Snake.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,100 @@
+/*
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999-2003 The Apache Software Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ *    any, must include the following acknowledgement:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgement may appear in the software itself,
+ *    if and wherever such third-party acknowledgements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Commons", and "Apache Software
+ *    Foundation" must not be used to endorse or promote products derived
+ *    from this software without prior written permission. For written
+ *    permission, please contact apache at apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ *    nor may "Apache" appear in their names without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package com.xpn.xwiki.wysiwyg.client.diff.myers;
+
+/**
+ * Represents a snake in a diffpath.
+ * <p>
+ * 
+ * {@link DiffNode DiffNodes} and {@link Snake Snakes} allow for compression of
+ * diffpaths, as each snake is represented by a single {@link Snake Snake} node
+ * and each contiguous series of insertions and deletions is represented by a
+ * single {@link DiffNode DiffNodes}.
+ * 
+ * @version $Revision: 1.1 $ $Date: 2006/03/12 00:24:21 $
+ * @author <a href="mailto:juanco at suigeneris.org">Juanco Anez</a>
+ * 
+ */
+public final class Snake extends PathNode
+{
+    /**
+     * Constructs a snake node.
+     * 
+     * @param the
+     *            position in the original sequence
+     * @param the
+     *            position in the revised sequence
+     * @param prev
+     *            the previous node in the path.
+     */
+    public Snake(int i, int j, PathNode prev)
+    {
+        super(i, j, prev);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @return true always
+     */
+    public boolean isSnake()
+    {
+        return true;
+    }
+
+}
\ No newline at end of file

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DOMUtils.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DOMUtils.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DOMUtils.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,1213 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Node;
+
+/**
+ * Utility class providing methods for manipulating the DOM tree. Add here only the methods that work with any kind of
+ * DOM node and the methods that have different implementation for different browsers. For specific node types see
+ * {@link Document}, {@link Element} or {@link Text}.
+ * 
+ * @version $Id$
+ */
+public abstract class DOMUtils
+{
+    /**
+     * Common error message used when a particular node type is not supported by a method.
+     */
+    public static final String UNSUPPORTED_NODE_TYPE = "Unsupported node type!";
+
+    /**
+     * The list of all HTML elements that can have both in-line and block-level content, as specified by the XHTML 1.0
+     * strict DTD.
+     * <p>
+     * NOTE: We added the {@code body} and {@code blockquote} elements since the editor allows us to write text directly
+     * inside them. We also kept only the most important (used) flow containers to improve the search speed.
+     */
+    public static final String[] HTML_FLOW_CONTAINERS =
+        new String[] {"body", "li", "td", "th", "dd", "div", "blockquote"};
+
+    /**
+     * The list of all block-level HTML elements that can have only in-line content, as specified by the XHTML 1.0
+     * strict DTD.
+     * <p>
+     * NOTE: We kept only the most important (used) elements to improve the search speed.
+     */
+    public static final String[] HTML_BLOCK_LEVEL_INLINE_CONTAINERS =
+        new String[] {"p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "dt", "address"};
+
+    /**
+     * The list of all block-level HTML elements that can have only special content, or no content at all, as specified
+     * by the XHTML 1.0 strict DTD.
+     */
+    public static final String[] HTML_SPECIAL_BLOCK_LEVEL_ELEMENTS =
+        new String[] {"hr", "ul", "ol", "dl", "table", "tbody", "thead", "tfoot", "tr"};
+
+    /**
+     * The list of all HTML tags that must be empty. All of them appear as <code>&lt;tagName/&gt;</code> in the HTML
+     * code.<br/>
+     * NOTE: We had to move this array from Element because there is a problem with static field initialization for
+     * classes extending JavaScriptObject.
+     * 
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3192
+     */
+    public static final String[] HTML_EMPTY_TAGS =
+        new String[] {"area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "isindex", "link",
+            "meta", "param", "nextid", "bgsound", "embed", "keygen", "spacer", "wbr"};
+
+    /**
+     * Constant for the comment node type.
+     */
+    public static final short COMMENT_NODE = 8;
+
+    /**
+     * Constant for the CDATA node type.
+     */
+    public static final short CDATA_NODE = 4;
+
+    /**
+     * Constant for the DocumentFragment node type.
+     */
+    public static final short DOCUMENT_FRAGMENT_NODE = 11;
+
+    /**
+     * The instance in use.
+     */
+    private static DOMUtils instance;
+
+    /**
+     * NOTE: We use deferred binding because some of the methods don't have cross-browser implementation and we want to
+     * load the implementation specific to the browser used.
+     * 
+     * @return the instance in use.
+     */
+    public static synchronized DOMUtils getInstance()
+    {
+        if (instance == null) {
+            instance = GWT.create(DOMUtils.class);
+        }
+        return instance;
+    }
+
+    /**
+     * Returns the value of the specified CSS property for the given element as it is computed by the browser before it
+     * displays that element. The CSS property doesn't have to be applied explicitly or directly on the given element.
+     * It can be inherited or assumed by default on that element.
+     * 
+     * @param el the element for which we retrieve the property value.
+     * @param propertyName the name of the CSS property whose value is returned.
+     * @return the computed value of the specified CSS property for the given element.
+     */
+    public abstract String getComputedStyleProperty(Element el, String propertyName);
+
+    /**
+     * @param node the node from where to begin the search for the next leaf.
+     * @return the next leaf node in a deep-first search, considering we already looked in the subtree whose root is the
+     *         given node.
+     */
+    public Node getNextLeaf(Node node)
+    {
+        Node ancestor = node;
+        while (ancestor != null && ancestor.getNextSibling() == null) {
+            ancestor = ancestor.getParentNode();
+        }
+        if (ancestor == null) {
+            // There's no next leaf.
+            return null;
+        } else {
+            // Return the first leaf in the subtree whose root is the next sibling of the ancestor.
+            return getFirstLeaf(ancestor.getNextSibling());
+        }
+    }
+
+    /**
+     * @param range the range after which to look for a leaf
+     * @return the next leaf which is not touched by the specified range.
+     */
+    public Node getNextLeaf(Range range)
+    {
+        Node endContainer = range.getEndContainer();
+        if (endContainer.getNodeType() != Node.ELEMENT_NODE) {
+            // The node is either a text node or a comment node, return next leaf
+            return getNextLeaf(endContainer);
+        } else {
+            // the node is an element node, and the selection ends somewhere in between two child nodes. Check if it's
+            // at the end of it's parent and return the parent's next leaf or the first leaf of the next node otherwise
+            if (endContainer.hasChildNodes() && range.getEndOffset() < endContainer.getChildNodes().getLength()) {
+                // get the first leaf of the node after the end container
+                Node nodeAfter = endContainer.getChildNodes().getItem(range.getEndOffset());
+                return getFirstLeaf(nodeAfter);
+            } else {
+                return getNextLeaf(endContainer);
+            }
+        }
+    }
+
+    /**
+     * @param range the range before which to look for a leaf
+     * @return the previous leaf which is not touched by the specified range.
+     */
+    public Node getPreviousLeaf(Range range)
+    {
+        Node startContainer = range.getStartContainer();
+        if (startContainer.getNodeType() != Node.ELEMENT_NODE) {
+            return getPreviousLeaf(startContainer);
+        } else {
+            // the node is an element node and the selection begins somewhere in between two child nodes. Check if it's
+            // at the beginning of its parent and return the parent's previous leaf or the last leaf of the previous
+            // node otherwise.
+            if (startContainer.hasChildNodes() && range.getStartOffset() > 0) {
+                Node nodeBefore = startContainer.getChildNodes().getItem(range.getStartOffset() - 1);
+                return getLastLeaf(nodeBefore);
+            } else {
+                return getPreviousLeaf(startContainer);
+            }
+
+        }
+    }
+
+    /**
+     * @param node the node from where to begin the search for the previous leaf.
+     * @return the previous leaf node in a reverse deep-first search, considering we already looked in the subtree whose
+     *         root is the given node.
+     */
+    public Node getPreviousLeaf(Node node)
+    {
+        Node ancestor = node;
+        while (ancestor != null && ancestor.getPreviousSibling() == null) {
+            ancestor = ancestor.getParentNode();
+        }
+        if (ancestor == null) {
+            // There's no previous leaf.
+            return null;
+        } else {
+            // Return the last leaf in the subtree whose root is the next sibling of the ancestor.
+            return getLastLeaf(ancestor.getPreviousSibling());
+        }
+    }
+
+    /**
+     * @param node the root of the DOM subtree whose first leaf is returned.
+     * @return the first leaf node of the DOM subtree whose root is the given node.
+     */
+    public Node getFirstLeaf(Node node)
+    {
+        Node descendant = node;
+        while (descendant.hasChildNodes()) {
+            descendant = descendant.getFirstChild();
+        }
+        return descendant;
+    }
+
+    /**
+     * @param node the root of the DOM subtree whose last leaf is returned.
+     * @return the last leaf node of the DOM subtree whose root is the given node.
+     */
+    public Node getLastLeaf(Node node)
+    {
+        Node descendant = node;
+        while (descendant.hasChildNodes()) {
+            descendant = descendant.getLastChild();
+        }
+        return descendant;
+    }
+
+    /**
+     * @param node the node whose index is returned.
+     * @return the index of the given node among its siblings.
+     */
+    public int getNodeIndex(Node node)
+    {
+        int count = 0;
+        Node leftSibling = node.getPreviousSibling();
+        Node rightSibling = node.getNextSibling();
+        while (leftSibling != null && rightSibling != null) {
+            count++;
+            leftSibling = leftSibling.getPreviousSibling();
+            rightSibling = rightSibling.getNextSibling();
+        }
+        if (leftSibling == null) {
+            return count;
+        } else {
+            return node.getParentNode().getChildNodes().getLength() - 1 - count;
+        }
+    }
+
+    /**
+     * Computes the index that can be used with <code>getChildNodes().getItem()</code> to retrieve the given node from
+     * its parent after the parent is serialized and deserialized.
+     * 
+     * @param node a DOM node
+     * @return the index of the given DOM node among its siblings, considering successive text nodes as one single node
+     *         and ignoring hidden siblings
+     */
+    public int getNormalizedNodeIndex(Node node)
+    {
+        int count = 0;
+        Node sibling = node;
+        while (sibling != null) {
+            Node left = sibling.getPreviousSibling();
+            if (sibling.getNodeType() == Node.TEXT_NODE) {
+                while (left != null && (left.getNodeType() == Node.TEXT_NODE || !isSerializable(left))) {
+                    left = left.getPreviousSibling();
+                }
+            } else {
+                while (left != null && !isSerializable(left)) {
+                    left = left.getPreviousSibling();
+                }
+            }
+            count += (left != null) ? 1 : 0;
+            sibling = left;
+        }
+        return count;
+    }
+
+    /**
+     * Specifies if a node's HTML serialization is included in its parent node's HTML serialization. Normally the inner
+     * HTML of an element includes the HTML representation of all of its descendants. This is not the case when one of
+     * the descendants is an element with an empty meta data associated. Associating empty meta data to an element is a
+     * way to hide that element from the inner HTML of his ancestors.
+     * 
+     * @param node a DOM node
+     * @return true if the given node is represented in its parent inner HTML
+     */
+    public boolean isSerializable(Node node)
+    {
+        switch (node.getNodeType()) {
+            case Node.TEXT_NODE:
+                return node.getNodeValue().length() > 0;
+            case Node.ELEMENT_NODE:
+                Element element = (Element) node;
+                return !element.hasAttribute(Element.META_DATA_ATTR)
+                    || !"".equals(element.xGetAttribute(Element.META_DATA_ATTR));
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * @param node a DOM node.
+     * @return the child count for the given DOM node, considering successive child text nodes as one single child.
+     * @see #getNormalizedNodeIndex(Node)
+     */
+    public int getNormalizedChildCount(Node node)
+    {
+        if (!node.hasChildNodes()) {
+            return 0;
+        } else {
+            Node last = node.getLastChild();
+            return (isSerializable(last) ? 1 : 0) + getNormalizedNodeIndex(last);
+        }
+    }
+
+    /**
+     * @param node a DOM node
+     * @return {@code true} if the given DOM node represents in-line content
+     */
+    public boolean isInline(Node node)
+    {
+        return !isBlock(node);
+    }
+
+    /**
+     * @param node a DOM node
+     * @return {@code true} if the given node is a block-level element, {@code false} otherwise
+     */
+    public boolean isBlock(Node node)
+    {
+        return isFlowContainer(node) || isBlockLevelInlineContainer(node) || isSpecialBlock(node);
+    }
+
+    /**
+     * @param node a DOM node
+     * @return {@code true} if the given node is a block-level element that can have only in-line content, {@code false}
+     *         otherwise
+     * @see #HTML_BLOCK_LEVEL_INLINE_CONTAINERS
+     */
+    public boolean isBlockLevelInlineContainer(Node node)
+    {
+        if (node.getNodeType() != Node.ELEMENT_NODE) {
+            return false;
+        }
+        String tagName = node.getNodeName().toLowerCase();
+        for (int i = 0; i < HTML_BLOCK_LEVEL_INLINE_CONTAINERS.length; i++) {
+            if (tagName.equals(HTML_BLOCK_LEVEL_INLINE_CONTAINERS[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param node a DOM node
+     * @return {@code true} if the given node is a block-level element that can have only special content, {@code false}
+     *         otherwise
+     * @see #HTML_SPECIAL_BLOCK_LEVEL_ELEMENTS
+     */
+    public boolean isSpecialBlock(Node node)
+    {
+        if (node.getNodeType() != Node.ELEMENT_NODE) {
+            return false;
+        }
+        String tagName = node.getNodeName().toLowerCase();
+        for (int i = 0; i < HTML_SPECIAL_BLOCK_LEVEL_ELEMENTS.length; i++) {
+            if (tagName.equals(HTML_SPECIAL_BLOCK_LEVEL_ELEMENTS[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Computes the longest text range included in the specified range. By text range we understand any range that
+     * starts and ends in a text node. The end points of a text range can be in different text nodes.
+     * 
+     * @param range any range
+     * @return the longest text range included in the given range.
+     */
+    public Range getTextRange(Range range)
+    {
+        Range textRange = range.cloneRange();
+        Node firstLeaf = getFirstLeaf(range);
+        if (firstLeaf != null) {
+            Node lastLeaf = getLastLeaf(range);
+            // Find the first text node in the range and start the range there.
+            while (firstLeaf != lastLeaf && firstLeaf.getNodeType() != Node.TEXT_NODE) {
+                firstLeaf = getNextLeaf(firstLeaf);
+            }
+            if (firstLeaf.getNodeType() == Node.TEXT_NODE && firstLeaf != textRange.getStartContainer()) {
+                textRange.setStart(firstLeaf, 0);
+            }
+            // Find the last text node in the range and end the range there.
+            while (lastLeaf != firstLeaf && lastLeaf.getNodeType() != Node.TEXT_NODE) {
+                lastLeaf = getPreviousLeaf(lastLeaf);
+            }
+            if (lastLeaf.getNodeType() == Node.TEXT_NODE && lastLeaf != textRange.getEndContainer()) {
+                textRange.setEnd(lastLeaf, lastLeaf.getNodeValue().length());
+            }
+        }
+        return textRange;
+    }
+
+    /**
+     * Creates a copy of a node from an external document that can be inserted into the given document.
+     * 
+     * @param doc The document in which we want to insert the returned copy.
+     * @param externalNode The node from another document to be imported.
+     * @param deep Indicates whether the children of the given node need to be imported.
+     * @return a copy of the given node that can be inserted into the specified document.
+     */
+    public abstract Node importNode(Document doc, Node externalNode, boolean deep);
+
+    /**
+     * @param element The DOM element whose attribute names are returned.
+     * @return The names of DOM attributes present on the given element.
+     */
+    public abstract JsArrayString getAttributeNames(Element element);
+
+    /**
+     * Searches for the first ancestor with the name <code>tagName</code> of the passed node, including the node itself.
+     * The search order starts with <code>node</code> and continued to the root of the tree.
+     * 
+     * @param node the node to find ancestor for
+     * @param tagName the tag name to look for up in the DOM tree.
+     * @return the first node with name <code>tagName</code> found.
+     */
+    public Node getFirstAncestor(Node node, String tagName)
+    {
+        Node parent = node;
+        // While there is a parent
+        while (parent != null) {
+            // Check if this node is the needed element
+            if (parent.getNodeType() == Node.ELEMENT_NODE && parent.getNodeName().equalsIgnoreCase(tagName)) {
+                return parent;
+            }
+            parent = parent.getParentNode();
+        }
+        return null;
+    }
+
+    /**
+     * Searches for the first element descendant with the name <code>tagName</code>. Searching is done in a DFS order
+     * with node processing on first pass through them.
+     * 
+     * @param node the node to start the search from
+     * @param tagName the name of the searched element
+     * @return the first descendant of type <code>tagName</code> of the passed node, in DFS order.
+     */
+    public Node getFirstDescendant(Node node, String tagName)
+    {
+        Iterator<Node> it = ((Document) node.getOwnerDocument()).getIterator(node);
+        while (it.hasNext()) {
+            Node currentNode = it.next();
+            if (currentNode.getNodeType() == Node.ELEMENT_NODE && currentNode.getNodeName().equalsIgnoreCase(tagName)) {
+                return currentNode;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helps setting the inner HTML for an element, in a cross-browser manner, because IE seems to trim leading comments
+     * in the inner HTML.
+     * 
+     * @param element element to set the inner HTML for
+     * @param html the HTML string to set
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3146
+     */
+    public abstract void setInnerHTML(Element element, String html);
+
+    /**
+     * @param alice A DOM node.
+     * @param bob A DOM node.
+     * @return The nearest common ancestor of the given nodes.
+     */
+    public Node getNearestCommonAncestor(Node alice, Node bob)
+    {
+        if (alice == bob) {
+            return alice;
+        }
+
+        // Build the chain of parents
+        List<Node> aliceAncestors = getAncestors(alice);
+        List<Node> bobAncestors = getAncestors(bob);
+
+        // Find where the parent chain differs
+        int count = Math.min(aliceAncestors.size(), bobAncestors.size());
+        int aliceIndex = aliceAncestors.size();
+        int bobIndex = bobAncestors.size();
+        Node ancestor = null;
+        while (count-- > 0 && aliceAncestors.get(--aliceIndex) == bobAncestors.get(--bobIndex)) {
+            ancestor = aliceAncestors.get(aliceIndex);
+        }
+        return ancestor;
+    }
+
+    /**
+     * @param node a DOM node
+     * @return the list of ancestors of the given node, starting with it
+     */
+    public List<Node> getAncestors(Node node)
+    {
+        if (node == null) {
+            return Collections.emptyList();
+        }
+        List<Node> ancestors = new ArrayList<Node>();
+        Node ancestor = node;
+        do {
+            ancestors.add(ancestor);
+            ancestor = ancestor.getParentNode();
+        } while (ancestor != null);
+        return ancestors;
+    }
+
+    /**
+     * Clones the contents of the given node. If node type is text, CDATA or comment then only the data between
+     * startOffset (including) and endOffset is kept. If node type is element then only the child nodes with indexes
+     * between startOffset (including) and endOffset are included in the document fragment returned.
+     * 
+     * @param node The DOM node whose contents will be cloned.
+     * @param startOffset the index of the first child to clone or the first character to include in the cloned
+     *            contents.
+     * @param endOffset specifies where the cloned contents end.
+     * @return the cloned contents of the given node, between start offset and end offset.
+     */
+    public DocumentFragment cloneNodeContents(Node node, int startOffset, int endOffset)
+    {
+        DocumentFragment contents = ((Document) node.getOwnerDocument()).createDocumentFragment();
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                if (startOffset < endOffset) {
+                    Node clone = node.cloneNode(false);
+                    clone.setNodeValue(node.getNodeValue().substring(startOffset, endOffset));
+                    contents.appendChild(clone);
+                }
+                break;
+            case Node.ELEMENT_NODE:
+                for (int i = startOffset; i < endOffset; i++) {
+                    contents.appendChild(node.getChildNodes().getItem(i).cloneNode(true));
+                }
+                break;
+            default:
+                // ignore
+        }
+        return contents;
+    }
+
+    /**
+     * Clones the given DOM node, keeping only the contents between start and end offset. If node type is text, CDATA or
+     * comment then both offsets represent character indexes. Otherwise they represent child indexes.
+     * 
+     * @param node The DOM node to be cloned.
+     * @param startOffset specifies where to start the cloning.
+     * @param endOffset specifies where to end the cloning.
+     * @return A clone of the given node, containing only the contents between start and end offset.
+     */
+    public Node cloneNode(Node node, int startOffset, int endOffset)
+    {
+        Node clone = node.cloneNode(false);
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                clone.setNodeValue(node.getNodeValue().substring(startOffset, endOffset));
+                return clone;
+            case Node.ELEMENT_NODE:
+                for (int i = startOffset; i < endOffset; i++) {
+                    clone.appendChild(node.getChildNodes().getItem(i).cloneNode(true));
+                }
+                return clone;
+            default:
+                throw new IllegalArgumentException(UNSUPPORTED_NODE_TYPE);
+        }
+    }
+
+    /**
+     * @param node A DOM node.
+     * @return the number of characters if the given node is a text, a CDATA section or a comment. Otherwise the
+     *         returned value is the number of child nodes.
+     */
+    public int getLength(Node node)
+    {
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                return node.getNodeValue().length();
+            default:
+                return node.getChildNodes().getLength();
+        }
+    }
+
+    /**
+     * Clones the left or right side of the subtree rooted in the given node.
+     * 
+     * @param node The root of the subtree whose left or right side will be cloned.
+     * @param offset Marks the boundary between the left and the right subtrees. It can be either a character index or a
+     *            child index, depending on the type of the given node.
+     * @param left Specifies which of the subtrees to be cloned.
+     * @return The clone of the specified subtree.
+     */
+    public Node cloneNode(Node node, int offset, boolean left)
+    {
+        return left ? cloneNode(node, 0, offset) : cloneNode(node, offset, getLength(node));
+    }
+
+    /**
+     * Clones the node specified by its parent and its descendant, including only the left or right part of the tree
+     * whose separator is the path from the given descendant to the parent of the cloned node.
+     * 
+     * @param parent The parent of the cloned node.
+     * @param descendant A descendant of the cloned node.
+     * @param offset The offset within the given descendant. It can be either a character index or a child index
+     *            depending on the descendant node type.
+     * @param left Specifies which subtree to be cloned. Left and right subtrees are delimited by the path from the
+     *            given descendant to the parent of the cloned node.
+     * @return The clone of the specified subtree.
+     */
+    public Node cloneNode(Node parent, Node descendant, int offset, boolean left)
+    {
+        int delta = left ? 0 : 1;
+        int index = getNodeIndex(descendant) + delta;
+        Node clone = cloneNode(descendant, offset, left);
+        Node node = descendant.getParentNode();
+        while (node != parent) {
+            Node child = clone;
+            clone = cloneNode(node, index, left);
+            if (left || clone.getFirstChild() == null) {
+                clone.appendChild(child);
+            } else {
+                clone.insertBefore(child, clone.getFirstChild());
+            }
+            index = getNodeIndex(node) + delta;
+            node = node.getParentNode();
+        }
+        return clone;
+    }
+
+    /**
+     * @param parent the parent node of the retrieved child
+     * @param descendant a descendant of the retrieved child
+     * @return the child of the given parent, which has the specified descendant
+     */
+    public Node getChild(Node parent, Node descendant)
+    {
+        Node child = descendant;
+        while (child != null && child.getParentNode() != parent) {
+            child = child.getParentNode();
+        }
+        return child;
+    }
+
+    /**
+     * Inserts the given child node after the reference node.
+     * 
+     * @param newChild The child node to be inserted.
+     * @param refChild The reference node.
+     */
+    public void insertAfter(Node newChild, Node refChild)
+    {
+        if (refChild.getNextSibling() != null) {
+            refChild.getParentNode().insertBefore(newChild, refChild.getNextSibling());
+        } else {
+            refChild.getParentNode().appendChild(newChild);
+        }
+    }
+
+    /**
+     * Deletes the contents of the given node between the specified offsets. If node type is text, CDATA or comment then
+     * only the data between startOffset (including) and endOffset is deleted. If node type is element then only the
+     * child nodes with indexes between startOffset (including) and endOffset are deleted.
+     * 
+     * @param node The DOM node whose contents will be deleted.
+     * @param startOffset the index of the first child or the first character to delete, depending on node type.
+     * @param endOffset specifies where to stop deleting content.
+     */
+    public void deleteNodeContents(Node node, int startOffset, int endOffset)
+    {
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                if (startOffset < endOffset) {
+                    node.setNodeValue(node.getNodeValue().substring(0, startOffset)
+                        + node.getNodeValue().substring(endOffset));
+                }
+                break;
+            case Node.ELEMENT_NODE:
+                for (int i = startOffset; i < endOffset; i++) {
+                    node.removeChild(node.getChildNodes().getItem(startOffset));
+                }
+                break;
+            default:
+                // ignore
+        }
+    }
+
+    /**
+     * Deletes the left or right side of the subtree rooted in the given node.
+     * 
+     * @param node The root of the subtree whose left or right side will be deleted.
+     * @param offset Marks the boundary between the left and the right subtrees. It can be either a character index or a
+     *            child index, depending on the type of the given node.
+     * @param left Specifies which of the subtrees to be deleted.
+     */
+    public void deleteNodeContents(Node node, int offset, boolean left)
+    {
+        if (left) {
+            deleteNodeContents(node, 0, offset);
+        } else {
+            deleteNodeContents(node, offset, getLength(node));
+        }
+    }
+
+    /**
+     * Deletes left or right siblings of the given node.
+     * 
+     * @param node The DOM node whose left or right siblings will be deleted.
+     * @param left Specifies which siblings to delete.
+     */
+    public void deleteSiblings(Node node, boolean left)
+    {
+        Node sibling = left ? node.getPreviousSibling() : node.getNextSibling();
+        while (sibling != null) {
+            node.getParentNode().removeChild(sibling);
+            sibling = left ? node.getPreviousSibling() : node.getNextSibling();
+        }
+    }
+
+    /**
+     * Given a subtree specified by its root parent and one of the inner nodes, this method deletes the left or right
+     * part delimited by the path from the given descendant (inner node) to the root parent.
+     * 
+     * @param parent The parent node of the subtree's root.
+     * @param descendant An inner node within the specified subtree.
+     * @param offset The offset within the given descendant. It can be either a character index or a child index
+     *            depending on the descendant node type.
+     * @param left Specifies which side of the subtree to be deleted. Left and right parts are delimited by the path
+     *            from the given descendant to the parent of the subtree's root.
+     */
+    public void deleteNodeContents(Node parent, Node descendant, int offset, boolean left)
+    {
+        deleteNodeContents(descendant, offset, left);
+        Node node = descendant;
+        while (node.getParentNode() != parent) {
+            deleteSiblings(node, left);
+            node = node.getParentNode();
+        }
+    }
+
+    /**
+     * Splits the given DOM node at the specified offset.
+     * 
+     * @param node The node to be split.
+     * @param offset Specifies where to split. It can be either a character index or a child index depending on node
+     *            type.
+     * @return The node resulted after the split. It should be the next sibling of the given node.
+     */
+    public Node splitNode(Node node, int offset)
+    {
+        Node clone = node.cloneNode(false);
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                clone.setNodeValue(node.getNodeValue().substring(offset));
+                node.setNodeValue(node.getNodeValue().substring(0, offset));
+                break;
+            case Node.ELEMENT_NODE:
+                Element.as(clone).removeAttribute("id");
+                for (int i = node.getChildNodes().getLength(); i > offset; i--) {
+                    clone.appendChild(node.getChildNodes().getItem(offset));
+                }
+                break;
+            default:
+                throw new IllegalArgumentException(UNSUPPORTED_NODE_TYPE);
+        }
+        insertAfter(clone, node);
+        return clone;
+    }
+
+    /**
+     * Given a subtree specified by its root parent and one of the inner nodes, this method splits the subtree by the
+     * path from the given descendant (inner node) to the root parent.
+     * 
+     * @param parent The parent node of the subtree's root.
+     * @param descendant An inner node within the specified subtree.
+     * @param offset The offset within the given descendant. It can be either a character index or a child index
+     *            depending on the descendant node type.
+     * @return The node resulted from splitting the descendant.
+     */
+    public Node splitNode(Node parent, Node descendant, int offset)
+    {
+        if (descendant == parent) {
+            return descendant;
+        }
+        Node nextLevelSibling = splitNode(descendant, offset);
+        Node node = descendant;
+        while (node.getParentNode() != parent) {
+            splitNode(node.getParentNode(), getNodeIndex(node) + 1);
+            node = node.getParentNode();
+        }
+        return nextLevelSibling;
+    }
+
+    /**
+     * Given a subtree specified by its root parent and one of the inner nodes, this method splits the subtree by the
+     * path from the given descendant (inner node) to the root parent. Additionally to what
+     * {@link #splitNode(Node, Node, int)}) does this method ensures that both subtrees are editable in design mode.
+     * This method is required because some browsers like Firefox prevent the user from placing the caret inside empty
+     * block elements such as paragraphs or headers. This empty block elements can be obtained by splitting at the
+     * beginning or at the end of such a block element.
+     * 
+     * @param parent the parent node of the subtree's root
+     * @param descendant an inner node within the specified subtree
+     * @param offset the offset within the given descendant. It can be either a character index or a child index
+     *            depending on the descendant node type.
+     * @return the node resulted from splitting the descendant
+     * @see #splitNode(Node, Node, int)
+     */
+    public Node splitHTMLNode(Node parent, Node descendant, int offset)
+    {
+        // By default we just do the split because browsers shouldn't require any further adjustments.
+        // Those who do require adjustments should overwrite this method.
+        return splitNode(parent, descendant, offset);
+    }
+
+    /**
+     * @param node A DOM node.
+     * @return true is the given node is an element that can have both in-line and block content.
+     */
+    public boolean isFlowContainer(Node node)
+    {
+        if (node.getNodeType() != Node.ELEMENT_NODE) {
+            return false;
+        }
+        String tagName = node.getNodeName().toLowerCase();
+        for (int i = 0; i < HTML_FLOW_CONTAINERS.length; i++) {
+            if (tagName.equals(HTML_FLOW_CONTAINERS[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param innerNode A DOM node.
+     * @return The nearest ancestor of the given node that can contain both in-line and block content.
+     */
+    public Node getNearestFlowContainer(Node innerNode)
+    {
+        Node node = innerNode;
+        while (node != null && !isFlowContainer(node)) {
+            node = node.getParentNode();
+        }
+        return node;
+    }
+
+    /**
+     * Inserts a node at the specified index under the given parent.
+     * 
+     * @param parent The parent node which will adopt the given node.
+     * @param newChild The node to be inserted.
+     * @param index Specifies the position inside the parent node where the new child should be placed.
+     */
+    public void insertAt(Node parent, Node newChild, int index)
+    {
+        int i = Math.max(0, index);
+        if (i >= parent.getChildNodes().getLength()) {
+            parent.appendChild(newChild);
+        } else {
+            parent.insertBefore(newChild, parent.getChildNodes().getItem(i));
+        }
+    }
+
+    /**
+     * Walks from the given node up to the root of the DOM tree as long as the ancestors represent in-line content.
+     * Returns the last node in the walk.
+     * 
+     * @param node a DOM node
+     * @return the farthest ancestor of the given node that represents in-line content
+     */
+    public Node getFarthestInlineAncestor(Node node)
+    {
+        Node ancestor = node;
+        Node inlineAncestor = null;
+        while (ancestor != null && isInline(ancestor)) {
+            inlineAncestor = ancestor;
+            ancestor = ancestor.getParentNode();
+        }
+        return inlineAncestor;
+    }
+
+    /**
+     * @param range A DOM range.
+     * @return the first leaf node that is partially or entirely included in the given range.
+     */
+    public Node getFirstLeaf(Range range)
+    {
+        if (range.getStartContainer().hasChildNodes()) {
+            if (range.isCollapsed()) {
+                return null;
+            } else if (range.getStartOffset() >= range.getStartContainer().getChildNodes().getLength()) {
+                return getNextLeaf(range.getStartContainer());
+            } else {
+                return getFirstLeaf(range.getStartContainer().getChildNodes().getItem(range.getStartOffset()));
+            }
+        } else {
+            return range.getStartContainer();
+        }
+    }
+
+    /**
+     * @param range A DOM range.
+     * @return the last leaf node that is partially or entirely included in the given range.
+     */
+    public Node getLastLeaf(Range range)
+    {
+        if (range.getEndContainer().hasChildNodes()) {
+            if (range.isCollapsed()) {
+                return null;
+            } else if (range.getEndOffset() == 0) {
+                return getPreviousLeaf(range.getEndContainer());
+            } else {
+                return getLastLeaf(range.getEndContainer().getChildNodes().getItem(range.getEndOffset() - 1));
+            }
+        } else {
+            return range.getEndContainer();
+        }
+    }
+
+    /**
+     * Removes the given node from its parent.
+     * 
+     * @param node A DOM node.
+     */
+    public void detach(Node node)
+    {
+        if (node != null && node.getParentNode() != null) {
+            node.getParentNode().removeChild(node);
+        }
+    }
+
+    /**
+     * @param node A DOM node
+     * @return The nearest block level ancestor of the given node.
+     */
+    public Node getNearestBlockContainer(Node node)
+    {
+        Node ancestor = DOMUtils.getInstance().getFarthestInlineAncestor(node);
+        if (ancestor == null) {
+            return node;
+        } else {
+            return ancestor.getParentNode();
+        }
+    }
+
+    /**
+     * Returns the value of the named attribute of the specified element. This method will be overwritten for internet
+     * explorer browsers to handle bug http://code.google.com/p/google-web-toolkit/issues/detail?id=3238 .
+     * 
+     * @param element the element to get the attribute for
+     * @param name the name of the attribute to return
+     * @return the value of the attribute
+     */
+    public String getAttribute(Element element, String name)
+    {
+        return element.getAttribute(name);
+    }
+
+    /**
+     * Compares two points in a {@link Document}. Each point is specified by a DOM node and an offset within that node.
+     * 
+     * @param alice first point's node
+     * @param aliceOffset first point's offset
+     * @param bob second point's node
+     * @param bobOffset second point's offset
+     * @return -1, 0 or 1 depending on whether the first point is respectively before, equal to, or after the second
+     *         point
+     */
+    public short comparePoints(Node alice, int aliceOffset, Node bob, int bobOffset)
+    {
+        if (alice == bob) {
+            return (short) (aliceOffset < bobOffset ? -1 : aliceOffset > bobOffset ? 1 : 0);
+        }
+
+        // Build the chain of parents.
+        List<Node> aliceAncestors = getAncestors(alice);
+        List<Node> bobAncestors = getAncestors(bob);
+
+        // Test is the input nodes are disconnected.
+        int aliceIndex = aliceAncestors.size() - 1;
+        int bobIndex = bobAncestors.size() - 1;
+        if (aliceAncestors.get(aliceIndex) != bobAncestors.get(bobIndex)) {
+            throw new IllegalArgumentException();
+        }
+
+        // Find where the parent chain differs.
+        for (int count = Math.min(aliceIndex, bobIndex); count > 0; --count) {
+            Node aliceAncestor = aliceAncestors.get(--aliceIndex);
+            Node bobAncestor = bobAncestors.get(--bobIndex);
+            if (aliceAncestor != bobAncestor) {
+                return (short) (getNodeIndex(aliceAncestor) < getNodeIndex(bobAncestor) ? -1 : 1);
+            }
+        }
+
+        // The parent chains never differed, so one of the nodes is an ancestor of the other.
+        if (aliceIndex == 0) {
+            Node bobAncestor = bobAncestors.get(--bobIndex);
+            return (short) (aliceOffset <= getNodeIndex(bobAncestor) ? -1 : 1);
+        }
+
+        Node aliceAncestor = aliceAncestors.get(--aliceIndex);
+        return (short) (getNodeIndex(aliceAncestor) < bobOffset ? -1 : 1);
+    }
+
+    /**
+     * We need our own implementation because the one provided by GWT includes commented text in the output.
+     * 
+     * @param element the element whose inner text to return
+     * @return the text between the start and end tags of the given element
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3275
+     */
+    public String getInnerText(Element element)
+    {
+        // To mimic IE's 'innerText' property in the W3C DOM, we need to recursively
+        // concatenate all child text nodes (depth first).
+        StringBuffer text = new StringBuffer();
+        Node child = element.getFirstChild();
+        while (child != null) {
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                text.append(getInnerText((Element) child));
+            } else if (child.getNodeType() == Node.TEXT_NODE) {
+                text.append(child.getNodeValue());
+            }
+            child = child.getNextSibling();
+        }
+        return text.toString();
+    }
+
+    /**
+     * @param element a DOM element
+     * @param attrName a string representing the name of an attribute
+     * @return true is the given element has an attribute with the specified name, false otherwise
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=2852
+     */
+    public native boolean hasAttribute(Element element, String attrName)
+    /*-{
+        return element.hasAttribute(attrName);
+    }-*/;
+
+    /**
+     * Extracts the contents of the given node. If node type is text, CDATA or comment then only the data between
+     * startOffset (including) and endOffset is kept. If node type is element then only the child nodes with indexes
+     * between startOffset (including) and endOffset are included in the document fragment returned.
+     * 
+     * @param node the DOM node whose contents will be extracted
+     * @param startOffset the index of the first child to extract or the first character to include in the extracted
+     *            contents
+     * @param endOffset specifies where the extracted contents end
+     * @return the extracted contents of the given node, between start offset and end offset
+     */
+    public DocumentFragment extractNodeContents(Node node, int startOffset, int endOffset)
+    {
+        DocumentFragment contents = ((Document) node.getOwnerDocument()).createDocumentFragment();
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                if (startOffset < endOffset) {
+                    Node clone = node.cloneNode(false);
+                    clone.setNodeValue(node.getNodeValue().substring(startOffset, endOffset));
+                    contents.appendChild(clone);
+                    node.setNodeValue(node.getNodeValue().substring(0, startOffset)
+                        + node.getNodeValue().substring(endOffset));
+                }
+                break;
+            case Node.ELEMENT_NODE:
+                for (int i = startOffset; i < endOffset; i++) {
+                    contents.appendChild(node.getChildNodes().getItem(startOffset));
+                }
+                break;
+            default:
+                // ignore
+        }
+        return contents;
+    }
+
+    /**
+     * Extracts the node specified by its parent and its descendant, including only the left or right part of the tree
+     * whose separator is the path from the given descendant to the parent of the extracted node.
+     * 
+     * @param parent the parent of the extracted node
+     * @param descendant a descendant of the extracted node
+     * @param offset the offset within the given descendant. It can be either a character index or a child index
+     *            depending on the descendant node type.
+     * @param left specifies which subtree to be extracted. Left and right subtrees are delimited by the path from the
+     *            given descendant to the parent of the extracted node.
+     * @return the extracted subtree
+     */
+    public Node extractNode(Node parent, Node descendant, int offset, boolean left)
+    {
+        int delta = left ? 0 : 1;
+        int index = getNodeIndex(descendant) + delta;
+        Node clone = extractNode(descendant, offset, left);
+        Node node = descendant.getParentNode();
+        while (node != parent) {
+            Node child = clone;
+            clone = extractNode(node, index, left);
+            if (left || clone.getFirstChild() == null) {
+                clone.appendChild(child);
+            } else {
+                clone.insertBefore(child, clone.getFirstChild());
+            }
+            index = getNodeIndex(node) + delta;
+            node = node.getParentNode();
+        }
+        return clone;
+    }
+
+    /**
+     * Extracts the left or right side of the subtree rooted in the given node.
+     * 
+     * @param node the root of the subtree whose left or right side will be extracted
+     * @param offset marks the boundary between the left and the right subtrees. It can be either a character index or a
+     *            child index, depending on the type of the given node.
+     * @param left specifies which of the subtrees to be extracted
+     * @return the extracted subtree
+     */
+    public Node extractNode(Node node, int offset, boolean left)
+    {
+        return left ? extractNode(node, 0, offset) : extractNode(node, offset, getLength(node));
+    }
+
+    /**
+     * Extracts the given DOM node, keeping only the contents between start and end offset. If node type is text, CDATA
+     * or comment then both offsets represent character indexes. Otherwise they represent child indexes.
+     * 
+     * @param node the DOM node to be extracted
+     * @param startOffset specifies where to start the extraction
+     * @param endOffset specifies where to end the extraction
+     * @return a shallow clone of the given node, containing only the contents between start and end offset, which have
+     *         been extracted from the input node
+     */
+    public Node extractNode(Node node, int startOffset, int endOffset)
+    {
+        Node clone = node.cloneNode(false);
+        switch (node.getNodeType()) {
+            case CDATA_NODE:
+            case COMMENT_NODE:
+            case Node.TEXT_NODE:
+                clone.setNodeValue(node.getNodeValue().substring(startOffset, endOffset));
+                node.setNodeValue(node.getNodeValue().substring(0, startOffset)
+                    + node.getNodeValue().substring(endOffset));
+                return clone;
+            case Node.ELEMENT_NODE:
+                for (int i = startOffset; i < endOffset; i++) {
+                    clone.appendChild(node.getChildNodes().getItem(startOffset));
+                }
+                return clone;
+            default:
+                throw new IllegalArgumentException(UNSUPPORTED_NODE_TYPE);
+        }
+    }
+
+    /**
+     * @param range a DOM range
+     * @return the node that follows after the end point of the given range, in a depth-first pre-order search
+     */
+    public Node getNextNode(Range range)
+    {
+        Node node = range.getEndContainer();
+        if (node.hasChildNodes() && range.getEndOffset() < node.getChildNodes().getLength()) {
+            return node.getChildNodes().getItem(range.getEndOffset());
+        }
+        while (node != null && node.getNextSibling() == null) {
+            node = node.getParentNode();
+        }
+        return node == null ? null : node.getNextSibling();
+    }
+
+    /**
+     * @param range a DOM range
+     * @return the node that precedes the start point of the given range, in a depth-first pre-order search
+     */
+    public Node getPreviousNode(Range range)
+    {
+        Node node = range.getStartContainer();
+        if (node.hasChildNodes() && range.getStartOffset() > 0) {
+            return node.getChildNodes().getItem(range.getStartOffset() - 1);
+        }
+        while (node != null && node.getPreviousSibling() == null) {
+            node = node.getParentNode();
+        }
+        return node == null ? null : node.getPreviousSibling();
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DepthFirstPreOrderIterator.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DepthFirstPreOrderIterator.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DepthFirstPreOrderIterator.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,103 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * Iterator for the depth-first pre-order strategy, starting in a specified node.
+ * 
+ * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-Document
+ * @version $Id$
+ */
+public class DepthFirstPreOrderIterator implements Iterator<Node>
+{
+    /**
+     * The current position of the iterator.
+     */
+    private Node currentNode;
+
+    /**
+     * The node where the iteration has started (the root of the subtree which we're iterating).
+     */
+    private Node startNode;
+
+    /**
+     * Creates an iterator for the subtree rooted in startNode.
+     * 
+     * @param startNode root of the subtree to iterate through.
+     */
+    public DepthFirstPreOrderIterator(Node startNode)
+    {
+        this.startNode = startNode;
+        this.currentNode = startNode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+        return this.currentNode != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Node next()
+    {
+        // return the currentNode
+        Node nodeToReturn = this.currentNode;
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        // compute the next node
+        // try to go down
+        if (currentNode.getFirstChild() != null) {
+            this.currentNode = currentNode.getFirstChild();
+        } else {
+            // try to go right: from this node or any of its ancestors, until we haven't reached the startNode
+            Node ancestor = currentNode;
+            while (ancestor != startNode) {
+                if (ancestor.getNextSibling() != null) {
+                    this.currentNode = ancestor.getNextSibling();
+                    break;
+                }
+                ancestor = ancestor.getParentNode();
+            }
+            // if we got back to the root searching up, then we have no more options
+            if (ancestor == startNode) {
+                this.currentNode = null;
+            }
+        }
+        return nodeToReturn;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+        throw new UnsupportedOperationException();
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Document.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Document.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Document.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,411 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.BRElement;
+import com.google.gwt.dom.client.BaseElement;
+import com.google.gwt.dom.client.ButtonElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.HRElement;
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.LIElement;
+import com.google.gwt.dom.client.LinkElement;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.ParagraphElement;
+import com.google.gwt.dom.client.ScriptElement;
+import com.google.gwt.dom.client.SpanElement;
+
+/**
+ * Extends the document implementation provided by GWT to add support for multi-window, selection and range.
+ * 
+ * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=2772
+ * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3006
+ * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3053
+ * @version $Id$
+ */
+public class Document extends com.google.gwt.dom.client.Document
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected Document()
+    {
+        super();
+    }
+
+    /**
+     * Creates a new element.<br/>
+     * We've added this method because at the time of writing {@link com.google.gwt.dom.client.Document} doesn't offer
+     * support for multi-window. This means that currently, using GWT's API we can create elements only within the
+     * document of the host page. Since {@link com.google.gwt.user.client.ui.RichTextArea} is based on an in-line frame
+     * which has its own window and document we have to be able to create elements within the edited document.
+     * 
+     * @param tagName the tag name of the element to be created
+     * @return the newly created element
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=2772
+     */
+    public final native Element xCreateElement(String tagName)
+    /*-{
+        return this.createElement(tagName);
+    }-*/;
+
+    /**
+     * @param data contains the data to be added to the comment.
+     * @return the created comment node.
+     */
+    public final native Node createComment(String data)
+    /*-{
+        return this.createComment(data);
+    }-*/;
+
+    /**
+     * Creates a &lt;link&gt; element.
+     * 
+     * @return the newly created element
+     */
+    public final LinkElement xCreateLinkElement()
+    {
+        return xCreateElement("link").cast();
+    }
+
+    /**
+     * Creates a &lt;script&gt; element.
+     * 
+     * @return the newly created element
+     */
+    public final ScriptElement xCreateScriptElement()
+    {
+        return xCreateElement("script").cast();
+    }
+
+    /**
+     * Creates a &lt;br&gt; element.
+     * 
+     * @return the newly created element
+     */
+    public final BRElement xCreateBRElement()
+    {
+        return xCreateElement("br").cast();
+    }
+
+    /**
+     * Creates a &lt;p&gt; element.
+     * 
+     * @return the newly created element
+     */
+    public final ParagraphElement xCreatePElement()
+    {
+        return xCreateElement("p").cast();
+    }
+
+    /**
+     * Creates a &lt;div&gt; element.
+     * 
+     * @return the newly created element.
+     */
+    public final DivElement xCreateDivElement()
+    {
+        return xCreateElement("div").cast();
+    }
+
+    /**
+     * Creates a &lt;span&gt; element.
+     * 
+     * @return the newly created element.
+     */
+    public final SpanElement xCreateSpanElement()
+    {
+        return xCreateElement("span").cast();
+    }
+
+    /**
+     * @return a new base element
+     */
+    public final BaseElement xCreateBaseElement()
+    {
+        return xCreateElement("base").cast();
+    }
+
+    /**
+     * Creates a &lt;hr&gt; element.
+     * 
+     * @return the newly create element.
+     */
+    public final HRElement xCreateHRElement()
+    {
+        return xCreateElement("hr").cast();
+    }
+
+    /**
+     * Creates a &lt;img&gt; element.
+     * 
+     * @return the newly create element.
+     */
+    public final ImageElement xCreateImageElement()
+    {
+        return xCreateElement("img").cast();
+    }
+
+    /**
+     * Creates a &lt;li&gt; element.
+     * 
+     * @return the newly created element.
+     */
+    public final LIElement xCreateLIElement()
+    {
+        return xCreateElement("li").cast();
+    }
+
+    /**
+     * Creates a &lt;button&gt; element.
+     * 
+     * @return the newly create element.
+     */
+    public final ButtonElement xCreateButtonElement()
+    {
+        return xCreateElement("button").cast();
+    }
+
+    /**
+     * We've added this method because at the time of writing {@link com.google.gwt.dom.client.Document} doesn't offer
+     * support for retrieving the current selection.
+     * 
+     * @return The selection object associated with this document.
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3053
+     */
+    public final Selection getSelection()
+    {
+        return SelectionManager.INSTANCE.getSelection(this);
+    }
+
+    /**
+     * We've added this method because at the time of writing {@link com.google.gwt.dom.client.Document} doesn't offer
+     * support for creating a range.
+     * 
+     * @return A new range for this document.
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3053
+     */
+    public final Range createRange()
+    {
+        return RangeFactory.INSTANCE.createRange(this);
+    }
+
+    /**
+     * Creates a copy of a node from an external document that can be inserted into this document.<br/>
+     * We've added this method because at time of writing
+     * {@link com.google.gwt.dom.client.Document#importNode(Node, boolean)} is not well implemented.
+     * 
+     * @param externalNode The node from another document to be imported.
+     * @param deep Indicates whether the children of the given node need to be imported.
+     * @return a copy of the given node that can be inserted into this document.
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3006
+     */
+    public final Node xImportNode(Node externalNode, boolean deep)
+    {
+        return DOMUtils.getInstance().importNode(this, externalNode, deep);
+    }
+
+    /**
+     * Returns an iterator for the depth-first pre-order strategy, starting in <code>startNode</code>.
+     * 
+     * @param startNode node to start iteration from
+     * @return the depth-first pre-order iterator
+     * @see {@link DepthFirstPreOrderIterator}
+     * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-Document
+     */
+    public final Iterator<Node> getIterator(Node startNode)
+    {
+        return new DepthFirstPreOrderIterator(startNode);
+    }
+
+    /**
+     * When an HTML document has been switched to designMode, the document object exposes the execCommand method which
+     * allows one to run commands to manipulate the contents of the editable region. Most commands affect the document's
+     * selection (bold, italics, etc), while others insert new elements (adding a link) or affect an entire line
+     * (indenting). When using contentEditable, calling execCommand will affect the currently active editable element.
+     * 
+     * @param command The name of the command.
+     * @param parameter Some commands (such as insertimage) require an extra value argument (the image's url). Pass an
+     *            argument of null if no argument is needed.
+     * @return true if the specified command has been successfully executed.
+     */
+    public final native boolean execCommand(String command, String parameter)
+    /*-{
+        try{
+            return this.execCommand(command, false, parameter);
+        } catch(e) {
+            return false;
+        }
+    }-*/;
+
+    /**
+     * @param command The name of the command to query.
+     * @return The current value of the current range for the given command. If a command value has not been explicitly
+     *         set then it returns null.
+     */
+    public final native String queryCommandValue(String command)
+    /*-{
+        try{
+            return this.queryCommandValue(command);
+        } catch(e) {
+            return null;
+        }
+    }-*/;
+
+    /**
+     * @param command The name of the command to query.
+     * @return true if the given command can be executed on the current range.
+     */
+    public final native boolean queryCommandEnabled(String command)
+    /*-{
+        try{
+            return this.queryCommandEnabled(command);
+        } catch(e) {
+            return false;
+        }
+    }-*/;
+
+    /**
+     * @param command The name of the command to query.
+     * @return true if the given command has been executed on the current range.
+     */
+    public final native boolean queryCommandState(String command)
+    /*-{
+        try{
+            return this.queryCommandState(command);
+        } catch(e) {
+            return false;
+        }
+    }-*/;
+
+    /**
+     * @param command The name of the command to query.
+     * @return true if the given command is supported by the current browser.
+     */
+    public final native boolean queryCommandSupported(String command)
+    /*-{
+        try{
+            return this.queryCommandSupported(command);
+        } catch(e) {
+            return true;
+        }
+    }-*/;
+
+    /**
+     * @return the document element.
+     */
+    public final native Element getDocumentElement()
+    /*-{
+        return this.documentElement;
+    }-*/;
+
+    /**
+     * Creates an empty document fragment.<br/>
+     * A DocumentFragment is a minimal document object that has no parent. It supports the following DOM 2 methods:
+     * appendChild, cloneNode, hasAttributes, hasChildNodes, insertBefore, normalize, removeChild, replaceChild.<br/>
+     * It also supports the following DOM 2 properties: attributes, childNodes, firstChild, lastChild, localName,
+     * namespaceURI, nextSibling, nodeName, nodeType, nodeValue, ownerDocument, parentNode, prefix, previousSibling,
+     * textContent.<br/>
+     * Various other methods can take a document fragment as an argument (e.g. Node interface methods such as
+     * appendChild and insertBefore), in which case the children of the fragment are appended or inserted, not the
+     * fragment itself.
+     * 
+     * @return The newly created document fragment.
+     */
+    public final native DocumentFragment createDocumentFragment()
+    /*-{
+        return this.createDocumentFragment();
+    }-*/;
+
+    /**
+     * Opens a document stream for writing.
+     */
+    public final native void open()
+    /*-{
+        this.open();
+    }-*/;
+
+    /**
+     * Closes a document stream for writing.
+     */
+    public final native void close()
+    /*-{
+        this.close();
+    }-*/;
+
+    /**
+     * Writes a string of text to a document stream opened by {@link #open()}.
+     * 
+     * @param html a string containing the HTML to be written to the document.
+     */
+    public final native void write(String html)
+    /*-{
+        this.write(html);
+    }-*/;
+
+    /**
+     * Registers a new listener for changes to <code>innerHTML</code> property of element within this document.
+     * 
+     * @param listener The listener to be registered.
+     */
+    public final native void addInnerHTMLListener(InnerHTMLListener listener)
+    /*-{
+        if (!this.innerHTMLListeners) {
+            this.innerHTMLListeners = [];
+        }
+        this.innerHTMLListeners.push(listener);
+    }-*/;
+
+    /**
+     * Stop sending notifications to the given listener when the <code>innerHTML</code> property, of some element
+     * included in this document, changes.
+     * 
+     * @param listener The listener to be unregistered.
+     */
+    public final native void removeInnerHTMLListener(InnerHTMLListener listener)
+    /*-{
+        if (this.innerHTMLListeners) {
+            for (var i = 0; i < this.innerHTMLListeners.length; i++) {
+                if (this.innerHTMLListeners[i] == listener) {
+                    this.innerHTMLListeners.splice(i, 1);
+                }
+            }
+        }
+    }-*/;
+
+    /**
+     * Notify all listeners of the change to the given element's <code>innerHTML</code> property.<br/>
+     * NOTE: Keep this method accessible only from within this package because only {@link Element} should call it.
+     * 
+     * @param element The element whose <code>innerHTML</code> property has changed.
+     */
+    final native void fireInnerHTMLChange(Element element)
+    /*-{
+        if (this.innerHTMLListeners) {
+            for (var i = 0; i < this.innerHTMLListeners.length; i++) {
+                this.innerHTMLListeners[i].
+    @com.xpn.xwiki.wysiwyg.client.dom.InnerHTMLListener::onInnerHTMLChange(Lcom/xpn/xwiki/wysiwyg/client/dom/Element;)
+    (element);
+            }
+        }
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DocumentFragment.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DocumentFragment.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/DocumentFragment.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,67 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * A fragment of a DOM document.<br/>
+ * We've added this class because at the time of writing GWT doesn't offer a similar implementation.
+ * 
+ * @version $Id$
+ * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=2955
+ */
+public final class DocumentFragment extends Node
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected DocumentFragment()
+    {
+    }
+
+    /**
+     * @return the HTML serialization of this document fragment
+     */
+    public String getInnerHTML()
+    {
+        Element container = ((Document) getOwnerDocument()).xCreateDivElement().cast();
+        // We avoid attaching a clone to limit the memory used.
+        container.appendChild(this);
+        String innerHTML = container.xGetInnerHTML();
+        // We restore the document fragment.
+        appendChild(container.extractContents());
+        return innerHTML;
+    }
+
+    /**
+     * @return the text, without mark-up, found within this document fragment
+     */
+    public String getInnerText()
+    {
+        Element container = ((Document) getOwnerDocument()).xCreateDivElement().cast();
+        // We avoid attaching a clone to limit the memory used.
+        container.appendChild(this);
+        String innerText = container.xGetInnerText();
+        // We restore the document fragment.
+        appendChild(container.extractContents());
+        return innerText;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Element.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Element.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Element.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,296 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Node;
+
+/**
+ * Extends the element implementation provided by GWT to add useful methods. All of them should be removed as soon as
+ * they make their way into GWT's API.
+ * 
+ * @version $Id$
+ */
+public class Element extends com.google.gwt.dom.client.Element
+{
+    /**
+     * The text used in an element's meta data as a place holder for that element's outer HTML.
+     */
+    public static final String INNER_HTML_PLACEHOLDER = "com.xpn.xwiki.wysiwyg.client.dom.Element#placeholder";
+
+    /**
+     * The name of the JavaScript property storing the reference to the meta data.<br/>
+     * NOTE: We can't use the same name as for {@link #META_DATA_ATTR} because IE stores attribute values as JavaScript
+     * properties of DOM element objects.
+     */
+    public static final String META_DATA_REF = "metaDataRef";
+
+    /**
+     * The name of the DOM attribute storing the HTML of the meta data. This HTML is used to recreate the meta data when
+     * an element is cloned or copy&pasted.
+     */
+    public static final String META_DATA_ATTR = "metadata";
+
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected Element()
+    {
+        super();
+    }
+
+    /**
+     * Casts a {@link Node} to an instance of this type.
+     * 
+     * @param node the instance to be casted to this type.
+     * @return the given object as an instance of {@link Element}.
+     */
+    public static Element as(Node node)
+    {
+        return (Element) com.google.gwt.dom.client.Element.as(node);
+    }
+
+    /**
+     * @return The names of DOM attributes present on this element.
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3054
+     */
+    public final JsArrayString getAttributeNames()
+    {
+        return DOMUtils.getInstance().getAttributeNames(this);
+    }
+
+    /**
+     * Returns the value of the specified CSS property for this element as it is computed by the browser before the
+     * element is displayed. The CSS property doesn't have to be applied explicitly or directly on this element. It can
+     * be inherited or assumed by default on this element.
+     * 
+     * @param propertyName the name of the CSS property whose value is returned.
+     * @return the computed value of the specified CSS property for this element.
+     */
+    public final String getComputedStyleProperty(String propertyName)
+    {
+        return DOMUtils.getInstance().getComputedStyleProperty(this, propertyName);
+    }
+
+    /**
+     * Set inner HTML in cross browser manner and notify the owner document.
+     * 
+     * @param html the html to set.
+     * @see {@link DOMUtils#setInnerHTML(Element, String)}
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3146
+     */
+    public final void xSetInnerHTML(String html)
+    {
+        DOMUtils.getInstance().setInnerHTML(this, html);
+        ((Document) getOwnerDocument()).fireInnerHTMLChange(this);
+    }
+
+    /**
+     * @return the extended inner HTML of this element, which includes meta data.
+     * @see #getInnerHTML()
+     */
+    public final String xGetInnerHTML()
+    {
+        if (getFirstChildElement() == null) {
+            return getInnerHTML();
+        } else {
+            Element container = ((Document) getOwnerDocument()).xCreateDivElement().cast();
+            StringBuffer innerHTML = new StringBuffer();
+            Node child = getFirstChild();
+            do {
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    innerHTML.append(Element.as(child).xGetString());
+                } else {
+                    container.appendChild(child.cloneNode(true));
+                    innerHTML.append(container.getInnerHTML());
+                    container.removeChild(container.getLastChild());
+                }
+                child = child.getNextSibling();
+            } while (child != null);
+            return innerHTML.toString();
+        }
+    }
+
+    /**
+     * @return the extended outer HTML of this element, which includes meta data.
+     * @see #getString()
+     */
+    public final String xGetString()
+    {
+        String outerHTML;
+        // We need to remove the meta data attribute on serialization
+        String metaDataHTML = null;
+        if (hasAttribute(META_DATA_ATTR)) {
+            metaDataHTML = xGetAttribute(META_DATA_ATTR);
+            // Remove the attribute from this element
+            removeAttribute(META_DATA_ATTR);
+        }
+        if (hasChildNodes()) {
+            Element clone = Element.as(cloneNode(false));
+            clone.appendChild(getOwnerDocument().createTextNode(INNER_HTML_PLACEHOLDER));
+            outerHTML = clone.getString();
+            outerHTML = outerHTML.replace(INNER_HTML_PLACEHOLDER, xGetInnerHTML());
+        } else {
+            outerHTML = getString();
+        }
+        // Some browsers, including IE, format the HTML returned by innerHTML and outerHTML properties by adding new
+        // lines or tabs. We have to remove leading and trailing white spaces from the outerHTML because when we reset
+        // the innerHTML or outerHTML properties these white spaces can generate additional text nodes which can, for
+        // instance, mess up the History mechanism.
+        outerHTML = outerHTML.trim();
+        if (metaDataHTML != null) {
+            // Put the meta data attribute back
+            setAttribute(META_DATA_ATTR, metaDataHTML);
+            outerHTML = metaDataHTML.replace(INNER_HTML_PLACEHOLDER, outerHTML);
+        }
+        return outerHTML;
+    }
+
+    /**
+     * Places all the children of this element in a document fragment and returns it.<br/>
+     * NOTE: The element will remain empty after this method call.
+     * 
+     * @return A document fragment containing all the descendants of this element.
+     */
+    public final DocumentFragment extractContents()
+    {
+        DocumentFragment contents = ((Document) getOwnerDocument()).createDocumentFragment();
+        Node child = getFirstChild();
+        while (child != null) {
+            contents.appendChild(child);
+            child = getFirstChild();
+        }
+        return contents;
+    }
+
+    /**
+     * Replaces this element with its child nodes. In other words, all the child nodes of this element are moved to its
+     * parent node and the element is removed from its parent.
+     */
+    public final void unwrap()
+    {
+        if (getParentNode() == null || getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
+            return;
+        }
+        getParentNode().replaceChild(extractContents(), this);
+    }
+
+    /**
+     * Wraps the passed node and takes its place in its parent. In other words, it adds the passed element as a child of
+     * this element and replaces it in its parent.
+     * 
+     * @param node the node to wrap
+     */
+    public final void wrap(Node node)
+    {
+        if (node.getParentNode() == null) {
+            return;
+        }
+        node.getParentNode().replaceChild(this, node);
+        appendChild(node);
+    }
+
+    /**
+     * @return the meta data associated with this element.
+     */
+    public final DocumentFragment getMetaData()
+    {
+        DocumentFragment metaData = (DocumentFragment) ((JavaScriptObject) cast()).get(META_DATA_REF);
+        if (metaData == null) {
+            // There's no saved reference to the meta data.
+            // Test if this element has stored meta data.
+            if (hasAttribute(META_DATA_ATTR)) {
+                // This element could be the result of node cloning or copy&paste.
+                // Let's update the cached meta data reference.
+                Element container = (Element) getOwnerDocument().createDivElement().cast();
+                container.xSetInnerHTML(xGetAttribute(META_DATA_ATTR));
+                metaData = container.extractContents();
+                ((JavaScriptObject) cast()).set(META_DATA_REF, metaData);
+            }
+        }
+        return metaData;
+    };
+
+    /**
+     * Sets the meta data of this element.
+     * 
+     * @param metaData a document fragment with additional information regarding this element.
+     */
+    public final void setMetaData(DocumentFragment metaData)
+    {
+        // Save a reference to the meta data for fast retrieval.
+        ((JavaScriptObject) cast()).set(META_DATA_REF, metaData);
+        if (metaData != null) {
+            // We have to serialize the meta data and store it using a custom attribute to avoid loosing the meta data
+            // over node cloning or copy&paste. The custom attribute used for storing the meta data should be filtered
+            // when getting the outer HTML.
+            setAttribute(META_DATA_ATTR, metaData.getInnerHTML());
+        } else {
+            removeAttribute(META_DATA_ATTR);
+        }
+    };
+
+    /**
+     * @return true if HTML Strict DTD specifies that this element must be empty.
+     */
+    public final boolean mustBeEmpty()
+    {
+        for (int i = 0; i < DOMUtils.HTML_EMPTY_TAGS.length; i++) {
+            if (DOMUtils.HTML_EMPTY_TAGS[i].equalsIgnoreCase(getTagName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the value for the specified attribute in cross browser manner.
+     * 
+     * @param name the name of the attribute
+     * @return the value of the attribute
+     * @see DOMUtils#getAttribute(Element, String)
+     */
+    public final String xGetAttribute(String name)
+    {
+        return DOMUtils.getInstance().getAttribute(this, name);
+    }
+
+    /**
+     * We need this method because {@link #getInnerText()} includes commented text in the output.
+     * 
+     * @return the text between the start and end tags of this element
+     * @see #getInnerText()
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3275
+     */
+    public final String xGetInnerText()
+    {
+        return DOMUtils.getInstance().getInnerText(this);
+    }
+
+    /**
+     * @param attrName a string representing the name of an attribute
+     * @return true is this element has an attribute with the specified name, false otherwise
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=2852
+     */
+    public final boolean hasAttribute(String attrName)
+    {
+        return DOMUtils.getInstance().hasAttribute(this, attrName);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Event.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Event.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Event.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,63 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * DOM Event.
+ * 
+ * @version $Id$
+ */
+public class Event extends com.google.gwt.user.client.Event
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected Event()
+    {
+        super();
+    }
+
+    /**
+     * @return true if this event was cancelled by calling {@link #xPreventDefault()}.
+     */
+    public final native boolean isCancelled()
+    /*-{
+        return !(!this.__cancelled);
+    }-*/;
+
+    /**
+     * Sets this event's cancelled state.
+     * 
+     * @param cancelled specifies if this event should be cancelled or not.
+     */
+    protected final native void setCancelled(boolean cancelled)
+    /*-{
+        this.__cancelled = cancelled;
+    }-*/;
+
+    /**
+     * Cancel this event.
+     */
+    public final void xPreventDefault()
+    {
+        preventDefault();
+        setCancelled(true);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/InnerHTMLListener.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/InnerHTMLListener.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/InnerHTMLListener.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,36 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * Interface for listening to changes of the <code>innerHTML</code> property of a DOM element.
+ * 
+ * @version $Id$
+ */
+public interface InnerHTMLListener
+{
+    /**
+     * Called whenever the <code>innerHTML</code> property, of an element within the document this listener has been
+     * registered to, changes.
+     * 
+     * @param element The element whose <code>innerHTML</code> has changed.
+     */
+    void onInnerHTMLChange(Element element);
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/JavaScriptObject.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/JavaScriptObject.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/JavaScriptObject.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,82 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * Extends GWT JavaScriptObject to add a fromJson method. Usage : <code>
+ * public class MyCar extends JavaScriptObject {   
+ *     protected MyCar() {}
+ *     public final native int getWheelNumber() / *-{ return this.wheelnb; }-* /;
+ *     public final native String getColor() / *-{ return this.color; }-* /;
+ * }
+ * 
+ * MyCar redcar = (MyCar) MyCar.fromJson("{ wheelnb: 4, color: 'red' }");
+ * redcar.getWheelNumber();
+ * redcar.getColor();
+ * </code>
+ * 
+ * @see com.google.gwt.core.client.JavaScriptObject
+ * @version $Id$
+ */
+public class JavaScriptObject extends com.google.gwt.core.client.JavaScriptObject
+{
+    /**
+     * Default constructor. Overlay types always have protected, zero-arguments constructors.
+     */
+    protected JavaScriptObject()
+    {
+    }
+
+    /**
+     * Create a JavaScriptObject from a JSON string.
+     * 
+     * @param input a valid JSON string.
+     * @return resulting JavaScriptObject
+     */
+    public static final native JavaScriptObject fromJson(String input)
+    /*-{ 
+         return eval('(' + input + ')') 
+    }-*/;
+
+    /**
+     * Returns the reference stored in this JavaScript object for the given key.
+     * 
+     * @param key the key whose value to return
+     * @return the value of the specified key
+     */
+    public final native Object get(String key)
+    /*-{
+        return this[key];
+    }-*/;
+
+    /**
+     * Saves the given reference in this JavaScript object using the specified key.
+     * 
+     * @param key the string used for storing and retrieving the reference
+     * @param ref the object whose reference will be stored
+     * @return the previous reference associated with the given key
+     */
+    public final native Object set(String key, Object ref)
+    /*-{
+        var oldRef = this[key];
+        this[key] = ref;
+        return oldRef;
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Range.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Range.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Range.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,201 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * A contiguous fragment of a {@link Document} or {@link DocumentFragment} that can contain nodes and parts of text
+ * nodes.
+ * 
+ * @version $Id$
+ * @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
+ */
+public interface Range
+{
+    /**
+     * Duplicates the contents of this range.
+     * 
+     * @return a DocumentFragment that contains content equivalent to this range
+     */
+    DocumentFragment cloneContents();
+
+    /**
+     * Produces a new Range whose boundary-points are equal to the boundary-points of this range.
+     * 
+     * @return the duplicated range
+     */
+    Range cloneRange();
+
+    /**
+     * Collapse this range onto one of its boundary-points.
+     * 
+     * @param toStart if true, collapses this range onto its start; if false, collapses it onto its end.
+     */
+    void collapse(boolean toStart);
+
+    /**
+     * Compare the boundary-points of two Ranges in a document.
+     * 
+     * @param how the type of comparison
+     * @param sourceRange the range to compared to
+     * @return -1, 0 or 1 depending on whether the corresponding boundary-point of this range is respectively before,
+     *         equal to, or after the corresponding boundary-point of sourceRange
+     */
+    short compareBoundaryPoints(RangeCompare how, Range sourceRange);
+
+    /**
+     * Removes the contents of this range from the containing document or document fragment without returning a
+     * reference to the removed content.
+     */
+    void deleteContents();
+
+    /**
+     * Called to indicate that this range is no longer in use and that the implementation may relinquish any resources
+     * associated with this range.
+     */
+    void detach();
+
+    /**
+     * Moves the contents of this range from the containing document or document fragment to a new DocumentFragment.
+     * 
+     * @return a DocumentFragment containing the extracted contents
+     */
+    DocumentFragment extractContents();
+
+    /**
+     * @return the deepest common ancestor container of this range's two boundary-points
+     */
+    Node getCommonAncestorContainer();
+
+    /**
+     * @return the node within which this range ends
+     */
+    Node getEndContainer();
+
+    /**
+     * @return the offset within the ending node of this range
+     */
+    int getEndOffset();
+
+    /**
+     * @return the node within which this range begins
+     */
+    Node getStartContainer();
+
+    /**
+     * @return the offset within the starting node of this range
+     */
+    int getStartOffset();
+
+    /**
+     * Inserts a node into the Document or DocumentFragment at the start of the Range. If the container is a Text node,
+     * this will be split at the start of the Range (as if the Text node's splitText method was performed at the
+     * insertion point) and the insertion will occur between the two resulting Text nodes. Adjacent Text nodes will not
+     * be automatically merged. If the node to be inserted is a DocumentFragment node, the children will be inserted
+     * rather than the DocumentFragment node itself.
+     * 
+     * @param newNode the node to insert at the start of this range.
+     */
+    void insertNode(Node newNode);
+
+    /**
+     * @return true if this range is collapsed
+     */
+    boolean isCollapsed();
+
+    /**
+     * Select a node and its contents.
+     * 
+     * @param refNode the node to select
+     */
+    void selectNode(Node refNode);
+
+    /**
+     * Select the contents within a node.
+     * 
+     * @param refNode the node to select from
+     */
+    void selectNodeContents(Node refNode);
+
+    /**
+     * Sets the attributes describing the end of this range.
+     * 
+     * @param refNode the {@link #endContainer} value. This parameter must be different from null.
+     * @param offset the {@link #endOffset} value
+     */
+    void setEnd(Node refNode, int offset);
+
+    /**
+     * Sets the end of this Range to be after the given node.
+     * 
+     * @param refNode the reference node, after which this range will end
+     */
+    void setEndAfter(Node refNode);
+
+    /**
+     * Sets the end position to be before the given node.
+     * 
+     * @param refNode the reference node, before which this range will end
+     */
+    void setEndBefore(Node refNode);
+
+    /**
+     * Sets the attributes describing the start of this range.
+     * 
+     * @param refNode the {@link #startContainer} value. This parameter must be different from null.
+     * @param offset the {@link #startOffset} value
+     */
+    void setStart(Node refNode, int offset);
+
+    /**
+     * Sets the start position to be after the given node.
+     * 
+     * @param refNode the reference node, after which this range will start
+     */
+    void setStartAfter(Node refNode);
+
+    /**
+     * Sets the start position to be before the given node.
+     * 
+     * @param refNode the reference node, before which this range will start
+     */
+    void setStartBefore(Node refNode);
+
+    /**
+     * Re-parents the contents of this range to the given node and inserts the node at the position of the start of this
+     * range.
+     * 
+     * @param newParent the node to surround the contents with
+     */
+    void surroundContents(Node newParent);
+
+    /**
+     * @return the HTML contents of this range
+     */
+    String toHTML();
+
+    /**
+     * Returns the contents of this range as a string. This string contains only the data characters, not any mark-up.
+     * 
+     * @return the contents of this range
+     */
+    String toString();
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeCompare.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeCompare.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeCompare.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,113 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * Passed as a parameter to the compareBoundaryPoints method.
+ * 
+ * @version $Id$
+ */
+public enum RangeCompare
+{
+    /**
+     * Compare start boundary-point of sourceRange to start boundary-point of Range on which compareBoundaryPoints is
+     * invoked.
+     */
+    START_TO_START("StartToStart"),
+
+    /**
+     * Compare start boundary-point of sourceRange to end boundary-point of Range on which compareBoundaryPoints is
+     * invoked.
+     */
+    START_TO_END("StartToEnd"),
+
+    /**
+     * Compare end boundary-point of sourceRange to end boundary-point of Range on which compareBoundaryPoints is
+     * invoked.
+     */
+    END_TO_END("EndToEnd"),
+
+    /**
+     * Compare end boundary-point of sourceRange to start boundary-point of Range on which compareBoundaryPoints is
+     * invoked.
+     */
+    END_TO_START("EndToStart");
+
+    /**
+     * The value of this constant, which will be passed to JNI methods. We need it in order to overwrite
+     * {@link #toString()} method.
+     */
+    private String value;
+
+    /**
+     * Creates a new RangeCompare constant based on the specified value. This value will be returned by
+     * {@link #toString()}.
+     * 
+     * @param value The value of the created constant.
+     */
+    RangeCompare(String value)
+    {
+        this.value = value;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Enum#toString()
+     */
+    public String toString()
+    {
+        return this.value;
+    }
+
+    /**
+     * @return The reverse the end points that are compared.
+     */
+    public RangeCompare reverse()
+    {
+        switch (this) {
+            case START_TO_START:
+            case END_TO_END:
+                return this;
+            case START_TO_END:
+                return END_TO_START;
+            case END_TO_START:
+                return START_TO_END;
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * @param firstEndPoint true for START and false for END.
+     * @param secondEndPoint true for START and false for END.
+     * @return the value associated with the specified end points.
+     */
+    public static RangeCompare valueOf(boolean firstEndPoint, boolean secondEndPoint)
+    {
+        if (firstEndPoint) {
+            // START_TO_
+            return secondEndPoint ? START_TO_START : START_TO_END;
+        } else {
+            // END_TO_
+            return secondEndPoint ? END_TO_START : END_TO_END;
+        }
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeFactory.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeFactory.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/RangeFactory.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,43 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.core.client.GWT;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultRangeFactory;
+
+/**
+ * Defines the interface used to create range objects.
+ * 
+ * @version $Id$
+ */
+public interface RangeFactory
+{
+    /**
+     * We create the singleton instance using deferred binding in order to use different implementations for different
+     * browsers.
+     */
+    RangeFactory INSTANCE = GWT.create(DefaultRangeFactory.class);
+
+    /**
+     * @param doc The DOM document for which to create the range.
+     * @return A new range bound to the specified document.
+     */
+    Range createRange(Document doc);
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Selection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Selection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Selection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,150 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * A selection object represents the ranges that the user has selected, or the cursor position when the user didn't
+ * select any range. It is the only was to get access to the current DOM nodes being edited with the WYSIWYG editor.
+ * 
+ * @version $Id$
+ */
+public interface Selection
+{
+    /**
+     * @return the node in which the selection begins
+     */
+    Node getAnchorNode();
+
+    /**
+     * @return the offset within the {@link #getAnchorNode()} where the selection begins
+     */
+    int getAnchorOffset();
+
+    /**
+     * @return the node in which the selection ends
+     */
+    Node getFocusNode();
+
+    /**
+     * @return the offset within the {@link #getFocusNode()} where the selection ends.
+     */
+    int getFocusOffset();
+
+    /**
+     * @return true if the selection is collapsed
+     */
+    boolean isCollapsed();
+
+    /**
+     * @return the number of ranges in the selection
+     */
+    int getRangeCount();
+
+    /**
+     * @param index the index of the range to retrieve. Usually the selection contains just one range.
+     * @return the range at the specified index
+     */
+    Range getRangeAt(int index);
+
+    /**
+     * Collapses the selection to a single point, at the specified offset in the given DOM node. When the selection is
+     * collapsed, and the content is focused and editable, the caret will blink there.
+     * 
+     * @param parentNode the DOM node where the selection will be set
+     * @param offset specifies where to place the selection in the given node
+     */
+    void collapse(Node parentNode, int offset);
+
+    /**
+     * Extends the selection by moving the selection end to the specified node and offset, preserving the selection
+     * begin position. The new selection end result will always be from the anchorNode to the new focusNode, regardless
+     * of direction.
+     * 
+     * @param parentNode the node where the selection will be extended to
+     * @param offset specifies where to end the selection in the given node
+     */
+    void extend(Node parentNode, int offset);
+
+    /**
+     * Collapses the whole selection to a single point at the start of the current selection (irrespective of
+     * direction). If content is focused and editable, the caret will blink there.
+     */
+    void collapseToStart();
+
+    /**
+     * Collapses the whole selection to a single point at the end of the current selection (irrespective of direction).
+     * If content is focused and editable, the caret will blink there.
+     */
+    void collapseToEnd();
+
+    /**
+     * Indicates whether the given node is part of the selection.
+     * 
+     * @param node the DOM node to be tested
+     * @param partlyContained if false, the entire subtree rooted in the given node is tested
+     * @return true when the entire node is part of the selection
+     */
+    boolean containsNode(Node node, boolean partlyContained);
+
+    /**
+     * Adds all children of the specified node to the selection. Previous selection is lost.
+     * 
+     * @param parentNode the parent of the children to be added to the selection
+     */
+    void selectAllChildren(Node parentNode);
+
+    /**
+     * Adds a range to this selection.
+     * 
+     * @param range the range to be added
+     */
+    void addRange(Range range);
+
+    /**
+     * Removes the given range from the selection.
+     * 
+     * @param range the range to be removed from the selection.
+     */
+    void removeRange(Range range);
+
+    /**
+     * Removes all ranges from the current selection.
+     */
+    void removeAllRanges();
+
+    /**
+     * Deletes this selection from document the nodes belong to.
+     */
+    void deleteFromDocument();
+
+    /**
+     * Modifies the cursor Bidi level after a change in keyboard direction.
+     * 
+     * @param langRTL is true if the new language is right-to-left or false if the new language is left-to-right
+     */
+    void selectionLanguageChange(boolean langRTL);
+
+    /**
+     * @return the currently selected text
+     */
+    String toString();
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/SelectionManager.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/SelectionManager.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/SelectionManager.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,43 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.core.client.GWT;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelectionManager;
+
+/**
+ * Interface for retrieving the current selection.
+ * 
+ * @version $Id$
+ */
+public interface SelectionManager
+{
+    /**
+     * We create the singleton instance using deferred binding in order to use different implementations for different
+     * browsers.
+     */
+    SelectionManager INSTANCE = GWT.create(DefaultSelectionManager.class);
+
+    /**
+     * @param doc The document for which to retrieve the selection.
+     * @return The selection associated with the specified document.
+     */
+    Selection getSelection(Document doc);
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Style.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Style.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Style.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,256 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * Extends the {@link com.google.gwt.dom.client.Style} to add constants for standard property names and values.
+ * 
+ * @version $Id$
+ */
+public class Style extends com.google.gwt.dom.client.Style
+{
+    /**
+     * Sets how/if an element is displayed.
+     */
+    public static final String DISPLAY = "display";
+
+    /**
+     * Sets the background color of an element.
+     */
+    public static final String BACKGROUND_COLOR = "backgroundColor";
+
+    /**
+     * Sets the stack order of an element. An element with greater stack order is always in front of another element
+     * with lower stack order.<br/>
+     * Elements can have negative stack orders.<br/>
+     * Z-index only works on elements that have been positioned (eg position:absolute;)!
+     */
+    public static final String Z_INDEX = "zIndex";
+
+    /**
+     * Sets how thick or thin characters in text should be displayed.
+     */
+    public static final String FONT_WEIGHT = "font-weight";
+
+    /**
+     * Sets the style of a font.
+     */
+    public static final String FONT_STYLE = "font-style";
+
+    /**
+     * Decorates the text.
+     */
+    public static final String TEXT_DECORATION = "text-decoration";
+
+    /**
+     * The font-family property is a prioritized list of font family names and/or generic family names for an element.
+     * The browser will use the first value it recognizes.<br/>
+     * There are two types of font-family values:
+     * <ul>
+     * <li>family-name: "times", "courier", "arial", etc.</li>
+     * <li>generic-family: "serif", "sans-serif", "cursive", "fantasy", "monospace".</li>
+     * </ul>
+     * Note: Separate each value with a comma, and always offer a generic-family name as the last alternative.<br/>
+     * Note: If a family-name contains white-space, it should be quoted. Single quotes must be used when using the
+     * "style" attribute in HTML.
+     */
+    public static final String FONT_FAMILY = "font-family";
+
+    /**
+     * Sets the width of an element.
+     */
+    public static final String WIDTH = "width";
+
+    /**
+     * sets the height of an element.
+     */
+    public static final String HEIGHT = "height";
+
+    /**
+     * Sets how far the top edge of an element is above/below the top edge of the parent element.
+     */
+    public static final String TOP = "top";
+
+    /**
+     * Sets how far the left edge of an element is to the right/left of the left edge of the parent element.
+     */
+    public static final String LEFT = "left";
+
+    /**
+     * Places an element in a static, relative, absolute or fixed position.
+     */
+    public static final String POSITION = "position";
+
+    /**
+     * Standard values for {@link Style#DISPLAY}.
+     */
+    public static final class Display
+    {
+        /**
+         * Default. The element will be displayed as an inline element, with no line break before or after the element.
+         */
+        public static final String INLINE = "inline";
+
+        /**
+         * The element will be displayed as a block-level element, with a line break before and after the element.
+         */
+        public static final String BLOCK = "block";
+
+        /**
+         * The element will not be displayed.
+         */
+        public static final String NONE = "none";
+
+        /**
+         * This is a utility class so it has a private constructor.
+         */
+        private Display()
+        {
+        }
+    }
+
+    /**
+     * Standard values for {@link Style#FONT_WEIGHT}.
+     */
+    public static final class FontWeight
+    {
+        /**
+         * Defines thick characters.
+         */
+        public static final String BOLD = "bold";
+
+        /**
+         * Defines thicker characters.
+         */
+        public static final String BOLDER = "bolder";
+
+        /**
+         * This is a utility class so it has a private constructor.
+         */
+        private FontWeight()
+        {
+        }
+    }
+
+    /**
+     * Standard values for {@link Style#FONT_STYLE}.
+     */
+    public static final class FontStyle
+    {
+        /**
+         * The browser displays an italic font.
+         */
+        public static final String ITALIC = "italic";
+
+        /**
+         * This is a utility class so it has a private constructor.
+         */
+        private FontStyle()
+        {
+        }
+    }
+
+    /**
+     * Standard values for {@link Style#TEXT_DECORATION}.
+     */
+    public static final class TextDecoration
+    {
+        /**
+         * Defines a line through the text.
+         */
+        public static final String LINE_THROUGH = "line-through";
+
+        /**
+         * Defines a line under the text.
+         */
+        public static final String UNDERLINE = "underline";
+
+        /**
+         * This is a utility class so it has a private constructor.
+         */
+        private TextDecoration()
+        {
+        }
+    }
+
+    /**
+     * Standard values for {@link Style#POSITION}.
+     */
+    public static final class Position
+    {
+        /**
+         * Default. An element with position: static always has the position the normal flow of the page gives it (a
+         * static element ignores any top, bottom, left, or right declarations).
+         */
+        public static final String STATIC = "static";
+
+        /**
+         * An element with position: relative moves an element relative to its normal position, so "left:20" adds 20
+         * pixels to the element's LEFT position.
+         */
+        public static final String RELATIVE = "relative";
+
+        /**
+         * An element with position: absolute is positioned at the specified coordinates relative to its containing
+         * block. The element's position is specified with the "left", "top", "right", and "bottom" properties.
+         */
+        public static final String ABSOLUTE = "absolute";
+
+        /**
+         * An element with position: fixed is positioned at the specified coordinates relative to the browser window.
+         * The element's position is specified with the "left", "top", "right", and "bottom" properties. The element
+         * remains at that position regardless of scrolling. Works in IE7 (strict mode).
+         */
+        public static final String FIXED = "fixed";
+
+        /**
+         * This is a utility class so it has a private constructor.
+         */
+        private Position()
+        {
+        }
+    }
+
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected Style()
+    {
+    }
+
+    /**
+     * Some browsers expect the camel case form of a style property. For instance, the camel case form of "font-weight"
+     * is "fontWeight".
+     * 
+     * @param propertyName The name of style property.
+     * @return The camel case form of the given property name.
+     */
+    public static String toCamelCase(String propertyName)
+    {
+        int dashIndex = propertyName.indexOf('-');
+        if (dashIndex < 0 || dashIndex == propertyName.length() - 1) {
+            return propertyName;
+        }
+        StringBuffer camelCase = new StringBuffer(propertyName.substring(0, dashIndex));
+        camelCase.append(propertyName.substring(dashIndex + 1, dashIndex + 2).toUpperCase());
+        camelCase.append(propertyName.substring(dashIndex + 2));
+        return camelCase.toString();
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableCellElement.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableCellElement.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableCellElement.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,77 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * Extends the implementation provided by GWT to add useful methods. All of them should be removed as soon as they make
+ * their way into GWT's API.
+ * 
+ * @version $Id$
+ */
+public final class TableCellElement extends com.google.gwt.dom.client.TableCellElement
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected TableCellElement()
+    {
+        super();
+    }
+
+    /**
+     * @return The row containing this cell.
+     */
+    public TableRowElement getOwnerRow()
+    {
+        return (TableRowElement) getParentElement();
+    }
+
+    /**
+     * @return The next cell inside the same table. If this is the last cell then it returns null.
+     */
+    public TableCellElement getNextCell()
+    {
+        Node nextCell = getNextSiblingElement();
+        if (nextCell == null) {
+            TableRowElement nextRow = getOwnerRow().getNextRow();
+            if (nextRow != null) {
+                nextCell = nextRow.getFirstCell();
+            }
+        }
+        return (TableCellElement) nextCell;
+    }
+
+    /**
+     * @return The previous cell inside the same table. If this is the first cell then it returns null.
+     */
+    public TableCellElement getPreviousCell()
+    {
+        Node prevCell = getPreviousSibling();
+        if (prevCell == null) {
+            TableRowElement prevRow = getOwnerRow().getPreviousRow();
+            if (prevRow != null) {
+                prevCell = prevRow.getLastCell();
+            }
+        }
+        return (TableCellElement) prevCell;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableRowElement.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableRowElement.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TableRowElement.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,94 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.TableElement;
+
+/**
+ * Extends the implementation provided by GWT to add useful methods. All of them should be removed as soon as they make
+ * their way into GWT's API.
+ * 
+ * @version $Id$
+ */
+public final class TableRowElement extends com.google.gwt.dom.client.TableRowElement
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected TableRowElement()
+    {
+        super();
+    }
+
+    /**
+     * @return The table containing this cell.
+     */
+    public TableElement getOwnerTable()
+    {
+        Node ancestor = this.getParentNode();
+        while (ancestor != null && !"table".equalsIgnoreCase(ancestor.getNodeName())) {
+            ancestor = ancestor.getParentNode();
+        }
+        return (TableElement) ancestor;
+    }
+
+    /**
+     * @return The next row inside the same table. If this is the last row then it returns null.
+     */
+    public TableRowElement getNextRow()
+    {
+        TableElement table = getOwnerTable();
+        if (getRowIndex() < table.getRows().getLength() - 1) {
+            return (TableRowElement) table.getRows().getItem(getRowIndex() + 1);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return The previous row inside the same table. If this is the first row then it returns null.
+     */
+    public TableRowElement getPreviousRow()
+    {
+        TableElement table = getOwnerTable();
+        if (getRowIndex() > 0) {
+            return (TableRowElement) table.getRows().getItem(getRowIndex() - 1);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return The first cell of this row.
+     */
+    public TableCellElement getFirstCell()
+    {
+        return (TableCellElement) getCells().getItem(0);
+    }
+
+    /**
+     * @return The last cell of this row.
+     */
+    public TableCellElement getLastCell()
+    {
+        return (TableCellElement) getCells().getItem(getCells().getLength() - 1);
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Text.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Text.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/Text.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,124 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+import com.google.gwt.dom.client.Node;
+
+/**
+ * Extends the text implementation provided by GWT to add useful methods. All of them should be removed as soon as they
+ * make their way into GWT's API.
+ * 
+ * @version $Id$
+ */
+public final class Text extends com.google.gwt.dom.client.Text
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected Text()
+    {
+    }
+
+    /**
+     * Casts a {@link Node} to an instance of this type.
+     * 
+     * @param node the instance to be casted to this type.
+     * @return the given object as an instance of {@link Text}.
+     */
+    public static Text as(Node node)
+    {
+        assert node.getNodeType() == Node.TEXT_NODE;
+        return (Text) com.google.gwt.dom.client.Text.as(node);
+    }
+
+    /**
+     * Merges all the neighbor text nodes of this text node and returns a text fragment specifying where is this text
+     * placed in the final text node resulted after the merge.
+     * 
+     * @return a text fragment showing the place of this text in the node obtained after the merge.
+     */
+    public TextFragment normalize()
+    {
+        StringBuffer leftText = new StringBuffer();
+        Node leftSibling = this.getPreviousSibling();
+        while (leftSibling != null && leftSibling.getNodeType() == Node.TEXT_NODE) {
+            leftText.insert(0, leftSibling.getNodeValue());
+            leftSibling.getParentNode().removeChild(leftSibling);
+            leftSibling = this.getPreviousSibling();
+        }
+
+        StringBuffer rightText = new StringBuffer();
+        Node rightSibling = this.getNextSibling();
+        while (rightSibling != null && rightSibling.getNodeType() == Node.TEXT_NODE) {
+            rightText.append(rightSibling.getNodeValue());
+            rightSibling.getParentNode().removeChild(rightSibling);
+            rightSibling = this.getNextSibling();
+        }
+
+        int startIndex = leftText.length();
+        int endIndex = startIndex + this.getLength();
+        this.setData(leftText.toString() + this.getData() + rightText.toString());
+        return new TextFragment(this, startIndex, endIndex);
+    }
+
+    /**
+     * @return the offset of this text node relative to the left-most successive text node sibling. The offset is
+     *         expressed as the number of characters between this text node and the reference point.
+     */
+    public int getOffset()
+    {
+        int offset = 0;
+        Node leftSibling = this.getPreviousSibling();
+        while (leftSibling != null) {
+            if (leftSibling.getNodeType() == Node.TEXT_NODE) {
+                offset += leftSibling.getNodeValue().length();
+            } else if (DOMUtils.getInstance().isSerializable(leftSibling)) {
+                break;
+            }
+            leftSibling = leftSibling.getPreviousSibling();
+        }
+        return offset;
+    }
+
+    /**
+     * Keeps the text between the given indexes as the value of this node. The remaining text, if present, is placed in
+     * sibling text nodes.
+     * 
+     * @param startIndex crop start
+     * @param endIndex crop end
+     */
+    public void crop(int startIndex, int endIndex)
+    {
+        if (startIndex > 0) {
+            String leftData = getData().substring(0, startIndex);
+            Text left = getOwnerDocument().createTextNode(leftData).cast();
+            getParentNode().insertBefore(left, this);
+            setData(getData().substring(startIndex));
+        }
+
+        int length = endIndex - startIndex;
+        if (length < getLength()) {
+            String rightData = getData().substring(length);
+            Text right = getOwnerDocument().createTextNode(rightData).cast();
+            DOMUtils.getInstance().insertAfter(right, this);
+            setData(getData().substring(0, length));
+        }
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TextFragment.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TextFragment.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/TextFragment.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,82 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom;
+
+/**
+ * A text fragment is a substring of a text node from a DOM tree. It is uniquely identified by the source text node, the
+ * index of the first character and the index of the last character.
+ * 
+ * @version $Id$
+ */
+public class TextFragment
+{
+    /**
+     * The source text node.
+     */
+    private final Text text;
+
+    /**
+     * The index of fragment's first character, in the source text node.
+     */
+    private final int startIndex;
+
+    /**
+     * The index of fragment's last character, in the source text node.
+     */
+    private final int endIndex;
+
+    /**
+     * Creates a new fragment of the given text node.
+     * 
+     * @param text the source text node.
+     * @param startIndex the index of fragment's first character, in the source text node.
+     * @param endIndex the index of fragment's last character, in the source text node.
+     */
+    public TextFragment(Text text, int startIndex, int endIndex)
+    {
+        this.text = text;
+        this.startIndex = startIndex;
+        this.endIndex = endIndex;
+    }
+
+    /**
+     * @return the source text node.
+     */
+    public Text getText()
+    {
+        return text;
+    }
+
+    /**
+     * @return the index of fragment's first character, in the source text node.
+     */
+    public int getStartIndex()
+    {
+        return startIndex;
+    }
+
+    /**
+     * @return the index of fragment's last character, in the source text node.
+     */
+    public int getEndIndex()
+    {
+        return endIndex;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/AbstractSelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/AbstractSelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/AbstractSelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,233 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.Selection;
+
+/**
+ * Abstract {@link Selection} implementation.
+ * <p>
+ * NOTE: In the current implementation we often make the assumption that the selection contains at most one range. All
+ * the other ranges, if they exist, are sometimes ignored. Additionally, but somehow as a consequence, we consider the
+ * anchor node as being the start container of the first range and the focus node as the end container of the first
+ * range. This has to do with the fact that not all the browsers distinguish the direction in which the user makes the
+ * selection (from left to right or the opposite).
+ * 
+ * @version $Id$
+ */
+public abstract class AbstractSelection implements Selection
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#collapse(Node, int)
+     */
+    public void collapse(Node parentNode, int offset)
+    {
+        Range range = ((Document) parentNode.getOwnerDocument()).createRange();
+        range.setStart(parentNode, offset);
+        range.collapse(true);
+        removeAllRanges();
+        addRange(range);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#collapseToEnd()
+     */
+    public void collapseToEnd()
+    {
+        collapse(false);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#collapseToStart()
+     */
+    public void collapseToStart()
+    {
+        collapse(true);
+    }
+
+    /**
+     * Collapses this selection to the specified end point.
+     * 
+     * @param toStart whether to collapse to the start or to the end point of the first range in this selection
+     */
+    private void collapse(boolean toStart)
+    {
+        if (getRangeCount() > 0) {
+            Range range = getRangeAt(0);
+            range.collapse(toStart);
+            removeAllRanges();
+            addRange(range);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#containsNode(Node, boolean)
+     */
+    public boolean containsNode(Node node, boolean partlyContained)
+    {
+        // TODO
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#deleteFromDocument()
+     */
+    public void deleteFromDocument()
+    {
+        if (getRangeCount() > 0) {
+            Range range = getRangeAt(0);
+            range.deleteContents();
+            removeAllRanges();
+            addRange(range);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#extend(Node, int)
+     */
+    public void extend(Node parentNode, int offset)
+    {
+        if (getRangeCount() > 0) {
+            Range range = getRangeAt(0);
+            range.setEnd(parentNode, offset);
+            removeAllRanges();
+            addRange(range);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#getAnchorNode()
+     */
+    public Node getAnchorNode()
+    {
+        if (getRangeCount() > 0) {
+            return getRangeAt(0).getStartContainer();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#getAnchorOffset()
+     */
+    public int getAnchorOffset()
+    {
+        if (getRangeCount() > 0) {
+            return getRangeAt(0).getStartOffset();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#getFocusNode()
+     */
+    public Node getFocusNode()
+    {
+        if (getRangeCount() > 0) {
+            return getRangeAt(0).getEndContainer();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#getFocusOffset()
+     */
+    public int getFocusOffset()
+    {
+        if (getRangeCount() > 0) {
+            return getRangeAt(0).getEndOffset();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#isCollapsed()
+     */
+    public boolean isCollapsed()
+    {
+        return getRangeCount() == 1 && getRangeAt(0).isCollapsed();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#selectAllChildren(Node)
+     */
+    public void selectAllChildren(Node parentNode)
+    {
+        Range range = ((Document) parentNode.getOwnerDocument()).createRange();
+        range.selectNodeContents(parentNode);
+        removeAllRanges();
+        addRange(range);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#selectionLanguageChange(boolean)
+     */
+    public void selectionLanguageChange(boolean langRTL)
+    {
+        // TODO
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Selection#toString()
+     */
+    public String toString()
+    {
+        if (getRangeCount() > 0) {
+            return getRangeAt(0).toString();
+        } else {
+            return "";
+        }
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultNativeRangeWrapper.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultNativeRangeWrapper.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultNativeRangeWrapper.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,73 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.xpn.xwiki.wysiwyg.client.dom.JavaScriptObject;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+
+/**
+ * Wraps a native range to be used by the selection who created this object. For instance, in order to remove a range
+ * from the selection we must keep a reference to the native range returned by the native selection. The selection
+ * implementation is responsible for setting and handling the reference to the native range.
+ * 
+ * @version $Id$
+ */
+public class DefaultNativeRangeWrapper extends DefaultRange implements NativeRangeWrapper
+{
+    /**
+     * The native range wrapped by this object.
+     */
+    private JavaScriptObject nativeRange;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see NativeRangeWrapper#getNativeRange()
+     */
+    public JavaScriptObject getNativeRange()
+    {
+        return nativeRange;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see NativeRangeWrapper#setNativeRange(JavaScriptObject)
+     */
+    public void setNativeRange(JavaScriptObject nativeRange)
+    {
+        this.nativeRange = nativeRange;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DefaultRange#cloneRange()
+     */
+    public Range cloneRange()
+    {
+        // We should use Object.clone when it is implemented in GWT
+        // See http://code.google.com/p/google-web-toolkit/issues/detail?id=1843
+        DefaultNativeRangeWrapper clone = new DefaultNativeRangeWrapper();
+        clone.setStart(getStartContainer(), getStartOffset());
+        clone.setEnd(getEndContainer(), getEndOffset());
+        return clone;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRange.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRange.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRange.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,685 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.DOMUtils;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.DocumentFragment;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.RangeCompare;
+
+/**
+ * This is a cross-browser implementation of the W3C Range specification.
+ * <p>
+ * Acknowledgment to Mozilla Foundation for making nsRange.cpp public.
+ * 
+ * @version $Id$
+ * @see http://hg.mozilla.org/mozilla-central/file/b945b4f67e7e/content/base/src/nsRange.cpp
+ */
+public class DefaultRange implements Range
+{
+    /**
+     * The DOM node containing the start of this range.
+     */
+    private Node startContainer;
+
+    /**
+     * The offset within the {@link #startContainer}.
+     */
+    private int startOffset;
+
+    /**
+     * The DOM node containing the end of this range.
+     */
+    private Node endContainer;
+
+    /**
+     * The offset within the {@link #endContainer}.
+     */
+    private int endOffset;
+
+    /**
+     * Specifies if both boundaries of this range have been successfully set to valid DOM nodes.
+     */
+    private boolean positioned;
+
+    /**
+     * Specifies if this range is in use.
+     */
+    private boolean detached;
+
+    /**
+     * Collection of DOM utility methods.
+     */
+    private DOMUtils domUtils = DOMUtils.getInstance();
+
+    /**
+     * Sets the boundaries of this range.
+     * 
+     * @param startContainer {@link #startContainer}
+     * @param startOffset {@link #startOffset}
+     * @param endContainer {@link #endContainer}
+     * @param endOffset {@link #endOffset}
+     */
+    private void setRange(Node startContainer, int startOffset, Node endContainer, int endOffset)
+    {
+        boolean valid = !(startContainer == null ^ endContainer == null);
+        if (valid && startContainer != null) {
+            valid = valid && startContainer.getOwnerDocument() == endContainer.getOwnerDocument();
+            valid = valid && startOffset >= 0 && startOffset <= domUtils.getLength(startContainer);
+            valid = valid && endOffset >= 0 && endOffset <= domUtils.getLength(endContainer);
+        }
+
+        if (!valid) {
+            throw new IllegalArgumentException();
+        }
+
+        this.startContainer = startContainer;
+        this.startOffset = startOffset;
+        this.endContainer = endContainer;
+        this.endOffset = endOffset;
+        positioned = startContainer != null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#cloneContents()
+     */
+    public DocumentFragment cloneContents()
+    {
+        if (detached) {
+            throw new IllegalStateException();
+        }
+
+        Node root = getCommonAncestorContainer();
+
+        if (startContainer == endContainer) {
+            return domUtils.cloneNodeContents(root, startOffset, endOffset);
+        }
+
+        DocumentFragment contents = ((Document) root.getOwnerDocument()).createDocumentFragment();
+
+        int startIndex = startOffset;
+        if (startContainer != root) {
+            contents.appendChild(domUtils.cloneNode(root, startContainer, startOffset, false));
+            startIndex = domUtils.getNodeIndex(domUtils.getChild(root, startContainer)) + 1;
+        }
+
+        if (endContainer != root) {
+            int endIndex = domUtils.getNodeIndex(domUtils.getChild(root, endContainer));
+            contents.appendChild(domUtils.cloneNodeContents(root, startIndex, endIndex));
+            contents.appendChild(domUtils.cloneNode(root, endContainer, endOffset, true));
+        } else {
+            contents.appendChild(domUtils.cloneNodeContents(root, startIndex, endOffset));
+        }
+
+        return contents;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#cloneRange()
+     */
+    public Range cloneRange()
+    {
+        if (detached) {
+            throw new IllegalStateException();
+        }
+
+        // We should use Object.clone when it is implemented in GWT
+        // See http://code.google.com/p/google-web-toolkit/issues/detail?id=1843
+        DefaultRange clone = new DefaultRange();
+        clone.setRange(startContainer, startOffset, endContainer, endOffset);
+        return clone;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#collapse(boolean)
+     */
+    public void collapse(boolean toStart)
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+
+        if (toStart) {
+            setRange(startContainer, startOffset, startContainer, startOffset);
+        } else {
+            setRange(endContainer, endOffset, endContainer, endOffset);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#compareBoundaryPoints(RangeCompare, Range)
+     */
+    public short compareBoundaryPoints(RangeCompare how, Range sourceRange)
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+        if (startContainer.getOwnerDocument() != sourceRange.getStartContainer().getOwnerDocument()) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (how) {
+            case START_TO_START:
+                return domUtils.comparePoints(startContainer, startOffset, sourceRange.getStartContainer(), sourceRange
+                    .getStartOffset());
+            case START_TO_END:
+                return domUtils.comparePoints(endContainer, endOffset, sourceRange.getStartContainer(), sourceRange
+                    .getStartOffset());
+            case END_TO_START:
+                return domUtils.comparePoints(startContainer, startOffset, sourceRange.getEndContainer(), sourceRange
+                    .getEndOffset());
+            case END_TO_END:
+                return domUtils.comparePoints(endContainer, endOffset, sourceRange.getEndContainer(), sourceRange
+                    .getEndOffset());
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#deleteContents()
+     */
+    public void deleteContents()
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+
+        Node root = getCommonAncestorContainer();
+
+        if (startContainer == endContainer) {
+            domUtils.deleteNodeContents(root, startOffset, endOffset);
+            // Take the range gravity into account.
+            collapse(true);
+        } else {
+            int startIndex = startOffset;
+            if (startContainer != root) {
+                domUtils.deleteNodeContents(root, startContainer, startOffset, false);
+                startIndex = domUtils.getNodeIndex(domUtils.getChild(root, startContainer)) + 1;
+            }
+
+            int endIndex = endOffset;
+            if (endContainer != root) {
+                endIndex = domUtils.getNodeIndex(domUtils.getChild(root, endContainer));
+                domUtils.deleteNodeContents(root, endContainer, endOffset, true);
+                // Take the range gravity into account.
+                setEnd(endContainer, 0);
+            } else {
+                // Take the range gravity into account.
+                setEnd(root, startIndex);
+            }
+            domUtils.deleteNodeContents(root, startIndex, endIndex);
+        }
+
+        // At this point we should be checking for the case where we have 2 adjacent text nodes left, each containing
+        // one of the range end points. The spec says the 2 nodes should be merged in that case, and to use Normalize()
+        // to do the merging, but calling Normalize() on the common parent to accomplish this might also normalize nodes
+        // that are outside the range but under the common parent. Need to verify with the range commitee members that
+        // this was the desired behavior. For now we don't merge anything!
+        // Filed as https://bugzilla.mozilla.org/show_bug.cgi?id=401276
+
+        collapseRangeAfterDelete();
+    }
+
+    /**
+     * Utility method that is used by {@link #deleteContents()} and {@link #extractContents()} to collapse the range in
+     * the correct place, under the range's root container (the range end points common container) as outlined by the
+     * Range spec: http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html
+     * <p>
+     * The assumption made by this method is that the delete or extract has been done already, and left the range in a
+     * state where there is no content between the 2 end points.
+     */
+    private void collapseRangeAfterDelete()
+    {
+        // Check if range gravity took care of collapsing the range for us!
+        if (isCollapsed()) {
+            // The range is collapsed so there's nothing for us to do.
+            //
+            // There are 2 possible scenarios here:
+            //
+            // 1. the range could've been collapsed prior to the delete/extract,
+            // which would've resulted in nothing being removed, so the range
+            // is already where it should be.
+            //
+            // 2. Prior to the delete/extract, the range's start and end were in
+            // the same container which would mean everything between them
+            // was removed, causing range gravity to collapse the range.
+        } else {
+            // The range isn't collapsed so figure out the appropriate place to collapse!
+            Node commonAncestor = getCommonAncestorContainer();
+
+            // Collapse to one of the end points if they are already in the
+            // commonAncestor. This should work ok since this method is called
+            // immediately after a delete or extract that leaves no content
+            // between the 2 end points!
+            if (startContainer == commonAncestor) {
+                collapse(true);
+            } else if (endContainer == commonAncestor) {
+                collapse(false);
+            } else {
+                // End points are at differing levels. We want to collapse to the
+                // point that is between the 2 subtrees that contain each point,
+                // under the common ancestor.
+                Node startAncestor = domUtils.getChild(commonAncestor, startContainer);
+                selectNode(startAncestor);
+                collapse(false);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#detach()
+     */
+    public void detach()
+    {
+        if (detached) {
+            throw new IllegalStateException();
+        }
+
+        detached = true;
+        setRange(null, 0, null, 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#extractContents()
+     * @see #deleteContents()
+     */
+    public DocumentFragment extractContents()
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+
+        Node root = getCommonAncestorContainer();
+        DocumentFragment contents;
+
+        if (startContainer == endContainer) {
+            contents = domUtils.extractNodeContents(root, startOffset, endOffset);
+            // Take the range gravity into account.
+            collapse(true);
+        } else {
+            contents = ((Document) root.getOwnerDocument()).createDocumentFragment();
+
+            int startIndex = startOffset;
+            if (startContainer != root) {
+                contents.appendChild(domUtils.extractNode(root, startContainer, startOffset, false));
+                startIndex = domUtils.getNodeIndex(domUtils.getChild(root, startContainer)) + 1;
+            }
+
+            if (endContainer != root) {
+                int endIndex = domUtils.getNodeIndex(domUtils.getChild(root, endContainer));
+                contents.appendChild(domUtils.extractNodeContents(root, startIndex, endIndex));
+                contents.appendChild(domUtils.extractNode(root, endContainer, endOffset, true));
+                // Take the range gravity into account.
+                setEnd(endContainer, 0);
+            } else {
+                contents.appendChild(domUtils.extractNodeContents(root, startIndex, endOffset));
+                // Take the range gravity into account.
+                setEnd(root, startIndex);
+            }
+        }
+
+        collapseRangeAfterDelete();
+
+        return contents;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#getCommonAncestorContainer()
+     */
+    public Node getCommonAncestorContainer()
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+
+        return domUtils.getNearestCommonAncestor(startContainer, endContainer);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#getEndContainer()
+     */
+    public Node getEndContainer()
+    {
+        if (!positioned) {
+            throw new IllegalStateException();
+        }
+
+        return endContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#getEndOffset()
+     */
+    public int getEndOffset()
+    {
+        if (!positioned) {
+            throw new IllegalStateException();
+        }
+
+        return endOffset;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#getStartContainer()
+     */
+    public Node getStartContainer()
+    {
+        if (!positioned) {
+            throw new IllegalStateException();
+        }
+
+        return startContainer;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#getStartOffset()
+     */
+    public int getStartOffset()
+    {
+        if (!positioned) {
+            throw new IllegalStateException();
+        }
+
+        return startOffset;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#insertNode(Node)
+     */
+    public void insertNode(Node newNode)
+    {
+        // Compute the number of nodes to insert.
+        int delta = newNode.getNodeType() == DOMUtils.DOCUMENT_FRAGMENT_NODE ? newNode.getChildNodes().getLength() : 1;
+        if (delta == 0) {
+            // There's nothing to insert.
+            return;
+        }
+        switch (startContainer.getNodeType()) {
+            case DOMUtils.CDATA_NODE:
+            case DOMUtils.COMMENT_NODE:
+            case Node.TEXT_NODE:
+                Node sibling = domUtils.splitNode(startContainer, startOffset);
+                sibling.getParentNode().insertBefore(newNode, sibling);
+                if (startContainer == endContainer) {
+                    setEnd(sibling, endOffset - startOffset);
+                } else if (startContainer.getParentNode() == endContainer) {
+                    // Move the end with the number of inserted nodes plus 1 text node resulted after the split.
+                    setEnd(endContainer, endOffset + delta + 1);
+                }
+                break;
+            case Node.ELEMENT_NODE:
+                domUtils.insertAt(startContainer, newNode, startOffset);
+                if (startContainer == endContainer) {
+                    // Move the end with the number of inserted nodes.
+                    setEnd(endContainer, endOffset + delta);
+                }
+                break;
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#isCollapsed()
+     */
+    public boolean isCollapsed()
+    {
+        if (detached || !positioned) {
+            throw new IllegalStateException();
+        }
+
+        return startContainer == endContainer && startOffset == endOffset;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#selectNode(Node)
+     */
+    public void selectNode(Node refNode)
+    {
+        Node parent = refNode.getParentNode();
+        int index = domUtils.getNodeIndex(refNode);
+        setRange(parent, index, parent, index + 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#selectNodeContents(Node)
+     */
+    public void selectNodeContents(Node refNode)
+    {
+        setRange(refNode, 0, refNode, domUtils.getLength(refNode));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setEnd(Node, int)
+     */
+    public void setEnd(Node refNode, int offset)
+    {
+        if (!positioned || startContainer.getOwnerDocument() != refNode.getOwnerDocument()
+            || isAfterOrDisconnected(startContainer, startOffset, refNode, offset)) {
+            setRange(refNode, offset, refNode, offset);
+        } else {
+            setRange(startContainer, startOffset, refNode, offset);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setEndAfter(Node)
+     */
+    public void setEndAfter(Node refNode)
+    {
+        setEnd(refNode.getParentNode(), domUtils.getNodeIndex(refNode) + 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setEndBefore(Node)
+     */
+    public void setEndBefore(Node refNode)
+    {
+        setEnd(refNode.getParentNode(), domUtils.getNodeIndex(refNode));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setStart(Node, int)
+     */
+    public void setStart(Node refNode, int offset)
+    {
+        if (!positioned || endContainer.getOwnerDocument() != refNode.getOwnerDocument()
+            || isAfterOrDisconnected(refNode, offset, endContainer, endOffset)) {
+            setRange(refNode, offset, refNode, offset);
+        } else {
+            setRange(refNode, offset, endContainer, endOffset);
+        }
+    }
+
+    /**
+     * Utility method for testing if one boundary point is after another or it they are disconnected.
+     * 
+     * @param alice first point's node
+     * @param aliceOffset first point's offset
+     * @param bob second point's node
+     * @param bobOffset second point's offset
+     * @return true if the first point is after the second point or if the given points are disconnected
+     */
+    private boolean isAfterOrDisconnected(Node alice, int aliceOffset, Node bob, int bobOffset)
+    {
+        try {
+            return domUtils.comparePoints(alice, aliceOffset, bob, bobOffset) == 1;
+        } catch (IllegalArgumentException e) {
+            // The given boundary points are disconnected.
+            return true;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setStartAfter(Node)
+     */
+    public void setStartAfter(Node refNode)
+    {
+        setStart(refNode.getParentNode(), domUtils.getNodeIndex(refNode) + 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#setStartBefore(Node)
+     */
+    public void setStartBefore(Node refNode)
+    {
+        setStart(refNode.getParentNode(), domUtils.getNodeIndex(refNode));
+    }
+
+    /**
+     * The {@link #surroundContents(Node)} method raises an exception if the Range partially selects a non-Text node. An
+     * example of a Range for which {@link #surroundContents(Node)} raises an exception is:
+     * <code>&lt;FOO&gt;A<strong>B&lt;BAR&gt;C</strong>D&lt;/BAR&gt;E&lt;/FOO&gt;</code>
+     * 
+     * @return true if {@link #surroundContents(Node)} can be called on the current range
+     */
+    private boolean canSurroundContents()
+    {
+        if (startContainer == endContainer) {
+            return true;
+        }
+
+        boolean startIsText = startContainer.getNodeType() == Node.TEXT_NODE;
+        boolean endIsText = endContainer.getNodeType() == Node.TEXT_NODE;
+        Node startParent = startContainer.getParentNode();
+        Node endParent = endContainer.getParentNode();
+
+        // I'm using the bitwise AND operator to reduce the cyclomatic complexity.
+        boolean can = (startIsText & endIsText) && startParent != null && startParent == endParent;
+        can = can || (startIsText && startParent != null && startParent == endContainer);
+        return can || (endIsText && endParent != null && endParent == startContainer);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#surroundContents(Node)
+     */
+    public void surroundContents(Node newParent)
+    {
+        // The surroundContents() method raises an exception if the Range partially selects a non-Text node. An example
+        // of a Range for which surroundContents() raises an exception is: <FOO>A|B<BAR>C|D</BAR>E</FOO>
+        if (!canSurroundContents()) {
+            throw new IllegalStateException();
+        }
+
+        // Extract the contents within the range.
+        DocumentFragment contents = extractContents();
+
+        // Spec says we need to remove all of newParent's children prior to insertion.
+        Node child = newParent.getFirstChild();
+        while (child != null) {
+            newParent.removeChild(child);
+            child = newParent.getFirstChild();
+        }
+
+        // Insert newParent at the range's start point.
+        insertNode(newParent);
+
+        // Append the contents we extracted under newParent.
+        newParent.appendChild(contents);
+
+        // Select newParent, and its contents.
+        selectNode(newParent);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#toHTML()
+     */
+    public String toHTML()
+    {
+        if (detached) {
+            throw new IllegalStateException();
+        }
+
+        if (positioned) {
+            return cloneContents().getInnerHTML();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see Range#toString()
+     */
+    public String toString()
+    {
+        if (detached) {
+            throw new IllegalStateException();
+        }
+
+        if (positioned) {
+            return cloneContents().getInnerText();
+        } else {
+            return "";
+        }
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRangeFactory.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRangeFactory.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultRangeFactory.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,46 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.RangeFactory;
+
+/**
+ * The default {@link RangeFactory} implementation.
+ * 
+ * @version $Id$
+ */
+public class DefaultRangeFactory implements RangeFactory
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see RangeFactory#createRange(Document)
+     */
+    public Range createRange(Document doc)
+    {
+        // We create a new native range wrapper without passing a reference to a native range. The selection
+        // implementation should initialize this reference when the new range will be added.
+        Range range = new DefaultNativeRangeWrapper();
+        range.selectNodeContents(doc.getBody());
+        return range;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,127 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla.NativeRange;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla.NativeSelection;
+
+/**
+ * The default selection implementation for browsers supporting the W3C Range specification and following Mozilla
+ * Selection API.
+ * 
+ * @version $Id$
+ */
+public class DefaultSelection extends AbstractSelection
+{
+    /**
+     * The underlying native selection object provided by the browser.
+     */
+    private final NativeSelection nativeSelection;
+
+    /**
+     * Creates a new selection object. This object will handle the conversion of {@link Range} objects to the native
+     * range supported by the browser. The native ranges obtained will be applied to the underlying native selection.
+     * 
+     * @param nativeSelection the underlying native selection to be used
+     */
+    public DefaultSelection(NativeSelection nativeSelection)
+    {
+        this.nativeSelection = nativeSelection;
+    }
+
+    /**
+     * @return {@link #nativeSelection}
+     */
+    protected NativeSelection getNativeSelection()
+    {
+        return nativeSelection;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#addRange(Range)
+     */
+    public void addRange(Range range)
+    {
+        NativeRangeWrapper wrapper = ((NativeRangeWrapper) range);
+        if (wrapper.getNativeRange() == null) {
+            Document doc = (Document) range.getStartContainer().getOwnerDocument();
+            wrapper.setNativeRange(NativeRange.newInstance(doc));
+        }
+        NativeRange nativeRange = wrapper.getNativeRange().cast();
+        nativeRange.setStart(range.getStartContainer(), range.getStartOffset());
+        nativeRange.setEnd(range.getEndContainer(), range.getEndOffset());
+        nativeSelection.addRange(nativeRange);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#getRangeAt(int)
+     */
+    public Range getRangeAt(int index)
+    {
+        if (index < 0 || index >= getRangeCount()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        NativeRange nativeRange = nativeSelection.getRangeAt(index);
+        Range range = ((Document) nativeRange.getStartContainer().getOwnerDocument()).createRange();
+        range.setStart(nativeRange.getStartContainer(), nativeRange.getStartOffset());
+        range.setEnd(nativeRange.getEndContainer(), nativeRange.getEndOffset());
+        ((NativeRangeWrapper) range).setNativeRange(nativeRange);
+
+        return range;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#getRangeCount()
+     */
+    public int getRangeCount()
+    {
+        return nativeSelection.getRangeCount();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#removeAllRanges()
+     */
+    public void removeAllRanges()
+    {
+        nativeSelection.removeAllRanges();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#removeRange(Range)
+     */
+    public void removeRange(Range range)
+    {
+        NativeRangeWrapper wrapper = (NativeRangeWrapper) range;
+        nativeSelection.removeRange((NativeRange) wrapper.getNativeRange());
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelectionManager.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelectionManager.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/DefaultSelectionManager.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,43 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Selection;
+import com.xpn.xwiki.wysiwyg.client.dom.SelectionManager;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla.NativeSelection;
+
+/**
+ * The default {@link SelectionManager} implementation. Retrieves the selection object using Mozilla's API.
+ * 
+ * @version $Id$
+ */
+public class DefaultSelectionManager implements SelectionManager
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see SelectionManager#getSelection(Document)
+     */
+    public Selection getSelection(Document document)
+    {
+        return new DefaultSelection(NativeSelection.getInstance(document));
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/NativeRangeWrapper.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/NativeRangeWrapper.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/NativeRangeWrapper.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,42 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal;
+
+import com.xpn.xwiki.wysiwyg.client.dom.JavaScriptObject;
+
+/**
+ * Interface to access the native range object wrapped by a specific range implementation.
+ * 
+ * @version $Id$
+ */
+public interface NativeRangeWrapper
+{
+    /**
+     * @return the native range wrapped by this object.
+     */
+    JavaScriptObject getNativeRange();
+
+    /**
+     * Sets the native range to be wrapped by this object.
+     * 
+     * @param nativeRange the native range to be wrapped by this object.
+     */
+    void setNativeRange(JavaScriptObject nativeRange);
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/ControlRange.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/ControlRange.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/ControlRange.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,90 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Element;
+
+/**
+ * A control range is a list of DOM elements. When an element is added to a control range and that range is selected the
+ * element will be decorated with special markers for editing that element. For instance if an image is selected this
+ * way the user will be able to resize the image.
+ * 
+ * @version $Id$
+ */
+public final class ControlRange extends NativeRange
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected ControlRange()
+    {
+    }
+
+    /**
+     * Creates a new control range from the given document.
+     * 
+     * @param doc The owner document of the created control range.
+     * @return A new control range within the specified document.
+     */
+    public static native ControlRange newInstance(Document doc)
+    /*-{
+        var controlRange = doc.body.createControlRange();
+        controlRange.ownerDocument = doc;
+        return controlRange;
+    }-*/;
+
+    /**
+     * @return The number of objects included the range.
+     */
+    public native int getLength()
+    /*-{
+        return this.length;
+    }-*/;
+
+    /**
+     * Adds an element to the control range.
+     * 
+     * @param element The element to include in this range.
+     */
+    public native void add(Element element)
+    /*-{
+        this.addElement(element);
+    }-*/;
+
+    /**
+     * @param index Zero-based index of the element to get.
+     * @return The element at the specified index in this control range.
+     */
+    public native Element get(int index)
+    /*-{
+        return this.item(index);
+    }-*/;
+
+    /**
+     * Removes an element from this control range.
+     * 
+     * @param index Zero-based index of the element to remove from the control range.
+     */
+    public native void remove(int index)
+    /*-{
+        this.remove(index);
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IEDOMUtils.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IEDOMUtils.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IEDOMUtils.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,141 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.DOMUtils;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Element;
+import com.xpn.xwiki.wysiwyg.client.dom.Style;
+
+/**
+ * Contains methods from {@link DOMUtils} that require a different implementation in Internet Explorer.
+ * 
+ * @version $Id$
+ */
+public class IEDOMUtils extends DOMUtils
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getComputedStyleProperty(Element, String)
+     */
+    public String getComputedStyleProperty(Element el, String propertyName)
+    {
+        return getComputedStylePropertyIE(el, Style.toCamelCase(propertyName));
+    }
+
+    /**
+     * @param el The element for which we retrieve the computed value of the given style property.
+     * @param propertyName The name of the property in camel case format.
+     * @return The computed value of the given style property on the specified element.
+     */
+    private native String getComputedStylePropertyIE(Element el, String propertyName)
+    /*-{
+        // We force it to be a string because we treat it as a string in the java code.
+        return '' + el.currentStyle[propertyName];
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#importNode(Document, Node, boolean)
+     */
+    public Node importNode(Document doc, Node externalNode, boolean deep)
+    {
+        switch (externalNode.getNodeType()) {
+            case Node.TEXT_NODE:
+                return doc.createTextNode(externalNode.getNodeValue());
+            case Node.ELEMENT_NODE:
+                Element externalElement = Element.as(externalNode);
+                Element internalElement = doc.xCreateElement(externalElement.getTagName());
+                JsArrayString attrNames = getAttributeNames(externalElement);
+                for (int i = 0; i < attrNames.length(); i++) {
+                    String attrName = attrNames.get(i);
+                    internalElement.setAttribute(attrName, externalElement.getAttribute(attrName));
+                }
+                if (deep) {
+                    // TODO
+                }
+                return internalElement;
+            default:
+                throw new IllegalArgumentException("Cannot import node of type " + externalNode.getNodeType() + "!");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getAttributeNames(Element)
+     */
+    public native JsArrayString getAttributeNames(Element element)
+    /*-{
+        var attrNames = [];
+        for(var i = 0; i < element.attributes.length; i++){
+            if(element.attributes[i].specified) {
+                attrNames.push(element.attributes[i].nodeName);
+            }
+        }
+        return attrNames;
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#setInnerHTML(Element, String)
+     * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=3146
+     */
+    public void setInnerHTML(Element element, String html)
+    {
+        element.setInnerHTML("<span>iesucks</span>" + html);
+        element.removeChild(element.getFirstChild());
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getAttribute(Element, String)
+     */
+    public String getAttribute(Element element, String name)
+    {
+        return element.getAttribute(name) + "";
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getInnerText(Element)
+     */
+    public native String getInnerText(Element element)
+    /*-{
+        return element.innerText;
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#hasAttribute(Element, String)
+     */
+    public native boolean hasAttribute(Element element, String attrName)
+    /*-{
+        return element.getAttribute(attrName) != null;
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,496 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.DOMUtils;
+import com.xpn.xwiki.wysiwyg.client.dom.Element;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.RangeCompare;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.AbstractSelection;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.ie.TextRange.Unit;
+
+/**
+ * The implementation of Mozilla's selection specification using Internet Explorer's selection API.
+ * 
+ * @version $Id$
+ */
+public class IESelection extends AbstractSelection
+{
+    /**
+     * Specifies where a range starts or ends inside the DOM tree.
+     */
+    protected static final class RangeBoundary
+    {
+        /**
+         * The node containing the range boundary.
+         */
+        private final Node container;
+
+        /**
+         * The offset within the {@link #container} where the boundary is placed.
+         */
+        private final int offset;
+
+        /**
+         * Creates a new range boundary.
+         * 
+         * @param container {@link #container}
+         * @param offset {@link #offset}
+         */
+        public RangeBoundary(Node container, int offset)
+        {
+            this.container = container;
+            this.offset = offset;
+        }
+
+        /**
+         * @return {@link #container}
+         */
+        public Node getContainer()
+        {
+            return container;
+        }
+
+        /**
+         * @return {@link #offset}
+         */
+        public int getOffset()
+        {
+            return offset;
+        }
+
+    }
+
+    /**
+     * The list of elements supporting control selection.
+     */
+    private static final String[] SELECTABLE_ELEMENTS = new String[] {"img", "button"};
+
+    /**
+     * The underlying native selection object provided by the browser.
+     */
+    private final NativeSelection nativeSelection;
+
+    /**
+     * The element used to mark the start of the selection.
+     * 
+     * @see #addRange(Range)
+     */
+    private final Element startMarker;
+
+    /**
+     * The native text range used to set the start of the selection.
+     * 
+     * @see TextRange#setEndPoint(RangeCompare, TextRange)
+     * @see #addRange(Range)
+     */
+    private final TextRange startRef;
+
+    /**
+     * The element used to mark the end of the selection.
+     * 
+     * @see #addRange(Range)
+     */
+    private final Element endMarker;
+
+    /**
+     * The native text range used to set the end of the selection.
+     * 
+     * @see TextRange#setEndPoint(RangeCompare, TextRange)
+     * @see #addRange(Range)
+     */
+    private final TextRange endRef;
+
+    /**
+     * Creates a new instance that wraps the given native selection object. This object will be used to implement
+     * Mozilla's selection specification.
+     * 
+     * @param nativeSelection the underlying native selection object to be used
+     */
+    public IESelection(NativeSelection nativeSelection)
+    {
+        this.nativeSelection = nativeSelection;
+        startMarker = createBoundaryMarker();
+        startRef = TextRange.newInstance(nativeSelection.getOwnerDocument());
+        endMarker = (Element) startMarker.cloneNode(true);
+        endRef = startRef.duplicate();
+    }
+
+    /**
+     * @return {@link #nativeSelection}
+     */
+    protected NativeSelection getNativeSelection()
+    {
+        return nativeSelection;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#addRange(Range)
+     */
+    public void addRange(Range range)
+    {
+        if (range.getStartContainer() == range.getEndContainer()
+            && range.getStartContainer().getNodeType() == Node.ELEMENT_NODE
+            && range.getStartOffset() == range.getEndOffset() - 1) {
+            Node selectedNode = range.getStartContainer().getChildNodes().getItem(range.getStartOffset());
+            // Test if the selected node supports control selection.
+            if (supportsControlSelection(selectedNode)) {
+                ControlRange controlRange = ControlRange.newInstance(nativeSelection.getOwnerDocument());
+                controlRange.add((Element) selectedNode);
+                controlRange.select();
+                return;
+            }
+        }
+
+        // Otherwise use text selection.
+        addTextRange(adjustRangeAndDOM(range));
+    }
+
+    /**
+     * @param node a DOM node
+     * @return {@code true} if the given node supports control selection, {@code false} otherwise
+     */
+    private boolean supportsControlSelection(Node node)
+    {
+        for (int i = 0; i < SELECTABLE_ELEMENTS.length; i++) {
+            if (SELECTABLE_ELEMENTS[i].equalsIgnoreCase(node.getNodeName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Adjusts the given range and the DOM tree prior to selecting the range. We have to do this because IE selection
+     * boundaries cannot, in most of the cases, be placed inside empty elements or empty text nodes or at the beginning
+     * of a text node.
+     * 
+     * @param range the range to be adjusted
+     * @return the adjusted range
+     */
+    private Range adjustRangeAndDOM(Range range)
+    {
+        Node start = range.getStartContainer();
+        if (range.isCollapsed() && start.getNodeType() == Node.TEXT_NODE && range.getStartOffset() == 0
+            && !isTextBefore(start)) {
+            start.setNodeValue("\u00A0" + start.getNodeValue());
+            Range adjusted = range.cloneRange();
+            adjusted.setEnd(start, 1);
+            return adjusted;
+        }
+        return range;
+    }
+
+    /**
+     * Utility method for testing if the previous sibling (skipping empty text nodes) of a DOM node is a non-empty text
+     * node.
+     * 
+     * @param node a DOM node
+     * @return true if the previous sibling, skipping empty text nodes, of the given node is a non-empty text node
+     */
+    private boolean isTextBefore(Node node)
+    {
+        Node sibling = node.getPreviousSibling();
+        // Skip empty text nodes.
+        while (sibling != null && sibling.getNodeType() == Node.TEXT_NODE && sibling.getNodeValue().length() == 0) {
+            sibling = sibling.getPreviousSibling();
+        }
+        return sibling != null && sibling.getNodeType() == Node.TEXT_NODE;
+    }
+
+    /**
+     * Creates a text selection from the given range.
+     * 
+     * @param range the range to be added to the selection
+     */
+    protected void addTextRange(Range range)
+    {
+        DOMUtils domUtils = DOMUtils.getInstance();
+        int leftOffset = 0;
+        int rightOffset = domUtils.getLength(range.getEndContainer()) - range.getEndOffset();
+
+        switch (range.getStartContainer().getNodeType()) {
+            case Node.TEXT_NODE:
+                leftOffset = range.getStartOffset();
+                // fall through
+            case DOMUtils.CDATA_NODE:
+            case DOMUtils.COMMENT_NODE:
+                range.getStartContainer().getParentNode().insertBefore(startMarker, range.getStartContainer());
+                break;
+            case Node.ELEMENT_NODE:
+                domUtils.insertAt(range.getStartContainer(), startMarker, range.getStartOffset());
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        startRef.moveToElementText(startMarker);
+        startRef.moveEnd(Unit.CHARACTER, leftOffset);
+
+        switch (range.getEndContainer().getNodeType()) {
+            case DOMUtils.CDATA_NODE:
+            case DOMUtils.COMMENT_NODE:
+                rightOffset = 0;
+                // fall through
+            case Node.TEXT_NODE:
+                domUtils.insertAfter(endMarker, range.getEndContainer());
+                break;
+            case Node.ELEMENT_NODE:
+                domUtils.insertAt(range.getEndContainer(), endMarker, range.getEndContainer().getChildNodes()
+                    .getLength()
+                    - rightOffset);
+                rightOffset = 0;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        endRef.moveToElementText(endMarker);
+        endRef.moveStart(Unit.CHARACTER, -rightOffset);
+
+        TextRange textRange = TextRange.newInstance(nativeSelection.getOwnerDocument());
+        textRange.setEndPoint(RangeCompare.END_TO_START, startRef);
+        textRange.setEndPoint(RangeCompare.START_TO_END, endRef);
+
+        domUtils.detach(startMarker);
+        domUtils.detach(endMarker);
+
+        textRange.select();
+    }
+
+    /**
+     * @return an element that can be used a range boundary marker for this selection
+     */
+    protected Element createBoundaryMarker()
+    {
+        ImageElement marker = nativeSelection.getOwnerDocument().xCreateImageElement();
+        marker.setWidth(0);
+        marker.setHeight(0);
+        return marker.cast();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#getRangeAt(int)
+     */
+    public Range getRangeAt(int index)
+    {
+        if (index != 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        NativeRange nativeRange = nativeSelection.createRange();
+        Range range = nativeRange.getOwnerDocument().createRange();
+
+        if (nativeRange.isTextRange()) {
+            TextRange textRange = (TextRange) nativeRange;
+
+            RangeBoundary start = getBoundary(textRange, true);
+            range.setStart(start.getContainer(), start.getOffset());
+
+            RangeBoundary end = getBoundary(textRange, false);
+            range.setEnd(end.getContainer(), end.getOffset());
+        } else {
+            range.selectNode(((ControlRange) nativeRange).get(0));
+        }
+
+        return range;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#getRangeCount()
+     */
+    public int getRangeCount()
+    {
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#removeAllRanges()
+     */
+    public void removeAllRanges()
+    {
+        getNativeSelection().empty();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see AbstractSelection#removeRange(Range)
+     */
+    public void removeRange(Range range)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Computes the start or end container of a text range.
+     * 
+     * @param textRange the text range for which to compute the boundary container
+     * @param start specifies which boundary container to compute
+     * @return the container of the range's start , if start is true, or the container of the range's end, otherwise
+     */
+    protected RangeBoundary getBoundary(TextRange textRange, boolean start)
+    {
+        // Determine the element containing the specified range boundary.
+        TextRange refRange = textRange.duplicate();
+        refRange.collapse(start);
+        Node container = refRange.getParentElement();
+        // The specified range boundary is either between child nodes or inside a text node.
+
+        // We use a text range to find the text node that could possibly contain the range boundary.
+        TextRange searchRange = TextRange.newInstance(textRange.getOwnerDocument());
+        searchRange.moveToElementText((Element) container);
+
+        // The claws used to catch the specified range boundary.
+        RangeCompare compareStart = RangeCompare.valueOf(true, start);
+        RangeCompare compareEnd = RangeCompare.valueOf(false, start);
+
+        // Iterate through all child nodes in search for the range boundary.
+        Node child = container.getFirstChild();
+        int offset = 0;
+        while (child != null) {
+            if (child.getNodeType() == Node.ELEMENT_NODE) {
+                // Move the reference range before this child element.
+                moveTextRangeBeforeElement(refRange, (Element) child);
+                // Let's see if the boundary is before this child element.
+                if (textRange.compareEndPoints(compareStart, refRange) <= 0) {
+                    break;
+                }
+            } else if (child.getNodeType() == Node.TEXT_NODE && child.getNodeValue().length() > 0) {
+                // Select this text node.
+                refRange = searchRange.duplicate();
+                // We have to convert nbsp's to plain spaces because TextRange#getText seems to do so.
+                if (!refRange.findText(child.getNodeValue().replace('\u00A0', '\u0020'), 0, 4)) {
+                    // We shouldn't get here!
+                    throw new RuntimeException("Unexpected behavior of TextRange#findText");
+                }
+                // See if the range boundary is inside this text node.
+                if (textRange.compareEndPoints(compareEnd, refRange) <= 0) {
+                    if (textRange.compareEndPoints(compareStart, refRange) >= 0) {
+                        container = child;
+                        // Now we have to compute the offset within this text node.
+                        offset = getOffset(refRange, textRange, start);
+                    }
+                    // We either found the boundary or passed beyond it.
+                    break;
+                }
+                // Update the search range.
+                searchRange.setEndPoint(RangeCompare.END_TO_START, refRange);
+            }
+            child = child.getNextSibling();
+            offset++;
+        }
+        return new RangeBoundary(container, offset);
+    }
+
+    /**
+     * Moves the given text range before the specified element.
+     * 
+     * @param textRange the text range to be moved
+     * @param element the element before which the text range is moved
+     */
+    protected void moveTextRangeBeforeElement(TextRange textRange, Element element)
+    {
+        textRange.moveToElementText(element);
+        // Sometimes moveToElementText has unexpected results. Let's test if textRange starts before element and if not
+        // then try something different.
+        int left = getLeft(element);
+        int top = getTop(element);
+        if (textRange.getOffsetLeft() != left || top < textRange.getOffsetTop()
+            || top > (textRange.getOffsetTop() + getFirstLineHeight(textRange))) {
+            // This can fail moving the text range before the element too (that's why we tried moveToElementText first).
+            textRange.moveToPoint(left, top);
+            // Don't bet on the result!
+        }
+    }
+
+    /**
+     * NOTE: For a {@code strong} element that starts in the middle of a line and spans multiple lines this method
+     * returns the distance in pixels from its first character (provided it starts with text) to the left boundary of
+     * the parent window. This is important since the bounding rectangle of the {@code strong} element can have the
+     * width of the parent window so the distance from the left side of this bounding rectangle to the left boundary of
+     * the parent window could be 0.
+     * 
+     * @param element a DOM element
+     * @return the distance, in pixels, from the given element's start point to the left boundary of the parent window
+     */
+    protected native int getLeft(Element element)
+    /*-{
+        var left = -element.ownerDocument.documentElement.scrollLeft;
+        while (element) {
+            left += element.offsetLeft + element.clientLeft - element.scrollLeft;
+            element = element.offsetParent;
+        }
+        return left;
+    }-*/;
+
+    /**
+     * @param element a DOM element
+     * @return the distance, in pixels, from the given element's start point to the top boundary of the parent window
+     */
+    protected native int getTop(Element element)
+    /*-{
+        var top = -element.ownerDocument.documentElement.scrollTop;
+        while (element) {
+            top += element.offsetTop + element.clientTop - element.scrollTop;
+            element = element.offsetParent;
+        }
+        return top;
+    }-*/;
+
+    /**
+     * @param textRange a text range
+     * @return the height, in pixels, of the first line selected by the given text range
+     */
+    protected native int getFirstLineHeight(TextRange textRange)
+    /*-{
+        var firstLineRect = textRange.getClientRects()[0];
+        return firstLineRect.bottom - firstLineRect.top;
+    }-*/;
+
+    /**
+     * Computes the number of characters between the start of the left range to the specified boundary of the right
+     * range. The given ranges need to overlap and the start of the left range has to be before or equal to the
+     * specified boundary of the right range.
+     * 
+     * @param left the left text range
+     * @param right the rich text range
+     * @param rightBoundary specifies which boundary of the right text range to consider. Use {@code true} for start
+     *            boundary and {@code false} for end boundary.
+     * @return the offset of the right range from the start of the left range
+     */
+    protected int getOffset(TextRange left, TextRange right, boolean rightBoundary)
+    {
+        int offset = 0;
+        RangeCompare whichEndPoints = RangeCompare.valueOf(rightBoundary, true);
+        while (left.compareEndPoints(whichEndPoints, right) < 0 && left.moveStart(Unit.CHARACTER, 1) > 0) {
+            offset++;
+        }
+        return offset;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelectionManager.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelectionManager.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/IESelectionManager.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,42 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Selection;
+import com.xpn.xwiki.wysiwyg.client.dom.SelectionManager;
+
+/**
+ * {@link SelectionManager} implementation for Internet Explorer.
+ * 
+ * @version $Id$
+ */
+public final class IESelectionManager implements SelectionManager
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see SelectionManager#getSelection(Document)
+     */
+    public Selection getSelection(Document doc)
+    {
+        return new IESelection(NativeSelection.getInstance(doc));
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeRange.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeRange.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeRange.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,74 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+
+/**
+ * Base class for {@link TextRange} and {@link ControlRange}, the two types of range provided by Internet Explorer.
+ * 
+ * @version $Id$
+ */
+public class NativeRange extends JavaScriptObject
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected NativeRange()
+    {
+    }
+
+    /**
+     * Makes the selection equal to the current object. When applied to a TextRange object, the select method causes the
+     * current object to be highlighted. When applied to a ControlRange object, the select method produces a shaded
+     * rectangle around the elements in the control range.
+     */
+    public final native void select()
+    /*-{
+        this.select();
+    }-*/;
+
+    /**
+     * @return The document used to create this range.
+     */
+    public final native Document getOwnerDocument()
+    /*-{
+        return this.ownerDocument;
+    }-*/;
+
+    /**
+     * This method is needed because <code>instanceof</code> operator returns true all the time when applied on a
+     * overlay type. For instance:<br/>
+     * 
+     * <pre>
+     * TextRange textRange = TextRange.newInstance(doc);
+     * boolean result = textRange instanceof TextRange; // result is true, which is right.
+     * result = textRange instanceof ControlRange // result is also true, which is wrong.
+     * </pre>
+     * 
+     * @return true if this is a text range, and false if it is a control range.
+     * @see http://code.google.com/p/google-web-toolkit/wiki/OverlayTypes
+     */
+    public final native boolean isTextRange()
+    /*-{
+        return this.item ? false : true;
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeSelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeSelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/NativeSelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,101 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+
+/**
+ * Wraps the selection JavaScript object provided by Internet Explorer.
+ * 
+ * @version $Id$
+ */
+public final class NativeSelection extends JavaScriptObject
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected NativeSelection()
+    {
+    }
+
+    /**
+     * @param doc The DOM document for which to retrieve the selection object.
+     * @return The selection object associated with the given in-line frame.
+     */
+    public static synchronized native NativeSelection getInstance(Document doc)
+    /*-{
+        var selection = doc.selection;
+        selection.ownerDocument = doc;
+        return selection;
+    }-*/;
+
+    /**
+     * Retrieves the type of selection.
+     * 
+     * @return One of the following values:
+     *         <ul>
+     *         <li>none: No selection/insertion point.</li>
+     *         <li>text: Specifies a text selection.</li>
+     *         <li>control: Specifies a control selection, which enables dimension controls allowing the selected object
+     *         to be resized.</li>
+     *         </ul>
+     */
+    public native String getType()
+    /*-{
+        return this.type;
+    }-*/;
+
+    /**
+     * Clears the contents of the selection.
+     */
+    public native void clear()
+    /*-{
+        this.clear();
+    }-*/;
+
+    /**
+     * Creates a TextRange object from the current text selection, or a ControlRange object from a control selection.
+     * 
+     * @return The created range object.
+     */
+    public native NativeRange createRange()
+    /*-{
+        var range = this.createRange();
+        range.ownerDocument = this.ownerDocument;
+        return range;
+    }-*/;
+
+    /**
+     * Cancels the current selection and sets the selection type to none.
+     */
+    public native void empty()
+    /*-{
+        this.empty();
+    }-*/;
+
+    /**
+     * @return The document associated with this selection object.
+     */
+    public native Document getOwnerDocument()
+    /*-{
+        return this.ownerDocument;
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/TextRange.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/TextRange.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/ie/TextRange.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,520 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.ie;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Element;
+import com.xpn.xwiki.wysiwyg.client.dom.RangeCompare;
+
+/**
+ * A text range is a DOM fragment that usually starts and ends inside a text node. It can be used to visually select a
+ * continuous text fragment.
+ * 
+ * @version $Id$
+ */
+public final class TextRange extends NativeRange
+{
+    /**
+     * A unit defining a fragment of a text range. It is used when moving the end points of a text range.
+     */
+    public static enum Unit
+    {
+        /**
+         * A character.
+         */
+        CHARACTER,
+        /**
+         * A word is a collection of characters terminated by a space or some other white-space character, such as a
+         * tab.
+         */
+        WORD,
+        /**
+         * A sentence is a collection of words terminated by a punctuation character, such as a period.
+         */
+        SENTENCE,
+        /**
+         * The start or end of the original range.
+         */
+        TEXTEDIT
+    }
+
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected TextRange()
+    {
+    }
+
+    /**
+     * Creates a new text range for the given document. This range can be used to select only DOM nodes within this
+     * document.
+     * 
+     * @param doc The owner document of the created range.
+     * @return The created text range.
+     */
+    public static native TextRange newInstance(Document doc)
+    /*-{
+        var textRange = doc.body.createTextRange();
+        textRange.ownerDocument = doc;
+        return textRange;
+    }-*/;
+
+    /**
+     * @return The HTML source as a valid HTML fragment.
+     */
+    public native String getHTML()
+    /*-{
+        return this.htmlText;
+    }-*/;
+
+    /**
+     * Pastes HTML text into this text range, replacing any previous text and HTML elements in the range. This method
+     * might alter the HTML text to make it fit the given text range. For example, pasting a table cell into a text
+     * range that does not contain a table might cause the method to insert a table element. For predictable results,
+     * paste only well-formed HTML text that fits within the given text range.
+     * 
+     * @param html The HTML text to paste. The string can contain text and any combination of the HTML tags.
+     */
+    public native void setHTML(String html)
+    /*-{
+        this.pasteHTML(html);
+    }-*/;
+
+    /**
+     * @return The text contained within the range.
+     */
+    public native String getText()
+    /*-{
+        return this.text;
+    }-*/;
+
+    /**
+     * @param text The text to be placed inside the range.
+     */
+    public native void setText(String text)
+    /*-{
+        this.text = text;
+    }-*/;
+
+    /**
+     * Moves the insertion point to the beginning or end of the current range.
+     * 
+     * @param toStart if true moves the insertion point to the beginning of the text range. Otherwise, moves the
+     *            insertion point to the end of the text range.
+     */
+    public native void collapse(boolean toStart)
+    /*-{
+        this.collapse(toStart);
+    }-*/;
+
+    /**
+     * Compares an end point of a TextRange object with an end point of another range. A text range has two end points.
+     * One end point is located at the beginning of the text range, and the other is located at the end of the text
+     * range. An end point also can be characterized as the position between two characters in an HTML document.
+     * 
+     * @param how Specifies which end points to compare.
+     * @param range The range object to compare with this object.
+     * @return One of the following possible values:
+     *         <ul>
+     *         <li>-1 if the end point of this object is further to the left than the end point of the given range.</li>
+     *         <li>0 if the end point of this object is at the same location as the end point of the given range.</li>
+     *         <li>1 if the end point of this object is further to the right than the end point of the given range.</li>
+     *         </ul>
+     */
+    public short compareEndPoints(RangeCompare how, TextRange range)
+    {
+        // In Internet Explorer the meaning of RangeCompare is reversed, so we reverse the end points.
+        return compareEndPoints(how.reverse().toString(), range);
+    }
+
+    /**
+     * Compares an end point of a TextRange object with an end point of another range. A text range has two end points.
+     * One end point is located at the beginning of the text range, and the other is located at the end of the text
+     * range. An end point also can be characterized as the position between two characters in an HTML document.
+     * 
+     * @param type Can be one of the following:
+     *            <ul>
+     *            <li>StartToEnd: Compare the start of this TextRange object with the end of the range parameter.</li>
+     *            <li>StartToStart: Compare the start of this TextRange object with the start of the given range
+     *            parameter.</li>
+     *            <li>EndToStart: Compare the end of this TextRange object with the start of the range parameter.</li>
+     *            <li>EndToEnd: Compare the end of this TextRange object with the end of the range parameter.</li>
+     *            </ul>
+     * @param range TextRange object that specifies the range to compare with this object.
+     * @return One of the following possible values:
+     *         <ul>
+     *         <li>-1 if the end point of this object is further to the left than the end point of the given range.</li>
+     *         <li>0 if the end point of this object is at the same location as the end point of the given range.</li>
+     *         <li>1 if the end point of this object is further to the right than the end point of the given range.</li>
+     *         </ul>
+     */
+    private native short compareEndPoints(String type, TextRange range)
+    /*-{
+        return this.compareEndPoints(type, range);
+    }-*/;
+
+    /**
+     * @return A duplicate of this TextRange.
+     */
+    public native TextRange duplicate()
+    /*-{
+        var clone = this.duplicate();
+        clone.ownerDocument = this.ownerDocument;
+        return clone;
+    }-*/;
+
+    /**
+     * Collapses the given text range and moves the empty range by the given number of units.
+     * 
+     * @param unit Specifies the units to move
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    public int move(Unit unit, int count)
+    {
+        return move(unit.toString(), count);
+    }
+
+    /**
+     * Collapses the given text range and moves the empty range by the given number of units.
+     * 
+     * @param unit Specifies the units to move, using one of the following values:
+     *            <ul>
+     *            <li>character: Moves one or more characters.</li>
+     *            <li>word: Moves one or more words. A word is a collection of characters terminated by a space or some
+     *            other white-space character, such as a tab.</li>
+     *            <li>sentence: Moves one or more sentences. A sentence is a collection of words terminated by a
+     *            punctuation character, such as a period.</li>
+     *            <li>textedit: Moves to the start or end of the original range.</li>
+     *            </ul>
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    private native int move(String unit, int count)
+    /*-{
+        return this.move(unit, count);
+    }-*/;
+
+    /**
+     * Moves the end of this range by the given number of units.
+     * 
+     * @param unit Specifies the units to move.
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    public int moveEnd(Unit unit, int count)
+    {
+        return moveEnd(unit.toString(), count);
+    }
+
+    /**
+     * Moves the end of this range by the given number of units.
+     * 
+     * @param unit Specifies the units to move, using one of the following values:
+     *            <ul>
+     *            <li>character: Moves one or more characters.</li>
+     *            <li>word: Moves one or more words. A word is a collection of characters terminated by a space or some
+     *            other white-space character, such as a tab.</li>
+     *            <li>sentence: Moves one or more sentences. A sentence is a collection of words terminated by a
+     *            punctuation character, such as a period.</li>
+     *            <li>textedit: Moves to the start or end of the original range.</li>
+     *            </ul>
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    private native int moveEnd(String unit, int count)
+    /*-{
+        return this.moveEnd(unit, count);
+    }-*/;
+
+    /**
+     * Moves the start of this range by the given number of units.
+     * 
+     * @param unit Specifies the units to move.
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    public int moveStart(Unit unit, int count)
+    {
+        return moveStart(unit.toString(), count);
+    }
+
+    /**
+     * Moves the start of this range by the given number of units.
+     * 
+     * @param unit Specifies the units to move, using one of the following values:
+     *            <ul>
+     *            <li>character: Moves one or more characters.</li>
+     *            <li>word: Moves one or more words. A word is a collection of characters terminated by a space or some
+     *            other white-space character, such as a tab.</li>
+     *            <li>sentence: Moves one or more sentences. A sentence is a collection of words terminated by a
+     *            punctuation character, such as a period.</li>
+     *            <li>textedit: Moves to the start or end of the original range.</li>
+     *            </ul>
+     * @param count Specifies the number of units to move. This can be positive or negative.
+     * @return The number of units moved.
+     */
+    private native int moveStart(String unit, int count)
+    /*-{
+        return this.moveStart(unit, count);
+    }-*/;
+
+    /**
+     * Moves the text range so that the start and end positions of the range encompass the text in the given element.
+     * 
+     * @param element The element object to move to.
+     */
+    public native void moveToElementText(Element element)
+    /*-{
+        this.moveToElementText(element);
+    }-*/;
+
+    /**
+     * The parent element is the element that completely encloses the text in the range. If the text range spans text in
+     * more than one element, this method returns the smallest element that encloses all the elements. When you insert
+     * text into a range that spans multiple elements, the text is placed in the parent element rather than in any of
+     * the contained elements.
+     * 
+     * @return The parent element for this text range.
+     */
+    public native Element getParentElement()
+    /*-{
+        return this.parentElement();
+    }-*/;
+
+    /**
+     * Sets the end point of this range based on the end point of another range. A text range has two end points: one at
+     * the beginning of the text range and one at the end. An end point can also be the position between two characters
+     * in an HTML document.
+     * 
+     * @param how Specifies which end point of this text range should be moved and which of the given text range's end
+     *            points is the reference.
+     * @param range The text range used as the reference.
+     */
+    public void setEndPoint(RangeCompare how, TextRange range)
+    {
+        // In Internet Explorer the meaning of RangeCompare is reversed, so we reverse the end points.
+        switch (how) {
+            case END_TO_START:
+                // Move the start of this range to the end of the given range.
+                if (compareEndPoints(RangeCompare.END_TO_END, range) < 0) {
+                    // The end point needs to be updated also because other wise the start point will go beyond the end
+                    // point.
+                    setEndPoint(RangeCompare.END_TO_END.toString(), range);
+                }
+                break;
+            case START_TO_START:
+                // Move the start of this range to the start of the given range.
+                if (compareEndPoints(RangeCompare.START_TO_END, range) < 0) {
+                    // The end point needs to be updated also because other wise the start point will go beyond the end
+                    // point.
+                    setEndPoint(RangeCompare.END_TO_START.toString(), range);
+                }
+                break;
+            case END_TO_END:
+                // Move the end of this range to the end of the given range.
+                if (compareEndPoints(RangeCompare.END_TO_START, range) > 0) {
+                    // The start point needs to be updated also because other wise the end point will go before the
+                    // start point.
+                    setEndPoint(RangeCompare.START_TO_END.toString(), range);
+                }
+                break;
+            case START_TO_END:
+                // Move the end of this range to the start of the given range.
+                if (compareEndPoints(RangeCompare.START_TO_START, range) > 0) {
+                    // The start point needs to be updated also because other wise the end point will go before the
+                    // start point.
+                    setEndPoint(RangeCompare.START_TO_START.toString(), range);
+                }
+                break;
+            default:
+                // We shouldn't get here.
+        }
+        setEndPoint(how.reverse().toString(), range);
+    }
+
+    /**
+     * Sets the end point of this range based on the end point of another range. A text range has two end points: one at
+     * the beginning of the text range and one at the end. An end point can also be the position between two characters
+     * in an HTML document.
+     * 
+     * @param type Specifies the end point to transfer using one of the following values:
+     *            <ul>
+     *            <li>StartToEnd: Move the start of this TextRange object to the end of the range parameter.</li>
+     *            <li>StartToStart: Move the start of this TextRange object to the start of the range parameter.</li>
+     *            <li>EndToStart: Move the end of this TextRange object to the start of the range parameter.</li>
+     *            <li>EndToEnd: Move the end of this TextRange object to the end of the range parameter.</li>
+     *            </ul>
+     * @param range TextRange object from which the source end point is to be taken.
+     */
+    private native void setEndPoint(String type, TextRange range)
+    /*-{
+        this.setEndPoint(type, range);
+    }-*/;
+
+    /**
+     * Searches for text in the document and positions the start and end points of the range to encompass the search
+     * string.<br/>
+     * The value passed for the searchScope parameter controls the part of the document, relative to the range, that is
+     * searched. The behavior of the findText method depends on whether the range is collapsed or not:
+     * <ul>
+     * <li>If the range is collapsed, passing a large positive number causes the text to the right of the range to be
+     * searched. Passing a large negative number causes the text to the left of the range to be searched.</li>
+     * <li>If the range is not collapsed, passing a large positive number causes the text to the right of the start of
+     * the range to be searched. Passing a large negative number causes the text to the left of the end of the range to
+     * be searched. Passing 0 causes only the text selected by the range to be searched.</li>
+     * </ul>
+     * A text range is not modified if the text specified for the findText method is not found.
+     * 
+     * @param text The text to find.
+     * @param searchScope The number of characters to search from the starting point of the range. A positive integer
+     *            indicates a forward search; a negative integer indicates a backward search.
+     * @param flags One or more of the following flags to indicate the type of search:
+     *            <table>
+     *            <tr>
+     *            <td>0</td>
+     *            <td>Default. Match partial words.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>1</td>
+     *            <td>Match backwards.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>2</td>
+     *            <td>Match whole words only.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>4</td>
+     *            <td>Match case.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>131072</td>
+     *            <td>Match bytes.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>536870912</td>
+     *            <td>Match diacritical marks.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>1073741824</td>
+     *            <td>Match Kashida character.</td>
+     *            </tr>
+     *            <tr>
+     *            <td>2147483648</td>
+     *            <td>Match AlefHamza character.</td>
+     *            </tr>
+     *            </table>
+     * @return true if the given text was found.
+     */
+    public native boolean findText(String text, int searchScope, int flags)
+    /*-{
+        return this.findText(text, searchScope, flags);
+    }-*/;
+
+    /**
+     * Tests if this text range equals the given text range.
+     * 
+     * @param other the text range to compare with this text range.
+     * @return true if the given text range is equal to this text rage.
+     */
+    public native boolean isEqual(TextRange other)
+    /*-{
+        return this.isEqual(other);
+    }-*/;
+
+    /**
+     * Tests whether one range is contained within another.
+     * 
+     * @param other The text range that might be contained in this text range.
+     * @return true if the given text range is contained within or is equal to this text range.
+     */
+    public native boolean inRange(TextRange other)
+    /*-{
+        return this.inRange(other);
+    }-*/;
+
+    /**
+     * Expands the range so that partial units are completely contained.
+     * 
+     * @param unit specifies the units that have to be completely included in the range
+     * @return {@code true} if the range was successfully expanded, {@code false} otherwise
+     */
+    public boolean expand(Unit unit)
+    {
+        return expand(unit.toString());
+    }
+
+    /**
+     * Expands the range so that partial units are completely contained.
+     * 
+     * @param unit specifies the units that have to be completely included in the range, using one of the following
+     *            values:
+     *            <ul>
+     *            <li>character: Moves one or more characters.</li>
+     *            <li>word: Moves one or more words. A word is a collection of characters terminated by a space or some
+     *            other white-space character, such as a tab.</li>
+     *            <li>sentence: Moves one or more sentences. A sentence is a collection of words terminated by a
+     *            punctuation character, such as a period.</li>
+     *            <li>textedit: Moves to the start or end of the original range.</li>
+     *            </ul>
+     * @return {@code true} if the range was successfully expanded, {@code false} otherwise
+     */
+    private native boolean expand(String unit)
+    /*-{
+        return this.expand(unit);
+    }-*/;
+
+    /**
+     * Moves the start and end positions of this text range to the given point.
+     * <p>
+     * The coordinates of the point must be in pixels and be relative to the upper-left corner of the window. The
+     * resulting text range is empty, but you can expand and move the range using methods such as {@link #expand(Unit)}
+     * and {@link #moveEnd(Unit, int)}.
+     * 
+     * @param x integer that specifies the horizontal offset relative to the upper-left corner of the window, in pixels
+     * @param y integer that specifies the vertical offset relative to the upper-left corner of the window, in pixels
+     */
+    public native void moveToPoint(int x, int y)
+    /*-{
+        this.moveToPoint(x, y);
+    }-*/;
+
+    /**
+     * Retrieves the calculated left position of this range's start point relative to the layout or coordinate parent,
+     * as specified by the offsetParent property.
+     * 
+     * @return an integer that specifies the left position, in pixels
+     */
+    public native int getOffsetLeft()
+    /*-{
+        return this.offsetLeft;
+    }-*/;
+
+    /**
+     * Retrieves the calculated top position of this range's start point relative to the layout or coordinate parent, as
+     * specified by the offsetParent property.
+     * 
+     * @return an integer that specifies the top position, in pixels
+     */
+    public native int getOffsetTop()
+    /*-{
+        return this.offsetTop;
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaDOMUtils.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaDOMUtils.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaDOMUtils.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,119 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla;
+
+import com.google.gwt.core.client.JsArrayString;
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.DOMUtils;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Element;
+
+/**
+ * Contains methods from {@link DOMUtils} that require a different implementation in Mozilla.
+ * 
+ * @version $Id$
+ */
+public class MozillaDOMUtils extends DOMUtils
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getComputedStyleProperty(Element, String)
+     */
+    public native String getComputedStyleProperty(Element el, String propertyName)
+    /*-{
+        // We force it to be a string because we treat it as a string in the java code.
+        return '' + el.ownerDocument.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName);
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#importNode(Document, Node, boolean)
+     */
+    public native Node importNode(Document doc, Node externalNode, boolean deep)
+    /*-{
+        return doc.importNode(externalNode, deep);
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#getAttributeNames(Element)
+     */
+    public native JsArrayString getAttributeNames(Element element)
+    /*-{
+        var attrNames = [];
+        for(var i = 0; i < element.attributes.length; i++) {
+            attrNames.push(element.attributes.item(i));
+        }
+        return attrNames;
+    }-*/;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#setInnerHTML(Element, String)
+     */
+    public void setInnerHTML(Element element, String html)
+    {
+        element.setInnerHTML(html);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DOMUtils#splitHTMLNode(Node, Node, int)
+     */
+    public Node splitHTMLNode(Node parent, Node descendant, int offset)
+    {
+        // Save the length of the descendant before the split to be able to detect where the split took place.
+        int length = getLength(descendant);
+
+        // Split the subtree rooted in the given parent.
+        Node nextLevelSibling = super.splitHTMLNode(parent, descendant, offset);
+
+        // See if the split took place.
+        if (nextLevelSibling != descendant) {
+            if (offset == 0) {
+                // The split took place at the beginning of the descendant. Ensure the first subtree is accessible.
+                // But first see if the first subtree has any leafs besides the descendant.
+                Node child = getChild(parent, descendant);
+                if (!isInline(child) && getFirstLeaf(child) == descendant) {
+                    Node refNode = getFarthestInlineAncestor(descendant);
+                    refNode = refNode == null ? child : refNode.getParentNode();
+                    refNode.appendChild(((Document) refNode.getOwnerDocument()).xCreateBRElement());
+                }
+            }
+            if (offset == length) {
+                // The split took place at the end of the descendant. Ensure the second subtree is accessible.
+                // But first see if the second subtree has any leafs besides the nextLevelSibling.
+                Node child = getChild(parent, nextLevelSibling);
+                if (!isInline(child) && getLastLeaf(child) == nextLevelSibling) {
+                    Node refNode = getFarthestInlineAncestor(nextLevelSibling);
+                    refNode = refNode == null ? child : refNode.getParentNode();
+                    refNode.appendChild(((Document) refNode.getOwnerDocument()).xCreateBRElement());
+                }
+            }
+        }
+
+        return nextLevelSibling;
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,87 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla;
+
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.DOMUtils;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Range;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.DefaultSelection;
+import com.xpn.xwiki.wysiwyg.client.dom.internal.NativeRangeWrapper;
+
+/**
+ * Fixes selection problems found in Firefox versions prior to 3.0.
+ * 
+ * @version $Id$
+ */
+public class MozillaSelection extends DefaultSelection
+{
+    /**
+     * Creates a new selection object.
+     * 
+     * @param nativeSelection the underlying native selection to be used
+     */
+    public MozillaSelection(NativeSelection nativeSelection)
+    {
+        super(nativeSelection);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see DefaultSelection#addRange(Range)
+     */
+    public void addRange(Range range)
+    {
+        NativeRangeWrapper wrapper = ((NativeRangeWrapper) range);
+        Document doc = (Document) range.getStartContainer().getOwnerDocument();
+        if (wrapper.getNativeRange() == null) {
+            wrapper.setNativeRange(NativeRange.newInstance(doc));
+        } else {
+            // Firefox prior to version 3.0 throws NS_ERROR_ILLEGAL_VALUE if we try to set the start boundary of the
+            // native range after the end boundary, or the other way around. Firefox 3 collapses the native range in
+            // this case. The DefaultRange already implements this behavior so the input range should be valid. We
+            // select all content to avoid the exception in Firefox 2.
+            ((NativeRange) wrapper.getNativeRange()).selectNode(doc.getBody());
+        }
+        NativeRange nativeRange = wrapper.getNativeRange().cast();
+        nativeRange
+            .setStart(range.getStartContainer(), adjustOffset(range.getStartContainer(), range.getStartOffset()));
+        nativeRange.setEnd(range.getEndContainer(), adjustOffset(range.getEndContainer(), range.getEndOffset()));
+        getNativeSelection().addRange(nativeRange);
+    }
+
+    /**
+     * Adjusts the specified offset within the given node to avoid NS_ERROR_DOM_INDEX_SIZE_ERR.
+     * 
+     * @param node A DOM node.
+     * @param offset The offset within the given node.
+     * @return the adjusted value of the specified offset.
+     */
+    protected int adjustOffset(Node node, int offset)
+    {
+        if (node.getNodeType() == DOMUtils.COMMENT_NODE) {
+            // Only 0 is allowed.
+            return 0;
+        } else {
+            return offset;
+        }
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelectionManager.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelectionManager.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/MozillaSelectionManager.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,42 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla;
+
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.Selection;
+import com.xpn.xwiki.wysiwyg.client.dom.SelectionManager;
+
+/**
+ * Implements {@link SelectionManager} for Firefox prior to version 3.0.
+ * 
+ * @version $Id$
+ */
+public class MozillaSelectionManager implements SelectionManager
+{
+    /**
+     * {@inheritDoc}
+     * 
+     * @see SelectionManager#getSelection(Document)
+     */
+    public Selection getSelection(Document document)
+    {
+        return new MozillaSelection(NativeSelection.getInstance(document));
+    }
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeRange.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeRange.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeRange.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,277 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla;
+
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.DocumentFragment;
+import com.xpn.xwiki.wysiwyg.client.dom.JavaScriptObject;
+
+/**
+ * The native range implementation for browsers that follow the W3C Range specification.
+ * 
+ * @version $Id$
+ */
+public final class NativeRange extends JavaScriptObject
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected NativeRange()
+    {
+    }
+
+    /**
+     * Creates a new native range using the given document.
+     * 
+     * @param document the document to use for creating the new native range
+     * @return the newly created native range
+     */
+    public static native NativeRange newInstance(Document document)
+    /*-{
+        return document.createRange();
+    }-*/;
+
+    /**
+     * @return true if this range is collapsed
+     */
+    public native boolean isCollapsed()
+    /*-{
+        return this.collapsed;
+    }-*/;
+
+    /**
+     * @return the deepest common ancestor container of this range's two boundary-points
+     */
+    public native Node getCommonAncestorContainer()
+    /*-{
+        return this.commonAncestorContainer;
+    }-*/;
+
+    /**
+     * @return the node within which this range ends
+     */
+    public native Node getEndContainer()
+    /*-{
+        return this.endContainer;
+    }-*/;
+
+    /**
+     * @return the offset within the ending node of this range
+     */
+    public native int getEndOffset()
+    /*-{
+        return this.endOffset;
+    }-*/;
+
+    /**
+     * @return the node within which this range begins
+     */
+    public native Node getStartContainer()
+    /*-{
+        return this.startContainer;
+    }-*/;
+
+    /**
+     * @return the offset within the starting node of this range
+     */
+    public native int getStartOffset()
+    /*-{
+        return this.startOffset;
+    }-*/;
+
+    /**
+     * Sets the attributes describing the start of this range.
+     * 
+     * @param refNode the {@link #startContainer} value. This parameter must be different from null.
+     * @param offset the {@link #startOffset} value
+     */
+    public native void setStart(Node refNode, int offset)
+    /*-{
+        this.setStart(refNode, offset);
+    }-*/;
+
+    /**
+     * Sets the attributes describing the end of this range.
+     * 
+     * @param refNode the {@link #endContainer} value. This parameter must be different from null.
+     * @param offset the {@link #endOffset} value
+     */
+    public native void setEnd(Node refNode, int offset)
+    /*-{
+        this.setEnd(refNode, offset);
+    }-*/;
+
+    /**
+     * Sets the start position to be before the given node.
+     * 
+     * @param refNode the reference node, before which this range will start
+     */
+    public native void setStartBefore(Node refNode)
+    /*-{
+        this.setStartBefore(refNode);
+    }-*/;
+
+    /**
+     * Sets the start position to be after the given node.
+     * 
+     * @param refNode the reference node, after which this range will start
+     */
+    public native void setStartAfter(Node refNode)
+    /*-{
+        this.setStartAfter(refNode);
+    }-*/;
+
+    /**
+     * Sets the end position to be before the given node.
+     * 
+     * @param refNode the reference node, before which this range will end
+     */
+    public native void setEndBefore(Node refNode)
+    /*-{
+        this.setEndBefore(refNode);
+    }-*/;
+
+    /**
+     * Sets the end of this Range to be after the given node.
+     * 
+     * @param refNode the reference node, after which this range will end
+     */
+    public native void setEndAfter(Node refNode)
+    /*-{
+        this.setEndAfter(refNode);
+    }-*/;
+
+    /**
+     * Select a node and its contents.
+     * 
+     * @param refNode the node to select
+     */
+    public native void selectNode(Node refNode)
+    /*-{
+        this.selectNode(refNode);
+    }-*/;
+
+    /**
+     * Select the contents within a node.
+     * 
+     * @param refNode the node to select from
+     */
+    public native void selectNodeContents(Node refNode)
+    /*-{
+        this.selectNodeContents(refNode);
+    }-*/;
+
+    /**
+     * Collapse this range onto one of its boundary-points.
+     * 
+     * @param toStart if true, collapses this range onto its start; if false, collapses it onto its end.
+     */
+    public native void collapse(boolean toStart)
+    /*-{
+        this.collapse(toStart);
+    }-*/;
+
+    /**
+     * Duplicates the contents of this range.
+     * 
+     * @return a DocumentFragment that contains content equivalent to this range
+     */
+    public native DocumentFragment cloneContents()
+    /*-{
+        return this.cloneContents();
+    }-*/;
+
+    /**
+     * Removes the contents of this range from the containing document or document fragment without returning a
+     * reference to the removed content.
+     */
+    public native void deleteContents()
+    /*-{
+        this.deleteContents();
+    }-*/;
+
+    /**
+     * Moves the contents of this range from the containing document or document fragment to a new DocumentFragment.
+     * 
+     * @return a DocumentFragment containing the extracted contents
+     */
+    public native DocumentFragment extractContents()
+    /*-{
+        return this.extractContents();
+    }-*/;
+
+    /**
+     * Inserts a node into the Document or DocumentFragment at the start of the Range. If the container is a Text node,
+     * this will be split at the start of the Range (as if the Text node's splitText method was performed at the
+     * insertion point) and the insertion will occur between the two resulting Text nodes. Adjacent Text nodes will not
+     * be automatically merged. If the node to be inserted is a DocumentFragment node, the children will be inserted
+     * rather than the DocumentFragment node itself.
+     * 
+     * @param newNode the node to insert at the start of this range.
+     */
+    public native void insertNode(Node newNode)
+    /*-{
+        this.insertNode(newNode);
+    }-*/;
+
+    /**
+     * Re-parents the contents of this range to the given node and inserts the node at the position of the start of this
+     * range.
+     * 
+     * @param newParent the node to surround the contents with
+     */
+    public native void surroundContents(Node newParent)
+    /*-{
+        this.surroundContents(newParent);
+    }-*/;
+
+    /**
+     * Compare the boundary-points of two ranges in a document.
+     * 
+     * @param how the type of comparison
+     * @param sourceRange the range to compared to
+     * @return -1, 0 or 1 depending on whether the corresponding boundary-point of this range is respectively before,
+     *         equal to, or after the corresponding boundary-point of sourceRange
+     */
+    public native short compareBoundaryPoints(int how, NativeRange sourceRange)
+    /*-{
+        return this.compareBoundaryPoints(how, sourceRange);
+    }-*/;
+
+    /**
+     * Produces a new range whose boundary-points are equal to the boundary-points of this range.
+     * 
+     * @return the duplicated range
+     */
+    public native NativeRange cloneRange()
+    /*-{
+        return this.cloneRange();
+    }-*/;
+
+    /**
+     * Called to indicate that this range is no longer in use and that the implementation may relinquish any resources
+     * associated with this range.
+     */
+    public native void detach()
+    /*-{
+        this.detach();
+    }-*/;
+}

Added: trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeSelection.java
===================================================================
--- trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeSelection.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/com/xpn/xwiki/wysiwyg/client/dom/internal/mozilla/NativeSelection.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -0,0 +1,218 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.wysiwyg.client.dom.internal.mozilla;
+
+import com.google.gwt.dom.client.Node;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+import com.xpn.xwiki.wysiwyg.client.dom.JavaScriptObject;
+
+/**
+ * The native selection implementation provided by Mozilla.
+ * 
+ * @version $Id$
+ */
+public final class NativeSelection extends JavaScriptObject
+{
+    /**
+     * Default constructor. Needs to be protected because all instances are created from JavaScript.
+     */
+    protected NativeSelection()
+    {
+    }
+
+    /**
+     * Retrieves the native selection object using Mozilla's API.
+     * 
+     * @param document the document for which to retrieve the selection instance
+     * @return the native selection object associated with the given document
+     */
+    public static native NativeSelection getInstance(Document document)
+    /*-{
+        return document.defaultView.getSelection();
+    }-*/;
+
+    /**
+     * Adds a range to this selection.
+     * 
+     * @param range the range to be added
+     */
+    public native void addRange(NativeRange range)
+    /*-{
+        this.addRange(range);
+    }-*/;
+
+    /**
+     * Collapses the selection to a single point, at the specified offset in the given DOM node. When the selection is
+     * collapsed, and the content is focused and editable, the caret will blink there.
+     * 
+     * @param parentNode the DOM node where the selection will be set
+     * @param offset specifies where to place the selection in the given node
+     */
+    public native void collapse(Node parentNode, int offset)
+    /*-{
+        this.collapse(parentNode, offset);
+    }-*/;
+
+    /**
+     * Collapses the whole selection to a single point at the end of the current selection (irrespective of direction).
+     * If content is focused and editable, the caret will blink there.
+     */
+    public native void collapseToEnd()
+    /*-{
+        this.collapseToEnd();
+    }-*/;
+
+    /**
+     * Collapses the whole selection to a single point at the start of the current selection (irrespective of
+     * direction). If content is focused and editable, the caret will blink there.
+     */
+    public native void collapseToStart()
+    /*-{
+        this.collapseToStart();
+    }-*/;
+
+    /**
+     * Indicates whether the given node is part of the selection.
+     * 
+     * @param node the DOM node to be tested
+     * @param partlyContained if false, the entire subtree rooted in the given node is tested
+     * @return true when the entire node is part of the selection
+     */
+    public native boolean containsNode(Node node, boolean partlyContained)
+    /*-{
+        return this.containsNode(node, partlyContained);
+    }-*/;
+
+    /**
+     * Deletes this selection from document the nodes belong to.
+     */
+    public native void deleteFromDocument()
+    /*-{
+        this.deleteFromDocument();
+    }-*/;
+
+    /**
+     * Extends the selection by moving the selection end to the specified node and offset, preserving the selection
+     * begin position. The new selection end result will always be from the anchorNode to the new focusNode, regardless
+     * of direction.
+     * 
+     * @param parentNode the node where the selection will be extended to
+     * @param offset specifies where to end the selection in the given node
+     */
+    public native void extend(Node parentNode, int offset)
+    /*-{
+        this.extend(parentNode, offset);
+    }-*/;
+
+    /**
+     * @return the node in which the selection begins
+     */
+    public native Node getAnchorNode()
+    /*-{
+        return this.anchorNode;
+    }-*/;
+
+    /**
+     * @return the offset within the {@link #getAnchorNode()} where the selection begins
+     */
+    public native int getAnchorOffset()
+    /*-{
+        return this.anchorOffset;
+    }-*/;
+
+    /**
+     * @return the node in which the selection ends
+     */
+    public native Node getFocusNode()
+    /*-{
+        return this.focusNode;
+    }-*/;
+
+    /**
+     * @return the offset within the {@link #getFocusNode()} where the selection ends.
+     */
+    public native int getFocusOffset()
+    /*-{
+        return this.focusOffset;
+    }-*/;
+
+    /**
+     * @param index the index of the range to retrieve. Usually the selection contains just one range.
+     * @return the range at the specified index
+     */
+    public native NativeRange getRangeAt(int index)
+    /*-{
+        return this.getRangeAt(index);
+    }-*/;
+
+    /**
+     * @return the number of ranges in the selection
+     */
+    public native int getRangeCount()
+    /*-{
+        return this.rangeCount;
+    }-*/;
+
+    /**
+     * @return true if the selection is collapsed
+     */
+    public native boolean isCollapsed()
+    /*-{
+        return this.isCollapsed;
+    }-*/;
+
+    /**
+     * Removes all ranges from the current selection.
+     */
+    public native void removeAllRanges()
+    /*-{
+        this.removeAllRanges();
+    }-*/;
+
+    /**
+     * Removes the given range from the selection.
+     * 
+     * @param range the range to be removed from the selection.
+     */
+    public native void removeRange(NativeRange range)
+    /*-{
+        this.removeRange(range);
+    }-*/;
+
+    /**
+     * Adds all children of the specified node to the selection. Previous selection is lost.
+     * 
+     * @param parentNode the parent of the children to be added to the selection
+     */
+    public native void selectAllChildren(Node parentNode)
+    /*-{
+        this.selectAllChildren(parentNode);
+    }-*/;
+
+    /**
+     * Modifies the cursor Bidi level after a change in keyboard direction.
+     * 
+     * @param langRTL is true if the new language is right-to-left or false if the new language is left-to-right
+     */
+    public native void selectionLanguageChange(boolean langRTL)
+    /*-{
+        this.selectionLanguageChange(langRTL);
+    }-*/;
+}

Modified: trunk/src/main/java/org/ourproject/kune/platf/Kune-Platform.gwt.xml
===================================================================
--- trunk/src/main/java/org/ourproject/kune/platf/Kune-Platform.gwt.xml	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/org/ourproject/kune/platf/Kune-Platform.gwt.xml	2009-03-12 23:46:56 UTC (rev 1074)
@@ -5,10 +5,11 @@
   <inherits name='com.google.gwt.i18n.I18N' />
   <inherits name="com.google.gwt.user.UserAgent" />
   <inherits name="org.ourproject.kune.platf.RichText" />
-
   <inherits name="com.google.gwt.libideas.ImmutableResources" />
   <inherits name="com.google.gwt.libideas.StyleInjector" />
 
+  <inherits name="com.xpn.xwiki.wysiwyg.Wysiwyg" />
+
   <public path="public" />
 
       <inherits name="com.google.gwt.user.UserAgent" />

Modified: trunk/src/main/java/org/ourproject/kune/platf/client/actions/ActionShortcut.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/platf/client/actions/ActionShortcut.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/org/ourproject/kune/platf/client/actions/ActionShortcut.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -6,6 +6,8 @@
 
 public class ActionShortcut {
 
+    private static final String NO_KEYNAME = "nokeyname";
+
     private static boolean has(int modifiers, int modifier) {
         return ((modifiers & modifier) == modifier);
     }
@@ -16,6 +18,10 @@
     private final char key;
     private final String keyName;
 
+    public ActionShortcut(boolean alt, boolean ctrl, boolean shift, char key) {
+        this(alt, ctrl, shift, key, NO_KEYNAME);
+    }
+
     public ActionShortcut(boolean alt, boolean ctrl, boolean shift, char key, String keyName) {
         this.alt = alt;
         this.ctrl = ctrl;
@@ -25,7 +31,7 @@
     }
 
     public ActionShortcut(boolean ctrl, boolean shift, char key) {
-        this(false, ctrl, shift, key, null);
+        this(false, ctrl, shift, key, NO_KEYNAME);
     }
 
     public ActionShortcut(boolean ctrl, boolean shift, char key, String keyName) {
@@ -33,7 +39,7 @@
     }
 
     public ActionShortcut(boolean ctrl, char key) {
-        this(false, ctrl, false, key, null);
+        this(false, ctrl, false, key, NO_KEYNAME);
     }
 
     public ActionShortcut(boolean ctrl, char key, String keyName) {
@@ -42,7 +48,7 @@
 
     public ActionShortcut(char key, int modifiers) {
         this(has(modifiers, KeyboardListener.MODIFIER_ALT), has(modifiers, KeyboardListener.MODIFIER_CTRL), has(
-                modifiers, KeyboardListener.MODIFIER_SHIFT), key, null);
+                modifiers, KeyboardListener.MODIFIER_SHIFT), key, NO_KEYNAME);
     }
 
     public ActionShortcut(char key, int modifiers, String keyName) {
@@ -104,7 +110,7 @@
         s += sKey(alt, "Alt");
         s += sKey(ctrl, "Ctrl");
         s += sKey(shift, "Shift");
-        s += keyName != null ? translateKey(keyName) + ")" : String.valueOf(key).toUpperCase() + ")";
+        s += keyName != NO_KEYNAME ? translateKey(keyName) + ")" : String.valueOf(key).toUpperCase() + ")";
         return s;
     }
 

Modified: trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPanel.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPanel.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPanel.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -17,12 +17,14 @@
 
 import com.allen_sauer.gwt.log.client.Log;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.IFrameElement;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.ChangeListener;
 import com.google.gwt.user.client.ui.ClickListener;
 import com.google.gwt.user.client.ui.FocusListener;
 import com.google.gwt.user.client.ui.KeyboardListener;
 import com.google.gwt.user.client.ui.Widget;
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
 
 public class RTEditorPanel implements RTEditorView {
 
@@ -153,6 +155,28 @@
         rta.setFocus(true);
     }
 
+    /**
+     * NOTE: If the current browser doesn't support rich text editing this
+     * method returns <code>null</code>. You should test the returned value and
+     * fail save to an appropriate behavior!<br/>
+     * The appropriate test would be: <code><pre>
+     * if (rta.isAttached() && rta.getDocument() == null) {
+     *   // The current browser doesn't support rich text editing.
+     * }
+     * </pre></code>
+     * 
+     * @return The DOM document being edited with this rich text area.
+     * 
+     *         copied from xwiki
+     */
+    public Document getDocument() {
+        if (rta.getElement().getTagName().equalsIgnoreCase("iframe")) {
+            return IFrameElement.as(rta.getElement()).getContentDocument().cast();
+        } else {
+            return null;
+        }
+    }
+
     public String getHtml() {
         return rta.getHTML();
     }

Modified: trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPresenter.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPresenter.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorPresenter.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -16,6 +16,7 @@
 import org.ourproject.kune.platf.client.dto.AccessRolDTO;
 import org.ourproject.kune.platf.client.i18n.I18nTranslationService;
 import org.ourproject.kune.platf.client.state.Session;
+import org.ourproject.kune.platf.client.ui.noti.NotifyUser;
 import org.ourproject.kune.platf.client.ui.palette.ColorWebSafePalette;
 import org.ourproject.kune.platf.client.ui.rte.RichTextArea;
 import org.ourproject.kune.platf.client.ui.rte.edithtml.EditHtmlDialog;
@@ -30,6 +31,7 @@
 import com.calclab.suco.client.events.Listener2;
 import com.calclab.suco.client.ioc.Provider;
 import com.google.gwt.libideas.resources.client.ImageResource;
+import com.xpn.xwiki.wysiwyg.client.dom.Selection;
 
 public class RTEditorPresenter implements RTEditor {
 
@@ -632,6 +634,21 @@
         backgroundColor.setToolTip(i18n.t("Text Background Colour"));
         backgroundColor.setAddCondition(canBeBasic);
 
+        final ActionToolbarMenuDescriptor<Object> devInfo = new ActionToolbarMenuDescriptor<Object>(accessRol, topbar,
+                new Listener0() {
+                    public void onEvent() {
+                        Selection selection = view.getDocument().getSelection();
+                        String info = "range count: " + selection.getRangeCount() + "<br/>focus offset: "
+                                + selection.getFocusOffset() + "<br/>anchor offset:" + selection.getAnchorOffset()
+                                + "<br/>range 0 as html: " + selection.getRangeAt(0).toHTML();
+                        NotifyUser.info(info);
+                    }
+                });
+        devInfo.setTextDescription(i18n.t("Developers info"));
+        devInfo.setAddCondition(canBeExtended);
+        devInfo.setParentMenuTitle(i18n.t(FORMAT_MENU));
+        devInfo.setShortcut(new ActionShortcut(true, false, false, 'I'));
+
         actions.add(withNoItem(bold));
         actions.add(withNoItem(italic));
         actions.add(withNoItem(underline));
@@ -665,6 +682,7 @@
         actions.add(withNoItem(comment));
         actions.add(withNoItem(undoBtn));
         actions.add(withNoItem(redoBtn));
+        actions.add(withNoItem(devInfo));
 
         for (String fontName : this.fontNames) {
             ActionToolbarMenuDescriptor<Object> fontNameAction = createFontNameAction(canBeBasic, fontName);

Modified: trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorView.java
===================================================================
--- trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorView.java	2009-03-12 08:05:10 UTC (rev 1073)
+++ trunk/src/main/java/org/ourproject/kune/platf/client/ui/rte/basic/RTEditorView.java	2009-03-12 23:46:56 UTC (rev 1074)
@@ -4,6 +4,8 @@
 import org.ourproject.kune.platf.client.actions.ActionItemCollection;
 import org.ourproject.kune.platf.client.ui.rte.RichTextArea.FontSize;
 
+import com.xpn.xwiki.wysiwyg.client.dom.Document;
+
 public interface RTEditorView extends View {
 
     void addActions(ActionItemCollection<Object> actions);
@@ -26,6 +28,8 @@
 
     void focus();
 
+    Document getDocument();
+
     String getHtml();
 
     String getText();




More information about the kune-commits mailing list