| /******************************************************************************* |
| * 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; |
| } |
| } |