[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><tagName/></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 <link> element.
+ *
+ * @return the newly created element
+ */
+ public final LinkElement xCreateLinkElement()
+ {
+ return xCreateElement("link").cast();
+ }
+
+ /**
+ * Creates a <script> element.
+ *
+ * @return the newly created element
+ */
+ public final ScriptElement xCreateScriptElement()
+ {
+ return xCreateElement("script").cast();
+ }
+
+ /**
+ * Creates a <br> element.
+ *
+ * @return the newly created element
+ */
+ public final BRElement xCreateBRElement()
+ {
+ return xCreateElement("br").cast();
+ }
+
+ /**
+ * Creates a <p> element.
+ *
+ * @return the newly created element
+ */
+ public final ParagraphElement xCreatePElement()
+ {
+ return xCreateElement("p").cast();
+ }
+
+ /**
+ * Creates a <div> element.
+ *
+ * @return the newly created element.
+ */
+ public final DivElement xCreateDivElement()
+ {
+ return xCreateElement("div").cast();
+ }
+
+ /**
+ * Creates a <span> 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 <hr> element.
+ *
+ * @return the newly create element.
+ */
+ public final HRElement xCreateHRElement()
+ {
+ return xCreateElement("hr").cast();
+ }
+
+ /**
+ * Creates a <img> element.
+ *
+ * @return the newly create element.
+ */
+ public final ImageElement xCreateImageElement()
+ {
+ return xCreateElement("img").cast();
+ }
+
+ /**
+ * Creates a <li> element.
+ *
+ * @return the newly created element.
+ */
+ public final LIElement xCreateLIElement()
+ {
+ return xCreateElement("li").cast();
+ }
+
+ /**
+ * Creates a <button> 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><FOO>A<strong>B<BAR>C</strong>D</BAR>E</FOO></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