| /******************************************************************************* |
| * 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.Comparator; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.jst.jsf.core.internal.tld.CMUtil; |
| import org.eclipse.jst.jsf.core.internal.tld.ITLDConstants; |
| import org.eclipse.jst.pagedesigner.adapters.IBodyInfo; |
| import org.eclipse.jst.pagedesigner.adapters.internal.BodyInfo; |
| import org.eclipse.jst.pagedesigner.dom.DOMPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition2; |
| import org.eclipse.jst.pagedesigner.dom.IDOMPosition; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * This class helps location insertion position to inside correct body or doc |
| * prefix. NOTE: this class only doing limited support on doc level position |
| * validation. Element specific position validation will be done in other |
| * places. |
| * |
| * @author mengbo |
| */ |
| public class BodyHelper { |
| // bit flags used for child skipping. |
| /** |
| * Bit flag for empty text node |
| */ |
| public static final int EMPTY_TEXT = 1; |
| |
| /** |
| * Bit flag for comment node |
| */ |
| public static final int COMMENT = 2; |
| |
| /** |
| * Bit flag for HEAD node |
| */ |
| public static final int HEADER = 3; |
| |
| /** |
| * |
| * @param child |
| * @return boolean |
| */ |
| private static boolean isSkippableChild(Node parent, Node child, int flag) { |
| if ((flag & COMMENT) != 0 && child.getNodeType() == Node.COMMENT_NODE) |
| return true; |
| if ((flag & EMPTY_TEXT) != 0 && child instanceof IDOMText |
| && ((IDOMText) child).isElementContentWhitespace()) |
| return true; |
| |
| if ((flag & HEADER) != 0 && child.getNodeType() == Node.ELEMENT_NODE) { |
| String uri = CMUtil.getElementNamespaceURI((Element) child); |
| IBodyInfo parentInfo = getBodyInfo((IDOMNode) parent); |
| if (parentInfo != null |
| && parentInfo.isBodyHeader((IDOMNode) parent, uri, |
| ((Element) child).getLocalName())) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * check whether uri/tag should be header of any body container that is |
| * ancester of the start node. |
| * |
| * @param start |
| * @param uri |
| * @param tag |
| * @return IDOMNode |
| */ |
| public static IDOMNode findHeaderContainer(IDOMNode start, String uri, |
| String tag) { |
| while (start != null) { |
| IBodyInfo designInfo = getBodyInfo(start); |
| if (designInfo != null && designInfo.isBodyContainer(start)) { |
| if (designInfo.isBodyHeader(start, uri, tag)) |
| return start; |
| } |
| start = (IDOMNode) start.getParentNode(); |
| } |
| return null; |
| } |
| |
| /** |
| * find the closest body insertion point, to make it as deep as possible. |
| * (Move into as more body as possible) |
| * @param position |
| * @return IDOMPosition |
| */ |
| public static IDOMPosition findBodyInsertLocation(IDOMPosition position) { |
| // forward first. |
| Node reference = position.getNextSiblingNode(); |
| Node container = position.getContainerNode(); |
| while (reference != null) { |
| IBodyInfo info = getBodyInfo((IDOMNode) reference); |
| if (info != null && info.isBodyContainer((IDOMNode) reference)) { |
| // good, we find a body! |
| position = new DOMPosition(reference, 0); |
| return findBodyInsertLocation(position); |
| } |
| if (isSkippableChild(container, reference, EMPTY_TEXT | COMMENT |
| | HEADER)) { |
| reference = reference.getNextSibling(); |
| continue; |
| } |
| break; |
| } |
| |
| // backward |
| reference = position.getPreviousSiblingNode(); |
| while (reference != null) { |
| IBodyInfo info = getBodyInfo((IDOMNode) reference); |
| if (info != null && info.isBodyContainer((IDOMNode) reference)) { |
| // good, we find a body! |
| position = new DOMPosition(reference, reference.getChildNodes() |
| .getLength()); |
| return findBodyInsertLocation(position); |
| } |
| // XXX: not skip header here. So if there is some header with wrong |
| // location, we will respect user. |
| if (isSkippableChild(container, reference, EMPTY_TEXT | COMMENT)) { |
| reference = reference.getPreviousSibling(); |
| continue; |
| } |
| break; |
| } |
| |
| // not find any body at same level as the insertion point. |
| return position; |
| } |
| |
| /** |
| * The element type identified by "uri" and "tag" is going to be inserted |
| * into the document. This method is used to adjust the insert position so |
| * it can be put into correct body or header section. |
| * @param uri |
| * @param tag |
| * @param position |
| * @return IDOMPosition |
| * |
| */ |
| public static IDOMPosition adjustInsertPosition(String uri, String tag, |
| IDOMPosition position) { |
| IDOMNode parent = (IDOMNode) position.getContainerNode(); |
| IBodyInfo designInfo = getBodyInfo(parent); |
| if (designInfo == null) { |
| return position; // should not happen. |
| } |
| |
| IDOMNode headerContainer = findHeaderContainer(parent, uri, tag); |
| |
| if (headerContainer == null) { |
| // the new node is not header. |
| if (shouldIgnoreAdjust(uri, tag)) { |
| return position; |
| } |
| |
| // new node is not body header. So should place inside the inner most |
| // body. |
| if (!designInfo.isBodyContainer(parent)) { |
| return position; // it's parent is not body, so we suggest |
| // it's parent already correctly located, and respect user's |
| // choice. |
| } |
| |
| // ok, we are inside some body, but we don't know whether we are in |
| // the inner most body. |
| // try to find a body container at same level and see whether we can |
| // move into that body. |
| return findBodyInsertLocation(position); |
| } |
| // good, we find a body container and the new node should be header |
| // of it. |
| Node child = headerContainer.getFirstChild(); |
| |
| // if parent is different from headerContainer, then |
| // child!=referenceHolder[0] will always be true |
| while (child != null) // && child != refNode) |
| { |
| Comparator comp = NodeLocationComparator.getInstance(); |
| // Currently the comparator deels with tags like taglib and |
| // loadbundle particularly, comparasion result 0 |
| // means it didn't compare the tags. |
| if (comp.compare(child, tag) < 0 |
| || (comp.compare(child, tag) == 0 && isSkippableChild( |
| headerContainer, child, COMMENT | EMPTY_TEXT |
| | HEADER))) { |
| child = child.getNextSibling(); |
| } else { |
| break; |
| } |
| } |
| if (child != null) { |
| return new DOMRefPosition(child, false); |
| } |
| return new DOMPosition(parent, parent.getChildNodes() |
| .getLength()); |
| } |
| |
| /** |
| * Find the position to insert a header element into the specified parent. |
| * |
| * @param uri |
| * @param tag |
| * @param parent |
| * @param ref |
| */ |
| public static void findHeaderInsertPosition(String uri, String tag, |
| Node parent, Node[] ref) { |
| Node child = parent.getFirstChild(); |
| while (child != null) { |
| Comparator comp = NodeLocationComparator.getInstance(); |
| if (comp.compare(child, tag) < 0 |
| || (comp.compare(child, tag) == 0 && isSkippableChild( |
| parent, child, COMMENT | EMPTY_TEXT | HEADER))) { |
| child = child.getNextSibling(); |
| } else { |
| break; |
| } |
| } |
| ref[0] = child; |
| return; |
| } |
| |
| /** |
| * @param position |
| * @param body |
| * @param defaultPrefix |
| * @return the new dom position based on the insert. May return null if |
| * insert fails. |
| */ |
| public static IDOMPosition insertBody(IDOMPosition position, QName body, |
| String defaultPrefix) { |
| IBodyInfo bodyInfo = getBodyInfo((IDOMNode) position.getContainerNode()); |
| |
| Node node = position.getContainerNode(); |
| final Node originalContainer = node; |
| final Node nextSibling = position.getNextSiblingNode(); |
| |
| // create the body element first. |
| Document ownerDoc; |
| if (node instanceof Document) { |
| ownerDoc = (Document) node; |
| } else { |
| ownerDoc = node.getOwnerDocument(); |
| } |
| if (ownerDoc == null) { |
| return null; // should not happen |
| } |
| |
| final String prefix = JSPUtil.getOrCreatePrefix(((IDOMNode) node).getModel(), |
| body.getNamespaceURI(), defaultPrefix); |
| final Element ele = ownerDoc.createElement((prefix == null ? "" |
| : (prefix + ":")) |
| + body.getLocalPart()); |
| |
| // need to find out the insertion point |
| while (node instanceof IDOMNode) { |
| if (bodyInfo.isBodyContainer((IDOMNode) node)) { |
| // ok, node is a body container. |
| // we could create the new node as child of node and move all |
| // node's none header children |
| // as children of the new node. |
| |
| NodeList nl = node.getChildNodes(); |
| ArrayList list = new ArrayList(); |
| for (int i = 0; i < nl.getLength(); i++) { |
| Node child = nl.item(i); |
| if (isSkippableChild(node, child, HEADER | COMMENT |
| | EMPTY_TEXT)) { |
| continue; |
| } |
| list.add(nl.item(i)); |
| } |
| for (int i = 0; i < list.size(); i++) { |
| ele.appendChild((Node) list.get(i)); |
| } |
| node.appendChild(ele); |
| |
| if (node == originalContainer) { |
| if (nextSibling == null) { |
| return new DOMRefPosition2(ele, true); |
| } else if (nextSibling.getParentNode() == ele) { |
| // next sibling is not in header part |
| return new DOMRefPosition(nextSibling, false); |
| } else { |
| return new DOMPosition(ele, 0); |
| } |
| } |
| return position; |
| } |
| node = node.getParentNode(); |
| } |
| // should not happen, because document and documentfragment node will |
| // always be body node |
| // so if reach here, means the position is not in document. |
| return null; |
| } |
| |
| /** |
| * For certain special tags, do not following the "header"/"body" separation |
| * and can't fit into the relocation process. |
| * |
| * @param uri |
| * @param tag |
| * @return true if tag is an element that should be moved in response to |
| * body insert. |
| */ |
| public static boolean shouldIgnoreAdjust(String uri, String tag) { |
| // FIXME: |
| return (ITLDConstants.URI_HTML.equalsIgnoreCase(uri) && "script" |
| .equalsIgnoreCase(tag)) |
| || (ITLDConstants.URI_JSP.equals(uri)); |
| } |
| |
| /** |
| * @param node |
| * @return the body info corresponding to node (should we use a node adapter?) |
| */ |
| public static IBodyInfo getBodyInfo(IDOMNode node) { |
| // TODO: in the future, when bodyinfo is no longer singleton, we'll use |
| // adapter mechanism. |
| return BodyInfo.getInstance(); |
| } |
| } |