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/inter
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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">Juanco Anez</a> + * @author <a href="mailto:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">Juanco Anez</a> + * @author <a href="mailto:[email protected]">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 [email protected]. + * + * 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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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 [email protected]. + * + * 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:[email protected]">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-Do... + * @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-Do... + */ + 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/nsR... + */ +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.ht... + * <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();
participants (1)
-
vjrj