blob: 14914df7bff0d1d3bd7e0d1bb19cecd082e5eb84 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Jens Lukowski/Innoopract - initial renaming/restructuring
*
*******************************************************************************/
package org.eclipse.wst.xml.core.internal.contentmodel.modelqueryimpl;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.CMValidator;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAction;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*
*/
public class ModelQueryActionHelper
{
protected ModelQueryImpl modelQuery;
protected static class Action implements ModelQueryAction
{
public int kind;
public int startIndex;
public int endIndex;
public Node parent;
public CMNode cmNode;
public Object userData;
public Action(int kind, Node parent, CMNode cmNode)
{
this.kind = kind;
this.parent = parent;
this.cmNode = cmNode;
}
public Action(int kind, Node parent, CMNode cmNode, int startIndex, int endIndex)
{
this.kind = kind;
this.parent = parent;
this.cmNode = cmNode;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public int getKind()
{
return kind;
}
public int getStartIndex()
{
return startIndex;
}
public int getEndIndex()
{
return endIndex;
}
public Node getParent()
{
return parent;
}
public CMNode getCMNode()
{
return cmNode;
}
public Object getUserData()
{
return userData;
}
public void setUserData(Object object)
{
userData = object;
}
public void performAction()
{
}
}
public ModelQueryActionHelper(ModelQueryImpl modelQuery)
{
this.modelQuery = modelQuery;
}
public void getAllActions(Element parent, CMElementDeclaration ed, int validityChecking, List actionList)
{
}
// insert actions
//
public void getInsertActions(Element parent, CMElementDeclaration ed, int index, int includeOptions, int validityChecking, List actionList)
{
if ((includeOptions & ModelQuery.INCLUDE_ATTRIBUTES) != 0)
{
getInsertAttributeActions(parent, ed, validityChecking, actionList);
}
includeOptions &= ~ModelQuery.INCLUDE_ATTRIBUTES;
if ((includeOptions & ModelQuery.INCLUDE_CHILD_NODES) != 0)
{
if (index != -1)
{
getInsertChildNodeActionsAtIndex(parent, ed, index, includeOptions, validityChecking, actionList);
}
else
{
getInsertChildNodeActions(parent, ed, includeOptions, validityChecking, actionList);
}
}
}
protected void getInsertAttributeActions(Element parent, CMElementDeclaration ed, int validityChecking, List actionList)
{
// get actions for each insertable attribute
//
List availableAttributeList = modelQuery.getAvailableContent(parent, ed, ModelQuery.INCLUDE_ATTRIBUTES);
for (Iterator i = availableAttributeList.iterator(); i.hasNext(); )
{
CMAttributeDeclaration ad = (CMAttributeDeclaration)i.next();
if (modelQuery.canInsert(parent, ed, ad, 0, validityChecking))
{
Action action = new Action(ModelQueryAction.INSERT, parent, ad);
actionList.add(action);
}
}
}
protected void getInsertChildNodeActionsAtIndex(Element parent, CMElementDeclaration ed, int index, int includeOptions, int validityChecking, List actionList)
{
// get actions for each insertable attribute
//
int size = parent.getChildNodes().getLength();
if (index <= size)
{
List contentSpecificationList = modelQuery.getValidator().createContentSpecificationList(parent, ed);
List availableChildNodeList = modelQuery.getAvailableContent(parent, ed, includeOptions);
boolean isSimpleChoice = isSimpleChoiceGroupContentModel(ed);
for (Iterator i = availableChildNodeList.iterator(); i.hasNext(); )
{
CMNode cmNode = (CMNode)i.next();
if (isSimpleChoice || modelQuery.canInsert(parent, ed, cmNode, index, validityChecking, contentSpecificationList))
{
Action action = new Action(ModelQueryAction.INSERT, parent, cmNode, index, index);
actionList.add(action);
}
}
}
}
protected boolean isSimpleChoiceGroupContentModel(CMElementDeclaration ed)
{
boolean result = false;
CMNode cmNode = ed.getContent();
if (cmNode != null && cmNode.getNodeType() == CMNode.GROUP)
{
CMGroup cmGroup = (CMGroup)cmNode;
if (cmGroup.getOperator() == CMGroup.CHOICE && cmGroup.getMaxOccur() == -1)
{
result = true;
CMNodeList list = cmGroup.getChildNodes();
for (int i = list.getLength() - 1; i >= 0; i--)
{
if (list.item(i).getNodeType() != CMNode.ELEMENT_DECLARATION)
{
result = false;
break;
}
}
}
}
return result;
}
protected void getInsertChildNodeActions(Element parent, CMElementDeclaration ed, int includeOptions, int validityChecking, List actionList)
{
int size = parent.getChildNodes().getLength();
List contentSpecificationList = modelQuery.getValidator().createContentSpecificationList(parent, ed);
List availableChildNodeList = modelQuery.getAvailableContent(parent, ed, includeOptions);
boolean isSimpleChoice = isSimpleChoiceGroupContentModel(ed);
for (Iterator iterator = availableChildNodeList.iterator(); iterator.hasNext(); )
{
CMNode cmNode = (CMNode)iterator.next();
for (int i = size; i >= 0; i--)
{
if (isSimpleChoice || modelQuery.canInsert(parent, ed, cmNode, i, validityChecking, contentSpecificationList))
{
Action action = new Action(ModelQueryAction.INSERT, parent, cmNode, i, i);
actionList.add(action);
break;
}
}
}
}
public void getInsertActions(Document parent, CMDocument cmDocument, int index, int includeOptions, int validityChecking, List actionList)
{
// get the root element and doctype index (if any)
//
int doctypeIndex = -1;
DocumentType doctype = null;
Element rootElement = null;
NodeList nodeList = parent.getChildNodes();
int nodeListLength = nodeList.getLength();
for (int i = 0; i < nodeListLength; i++)
{
Node childNode = nodeList.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE)
{
rootElement = (Element)childNode;
break;
}
else if (childNode.getNodeType() == Node.DOCUMENT_TYPE_NODE)
{
doctype = (DocumentType)childNode;
doctypeIndex = i;
}
}
// make sure that root elements are only added after the doctype (if any)
if (rootElement == null && index > doctypeIndex)
{
CMNamedNodeMap map = cmDocument.getElements();
int mapLength = map.getLength();
for (int i = 0; i < mapLength; i++)
{
CMNode cmNode = map.item(i);
boolean canAdd = true;
if (validityChecking == ModelQuery.VALIDITY_STRICT)
{
canAdd = doctype == null || doctype.getName().equals(cmNode.getNodeName());
}
if (canAdd)
{
Action action = new Action(ModelQueryAction.INSERT, parent, cmNode, index, index);
actionList.add(action);
}
}
}
}
public void getInsertChildNodeActionTable(Element parent, CMElementDeclaration ed, int validityChecking, Hashtable actionTable)
{
}
public void getReplaceActions(Element parent, CMElementDeclaration ed, int includeOptions, int validityChecking, List actionList)
{
CMValidator.MatchModelNode matchModelNode = modelQuery.getValidator().getMatchModel(ed, parent);
if (matchModelNode != null)
{
MatchModelVisitor visitor = new MatchModelVisitor(parent, actionList);
visitor.visitMatchModelNode(matchModelNode);
}
}
public void getReplaceActions(Element parent, CMElementDeclaration ed, List selectedChildren, int includeOptions, int validityChecking, List actionList)
{
int[] range = getRange(parent, selectedChildren);
if (range != null)
{
if (isContiguous(parent, range, selectedChildren))
{
List tempList = new Vector();
getReplaceActions(parent, ed, includeOptions, validityChecking, tempList);
if ((includeOptions & ModelQuery.INCLUDE_ENCLOSING_REPLACE_ACTIONS) != 0)
{
removeActionsNotContainingRange(tempList, range[0], range[1]);
}
else
{
removeActionsNotMatchingRange(tempList, range[0], range[1]);
}
actionList.addAll(tempList);
}
}
if (selectedChildren.size() == 1)
{
Node node = (Node)selectedChildren.get(0);
if (node.getNodeType() == Node.ELEMENT_NODE)
{
Element childElement = (Element)node;
CMNode childEd = modelQuery.getCMElementDeclaration(childElement);
if (childEd != null)
{
CMNode childOrigin= modelQuery.getOrigin(childElement);
CMNodeList cmNodeList = childOrigin != null ?
(CMNodeList)childOrigin.getProperty("SubstitutionGroup") : //$NON-NLS-1$
(CMNodeList)childEd.getProperty("SubstitutionGroup"); //$NON-NLS-1$
if (cmNodeList != null && cmNodeList.getLength() > 1)
{
int replaceIndex = getIndex(parent, childElement);
String childEdName = childEd.getNodeName();
for (int i = 0; i < cmNodeList.getLength(); i++)
{
CMNode substitution = cmNodeList.item(i);
if (!substitution.getNodeName().equals(childEdName) && !Boolean.TRUE.equals(substitution.getProperty("Abstract"))) //$NON-NLS-1$
{
Action action = new Action(ModelQueryAction.REPLACE, parent, cmNodeList.item(i), replaceIndex, replaceIndex);
actionList.add(action);
}
}
}
}
}
}
}
// returns true if the selected nodes are contiguous
//
protected boolean isContiguous(Element parent, int[] range, List selectedNodeList)
{
boolean result = true;
NodeList nodeList = parent.getChildNodes();
// issue: nodeListLength was never read, but in theory,
// nodelList.getLength() might cause some clearing of cached
// data, or something, so leaving in a potential meaningless call, for now.
//int nodeListLength = nodeList.getLength();
nodeList.getLength();
for (int i = range[0]; i < range[1]; i++)
{
Node node = nodeList.item(i);
if (!isWhitespaceNode(node) && !selectedNodeList.contains(node))
{
result = false;
break;
}
}
return result;
}
protected int[] getRange(Element parent, List list)
{
int[] result = null;
int first = -1;
int last = -1;
NodeList nodeList = parent.getChildNodes();
int nodeListLength = nodeList.getLength();
for (int i = 0; i < nodeListLength; i++)
{
Node node = nodeList.item(i);
if (list.contains(node))
{
first = (first == -1) ? i : Math.min(first, i);
last = Math.max(last, i);
}
}
if (first != -1 && last!= -1)
{
result = new int[2];
result[0] = first;
result[1] = last;
}
return result;
}
protected boolean isWhitespaceNode(Node node)
{
return node.getNodeType() == Node.TEXT_NODE &&
node.getNodeValue().trim().length() == 0;
}
protected int getIndex(Node parentNode, Node child)
{
NodeList nodeList = parentNode.getChildNodes();
int index = -1;
int size = nodeList.getLength();
for (int i = 0; i < size; i++)
{
if (nodeList.item(i) == child)
{
index = i;
break;
}
}
return index;
}
protected boolean isActionContainingRange(ModelQueryAction action, int startIndex, int endIndex)
{
int actionStartIndex = action.getStartIndex();
int actionEndIndex = action.getEndIndex();
return (actionStartIndex <= startIndex &&
actionEndIndex >= endIndex);
}
protected boolean isActionMatchingRange(ModelQueryAction action, int startIndex, int endIndex)
{
int actionStartIndex = action.getStartIndex();
int actionEndIndex = action.getEndIndex();
return (actionStartIndex == startIndex &&
actionEndIndex == endIndex);
}
protected void removeActionsNotContainingRange(List actionList, int startIndex, int endIndex)
{
for (int i = actionList.size() - 1; i >= 0; i--)
{
ModelQueryAction action = (ModelQueryAction)actionList.get(i);
if (!isActionContainingRange(action, startIndex, endIndex))
{
actionList.remove(i);
}
}
}
protected void removeActionsNotMatchingRange(List actionList, int startIndex, int endIndex)
{
for (int i = actionList.size() - 1; i >= 0; i--)
{
ModelQueryAction action = (ModelQueryAction)actionList.get(i);
if (!isActionMatchingRange(action, startIndex, endIndex))
{
actionList.remove(i);
}
}
}
public static class MatchModelVisitor
{
int indent;
int elementIndex;
Node parent;
List actionList;
public MatchModelVisitor(Node parent, List actionList)
{
this.parent = parent;
this.actionList = actionList;
}
public int indexOfNextElement(int start)
{
NodeList nodeList = parent.getChildNodes();
int length = nodeList.getLength();
int result = length;
for (int i = start; i < length; i++)
{
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE)
{
result = i;
break;
}
}
return result;
}
public void visitMatchModelNode(CMValidator.MatchModelNode matchModelNode)
{
int startIndex = indexOfNextElement(elementIndex);
//String cmNodeName = matchModelNode.cmNode != null ? matchModelNode.cmNode.getNodeName() : "null";
//printIndented(indent, "+MatchModelNode : " + cmNodeName + " " + startIndex);
indent += 2;
for (Iterator iterator = matchModelNode.children.iterator(); iterator.hasNext(); )
{
CMValidator.MatchModelNode child = (CMValidator.MatchModelNode)iterator.next();
visitMatchModelNode(child);
}
indent -= 2;
if (matchModelNode.cmNode != null)
{
int nodeType = matchModelNode.cmNode.getNodeType();
if (nodeType == CMNode.GROUP)
{
CMGroup group = (CMGroup)matchModelNode.cmNode;
if (group.getOperator() == CMGroup.CHOICE)
{
addReplaceActions(matchModelNode, group, startIndex, elementIndex - 1);
}
}
else if (nodeType == CMNode.ELEMENT_DECLARATION)
{
elementIndex = startIndex + 1;
}
//printIndented(indent, "-MatchModelNode : " + cmNodeName + " " + (elementIndex - 1));
}
}
public void addReplaceActions(CMValidator.MatchModelNode matchModelNode, CMGroup group, int startIndex, int endIndex)
{
CMNode excludeCMNode = null;
if (matchModelNode.children.size() > 0)
{
CMValidator.MatchModelNode child = (CMValidator.MatchModelNode)matchModelNode.children.get(0);
excludeCMNode = child.cmNode;
}
CMNodeList nodeList = group.getChildNodes();
int size = nodeList.getLength();
for (int i = 0; i < size; i++)
{
CMNode alternative = nodeList.item(i);
if (alternative != excludeCMNode)
{
Action action = new Action(ModelQueryAction.REPLACE, parent, alternative, startIndex, endIndex);
actionList.add(action);
}
}
}
}
//public static void printIndented(int indent, String string)
//{
// for (int i = 0; i < indent; i++)
// {
// System.out.print(" ");
// }
// System.out.println(string);
//}
}