348137ad3027f0524df7398920bf42bdd5aaf500
[idea/community.git] / xml / impl / src / com / intellij / xml / util / XmlUtil.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.xml.util;
17
18 import com.intellij.codeInsight.completion.CompletionUtil;
19 import com.intellij.codeInsight.daemon.Validator;
20 import com.intellij.javaee.ExternalResourceManager;
21 import com.intellij.javaee.ExternalResourceManagerEx;
22 import com.intellij.javaee.UriUtil;
23 import com.intellij.lang.ASTNode;
24 import com.intellij.lang.Language;
25 import com.intellij.lang.ParserDefinition;
26 import com.intellij.lang.html.HTMLLanguage;
27 import com.intellij.lang.xhtml.XHTMLLanguage;
28 import com.intellij.lexer.Lexer;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.extensions.Extensions;
32 import com.intellij.openapi.fileTypes.FileType;
33 import com.intellij.openapi.fileTypes.StdFileTypes;
34 import com.intellij.openapi.module.Module;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.util.Key;
37 import com.intellij.openapi.util.NullableComputable;
38 import com.intellij.openapi.util.Pair;
39 import com.intellij.openapi.util.RecursionManager;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.openapi.vfs.CharsetToolkit;
42 import com.intellij.openapi.vfs.LocalFileSystem;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.patterns.StandardPatterns;
45 import com.intellij.patterns.StringPattern;
46 import com.intellij.patterns.XmlPatterns;
47 import com.intellij.psi.*;
48 import com.intellij.psi.XmlElementFactory;
49 import com.intellij.psi.codeStyle.CodeStyleManager;
50 import com.intellij.psi.filters.ElementFilter;
51 import com.intellij.psi.filters.XmlTagFilter;
52 import com.intellij.psi.filters.position.FilterPattern;
53 import com.intellij.psi.impl.source.html.HtmlDocumentImpl;
54 import com.intellij.psi.impl.source.tree.CompositeElement;
55 import com.intellij.psi.impl.source.tree.LeafElement;
56 import com.intellij.psi.impl.source.xml.XmlEntityRefImpl;
57 import com.intellij.psi.scope.processor.FilterElementProcessor;
58 import com.intellij.psi.search.PsiElementProcessor;
59 import com.intellij.psi.tree.IElementType;
60 import com.intellij.psi.util.*;
61 import com.intellij.psi.xml.*;
62 import com.intellij.util.*;
63 import com.intellij.util.containers.ContainerUtil;
64 import com.intellij.util.xmlb.JDOMXIncluder;
65 import com.intellij.xml.*;
66 import com.intellij.xml.impl.schema.ComplexTypeDescriptor;
67 import com.intellij.xml.impl.schema.TypeDescriptor;
68 import com.intellij.xml.impl.schema.XmlElementDescriptorImpl;
69 import com.intellij.xml.impl.schema.XmlNSDescriptorImpl;
70 import com.intellij.xml.index.IndexedRelevantResource;
71 import com.intellij.xml.index.XmlNamespaceIndex;
72 import org.jetbrains.annotations.NonNls;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
75
76 import java.io.File;
77 import java.util.*;
78 import java.util.regex.Matcher;
79
80 /**
81  * @author Mike
82  */
83 public class XmlUtil {
84   private static final Logger LOG = Logger.getInstance("#com.intellij.xml.util.XmlUtil");
85
86   @NonNls public static final String TAGLIB_1_2_URI = "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd";
87
88   @NonNls public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema";
89   @NonNls public static final String XML_SCHEMA_URI2 = "http://www.w3.org/1999/XMLSchema";
90   @NonNls public static final String XML_SCHEMA_URI3 = "http://www.w3.org/2000/10/XMLSchema";
91   public static final String[] SCHEMA_URIS = {XML_SCHEMA_URI, XML_SCHEMA_URI2, XML_SCHEMA_URI3};
92   @NonNls public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance";
93
94   @NonNls public static final String XSLT_URI = "http://www.w3.org/1999/XSL/Transform";
95   @NonNls public static final String XINCLUDE_URI = "http://www.w3.org/2001/XInclude";
96
97   @NonNls public static final String ANT_URI = "http://ant.apache.org/schema.xsd";
98   @NonNls public static final String XHTML_URI = "http://www.w3.org/1999/xhtml";
99   @NonNls public static final String HTML_URI = "http://www.w3.org/1999/html";
100   @NonNls public static final String EMPTY_URI = "";
101   @NonNls public static final Key<String> TEST_PATH = Key.create("TEST PATH");
102   @NonNls public static final String JSP_URI = "http://java.sun.com/JSP/Page";
103   @NonNls public static final String ANY_URI = "http://www.intellij.net/ns/any";
104
105   @NonNls public static final String JSTL_CORE_URI = "http://java.sun.com/jsp/jstl/core";
106   @NonNls public static final String JSTL_CORE_URI2 = "http://java.sun.com/jstl/core";
107   @NonNls public static final String JSTL_CORE_URI3 = "http://java.sun.com/jstl/core_rt";
108   @NonNls public static final String[] JSTL_CORE_URIS = {JSTL_CORE_URI, JSTL_CORE_URI2, JSTL_CORE_URI3};
109
110   @NonNls public static final String JSF_HTML_URI = "http://java.sun.com/jsf/html";
111   @NonNls public static final String JSF_CORE_URI = "http://java.sun.com/jsf/core";
112
113   @NonNls public static final String JSTL_FORMAT_URI = "http://java.sun.com/jsp/jstl/fmt";
114   @NonNls public static final String JSTL_FORMAT_URI2 = "http://java.sun.com/jstl/fmt";
115   @NonNls private static final String JSTL_FORMAT_URI3 = "http://java.sun.com/jstl/fmt_rt";
116   @NonNls public static final String[] JSTL_FORMAT_URIS = {JSTL_FORMAT_URI, JSTL_FORMAT_URI2, JSTL_FORMAT_URI3};
117
118   @NonNls public static final String SPRING_URI = "http://www.springframework.org/tags";
119   @NonNls public static final String SPRING_FORMS_URI = "http://www.springframework.org/tags/form";
120   @NonNls public static final String STRUTS_BEAN_URI = "http://struts.apache.org/tags-bean";
121   @NonNls public static final String STRUTS_BEAN_URI2 = "http://jakarta.apache.org/struts/tags-bean";
122   @NonNls public static final String APACHE_I18N_URI = "http://jakarta.apache.org/taglibs/i18n-1.0";
123   @NonNls public static final String STRUTS_LOGIC_URI = "http://struts.apache.org/tags-logic";
124   @NonNls public static final String STRUTS_HTML_URI = "http://struts.apache.org/tags-html";
125   @NonNls public static final String STRUTS_HTML_URI2 = "http://jakarta.apache.org/struts/tags-html";
126
127   @NonNls public static final String APACHE_TRINIDAD_URI = "http://myfaces.apache.org/trinidad";
128   @NonNls public static final String APACHE_TRINIDAD_HTML_URI = "http://myfaces.apache.org/trinidad/html";
129
130
131   @NonNls public static final String[] HIBERNATE_URIS =
132     {"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd", "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"};
133
134   @NonNls public static final String XSD_SIMPLE_CONTENT_TAG = "simpleContent";
135   @NonNls public static final String NO_NAMESPACE_SCHEMA_LOCATION_ATT = "noNamespaceSchemaLocation";
136   @NonNls public static final String SCHEMA_LOCATION_ATT = "schemaLocation";
137   @NonNls public static final String[] WEB_XML_URIS =
138     {"http://java.sun.com/xml/ns/j2ee", "http://java.sun.com/xml/ns/javaee", "http://java.sun.com/dtd/web-app_2_3.dtd",
139       "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"};
140   @NonNls public static final String FACELETS_URI = "http://java.sun.com/jsf/facelets";
141   @NonNls public static final String JSTL_FUNCTIONS_URI = "http://java.sun.com/jsp/jstl/functions";
142   @NonNls public static final String JSTL_FUNCTIONS_URI2 = "http://java.sun.com/jstl/functions";
143   @NonNls public static final String JSTL_FN_FACELET_URI = "com.sun.facelets.tag.jstl.fn.JstlFnLibrary";
144   @NonNls public static final String JSTL_CORE_FACELET_URI = "com.sun.facelets.tag.jstl.core.JstlCoreLibrary";
145   @NonNls public static final String TARGET_NAMESPACE_ATTR_NAME = "targetNamespace";
146   @NonNls public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
147   public static final List<String> ourSchemaUrisList = Arrays.asList(SCHEMA_URIS);
148   public static final Key<Boolean> ANT_FILE_SIGN = new Key<Boolean>("FORCED ANT FILE");
149   @NonNls public static final String TAG_DIR_NS_PREFIX = "urn:jsptagdir:";
150   @NonNls public static final String VALUE_ATTR_NAME = "value";
151   @NonNls public static final String ENUMERATION_TAG_NAME = "enumeration";
152   @NonNls public static final String HTML4_LOOSE_URI = "http://www.w3.org/TR/html4/loose.dtd";
153   @NonNls public static final String WSDL_SCHEMA_URI = "http://schemas.xmlsoap.org/wsdl/";
154   public static final Key<PsiAnchor> ORIGINAL_ELEMENT = Key.create("ORIGINAL_ELEMENT");
155
156
157   private XmlUtil() {
158   }
159
160   @Nullable
161   public static String getSchemaLocation(XmlTag tag, String namespace) {
162     final String uri = ExternalResourceManagerEx.getInstanceEx().getResourceLocation(namespace, tag.getProject());
163     if (uri != null && !uri.equals(namespace)) return uri;
164
165     while (true) {
166       if ("".equals(namespace)) {
167         final String attributeValue = tag.getAttributeValue("noNamespaceSchemaLocation", XML_SCHEMA_INSTANCE_URI);
168         if (attributeValue != null) return attributeValue;
169       }
170       else {
171         String schemaLocation = tag.getAttributeValue("schemaLocation", XML_SCHEMA_INSTANCE_URI);
172         if (schemaLocation != null) {
173           int start = schemaLocation.indexOf(namespace);
174           if (start >= 0) {
175             start += namespace.length();
176             final StringTokenizer tokenizer = new StringTokenizer(schemaLocation.substring(start + 1));
177             if (tokenizer.hasMoreTokens()) {
178               return tokenizer.nextToken();
179             }
180             else {
181               return null;
182             }
183           }
184         }
185       }
186       if (tag.getParent() instanceof XmlTag) {
187         tag = (XmlTag)tag.getParent();
188       }
189       else {
190         break;
191       }
192     }
193     return null;
194   }
195
196   @Nullable
197   public static String findNamespacePrefixByURI(XmlFile file, @NonNls String uri) {
198     final XmlTag tag = file.getRootTag();
199     if (tag == null) return null;
200
201     for (XmlAttribute attribute : tag.getAttributes()) {
202       if (attribute.getName().startsWith("xmlns:") && attribute.getValue().equals(uri)) {
203         return attribute.getName().substring("xmlns:".length());
204       }
205       if ("xmlns".equals(attribute.getName()) && attribute.getValue().equals(uri)) return "";
206     }
207
208     return null;
209   }
210
211   public static String[] findNamespacesByURI(XmlFile file, String uri) {
212     if (file == null) return ArrayUtil.EMPTY_STRING_ARRAY;
213     final XmlDocument document = file.getDocument();
214     if (document == null) return ArrayUtil.EMPTY_STRING_ARRAY;
215     final XmlTag tag = document.getRootTag();
216     if (tag == null) return ArrayUtil.EMPTY_STRING_ARRAY;
217     XmlAttribute[] attributes = tag.getAttributes();
218
219
220     List<String> result = new ArrayList<String>();
221
222     for (XmlAttribute attribute : attributes) {
223       if (attribute.getName().startsWith("xmlns:") && attribute.getValue().equals(uri)) {
224         result.add(attribute.getName().substring("xmlns:".length()));
225       }
226       if ("xmlns".equals(attribute.getName()) && attribute.getValue().equals(uri)) result.add("");
227     }
228
229     return ArrayUtil.toStringArray(result);
230   }
231
232   @Nullable
233   public static String getXsiNamespace(XmlFile file) {
234     return findNamespacePrefixByURI(file, XML_SCHEMA_INSTANCE_URI);
235   }
236
237   @Nullable
238   public static XmlFile findNamespace(@NotNull PsiFile base, @NotNull String nsLocation) {
239     final String location = ExternalResourceManager.getInstance().getResourceLocation(nsLocation, base.getProject());
240     if (!location.equals(nsLocation)) { // is mapped
241       return findXmlFile(base, location);
242     }
243     final XmlFile xmlFile = XmlSchemaProvider.findSchema(location, base);
244     return xmlFile == null ? findXmlFile(base, location) : xmlFile;
245   }
246
247   @Nullable
248   public static XmlFile findNamespaceByLocation(PsiFile base, @NotNull String nsLocation) {
249     final String location = ExternalResourceManager.getInstance().getResourceLocation(nsLocation, base.getProject());
250     return findXmlFile(base, location);
251   }
252
253   public static Collection<XmlFile> findNSFilesByURI(String namespace, final Project project, Module module) {
254     final List<IndexedRelevantResource<String,String>> resources = XmlNamespaceIndex.getResourcesByNamespace(namespace, project, module);
255     final PsiManager psiManager = PsiManager.getInstance(project);
256     return ContainerUtil.mapNotNull(resources, new NullableFunction<IndexedRelevantResource<String, String>, XmlFile>() {
257       public XmlFile fun(IndexedRelevantResource<String, String> stringStringIndexedRelevantResource) {
258         PsiFile file = psiManager.findFile(stringStringIndexedRelevantResource.getFile());
259         return file instanceof XmlFile ? (XmlFile)file : null;
260       }
261     });
262   }
263
264   @Nullable
265   public static XmlFile findXmlFile(PsiFile base, @NotNull String uri) {
266     PsiFile result = null;
267
268     if (ApplicationManager.getApplication().isUnitTestMode()) {
269       String data = base.getOriginalFile().getUserData(TEST_PATH);
270
271       if (data != null) {
272         String filePath = data + "/" + uri;
273         final VirtualFile path = LocalFileSystem.getInstance().findFileByPath(filePath.replace(File.separatorChar, '/'));
274         if (path != null) {
275           result = base.getManager().findFile(path);
276         }
277       }
278     }
279     if (result == null) {
280       result = findRelativeFile(uri, base);
281     }
282
283     if (result instanceof XmlFile) {
284       return (XmlFile)result;
285     }
286
287     return null;
288   }
289
290   @Nullable
291   public static XmlToken getTokenOfType(PsiElement element, IElementType type) {
292     if (element == null) {
293       return null;
294     }
295
296     PsiElement[] children = element.getChildren();
297
298     for (PsiElement child : children) {
299       if (child instanceof XmlToken) {
300         XmlToken token = (XmlToken)child;
301
302         if (token.getTokenType() == type) {
303           return token;
304         }
305       }
306     }
307
308     return null;
309   }
310
311   public static boolean processXmlElements(XmlElement element, PsiElementProcessor processor, boolean deepFlag) {
312     return processXmlElements(element, processor, deepFlag, false);
313   }
314
315   public static boolean processXmlElements(XmlElement element, PsiElementProcessor processor, boolean deepFlag, boolean wideFlag) {
316     if (element == null) return true;
317     PsiFile baseFile = element.isValid() ? element.getContainingFile() : null;
318     return processXmlElements(element, processor, deepFlag, wideFlag, baseFile);
319   }
320
321   public static boolean processXmlElements(final XmlElement element,
322                                            final PsiElementProcessor processor,
323                                            final boolean deepFlag,
324                                            final boolean wideFlag,
325                                            final PsiFile baseFile) {
326     return processXmlElements(element, processor, deepFlag, wideFlag, baseFile, true);
327   }
328
329   public static boolean processXmlElements(final XmlElement element,
330                                            final PsiElementProcessor processor,
331                                            final boolean deepFlag,
332                                            final boolean wideFlag,
333                                            final PsiFile baseFile,
334                                            boolean processIncludes) {
335     return new XmlElementProcessor(processor, baseFile).processXmlElements(element, deepFlag, wideFlag, processIncludes);
336   }
337
338   public static boolean processXmlElementChildren(final XmlElement element, final PsiElementProcessor processor, final boolean deepFlag) {
339     final XmlElementProcessor p = new XmlElementProcessor(processor, element.getContainingFile());
340
341     final boolean wideFlag = false;
342     for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
343       if (!p.processElement(child, deepFlag, wideFlag, true) && !wideFlag) return false;
344     }
345
346     return true;
347   }
348
349   public static ParserDefinition.SpaceRequirements canStickTokensTogetherByLexerInXml(final ASTNode left,
350                                                                                       final ASTNode right,
351                                                                                       final Lexer lexer,
352                                                                                       int state) {
353     if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN ||
354         right.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
355       return ParserDefinition.SpaceRequirements.MUST_NOT;
356     }
357     if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER && right.getElementType() == XmlTokenType.XML_NAME) {
358       return ParserDefinition.SpaceRequirements.MUST;
359     }
360     if (left.getElementType() == XmlTokenType.XML_NAME && right.getElementType() == XmlTokenType.XML_NAME) {
361       return ParserDefinition.SpaceRequirements.MUST;
362     }
363     return ParserDefinition.SpaceRequirements.MAY;
364   }
365
366   public static boolean tagFromTemplateFramework(@NotNull final XmlTag tag) {
367     final String ns = tag.getNamespace();
368     return nsFromTemplateFramework(ns);
369   }
370
371   public static boolean nsFromTemplateFramework(final String ns) {
372     return XSLT_URI.equals(ns) || XINCLUDE_URI.equals(ns);
373   }
374
375
376   public static char getCharFromEntityRef(@NonNls String text) {
377     //LOG.assertTrue(text.startsWith("&#") && text.endsWith(";"));
378     if (text.charAt(1) != '#') {
379       text = text.substring(1, text.length() - 1);
380       return XmlTagUtil.getCharacterByEntityName(text);
381     }
382     text = text.substring(2, text.length() - 1);
383     try {
384       int code;
385       if (StringUtil.startsWithChar(text, 'x')) {
386         text = text.substring(1);
387         code = Integer.parseInt(text, 16);
388       }
389       else {
390         code = Integer.parseInt(text);
391       }
392       return (char)code;
393     }
394     catch (NumberFormatException e) {
395       return 0;
396     }
397   }
398
399   public static boolean attributeFromTemplateFramework(@NonNls final String name, final XmlTag tag) {
400     return "jsfc".equals(name) && tag.getNSDescriptor(JSF_HTML_URI, true) != null;
401   }
402
403   @Nullable
404   public static String getTargetSchemaNsFromTag(@Nullable final XmlTag xmlTag) {
405     if (xmlTag == null) return null;
406     String targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI);
407     if (targetNamespace == null) targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI2);
408     if (targetNamespace == null) targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI3);
409     return targetNamespace;
410   }
411
412   @Nullable
413   public static XmlTag getSchemaSimpleContent(@NotNull XmlTag tag) {
414     XmlElementDescriptor descriptor = tag.getDescriptor();
415
416     if (descriptor instanceof XmlElementDescriptorImpl) {
417       final TypeDescriptor type = ((XmlElementDescriptorImpl)descriptor).getType(tag);
418
419       if (type instanceof ComplexTypeDescriptor) {
420         final XmlTag[] simpleContent = new XmlTag[1];
421
422         processXmlElements(((ComplexTypeDescriptor)type).getDeclaration(), new PsiElementProcessor() {
423           public boolean execute(@NotNull final PsiElement element) {
424             if (element instanceof XmlTag) {
425               final XmlTag tag = (XmlTag)element;
426               @NonNls final String s = ((XmlTag)element).getLocalName();
427
428               if ((s.equals(XSD_SIMPLE_CONTENT_TAG) ||
429                    s.equals("restriction") && "string".equals(findLocalNameByQualifiedName(tag.getAttributeValue("base")))) &&
430                   tag.getNamespace().equals(XML_SCHEMA_URI)) {
431                 simpleContent[0] = tag;
432                 return false;
433               }
434             }
435
436             return true;
437           }
438         }, true);
439
440         return simpleContent[0];
441       }
442     }
443     return null;
444   }
445
446   public static <T extends PsiElement> void doDuplicationCheckForElements(final T[] elements,
447                                                                           final Map<String, T> presentNames,
448                                                                           DuplicationInfoProvider<T> provider,
449                                                                           final Validator.ValidationHost host) {
450     for (T t : elements) {
451       final String name = provider.getName(t);
452       if (name == null) continue;
453
454       final String nameKey = provider.getNameKey(t, name);
455
456       if (presentNames.containsKey(nameKey)) {
457         final T psiElement = presentNames.get(nameKey);
458         final String message = XmlBundle.message("duplicate.declaration", nameKey);
459
460         if (psiElement != null) {
461           presentNames.put(nameKey, null);
462
463           host.addMessage(provider.getNodeForMessage(psiElement), message, Validator.ValidationHost.ERROR);
464         }
465
466         host.addMessage(provider.getNodeForMessage(t), message, Validator.ValidationHost.ERROR);
467       }
468       else {
469         presentNames.put(nameKey, t);
470       }
471     }
472   }
473
474   public static String getEntityValue(final XmlEntityRef entityRef) {
475     final XmlEntityDecl decl = entityRef.resolve(entityRef.getContainingFile());
476     if (decl != null) {
477       final XmlAttributeValue valueElement = decl.getValueElement();
478       if (valueElement != null) {
479         final String value = valueElement.getValue();
480         if (value != null) {
481           return value;
482         }
483       }
484     }
485     return entityRef.getText();
486   }
487
488   public static boolean isAntFile(final PsiFile file) {
489     if (file instanceof XmlFile) {
490       final XmlFile xmlFile = (XmlFile)file;
491       final XmlDocument document = xmlFile.getDocument();
492       if (document != null) {
493         final XmlTag tag = document.getRootTag();
494         if (tag != null && "project".equals(tag.getName()) && tag.getContext() instanceof XmlDocument) {
495           if (tag.getAttributeValue("default") != null) {
496             return true;
497           }
498           VirtualFile vFile = xmlFile.getOriginalFile().getVirtualFile();
499           if (vFile != null && vFile.getUserData(ANT_FILE_SIGN) != null) {
500             return true;
501           }
502         }
503       }
504     }
505     return false;
506   }
507
508   @Nullable
509   public static PsiFile findRelativeFile(String uri, PsiElement base) {
510     if (base instanceof PsiFile) {
511       PsiFile baseFile = (PsiFile)base;
512       VirtualFile file = UriUtil.findRelative(uri, baseFile.getOriginalFile());
513       if (file == null) return null;
514       return base.getManager().findFile(file);
515     }
516     else if (base instanceof PsiDirectory) {
517       PsiDirectory baseDir = (PsiDirectory)base;
518       VirtualFile file = UriUtil.findRelative(uri, baseDir);
519       if (file == null) return null;
520       return base.getManager().findFile(file);
521     }
522
523     return null;
524   }
525
526   @Nullable
527   public static String getCommentText(XmlComment comment) {
528     final PsiElement firstChild = comment.getFirstChild();
529     if (firstChild != null) {
530       final PsiElement nextSibling = firstChild.getNextSibling();
531       if (nextSibling instanceof XmlToken) {
532         final XmlToken token = (XmlToken)nextSibling;
533         if (token.getTokenType() == XmlTokenType.XML_COMMENT_CHARACTERS) {
534           return token.getText();
535         }
536       }
537     }
538     return null;
539   }
540
541   @Nullable
542   public static PsiElement findNamespaceDeclaration(XmlElement xmlElement, String nsName) {
543     while (! (xmlElement instanceof XmlTag) && xmlElement != null) {
544       final PsiElement parent = xmlElement.getParent();
545       if (!(parent instanceof XmlElement)) return null;
546       xmlElement = (XmlElement)parent;
547     }
548     if (xmlElement != null) {
549       XmlTag tag = (XmlTag)xmlElement;
550       while (tag != null) {
551         for (XmlAttribute attribute : tag.getAttributes()) {
552           if (attribute.isNamespaceDeclaration() && attribute.getLocalName().equals(nsName)) {
553             return attribute;
554           }
555         }
556         tag = tag.getParentTag();
557       }
558     }
559     return null;
560   }
561
562   public static void reformatTagStart(XmlTag tag) {
563     ASTNode child = XmlChildRole.START_TAG_END_FINDER.findChild(tag.getNode());
564     if (child == null) {
565       CodeStyleManager.getInstance(tag.getProject()).reformat(tag);
566     }
567     else {
568       CodeStyleManager.getInstance(tag.getProject()).reformatRange(tag, tag.getTextRange().getStartOffset(), child.getTextRange().getEndOffset());
569     }
570   }
571
572   @Nullable
573   public static XmlElementDescriptor getDescriptorFromContext(@NotNull XmlTag tag) {
574     PsiElement parent = tag.getParent();
575     if (parent instanceof XmlTag) {
576       XmlTag parentTag = (XmlTag)parent;
577       final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
578
579       if (parentDescriptor != null) {
580         return XmlExtension.getExtension(tag.getContainingFile()).getElementDescriptor(tag, parentTag, parentDescriptor);
581       }
582     }
583     return null;
584   }
585
586   public static void expandTag(@NotNull XmlTag tag) {
587     XmlTag newTag = XmlElementFactory.getInstance(tag.getProject()).createTagFromText('<' + tag.getName() + "></" + tag.getName() + '>');
588
589     ASTNode node = tag.getNode();
590     if (!(node instanceof CompositeElement)) return;
591     CompositeElement compositeElement = (CompositeElement)node;
592
593     final LeafElement emptyTagEnd = (LeafElement)XmlChildRole.EMPTY_TAG_END_FINDER.findChild(compositeElement);
594     if (emptyTagEnd == null) return;
595
596     compositeElement.removeChild(emptyTagEnd);
597     PsiElement[] children = newTag.getChildren();
598
599     compositeElement.addChildren(children[2].getNode(), null, null);
600   }
601   //
602   //public static void expandTag(@NotNull XmlTag tag) {
603   //  final LeafElement emptyTagEnd = (LeafElement)XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tag.getNode());
604   //  if (emptyTagEnd == null) return;
605   //
606   //  PsiFile file = tag.getContainingFile().getOriginalFile();
607   //
608   //  TextRange textRange = emptyTagEnd.getTextRange();
609   //  Document document = file.getViewProvider().getDocument();
610   //  if (document == null) return;
611   //
612   //  PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
613   //  document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), "></" + tag.getName() + '>');
614   //  PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
615   //}
616
617   private static class XmlElementProcessor {
618     private final PsiElementProcessor processor;
619     private final PsiFile targetFile;
620
621     XmlElementProcessor(PsiElementProcessor _processor, PsiFile _targetFile) {
622       processor = _processor;
623       targetFile = _targetFile;
624     }
625
626     private boolean processXmlElements(PsiElement element, boolean deepFlag, boolean wideFlag, boolean processIncludes) {
627       if (deepFlag) if (!processor.execute(element)) return false;
628
629       PsiElement startFrom = element.getFirstChild();
630
631       if (element instanceof XmlEntityRef) {
632         XmlEntityRef ref = (XmlEntityRef)element;
633
634         //if ("Number.datatype".equals(ref.getText().substring(1, ref.getTextLength() - 1))) {
635         //  int a = 1;
636         //}
637         PsiElement newElement = parseEntityRef(targetFile, ref, true);
638         //if (newElement == null) {
639         //  System.out.println("No image for :" + ref.getText());
640         //  return true;
641         //}
642
643         while (newElement != null) {
644           if (!processElement(newElement, deepFlag, wideFlag, processIncludes)) return false;
645           newElement = newElement.getNextSibling();
646         }
647
648         return true;
649       }
650       else if (element instanceof XmlConditionalSection) {
651         XmlConditionalSection xmlConditionalSection = (XmlConditionalSection)element;
652         if (!xmlConditionalSection.isIncluded(targetFile)) return true;
653         startFrom = xmlConditionalSection.getBodyStart();
654       }
655       else if (processIncludes && XmlIncludeHandler.isXInclude(element)) {
656         XmlTag tag = (XmlTag)element;
657         if (!processXInclude(deepFlag, wideFlag, tag)) return false;
658       }
659
660       for (PsiElement child = startFrom; child != null; child = child.getNextSibling()) {
661         if (!processElement(child, deepFlag, wideFlag, processIncludes) && !wideFlag) return false;
662       }
663
664       return true;
665     }
666
667     private boolean processXInclude(final boolean deepFlag, final boolean wideFlag, @NotNull final XmlTag xincludeTag) {
668
669       final PsiElement[] inclusion = CachedValuesManager.getManager(xincludeTag.getProject()).getCachedValue(xincludeTag, new CachedValueProvider<PsiElement[]>() {
670           public Result<PsiElement[]> compute() {
671             PsiElement[] result = RecursionManager.doPreventingRecursion(xincludeTag, true, new NullableComputable<PsiElement[]>() {
672               @Override
673               public PsiElement[] compute() {
674                 return computeInclusion(xincludeTag);
675               }
676             });
677             return Result.create(result, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
678           }
679         });
680
681       if (inclusion != null) {
682         for (PsiElement psiElement : inclusion) {
683           if (!processElement(psiElement, deepFlag, wideFlag, true)) return false;
684         }
685       }
686
687       return true;
688     }
689
690     @Nullable
691     private static PsiElement[] computeInclusion(final XmlTag xincludeTag) {
692       final XmlFile included = XmlIncludeHandler.resolveXIncludeFile(xincludeTag);
693       final XmlDocument document = included != null ? included.getDocument() : null;
694       final XmlTag rootTag = document != null ? document.getRootTag() : null;
695       if (rootTag != null) {
696         final String xpointer = xincludeTag.getAttributeValue("xpointer", XINCLUDE_URI);
697         final XmlTag[] includeTag = extractXpointer(rootTag, xpointer);
698         PsiElement[] result = new PsiElement[includeTag.length];
699         for (int i = 0; i < includeTag.length; i++) {
700           XmlTag xmlTag = includeTag[i];
701           final PsiElement psiElement = xmlTag.copy();
702           psiElement.putUserData(XmlElement.INCLUDING_ELEMENT, xincludeTag.getParentTag());
703           psiElement.putUserData(ORIGINAL_ELEMENT, PsiAnchor.create(xmlTag));
704           result[i] = psiElement;
705         }
706         return result;
707       }
708
709       return null;
710     }
711
712     private static XmlTag[] extractXpointer(XmlTag rootTag, @Nullable final String xpointer) {
713
714       if (xpointer != null) {
715         Matcher matcher = JDOMXIncluder.XPOINTER_PATTERN.matcher(xpointer);
716         if (matcher.matches()) {
717           String pointer = matcher.group(1);
718           matcher = JDOMXIncluder.CHILDREN_PATTERN.matcher(pointer);
719
720           if (matcher.matches()) {
721             final String rootTagName = matcher.group(1);
722
723             if (rootTagName.equals(rootTag.getName())) return rootTag.getSubTags();
724           }
725         }
726       }
727
728       return new XmlTag[]{rootTag};
729     }
730
731     private boolean processElement(PsiElement child, boolean deepFlag, boolean wideFlag, boolean processIncludes) {
732       if (deepFlag) {
733         if (!processXmlElements(child, true, wideFlag, processIncludes)) {
734           return false;
735         }
736       }
737       else {
738         if (child instanceof XmlEntityRef) {
739           if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
740         }
741         else if (child instanceof XmlConditionalSection) {
742           if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
743         }
744         else if (processIncludes && XmlIncludeHandler.isXInclude(child)) {
745           if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
746         }
747         else if (!processor.execute(child)) return false;
748       }
749       if (targetFile != null && child instanceof XmlEntityDecl) {
750         XmlEntityDecl xmlEntityDecl = (XmlEntityDecl)child;
751         XmlEntityRefImpl.cacheParticularEntity(targetFile, xmlEntityDecl);
752       }
753       return true;
754     }
755
756   }
757
758   private static PsiElement parseEntityRef(PsiFile targetFile, XmlEntityRef ref, boolean cacheValue) {
759     int type = getContextType(ref);
760
761     {
762       final XmlEntityDecl entityDecl = ref.resolve(targetFile);
763       if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
764     }
765
766     PsiElement e = ref;
767     while (e != null) {
768       if (e.getUserData(XmlElement.INCLUDING_ELEMENT) != null) {
769         e = e.getUserData(XmlElement.INCLUDING_ELEMENT);
770         final PsiFile f = e.getContainingFile();
771         if (f != null) {
772           final XmlEntityDecl entityDecl = ref.resolve(targetFile);
773           if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
774         }
775
776         continue;
777       }
778       if (e instanceof PsiFile) {
779         PsiFile refFile = (PsiFile)e;
780         final XmlEntityDecl entityDecl = ref.resolve(refFile);
781         if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
782         break;
783       }
784
785       e = e.getParent();
786     }
787
788     final PsiElement element = ref.getUserData(XmlElement.DEPENDING_ELEMENT);
789     if (element instanceof XmlFile) {
790       final XmlEntityDecl entityDecl = ref.resolve((PsiFile)element);
791       if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
792     }
793
794     return null;
795   }
796
797   private static int getContextType(XmlEntityRef ref) {
798     int type = XmlEntityDecl.CONTEXT_GENERIC_XML;
799     PsiElement temp = ref;
800     while (temp != null) {
801       if (temp instanceof XmlAttributeDecl) {
802         type = XmlEntityDecl.CONTEXT_ATTRIBUTE_SPEC;
803       }
804       else if (temp instanceof XmlElementDecl) {
805         type = XmlEntityDecl.CONTEXT_ELEMENT_CONTENT_SPEC;
806       }
807       else if (temp instanceof XmlAttlistDecl) {
808         type = XmlEntityDecl.CONTEXT_ATTLIST_SPEC;
809       }
810       else if (temp instanceof XmlEntityDecl) {
811         type = XmlEntityDecl.CONTEXT_ENTITY_DECL_CONTENT;
812       }
813       else if (temp instanceof XmlEnumeratedType) {
814         type = XmlEntityDecl.CONTEXT_ENUMERATED_TYPE;
815       }
816       else if (temp instanceof XmlAttributeValue) {
817         type = XmlEntityDecl.CONTEXT_ATTR_VALUE;
818       }
819       else {
820         temp = temp.getContext();
821         continue;
822       }
823       break;
824     }
825     return type;
826   }
827
828   private static final Key<CachedValue<PsiElement>> PARSED_DECL_KEY = Key.create("PARSED_DECL_KEY");
829
830   private static PsiElement parseEntityDecl(final XmlEntityDecl entityDecl,
831                                             final PsiFile targetFile,
832                                             final int type,
833                                             boolean cacheValue,
834                                             final XmlEntityRef entityRef) {
835     if (!cacheValue) return entityDecl.parse(targetFile, type, entityRef);
836
837     synchronized (PsiLock.LOCK) { // we depend on targetFile and entityRef
838       CachedValue<PsiElement> value = entityRef.getUserData(PARSED_DECL_KEY);
839       //    return entityDecl.parse(targetFile, type);
840
841       if (value == null) {
842         value = CachedValuesManager.getManager(entityDecl.getProject()).createCachedValue(new CachedValueProvider<PsiElement>() {
843           public Result<PsiElement> compute() {
844             final PsiElement res = entityDecl.parse(targetFile, type, entityRef);
845             if (res == null) return new Result<PsiElement>(res, targetFile);
846             if (!entityDecl.isInternalReference()) XmlEntityRefImpl.copyEntityCaches(res.getContainingFile(), targetFile);
847             return new Result<PsiElement>(res, res.getUserData(XmlElement.DEPENDING_ELEMENT), entityDecl, targetFile, entityRef);
848           }
849         }, false);
850         entityRef.putUserData(PARSED_DECL_KEY, value);
851       }
852
853       return value.getValue();
854     }
855   }
856
857   /**
858    * add child to the parent according to DTD/Schema element ordering
859    *
860    * @return newly added child
861    */
862   public static XmlTag addChildTag(XmlTag parent, XmlTag child) throws IncorrectOperationException {
863     return addChildTag(parent, child, -1);
864   }
865
866   public static XmlTag addChildTag(XmlTag parent, XmlTag child, int index) throws IncorrectOperationException {
867
868     // bug in PSI: cannot add child to <tag/>
869     if (parent.getSubTags().length == 0 && parent.getText().endsWith("/>")) {
870       final XmlElementFactory factory = XmlElementFactory.getInstance(parent.getProject());
871       final String name = parent.getName();
872       final String text = parent.getText();
873       final XmlTag tag = factory.createTagFromText(text.substring(0, text.length() - 2) + "></" + name + ">");
874       parent = (XmlTag)parent.replace(tag);
875     }
876
877     final XmlElementDescriptor parentDescriptor = parent.getDescriptor();
878     final XmlTag[] subTags = parent.getSubTags();
879     if (parentDescriptor == null || subTags.length == 0) return (XmlTag)parent.add(child);
880     int subTagNum = -1;
881
882     for (XmlElementDescriptor childElementDescriptor : parentDescriptor.getElementsDescriptors(parent)) {
883       final String childElementName = childElementDescriptor.getName();
884       int prevSubTagNum = subTagNum;
885       while (subTagNum < subTags.length - 1 && subTags[subTagNum + 1].getName().equals(childElementName)) {
886         subTagNum++;
887       }
888       if (childElementName.equals(child.getLocalName())) {
889         // insert child just after anchor
890         // insert into the position specified by index
891         subTagNum = index == -1 || index > subTagNum - prevSubTagNum ? subTagNum : prevSubTagNum + index;
892         return (XmlTag)(subTagNum == -1 ? parent.addBefore(child, subTags[0]) : parent.addAfter(child, subTags[subTagNum]));
893       }
894     }
895     return (XmlTag)parent.add(child);
896   }
897
898   public static String getAttributeValue(XmlTag tag, String name) {
899     for (XmlAttribute attribute : tag.getAttributes()) {
900       if (name.equals(attribute.getName())) return attribute.getValue();
901     }
902     return null;
903   }
904
905   public static XmlTag findOnAnyLevel(XmlTag root, String[] chain) {
906     XmlTag curTag = root;
907     for (String s : chain) {
908       curTag = curTag.findFirstSubTag(s);
909       if (curTag == null) return null;
910     }
911
912     return curTag;
913   }
914
915   public static XmlTag findSubTag(XmlTag rootTag, String path) {
916     String[] pathElements = path.split("/");
917
918     XmlTag curTag = rootTag;
919     for (String curTagName : pathElements) {
920       curTag = curTag.findFirstSubTag(curTagName);
921       if (curTag == null) break;
922     }
923     return curTag;
924   }
925
926   @Nullable
927   public static XmlTag findSubTagWithValue(XmlTag rootTag, String tagName, String tagValue) {
928     if (rootTag == null) return null;
929     final XmlTag[] subTags = rootTag.findSubTags(tagName);
930     for (XmlTag subTag : subTags) {
931       if (subTag.getValue().getTrimmedText().equals(tagValue)) {
932         return subTag;
933       }
934     }
935     return null;
936   }
937
938   // Read the function name and parameter names to find out what this function does... :-)
939   @Nullable
940   public static XmlTag find(String subTag, String withValue, String forTag, XmlTag insideRoot) {
941     final XmlTag[] forTags = insideRoot.findSubTags(forTag);
942
943     for (XmlTag tag : forTags) {
944       final XmlTag[] allTags = tag.findSubTags(subTag);
945
946       for (XmlTag curTag : allTags) {
947         if (curTag.getName().equals(subTag) && curTag.getValue().getTrimmedText().equalsIgnoreCase(withValue)) {
948           return tag;
949         }
950       }
951     }
952
953     return null;
954   }
955
956   @Nullable
957   @NonNls
958   public static String[][] getDefaultNamespaces(final XmlDocument document) {
959     final XmlFile file = getContainingFile(document);
960
961     final XmlTag tag = document.getRootTag();
962     if (tag == null) return null;
963
964     if (file != null) {
965       @NotNull final XmlFileNSInfoProvider[] nsProviders = Extensions.getExtensions(XmlFileNSInfoProvider.EP_NAME);
966
967       NextProvider:
968       for (XmlFileNSInfoProvider nsProvider : nsProviders) {
969         final String[][] pairs = nsProvider.getDefaultNamespaces(file);
970         if (pairs != null && pairs.length > 0) {
971
972           for (final String[] nsMapping : pairs) {
973             if (nsMapping == null || nsMapping.length != 2 || nsMapping[0] == null || nsMapping[1] == null) {
974               LOG.debug("NSInfoProvider " + nsProvider + " gave wrong info about " + file.getVirtualFile());
975               continue NextProvider;
976             }
977           }
978           return pairs;
979         }
980       }
981     }
982
983     String namespace = getDtdUri(document);
984
985     if (namespace != null) {
986       boolean overrideNamespaceFromDocType = false;
987
988       if (file != null) {
989         final FileType fileType = file.getFileType();
990         overrideNamespaceFromDocType =
991           fileType == StdFileTypes.HTML || fileType == StdFileTypes.XHTML || fileType == StdFileTypes.JSPX || fileType == StdFileTypes.JSP;
992       }
993
994       if (!overrideNamespaceFromDocType) return new String[][]{new String[]{"", namespace}};
995     }
996
997     if ("taglib".equals(tag.getName())) {
998       return new String[][]{new String[]{"", TAGLIB_1_2_URI}};
999     }
1000
1001     if (file != null) {
1002
1003       final Language language = file.getLanguage();
1004       if (language == HTMLLanguage.INSTANCE || language == XHTMLLanguage.INSTANCE) {
1005         return new String[][]{new String[]{"", XHTML_URI}};
1006         }
1007       }
1008
1009     return null;
1010   }
1011   
1012   @Nullable
1013   public static String getDtdUri(XmlDocument document) {
1014     XmlProlog prolog = document.getProlog();
1015     if (prolog != null) {
1016       return getDtdUri( prolog.getDoctype() );
1017     }
1018     return null;
1019   }
1020
1021   @Nullable
1022   public static String getDtdUri(XmlDoctype doctype) {
1023     if (doctype != null) {
1024       String docType = doctype.getDtdUri();
1025       if (docType == null) {
1026         final String publicId = doctype.getPublicId();
1027         if (PsiTreeUtil.getParentOfType(doctype, XmlDocument.class) instanceof HtmlDocumentImpl &&
1028             publicId != null &&
1029             publicId.indexOf("-//W3C//DTD HTML") != -1) {
1030           return HTML4_LOOSE_URI;
1031         }
1032         else if (HtmlUtil.isHtml5Doctype(doctype)) {
1033           docType = doctype.getLanguage() instanceof HTMLLanguage
1034                     ? Html5SchemaProvider.HTML5_SCHEMA_LOCATION
1035                     : Html5SchemaProvider.XHTML5_SCHEMA_LOCATION;
1036         }
1037       }
1038       return docType;
1039     }
1040     return null;
1041   }
1042
1043   private static void computeTag(XmlTag tag,
1044                                  final Map<String, List<String>> tagsMap,
1045                                  final Map<String, List<MyAttributeInfo>> attributesMap,
1046                                  final boolean processIncludes) {
1047     if (tag == null) {
1048       return;
1049     }
1050     final String tagName = tag.getName();
1051
1052     List<MyAttributeInfo> list = attributesMap.get(tagName);
1053     if (list == null) {
1054       list = new ArrayList<MyAttributeInfo>();
1055       final XmlAttribute[] attributes = tag.getAttributes();
1056       for (final XmlAttribute attribute : attributes) {
1057         list.add(new MyAttributeInfo(attribute.getName()));
1058       }
1059     }
1060     else {
1061       final XmlAttribute[] attributes = tag.getAttributes();
1062       ContainerUtil.sort(list);
1063       Arrays.sort(attributes, new Comparator<XmlAttribute>() {
1064         public int compare(XmlAttribute attr1, XmlAttribute attr2) {
1065           return attr1.getName().compareTo(attr2.getName());
1066         }
1067       });
1068
1069       final Iterator<MyAttributeInfo> iter = list.iterator();
1070       list = new ArrayList<MyAttributeInfo>();
1071       int index = 0;
1072       while (iter.hasNext()) {
1073         final MyAttributeInfo info = iter.next();
1074         boolean requiredFlag = false;
1075         while (attributes.length > index) {
1076           if (info.compareTo(attributes[index]) != 0) {
1077             if (info.compareTo(attributes[index]) < 0) {
1078               break;
1079             }
1080             if (attributes[index].getValue() != null) list.add(new MyAttributeInfo(attributes[index].getName(), false));
1081             index++;
1082           }
1083           else {
1084             requiredFlag = true;
1085             index++;
1086             break;
1087           }
1088         }
1089         info.myRequired &= requiredFlag;
1090         list.add(info);
1091       }
1092       while (attributes.length > index) {
1093         if (attributes[index].getValue() != null) {
1094           list.add(new MyAttributeInfo(attributes[index++].getName(), false));
1095         }
1096         else {
1097           index++;
1098         }
1099       }
1100     }
1101     attributesMap.put(tagName, list);
1102     final List<String> tags = tagsMap.get(tagName) != null ? tagsMap.get(tagName) : new ArrayList<String>();
1103     tagsMap.put(tagName, tags);
1104     PsiFile file = tag.isValid() ? tag.getContainingFile() : null;
1105     processXmlElements(tag, new FilterElementProcessor(XmlTagFilter.INSTANCE) {
1106       public void add(PsiElement element) {
1107         XmlTag tag = (XmlTag)element;
1108         if (!tags.contains(tag.getName())) {
1109           tags.add(tag.getName());
1110         }
1111         computeTag(tag, tagsMap, attributesMap, processIncludes);
1112       }
1113     }, false, false, file, processIncludes);
1114     /*tag.processElements(new FilterElementProcessor(XmlTagFilter.INSTANCE) {
1115       public void add(PsiElement element) {
1116         XmlTag tag = (XmlTag)element;
1117         if (!tags.contains(tag.getName())) {
1118           tags.add(tag.getName());
1119         }
1120         computeTag(tag, tagsMap, attributesMap);
1121       }
1122     }, tag);*/
1123   }
1124
1125   @Nullable
1126   public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag) {
1127     return findXmlDescriptorByType(xmlTag, null);
1128   }
1129
1130   @Nullable
1131   public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag, @Nullable XmlTag context) {
1132     String type = xmlTag.getAttributeValue("type", XML_SCHEMA_INSTANCE_URI);
1133
1134     if (type == null) {
1135       String ns = xmlTag.getNamespace();
1136       if (ourSchemaUrisList.indexOf(ns) >= 0) {
1137         type = xmlTag.getAttributeValue("type", null);
1138       }
1139     }
1140
1141     XmlElementDescriptor elementDescriptor = null;
1142     if (type != null) {
1143       final String namespaceByPrefix = findNamespaceByPrefix(findPrefixByQualifiedName(type), xmlTag);
1144       XmlNSDescriptor typeDecr = xmlTag.getNSDescriptor(namespaceByPrefix, true);
1145
1146       if (typeDecr == null && namespaceByPrefix.length() == 0) {
1147         if (context != null) typeDecr = context.getNSDescriptor("", true);
1148
1149         if (typeDecr == null) {
1150           final PsiFile containingFile = xmlTag.getContainingFile();
1151           if (containingFile instanceof XmlFile) {
1152             final XmlDocument document = ((XmlFile)containingFile).getDocument();
1153             if (document != null) typeDecr = (XmlNSDescriptor)document.getMetaData();
1154           }
1155         }
1156       }
1157
1158       if (typeDecr instanceof XmlNSDescriptorImpl) {
1159         final XmlNSDescriptorImpl schemaDescriptor = (XmlNSDescriptorImpl)typeDecr;
1160         elementDescriptor = schemaDescriptor.getDescriptorByType(type, xmlTag);
1161       }
1162     }
1163
1164     return elementDescriptor;
1165   }
1166
1167   public static boolean collectEnumerationValues(final XmlTag element, final HashSet<String> variants) {
1168     return processEnumerationValues(element, new Processor<XmlTag>() {
1169       public boolean process(XmlTag xmlTag) {
1170         variants.add(xmlTag.getAttributeValue(VALUE_ATTR_NAME));
1171         return true;
1172       }
1173     });
1174   }
1175
1176   public static boolean processEnumerationValues(final XmlTag element, final Processor<XmlTag> tagProcessor) {
1177     boolean exaustiveEnum = true;
1178
1179     for (final XmlTag tag : element.getSubTags()) {
1180       @NonNls final String localName = tag.getLocalName();
1181
1182       if (localName.equals(ENUMERATION_TAG_NAME)) {
1183         final String attributeValue = tag.getAttributeValue(VALUE_ATTR_NAME);
1184         if (attributeValue != null) tagProcessor.process(tag);
1185       }
1186       else if (localName.equals("union")) {
1187         exaustiveEnum = false;
1188         processEnumerationValues(tag, tagProcessor);
1189       }
1190       else if (!localName.equals("annotation")) {
1191         // don't go into annotation
1192         exaustiveEnum &= processEnumerationValues(tag, tagProcessor);
1193       }
1194     }
1195     return exaustiveEnum;
1196   }
1197
1198   /**
1199    *
1200    * @param xmlTag
1201    * @param localName
1202    * @param namespace
1203    * @param bodyText pass null to create collapsed tag, empty string means creating expanded one
1204    * @param enforceNamespacesDeep
1205    * @return
1206    */
1207   public static XmlTag createChildTag(final XmlTag xmlTag,
1208                                       String localName,
1209                                       String namespace,
1210                                       @Nullable String bodyText,
1211                                       boolean enforceNamespacesDeep) {
1212     String qname;
1213     final String prefix = xmlTag.getPrefixByNamespace(namespace);
1214     if (prefix != null && prefix.length() > 0) {
1215       qname = prefix + ":" + localName;
1216     }
1217     else {
1218       qname = localName;
1219     }
1220     try {
1221       @NonNls StringBuilder tagStartBuilder = StringBuilderSpinAllocator.alloc();
1222       String tagStart;
1223       try {
1224         tagStartBuilder.append(qname);
1225         if (!StringUtil.isEmpty(namespace) && xmlTag.getPrefixByNamespace(namespace) == null &&
1226             !(StringUtil.isEmpty(xmlTag.getNamespacePrefix()) && namespace.equals(xmlTag.getNamespace()))) {
1227           tagStartBuilder.append(" xmlns=\"");
1228           tagStartBuilder.append(namespace);
1229           tagStartBuilder.append("\"");
1230         }
1231         tagStart = tagStartBuilder.toString();
1232       }
1233       finally {
1234         StringBuilderSpinAllocator.dispose(tagStartBuilder);
1235       }
1236       Language language = xmlTag.getLanguage();
1237       if (!(language instanceof HTMLLanguage)) language = StdFileTypes.XML.getLanguage();
1238       XmlTag retTag;
1239       if (bodyText != null) {
1240         retTag = XmlElementFactory.getInstance(xmlTag.getProject())
1241           .createTagFromText("<" + tagStart + ">" + bodyText + "</" + qname + ">", language);
1242         if (enforceNamespacesDeep) {
1243           retTag.acceptChildren(new XmlRecursiveElementVisitor() {
1244             @Override
1245             public void visitXmlTag(XmlTag tag) {
1246               final String namespacePrefix = tag.getNamespacePrefix();
1247               if (namespacePrefix.length() == 0) {
1248                 String qname;
1249                 if (prefix != null && prefix.length() > 0) {
1250                   qname = prefix + ":" + tag.getLocalName();
1251                 }
1252                 else {
1253                   qname = tag.getLocalName();
1254                 }
1255                 try {
1256                   tag.setName(qname);
1257                 }
1258                 catch (IncorrectOperationException e) {
1259                   LOG.error(e);
1260                 }
1261               }
1262               super.visitXmlTag(tag);
1263             }
1264           });
1265         }
1266       }
1267       else {
1268         retTag = XmlElementFactory.getInstance(xmlTag.getProject()).createTagFromText("<" + tagStart + "/>", language);
1269       }
1270       return retTag;
1271     }
1272     catch (IncorrectOperationException e) {
1273       LOG.error(e);
1274     }
1275     return null;
1276   }
1277
1278   @Nullable
1279   public static Pair<XmlTagChild, XmlTagChild> findTagChildrenInRange(final PsiFile file, int startOffset, int endOffset) {
1280     PsiElement elementAtStart = file.findElementAt(startOffset);
1281     PsiElement elementAtEnd = file.findElementAt(endOffset - 1);
1282     if (elementAtStart instanceof PsiWhiteSpace) {
1283       startOffset = elementAtStart.getTextRange().getEndOffset();
1284       elementAtStart = file.findElementAt(startOffset);
1285     }
1286     if (elementAtEnd instanceof PsiWhiteSpace) {
1287       endOffset = elementAtEnd.getTextRange().getStartOffset();
1288       elementAtEnd = file.findElementAt(endOffset - 1);
1289     }
1290     if (elementAtStart == null || elementAtEnd == null) return null;
1291
1292     XmlTagChild first = PsiTreeUtil.getParentOfType(elementAtStart, XmlTagChild.class);
1293     if (first == null) return null;
1294
1295     if (first.getTextRange().getStartOffset() != startOffset) {
1296       //Probably 'first' starts with whitespace
1297       PsiElement elementAt = file.findElementAt(first.getTextRange().getStartOffset());
1298       if (!(elementAt instanceof PsiWhiteSpace) || elementAt.getTextRange().getEndOffset() != startOffset) return null;
1299     }
1300
1301     XmlTagChild last = first;
1302     while (last != null && last.getTextRange().getEndOffset() < endOffset) {
1303       last = PsiTreeUtil.getNextSiblingOfType(last, XmlTagChild.class);
1304     }
1305
1306     if (last == null) return null;
1307     if (last.getTextRange().getEndOffset() != elementAtEnd.getTextRange().getEndOffset()) {
1308       //Probably 'last' ends with whitespace
1309       PsiElement elementAt = file.findElementAt(last.getTextRange().getEndOffset() - 1);
1310       if (!(elementAt instanceof PsiWhiteSpace) || elementAt.getTextRange().getStartOffset() != endOffset) {
1311         return null;
1312       }
1313     }
1314
1315     return new Pair<XmlTagChild, XmlTagChild>(first, last);
1316   }
1317
1318   public static boolean isSimpleXmlAttributeValue(final String unquotedValue, final XmlAttributeValue context) {
1319     for (int i = 0; i < unquotedValue.length(); ++i) {
1320       final char ch = unquotedValue.charAt(i);
1321       if (!Character.isJavaIdentifierPart(ch) && ch != ':' && ch != '-') {
1322         final XmlFile file = PsiTreeUtil.getParentOfType(context, XmlFile.class);
1323         return file != null && !tagFromTemplateFramework(file.getRootTag());
1324       }
1325     }
1326     return true;
1327   }
1328
1329   public static boolean toCode(String str) {
1330     for (int i = 0; i < str.length(); i++) {
1331       if (toCode(str.charAt(i))) return true;
1332     }
1333     return false;
1334   }
1335
1336   public static boolean toCode(char ch) {
1337     return "<&>\u00a0".indexOf(ch) >= 0;
1338   }
1339
1340   @Nullable
1341   public static PsiNamedElement findRealNamedElement(@NotNull final PsiNamedElement _element) {
1342     PsiElement currentElement = _element;
1343     final XmlEntityRef lastEntityRef = PsiTreeUtil.getParentOfType(currentElement, XmlEntityRef.class);
1344
1345     while (!(currentElement instanceof XmlFile)) {
1346       PsiElement dependingElement = currentElement.getUserData(XmlElement.DEPENDING_ELEMENT);
1347       if (dependingElement == null) dependingElement = currentElement.getContext();
1348       currentElement = dependingElement;
1349       if (dependingElement == null) break;
1350     }
1351
1352     if (currentElement != null) {
1353       final String name = _element.getName();
1354       if (_element instanceof XmlEntityDecl) {
1355         final XmlEntityDecl cachedEntity = XmlEntityRefImpl.getCachedEntity((PsiFile)currentElement, name);
1356         if (cachedEntity != null) return cachedEntity;
1357       }
1358
1359       final PsiNamedElement[] result = new PsiNamedElement[1];
1360
1361       processXmlElements((XmlFile)currentElement, new PsiElementProcessor() {
1362         public boolean execute(@NotNull final PsiElement element) {
1363           if (element instanceof PsiNamedElement) {
1364             final String elementName = ((PsiNamedElement)element).getName();
1365
1366             if (elementName.equals(name) && _element.getClass().isInstance(element)
1367                 || lastEntityRef != null && element instanceof XmlEntityDecl &&
1368                    elementName.equals(lastEntityRef.getText().substring(1, lastEntityRef.getTextLength() - 1))) {
1369               result[0] = (PsiNamedElement)element;
1370               return false;
1371             }
1372           }
1373
1374           return true;
1375         }
1376       }, true);
1377
1378       return result[0];
1379     }
1380
1381     return null;
1382   }
1383
1384   private static class MyAttributeInfo implements Comparable {
1385     boolean myRequired = true;
1386     String myName = null;
1387
1388     MyAttributeInfo(String name) {
1389       myName = name;
1390     }
1391
1392     MyAttributeInfo(String name, boolean flag) {
1393       myName = name;
1394       myRequired = flag;
1395     }
1396
1397     public int compareTo(Object o) {
1398       if (o instanceof MyAttributeInfo) {
1399         return myName.compareTo(((MyAttributeInfo)o).myName);
1400       }
1401       else if (o instanceof XmlAttribute) {
1402         return myName.compareTo(((XmlAttribute)o).getName());
1403       }
1404       return -1;
1405     }
1406   }
1407
1408   public static String generateDocumentDTD(XmlDocument doc, boolean full) {
1409     final Map<String, List<String>> tags = new LinkedHashMap<String, List<String>>();
1410     final Map<String, List<MyAttributeInfo>> attributes = new LinkedHashMap<String, List<MyAttributeInfo>>();
1411
1412     try {
1413       XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, true);
1414       final XmlTag rootTag = doc.getRootTag();
1415       computeTag(rootTag, tags, attributes, full);
1416
1417       // For supporting not welformed XML
1418       for (PsiElement element = rootTag != null ? rootTag.getNextSibling() : null; element != null; element = element.getNextSibling()) {
1419         if (element instanceof XmlTag) {
1420           computeTag((XmlTag)element, tags, attributes, full);
1421         }
1422       }
1423     }
1424     finally {
1425       XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, false);
1426     }
1427
1428     final StringBuilder buffer = new StringBuilder();
1429     for (final String tagName : tags.keySet()) {
1430       buffer.append(generateElementDTD(tagName, tags.get(tagName), attributes.get(tagName)));
1431     }
1432     return buffer.toString();
1433   }
1434
1435   public static String generateElementDTD(String name, List<String> tags, List<MyAttributeInfo> attributes) {
1436     if (name == null || "".equals(name)) return "";
1437     if (name.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) return "";
1438
1439     @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1440     try {
1441       buffer.append("<!ELEMENT ").append(name).append(" ");
1442       if (tags.isEmpty()) {
1443         buffer.append("(#PCDATA)>\n");
1444       }
1445       else {
1446         buffer.append("(");
1447         final Iterator<String> iter = tags.iterator();
1448         while (iter.hasNext()) {
1449           final String tagName = iter.next();
1450           buffer.append(tagName);
1451           if (iter.hasNext()) {
1452             buffer.append("|");
1453           }
1454           else {
1455             buffer.append(")*");
1456           }
1457         }
1458         buffer.append(">\n");
1459       }
1460       if (!attributes.isEmpty()) {
1461         buffer.append("<!ATTLIST ").append(name);
1462         for (final MyAttributeInfo info : attributes) {
1463           buffer.append("\n    ").append(generateAttributeDTD(info));
1464         }
1465         buffer.append(">\n");
1466       }
1467       return buffer.toString();
1468     }
1469     finally {
1470       StringBuilderSpinAllocator.dispose(buffer);
1471     }
1472   }
1473
1474   private static String generateAttributeDTD(MyAttributeInfo info) {
1475     if (info.myName.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) return "";
1476     @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1477     try {
1478       buffer.append(info.myName).append(" ");
1479       //if ("id".equals(info.myName)) {
1480       //  buffer.append("ID");
1481       //}
1482       //else if ("ref".equals(info.myName)) {
1483       //  buffer.append("IDREF");
1484       //} else {
1485       buffer.append("CDATA");
1486       //}
1487       if (info.myRequired) {
1488         buffer.append(" #REQUIRED");
1489       }
1490       else {
1491         buffer.append(" #IMPLIED");
1492       }
1493       return buffer.toString();
1494     }
1495     finally {
1496       StringBuilderSpinAllocator.dispose(buffer);
1497     }
1498   }
1499
1500   @Nullable
1501   public static String trimLeadingSpacesInMultilineTagValue(@NonNls String tagValue) {
1502     return tagValue == null ? null : tagValue.replaceAll("\n\\s*", "\n");
1503   }
1504
1505   public static String findNamespaceByPrefix(final String prefix, XmlTag contextTag) {
1506     return contextTag.getNamespaceByPrefix(prefix);
1507   }
1508
1509   public static String findPrefixByQualifiedName(String name) {
1510     final int prefixEnd = name.indexOf(':');
1511     if (prefixEnd > 0) {
1512       return name.substring(0, prefixEnd);
1513     }
1514     return "";
1515   }
1516
1517   @Nullable
1518   public static String findLocalNameByQualifiedName(String name) {
1519     return name == null ? null : name.substring(name.indexOf(':') + 1);
1520   }
1521
1522
1523   public static XmlFile getContainingFile(PsiElement element) {
1524     while (!(element instanceof XmlFile) && element != null) {
1525       final PsiElement context = element.getContext();
1526       if (context == null) {
1527         //todo Dmitry Avdeev: either XmlExtension should work on any PsiFile (not just XmlFile), or you need to handle elements from JspJavaFile in some other way
1528         final XmlExtension extension = XmlExtension.getExtensionByElement(element);
1529         if (extension != null) {
1530           element = extension.getContainingFile(element);
1531         }
1532       }
1533       else {
1534         if (element == context) {
1535           LOG.error("Context==element: " + element.getClass());
1536           return null;
1537         }
1538         element = context;
1539       }
1540     }
1541     return (XmlFile)element;
1542   }
1543
1544   @Nullable
1545   public static String getSubTagValue(XmlTag tag, final String subTagName) {
1546     final XmlTag subTag = tag.findFirstSubTag(subTagName);
1547     if (subTag != null) {
1548       return subTag.getValue().getTrimmedText();
1549     }
1550     return null;
1551   }
1552
1553   public static int getStartOffsetInFile(XmlTag xmlTag) {
1554     int off = 0;
1555     while (true) {
1556       off += xmlTag.getStartOffsetInParent();
1557       final PsiElement parent = xmlTag.getParent();
1558       if (!(parent instanceof XmlTag)) break;
1559       xmlTag = (XmlTag)parent;
1560     }
1561     return off;
1562   }
1563
1564   public static XmlElement setNewValue(XmlElement tag, String value) throws IncorrectOperationException {
1565     if (tag instanceof XmlTag) {
1566       ((XmlTag)tag).getValue().setText(value);
1567       return tag;
1568     }
1569     else if (tag instanceof XmlAttribute) {
1570       XmlAttribute attr = (XmlAttribute)tag;
1571       attr.setValue(value);
1572       return attr;
1573     }
1574     else {
1575       throw new IncorrectOperationException();
1576     }
1577   }
1578
1579   public static String decode(@NonNls String text) {
1580     if (text.length() == 0) return text;
1581     if (text.charAt(0) != '&' || text.length() < 3) {
1582       if (text.indexOf('<') < 0 && text.indexOf('>') < 0) return text;
1583       return text.replaceAll("<!\\[CDATA\\[", "").replaceAll("\\]\\]>", "");
1584     }
1585
1586     if (text.equals("&lt;")) {
1587       return "<";
1588     }
1589     if (text.equals("&gt;")) {
1590       return ">";
1591     }
1592     if (text.equals("&nbsp;")) {
1593       return "\u00a0";
1594     }
1595     if (text.equals("&amp;")) {
1596       return "&";
1597     }
1598     if (text.equals("&apos;")) {
1599       return "'";
1600     }
1601     if (text.equals("&quot;")) {
1602       return "\"";
1603     }
1604     if (text.startsWith("&quot;") && text.endsWith("&quot;")) {
1605       return "\"" + text.substring(6, text.length() - 6) + "\"";
1606     }
1607     if (text.startsWith("&#")) {
1608       text = text.substring(3, text.length() - 1);
1609       try {
1610         return String.valueOf((char)Integer.parseInt(text));
1611       }
1612       catch (NumberFormatException e) {
1613         // ignore
1614       }
1615     }
1616
1617     return text;
1618   }
1619
1620   public static String unescape(String text) {
1621     return StringUtil.unescapeXml(text);
1622   }
1623
1624   public static String escape(String text) {
1625     return StringUtil.escapeXml(text);
1626   }
1627
1628   @NonNls private static final String XML_PROLOG_START = "<?xml";
1629   @NonNls private static final byte[] XML_PROLOG_START_BYTES = CharsetToolkit.getUtf8Bytes(XML_PROLOG_START);
1630   @NonNls private static final String ENCODING = "encoding";
1631   @NonNls private static final byte[] ENCODING_BYTES = CharsetToolkit.getUtf8Bytes(ENCODING);
1632   @NonNls private static final String XML_PROLOG_END = "?>";
1633   @NonNls private static final byte[] XML_PROLOG_END_BYTES = CharsetToolkit.getUtf8Bytes(XML_PROLOG_END);
1634
1635   @Nullable
1636   public static String extractXmlEncodingFromProlog(final byte[] content) {
1637     return detect(content);
1638   }
1639
1640   @Nullable
1641   private static String detect(final byte[] bytes) {
1642     int index = 0;
1643     if (CharsetToolkit.hasUTF8Bom(bytes)) {
1644       index = CharsetToolkit.UTF8_BOM.length;
1645     }
1646
1647     index = skipWhiteSpace(index, bytes);
1648     if (!ArrayUtil.startsWith(bytes, index, XML_PROLOG_START_BYTES)) return null;
1649     index += XML_PROLOG_START_BYTES.length;
1650     while (index < bytes.length) {
1651       index = skipWhiteSpace(index, bytes);
1652       if (ArrayUtil.startsWith(bytes, index, XML_PROLOG_END_BYTES)) return null;
1653       if (ArrayUtil.startsWith(bytes, index, ENCODING_BYTES)) {
1654         index += ENCODING_BYTES.length;
1655         index = skipWhiteSpace(index, bytes);
1656         if (index >= bytes.length || bytes[index] != '=') continue;
1657         index++;
1658         index = skipWhiteSpace(index, bytes);
1659         if (index >= bytes.length || bytes[index] != '\'' && bytes[index] != '\"') continue;
1660         byte quote = bytes[index];
1661         index++;
1662         StringBuilder encoding = new StringBuilder();
1663         while (index < bytes.length) {
1664           if (bytes[index] == quote) return encoding.toString();
1665           encoding.append((char)bytes[index++]);
1666         }
1667       }
1668       index++;
1669     }
1670     return null;
1671   }
1672   @Nullable
1673   private static String detect(@NotNull String text) {
1674     int index = 0;
1675
1676     index = skipWhiteSpace(index, text);
1677     if (!StringUtil.startsWith(text, index, XML_PROLOG_START)) return null;
1678     index += XML_PROLOG_START.length();
1679     while (index < text.length()) {
1680       index = skipWhiteSpace(index, text);
1681       if (StringUtil.startsWith(text, index, XML_PROLOG_END)) return null;
1682       if (StringUtil.startsWith(text, index, ENCODING)) {
1683         index += ENCODING.length();
1684         index = skipWhiteSpace(index, text);
1685         if (index >= text.length() || text.charAt(index) != '=') continue;
1686         index++;
1687         index = skipWhiteSpace(index, text);
1688         if (index >= text.length()) continue;
1689         char quote = text.charAt(index);
1690         if (quote != '\'' && quote != '\"') continue;
1691         index++;
1692         StringBuilder encoding = new StringBuilder();
1693         while (index < text.length()) {
1694           char c = text.charAt(index);
1695           if (c == quote) return encoding.toString();
1696           encoding.append(c);
1697           index++;
1698         }
1699       }
1700       index++;
1701     }
1702     return null;
1703   }
1704
1705   private static int skipWhiteSpace(int start, @NotNull byte[] bytes) {
1706     while (start < bytes.length) {
1707       char c = (char)bytes[start];
1708       if (!Character.isWhitespace(c)) break;
1709       start++;
1710     }
1711     return start;
1712   }
1713   private static int skipWhiteSpace(int start, @NotNull String text) {
1714     while (start < text.length()) {
1715       char c = text.charAt(start);
1716       if (!Character.isWhitespace(c)) break;
1717       start++;
1718     }
1719     return start;
1720   }
1721
1722   @Nullable
1723   public static String extractXmlEncodingFromProlog(String text) {
1724     return detect(text);
1725   }
1726
1727   public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1728                                                                 @Nullable @NonNls String[] attributeNames,
1729                                                                 @Nullable ElementFilter elementFilter,
1730                                                                 @NotNull PsiReferenceProvider provider) {
1731     registerXmlAttributeValueReferenceProvider(registrar, attributeNames, elementFilter, true, provider);
1732   }
1733
1734   public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1735                                                                 @Nullable @NonNls String[] attributeNames,
1736                                                                 @Nullable ElementFilter elementFilter,
1737                                                                 boolean caseSensitive,
1738                                                                 @NotNull PsiReferenceProvider provider) {
1739     registerXmlAttributeValueReferenceProvider(registrar, attributeNames, elementFilter, caseSensitive, provider,
1740                                                PsiReferenceRegistrar.DEFAULT_PRIORITY);
1741   }
1742
1743   public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1744                                                                 @Nullable @NonNls String[] attributeNames,
1745                                                                 @Nullable ElementFilter elementFilter,
1746                                                                 boolean caseSensitive,
1747                                                                 @NotNull PsiReferenceProvider provider,
1748                                                                 double priority) {
1749     if (attributeNames == null) {
1750       registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().and(new FilterPattern(elementFilter)), provider, priority);
1751       return;
1752     }
1753
1754     final StringPattern namePattern = caseSensitive
1755                                       ? StandardPatterns.string().oneOf(attributeNames)
1756                                       : StandardPatterns.string().oneOfIgnoreCase(attributeNames);
1757     registrar
1758       .registerReferenceProvider(XmlPatterns.xmlAttributeValue().withLocalName(namePattern).and(new FilterPattern(elementFilter)), provider,
1759                                  priority);
1760   }
1761
1762   public static void registerXmlTagReferenceProvider(PsiReferenceRegistrar registrar,
1763                                                      @NonNls String[] names,
1764                                                      @Nullable ElementFilter elementFilter,
1765                                                      boolean caseSensitive,
1766                                                      @NotNull PsiReferenceProvider provider) {
1767     if (names == null) {
1768       registrar.registerReferenceProvider(XmlPatterns.xmlTag().and(new FilterPattern(elementFilter)), provider,
1769                                           PsiReferenceRegistrar.DEFAULT_PRIORITY);
1770       return;
1771     }
1772
1773
1774     final StringPattern namePattern =
1775       caseSensitive ? StandardPatterns.string().oneOf(names) : StandardPatterns.string().oneOfIgnoreCase(names);
1776     registrar.registerReferenceProvider(XmlPatterns.xmlTag().withLocalName(namePattern).and(new FilterPattern(elementFilter)), provider,
1777                                         PsiReferenceRegistrar.DEFAULT_PRIORITY);
1778   }
1779
1780   public interface DuplicationInfoProvider<T extends PsiElement> {
1781     @Nullable
1782     String getName(@NotNull T t);
1783
1784     @NotNull
1785     String getNameKey(@NotNull T t, @NotNull String name);
1786
1787     @NotNull
1788     PsiElement getNodeForMessage(@NotNull T t);
1789   }
1790 }