/******************************************************************************* * Copyright (c) 2001, 2006 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.ui.internal.contentassist; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.osgi.util.NLS; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; 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.ITextRegionContainer; import org.eclipse.wst.sse.core.internal.util.ScriptLanguageKeys; 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.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.editor.XMLEditorPluginImageHelper; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * @author pavery */ public class XMLContentAssistUtilities extends ContentAssistUtils { /** * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ private class DOMJSPRegionContextsPrivateCopy { private static final String JSP_CLOSE = "JSP_CLOSE"; //$NON-NLS-1$ private static final String JSP_DECLARATION_OPEN = "JSP_DECLARATION_OPEN"; //$NON-NLS-1$ private static final String JSP_SCRIPTLET_OPEN = "JSP_SCRIPTLET_OPEN"; //$NON-NLS-1$ private static final String JSP_EXPRESSION_OPEN = "JSP_EXPRESSION_OPEN"; //$NON-NLS-1$ } public static final String CONTENT = "Content"; //$NON-NLS-1$ public static final String CONTENT_SCRIPT_TYPE = "Content-Script-Type"; //$NON-NLS-1$ public static final String HEAD = "HEAD"; //$NON-NLS-1$ public static final String HTML = "HTML"; //$NON-NLS-1$ public static final String HTTP_EQUIV = "HTTP-EQUIV"; //$NON-NLS-1$ public static final String META = "META"; //$NON-NLS-1$ /** * A convenience method for getting the closing proposal given the * contents (IndexedRegion) of a tag that is started, but possibly not * ended * * @param viewer * the text viewer * @param documentPosition * the cursor position in the viewer * @param indexedNode * the contents of the tag that is started but possibly not * ended * @param parentTagName * the tag on which you are checkin for an ending tag * @param imagePath * content assist image relative path * @return ICompletionProposal */ public static ICompletionProposal computeJSPEndTagProposal(ITextViewer viewer, int documentPosition, IndexedRegion indexedNode, String parentTagName, String imagePath) { ICompletionProposal p = null; // check if tag is closed boolean hasEndTag = true; boolean isJSPTag = false; IDOMNode xnode = null; String tagName = ""; //$NON-NLS-1$ if (indexedNode instanceof IDOMNode) { xnode = ((IDOMNode) indexedNode); // it's ended already... if (xnode.getEndStructuredDocumentRegion() != null) { return null; } IDOMNode openNode = null; if (!xnode.getNodeName().equalsIgnoreCase(parentTagName)) { openNode = (IDOMNode) xnode.getParentNode(); } if (openNode != null) { if (openNode instanceof IDOMElement) { isJSPTag = ((IDOMElement) openNode).isJSPTag(); } tagName = openNode.getNodeName(); hasEndTag = (openNode.getEndStructuredDocumentRegion() != null); } } // it's closed, don't add close tag proposal if (!hasEndTag && !isJSPTag) { // create appropriate close tag text String proposedText = "</" + tagName; //$NON-NLS-1$ String viewerText = viewer.getTextWidget().getText(); if ((viewerText.length() >= documentPosition) && (viewerText.length() >= 2) && (documentPosition >= 2)) { String last2chars = viewerText.substring(documentPosition - 2, documentPosition); if (last2chars.endsWith("</")) { proposedText = tagName; } else if (last2chars.endsWith("<")) { proposedText = "/" + tagName; //$NON-NLS-1$ } } // create proposal p = new CustomCompletionProposal(proposedText + ">", //$NON-NLS-1$ documentPosition, 0, proposedText.length() + 1, XMLEditorPluginImageHelper.getInstance().getImage(imagePath), NLS.bind(XMLUIMessages.End_with_, (new Object[]{proposedText})), null, null, XMLRelevanceConstants.R_END_TAG); } else if (!hasEndTag && isJSPTag) { // create appropriate close tag text String proposedText = "%"; //$NON-NLS-1$ String viewerText = viewer.getTextWidget().getText(); // TODO (pa) make it smarter to add "%>" or just ">" if % is // already there... if ((viewerText.length() >= documentPosition) && (viewerText.length() >= 2)) { String last2chars = viewerText.substring(documentPosition - 2, documentPosition); String lastchar = viewerText.substring(documentPosition - 1, documentPosition); if (lastchar.equals("%")) //$NON-NLS-1$ { if (last2chars.endsWith("<%")) { proposedText = "%"; //$NON-NLS-1$ } else { proposedText = ""; //$NON-NLS-1$ } } } // create proposal p = new CustomCompletionProposal(proposedText + ">", //$NON-NLS-1$ documentPosition, 0, proposedText.length() + 1, XMLEditorPluginImageHelper.getInstance().getImage(imagePath), NLS.bind(XMLUIMessages.End_with_, (new Object[]{proposedText})), null, null, XMLRelevanceConstants.R_END_TAG); } return p; } /** * A convenience method for getting the closing proposal given the * contents (IndexedRegion) of a tag that is started, but possibly not * ended * * @param viewer * the text viewer * @param documentPosition * the cursor position in the viewer * @param indexedNode * the contents of the tag that is started but possibly not * ended * @param parentTagName * the tag on which you are checkin for an ending tag * @param imagePath * content assist relative image path * @return ICompletionProposal */ public static ICompletionProposal computeXMLEndTagProposal(ITextViewer viewer, int documentPosition, IndexedRegion indexedNode, String parentTagName, String imagePath) { ICompletionProposal p = null; // check if tag is closed boolean hasEndTag = true; IDOMNode xnode = null; String tagName = ""; //$NON-NLS-1$ if (indexedNode instanceof IDOMNode) { xnode = ((IDOMNode) indexedNode); // it's ended already... if (xnode.getEndStructuredDocumentRegion() != null) { return null; } IDOMNode styleNode = null; if (!xnode.getNodeName().equalsIgnoreCase(parentTagName)) { styleNode = (IDOMNode) xnode.getParentNode(); } if (styleNode != null) { tagName = styleNode.getNodeName(); hasEndTag = (styleNode.getEndStructuredDocumentRegion() != null); } } // it's closed, don't add close tag proposal if (!hasEndTag) { // create appropriate close tag text String proposedText = "</" + tagName; //$NON-NLS-1$ String viewerText = viewer.getTextWidget().getText(); if ((viewerText.length() >= documentPosition) && (viewerText.length() >= 2) && (documentPosition >= 2)) { String last2chars = viewerText.substring(documentPosition - 2, documentPosition); if (last2chars.endsWith("</")) { proposedText = tagName; } else if (last2chars.endsWith("<")) { proposedText = "/" + tagName; //$NON-NLS-1$ } } // create proposal p = new CustomCompletionProposal(proposedText + ">", //$NON-NLS-1$ documentPosition, 0, proposedText.length() + 1, XMLEditorPluginImageHelper.getInstance().getImage(imagePath), NLS.bind(XMLUIMessages.End_with_, (new Object[]{proposedText})), null, null, XMLRelevanceConstants.R_END_TAG); } return p; } private static String getMetaScriptType(Document doc) { // Can not just do a Document.getElementsByTagName(String) as this // needs // to be relatively fast. List metas = new ArrayList(); // check for META tags under the Document Node html = null; Node head = null; Node child = null; // ---------------------------------------------------------------------- // (pa) 20021217 // cmvc defect 235554 // performance enhancement: using child.getNextSibling() rather than // nodeList(item) for O(n) vs. O(n*n) // ---------------------------------------------------------------------- for (child = doc.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } if (child.getNodeName().equalsIgnoreCase(META)) { metas.add(child); } else if (child.getNodeName().equalsIgnoreCase(HTML)) { html = child; } } // NodeList children = doc.getChildNodes(); // for(int i = 0; i < children.getLength(); i++) { // child = children.item(i); // if(child.getNodeType() != Node.ELEMENT_NODE) // continue; // if(child.getNodeName().equalsIgnoreCase(META)) // metas.add(child); // else if(child.getNodeName().equalsIgnoreCase(HTML)) // html = child; // } // check for META tags under HEAD if (html != null) { for (child = html.getFirstChild(); (child != null) && (head == null); child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } if (child.getNodeName().equalsIgnoreCase(HEAD)) { head = child; } } // children = html.getChildNodes(); // for(int i = 0; i < children.getLength() && head == null; i++) { // child = children.item(i); // if(child.getNodeType() != Node.ELEMENT_NODE) // continue; // if(child.getNodeName().equalsIgnoreCase(HEAD)) // head = child; // } } if (head != null) { for (head.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } if (child.getNodeName().equalsIgnoreCase(META)) { metas.add(child); } } // children = head.getChildNodes(); // for(int i = 0 ; i < children.getLength(); i++) { // child = children.item(i); // if(child.getNodeType() != Node.ELEMENT_NODE) // continue; // if(child.getNodeName().equalsIgnoreCase(META)) // metas.add(child); // } } return getMetaScriptType(metas); } private static String getMetaScriptType(List metaNodeList) { Node meta = null; NamedNodeMap attributes = null; boolean httpEquiv = false; String contentScriptType = null; for (int i = metaNodeList.size() - 1; i >= 0; i--) { meta = (Node) metaNodeList.get(i); attributes = meta.getAttributes(); httpEquiv = false; contentScriptType = null; for (int j = 0; j < attributes.getLength(); j++) { if (attributes.item(j).getNodeName().equalsIgnoreCase(HTTP_EQUIV)) { httpEquiv = attributes.item(j).getNodeValue().equalsIgnoreCase(CONTENT_SCRIPT_TYPE); } else if (attributes.item(j).getNodeName().equalsIgnoreCase(CONTENT)) { contentScriptType = attributes.item(j).getNodeValue(); } } if (httpEquiv && (contentScriptType != null)) { return contentScriptType; } } return null; } /** * Returns the scripting language the scriptNode is in Currently returns * javascript unless some unknown type or language is specified. Then the * unknown type/language is returned * * @param scriptNode */ public static String getScriptLanguage(Node scriptNode) { Node attr = null; boolean specified = false; // try to find a scripting adapter for 'type' if ((scriptNode == null) || (scriptNode.getAttributes() == null)) { return null; } attr = scriptNode.getAttributes().getNamedItem("type");//$NON-NLS-1$ if (attr != null) { specified = true; String type = attr.getNodeValue(); return lookupScriptType(type); } // now try to find a scripting adapter for 'language' (deprecated by // HTML specifications) attr = scriptNode.getAttributes().getNamedItem("language");//$NON-NLS-1$ if (attr != null) { specified = true; String language = attr.getNodeValue(); return lookupScriptLanguage(language); } // check if one is specified by a META tag at the root level or inside // of HEAD String type = null; if (!specified) { type = getMetaScriptType(scriptNode.getOwnerDocument()); } if (type != null) { specified = true; return lookupScriptType(type); } // return default if (!specified) { return ScriptLanguageKeys.JAVASCRIPT; } return null; } /** * Tells you if the flatnode is the %> delimiter * * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isJSPCloseDelimiter(IStructuredDocumentRegion fn) { if (fn == null) { return false; } return isJSPCloseDelimiter(fn.getType()); } /** * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isJSPCloseDelimiter(String type) { if (type == null) { return false; } return (type.equals(DOMJSPRegionContextsPrivateCopy.JSP_CLOSE) || type.equals(DOMRegionContext.XML_TAG_CLOSE)); } /** * Tells you if the flatnode is the JSP region <%%>, <%=%>, <%!%> * * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isJSPDelimiter(IStructuredDocumentRegion fn) { boolean isDelimiter = false; String type = fn.getType(); if (type != null) { isDelimiter = isJSPDelimiter(type); } return isDelimiter; } /** * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isJSPDelimiter(String type) { if (type == null) { return false; } return (isJSPOpenDelimiter(type) || isJSPCloseDelimiter(type)); } /** * Tells you if the flatnode is <%, <%=, or <%! ISSUE: this is a bit of * hidden JSP knowledge that was implemented this way for expedency. * Should be evolved in future to depend on "nestedContext". */ public static boolean isJSPOpenDelimiter(IStructuredDocumentRegion fn) { if (fn == null) { return false; } return isJSPOpenDelimiter(fn.getType()); } /** * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isJSPOpenDelimiter(String type) { if (type == null) { return false; } return (type.equals(DOMJSPRegionContextsPrivateCopy.JSP_SCRIPTLET_OPEN) || type.equals(DOMJSPRegionContextsPrivateCopy.JSP_DECLARATION_OPEN) || type.equals(DOMJSPRegionContextsPrivateCopy.JSP_EXPRESSION_OPEN)); } /** * Tells you if the flatnode is the <jsp:scriptlet>, <jsp:expression>, or * <jsp:declaration>tag * * ISSUE: this is a bit of hidden JSP knowledge that was implemented this * way for expedency. Should be evolved in future to depend on * "nestedContext". */ public static boolean isXMLJSPDelimiter(IStructuredDocumentRegion fn) { boolean isDelimiter = false; if ((fn != null) && (fn instanceof ITextRegionContainer)) { Object[] regions = ((ITextRegionContainer) fn).getRegions().toArray(); ITextRegion temp = null; String regionText = ""; //$NON-NLS-1$ for (int i = 0; i < regions.length; i++) { temp = (ITextRegion) regions[i]; if (temp.getType() == DOMRegionContext.XML_TAG_NAME) { regionText = fn.getText(temp); if (regionText.equalsIgnoreCase("jsp:scriptlet") || regionText.equalsIgnoreCase("jsp:expression") || regionText.equalsIgnoreCase("jsp:declaration")) { isDelimiter = true; } } } } return isDelimiter; } /** * Returns "javascript" if language attribute is some form of javascript, * "java" if language attribute is some form of java. Otherwise, just * returns type. * * @param language */ public static String lookupScriptLanguage(String language) { for (int i = 0; i < ScriptLanguageKeys.JAVASCRIPT_LANGUAGE_KEYS.length; i++) { if (ScriptLanguageKeys.JAVASCRIPT_LANGUAGE_KEYS[i].equalsIgnoreCase(language)) { return ScriptLanguageKeys.JAVASCRIPT; } } for (int i = 0; i < ScriptLanguageKeys.JAVA_LANGUAGE_KEYS.length; i++) { if (ScriptLanguageKeys.JAVA_LANGUAGE_KEYS[i].equalsIgnoreCase(language)) { return ScriptLanguageKeys.JAVA; } } return language; } /** * Returns "javascript" if type (used in <script type="xxx"> is actually * javascript type. Otherwise, just returns type * * @param type */ public static String lookupScriptType(String type) { for (int i = 0; i < ScriptLanguageKeys.JAVASCRIPT_MIME_TYPE_KEYS.length; i++) { if (ScriptLanguageKeys.JAVASCRIPT_MIME_TYPE_KEYS[i].equalsIgnoreCase(type)) { return ScriptLanguageKeys.JAVASCRIPT; } } return type; } }