blob: 725bcad7e53bc461a6ffec8f255c6a6af4ddd44c [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.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 ? "" //$NON-NLS-1$
: (prefix + ":")) //$NON-NLS-1$
+ 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" //$NON-NLS-1$
.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();
}
}