2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.xml.util;
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;
76 import java.util.regex.Matcher;
81 public class XmlUtil {
82 private static final Logger LOG = Logger.getInstance("#com.intellij.xml.util.XmlUtil");
84 @NonNls public static final String TAGLIB_1_2_URI = "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd";
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";
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";
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";
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};
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";
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};
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";
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";
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"};
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";
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;
163 if ("".equals(namespace)) {
164 final String attributeValue = tag.getAttributeValue("noNamespaceSchemaLocation", XML_SCHEMA_INSTANCE_URI);
165 if (attributeValue != null) return attributeValue;
168 String schemaLocation = tag.getAttributeValue("schemaLocation", XML_SCHEMA_INSTANCE_URI);
169 if (schemaLocation != null) {
170 int start = schemaLocation.indexOf(namespace);
172 start += namespace.length();
173 final StringTokenizer tokenizer = new StringTokenizer(schemaLocation.substring(start + 1));
174 if (tokenizer.hasMoreTokens()) {
175 return tokenizer.nextToken();
183 if (tag.getParent() instanceof XmlTag) {
184 tag = (XmlTag)tag.getParent();
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;
201 for (XmlAttribute attribute : tag.getAttributes()) {
202 if (attribute.getName().startsWith("xmlns:") && attribute.getValue().equals(uri)) {
203 return attribute.getName().substring("xmlns:".length());
205 if ("xmlns".equals(attribute.getName()) && attribute.getValue().equals(uri)) return "";
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();
220 List<String> result = new ArrayList<String>();
222 for (XmlAttribute attribute : attributes) {
223 if (attribute.getName().startsWith("xmlns:") && attribute.getValue().equals(uri)) {
224 result.add(attribute.getName().substring("xmlns:".length()));
226 if ("xmlns".equals(attribute.getName()) && attribute.getValue().equals(uri)) result.add("");
229 return ArrayUtil.toStringArray(result);
233 public static String getXsiNamespace(XmlFile file) {
234 return findNamespacePrefixByURI(file, XML_SCHEMA_INSTANCE_URI);
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);
243 final XmlFile xmlFile = XmlSchemaProvider.findSchema(location, base);
244 return xmlFile == null ? findXmlFile(base, location) : xmlFile;
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);
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;
265 public static XmlFile findXmlFile(PsiFile base, @NotNull String uri) {
266 PsiFile result = null;
268 if (ApplicationManager.getApplication().isUnitTestMode()) {
269 String data = base.getOriginalFile().getUserData(TEST_PATH);
272 String filePath = data + "/" + uri;
273 final VirtualFile path = LocalFileSystem.getInstance().findFileByPath(filePath.replace(File.separatorChar, '/'));
275 result = base.getManager().findFile(path);
279 if (result == null) {
280 result = findRelativeFile(uri, base);
283 if (result instanceof XmlFile) {
284 return (XmlFile)result;
291 public static XmlToken getTokenOfType(PsiElement element, IElementType type) {
292 if (element == null) {
296 PsiElement[] children = element.getChildren();
298 for (PsiElement child : children) {
299 if (child instanceof XmlToken) {
300 XmlToken token = (XmlToken)child;
302 if (token.getTokenType() == type) {
311 public static boolean processXmlElements(XmlElement element, PsiElementProcessor processor, boolean deepFlag) {
312 return processXmlElements(element, processor, deepFlag, false);
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);
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);
329 public static boolean processXmlElementChildren(final XmlElement element, final PsiElementProcessor processor, final boolean deepFlag) {
330 final XmlElementProcessor p = new XmlElementProcessor(processor, element.getContainingFile());
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;
340 public static ParserDefinition.SpaceRequirements canStickTokensTogetherByLexerInXml(final ASTNode left,
344 if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN ||
345 right.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
346 return ParserDefinition.SpaceRequirements.MUST_NOT;
348 if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER && right.getElementType() == XmlTokenType.XML_NAME) {
349 return ParserDefinition.SpaceRequirements.MUST;
351 if (left.getElementType() == XmlTokenType.XML_NAME && right.getElementType() == XmlTokenType.XML_NAME) {
352 return ParserDefinition.SpaceRequirements.MUST;
354 return ParserDefinition.SpaceRequirements.MAY;
357 public static boolean tagFromTemplateFramework(@NotNull final XmlTag tag) {
358 final String ns = tag.getNamespace();
359 return nsFromTemplateFramework(ns);
362 public static boolean nsFromTemplateFramework(final String ns) {
363 return XSLT_URI.equals(ns) || XINCLUDE_URI.equals(ns);
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);
373 text = text.substring(2, text.length() - 1);
376 if (StringUtil.startsWithChar(text, 'x')) {
377 text = text.substring(1);
378 code = Integer.parseInt(text, 16);
381 code = Integer.parseInt(text);
385 catch (NumberFormatException e) {
390 public static boolean attributeFromTemplateFramework(@NonNls final String name, final XmlTag tag) {
391 return "jsfc".equals(name) && tag.getNSDescriptor(JSF_HTML_URI, true) != null;
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;
404 public static XmlTag getSchemaSimpleContent(@NotNull XmlTag tag) {
405 XmlElementDescriptor descriptor = tag.getDescriptor();
407 if (descriptor instanceof XmlElementDescriptorImpl) {
408 final TypeDescriptor type = ((XmlElementDescriptorImpl)descriptor).getType(tag);
410 if (type instanceof ComplexTypeDescriptor) {
411 final XmlTag[] simpleContent = new XmlTag[1];
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();
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;
431 return simpleContent[0];
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;
445 final String nameKey = provider.getNameKey(t, name);
447 if (presentNames.containsKey(nameKey)) {
448 final T psiElement = presentNames.get(nameKey);
449 final String message = XmlBundle.message("duplicate.declaration", nameKey);
451 if (psiElement != null) {
452 presentNames.put(nameKey, null);
454 host.addMessage(provider.getNodeForMessage(psiElement), message, Validator.ValidationHost.ERROR);
457 host.addMessage(provider.getNodeForMessage(t), message, Validator.ValidationHost.ERROR);
460 presentNames.put(nameKey, t);
465 public static String getEntityValue(final XmlEntityRef entityRef) {
466 final XmlEntityDecl decl = entityRef.resolve(entityRef.getContainingFile());
468 final XmlAttributeValue valueElement = decl.getValueElement();
469 if (valueElement != null) {
470 final String value = valueElement.getValue();
476 return entityRef.getText();
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) {
489 VirtualFile vFile = xmlFile.getOriginalFile().getVirtualFile();
490 if (vFile != null && vFile.getUserData(ANT_FILE_SIGN) != null) {
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);
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);
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();
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;
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)) {
547 tag = tag.getParentTag();
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");
558 XmlElementProcessor(PsiElementProcessor _processor, PsiFile _targetFile) {
559 processor = _processor;
560 targetFile = _targetFile;
563 private boolean processXmlElements(PsiElement element, boolean deepFlag, boolean wideFlag) {
564 if (deepFlag) if (!processor.execute(element)) return false;
566 PsiElement startFrom = element.getFirstChild();
568 if (element instanceof XmlEntityRef) {
569 XmlEntityRef ref = (XmlEntityRef)element;
571 //if ("Number.datatype".equals(ref.getText().substring(1, ref.getTextLength() - 1))) {
574 PsiElement newElement = parseEntityRef(targetFile, ref, true);
575 //if (newElement == null) {
576 // System.out.println("No image for :" + ref.getText());
580 while (newElement != null) {
581 if (!processElement(newElement, deepFlag, wideFlag)) return false;
582 newElement = newElement.getNextSibling();
587 else if (element instanceof XmlConditionalSection) {
588 XmlConditionalSection xmlConditionalSection = (XmlConditionalSection)element;
589 if (!xmlConditionalSection.isIncluded(targetFile)) return true;
590 startFrom = xmlConditionalSection.getBodyStart();
592 else if (XmlIncludeHandler.isXInclude(element)) {
593 XmlTag tag = (XmlTag)element;
594 if (!processXInclude(deepFlag, wideFlag, tag)) return false;
597 for (PsiElement child = startFrom; child != null; child = child.getNextSibling()) {
598 if (!processElement(child, deepFlag, wideFlag) && !wideFlag) return false;
604 private boolean processXInclude(final boolean deepFlag, final boolean wideFlag, final XmlTag xincludeTag) {
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);
614 if (inclusion != null) {
615 for (PsiElement psiElement : inclusion) {
616 if (!processElement(psiElement, deepFlag, wideFlag)) return false;
623 private static CachedValueProvider.Result<PsiElement[]> computeInclusion(final XmlTag xincludeTag) {
624 List<Object> deps = new ArrayList<Object>();
625 deps.add(xincludeTag.getContainingFile());
627 final XmlFile xmlFile = XmlIncludeHandler.resolveXIncludeFile(xincludeTag);
629 PsiElement[] result = null;
630 if (xmlFile != null) {
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);
644 }, new PsiElement[includeTag.length]);
649 return new CachedValueProvider.Result<PsiElement[]>(result, deps.toArray());
652 private static XmlTag[] extractXpointer(XmlTag rootTag, final XmlTag xincludeTag) {
653 final String xpointer = xincludeTag.getAttributeValue("xpointer", XINCLUDE_URI);
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);
661 if (matcher.matches()) {
662 final String rootTagName = matcher.group(1);
664 if (rootTagName.equals(rootTag.getName())) return rootTag.getSubTags();
669 return new XmlTag[]{rootTag};
672 private boolean processElement(PsiElement child, boolean deepFlag, boolean wideFlag) {
674 if (!processXmlElements(child, true, wideFlag)) {
679 if (child instanceof XmlEntityRef) {
680 if (!processXmlElements(child, false, wideFlag)) return false;
682 else if (child instanceof XmlConditionalSection) {
683 if (!processXmlElements(child, false, wideFlag)) return false;
685 else if (XmlIncludeHandler.isXInclude(child)) {
686 if (!processXmlElements(child, false, wideFlag)) return false;
688 else if (!processor.execute(child)) return false;
690 if (targetFile != null && child instanceof XmlEntityDecl) {
691 XmlEntityDecl xmlEntityDecl = (XmlEntityDecl)child;
692 XmlEntityRefImpl.cacheParticularEntity(targetFile, xmlEntityDecl);
699 private static PsiElement parseEntityRef(PsiFile targetFile, XmlEntityRef ref, boolean cacheValue) {
700 int type = getContextType(ref);
703 final XmlEntityDecl entityDecl = ref.resolve(targetFile);
704 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
709 if (e.getUserData(XmlElement.INCLUDING_ELEMENT) != null) {
710 e = e.getUserData(XmlElement.INCLUDING_ELEMENT);
711 final PsiFile f = e.getContainingFile();
713 final XmlEntityDecl entityDecl = ref.resolve(targetFile);
714 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
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);
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);
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;
745 else if (temp instanceof XmlElementDecl) {
746 type = XmlEntityDecl.CONTEXT_ELEMENT_CONTENT_SPEC;
748 else if (temp instanceof XmlAttlistDecl) {
749 type = XmlEntityDecl.CONTEXT_ATTLIST_SPEC;
751 else if (temp instanceof XmlEntityDecl) {
752 type = XmlEntityDecl.CONTEXT_ENTITY_DECL_CONTENT;
754 else if (temp instanceof XmlEnumeratedType) {
755 type = XmlEntityDecl.CONTEXT_ENUMERATED_TYPE;
757 else if (temp instanceof XmlAttributeValue) {
758 type = XmlEntityDecl.CONTEXT_ATTR_VALUE;
761 temp = temp.getContext();
769 private static final Key<CachedValue<PsiElement>> PARSED_DECL_KEY = Key.create("PARSED_DECL_KEY");
771 private static PsiElement parseEntityDecl(final XmlEntityDecl entityDecl,
772 final PsiFile targetFile,
775 final XmlEntityRef entityRef) {
776 if (!cacheValue) return entityDecl.parse(targetFile, type, entityRef);
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);
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);
791 entityRef.putUserData(PARSED_DECL_KEY, value);
794 return value.getValue();
799 * add child to the parent according to DTD/Schema element ordering
801 * @return newly added child
803 public static XmlTag addChildTag(XmlTag parent, XmlTag child) throws IncorrectOperationException {
804 return addChildTag(parent, child, -1);
807 public static XmlTag addChildTag(XmlTag parent, XmlTag child, int index) throws IncorrectOperationException {
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);
818 final XmlElementDescriptor parentDescriptor = parent.getDescriptor();
819 final XmlTag[] subTags = parent.getSubTags();
820 if (parentDescriptor == null || subTags.length == 0) return (XmlTag)parent.add(child);
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)) {
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]));
836 return (XmlTag)parent.add(child);
839 public static String getAttributeValue(XmlTag tag, String name) {
840 for (XmlAttribute attribute : tag.getAttributes()) {
841 if (name.equals(attribute.getName())) return attribute.getValue();
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;
856 public static XmlTag findSubTag(XmlTag rootTag, String path) {
857 String[] pathElements = path.split("/");
859 XmlTag curTag = rootTag;
860 for (String curTagName : pathElements) {
861 curTag = curTag.findFirstSubTag(curTagName);
862 if (curTag == null) break;
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)) {
879 // Read the function name and parameter names to find out what this function does... :-)
881 public static XmlTag find(String subTag, String withValue, String forTag, XmlTag insideRoot) {
882 final XmlTag[] forTags = insideRoot.findSubTags(forTag);
884 for (XmlTag tag : forTags) {
885 final XmlTag[] allTags = tag.findSubTags(subTag);
887 for (XmlTag curTag : allTags) {
888 if (curTag.getName().equals(subTag) && curTag.getValue().getTrimmedText().equalsIgnoreCase(withValue)) {
899 public static String[][] getDefaultNamespaces(final XmlDocument document) {
900 final XmlFile file = getContainingFile(document);
902 final XmlTag tag = document.getRootTag();
903 if (tag == null) return null;
906 @NotNull final XmlFileNSInfoProvider[] nsProviders = Extensions.getExtensions(XmlFileNSInfoProvider.EP_NAME);
909 for (XmlFileNSInfoProvider nsProvider : nsProviders) {
910 final String[][] pairs = nsProvider.getDefaultNamespaces(file);
911 if (pairs != null && pairs.length > 0) {
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;
924 String namespace = getDtdUri(document);
926 if (namespace != null) {
927 boolean overrideNamespaceFromDocType = false;
930 final FileType fileType = file.getFileType();
931 overrideNamespaceFromDocType =
932 fileType == StdFileTypes.HTML || fileType == StdFileTypes.XHTML || fileType == StdFileTypes.JSPX || fileType == StdFileTypes.JSP;
935 if (!overrideNamespaceFromDocType) return new String[][]{new String[]{"", namespace}};
938 if ("taglib".equals(tag.getName())) {
939 return new String[][]{new String[]{"", TAGLIB_1_2_URI}};
944 final Language language = file.getLanguage();
945 if (language == HTMLLanguage.INSTANCE || language == XHTMLLanguage.INSTANCE) {
946 return new String[][]{new String[]{"", XHTML_URI}};
953 private static final String HTML5_SCHEMA_LOCATION;
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():"";
962 public static String getDtdUri(XmlDocument document) {
963 XmlProlog prolog = document.getProlog();
964 if (prolog != null) {
965 return getDtdUri( prolog.getDoctype() );
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) {
977 final String publicId = doctype.getPublicId();
978 if (publicId != null && publicId.indexOf("-//W3C//DTD HTML") != -1) {
979 return HTML4_LOOSE_URI;
981 if (publicId == null) docType = HTML5_SCHEMA_LOCATION;
988 private static void computeTag(XmlTag tag,
989 final Map<String, List<String>> tagsMap,
990 final Map<String, List<MyAttributeInfo>> attributesMap) {
994 final String tagName = tag.getName();
996 List<MyAttributeInfo> list = attributesMap.get(tagName);
998 list = new ArrayList<MyAttributeInfo>();
999 final XmlAttribute[] attributes = tag.getAttributes();
1000 for (final XmlAttribute attribute : attributes) {
1001 list.add(new MyAttributeInfo(attribute.getName()));
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());
1013 final Iterator<MyAttributeInfo> iter = list.iterator();
1014 list = new ArrayList<MyAttributeInfo>();
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) {
1024 if (attributes[index].getValue() != null) list.add(new MyAttributeInfo(attributes[index].getName(), false));
1028 requiredFlag = true;
1033 info.myRequired &= requiredFlag;
1036 while (attributes.length > index) {
1037 if (attributes[index].getValue() != null) {
1038 list.add(new MyAttributeInfo(attributes[index++].getName(), false));
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());
1054 computeTag(tag, tagsMap, attributesMap);
1060 public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag) {
1061 return findXmlDescriptorByType(xmlTag, null);
1065 public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag, @Nullable XmlTag context) {
1066 String type = xmlTag.getAttributeValue("type", XML_SCHEMA_INSTANCE_URI);
1069 String ns = xmlTag.getNamespace();
1070 if (XmlUtil.ourSchemaUrisList.indexOf(ns) >= 0) {
1071 type = xmlTag.getAttributeValue("type", null);
1075 XmlElementDescriptor elementDescriptor = null;
1077 final String namespaceByPrefix = findNamespaceByPrefix(findPrefixByQualifiedName(type), xmlTag);
1078 XmlNSDescriptor typeDecr = xmlTag.getNSDescriptor(namespaceByPrefix, true);
1080 if (typeDecr == null && namespaceByPrefix.length() == 0) {
1081 if (context != null) typeDecr = context.getNSDescriptor("", true);
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();
1092 if (typeDecr instanceof XmlNSDescriptorImpl) {
1093 final XmlNSDescriptorImpl schemaDescriptor = (XmlNSDescriptorImpl)typeDecr;
1094 elementDescriptor = schemaDescriptor.getDescriptorByType(type, xmlTag);
1098 return elementDescriptor;
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));
1110 public static boolean processEnumerationValues(final XmlTag element, final Processor<XmlTag> tagProcessor) {
1111 boolean exaustiveEnum = true;
1113 for (final XmlTag tag : element.getSubTags()) {
1114 @NonNls final String localName = tag.getLocalName();
1116 if (localName.equals(ENUMERATION_TAG_NAME)) {
1117 final String attributeValue = tag.getAttributeValue(VALUE_ATTR_NAME);
1118 if (attributeValue != null) tagProcessor.process(tag);
1120 else if (localName.equals("union")) {
1121 exaustiveEnum = false;
1122 processEnumerationValues(tag, tagProcessor);
1124 else if (!localName.equals("annotation")) {
1125 // don't go into annotation
1126 exaustiveEnum &= processEnumerationValues(tag, tagProcessor);
1129 return exaustiveEnum;
1132 public static XmlTag createChildTag(final XmlTag xmlTag,
1136 boolean enforceNamespacesDeep) {
1138 final String prefix = xmlTag.getPrefixByNamespace(namespace);
1139 if (prefix != null && prefix.length() > 0) {
1140 qname = prefix + ":" + localName;
1146 @NonNls StringBuilder tagStartBuilder = StringBuilderSpinAllocator.alloc();
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("\"");
1156 tagStart = tagStartBuilder.toString();
1159 StringBuilderSpinAllocator.dispose(tagStartBuilder);
1161 Language language = xmlTag.getLanguage();
1162 if (!(language instanceof HTMLLanguage)) language = StdFileTypes.XML.getLanguage();
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() {
1170 public void visitXmlTag(XmlTag tag) {
1171 final String namespacePrefix = tag.getNamespacePrefix();
1172 if (namespacePrefix.length() == 0) {
1174 if (prefix != null && prefix.length() > 0) {
1175 qname = prefix + ":" + tag.getLocalName();
1178 qname = tag.getLocalName();
1183 catch (IncorrectOperationException e) {
1187 super.visitXmlTag(tag);
1193 retTag = XmlElementFactory.getInstance(xmlTag.getProject()).createTagFromText("<" + tagStart + "/>", language);
1197 catch (IncorrectOperationException e) {
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);
1211 if (elementAtEnd instanceof PsiWhiteSpace) {
1212 endOffset = elementAtEnd.getTextRange().getStartOffset();
1213 elementAtEnd = file.findElementAt(endOffset - 1);
1215 if (elementAtStart == null || elementAtEnd == null) return null;
1217 XmlTagChild first = PsiTreeUtil.getParentOfType(elementAtStart, XmlTagChild.class);
1218 if (first == null) return null;
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;
1226 XmlTagChild last = first;
1227 while (last != null && last.getTextRange().getEndOffset() < endOffset) {
1228 last = PsiTreeUtil.getNextSiblingOfType(last, XmlTagChild.class);
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) {
1240 return new Pair<XmlTagChild, XmlTagChild>(first, last);
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());
1255 public static boolean toCode(String str) {
1256 for (int i = 0; i < str.length(); i++) {
1257 if (toCode(str.charAt(i))) return true;
1262 public static boolean toCode(char ch) {
1263 return "<&>\u00a0".indexOf(ch) >= 0;
1267 public static PsiNamedElement findRealNamedElement(@NotNull final PsiNamedElement _element) {
1268 PsiElement currentElement = _element;
1269 final XmlEntityRef lastEntityRef = PsiTreeUtil.getParentOfType(currentElement, XmlEntityRef.class);
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;
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;
1285 final PsiNamedElement[] result = new PsiNamedElement[1];
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();
1292 if (elementName.equals(name) && _element.getClass().isInstance(element)) {
1293 result[0] = (PsiNamedElement)element;
1296 else if (lastEntityRef != null &&
1297 element instanceof XmlEntityDecl &&
1298 elementName.equals(lastEntityRef.getText().substring(1, lastEntityRef.getTextLength() - 1))) {
1299 result[0] = (PsiNamedElement)element;
1314 private static class MyAttributeInfo implements Comparable {
1315 boolean myRequired = true;
1316 String myName = null;
1318 MyAttributeInfo(String name) {
1322 MyAttributeInfo(String name, boolean flag) {
1327 public int compareTo(Object o) {
1328 if (o instanceof MyAttributeInfo) {
1329 return myName.compareTo(((MyAttributeInfo)o).myName);
1331 else if (o instanceof XmlAttribute) {
1332 return myName.compareTo(((XmlAttribute)o).getName());
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>>();
1343 XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, true);
1344 final XmlTag rootTag = doc.getRootTag();
1345 computeTag(rootTag, tags, attributes);
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);
1355 XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, false);
1358 final StringBuilder buffer = new StringBuilder();
1359 for (final String tagName : tags.keySet()) {
1360 buffer.append(generateElementDTD(tagName, tags.get(tagName), attributes.get(tagName)));
1362 return buffer.toString();
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 "";
1369 @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1371 buffer.append("<!ELEMENT ").append(name).append(" ");
1372 if (tags.isEmpty()) {
1373 buffer.append("(#PCDATA)>\n");
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()) {
1385 buffer.append(")*");
1388 buffer.append(">\n");
1390 if (!attributes.isEmpty()) {
1391 buffer.append("<!ATTLIST ").append(name);
1392 for (final MyAttributeInfo info : attributes) {
1393 buffer.append("\n ").append(generateAttributeDTD(info));
1395 buffer.append(">\n");
1397 return buffer.toString();
1400 StringBuilderSpinAllocator.dispose(buffer);
1404 private static String generateAttributeDTD(MyAttributeInfo info) {
1405 if (info.myName.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) return "";
1406 @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1408 buffer.append(info.myName).append(" ");
1409 //if ("id".equals(info.myName)) {
1410 // buffer.append("ID");
1412 //else if ("ref".equals(info.myName)) {
1413 // buffer.append("IDREF");
1415 buffer.append("CDATA");
1417 if (info.myRequired) {
1418 buffer.append(" #REQUIRED");
1421 buffer.append(" #IMPLIED");
1423 return buffer.toString();
1426 StringBuilderSpinAllocator.dispose(buffer);
1431 public static String trimLeadingSpacesInMultilineTagValue(@NonNls String tagValue) {
1432 return tagValue == null ? null : tagValue.replaceAll("\n\\s*", "\n");
1435 public static String findNamespaceByPrefix(final String prefix, XmlTag contextTag) {
1436 return contextTag.getNamespaceByPrefix(prefix);
1439 public static String findPrefixByQualifiedName(String name) {
1440 final int prefixEnd = name.indexOf(':');
1441 if (prefixEnd > 0) {
1442 return name.substring(0, prefixEnd);
1448 public static String findLocalNameByQualifiedName(String name) {
1449 return name == null ? null : name.substring(name.indexOf(':') + 1);
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);
1464 if (element == context) {
1465 LOG.error("Context==element: " + element.getClass());
1471 return (XmlFile)element;
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();
1483 public static int getStartOffsetInFile(XmlTag xmlTag) {
1486 off += xmlTag.getStartOffsetInParent();
1487 final PsiElement parent = xmlTag.getParent();
1488 if (!(parent instanceof XmlTag)) break;
1489 xmlTag = (XmlTag)parent;
1494 public static XmlElement setNewValue(XmlElement tag, String value) throws IncorrectOperationException {
1495 if (tag instanceof XmlTag) {
1496 ((XmlTag)tag).getValue().setText(value);
1499 else if (tag instanceof XmlAttribute) {
1500 XmlAttribute attr = (XmlAttribute)tag;
1501 attr.setValue(value);
1505 throw new IncorrectOperationException();
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("\\]\\]>", "");
1516 if (text.equals("<")) {
1519 if (text.equals(">")) {
1522 if (text.equals(" ")) {
1525 if (text.equals("&")) {
1528 if (text.equals("'")) {
1531 if (text.equals(""")) {
1534 if (text.startsWith(""") && text.endsWith(""")) {
1535 return "\"" + text.substring(6, text.length() - 6) + "\"";
1537 if (text.startsWith("&#")) {
1538 text = text.substring(3, text.length() - 1);
1540 return String.valueOf((char)Integer.parseInt(text));
1542 catch (NumberFormatException e) {
1550 public static String unescape(String text) {
1551 return StringUtil.unescapeXml(text);
1554 public static String escape(String text) {
1555 return StringUtil.escapeXml(text);
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("?>");
1563 public static String extractXmlEncodingFromProlog(final byte[] content) {
1564 return detect(content);
1568 private static String detect(final byte[] bytes) {
1570 if (CharsetToolkit.hasUTF8Bom(bytes)) {
1571 index = CharsetToolkit.UTF8_BOM.length;
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;
1585 index = skipWhiteSpace(index, bytes);
1586 if (index >= bytes.length || bytes[index] != '\'' && bytes[index] != '\"') continue;
1587 byte quote = bytes[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++]);
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;
1610 public static String extractXmlEncodingFromProlog(String text) {
1611 return detect(CharsetToolkit.getUtf8Bytes(text));
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);
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);
1630 public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1631 @Nullable @NonNls String[] attributeNames,
1632 @Nullable ElementFilter elementFilter,
1633 boolean caseSensitive,
1634 @NotNull PsiReferenceProvider provider,
1636 if (attributeNames == null) {
1637 registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().and(new FilterPattern(elementFilter)), provider, priority);
1641 final StringPattern namePattern = caseSensitive
1642 ? StandardPatterns.string().oneOf(attributeNames)
1643 : StandardPatterns.string().oneOfIgnoreCase(attributeNames);
1645 .registerReferenceProvider(XmlPatterns.xmlAttributeValue().withLocalName(namePattern).and(new FilterPattern(elementFilter)), provider,
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);
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);
1667 public interface DuplicationInfoProvider<T extends PsiElement> {
1669 String getName(@NotNull T t);
1672 String getNameKey(@NotNull T t, @NotNull String name);
1675 PsiElement getNodeForMessage(@NotNull T t);