blob: 9febaaa84edf179fc9c4f7e6929d4e3da3387eed [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Sybase, Inc. and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sybase, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.pagedesigner.range;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.gef.EditPart;
import org.eclipse.jst.pagedesigner.parts.DocumentEditPart;
import org.eclipse.jst.pagedesigner.parts.TextEditPart;
import org.eclipse.jst.pagedesigner.viewer.DesignPosition;
import org.eclipse.jst.pagedesigner.viewer.DesignRange;
import org.eclipse.jst.pagedesigner.viewer.TextPosition;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
/**
* @author mengbo
*/
public class RangeUtil {
/**
* append the child after the reference node as next sibling.
*
* @param child
* can't be null
* @param reference
* can't be null
*/
public static Node appendAfter(Node child, Node reference) {
Node next = reference.getNextSibling();
if (next == null)
{
return reference.getParentNode().appendChild(child);
}
return reference.getParentNode().insertBefore(child, next);
}
public static Node insertBefore(Node child, Node reference) {
return reference.getParentNode().insertBefore(child, reference);
}
/**
* Insert a node into the specified position. The node can be an element or
* DocumentFragment.
*
* @param node
* @param position
*/
public static Node insertElement(DesignPosition position, Element node) {
EditPart containerEditPart = position.getContainerPart();
int offset = position.getOffset();
if (containerEditPart instanceof TextEditPart) {
TextEditPart textPart = (TextEditPart) containerEditPart;
String textData = textPart.getTextData();
Node textNode = (Node) textPart.getModel();
if (offset == 0)
return insertBefore(node, textNode);
else if (offset == textData.length())
return appendAfter(node, textNode);
else {
// inserting the element in the middle of text.
String before = textData.substring(0, offset);
String after = textData.substring(offset);
// XXX: don't know whether setNodeValue() will do all those
// escape or not.
textNode.setNodeValue(after);
Node newnode = insertBefore(node, textNode);
// XXX: don't know whether createTextNode() will do all those
// escape or not
Text t = textNode.getOwnerDocument().createTextNode(before);
insertBefore(t, newnode);
return newnode;
}
}
return insertIntoEditPart(containerEditPart, node, offset);
}
/**
* @param containerEditPart
* @param node
* @param offset
* @return
*/
private static Node insertIntoEditPart(EditPart containerEditPart,
Node node, int offset) {
Node parent = (Node) containerEditPart.getModel();
List childParts = containerEditPart.getChildren();
if (offset >= childParts.size()) {
// to the end of parent
return parent.appendChild(node);
}
Node child = (Node) ((EditPart) childParts.get(offset)).getModel();
return insertBefore(node, child);
}
public static TextPosition insertText(DesignPosition position, String data) {
// TODO: never read EditPart containerEditPart = position.getContainerPart();
position = moveIntoText(position);
int offset = position.getOffset();
if (position.getContainerPart() instanceof TextEditPart) {
// it is guaranteeed that now the containing edit part is text node.
TextEditPart textPart = (TextEditPart) position.getContainerPart();
String textData = textPart.getTextData();
String before = textData.substring(0, offset);
String after = textData.substring(offset);
if (data.startsWith(" ") && before.endsWith(" ")) {
before = before.substring(0, before.length() - 1) + " ";
}
if (after.startsWith(" ") && data.endsWith(" ")) {
data = data.substring(0, data.length() - 1) + (char) 160;
}
String nextData = before + data + after;
IDOMText text = (IDOMText) textPart.getModel();
text.setData(nextData);
return new TextPosition(text, offset + data.length());
}
// can't merge into a neighboring text node. So create a text node
// of it's own
EditPart part = position.getContainerPart();
Node parent = (Node) part.getModel();
Text text = parent.getOwnerDocument().createTextNode(data);
insertIntoEditPart(part, text, offset);
return new TextPosition((IDOMText) text, offset);
}
/**
* Try to make the position move into a text node.
*
* @param position
* @return
*/
public static DesignPosition moveIntoText(DesignPosition position) {
EditPart container = position.getContainerPart();
if (container instanceof TextEditPart)
return position;
if (position.getOffset() > 0) {
EditPart pre = (EditPart) container.getChildren().get(
position.getOffset() - 1);
if (pre instanceof TextEditPart) {
return new DesignPosition(pre, ((TextEditPart) pre)
.getTextData().length());
}
}
if (position.getOffset() < container.getChildren().size()) {
EditPart next = (EditPart) container.getChildren().get(
position.getOffset());
if (next instanceof TextEditPart) {
return new DesignPosition(next, 0);
}
}
return position;
}
/**
* try to move the position up to not inside a text. if the position is at 0
* index or last index of a text node, then try to move it up.
*
* @param position
* @return
*/
public static DesignPosition moveOutFromText(DesignPosition position) {
EditPart container = position.getContainerPart();
if (container instanceof TextEditPart) {
int offset = position.getOffset();
String text = ((TextEditPart) container).getTextData();
if (offset == 0) {
return new DesignPosition(container.getParent(), container
.getParent().getChildren().indexOf(container));
} else if (offset == text.length()) {
return new DesignPosition(container.getParent(), container
.getParent().getChildren().indexOf(container) + 1);
}
}
return position;
}
public static void insertDocumentFragment(DesignPosition position,
DocumentFragment fragment) {
// FIXME: NOT DONE.
}
/**
* Test whether the range intersect with the part.
*
* @param range
* @param part
*/
public static boolean intersect(DesignRange range, EditPart part) {
if (range == null || !range.isValid())
return false;
range = normalize(range);
if (part instanceof DocumentEditPart)
return true;
EditPart parent = part.getParent();
int index = parent.getChildren().indexOf(part);
DesignPosition left = new DesignPosition(parent, index);
DesignPosition right = new DesignPosition(parent, index + 1);
int compare = compareDesignPosition(left, range.getEndPosition());
if (compare == 1 || compare == 0 || compare == Integer.MIN_VALUE)
return false;
compare = compareDesignPosition(right, range.getStartPosition());
if (compare == -1 || compare == 0 || compare == Integer.MIN_VALUE)
return false;
return true;
}
/**
* make sure the start position is before end position. If the original
* range is already normalized, then the original range will be returned
* without constructing a new one.
*
* @param range
* @return
*/
public static DesignRange normalize(DesignRange range) {
if (range == null || !range.isValid()) {
return range;
}
int result = compareDesignPosition(range.getStartPosition(), range
.getEndPosition());
if (result == 1)
{
return new DesignRange(range.getEndPosition(), range
.getStartPosition());
}
return range;
}
/**
*
* @param p1
* @param p2
* @return 0 means equal. 1 Means p1 is after p2. -1 means p1 is before p2.
* Integer.MIN_VALUE means some error and can't compare.
*/
public static int compareDesignPosition(DesignPosition p1, DesignPosition p2) {
if (!p1.isValid() || !p2.isValid())
return Integer.MIN_VALUE;
if (p1.equals(p2))
return 0;
int offset1 = p1.getOffset();
int offset2 = p2.getOffset();
List a1 = getAncesters(p1.getContainerPart());
List a2 = getAncesters(p2.getContainerPart());
if (a1 == null || a2 == null)
return Integer.MIN_VALUE;
if (a1.get(0) != a2.get(0))
return Integer.MIN_VALUE; // not same DocumentEditPart
for (int i = 1;; i++) {
EditPart p1a = (EditPart) a1.get(i);
EditPart p2a = (EditPart) a2.get(i);
if (p1a == p2a) {
if (p1a != null)
{
continue; // same ancester
}
// both are null. just compare the offset.
return offset1 < offset2 ? -1
: (offset1 == offset2 ? 0 : 1);
}
// p1a != p2a. now we can just compare p1a and p2a to decide the
// order.
if (p1a != null)
offset1 = p1a.getParent().getChildren().indexOf(p1a);
if (p2a != null)
offset2 = p2a.getParent().getChildren().indexOf(p2a);
if ((p1a == null && p2a == null) || (p1a != null && p2a != null)) {
return offset1 < offset2 ? -1 : (offset1 == offset2 ? 0 : 1);
} else if (p1a == null) {
return offset1 <= offset2 ? -1 : 1;
} else {
return offset1 >= offset2 ? 1 : -1;
}
}
}
/**
* Get a list of ancester nodes starting from the DocumentEditPart till the
* node.
*
* @param part
* @return
*/
private static List getAncesters(EditPart part) {
List list = new ArrayList();
while (part != null) {
list.add(part);
if (part instanceof DocumentEditPart)
{
break;
}
part = part.getParent();
}
if (part == null) {
// if part ==null, means we didn't find a DocumentEditPart,
// something must be wrong.
return null;
}
// reverse to make it starting from the docuemnteditpart node.
Collections.reverse(list);
list.add(null); // add an null terminator.
return list;
}
/**
* find the smallest common ancester of two edit part.
*
* @param part1
* @param part2
* @return
*/
public static EditPart findCommonAncester(EditPart part1, EditPart part2) {
if (part1 == part2) {
return part1;
}
List list1 = getAncesters(part1);
if (list1 == null)
return null;
List list2 = getAncesters(part2);
if (list2 == null)
return null;
if (list1.get(0) != list2.get(0))
return null;
EditPart common = (EditPart) list1.get(0);
for (int i = 1;; i++) {
EditPart p1 = (EditPart) list1.get(i);
EditPart p2 = (EditPart) list2.get(i);
if (p1 == null || p2 == null)
return common;
if (p1 != p2)
return common;
common = p1;
}
}
public static EditPart findCommonAncestor(DesignRange range) {
if (!range.isValid()) {
return null;
}
DesignPosition startPosition = range.getStartPosition();
DesignPosition endPosition = range.getEndPosition();
return findCommonAncester(startPosition.getContainerPart(), endPosition
.getContainerPart());
}
}