blob: e8e2171e8ff99c5766fb639df9d4f91a3ffc70dd [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.dom;
import java.util.List;
import java.util.Stack;
import org.eclipse.core.runtime.Assert;
import org.eclipse.gef.EditPart;
import org.eclipse.jst.pagedesigner.IHTMLConstants;
import org.eclipse.jst.pagedesigner.commands.range.WorkNode;
import org.eclipse.jst.pagedesigner.parts.TextEditPart;
import org.eclipse.jst.pagedesigner.utils.HTMLUtil;
import org.eclipse.jst.pagedesigner.validation.caret.ActionData;
import org.eclipse.jst.pagedesigner.validation.caret.IMovementMediator;
import org.eclipse.jst.pagedesigner.validation.caret.InlineEditingNavigationMediator;
import org.eclipse.jst.pagedesigner.viewer.DesignPosition;
import org.eclipse.jst.pagedesigner.viewer.DesignRefPosition;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* @author mengbo
*/
public class EditHelper {
// public final static boolean INNER_DEBUG = false;
private final static int OUT_OF_LEFT = 1;
private final static int LEFT_NAME = 2;
/**
* indicates a position in the middle
*/
public final static int IN_MIDDLE = 3;
private final static int RIGHT_NAME = 4;
private final static int OUT_OF_RIGHT = 5;
private static final EditHelper _instance = new EditHelper();
//private static Logger _log = PDPlugin.getLogger(EditHelper.class);
private EditHelper() {
// no external instantiation
}
/**
* Move operation position infront of next non-blank and non-transparent
* char. The caller should ensure position's container node is not
* tranparent text node.
*
* @param position
* @param forward
* @param forEmpty
* @return the offset
*/
public int getTextNextOffset(IDOMPosition position, boolean forward,
boolean forEmpty) {
EditValidateUtil.validPosition(position);
Assert.isTrue(!EditModelQuery.isTransparentText(position
.getContainerNode()));
Text text = (Text) position.getContainerNode();
int offset = position.getOffset();
String data = text.getNodeValue();
if (forward) {
while (offset < data.length()
&& HTMLUtil.isHTMLWhitespace(data.charAt(offset))) {
offset++;
}
} else {
while (offset > 0
&& HTMLUtil.isHTMLWhitespace(data.charAt(offset - 1))) {
offset--;
}
}
return offset;
}
/**
* @return the singleton instance
*/
public static EditHelper getInstance() {
return _instance;
}
/**
* This caret from current operation postion to next position, this method
* will convert DesignPosition in to DOMPosition, then call dom function to
* move dom position. Here we might insert some complex rules to see whether
* move is valid.
*
* @param action
* @param currentPosition
* @param forward
* @return the dom position
*/
public static DesignPosition moveToNextEditPosition(int action,
DesignPosition currentPosition, boolean forward) {
IDOMPosition position;
position = DOMPositionHelper.toDOMPosition(currentPosition);
position = moveToNextEditPosition(position, forward,
new InlineEditingNavigationMediator(
new ActionData(action, null)));
if (position == null) {
return currentPosition;
}
EditValidateUtil.validPosition(position);
return DOMPositionHelper.toDesignPosition(position);
}
/**
* Move operation position to next edit position. We may need rule to valid
* it based on operation ID and direction. We need to pack transparent
* string.
*
* @param currentPosition
* @param forward
* @param validator
* @return the dom position
*/
public static IDOMPosition moveToNextEditPosition(
IDOMPosition currentPosition, boolean forward,
IMovementMediator validator) {
IDOMPosition result = null;
CaretMoveIterator moveIterator = new CaretMoveIterator(null, validator,
currentPosition, forward);
if ((result = moveIterator.moveToNextEditPosition(currentPosition,
forward, validator)) == null) {
result = currentPosition;
}
return result;
}
/**
* Delete a node, in case it is 'body' or 'html', it won't perform delete.
*
* @param node
* @return the node
*/
public static Node deleteNode(Node node) {
if (node == null || node.getNodeName() == null) {
return null;
}
String name = node.getLocalName();
if (name != null
&& (name.equalsIgnoreCase(IHTMLConstants.TAG_BODY)
|| name.equalsIgnoreCase(IHTMLConstants.TAG_HEAD) || name
.equalsIgnoreCase(IHTMLConstants.TAG_HTML))) {
return null;
}
Node parent = node.getParentNode();
name = parent.getNodeName();
if (parent != null
&& name != null
&& parent.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_HEAD)) {
return null;
}
return parent.removeChild(node);
}
/**
* Order the IDOMPositions in a range in ascending order.
*
* @param range
* @return the dom range
*/
public static DOMRange normal(DOMRange range) {
EditValidateUtil.validRange(range);
IDOMPosition p1 = range.getStartPosition();
IDOMPosition p2 = range.getEndPosition();
if (EditModelQuery.getIndexedRegionLocation(p1) > EditModelQuery
.getIndexedRegionLocation(p2)) {
return new DOMRange(p2, p1);
}
return range;
}
/**
* Move position in to node from its outside, the node should be breakble.
*
* @param node
* @param validator
* @param forward
* @return the dom position
*/
public static IDOMPosition moveInto(Node node, IMovementMediator validator,
boolean forward) {
CaretMoveIterator moveIterator = new CaretMoveIterator(null, validator,
new DOMRefPosition(node, !forward), forward);
return moveIterator.moveIn(node);
}
/**
* Convert a DomRefPosition into DOMPosition.
*
* @param position
* @return the dom position
*/
public static IDOMPosition ensureDOMPosition(IDOMPosition position) {
if (position instanceof DOMRefPosition) {
return new DOMPosition(position.getContainerNode(), position
.getOffset());
}
return position;
}
/**
* @param currentNode
* @param pos1
* @param pos2
* @param top
* @param workNode
*/
public void processText(Text currentNode, final int pos1, final int pos2,
Node top, Stack workNode) {
// the text could be tranparent, or 0 length.
Assert.isTrue(pos1 <= pos2);
if (pos1 == pos2) {
return;
}
// int left = EditModelQuery.getNodeStartIndex(currentNode);
// int right = EditModelQuery.getNodeEndIndex(currentNode);
int location1 = getLocation(currentNode, pos1, false);
int location2 = getLocation(currentNode, pos2, false);
if (location1 <= IN_MIDDLE && location2 >= IN_MIDDLE) {
workNode.push(new WorkNode(currentNode, pos1, pos2));
}
}
/**
* @param currentNode
* @param pos1
* @param pos2
* @param top
* @param result
*/
public void collectNodes(Node currentNode, final int pos1, final int pos2,
Node top, Stack result) {
Assert.isTrue(pos1 <= pos2);
if (pos1 == pos2) {
return;
}
if (EditModelQuery.isText(currentNode)) {
processText((Text) currentNode, pos1, pos2, top, result);
} else {
int location1 = getLocation(currentNode, pos1, false);
int location2 = getLocation(currentNode, pos2, false);
if (location1 < IN_MIDDLE && location2 > IN_MIDDLE) {
// TODO: push the node into result.--
result.push(new WorkNode(currentNode, pos1, pos2));
} else if (location1 <= IN_MIDDLE && location2 >= IN_MIDDLE) {
if (currentNode.hasChildNodes()) {
Node child = currentNode.getFirstChild();
Stack myResult = new Stack();
while (child != null) {
collectNodes(child, pos1, pos2, top, myResult);
child = child.getNextSibling();
}
if (location1 < IN_MIDDLE && location2 >= IN_MIDDLE
|| location1 <= IN_MIDDLE && location2 > IN_MIDDLE) {
WorkNode workNode = new WorkNode(currentNode, pos1,
pos2);
while (myResult.size() > 0) {
WorkNode w = (WorkNode) myResult.remove(0);
if (w.getNode().getParentNode() == workNode
.getNode()) {
w.setParent(workNode);
}
result.push(w);
}
// TODO: push parent into result.--
result.push(workNode);
}
} else {
if (!(location1 == IN_MIDDLE && location2 == IN_MIDDLE)) {
// TODO: push this node into result.
result.push(new WorkNode(currentNode, pos1, pos2));
}
}
}
}
}
/**
* @param currentNode
* @param pos
* @param isOffset
* @return the location
*/
public int getLocation(Node currentNode, int pos, boolean isOffset) {
if (EditModelQuery.getInstance().isSingleRegionNode(currentNode)) {
// if (EditModelQuery.isText(currentNode))
{
int left = EditModelQuery.getNodeStartIndex(currentNode);
int right = EditModelQuery.getNodeEndIndex(currentNode);
if (isOffset) {
pos += left;
}
if (pos <= left) {
return OUT_OF_LEFT;
} else if (left < pos && pos < right) {
return IN_MIDDLE;
} else {
return OUT_OF_RIGHT;
}
}
}
int left = EditModelQuery.getNodeStartIndex(currentNode);
int left1 = EditModelQuery.getNodeStartNameEndIndex(currentNode);
int right = EditModelQuery.getNodeEndNameStartIndex(currentNode);
int right1 = EditModelQuery.getNodeEndIndex(currentNode);
if (isOffset) {
pos += left;
}
if (pos <= left) {
return OUT_OF_LEFT;
} else if (left < pos && pos < left1) {
return LEFT_NAME;
} else if (left1 <= pos && pos <= right) {
return IN_MIDDLE;
} else if (right < pos && pos < right1) {
return RIGHT_NAME;
} else {
return OUT_OF_RIGHT;
}
}
// TODO: dead?
// private Node cutCurrentNode(int pos[], Node currentNode,
// IDOMPosition position) {
// // at right edge
// int curpos = EditModelQuery.getIndexedRegionLocation(position);
// if (pos[0] <= curpos) {
// pos[1] = EditModelQuery.getNodeStartIndex(currentNode);
// currentNode = deleteNode(currentNode);
// if (INNER_DEBUG) {
// _log.info("cut:" + currentNode);
// }
// return currentNode;
// } else if (pos[1] >= curpos) {
// pos[0] = EditModelQuery.getNodeEndIndex(currentNode);
// currentNode = deleteNode(currentNode);
// if (INNER_DEBUG) {
// _log.info("cut:" + currentNode);
// }
// return currentNode;
// }
// return null;
// }
//TODO: dead?
// private int getPos(DOMRange range, boolean forStart) {
// if (forStart) {
// return EditModelQuery.getIndexedRegionLocation(range
// .getStartPosition());
// } else {
// return EditModelQuery.getIndexedRegionLocation(range
// .getEndPosition());
// }
// }
/**
* @param position
* @param forward
* @return the edit part for position
*/
public EditPart getEditPart(DesignPosition position, boolean forward) {
if (position instanceof DesignRefPosition) {
return ((DesignRefPosition) position).getRefPart();
}
EditPart container = position.getContainerPart();
if (container instanceof TextEditPart) {
return container;
}
if (container != null) {
List children = container.getChildren();
for (int i = 0, n = children.size(); i < n; i++) {
if (i == position.getOffset()) {
int index = (forward) ? i - 1 : i + 1;
if (index < 0) {
index = 0;
}
if (index >= children.size()) {
index = children.size() - 1;
}
return (EditPart) children.get(index);
}
}
}
return null;
}
/**
* @param position
* @return the resulting dom position
*/
public static IDOMPosition splitNode(IDOMPosition position) {
if (EditValidateUtil.validPosition(position)) {
Node container = null;
// Avoid to split tag at its edge
if (position.getOffset() > 0) {
if (position.isText()) {
container = position.getContainerNode();
if (position.getOffset() < ((Text) container).getLength()) {
position = DOMPositionHelper.splitText(position);
} else {
// position = new
// DOMRefPosition(position.getContainerNode(), true);
}
} else {
if (position.getNextSiblingNode() != null) {
container = position.getContainerNode();
Node parent = container.getParentNode();
Document document = EditModelQuery
.getDocumentNode(container);
Node newContainer = document.createElement(container
.getNodeName());
Node node = position.getPreviousSiblingNode();
Node refNode = null;
while (node != null) {
Node prev = node.getPreviousSibling();
node = node.getParentNode().removeChild(node);
newContainer.insertBefore(node, refNode);
refNode = node;
node = prev;
}
parent.insertBefore(newContainer, container);
// set the newContainer node align attribute to the
// original align attribue
// copy nodes under container node to container node's
// parent node
if (container.getNodeName().equalsIgnoreCase(
IHTMLConstants.TAG_P)) {
Element pNode = (Element) container;
String align = pNode
.getAttribute(IHTMLConstants.ATTR_ALIGN);
if (align != null && !"".equalsIgnoreCase(align)) {
((Element) newContainer).setAttribute(
IHTMLConstants.ATTR_ALIGN, align);
}
NodeList nodeList = pNode.getChildNodes();
for (int i = 0, size = nodeList.getLength(); i < size; i++) {
Node tempNode = nodeList.item(i);
parent.insertBefore(tempNode, container);
}
}
return new DOMRefPosition(newContainer, true);
}
// position = new
// DOMRefPosition(position.getContainerNode(), true);
}
} else {
// container = position.getContainerNode();
// position = new DOMRefPosition(container, false);
}
}
return position;
}
/**
* @param position
* @return the position of this 'position' in relative to it's container.
*/
public static int getLocation(IDOMPosition position) {
if (position.getOffset() == 0) {
return -1;
}
if (position.isText()) {
if (position.getOffset() == ((Text) position.getContainerNode())
.getLength()) {
return 1;
}
return 0;
}
if (position.getOffset() == position.getContainerNode()
.getChildNodes().getLength()) {
return 1;
}
return 0;
}
}