namespace vs location
[fedora-idea.git] / xml / impl / src / com / intellij / xml / DefaultXmlExtension.java
blob6ff7f6e008164e3adde3bbc05338455542fc7f4e
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.xml;
18 import com.intellij.openapi.editor.Editor;
19 import com.intellij.openapi.module.ModuleUtil;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.psi.PsiElement;
25 import com.intellij.psi.PsiFile;
26 import com.intellij.psi.XmlElementFactory;
27 import com.intellij.psi.codeStyle.CodeStyleManager;
28 import com.intellij.psi.impl.source.xml.TagNameReference;
29 import com.intellij.psi.xml.*;
30 import com.intellij.util.IncorrectOperationException;
31 import com.intellij.xml.index.XmlNamespaceIndex;
32 import com.intellij.xml.index.XmlTagNamesIndex;
33 import com.intellij.xml.util.XmlUtil;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
38 import java.util.*;
40 /**
41 * @author Dmitry Avdeev
43 public class DefaultXmlExtension extends XmlExtension {
45 public boolean isAvailable(final PsiFile file) {
46 return true;
49 @NotNull
50 public List<Pair<String,String>> getAvailableTagNames(@NotNull final XmlFile file, @NotNull final XmlTag context) {
52 final Set<String> namespaces = new HashSet<String>(Arrays.asList(context.knownNamespaces()));
53 final XmlSchemaProvider provider = XmlSchemaProvider.getAvailableProvider(file);
54 if (provider != null) {
55 namespaces.addAll(provider.getAvailableNamespaces(file, null));
57 final ArrayList<String> nsInfo = new ArrayList<String>();
58 final String[] names = TagNameReference.getTagNameVariants(context, namespaces, nsInfo);
59 final List<Pair<String, String>> set = new ArrayList<Pair<String,String>>(names.length);
60 final Iterator<String> iterator = nsInfo.iterator();
61 for (String name : names) {
62 final int pos = name.indexOf(':');
63 final String s = pos >= 0 ? name.substring(pos + 1) : name;
64 set.add(Pair.create(s, iterator.next()));
66 return set;
69 @NotNull
70 public Set<String> getNamespacesByTagName(@NotNull final String tagName, @NotNull final XmlFile context) {
71 final XmlSchemaProvider provider = XmlSchemaProvider.getAvailableProvider(context);
72 if (provider == null) {
73 return Collections.emptySet();
75 return provider.getAvailableNamespaces(context, tagName);
78 public static Set<String> filterNamespaces(final Set<String> namespaces, final String tagName, final XmlFile context) {
79 if (tagName == null) {
80 return namespaces;
82 final HashSet<String> set = new HashSet<String>();
83 for (String namespace : namespaces) {
84 final XmlFile xmlFile = XmlUtil.findNamespace(context, namespace);
85 if (xmlFile != null) {
86 final XmlDocument document = xmlFile.getDocument();
87 assert document != null;
88 final XmlNSDescriptor nsDescriptor = (XmlNSDescriptor)document.getMetaData();
89 assert nsDescriptor != null;
90 final XmlElementDescriptor[] elementDescriptors = nsDescriptor.getRootElementsDescriptors(document);
91 for (XmlElementDescriptor elementDescriptor : elementDescriptors) {
92 if (hasTag(elementDescriptor, tagName, new HashSet<XmlElementDescriptor>())) {
93 set.add(namespace);
94 break;
99 return set;
102 private static boolean hasTag(XmlElementDescriptor elementDescriptor, String tagName, Set<XmlElementDescriptor> visited) {
103 final String name = elementDescriptor.getDefaultName();
104 if (name.equals(tagName)) {
105 return true;
107 for (XmlElementDescriptor descriptor : elementDescriptor.getElementsDescriptors(null)) {
108 if (!visited.contains(elementDescriptor)) {
109 visited.add(elementDescriptor);
110 if (hasTag(descriptor, tagName, visited)) {
111 return true;
115 return false;
118 @NotNull
119 public Set<String> guessUnboundNamespaces(@NotNull final PsiElement element, @NotNull XmlFile file) {
120 if (!(element instanceof XmlTag)) {
121 return Collections.emptySet();
123 final XmlTag tag = (XmlTag)element;
124 final String name = tag.getLocalName();
125 final Set<String> byTagName = getNamespacesByTagName(name, file);
126 if (!byTagName.isEmpty()) {
127 byTagName.removeAll(Arrays.asList(tag.knownNamespaces()));
128 return byTagName;
130 final Set<String> set = guessNamespace(file, name);
131 set.removeAll(Arrays.asList(tag.knownNamespaces()));
133 final XmlTag parentTag = tag.getParentTag();
134 ns: for (Iterator<String> i = set.iterator(); i.hasNext();) {
135 final String s = i.next();
136 final Collection<XmlFile> namespaces = XmlUtil.findNSFilesByURI(s, element.getProject(), ModuleUtil.findModuleForPsiElement(file));
137 for (XmlFile namespace : namespaces) {
138 final XmlDocument document = namespace.getDocument();
139 assert document != null;
140 final XmlNSDescriptor nsDescriptor = (XmlNSDescriptor)document.getMetaData();
141 assert nsDescriptor != null;
142 if (parentTag != null) {
143 continue ns;
145 final XmlElementDescriptor[] descriptors = nsDescriptor.getRootElementsDescriptors(document);
146 for (XmlElementDescriptor descriptor : descriptors) {
147 if (descriptor.getName().equals(name)) {
148 continue ns;
152 i.remove();
154 return set;
157 public void insertNamespaceDeclaration(@NotNull final XmlFile file,
158 @NotNull final Editor editor,
159 @NotNull final Set<String> possibleNamespaces,
160 @Nullable String nsPrefix,
161 @Nullable final Runner<String, IncorrectOperationException> runAfter) throws IncorrectOperationException {
163 final String namespace = possibleNamespaces.iterator().next();
165 final Project project = file.getProject();
166 final XmlDocument document = file.getDocument();
167 assert document != null;
168 final XmlTag rootTag = document.getRootTag();
169 assert rootTag != null;
170 final XmlAttribute[] attributes = rootTag.getAttributes();
171 XmlAttribute anchor = null;
172 for (XmlAttribute attribute : attributes) {
173 final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
174 if (attribute.isNamespaceDeclaration() || (descriptor != null && descriptor.isRequired())) {
175 anchor = attribute;
176 } else {
177 break;
181 final XmlSchemaProvider provider = XmlSchemaProvider.getAvailableProvider(file);
182 String prefix = nsPrefix;
183 if (prefix == null && provider != null) {
184 prefix = provider.getDefaultPrefix(namespace, file);
186 if (prefix == null) {
187 prefix = "x";
189 final XmlElementFactory elementFactory = XmlElementFactory.getInstance(project);
191 @NonNls final String qname = "xmlns" + (prefix.length() > 0 ? ":"+ prefix :"");
192 final XmlAttribute attribute = elementFactory.createXmlAttribute(qname, namespace);
193 if (anchor == null) {
194 rootTag.add(attribute);
195 } else {
196 rootTag.addAfter(attribute, anchor);
199 String location = null;
200 if (namespace.length() > 0) {
201 if (provider != null) {
202 final Set<String> strings = provider.getLocations(namespace, file);
203 if (strings != null && strings.size() > 0) {
204 location = strings.iterator().next();
209 if (location != null) {
210 XmlAttribute xmlAttribute = rootTag.getAttribute("xsi:schemaLocation");
211 final String pair = namespace + " " + location;
212 if (xmlAttribute == null) {
213 xmlAttribute = elementFactory.createXmlAttribute("xsi:schemaLocation", pair);
214 rootTag.add(xmlAttribute);
215 } else {
216 final String value = xmlAttribute.getValue();
217 if (!value.contains(namespace)) {
218 if (StringUtil.isEmptyOrSpaces(value)) {
219 xmlAttribute.setValue(pair);
220 } else {
221 xmlAttribute.setValue(value.trim() + " " + pair);
226 CodeStyleManager.getInstance(project).reformat(rootTag);
228 if (namespace.length() == 0) {
229 final XmlAttribute xmlAttribute = rootTag.getAttribute(qname);
230 if (xmlAttribute != null) {
231 final XmlAttributeValue value = xmlAttribute.getValueElement();
232 assert value != null;
233 final int startOffset = value.getTextOffset();
234 editor.getCaretModel().moveToOffset(startOffset);
237 if (runAfter != null) {
238 runAfter.run(prefix);
242 public boolean isPrefixDeclared(final XmlTag context, String namespacePrefix) {
243 @NonNls String nsDeclarationAttrName = null;
244 for(XmlTag t = context; t != null; t = t.getParentTag()) {
245 if (t.hasNamespaceDeclarations()) {
246 if (nsDeclarationAttrName == null) nsDeclarationAttrName = "xmlns:"+namespacePrefix;
247 if (t.getAttributeValue(nsDeclarationAttrName) != null) return true;
250 return false;
253 private static Set<String> guessNamespace(final PsiFile file, String tagName) {
254 final Project project = file.getProject();
255 final Collection<VirtualFile> files = XmlTagNamesIndex.getFilesByTagName(tagName, project);
256 final Set<String> possibleUris = new LinkedHashSet<String>(files.size());
257 for (VirtualFile virtualFile : files) {
258 final String namespace = XmlNamespaceIndex.getNamespace(virtualFile, project);
259 if (namespace != null) {
260 possibleUris.add(namespace);
263 return possibleUris;