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