| /******************************************************************************* |
| * 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.utils; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.TextSelection; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jst.jsf.core.internal.tld.CMUtil; |
| import org.eclipse.jst.pagedesigner.dom.DOMPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMPositionHelper; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition2; |
| import org.eclipse.jst.pagedesigner.dom.DOMUtil; |
| import org.eclipse.jst.pagedesigner.dom.EditValidateUtil; |
| import org.eclipse.jst.pagedesigner.dom.IDOMPosition; |
| import org.eclipse.jst.pagedesigner.parts.NodeEditPart; |
| import org.eclipse.jst.pagedesigner.viewer.DesignPosition; |
| import org.eclipse.jst.pagedesigner.viewer.DesignRange; |
| import org.eclipse.jst.pagedesigner.viewer.IHTMLGraphicalViewer; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; |
| import org.w3c.dom.CharacterData; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.Text; |
| |
| /** |
| * @author mengbo |
| * @version 1.5 |
| */ |
| public class SelectionHelper { |
| /** |
| * convert the text selection to a Node. Will use the start offset of the |
| * text selection. |
| * |
| * @param model |
| * @param textSel |
| * @return |
| */ |
| public static Node toNode(IStructuredModel model, ITextSelection textSel) { |
| // FIXME: currently always normalize to a single node. should also |
| // consider change into DesignRange |
| // on text selection, find the appropriate Node |
| Object inode = model.getIndexedRegion(textSel.getOffset()); |
| if (inode instanceof Node) { |
| return (Node) inode; |
| } |
| return null; |
| } |
| |
| /** |
| * convert a structured selection of NodeEditPart or Node into the first |
| * node. |
| * |
| * @param selection |
| * @return |
| */ |
| public static Node toNode(IStructuredSelection selection) { |
| if (selection.isEmpty()) { |
| return null; |
| } |
| Object first = selection.getFirstElement(); |
| if (first instanceof Node) { |
| return (Node) first; |
| } else if (first instanceof NodeEditPart) { |
| return ((NodeEditPart) first).getIDOMNode(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * convert a DesignRange into a single node. |
| * |
| * @param range |
| * @return |
| */ |
| public static Node toNode(DesignRange range) { |
| if (range.isValid()) { |
| Node node1 = range.getStartPosition().getContainerNode(); |
| Node node2 = range.getEndPosition().getContainerNode(); |
| return DOMUtil.findCommonAncester(node1, node2); |
| } |
| return null; |
| } |
| |
| /** |
| * @param model |
| * @param region |
| * if null, then will calculate it using offset. |
| * @param offset |
| * offset in source. |
| * @return |
| */ |
| public static IDOMPosition toDOMPosition(IDOMModel model, |
| IndexedRegion region, int offset) { |
| if (region == null) { |
| region = model.getIndexedRegion(offset); |
| } |
| if (region == null && offset > 0) { |
| // in case this is at end of file. |
| offset = offset - 1; |
| region = model.getIndexedRegion(offset); |
| if (region != null) { |
| if (region.getEndOffset() >= offset + 1) { |
| offset += 1; // restore offset. |
| } |
| } |
| } |
| if (region == null) { |
| return new DOMPosition(model.getDocument(), 0); |
| } |
| IDOMNode node = (IDOMNode) region; |
| int start = node.getStartOffset(); |
| if (offset <= start) { |
| return new DOMRefPosition(node, false); |
| } |
| int end = node.getEndOffset(); |
| if (offset >= end) { |
| return new DOMRefPosition(node, true); |
| } |
| if (node instanceof CharacterData) { |
| String data = ((CharacterData) node).getData(); |
| String source = node.getSource(); |
| if (data.equals(source)) { |
| return new DOMPosition(node, offset - start); |
| } |
| IStructuredDocumentRegion r = node |
| .getFirstStructuredDocumentRegion(); |
| int countedData = 0; |
| // TODO: dead? int offsetInNode = offset - start; |
| while (r != null) { |
| if (DOMRegionContext.XML_CHAR_REFERENCE.equals(r.getType()) |
| || DOMRegionContext.XML_ENTITY_REFERENCE.equals(r |
| .getType())) { |
| countedData += 1; // FIXME: what if the entity reference's |
| // corresponding data is more than 1 |
| // char? |
| // where can we get that information? |
| if (r.getEnd() >= offset) { |
| return new DOMPosition(node, countedData); |
| } |
| } else { |
| if (r.getEnd() >= offset) { |
| return new DOMPosition(node, countedData + offset |
| - r.getStart()); |
| } |
| countedData += r.getLength(); |
| } |
| r = r.getNext(); |
| } |
| return new DOMRefPosition(node, true); |
| } else if (node instanceof Element) { |
| CMElementDeclaration cm = CMUtil |
| .getElementDeclaration((Element) node); |
| if (cm != null && cm.getContentType() == CMElementDeclaration.EMPTY) { |
| // this node can't have children. |
| return new DOMRefPosition(node, true); |
| } |
| IStructuredDocumentRegion startRegion = node |
| .getStartStructuredDocumentRegion(); |
| if (startRegion == null) { |
| return new DOMRefPosition(node, true); |
| } |
| int startRegionEnd = node.getStartStructuredDocumentRegion() |
| .getEnd(); |
| if (offset <= startRegionEnd) { |
| // it is in the start tag region. So put position at first |
| // child position. |
| return new DOMRefPosition2(node, false); |
| } |
| return new DOMRefPosition2(node, true); |
| } else { |
| return new DOMRefPosition(node, true); |
| } |
| // XXX: the implementation in EditModelQuery seemed to be very complex. |
| // Need revisit that |
| // and refactor the implementation to this class later. (lium) |
| } |
| |
| /** |
| * Give a text selection with offset and length, convert it into a Designer |
| * selection (IStrucuturedSelection of editpart or DesignerRange). If the |
| * text selection include just a single element node, we'll create a |
| * IStructuredSelection, otherwise we'll create a DesignerRange. |
| * |
| * @param graphicViewer |
| * @param offset |
| * @param length |
| */ |
| public static ISelection convertToDesignerSelection( |
| IHTMLGraphicalViewer graphicViewer, int offset, int length) { |
| IDOMModel model = graphicViewer.getModel(); |
| IndexedRegion region1 = model.getIndexedRegion(offset); |
| IndexedRegion region2 = model.getIndexedRegion(offset + length); |
| IDOMNode node1 = (IDOMNode) region1; |
| |
| if (node1 == null) { |
| IDOMPosition endOfDoc = new DOMRefPosition2(model.getDocument(), |
| true); |
| DesignPosition p = DOMPositionHelper.toDesignPosition(endOfDoc); |
| return new DesignRange(p, p); |
| } |
| |
| if ((region1 == region2 || node1.getEndOffset() == offset + length) |
| && !(node1 instanceof Text)) { |
| // ok, we selected a single node. |
| EditPart part = (EditPart) node1.getAdapterFor(EditPart.class); |
| if (part != null) { |
| return new StructuredSelection(part); |
| } |
| } |
| |
| // when we reach here, we'll create a DesignerRange |
| IDOMPosition position1 = toDOMPosition(model, region1, offset); |
| IDOMPosition position2 = (length == 0 ? position1 : toDOMPosition( |
| model, region2, offset + length)); |
| |
| if (position1 == null || position2 == null) { |
| return new DesignRange(null, null); |
| } |
| DesignPosition p1 = DOMPositionHelper.toDesignPosition(position1); |
| DesignPosition p2 = (length == 0 ? p1 : DOMPositionHelper |
| .toDesignPosition(position2)); |
| if (p1 == null || p2 == null) { |
| return new DesignRange(null, null); |
| } |
| |
| return new DesignRange(p1, p2); |
| |
| } |
| |
| /** |
| * convert a IDOMPosition into index in the source. |
| * |
| * @param p |
| * @return |
| */ |
| private static int getIndexedRegionLocation(IDOMPosition p) { |
| if (!EditValidateUtil.validPosition(p)) { |
| return 0; |
| } |
| |
| IDOMNode parent = (IDOMNode) p.getContainerNode(); |
| if (p.isText()) { |
| String text = ((CharacterData) parent).getData(); |
| String source = parent.getSource(); |
| if (text.length() == source.length()) { |
| // no entity reference. |
| return parent.getStartOffset() + p.getOffset(); |
| } |
| // CR404708. Need to handle entity reference in the text. |
| int offset = p.getOffset(); |
| int counted = 0; |
| IStructuredDocumentRegion r = parent |
| .getFirstStructuredDocumentRegion(); |
| while (r != null && counted < offset) { |
| if (DOMRegionContext.XML_CHAR_REFERENCE.equals(r.getType()) |
| || DOMRegionContext.XML_ENTITY_REFERENCE.equals(r |
| .getType())) { |
| counted++; |
| if (counted >= offset) { |
| return r.getEndOffset(); |
| } |
| } else { |
| int length = r.getLength(); |
| if (counted + length >= offset) { |
| return r.getStartOffset() + offset - counted; |
| } |
| counted += length; |
| } |
| r = r.getNext(); |
| } |
| return parent.getStartOffset() + p.getOffset(); |
| } |
| IDOMNode previous = (IDOMNode) p.getPreviousSiblingNode(); |
| if (previous != null) { |
| return previous.getEndOffset(); |
| } |
| IDOMNode next = (IDOMNode) p.getNextSiblingNode(); |
| if (next != null) { |
| return next.getStartOffset(); |
| } |
| IStructuredDocumentRegion r = parent |
| .getStartStructuredDocumentRegion(); |
| if (r != null) { |
| return r.getEnd(); |
| } |
| // r == null normally means the parent is the document node. |
| return parent.getEndOffset(); |
| } |
| |
| /** |
| * convert design selection of structured selection of NodeEditPart into |
| * structured selection of Node |
| * |
| * @param sel |
| * @return |
| */ |
| public static IStructuredSelection convertFromDesignSelection( |
| IStructuredSelection sel) { |
| List list = sel.toList(); |
| if (list != null) { |
| List result = new ArrayList(list.size()); |
| for (int i = 0, size = list.size(); i < size; i++) { |
| NodeEditPart part = (NodeEditPart) list.get(i); |
| result.add(part.getIDOMNode()); |
| } |
| return new StructuredSelection(result); |
| } |
| return new StructuredSelection(); |
| } |
| |
| /** |
| * |
| * @param selection |
| * selection from designer, could be IStructuredSelection of |
| * NodeEditPart, or DesignRange. |
| * @return |
| */ |
| public static ITextSelection convertFromDesignSelection(DesignRange range) { |
| if (range.isValid()) { |
| IDOMPosition start = DOMPositionHelper.toDOMPosition(range |
| .getStartPosition()); |
| IDOMPosition end = DOMPositionHelper.toDOMPosition(range |
| .getEndPosition()); |
| // We should not encounter invalid position. |
| if (EditValidateUtil.validPosition(start) |
| && EditValidateUtil.validPosition(end)) { |
| int offset = getIndexedRegionLocation(start); |
| int endoffset = getIndexedRegionLocation(end); |
| if (offset > endoffset) { |
| int temp = offset; |
| offset = endoffset; |
| endoffset = temp; |
| } |
| return new TextSelection(offset, endoffset - offset); |
| } |
| } |
| return new TextSelection(0, 0); |
| } |
| |
| public static ITextSelection convertFromDesignSelectionToTextSelection( |
| ISelection selection) { |
| if (selection instanceof IStructuredSelection) { |
| IStructuredSelection nodes = convertFromDesignSelection((IStructuredSelection) selection); |
| IDOMNode node = (IDOMNode) nodes.getFirstElement(); |
| if (node != null && node.getNodeType() != Node.DOCUMENT_NODE) { |
| return new TextSelection(node.getStartOffset(), node |
| .getEndOffset() |
| - node.getStartOffset()); |
| } |
| } else if (selection instanceof DesignRange) { |
| return convertFromDesignSelection((DesignRange) selection); |
| } |
| return new TextSelection(0, 0); |
| } |
| } |