blob: 3f63371d515e3e1dc27d7bd95356be3b3e74c15e [file] [log] [blame]
/*******************************************************************************
*Copyright (c) 2009 Standards for Technology in Automotive Retail 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:
* David Carver (STAR) - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.xsl.ui.internal.contentassist;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAction;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceHelper;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLRelevanceConstants;
import org.eclipse.wst.xml.ui.internal.editor.CMImageUtil;
import org.eclipse.wst.xml.ui.internal.editor.XMLEditorPluginImageHelper;
import org.eclipse.wst.xml.ui.internal.editor.XMLEditorPluginImages;
import org.eclipse.wst.xml.ui.internal.taginfo.MarkupTagInfoProvider;
import org.eclipse.wst.xml.xpath.core.util.XSLTXPathHelper;
import org.eclipse.wst.xsl.ui.internal.contentassist.contentmodel.XSLContentModelGenerator;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Adopters can extend this class to implement their own content assistance for Element
* proposals using the XML Content Model.
*
* @author David Carver
*
*/
public abstract class AbstractXMLElementContentAssistRequest extends AbstractXSLContentAssistRequest {
protected static final String XPATH_FIRST_XSLANCESTOR_NODE = "ancestor::xsl:*[1]"; //$NON-NLS-1$
protected MarkupTagInfoProvider infoProvider = null;
protected XSLContentModelGenerator contentModel;
/**
*
* @param node
* @param documentRegion
* @param completionRegion
* @param begin
* @param length
* @param filter
* @param textViewer
*/
public AbstractXMLElementContentAssistRequest(Node node,
IStructuredDocumentRegion documentRegion,
ITextRegion completionRegion, int begin, int length, String filter,
ITextViewer textViewer) {
super(node, documentRegion, completionRegion, begin, length, filter, textViewer);
}
protected Iterator<CMNode> getAvailableContentNodes(IDOMDocument domDocument, Node ancestorNode, int includeOptions) {
ModelQuery modelQuery = ModelQueryUtil.getModelQuery(domDocument);
CMElementDeclaration cmElementDec = modelQuery.getCMElementDeclaration((Element)ancestorNode);
List <CMNode> cmNodeList = modelQuery.getAvailableContent((Element)ancestorNode, cmElementDec, includeOptions);
Iterator <CMNode> cmNodeIt = cmNodeList.iterator();
return cmNodeIt;
}
protected CustomCompletionProposal createProposal(String proposedText, String additionalInfo, int offset,
Image image, int startLength) {
CustomCompletionProposal proposal = new CustomCompletionProposal(
proposedText, offset, 0, startLength + proposedText.length() - getMatchString().length(),
image, proposedText, null, additionalInfo, 0, true);
return proposal;
}
protected Image getCMNodeImage(CMNode cmNode) {
Image image = CMImageUtil.getImage(cmNode);
if (image == null) {
image = XMLEditorPluginImageHelper
.getInstance()
.getImage(
XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC);
}
return image;
}
protected String getRequiredName(Node ownerNode, CMNode cmnode) {
if (ownerNode != null) {
return DOMNamespaceHelper.computeName(cmnode, ownerNode, null);
}
return cmnode.getNodeName();
}
/**
* Retrieves cmnode's documentation to display in the completion proposal's
* additional info. If no documentation exists for cmnode, try displaying
* parentOrOwner's documentation
*
* String any documentation information to display for cmnode.
* <code>null</code> if there is nothing to display.
*/
protected String getAdditionalInfo(CMNode parentOrOwner, CMNode cmnode) {
String addlInfo = null;
if (cmnode == null) {
if (Debug.displayWarnings) {
new IllegalArgumentException("Null declaration!").printStackTrace(); //$NON-NLS-1$
}
return null;
}
addlInfo = getInfoProvider().getInfo(cmnode);
if ((addlInfo == null) && (parentOrOwner != null)) {
addlInfo = getInfoProvider().getInfo(parentOrOwner);
}
return addlInfo;
}
/**
* Gets the infoProvider.
*
* fInfoProvider and if fInfoProvider was <code>null</code> create a new
* instance
*/
protected MarkupTagInfoProvider getInfoProvider() {
if (infoProvider == null) {
infoProvider = new MarkupTagInfoProvider();
}
return infoProvider;
}
protected boolean beginsWith(String aString, String prefix) {
if ((aString == null) || (prefix == null)) {
return true;
}
return aString.toLowerCase().startsWith(prefix.toLowerCase());
}
/**
* Check to see if the current position is in an Attribute Region if so,
* return true otherwise false
* @return True if in attribute region, false otherwise.
*/
protected boolean inAttributeRegion() {
return replacementBeginPosition > documentRegion.getStartOffset(region) + region.getLength();
}
/**
* Adds proposals for the XML_TAG_NAME region.
* @param position
*/
protected void addTagNameProposals(int position) {
Node ancestorNode = null;
try {
ancestorNode = XSLTXPathHelper.selectSingleNode(getNode(),
XPATH_FIRST_XSLANCESTOR_NODE);
} catch (Exception ex) {
return;
}
List<CMNode> cmnodes = null;
if (ancestorNode.getNodeType() == Node.ELEMENT_NODE) {
cmnodes = getAvailableChildElementDeclarations(
(Element) ancestorNode, 0);
Iterator<CMNode> nodeIterator = cmnodes.iterator();
// chop off any leading <'s and whitespace from the matchstring
while ((matchString.length() > 0)
&& (Character.isWhitespace(matchString.charAt(0)) || beginsWith(
matchString, "<"))) { //$NON-NLS-1$
//$NON-NLS-1$
matchString = matchString.substring(1);
}
if (!nodeIterator.hasNext()) {
return;
}
while (nodeIterator.hasNext()) {
CMNode elementDecl = nodeIterator.next();
if (elementDecl != null) {
// only add proposals for the child element's that begin
// with the matchstring
String proposedText = null;
proposedText = contentModel.getRequiredName(ancestorNode,
elementDecl);
int cursorAdjustment = proposedText.length();
if (elementDecl instanceof CMElementDeclaration) {
CMElementDeclaration ed = (CMElementDeclaration) elementDecl;
if (ed.getContentType() == CMElementDeclaration.EMPTY) {
proposedText += contentModel.getStartTagClose(
ancestorNode, ed);
cursorAdjustment = proposedText.length();
} else {
StringBuffer sb = new StringBuffer();
contentModel.generateTag(ancestorNode, ed, sb);
// since it's a name proposal, assume '<' is
// already there
// only return the rest of the tag
proposedText = sb.toString().substring(1);
cursorAdjustment = getCursorPositionForProposedText(proposedText);
}
}
if (beginsWith(proposedText, matchString)) {
Image image = CMImageUtil.getImage(elementDecl);
if (image == null) {
image = XMLEditorPluginImageHelper
.getInstance()
.getImage(
XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC);
}
String proposedInfo = getAdditionalInfo(
getCMElementDeclaration(getParent()), elementDecl);
CustomCompletionProposal proposal = new CustomCompletionProposal(
proposedText, getReplacementBeginPosition(),
getReplacementLength(), cursorAdjustment,
image, contentModel.getRequiredName(getParent(),
elementDecl), null, proposedInfo,
XMLRelevanceConstants.R_TAG_NAME);
addProposal(proposal);
}
}
}
}
}
/** Returns a list of CMNodes that are available within this parent context
* Given the grammar shown below and a snippet of XML code (where the '|'
* indicated the cursor position)
* the list would return all of the element declarations that are
* potential child elements of Foo.
*
* grammar : Foo -> (A, B, C)
* snippet : <Foo><A>|
* result : {A, B, C}
*
* @param parent
* @param childPosition
* @return
*/
protected List<CMNode> getAvailableChildElementDeclarations(Element parent,
int childPosition) {
List modelQueryActions = getAvailableChildrenAtIndex(parent,
childPosition, ModelQuery.VALIDITY_NONE);
Iterator iterator = modelQueryActions.iterator();
List<CMNode> cmnodes = new Vector();
while (iterator.hasNext()) {
ModelQueryAction action = (ModelQueryAction) iterator.next();
if ((childPosition < 0)
|| (((action.getStartIndex() <= childPosition) && (childPosition <= action
.getEndIndex())))) {
CMNode actionCMNode = action.getCMNode();
if ((actionCMNode != null) && !cmnodes.contains(actionCMNode)) {
cmnodes.add(actionCMNode);
}
}
}
return cmnodes;
}
protected List getAvailableChildrenAtIndex(Element parent, int index,
int validityChecking) {
List list = new ArrayList();
CMElementDeclaration parentDecl = getCMElementDeclaration(parent);
if (parentDecl != null) {
ModelQuery modelQuery = ModelQueryUtil.getModelQuery(parent
.getOwnerDocument());
// taken from ActionManagers
// int editMode = modelQuery.getEditMode();
int editMode = ModelQuery.EDIT_MODE_UNCONSTRAINED;
int ic = (editMode == ModelQuery.EDIT_MODE_CONSTRAINED_STRICT) ? ModelQuery.INCLUDE_CHILD_NODES
| ModelQuery.INCLUDE_SEQUENCE_GROUPS
: ModelQuery.INCLUDE_CHILD_NODES;
modelQuery.getInsertActions(parent, parentDecl, index, ic,
validityChecking, list);
}
return list;
}
protected CMElementDeclaration getCMElementDeclaration(Node node) {
CMElementDeclaration result = null;
if (node.getNodeType() == Node.ELEMENT_NODE) {
ModelQuery modelQuery = ModelQueryUtil.getModelQuery(node
.getOwnerDocument());
if (modelQuery != null) {
result = modelQuery.getCMElementDeclaration((Element) node);
}
}
return result;
}
protected int getElementPosition(Node child) {
Node parent = child.getParentNode();
if (parent == null) {
return 0;
}
NodeList children = parent.getChildNodes();
if (children == null) {
return 0;
}
int count = 0;
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i) == child) {
return count;
} else {
// if (children.item(i).getNodeType() == Node.ELEMENT_NODE)
count++;
}
}
return 0;
}
/**
* This is the position the cursor should be in after the proposal is
* applied
*
* @param proposedText
* @return the position the cursor should be in after the proposal is
* applied
*/
protected int getCursorPositionForProposedText(String proposedText) {
int cursorAdjustment;
cursorAdjustment = proposedText.indexOf("\"\"") + 1; //$NON-NLS-1$
// otherwise, after the first tag
if (cursorAdjustment == 0) {
cursorAdjustment = proposedText.indexOf('>') + 1;
}
if (cursorAdjustment == 0) {
cursorAdjustment = proposedText.length() + 1;
}
return cursorAdjustment;
}
protected ITextRegion getNameRegion(IStructuredDocumentRegion flatNode) {
if (flatNode == null) {
return null;
}
Iterator regionList = flatNode.getRegions().iterator();
while (regionList.hasNext()) {
ITextRegion region = (ITextRegion) regionList.next();
if (isNameRegion(region)) {
return region;
}
}
return null;
}
/**
* Checks to the see if the element is in the correct region.
* @param region
* @return
*/
protected boolean isNameRegion(ITextRegion region) {
String type = region.getType();
return ((type == DOMRegionContext.XML_TAG_NAME)
|| (type == DOMRegionContext.XML_ELEMENT_DECL_NAME)
|| (type == DOMRegionContext.XML_DOCTYPE_NAME) || (type == DOMRegionContext.XML_ATTLIST_DECL_NAME));
}
}