/******************************************************************************* * Copyright (c) 2010, 2012 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 *******************************************************************************/ package org.eclipse.wst.xml.ui.internal.contentassist; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Vector; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; 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.provisional.text.ITextRegionList; import org.eclipse.wst.sse.core.internal.util.Debug; import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext; import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils; import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal; import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration; import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType; 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.CMEntityDeclaration; 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.basic.CMNamedNodeMapImpl; 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.document.AttrImpl; 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.provisional.document.IDOMElement; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; import org.eclipse.wst.xml.ui.internal.XMLUIMessages; import org.eclipse.wst.xml.ui.internal.XMLUIPlugin; 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.preferences.XMLUIPreferenceNames; import org.eclipse.wst.xml.ui.internal.taginfo.MarkupTagInfoProvider; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * <p>Implementation of an {@link AbstractXMLCompletionProposalComputer} that uses {@link ModelQuery}s * to make its proposals.</p> * * @base org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor */ public abstract class AbstractXMLModelQueryCompletionProposalComputer extends AbstractXMLCompletionProposalComputer { private static MarkupTagInfoProvider infoProvider = new MarkupTagInfoProvider(); /** * <p>Default constructor</p> */ public AbstractXMLModelQueryCompletionProposalComputer() { } /** * <p>default is to do nothing</p> * * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionEnded() */ public void sessionEnded() { //default is to do nothing } /** * <p>default is to do nothing</p> * * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionStarted() */ public void sessionStarted() { //default is to do nothing } /** * @return {@link XMLContentModelGenerator} used to generate proposals */ protected abstract XMLContentModelGenerator getContentGenerator(); /** * <p>Given a {@link CMNode} generated by a model query should decide if the * action is valid for this implementation of the model query proposal computer</p> * * <p>This is needed because {@link ModelQuery}s return a lot of {@link CMNode}s that * can come from multiple sources and a particular computer may not want to propose * all of the actions as content assist proposals</p> * * @param action {@link CMNode} to decide if it is valid as a result * for this model query proposal computer * * @return <code>true</code> if the given {@link CMNode} is valid for this * computer, <code>false</code> otherwise */ protected abstract boolean validModelQueryNode(CMNode node); protected void addAttributeNameProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { IDOMNode node = (IDOMNode) contentAssistRequest.getNode(); IStructuredDocumentRegion sdRegion = contentAssistRequest.getDocumentRegion(); // retrieve the list of attributes CMElementDeclaration elementDecl = getCMElementDeclaration(node); if (elementDecl != null) { CMNamedNodeMapImpl attributes = new CMNamedNodeMapImpl(elementDecl.getAttributes()); addModelQueryAttributeDeclarations(node, elementDecl,attributes); String matchString = contentAssistRequest.getMatchString(); int cursorOffset = context.getInvocationOffset(); // check whether an attribute really exists for the replacement // offsets AND if it possesses a value boolean attrAtLocationHasValue = false; boolean proposalNeedsSpace = false; NamedNodeMap attrs = node.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { AttrImpl existingAttr = (AttrImpl) attrs.item(i); ITextRegion name = existingAttr.getNameRegion(); if (name != null && (sdRegion.getStartOffset(name) <= contentAssistRequest.getReplacementBeginPosition()) && (sdRegion.getStartOffset(name) + name.getLength() >= contentAssistRequest.getReplacementBeginPosition() + contentAssistRequest.getReplacementLength()) && (existingAttr.getValueRegion() != null)) { // selected region is attribute name if (cursorOffset >= sdRegion.getStartOffset(name) && contentAssistRequest.getReplacementLength() != 0) attrAtLocationHasValue = true; // propose new attribute, cursor is at the start of another attribute name else if (cursorOffset == sdRegion.getStartOffset(name)) proposalNeedsSpace = true; break; } } // only add proposals for the attributes whose names begin with the matchstring if (attributes != null) { for (int i = 0; i < attributes.getLength(); i++) { CMAttributeDeclaration attrDecl = (CMAttributeDeclaration) attributes.item(i); if(validModelQueryNode(attrDecl)) { int isRequired = 0; if (attrDecl.getUsage() == CMAttributeDeclaration.REQUIRED) { isRequired = XMLRelevanceConstants.R_REQUIRED; } boolean showAttribute = true; showAttribute = showAttribute && beginsWith(getRequiredName(node, attrDecl), matchString.trim()); AttrImpl attr = (AttrImpl) node.getAttributes().getNamedItem(getRequiredName(node, attrDecl)); ITextRegion nameRegion = attr != null ? attr.getNameRegion() : null; // nameRegion.getEndOffset() + 1 is required to allow for // matches against the full name of an existing Attr showAttribute = showAttribute && ((attr == null) || nameRegion == null || ((nameRegion != null) && (sdRegion.getStartOffset(nameRegion) < contentAssistRequest.getReplacementBeginPosition()) && (sdRegion.getStartOffset(nameRegion) + nameRegion.getLength() >= (contentAssistRequest.getReplacementBeginPosition() + contentAssistRequest.getReplacementLength()) ))); if (showAttribute) { //get the proposal image Image attrImage = CMImageUtil.getImage(attrDecl); if (attrImage == null) { if (isRequired > 0) { attrImage = this.getRequiredAttributeImage(); } else { attrImage = this.getNotRequiredAttributeImage(); } } String proposedText = null; String proposedInfo = getAdditionalInfo(elementDecl, attrDecl); CustomCompletionProposal proposal = null; // attribute is at this location and already exists if (attrAtLocationHasValue) { // only propose the name proposedText = getRequiredName(node, attrDecl); proposal = new MarkupCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), proposedText.length(), attrImage, proposedText, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_NAME + isRequired, true); } // no attribute exists or is elsewhere, generate // minimally else { Attr existingAttrNode = (Attr) node.getAttributes().getNamedItem(getRequiredName(node, attrDecl)); String value = null; if (existingAttrNode != null && existingAttrNode.getSpecified()) { value = existingAttrNode.getNodeValue(); } int cursorPosition = 0; if ((value != null) && (value.length() > 0)) { proposedText = getRequiredName(node, attrDecl); cursorPosition = proposedText.length() + 2; } else { proposedText = getRequiredText(node, attrDecl); // skip the cursor past a fixed value if (attrDecl.getAttrType() != null && attrDecl.getAttrType().getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED) cursorPosition = proposedText.length(); else cursorPosition = getRequiredName(node, attrDecl).length() + 2; } if (proposalNeedsSpace) proposedText += " "; //$NON-NLS-1$ proposal = new MarkupCompletionProposal(proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), cursorPosition, attrImage, // if the value isn't empty (no empty set of quotes), show it // BUG 203494, content strings may have "", but not be empty // An empty string is when there's no content between double quotes // and there is no single quote that may be encasing a double quote ((proposedText.lastIndexOf('\"') - proposedText.indexOf('\"') == 1 && proposedText.indexOf('\'') == -1)) ? getRequiredName(node, attrDecl) : proposedText, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_NAME + isRequired); } contentAssistRequest.addProposal(proposal); } } } } } else { setErrorMessage(NLS.bind(XMLUIMessages.Element__is_unknown, (new Object[]{node.getNodeName()}))); } } protected void addAttributeValueProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { IDOMNode node = (IDOMNode) contentAssistRequest.getNode(); // Find the attribute region and name for which this position should // have a value proposed IStructuredDocumentRegion open = node.getFirstStructuredDocumentRegion(); ITextRegionList openRegions = open.getRegions(); int i = openRegions.indexOf(contentAssistRequest.getRegion()); if (i < 0) { return; } ITextRegion nameRegion = null; while (i >= 0) { nameRegion = openRegions.get(i--); if (nameRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { break; } } // the name region is REQUIRED to do anything useful if (nameRegion != null) { // Retrieve the declaration CMElementDeclaration elementDecl = getCMElementDeclaration(node); // String attributeName = nameRegion.getText(); String attributeName = open.getText(nameRegion); CMAttributeDeclaration attrDecl = null; // No CMElementDeclaration means no attribute metadata, but retrieve the // declaration for the attribute otherwise if (elementDecl != null) { CMNamedNodeMapImpl allAttributes = new CMNamedNodeMapImpl(elementDecl.getAttributes()) { private Map caseInsensitive; private Map getCaseInsensitiveMap() { if(caseInsensitive == null) caseInsensitive = new HashMap(); return caseInsensitive; } public CMNode getNamedItem(String name) { CMNode node = super.getNamedItem(name); if (node == null) { node = (CMNode) getCaseInsensitiveMap().get(name.toLowerCase(Locale.US)); } return node; } public void put(CMNode cmNode) { super.put(cmNode); getCaseInsensitiveMap().put(cmNode.getNodeName().toLowerCase(Locale.US), cmNode); } }; this.addModelQueryAttributeDeclarations(node, elementDecl, allAttributes); String noprefixName = DOMNamespaceHelper.getUnprefixedName(attributeName); if (allAttributes != null) { attrDecl = (CMAttributeDeclaration) allAttributes.getNamedItem(attributeName); if (attrDecl == null) { attrDecl = (CMAttributeDeclaration) allAttributes.getNamedItem(noprefixName); } } if (attrDecl == null) { setErrorMessage(XMLUIMessages.No_known_attribute__UI_ + attributeName); } } String currentValue = node.getAttributes().getNamedItem(attributeName).getNodeValue(); String proposedInfo = null; //get proposal image Image image = CMImageUtil.getImage(attrDecl); if (image == null) { if ((attrDecl != null) && (attrDecl.getUsage() == CMAttributeDeclaration.REQUIRED)) { image = this.getRequiredAttributeImage(); } else { image = this.getNotRequiredAttributeImage(); } } if ((attrDecl != null) && (attrDecl.getAttrType() != null)) { // attribute is known, prompt with values from the declaration proposedInfo = getAdditionalInfo(elementDecl, attrDecl); List possibleValues = getPossibleDataTypeValues(node, attrDecl); String defaultValue = attrDecl.getAttrType().getImpliedValue(); String qualifiedDelimiter = (String) attrDecl.getProperty("qualified-delimiter"); //$NON-NLS-1$ if (possibleValues.size() > 0 || defaultValue != null) { // ENUMERATED VALUES String matchString = contentAssistRequest.getMatchString(); if (matchString == null) { matchString = ""; //$NON-NLS-1$ } if ((matchString.length() > 0) && (matchString.startsWith("\"") || matchString.startsWith("'"))) { //$NON-NLS-1$ //$NON-NLS-2$ matchString = matchString.substring(1); } boolean currentValid = false; //create suggestions for enumerated values int rOffset = contentAssistRequest.getReplacementBeginPosition(); int rLength = contentAssistRequest.getReplacementLength(); for (Iterator j = possibleValues.iterator(); j.hasNext();) { String possibleValue = (String) j.next(); String alternateMatch = null; if (qualifiedDelimiter != null) { int delimiter = possibleValue.lastIndexOf(qualifiedDelimiter); if (delimiter >= 0 && delimiter < possibleValue.length() - 1) { alternateMatch = possibleValue.substring(delimiter + 1); } } if(!possibleValue.equals(defaultValue)) { currentValid = currentValid || possibleValue.equals(currentValue); if ((matchString.length() == 0) || possibleValue.startsWith(matchString)) { String rString = "\"" + possibleValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$ alternateMatch = "\"" + alternateMatch; //$NON-NLS-1$ CustomCompletionProposal proposal = new MarkupCompletionProposal( rString, rOffset, rLength, possibleValue.length() + 1, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM), rString, alternateMatch, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE, true); contentAssistRequest.addProposal(proposal); } } } if(defaultValue != null && ((matchString.length() == 0) || defaultValue.startsWith(matchString))) { String rString = "\"" + defaultValue + "\""; //$NON-NLS-1$ //$NON-NLS-2$ final String regionText = contentAssistRequest.getDocumentRegion().getText(contentAssistRequest.getRegion()); final int matchStringLength = contentAssistRequest.getMatchString().length(); if (matchString.length() > 0 && matchStringLength < regionText.length()) { final String remaining = regionText.substring(matchStringLength).trim(); if (remaining.charAt(0) != '\'' && remaining.charAt(0) != '"') { rLength = matchStringLength; } } CustomCompletionProposal proposal = new MarkupCompletionProposal( rString, rOffset, rLength, defaultValue.length() + 1, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT), rString, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE); contentAssistRequest.addProposal(proposal); } } else if (((attrDecl.getUsage() == CMAttributeDeclaration.FIXED) || (attrDecl.getAttrType().getImpliedValueKind() == CMDataType.IMPLIED_VALUE_FIXED)) && (attrDecl.getAttrType().getImpliedValue() != null)) { // FIXED values String value = attrDecl.getAttrType().getImpliedValue(); if ((value != null) && (value.length() > 0)) { String rValue = "\"" + value + "\"";//$NON-NLS-2$//$NON-NLS-1$ CustomCompletionProposal proposal = new MarkupCompletionProposal( rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), rValue.length() + 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE); contentAssistRequest.addProposal(proposal); if ((currentValue.length() > 0) && !value.equals(currentValue)) { rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$ proposal = new MarkupCompletionProposal(rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), rValue.length() + 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE); contentAssistRequest.addProposal(proposal); } } } } else { // unknown attribute, so supply nice empty values proposedInfo = getAdditionalInfo(null, elementDecl); CustomCompletionProposal proposal = null; if ((currentValue != null) && (currentValue.length() > 0)) { final String regionText = open.getText(contentAssistRequest.getRegion()); if (regionText.charAt(0) != '"' && regionText.charAt(0) != '\'') { String rValue = "\"" + currentValue + "\""; //$NON-NLS-2$//$NON-NLS-1$ proposal = new MarkupCompletionProposal(rValue, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 1, image, rValue, null, proposedInfo, XMLRelevanceConstants.R_XML_ATTRIBUTE_VALUE); contentAssistRequest.addProposal(proposal); } } } } else { setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_); } } protected void addCommentProposal( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { contentAssistRequest.addProposal(new CustomCompletionProposal("<!-- -->", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 5, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_COMMENT), NLS.bind(XMLUIMessages.Comment__, (new Object[]{" <!-- -->"})), //$NON-NLS-1$ null, null, XMLRelevanceConstants.R_COMMENT)); } /** * Add the proposals for the name in an end tag */ protected void addEndTagNameProposals(ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { if (contentAssistRequest.getStartOffset() + contentAssistRequest.getRegion().getTextLength() < contentAssistRequest.getReplacementBeginPosition()) { CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 1, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC), NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$ null, null, XMLRelevanceConstants.R_END_TAG_NAME); contentAssistRequest.addProposal(proposal); } else { Node aNode = contentAssistRequest.getNode(); String matchString = contentAssistRequest.getMatchString(); if (matchString.startsWith("</")) { //$NON-NLS-1$ matchString = matchString.substring(2); } while (aNode != null) { if (aNode.getNodeType() == Node.ELEMENT_NODE) { if (aNode.getNodeName().startsWith(matchString)) { IDOMNode aXMLNode = (IDOMNode) aNode; CMElementDeclaration ed = getCMElementDeclaration(aNode); //declaration must be valid for this computer to make proposal if ((aXMLNode.getEndStructuredDocumentRegion() == null) && (ed == null || (validModelQueryNode(ed) && ed.getContentType() != CMElementDeclaration.EMPTY))) { String replacementText = aNode.getNodeName(); String displayText = replacementText; String proposedInfo = (ed != null) ? getAdditionalInfo(null, ed) : null; if(!contentAssistRequest.getDocumentRegion().isEnded()) { replacementText += ">"; //$NON-NLS-1$ } CustomCompletionProposal proposal = null; // double check to see if the region acted upon is // a tag name; replace it if so Image image = CMImageUtil.getImage(ed); if (image == null) { image = this.getGenericTagImage(); } if (contentAssistRequest.getRegion().getType() == DOMRegionContext.XML_TAG_NAME) { proposal = new MarkupCompletionProposal( replacementText, contentAssistRequest.getStartOffset(), contentAssistRequest.getRegion().getTextLength(), replacementText.length(), image, displayText, null, proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME); } else { proposal = new MarkupCompletionProposal( replacementText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), replacementText.length(), image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{"'" + displayText + "'"})), //$NON-NLS-1$ //$NON-NLS-2$ null, proposedInfo, XMLRelevanceConstants.R_END_TAG_NAME); } contentAssistRequest.addProposal(proposal); } } } aNode = aNode.getParentNode(); } } } /** * Prompt for end tags to a non-empty Node that hasn't ended Handles these * cases: <br> * <tagOpen>| <br> * <tagOpen>< |<br> * <tagOpen></ | * * @param contentAssistRequest */ protected void addEndTagProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { IDOMNode node = (IDOMNode) contentAssistRequest.getParent(); if (isCommentNode(node)) { // loop and find non comment node parent while ((node != null) && isCommentNode(node)) { node = (IDOMNode) node.getParentNode(); } } // node is already closed if (node.isClosed()) { // loop and find non comment unclose node parent while ((node != null) && node.isClosed()) { node = (IDOMNode) node.getParentNode(); } } // there were no unclosed tags if (node == null) { return; } // data to create a CustomCompletionProposal String replaceText = node.getNodeName() + ">"; //$NON-NLS-1$ int replaceBegin = contentAssistRequest.getReplacementBeginPosition(); int replaceLength = contentAssistRequest.getReplacementLength(); int cursorOffset = node.getNodeName().length() + 1; String displayString = ""; //$NON-NLS-1$ String proposedInfo = ""; //$NON-NLS-1$ Image image = this.getGenericTagImage(); setErrorMessage(null); boolean addProposal = false; if (node.getNodeType() == Node.ELEMENT_NODE) { // //////////////////////////////////////////////////////////////////////////////////// IStructuredDocument sDoc = (IStructuredDocument) context.getDocument(); IStructuredDocumentRegion xmlEndTagOpen = sDoc.getRegionAtCharacterOffset(contentAssistRequest.getReplacementBeginPosition()); // skip backward to "<", "</", or the (unclosed) start tag, null if not found String type = ""; //$NON-NLS-1$ while ((xmlEndTagOpen != null) && ((type = xmlEndTagOpen.getType()) != DOMRegionContext.XML_END_TAG_OPEN) && (type != DOMRegionContext.XML_TAG_CLOSE) && !needsEndTag(xmlEndTagOpen, context) && (type != DOMRegionContext.XML_TAG_OPEN)) { xmlEndTagOpen = xmlEndTagOpen.getPrevious(); } if (xmlEndTagOpen == null) { return; } node = (IDOMNode) node.getModel().getIndexedRegion(xmlEndTagOpen.getStartOffset()); node = (IDOMNode) node.getParentNode(); if (isStartTag(xmlEndTagOpen)) { // this is the case for a start tag w/out end tag // eg: // <p> // <% String test = "test"; %> // | if (needsEndTag(xmlEndTagOpen, context)) { String tagName = getTagName(xmlEndTagOpen); xmlEndTagOpen.getTextEndOffset(); replaceLength = 0; replaceText = "</" + tagName + ">"; //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$ cursorOffset = tagName.length() + 3; displayString = NLS.bind(XMLUIMessages.End_with__, (new Object[]{tagName})); addProposal = true; } } else if (type == DOMRegionContext.XML_END_TAG_OPEN) { // this is the case for: <tag> </ | // possibly <tag> </ |<anotherTag> // should only be replacing white space... replaceLength = (replaceBegin > xmlEndTagOpen.getTextEndOffset()) ? replaceBegin - xmlEndTagOpen.getTextEndOffset() : 0; replaceText = node.getNodeName() + ">"; //$NON-NLS-1$ cursorOffset = replaceText.length(); replaceBegin = xmlEndTagOpen.getTextEndOffset(); displayString = NLS.bind(XMLUIMessages.End_with_, (new Object[]{node.getNodeName()})); addProposal = true; } else if (type == DOMRegionContext.XML_TAG_OPEN) { // this is the case for: <tag> < | replaceText = "/" + node.getNodeName() + ">"; //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$ cursorOffset = replaceText.length(); // should only be replacing white space... replaceLength = (replaceBegin > xmlEndTagOpen.getTextEndOffset()) ? replaceBegin - xmlEndTagOpen.getTextEndOffset() : 0; replaceBegin = xmlEndTagOpen.getTextEndOffset(); displayString = NLS.bind(XMLUIMessages.End_with_, (new Object[]{"/" + node.getNodeName()})); //$NON-NLS-1$ addProposal = true; } } // //////////////////////////////////////////////////////////////////////////////////// // sometimes the node is not null, but // getNodeValue() is null, put in a null check else if ((node.getNodeValue() != null) && (node.getNodeValue().indexOf("</") != -1)) { //$NON-NLS-1$ // the case where "</" is started, but the nodes comes in as a // text node (instead of element) // like this: <tag> </| Node parent = node.getParentNode(); if ((parent != null) && (parent.getNodeType() != Node.DOCUMENT_NODE)) { replaceText = parent.getNodeName() + ">"; //$NON-NLS-1$ cursorOffset = replaceText.length(); displayString = NLS.bind(XMLUIMessages.End_with__, (new Object[]{parent.getNodeName()})); setErrorMessage(null); addProposal = true; } } // //////////////////////////////////////////////////////////////////////////////////// else if (node.getNodeType() == Node.DOCUMENT_NODE) { setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_); } if (addProposal == true) { CustomCompletionProposal proposal = new MarkupCompletionProposal(replaceText, replaceBegin, replaceLength, cursorOffset, image, displayString, null, proposedInfo, XMLRelevanceConstants.R_END_TAG); contentAssistRequest.addProposal(proposal); } } protected void addEntityProposals( ContentAssistRequest contentAssistRequest, ITextRegion completionRegion, IDOMNode treeNode, CompletionProposalInvocationContext context) { ICompletionProposal[] eps = computeEntityReferenceProposals(completionRegion, treeNode, context); for (int i = 0; (eps != null) && (i < eps.length); i++) { contentAssistRequest.addProposal(eps[i]); } } protected void addEntityProposals(Vector proposals, Properties map, String key, int nodeOffset, IStructuredDocumentRegion sdRegion, ITextRegion completionRegion, CompletionProposalInvocationContext context) { if (map == null) { return; } String entityName = ""; //$NON-NLS-1$ String entityValue = ""; //$NON-NLS-1$ Image entityIcon = this.getEntityReferenceImage(); String replacementText = ""; //$NON-NLS-1$ String displayString = ""; //$NON-NLS-1$ Enumeration keys = map.keys(); while ((keys != null) && keys.hasMoreElements()) { entityName = (String) keys.nextElement(); entityValue = map.getProperty(entityName); // filter based on partial entity string... if (entityName.toLowerCase().startsWith(key.toLowerCase()) || key.trim().equals("")) //$NON-NLS-1$ { // figure out selection...if text is selected, add it to // selection length int selectionLength = nodeOffset; if (context.getViewer() != null) { selectionLength += context.getViewer().getSelectedRange().y; } // create a new proposal for entity string... replacementText = "&" + entityName + ";"; //$NON-NLS-1$ //$NON-NLS-2$ displayString = "&" + entityName + "; (" + entityValue + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ICompletionProposal cp = new CustomCompletionProposal(replacementText, sdRegion.getStartOffset(completionRegion), selectionLength, replacementText.length(), entityIcon, displayString, null, null, XMLRelevanceConstants.R_ENTITY); if (cp != null) { proposals.add(cp); } } } } protected void addPCDATAProposal(String nodeName, ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { CustomCompletionProposal proposal = new CustomCompletionProposal("<![CDATA[]]>", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 9, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_CDATASECTION), "CDATA Section", //$NON-NLS-1$ null, null, XMLRelevanceConstants.R_CDATA); contentAssistRequest.addProposal(proposal); proposal = new CustomCompletionProposal(nodeName, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), nodeName.length(), XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TXTEXT), "#PCDATA", //$NON-NLS-1$ null, null, XMLRelevanceConstants.R_CDATA); contentAssistRequest.addProposal(proposal); } protected void addStartDocumentProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { //determine if XMLPI is first element Node aNode = contentAssistRequest.getNode(); Document owningDocument = aNode.getOwnerDocument(); Node first = owningDocument.getFirstChild(); boolean xmlpiIsFirstElement = ((first != null) && (first.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)); // make sure xmlpi is root element don't want doctype proposal if XMLPI isn't first element... if (xmlpiIsFirstElement && (owningDocument.getDoctype() == null) && isCursorAfterXMLPI(contentAssistRequest)) { addDocTypeProposal(contentAssistRequest, context); } } /** * Close an unclosed start tag */ protected void addTagCloseProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { IDOMNode node = (IDOMNode) contentAssistRequest.getParent(); if (node.getNodeType() == Node.ELEMENT_NODE) { CMElementDeclaration elementDecl = getCMElementDeclaration(node); String proposedInfo = (elementDecl != null) ? getAdditionalInfo(null, elementDecl) : null; int contentType = (elementDecl != null) ? elementDecl.getContentType() : CMElementDeclaration.ANY; // if it's XML and content doesn't HAVE to be element, add "/>" proposal. boolean endWithSlashBracket = (isXMLNode(node) && (contentType != CMElementDeclaration.ELEMENT)); //get the image Image image = CMImageUtil.getImage(elementDecl); if (image == null) { image = this.getGenericTagImage(); } // is the start tag ended properly? if ((contentAssistRequest.getDocumentRegion() == node.getFirstStructuredDocumentRegion()) && !(node.getFirstStructuredDocumentRegion()).isEnded()) { setErrorMessage(null); // Is this supposed to be an empty tag? Note that if we can't // tell, we assume it's not. if ((elementDecl != null) && (elementDecl.getContentType() == CMElementDeclaration.EMPTY)) { // prompt with a self-closing end character if needed // this is one of the few times to ignore the length -- always insert // contentAssistRequest.getReplacementLength() CustomCompletionProposal proposal = new MarkupCompletionProposal( getContentGenerator().getStartTagClose(node, elementDecl), contentAssistRequest.getReplacementBeginPosition(), 0, getContentGenerator().getStartTagClose(node, elementDecl).length(), image, NLS.bind(XMLUIMessages.Close_with___,(new Object[]{getContentGenerator().getStartTagClose(node, elementDecl)})), null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG); contentAssistRequest.addProposal(proposal); } else { // prompt with a close for the start tag CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), // this is one of the few times to ignore the // length -- always insert // contentAssistRequest.getReplacementLength(), 0, 1, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$ null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG); contentAssistRequest.addProposal(proposal); // prompt with the closer for the start tag and an end tag if one is not present if (node.getEndStructuredDocumentRegion() == null) { // make sure tag name is actually what it thinks it // is...(eg. <%@ vs. <jsp:directive) IStructuredDocumentRegion sdr = contentAssistRequest.getDocumentRegion(); String openingTagText = (sdr != null) ? sdr.getFullText() : ""; //$NON-NLS-1$ if ((openingTagText != null) && (openingTagText.indexOf(node.getNodeName()) != -1)) { proposal = new MarkupCompletionProposal("></" + node.getNodeName() + ">", //$NON-NLS-2$//$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), // this is one of the few times to // ignore the length -- always insert // contentAssistRequest.getReplacementLength(), 0, 1, image, NLS.bind(XMLUIMessages.Close_with____, (new Object[]{node.getNodeName()})), null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG); contentAssistRequest.addProposal(proposal); } } // prompt with slash bracket "/>" incase if it's a self ending tag if (endWithSlashBracket) { proposal = new MarkupCompletionProposal("/>", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), // this is one of the few times to ignore // the length -- always insert // contentAssistRequest.getReplacementLength(), 0, 2, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" \"/>\""})), //$NON-NLS-1$ null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG + 1); // +1 // to bring to top of list contentAssistRequest.addProposal(proposal); } } } else if ((contentAssistRequest.getDocumentRegion() == node.getLastStructuredDocumentRegion()) && !node.getLastStructuredDocumentRegion().isEnded()) { setErrorMessage(null); // prompt with a closing end character for the end tag CustomCompletionProposal proposal = new MarkupCompletionProposal(">", //$NON-NLS-1$ contentAssistRequest.getReplacementBeginPosition(), // this is one of the few times to ignore the length -- always insert // contentAssistRequest.getReplacementLength(), 0, 1, image, NLS.bind(XMLUIMessages.Close_with__, (new Object[]{" '>'"})), //$NON-NLS-1$ null, proposedInfo, XMLRelevanceConstants.R_CLOSE_TAG); contentAssistRequest.addProposal(proposal); } } else if (node.getNodeType() == Node.DOCUMENT_NODE) { setErrorMessage(XMLUIMessages.Content_Assist_not_availab_UI_); } } protected void addTagInsertionProposals( ContentAssistRequest contentAssistRequest, int childPosition, CompletionProposalInvocationContext context) { List cmnodes = null; Node parent = contentAssistRequest.getParent(); String error = null; // (nsd) This is only valid at the document element level // only valid if it's XML (check added 2/17/2004) if ((parent != null) && (parent.getNodeType() == Node.DOCUMENT_NODE) && ((IDOMDocument) parent).isXMLType() && !isCursorAfterXMLPI(contentAssistRequest)) { return; } // only want proposals if cursor is after doctype... if (!isCursorAfterDoctype(contentAssistRequest)) { return; } // fix for meta-info comment nodes.. they currently "hide" other // proposals because the don't // have a content model (so can't propose any children..) if ((parent != null) && (parent instanceof IDOMNode) && isCommentNode((IDOMNode) parent)) { // loop and find non comment node? while ((parent != null) && isCommentNode((IDOMNode) parent)) { parent = parent.getParentNode(); } } if (parent.getNodeType() == Node.ELEMENT_NODE) { CMElementDeclaration parentDecl = getCMElementDeclaration(parent); if (parentDecl != null) { // XSD-specific ability - no filtering CMDataType childType = parentDecl.getDataType(); if (childType != null) { String[] childStrings = childType.getEnumeratedValues(); String defaultValue = childType.getImpliedValue(); if (childStrings != null || defaultValue != null) { // the content string is the sole valid child...so replace the rest int begin = contentAssistRequest.getReplacementBeginPosition(); int length = contentAssistRequest.getReplacementLength(); if (parent instanceof IDOMNode) { if (((IDOMNode) parent).getLastStructuredDocumentRegion() != ((IDOMNode) parent).getFirstStructuredDocumentRegion()) { begin = ((IDOMNode) parent).getFirstStructuredDocumentRegion().getEndOffset(); length = ((IDOMNode) parent).getLastStructuredDocumentRegion().getStartOffset() - begin; } } String proposedInfo = getAdditionalInfo(parentDecl, childType); for (int i = 0; i < childStrings.length; i++) { if(!childStrings[i].equals(defaultValue)) { CustomCompletionProposal textProposal = new MarkupCompletionProposal( childStrings[i],begin, length, childStrings[i].length(), XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENUM), childStrings[i], null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION); contentAssistRequest.addProposal(textProposal); } } if(defaultValue != null) { CustomCompletionProposal textProposal = new MarkupCompletionProposal( defaultValue, begin, length, defaultValue.length(), XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DEFAULT), defaultValue, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION); contentAssistRequest.addProposal(textProposal); } } } } if ((parentDecl != null) && (parentDecl.getContentType() == CMElementDeclaration.PCDATA)) { addPCDATAProposal(parentDecl.getNodeName(), contentAssistRequest, context); } else { // retrieve the list of all possible children within this parent context cmnodes = getAvailableChildElementDeclarations((Element) parent, childPosition,ModelQueryAction.INSERT); // retrieve the list of the possible children within this // parent context and at this index List strictCMNodeSuggestions = null; if (XMLUIPreferenceNames.SUGGESTION_STRATEGY_VALUE_STRICT.equals(XMLUIPlugin.getInstance().getPreferenceStore().getString(XMLUIPreferenceNames.SUGGESTION_STRATEGY))) { strictCMNodeSuggestions = getValidChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT); } Iterator nodeIterator = cmnodes.iterator(); if (!nodeIterator.hasNext()) { if (getCMElementDeclaration(parent) != null) { error = NLS.bind(XMLUIMessages._Has_no_available_child, (new Object[]{parent.getNodeName()})); } else { error = NLS.bind(XMLUIMessages.Element__is_unknown, (new Object[]{parent.getNodeName()})); } } String matchString = contentAssistRequest.getMatchString(); // chop off any leading <'s and whitespace from the matchstring while ((matchString.length() > 0) && (Character.isWhitespace(matchString.charAt(0)) || beginsWith(matchString, "<"))) { //$NON-NLS-1$ matchString = matchString.substring(1); } while (nodeIterator.hasNext()) { Object o = nodeIterator.next(); if (o instanceof CMElementDeclaration) { CMElementDeclaration elementDecl =(CMElementDeclaration) o; // only add proposals for the child element's that // begin with the matchstring String tagname = getRequiredName(parent, elementDecl); boolean isStrictCMNodeSuggestion = strictCMNodeSuggestions != null ? strictCMNodeSuggestions.contains(elementDecl) : false; //get the proposal image Image image = CMImageUtil.getImage(elementDecl); if (image == null) { if (strictCMNodeSuggestions != null) { image = isStrictCMNodeSuggestion ? this.getEmphasizedTagImage() : this.getDeemphasizedTagImage(); } else { image = this.getGenericTagImage(); } } if (beginsWith(tagname, matchString)) { String proposedText = getRequiredText(parent, elementDecl); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=89811 // place cursor in first empty quotes int markupAdjustment = getCursorPositionForProposedText(proposedText); String proposedInfo = getAdditionalInfo(parentDecl, elementDecl); int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_INSERTION : XMLRelevanceConstants.R_TAG_INSERTION; CustomCompletionProposal proposal = new MarkupCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), markupAdjustment, image, tagname, null, proposedInfo, relevance); contentAssistRequest.addProposal(proposal); } } } if (contentAssistRequest.getProposals().size() == 0) { if (error != null) { setErrorMessage(error); } else if ((contentAssistRequest.getMatchString() != null) && (contentAssistRequest.getMatchString().length() > 0)) { setErrorMessage(NLS.bind( XMLUIMessages.No_known_child_tag, (new Object[]{parent.getNodeName(), contentAssistRequest.getMatchString()}))); } else { setErrorMessage(NLS.bind( XMLUIMessages.__Has_no_known_child, (new Object[]{parent.getNodeName()}))); } } } } else if (parent.getNodeType() == Node.DOCUMENT_NODE) { // Can only prompt with elements if the cursor position is past // the XML processing // instruction and DOCTYPE declaration boolean xmlpiFound = false; boolean doctypeFound = false; int minimumOffset = -1; for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { boolean xmlpi = ((child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) && child.getNodeName().equals("xml")); //$NON-NLS-1$ boolean doctype = child.getNodeType() == Node.DOCUMENT_TYPE_NODE; if (xmlpi || (doctype && (minimumOffset < 0))) { minimumOffset = ((IDOMNode) child).getFirstStructuredDocumentRegion().getStartOffset() + ((IDOMNode) child).getFirstStructuredDocumentRegion().getTextLength(); } xmlpiFound = xmlpiFound || xmlpi; doctypeFound = doctypeFound || doctype; } if (contentAssistRequest.getReplacementBeginPosition() >= minimumOffset) { List childDecls = getAvailableRootChildren((Document) parent, childPosition); for (int i = 0; i < childDecls.size(); i++) { CMElementDeclaration ed = (CMElementDeclaration) childDecls.get(i); if (ed != null) { Image image = CMImageUtil.getImage(ed); if (image == null) { image = this.getGenericTagImage(); } String proposedText = getRequiredText(parent, ed); String tagname = getRequiredName(parent, ed); // account for the < and > int markupAdjustment = getContentGenerator().getMinimalStartTagLength(parent, ed); String proposedInfo = getAdditionalInfo(null, ed); CustomCompletionProposal proposal = new MarkupCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), markupAdjustment, image, tagname, null, proposedInfo, XMLRelevanceConstants.R_TAG_INSERTION); contentAssistRequest.addProposal(proposal); } } } } } protected void addTagNameProposals( ContentAssistRequest contentAssistRequest, int childPosition, CompletionProposalInvocationContext context) { List cmnodes = null; Node parent = contentAssistRequest.getParent(); IDOMNode node = (IDOMNode) contentAssistRequest.getNode(); String error = null; String matchString = contentAssistRequest.getMatchString(); if (parent.getNodeType() == Node.ELEMENT_NODE) { // retrieve the list of children // validActions = getAvailableChildrenAtIndex((Element) parent, // childPosition); cmnodes = getAvailableChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT); List strictCMNodeSuggestions = null; if (XMLUIPreferenceNames.SUGGESTION_STRATEGY_VALUE_STRICT.equals(XMLUIPlugin.getInstance().getPreferenceStore().getString(XMLUIPreferenceNames.SUGGESTION_STRATEGY))) { strictCMNodeSuggestions = getValidChildElementDeclarations((Element) parent, childPosition, ModelQueryAction.INSERT); } Iterator 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$ matchString = matchString.substring(1); } if (!nodeIterator.hasNext()) { error = NLS.bind(XMLUIMessages.__Has_no_known_child, (new Object[]{parent.getNodeName()})); } while (nodeIterator.hasNext()) { CMNode elementDecl = (CMNode) nodeIterator.next(); if (elementDecl != null) { // only add proposals for the child element's that begin with the matchstring String proposedText = null; int cursorAdjustment = 0; //determine if strict suggestion boolean isStrictCMNodeSuggestion = strictCMNodeSuggestions != null ? strictCMNodeSuggestions.contains(elementDecl) : false; // do a check to see if partial attributes of partial tag names are in list if ((contentAssistRequest.documentRegion.getStartOffset() < context.getInvocationOffset()) && (((node != null) && (node.getAttributes() != null) && (node.getAttributes().getLength() > 0) && attributeInList(node, parent, elementDecl)) || ((node.getNodeType() != Node.TEXT_NODE) && node.getFirstStructuredDocumentRegion().isEnded()))) { proposedText = getRequiredName(parent, elementDecl); cursorAdjustment = proposedText.length(); } else { proposedText = getRequiredName(parent, elementDecl); cursorAdjustment = proposedText.length(); if (elementDecl instanceof CMElementDeclaration) { CMElementDeclaration ed = (CMElementDeclaration) elementDecl; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=89811 StringBuffer sb = new StringBuffer(); getContentGenerator().generateTag(parent, 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)) { //get the proposal image Image image = CMImageUtil.getImage(elementDecl); if (image == null) { if (strictCMNodeSuggestions != null) { image = isStrictCMNodeSuggestion ? this.getEmphasizedTagImage() : this.getDeemphasizedTagImage(); } else { image = this.getGenericTagImage(); } } int relevance = isStrictCMNodeSuggestion ? XMLRelevanceConstants.R_STRICTLY_VALID_TAG_NAME : XMLRelevanceConstants.R_TAG_NAME; String proposedInfo = getAdditionalInfo(getCMElementDeclaration(parent), elementDecl); CustomCompletionProposal proposal = new MarkupCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), cursorAdjustment, image, getRequiredName(parent, elementDecl), null, proposedInfo, relevance); contentAssistRequest.addProposal(proposal); } } } if (contentAssistRequest.getProposals().size() == 0) { if (error != null) { setErrorMessage(error); } else if ((contentAssistRequest.getMatchString() != null) && (contentAssistRequest.getMatchString().length() > 0)) { setErrorMessage(NLS.bind( XMLUIMessages.No_known_child_tag_names, (new Object[]{parent.getNodeName(), contentAssistRequest.getMatchString()}))); } else { setErrorMessage(NLS.bind( XMLUIMessages.__Has_no_known_child, (new Object[]{parent.getNodeName()}))); } } } else if (parent.getNodeType() == Node.DOCUMENT_NODE) { List childElements = getAvailableRootChildren((Document) parent, childPosition); if ( childElements.size() == 0) { //No doctype available , treat it as empty document addEmptyDocumentProposals(contentAssistRequest, context); } for (int i = 0; i < childElements.size(); i++) { CMNode ed = (CMNode) childElements.get(i); if (ed == null) { continue; } String proposedText = null; int cursorAdjustment = 0; if (ed instanceof CMElementDeclaration) { // proposedText = getRequiredName(parent, ed); StringBuffer sb = new StringBuffer(); getContentGenerator().generateTag(parent, (CMElementDeclaration) ed, sb); // tag starts w/ '<', but we want to compare to name proposedText = sb.toString().substring(1); if (!beginsWith(proposedText, matchString)) { continue; } cursorAdjustment = getCursorPositionForProposedText(proposedText); String proposedInfo = getAdditionalInfo(null, ed); Image image = CMImageUtil.getImage(ed); if (image == null) { image = this.getGenericTagImage(); } CustomCompletionProposal proposal = new MarkupCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), cursorAdjustment, image, getRequiredName(parent, ed), null, proposedInfo, XMLRelevanceConstants.R_TAG_NAME); contentAssistRequest.addProposal(proposal); } } } } protected void addEmptyDocumentProposals( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { //by default do nothing } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for generic tag proposals */ protected Image getGenericTagImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC); } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for emphasized tag proposals */ protected Image getEmphasizedTagImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC_EMPHASIZED); } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for de-emphasized tag proposals */ protected Image getDeemphasizedTagImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_TAG_GENERIC_DEEMPHASIZED); } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for entity reference proposals */ protected Image getEntityReferenceImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ENTITY_REFERENCE); } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for not required attributes */ protected Image getNotRequiredAttributeImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ATTRIBUTE); } /** * <p>Implementers are allowed to override</p> * * @return the proposal image to display for required attributes */ protected Image getRequiredAttributeImage() { return XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_ATT_REQ_OBJ); } /** * This method can check if the cursor is after the XMLPI * * @param car */ protected boolean isCursorAfterXMLPI(ContentAssistRequest car) { Node aNode = car.getNode(); boolean xmlpiFound = false; Document parent = aNode.getOwnerDocument(); int xmlpiNodePosition = -1; boolean isAfterXMLPI = false; if (parent == null) { return true; // blank document case } for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { boolean xmlpi = ((child.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) && child.getNodeName().equals("xml")); //$NON-NLS-1$ xmlpiFound = xmlpiFound || xmlpi; if (xmlpiFound) { if (child instanceof IDOMNode) { xmlpiNodePosition = ((IDOMNode) child).getEndOffset(); isAfterXMLPI = (car.getReplacementBeginPosition() >= xmlpiNodePosition); } break; } } return isAfterXMLPI; } protected String getRequiredName(Node parentOrOwner, CMNode cmnode) { if ((cmnode == null) || (parentOrOwner == null)) { if (Debug.displayWarnings) { new IllegalArgumentException("Null declaration!").printStackTrace(); //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } return getContentGenerator().getRequiredName(parentOrOwner, cmnode); } private String getRequiredText(Node parentOrOwner, CMAttributeDeclaration attrDecl) { if (attrDecl == null) { if (Debug.displayWarnings) { new IllegalArgumentException("Null attribute declaration!").printStackTrace(); //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } StringBuffer buff = new StringBuffer(); getContentGenerator().generateRequiredAttribute(parentOrOwner, attrDecl, buff); return buff.toString(); } protected String getRequiredText(Node parentOrOwner, CMElementDeclaration elementDecl) { if (elementDecl == null) { if (Debug.displayWarnings) { new IllegalArgumentException("Null attribute declaration!").printStackTrace(); //$NON-NLS-1$ } return ""; //$NON-NLS-1$ } StringBuffer buff = new StringBuffer(); getContentGenerator().generateTag(parentOrOwner, elementDecl, buff); return buff.toString(); } /** * Retrieves all of the possible valid values for this attribute * declaration */ private List getPossibleDataTypeValues(Node node, CMAttributeDeclaration ad) { List list = null; if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; String[] dataTypeValues = null; // The ModelQuery may not be available if the corresponding // adapter // is absent ModelQuery modelQuery = ModelQueryUtil.getModelQuery(element.getOwnerDocument()); if (modelQuery != null) { dataTypeValues = modelQuery.getPossibleDataTypeValues(element, ad); } else { if (ad.getAttrType() != null) { dataTypeValues = ad.getAttrType().getEnumeratedValues(); } } if (dataTypeValues != null) { list = new ArrayList(dataTypeValues.length); for (int i = 0; i < dataTypeValues.length; i++) { list.add(dataTypeValues[i]); } } } if (list == null) { list = new ArrayList(0); } return list; } /** * This is to determine if a tag is a special meta-info comment tag that * shows up as an ELEMENT * * @param node * @return */ private boolean isCommentNode(IDOMNode node) { return ((node != null) && (node instanceof IDOMElement) && ((IDOMElement) node).isCommentTag()); } private boolean isStartTag(IStructuredDocumentRegion sdRegion) { boolean result = false; if (sdRegion.getRegions().size() > 0) { ITextRegion r = sdRegion.getRegions().get(0); result = (r.getType() == DOMRegionContext.XML_TAG_OPEN) && sdRegion.isEnded(); } return result; } /** * Gets the corresponding XMLNode, and checks if it's closed. * * @param startTag * */ private boolean needsEndTag(IStructuredDocumentRegion startTag, CompletionProposalInvocationContext context) { boolean result = false; IStructuredModel sModel = StructuredModelManager.getModelManager().getExistingModelForRead(context.getDocument()); try { if (sModel != null) { IDOMNode xmlNode = (IDOMNode) sModel.getIndexedRegion(startTag.getStart()); if (!isStartTag(startTag)) { result = false; } else if (isSelfClosed(startTag)) { result = false; } else if (!xmlNode.isContainer()) { result = false; } else { result = xmlNode.getEndStructuredDocumentRegion() == null; } } } finally { if (sModel != null) { sModel.releaseFromRead(); } } return result; } private boolean isSelfClosed(IStructuredDocumentRegion startTag) { ITextRegionList regions = startTag.getRegions(); return regions.get(regions.size() - 1).getType() == DOMRegionContext.XML_EMPTY_TAG_CLOSE; } private String getTagName(IStructuredDocumentRegion sdRegion) { ITextRegionList regions = sdRegion.getRegions(); ITextRegion region = null; String name = ""; //$NON-NLS-1$ for (int i = 0; i < regions.size(); i++) { region = regions.get(i); if (region.getType() == DOMRegionContext.XML_TAG_NAME) { name = sdRegion.getText(region); break; } } return name; } /** * return all possible EntityReferenceProposals (according to current * position in doc) */ private ICompletionProposal[] computeEntityReferenceProposals(ITextRegion completionRegion, IDOMNode treeNode,CompletionProposalInvocationContext context) { // only handle XML content for now int documentPosition = context.getInvocationOffset(); Vector proposals = new Vector(); // ICompletionProposals IStructuredDocumentRegion sdRegion = ContentAssistUtils.getStructuredDocumentRegion(context.getViewer(), context.getInvocationOffset()); if ((completionRegion != null) && (completionRegion.getType() == DOMRegionContext.XML_CONTENT)) { int nodeOffset = documentPosition - sdRegion.getStartOffset(completionRegion); String regionText = sdRegion.getFullText(completionRegion); // if directly to the right of a &, region will be null, need to // move to // the previous region...there might be a better way to do this if ((regionText != null) && regionText.trim().equals("") && (documentPosition > 0)) { //$NON-NLS-1$ IStructuredDocumentRegion prev = treeNode.getStructuredDocument().getRegionAtCharacterOffset(documentPosition - 1); if ((prev != null) && prev.getText().equals("&")) { //$NON-NLS-1$ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=206680 // examine previous region sdRegion = prev; completionRegion = prev.getLastRegion(); regionText = prev.getFullText(); nodeOffset = 1; } } // string must start w/ & if ((regionText != null) && regionText.startsWith("&")) { //$NON-NLS-1$ String key = (nodeOffset > 0) ? regionText.substring(1, nodeOffset) : ""; //$NON-NLS-1$ // get entity proposals, passing in the appropriate start // string ModelQuery mq = ModelQueryUtil.getModelQuery(((Node) treeNode).getOwnerDocument()); if (mq != null) { CMDocument xmlDoc = mq.getCorrespondingCMDocument(treeNode); CMNamedNodeMap cmmap = null; Properties entities = null; if (xmlDoc != null) { cmmap = xmlDoc.getEntities(); } if (cmmap != null) { entities = mapToProperties(cmmap); } else // 224787 in absence of content model, just use // minimal 5 entities { entities = new Properties(); entities.put("quot", "\""); //$NON-NLS-1$ //$NON-NLS-2$ entities.put("apos", "'"); //$NON-NLS-1$ //$NON-NLS-2$ entities.put("amp", "&"); //$NON-NLS-1$ //$NON-NLS-2$ entities.put("lt", "<"); //$NON-NLS-1$ //$NON-NLS-2$ entities.put("gt", ">"); //$NON-NLS-1$ //$NON-NLS-2$ entities.put("nbsp", " "); //$NON-NLS-1$ //$NON-NLS-2$ } addEntityProposals(proposals, entities, key, nodeOffset, sdRegion, completionRegion, context); } } } return (ICompletionProposal[]) ((proposals.size() > 0) ? proposals.toArray(new ICompletionProposal[proposals.size()]) : null); } /** * Similar to the call in HTMLContentAssistProcessor. Pass in a node, it * tells you if the document is XML type. * * @param node * */ private boolean isXMLNode(Node node) { if (node == null) { return false; } Document doc = null; doc = (node.getNodeType() != Node.DOCUMENT_NODE) ? node.getOwnerDocument() : ((Document) node); return (doc instanceof IDOMDocument) && ((IDOMDocument) doc).isXMLType(); } /** * Checks if cursor position is after doctype tag... * * @param car * */ private boolean isCursorAfterDoctype(ContentAssistRequest car) { Node aNode = car.getNode(); Document parent = aNode.getOwnerDocument(); int xmldoctypeNodePosition = -1; boolean isAfterDoctype = true; if (parent == null) { return true; // blank document case } for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { if (child instanceof IDOMNode) { if (child.getNodeType() == Node.DOCUMENT_TYPE_NODE) { xmldoctypeNodePosition = ((IDOMNode) child).getEndOffset(); isAfterDoctype = (car.getReplacementBeginPosition() >= xmldoctypeNodePosition); break; } } } return isAfterDoctype; } /** * returns a list of CMElementDeclarations * * @param document * @param childIndex * @return */ private List getAvailableRootChildren(Document document, int childIndex) { List list = null; // extract the valid 'root' node name from the DocumentType Node DocumentType docType = document.getDoctype(); String rootName = null; if (docType != null) { rootName = docType.getNodeName(); } if (rootName == null) { return new ArrayList(0); } for (Node child = document.getFirstChild(); child != null; child = child.getNextSibling()) { // make sure the "root" Element isn't already present // is it required to be an Element? if ((child.getNodeType() == Node.ELEMENT_NODE) && child.getNodeName().equalsIgnoreCase(rootName)) { // if the node is missing either the start or end tag, don't // count it as present if ((child instanceof IDOMNode) && ((((IDOMNode) child).getStartStructuredDocumentRegion() == null) || (((IDOMNode) child).getEndStructuredDocumentRegion() == null))) { continue; } if (Debug.displayInfo) { System.out.println(rootName + " already present!"); //$NON-NLS-1$ } setErrorMessage(NLS.bind(XMLUIMessages.The_document_element__, (new Object[]{rootName}))); return new ArrayList(0); } } list = new ArrayList(1); ModelQuery modelQuery = ModelQueryUtil.getModelQuery(document); if (modelQuery != null) { CMDocument cmdoc = modelQuery.getCorrespondingCMDocument(document); if (cmdoc != null) { if (rootName != null) { CMElementDeclaration rootDecl = (CMElementDeclaration) cmdoc.getElements().getNamedItem(rootName); if (rootDecl != null) { list.add(rootDecl); } else { // supply the given document name anyway, even if it // is an error list.add(new SimpleCMElementDeclaration(rootName)); String location = "" + (docType.getPublicId() != null ? docType.getPublicId() + "/" : "") + (docType.getSystemId() != null ? docType.getSystemId() : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ if (location.length() > 0) { setErrorMessage(NLS.bind( XMLUIMessages.No_definition_for_in, (new Object[]{rootName, location}))); } else { setErrorMessage(NLS.bind( XMLUIMessages.No_definition_for, (new Object[]{rootName}))); } } } } else { String location = "" + (docType.getPublicId() != null ? docType.getPublicId() + "/" : "") + (docType.getSystemId() != null ? docType.getSystemId() : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ if (location.length() > 0) { setErrorMessage(NLS.bind( XMLUIMessages.No_content_model_for, (new Object[]{location}))); } else { setErrorMessage(XMLUIMessages.No_content_model_found_UI_); } } } return list; } /** * This method determines if any of the attributes in the proposed XMLNode * node, are possible values of attributes from possible Elements at this * point in the document according to the Content Model. * * @param node * the element with attributes that you would like to test if * are possible for possible Elements at this point * @param cmnode * possible element at this point in the document (depending on * what 'node' is) true if any attributes of 'node' match any * possible attributes from 'cmnodes' list. */ private boolean attributeInList(IDOMNode node, Node parent, CMNode cmnode) { if ((node == null) || (parent == null) || (cmnode == null)) { return false; } String elementMatchString = node.getNodeName(); String cmnodeName = getRequiredName(parent, cmnode);// cmnode.getNodeName(); if (node instanceof Element) { NamedNodeMap map = ((Element) node).getAttributes(); String attrMatchString = ""; //$NON-NLS-1$ // iterate attribute possibilities for partially started node for (int i = 0; (map != null) && (i < map.getLength()); i++) { attrMatchString = map.item(i).getNodeName(); // filter on whatever user typed for element name already if (beginsWith(cmnodeName, elementMatchString)) { if (cmnode.getNodeType() == CMNode.ELEMENT_DECLARATION) { CMNamedNodeMapImpl attributes = new CMNamedNodeMapImpl(((CMElementDeclaration) cmnode).getAttributes()); this.addModelQueryAttributeDeclarations( node,((CMElementDeclaration) cmnode),attributes); // iterate possible attributes from a cmnode in // proposal list for (int k = 0; (attributes != null) && (k < attributes.getLength()); k++) { // check if name matches if (attributes.item(k).getNodeName().equals(attrMatchString)) { return true; } } } } } } return false; } private Properties mapToProperties(CMNamedNodeMap map) { Properties p = new Properties(); for (int i = 0; i < map.getLength(); i++) { CMEntityDeclaration decl = (CMEntityDeclaration) map.item(i); p.put(decl.getName(), decl.getValue()); } return p; } /** * <p>Adds model query attribute declaration proposals</p> * * @param node * @param elementDecl * @param allAttributes */ private void addModelQueryAttributeDeclarations(IDOMNode node, CMElementDeclaration elementDecl, CMNamedNodeMapImpl allAttributes) { if (node.getNodeType() == Node.ELEMENT_NODE) { List nodes = ModelQueryUtil.getModelQuery(node.getOwnerDocument()).getAvailableContent((Element) node, elementDecl, ModelQuery.INCLUDE_ATTRIBUTES); nodes = filterAvailableModelQueryCMNodes(nodes); for (int k = 0; k < nodes.size(); k++) { CMNode cmnode = (CMNode) nodes.get(k); if (cmnode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) { allAttributes.put(cmnode); } } } } /** * 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} * * TODO cs... do we need to pass in the 'kindOfAction'? Seems to me we * could assume it's always an insert. * * @param parent * @param childPosition * @param kindOfAction * @return */ private List getAvailableChildElementDeclarations(Element parent, int childPosition, int kindOfAction) { List modelQueryActions = getAvailableModelQueryActionsAtIndex(parent, childPosition, ModelQuery.VALIDITY_NONE); Iterator iterator = modelQueryActions.iterator(); List cmnodes = new Vector(); while (iterator.hasNext()) { ModelQueryAction action = (ModelQueryAction) iterator.next(); if ((childPosition < 0) || (((action.getStartIndex() <= childPosition) && (childPosition <= action.getEndIndex())) && (action.getKind() == kindOfAction))) { CMNode actionCMNode = action.getCMNode(); if ((actionCMNode != null) && !cmnodes.contains(actionCMNode)) { cmnodes.add(actionCMNode); } } } return cmnodes; } /** * returns a list of CMNodes that can be validly inserted at this * childPosition * Given the grammar shown below and a snippet of XML code (where the '|' * indicated the cursor position) * the list would return only the element declarations can be inserted * while maintaing validity of the content. * * grammar : Foo -> (A, B, C) * snippet : <Foo><A>| * result : {B} * * @param parent * @param childPosition * @param kindOfAction * @return */ private List getValidChildElementDeclarations(Element parent, int childPosition, int kindOfAction) { List modelQueryActions = getAvailableModelQueryActionsAtIndex(parent, childPosition, ModelQuery.VALIDITY_STRICT); Iterator iterator = modelQueryActions.iterator(); List cmnodes = new Vector(); while (iterator.hasNext()) { ModelQueryAction action = (ModelQueryAction) iterator.next(); if ((childPosition < 0) || (((action.getStartIndex() <= childPosition) && (childPosition <= action.getEndIndex())) && (action.getKind() == kindOfAction))) { CMNode actionCMNode = action.getCMNode(); if ((actionCMNode != null) && !cmnodes.contains(actionCMNode)) { cmnodes.add(actionCMNode); } } } return cmnodes; } /** * returns a list of ModelQueryActions * * @param parent * @param index * @param validityChecking * @return */ private List getAvailableModelQueryActionsAtIndex(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); } list = filterAvailableModelQueryActions(list); return list; } /** * <p>Filters out any model query actions that are not valid for this * implementation of the model query computer based on the {@link CMNode} * of the action.</p> * * @param modelQueryActions * @return the filtered list of {@link ModelQueryAction}s */ private List filterAvailableModelQueryActions(List modelQueryActions) { List filtered = new ArrayList(modelQueryActions.size()); Iterator iterator = modelQueryActions.iterator(); while (iterator.hasNext()) { ModelQueryAction action = (ModelQueryAction) iterator.next(); if(validModelQueryNode(action.getCMNode())) { filtered.add(action); } } return filtered; } /** * <p>Filters out any model query {@link CMNode}s that are not valid for this * implementation of the model query computer</p> * * @param modelQueryNodes * @return the filtered list of {@link CMNode}s */ private List filterAvailableModelQueryCMNodes(List modelQueryNodes) { List filtered = new ArrayList(modelQueryNodes.size()); Iterator iterator = modelQueryNodes.iterator(); while (iterator.hasNext()) { CMNode node = (CMNode) iterator.next(); if(validModelQueryNode(node)) { filtered.add(node); } } return filtered; } /** * <p>Adds a generic doc type proposal</p> * * @param contentAssistRequest * @param context */ private void addDocTypeProposal( ContentAssistRequest contentAssistRequest, CompletionProposalInvocationContext context) { // if a DocumentElement exists, use that for the root Element name String rootname = "unspecified"; //$NON-NLS-1$ if (contentAssistRequest.getNode().getOwnerDocument().getDocumentElement() != null) { rootname = contentAssistRequest.getNode().getOwnerDocument().getDocumentElement().getNodeName(); } String proposedText = "<!DOCTYPE " + rootname + " PUBLIC \"//UNKNOWN/\" \"unknown.dtd\">"; //$NON-NLS-1$ //$NON-NLS-2$ ICompletionProposal proposal = new CustomCompletionProposal( proposedText, contentAssistRequest.getReplacementBeginPosition(), contentAssistRequest.getReplacementLength(), 10, XMLEditorPluginImageHelper.getInstance().getImage(XMLEditorPluginImages.IMG_OBJ_DOCTYPE), "<!DOCTYPE ... >", //$NON-NLS-1$ null, null, XMLRelevanceConstants.R_DOCTYPE); // TODO provide special documentation on doc type contentAssistRequest.addProposal(proposal); } public static 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; } /** * 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. */ public static 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 = infoProvider.getInfo(cmnode); if ((addlInfo == null) && (parentOrOwner != null)) { addlInfo = infoProvider.getInfo(parentOrOwner); } return addlInfo; } }