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.UriUtil;
23 import com.intellij.lang.ASTNode;
24 import com.intellij.lang.Language;
25 import com.intellij.lang.ParserDefinition;
26 import com.intellij.lang.html.HTMLLanguage;
27 import com.intellij.lang.xhtml.XHTMLLanguage;
28 import com.intellij.lexer.Lexer;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.extensions.Extensions;
32 import com.intellij.openapi.fileTypes.FileType;
33 import com.intellij.openapi.fileTypes.StdFileTypes;
34 import com.intellij.openapi.module.Module;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.util.Key;
37 import com.intellij.openapi.util.NullableComputable;
38 import com.intellij.openapi.util.Pair;
39 import com.intellij.openapi.util.RecursionManager;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.openapi.vfs.CharsetToolkit;
42 import com.intellij.openapi.vfs.LocalFileSystem;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.patterns.StandardPatterns;
45 import com.intellij.patterns.StringPattern;
46 import com.intellij.patterns.XmlPatterns;
47 import com.intellij.psi.*;
48 import com.intellij.psi.XmlElementFactory;
49 import com.intellij.psi.codeStyle.CodeStyleManager;
50 import com.intellij.psi.filters.ElementFilter;
51 import com.intellij.psi.filters.XmlTagFilter;
52 import com.intellij.psi.filters.position.FilterPattern;
53 import com.intellij.psi.impl.source.html.HtmlDocumentImpl;
54 import com.intellij.psi.impl.source.tree.CompositeElement;
55 import com.intellij.psi.impl.source.tree.LeafElement;
56 import com.intellij.psi.impl.source.xml.XmlEntityRefImpl;
57 import com.intellij.psi.scope.processor.FilterElementProcessor;
58 import com.intellij.psi.search.PsiElementProcessor;
59 import com.intellij.psi.tree.IElementType;
60 import com.intellij.psi.util.*;
61 import com.intellij.psi.xml.*;
62 import com.intellij.util.*;
63 import com.intellij.util.containers.ContainerUtil;
64 import com.intellij.util.xmlb.JDOMXIncluder;
65 import com.intellij.xml.*;
66 import com.intellij.xml.impl.schema.ComplexTypeDescriptor;
67 import com.intellij.xml.impl.schema.TypeDescriptor;
68 import com.intellij.xml.impl.schema.XmlElementDescriptorImpl;
69 import com.intellij.xml.impl.schema.XmlNSDescriptorImpl;
70 import com.intellij.xml.index.IndexedRelevantResource;
71 import com.intellij.xml.index.XmlNamespaceIndex;
72 import org.jetbrains.annotations.NonNls;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
78 import java.util.regex.Matcher;
83 public class XmlUtil {
84 private static final Logger LOG = Logger.getInstance("#com.intellij.xml.util.XmlUtil");
86 @NonNls public static final String TAGLIB_1_2_URI = "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd";
88 @NonNls public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema";
89 @NonNls public static final String XML_SCHEMA_URI2 = "http://www.w3.org/1999/XMLSchema";
90 @NonNls public static final String XML_SCHEMA_URI3 = "http://www.w3.org/2000/10/XMLSchema";
91 public static final String[] SCHEMA_URIS = {XML_SCHEMA_URI, XML_SCHEMA_URI2, XML_SCHEMA_URI3};
92 @NonNls public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance";
94 @NonNls public static final String XSLT_URI = "http://www.w3.org/1999/XSL/Transform";
95 @NonNls public static final String XINCLUDE_URI = "http://www.w3.org/2001/XInclude";
97 @NonNls public static final String ANT_URI = "http://ant.apache.org/schema.xsd";
98 @NonNls public static final String XHTML_URI = "http://www.w3.org/1999/xhtml";
99 @NonNls public static final String HTML_URI = "http://www.w3.org/1999/html";
100 @NonNls public static final String EMPTY_URI = "";
101 @NonNls public static final Key<String> TEST_PATH = Key.create("TEST PATH");
102 @NonNls public static final String JSP_URI = "http://java.sun.com/JSP/Page";
103 @NonNls public static final String ANY_URI = "http://www.intellij.net/ns/any";
105 @NonNls public static final String JSTL_CORE_URI = "http://java.sun.com/jsp/jstl/core";
106 @NonNls public static final String JSTL_CORE_URI2 = "http://java.sun.com/jstl/core";
107 @NonNls public static final String JSTL_CORE_URI3 = "http://java.sun.com/jstl/core_rt";
108 @NonNls public static final String[] JSTL_CORE_URIS = {JSTL_CORE_URI, JSTL_CORE_URI2, JSTL_CORE_URI3};
110 @NonNls public static final String JSF_HTML_URI = "http://java.sun.com/jsf/html";
111 @NonNls public static final String JSF_CORE_URI = "http://java.sun.com/jsf/core";
113 @NonNls private static final String JSTL_FORMAT_URI = "http://java.sun.com/jsp/jstl/fmt";
114 @NonNls private static final String JSTL_FORMAT_URI2 = "http://java.sun.com/jstl/fmt";
115 @NonNls private static final String JSTL_FORMAT_URI3 = "http://java.sun.com/jstl/fmt_rt";
116 @NonNls public static final String[] JSTL_FORMAT_URIS = {JSTL_FORMAT_URI, JSTL_FORMAT_URI2, JSTL_FORMAT_URI3};
118 @NonNls public static final String SPRING_URI = "http://www.springframework.org/tags";
119 @NonNls public static final String SPRING_FORMS_URI = "http://www.springframework.org/tags/form";
120 @NonNls public static final String STRUTS_BEAN_URI = "http://struts.apache.org/tags-bean";
121 @NonNls public static final String STRUTS_BEAN_URI2 = "http://jakarta.apache.org/struts/tags-bean";
122 @NonNls public static final String APACHE_I18N_URI = "http://jakarta.apache.org/taglibs/i18n-1.0";
123 @NonNls public static final String STRUTS_LOGIC_URI = "http://struts.apache.org/tags-logic";
124 @NonNls public static final String STRUTS_HTML_URI = "http://struts.apache.org/tags-html";
125 @NonNls public static final String STRUTS_HTML_URI2 = "http://jakarta.apache.org/struts/tags-html";
127 @NonNls public static final String APACHE_TRINIDAD_URI = "http://myfaces.apache.org/trinidad";
128 @NonNls public static final String APACHE_TRINIDAD_HTML_URI = "http://myfaces.apache.org/trinidad/html";
131 @NonNls public static final String[] HIBERNATE_URIS =
132 {"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd", "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"};
134 @NonNls public static final String XSD_SIMPLE_CONTENT_TAG = "simpleContent";
135 @NonNls public static final String NO_NAMESPACE_SCHEMA_LOCATION_ATT = "noNamespaceSchemaLocation";
136 @NonNls public static final String SCHEMA_LOCATION_ATT = "schemaLocation";
137 @NonNls public static final String[] WEB_XML_URIS =
138 {"http://java.sun.com/xml/ns/j2ee", "http://java.sun.com/xml/ns/javaee", "http://java.sun.com/dtd/web-app_2_3.dtd",
139 "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"};
140 @NonNls public static final String FACELETS_URI = "http://java.sun.com/jsf/facelets";
141 @NonNls public static final String JSTL_FUNCTIONS_URI = "http://java.sun.com/jsp/jstl/functions";
142 @NonNls public static final String JSTL_FUNCTIONS_URI2 = "http://java.sun.com/jstl/functions";
143 @NonNls public static final String JSTL_FN_FACELET_URI = "com.sun.facelets.tag.jstl.fn.JstlFnLibrary";
144 @NonNls public static final String JSTL_CORE_FACELET_URI = "com.sun.facelets.tag.jstl.core.JstlCoreLibrary";
145 @NonNls public static final String TARGET_NAMESPACE_ATTR_NAME = "targetNamespace";
146 @NonNls public static final String XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
147 public static final List<String> ourSchemaUrisList = Arrays.asList(SCHEMA_URIS);
148 public static final Key<Boolean> ANT_FILE_SIGN = new Key<Boolean>("FORCED ANT FILE");
149 @NonNls public static final String TAG_DIR_NS_PREFIX = "urn:jsptagdir:";
150 @NonNls public static final String VALUE_ATTR_NAME = "value";
151 @NonNls public static final String ENUMERATION_TAG_NAME = "enumeration";
152 @NonNls public static final String HTML4_LOOSE_URI = "http://www.w3.org/TR/html4/loose.dtd";
153 @NonNls public static final String WSDL_SCHEMA_URI = "http://schemas.xmlsoap.org/wsdl/";
154 public static final Key<PsiAnchor> ORIGINAL_ELEMENT = Key.create("ORIGINAL_ELEMENT");
161 public static String getSchemaLocation(XmlTag tag, String namespace) {
162 final String uri = ExternalResourceManagerEx.getInstanceEx().getResourceLocation(namespace, tag.getProject());
163 if (uri != null && !uri.equals(namespace)) return uri;
166 if ("".equals(namespace)) {
167 final String attributeValue = tag.getAttributeValue("noNamespaceSchemaLocation", XML_SCHEMA_INSTANCE_URI);
168 if (attributeValue != null) return attributeValue;
171 String schemaLocation = tag.getAttributeValue("schemaLocation", XML_SCHEMA_INSTANCE_URI);
172 if (schemaLocation != null) {
173 int start = schemaLocation.indexOf(namespace);
175 start += namespace.length();
176 final StringTokenizer tokenizer = new StringTokenizer(schemaLocation.substring(start + 1));
177 if (tokenizer.hasMoreTokens()) {
178 return tokenizer.nextToken();
186 if (tag.getParent() instanceof XmlTag) {
187 tag = (XmlTag)tag.getParent();
197 public static String findNamespacePrefixByURI(XmlFile file, @NonNls String uri) {
198 final XmlTag tag = file.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(@NotNull PsiFile base, @NotNull String nsLocation) {
239 final String location = ExternalResourceManager.getInstance().getResourceLocation(nsLocation, base.getProject());
240 if (!location.equals(nsLocation)) { // is mapped
241 return findXmlFile(base, location);
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 processXmlElements(element, processor, deepFlag, wideFlag, baseFile, true);
329 public static boolean processXmlElements(final XmlElement element,
330 final PsiElementProcessor processor,
331 final boolean deepFlag,
332 final boolean wideFlag,
333 final PsiFile baseFile,
334 boolean processIncludes) {
335 return new XmlElementProcessor(processor, baseFile).processXmlElements(element, deepFlag, wideFlag, processIncludes);
338 public static boolean processXmlElementChildren(final XmlElement element, final PsiElementProcessor processor, final boolean deepFlag) {
339 final XmlElementProcessor p = new XmlElementProcessor(processor, element.getContainingFile());
341 final boolean wideFlag = false;
342 for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
343 if (!p.processElement(child, deepFlag, wideFlag, true) && !wideFlag) return false;
349 public static ParserDefinition.SpaceRequirements canStickTokensTogetherByLexerInXml(final ASTNode left,
353 if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN ||
354 right.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
355 return ParserDefinition.SpaceRequirements.MUST_NOT;
357 if (left.getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER && right.getElementType() == XmlTokenType.XML_NAME) {
358 return ParserDefinition.SpaceRequirements.MUST;
360 if (left.getElementType() == XmlTokenType.XML_NAME && right.getElementType() == XmlTokenType.XML_NAME) {
361 return ParserDefinition.SpaceRequirements.MUST;
363 return ParserDefinition.SpaceRequirements.MAY;
366 public static boolean tagFromTemplateFramework(@NotNull final XmlTag tag) {
367 final String ns = tag.getNamespace();
368 return nsFromTemplateFramework(ns);
371 public static boolean nsFromTemplateFramework(final String ns) {
372 return XSLT_URI.equals(ns) || XINCLUDE_URI.equals(ns);
376 public static char getCharFromEntityRef(@NonNls String text) {
377 //LOG.assertTrue(text.startsWith("&#") && text.endsWith(";"));
378 if (text.charAt(1) != '#') {
379 text = text.substring(1, text.length() - 1);
380 return XmlTagUtil.getCharacterByEntityName(text);
382 text = text.substring(2, text.length() - 1);
385 if (StringUtil.startsWithChar(text, 'x')) {
386 text = text.substring(1);
387 code = Integer.parseInt(text, 16);
390 code = Integer.parseInt(text);
394 catch (NumberFormatException e) {
399 public static boolean attributeFromTemplateFramework(@NonNls final String name, final XmlTag tag) {
400 return "jsfc".equals(name) && tag.getNSDescriptor(JSF_HTML_URI, true) != null;
404 public static String getTargetSchemaNsFromTag(@Nullable final XmlTag xmlTag) {
405 if (xmlTag == null) return null;
406 String targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI);
407 if (targetNamespace == null) targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI2);
408 if (targetNamespace == null) targetNamespace = xmlTag.getAttributeValue(TARGET_NAMESPACE_ATTR_NAME, XML_SCHEMA_URI3);
409 return targetNamespace;
413 public static XmlTag getSchemaSimpleContent(@NotNull XmlTag tag) {
414 XmlElementDescriptor descriptor = tag.getDescriptor();
416 if (descriptor instanceof XmlElementDescriptorImpl) {
417 final TypeDescriptor type = ((XmlElementDescriptorImpl)descriptor).getType(tag);
419 if (type instanceof ComplexTypeDescriptor) {
420 final XmlTag[] simpleContent = new XmlTag[1];
422 processXmlElements(((ComplexTypeDescriptor)type).getDeclaration(), new PsiElementProcessor() {
423 public boolean execute(@NotNull final PsiElement element) {
424 if (element instanceof XmlTag) {
425 final XmlTag tag = (XmlTag)element;
426 @NonNls final String s = ((XmlTag)element).getLocalName();
428 if ((s.equals(XSD_SIMPLE_CONTENT_TAG) ||
429 s.equals("restriction") && "string".equals(findLocalNameByQualifiedName(tag.getAttributeValue("base")))) &&
430 tag.getNamespace().equals(XML_SCHEMA_URI)) {
431 simpleContent[0] = tag;
440 return simpleContent[0];
446 public static <T extends PsiElement> void doDuplicationCheckForElements(final T[] elements,
447 final Map<String, T> presentNames,
448 DuplicationInfoProvider<T> provider,
449 final Validator.ValidationHost host) {
450 for (T t : elements) {
451 final String name = provider.getName(t);
452 if (name == null) continue;
454 final String nameKey = provider.getNameKey(t, name);
456 if (presentNames.containsKey(nameKey)) {
457 final T psiElement = presentNames.get(nameKey);
458 final String message = XmlBundle.message("duplicate.declaration", nameKey);
460 if (psiElement != null) {
461 presentNames.put(nameKey, null);
463 host.addMessage(provider.getNodeForMessage(psiElement), message, Validator.ValidationHost.ERROR);
466 host.addMessage(provider.getNodeForMessage(t), message, Validator.ValidationHost.ERROR);
469 presentNames.put(nameKey, t);
474 public static String getEntityValue(final XmlEntityRef entityRef) {
475 final XmlEntityDecl decl = entityRef.resolve(entityRef.getContainingFile());
477 final XmlAttributeValue valueElement = decl.getValueElement();
478 if (valueElement != null) {
479 final String value = valueElement.getValue();
485 return entityRef.getText();
488 public static boolean isAntFile(final PsiFile file) {
489 if (file instanceof XmlFile) {
490 final XmlFile xmlFile = (XmlFile)file;
491 final XmlDocument document = xmlFile.getDocument();
492 if (document != null) {
493 final XmlTag tag = document.getRootTag();
494 if (tag != null && "project".equals(tag.getName()) && tag.getContext() instanceof XmlDocument) {
495 if (tag.getAttributeValue("default") != null) {
498 VirtualFile vFile = xmlFile.getOriginalFile().getVirtualFile();
499 if (vFile != null && vFile.getUserData(ANT_FILE_SIGN) != null) {
509 public static PsiFile findRelativeFile(String uri, PsiElement base) {
510 if (base instanceof PsiFile) {
511 PsiFile baseFile = (PsiFile)base;
512 VirtualFile file = UriUtil.findRelative(uri, baseFile.getOriginalFile());
513 if (file == null) return null;
514 return base.getManager().findFile(file);
516 else if (base instanceof PsiDirectory) {
517 PsiDirectory baseDir = (PsiDirectory)base;
518 VirtualFile file = UriUtil.findRelative(uri, baseDir);
519 if (file == null) return null;
520 return base.getManager().findFile(file);
527 public static String getCommentText(XmlComment comment) {
528 final PsiElement firstChild = comment.getFirstChild();
529 if (firstChild != null) {
530 final PsiElement nextSibling = firstChild.getNextSibling();
531 if (nextSibling instanceof XmlToken) {
532 final XmlToken token = (XmlToken)nextSibling;
533 if (token.getTokenType() == XmlTokenType.XML_COMMENT_CHARACTERS) {
534 return token.getText();
542 public static PsiElement findNamespaceDeclaration(XmlElement xmlElement, String nsName) {
543 while (! (xmlElement instanceof XmlTag) && xmlElement != null) {
544 final PsiElement parent = xmlElement.getParent();
545 if (!(parent instanceof XmlElement)) return null;
546 xmlElement = (XmlElement)parent;
548 if (xmlElement != null) {
549 XmlTag tag = (XmlTag)xmlElement;
550 while (tag != null) {
551 for (XmlAttribute attribute : tag.getAttributes()) {
552 if (attribute.isNamespaceDeclaration() && attribute.getLocalName().equals(nsName)) {
556 tag = tag.getParentTag();
562 public static void reformatTagStart(XmlTag tag) {
563 ASTNode child = XmlChildRole.START_TAG_END_FINDER.findChild(tag.getNode());
565 CodeStyleManager.getInstance(tag.getProject()).reformat(tag);
568 CodeStyleManager.getInstance(tag.getProject()).reformatRange(tag, tag.getTextRange().getStartOffset(), child.getTextRange().getEndOffset());
573 public static XmlElementDescriptor getDescriptorFromContext(@NotNull XmlTag tag) {
574 PsiElement parent = tag.getParent();
575 if (parent instanceof XmlTag) {
576 XmlTag parentTag = (XmlTag)parent;
577 final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
579 if (parentDescriptor != null) {
580 return XmlExtension.getExtension(tag.getContainingFile()).getElementDescriptor(tag, parentTag, parentDescriptor);
586 public static void expandTag(@NotNull XmlTag tag) {
587 XmlTag newTag = XmlElementFactory.getInstance(tag.getProject()).createTagFromText('<' + tag.getName() + "></" + tag.getName() + '>');
589 ASTNode node = tag.getNode();
590 if (!(node instanceof CompositeElement)) return;
591 CompositeElement compositeElement = (CompositeElement)node;
593 final LeafElement emptyTagEnd = (LeafElement)XmlChildRole.EMPTY_TAG_END_FINDER.findChild(compositeElement);
594 if (emptyTagEnd == null) return;
596 compositeElement.removeChild(emptyTagEnd);
597 PsiElement[] children = newTag.getChildren();
599 compositeElement.addChildren(children[2].getNode(), null, null);
602 //public static void expandTag(@NotNull XmlTag tag) {
603 // final LeafElement emptyTagEnd = (LeafElement)XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tag.getNode());
604 // if (emptyTagEnd == null) return;
606 // PsiFile file = tag.getContainingFile().getOriginalFile();
608 // TextRange textRange = emptyTagEnd.getTextRange();
609 // Document document = file.getViewProvider().getDocument();
610 // if (document == null) return;
612 // PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
613 // document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), "></" + tag.getName() + '>');
614 // PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
617 private static class XmlElementProcessor {
618 private final PsiElementProcessor processor;
619 private final PsiFile targetFile;
621 XmlElementProcessor(PsiElementProcessor _processor, PsiFile _targetFile) {
622 processor = _processor;
623 targetFile = _targetFile;
626 private boolean processXmlElements(PsiElement element, boolean deepFlag, boolean wideFlag, boolean processIncludes) {
627 if (deepFlag) if (!processor.execute(element)) return false;
629 PsiElement startFrom = element.getFirstChild();
631 if (element instanceof XmlEntityRef) {
632 XmlEntityRef ref = (XmlEntityRef)element;
634 //if ("Number.datatype".equals(ref.getText().substring(1, ref.getTextLength() - 1))) {
637 PsiElement newElement = parseEntityRef(targetFile, ref, true);
638 //if (newElement == null) {
639 // System.out.println("No image for :" + ref.getText());
643 while (newElement != null) {
644 if (!processElement(newElement, deepFlag, wideFlag, processIncludes)) return false;
645 newElement = newElement.getNextSibling();
650 else if (element instanceof XmlConditionalSection) {
651 XmlConditionalSection xmlConditionalSection = (XmlConditionalSection)element;
652 if (!xmlConditionalSection.isIncluded(targetFile)) return true;
653 startFrom = xmlConditionalSection.getBodyStart();
655 else if (processIncludes && XmlIncludeHandler.isXInclude(element)) {
656 XmlTag tag = (XmlTag)element;
657 if (!processXInclude(deepFlag, wideFlag, tag)) return false;
660 for (PsiElement child = startFrom; child != null; child = child.getNextSibling()) {
661 if (!processElement(child, deepFlag, wideFlag, processIncludes) && !wideFlag) return false;
667 private boolean processXInclude(final boolean deepFlag, final boolean wideFlag, @NotNull final XmlTag xincludeTag) {
669 final PsiElement[] inclusion = CachedValuesManager.getManager(xincludeTag.getProject()).getCachedValue(xincludeTag, new CachedValueProvider<PsiElement[]>() {
670 public Result<PsiElement[]> compute() {
671 PsiElement[] result = RecursionManager.doPreventingRecursion(xincludeTag, true, new NullableComputable<PsiElement[]>() {
673 public PsiElement[] compute() {
674 return computeInclusion(xincludeTag);
677 return Result.create(result, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
681 if (inclusion != null) {
682 for (PsiElement psiElement : inclusion) {
683 if (!processElement(psiElement, deepFlag, wideFlag, true)) return false;
691 private static PsiElement[] computeInclusion(final XmlTag xincludeTag) {
692 final XmlFile included = XmlIncludeHandler.resolveXIncludeFile(xincludeTag);
693 final XmlDocument document = included != null ? included.getDocument() : null;
694 final XmlTag rootTag = document != null ? document.getRootTag() : null;
695 if (rootTag != null) {
696 final String xpointer = xincludeTag.getAttributeValue("xpointer", XINCLUDE_URI);
697 final XmlTag[] includeTag = extractXpointer(rootTag, xpointer);
698 PsiElement[] result = new PsiElement[includeTag.length];
699 for (int i = 0; i < includeTag.length; i++) {
700 XmlTag xmlTag = includeTag[i];
701 final PsiElement psiElement = xmlTag.copy();
702 psiElement.putUserData(XmlElement.INCLUDING_ELEMENT, xincludeTag.getParentTag());
703 psiElement.putUserData(ORIGINAL_ELEMENT, PsiAnchor.create(xmlTag));
704 result[i] = psiElement;
712 private static XmlTag[] extractXpointer(XmlTag rootTag, @Nullable final String xpointer) {
714 if (xpointer != null) {
715 Matcher matcher = JDOMXIncluder.XPOINTER_PATTERN.matcher(xpointer);
716 if (matcher.matches()) {
717 String pointer = matcher.group(1);
718 matcher = JDOMXIncluder.CHILDREN_PATTERN.matcher(pointer);
720 if (matcher.matches()) {
721 final String rootTagName = matcher.group(1);
723 if (rootTagName.equals(rootTag.getName())) return rootTag.getSubTags();
728 return new XmlTag[]{rootTag};
731 private boolean processElement(PsiElement child, boolean deepFlag, boolean wideFlag, boolean processIncludes) {
733 if (!processXmlElements(child, true, wideFlag, processIncludes)) {
738 if (child instanceof XmlEntityRef) {
739 if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
741 else if (child instanceof XmlConditionalSection) {
742 if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
744 else if (processIncludes && XmlIncludeHandler.isXInclude(child)) {
745 if (!processXmlElements(child, false, wideFlag, processIncludes)) return false;
747 else if (!processor.execute(child)) return false;
749 if (targetFile != null && child instanceof XmlEntityDecl) {
750 XmlEntityDecl xmlEntityDecl = (XmlEntityDecl)child;
751 XmlEntityRefImpl.cacheParticularEntity(targetFile, xmlEntityDecl);
758 private static PsiElement parseEntityRef(PsiFile targetFile, XmlEntityRef ref, boolean cacheValue) {
759 int type = getContextType(ref);
762 final XmlEntityDecl entityDecl = ref.resolve(targetFile);
763 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
768 if (e.getUserData(XmlElement.INCLUDING_ELEMENT) != null) {
769 e = e.getUserData(XmlElement.INCLUDING_ELEMENT);
770 final PsiFile f = e.getContainingFile();
772 final XmlEntityDecl entityDecl = ref.resolve(targetFile);
773 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
778 if (e instanceof PsiFile) {
779 PsiFile refFile = (PsiFile)e;
780 final XmlEntityDecl entityDecl = ref.resolve(refFile);
781 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
788 final PsiElement element = ref.getUserData(XmlElement.DEPENDING_ELEMENT);
789 if (element instanceof XmlFile) {
790 final XmlEntityDecl entityDecl = ref.resolve((PsiFile)element);
791 if (entityDecl != null) return parseEntityDecl(entityDecl, targetFile, type, cacheValue, ref);
797 private static int getContextType(XmlEntityRef ref) {
798 int type = XmlEntityDecl.CONTEXT_GENERIC_XML;
799 PsiElement temp = ref;
800 while (temp != null) {
801 if (temp instanceof XmlAttributeDecl) {
802 type = XmlEntityDecl.CONTEXT_ATTRIBUTE_SPEC;
804 else if (temp instanceof XmlElementDecl) {
805 type = XmlEntityDecl.CONTEXT_ELEMENT_CONTENT_SPEC;
807 else if (temp instanceof XmlAttlistDecl) {
808 type = XmlEntityDecl.CONTEXT_ATTLIST_SPEC;
810 else if (temp instanceof XmlEntityDecl) {
811 type = XmlEntityDecl.CONTEXT_ENTITY_DECL_CONTENT;
813 else if (temp instanceof XmlEnumeratedType) {
814 type = XmlEntityDecl.CONTEXT_ENUMERATED_TYPE;
816 else if (temp instanceof XmlAttributeValue) {
817 type = XmlEntityDecl.CONTEXT_ATTR_VALUE;
820 temp = temp.getContext();
828 private static final Key<CachedValue<PsiElement>> PARSED_DECL_KEY = Key.create("PARSED_DECL_KEY");
830 private static PsiElement parseEntityDecl(final XmlEntityDecl entityDecl,
831 final PsiFile targetFile,
834 final XmlEntityRef entityRef) {
835 if (!cacheValue) return entityDecl.parse(targetFile, type, entityRef);
837 synchronized (PsiLock.LOCK) { // we depend on targetFile and entityRef
838 CachedValue<PsiElement> value = entityRef.getUserData(PARSED_DECL_KEY);
839 // return entityDecl.parse(targetFile, type);
842 value = CachedValuesManager.getManager(entityDecl.getProject()).createCachedValue(new CachedValueProvider<PsiElement>() {
843 public Result<PsiElement> compute() {
844 final PsiElement res = entityDecl.parse(targetFile, type, entityRef);
845 if (res == null) return new Result<PsiElement>(res, targetFile);
846 if (!entityDecl.isInternalReference()) XmlEntityRefImpl.copyEntityCaches(res.getContainingFile(), targetFile);
847 return new Result<PsiElement>(res, res.getUserData(XmlElement.DEPENDING_ELEMENT), entityDecl, targetFile, entityRef);
850 entityRef.putUserData(PARSED_DECL_KEY, value);
853 return value.getValue();
858 * add child to the parent according to DTD/Schema element ordering
860 * @return newly added child
862 public static XmlTag addChildTag(XmlTag parent, XmlTag child) throws IncorrectOperationException {
863 return addChildTag(parent, child, -1);
866 public static XmlTag addChildTag(XmlTag parent, XmlTag child, int index) throws IncorrectOperationException {
868 // bug in PSI: cannot add child to <tag/>
869 if (parent.getSubTags().length == 0 && parent.getText().endsWith("/>")) {
870 final XmlElementFactory factory = XmlElementFactory.getInstance(parent.getProject());
871 final String name = parent.getName();
872 final String text = parent.getText();
873 final XmlTag tag = factory.createTagFromText(text.substring(0, text.length() - 2) + "></" + name + ">");
874 parent = (XmlTag)parent.replace(tag);
877 final XmlElementDescriptor parentDescriptor = parent.getDescriptor();
878 final XmlTag[] subTags = parent.getSubTags();
879 if (parentDescriptor == null || subTags.length == 0) return (XmlTag)parent.add(child);
882 for (XmlElementDescriptor childElementDescriptor : parentDescriptor.getElementsDescriptors(parent)) {
883 final String childElementName = childElementDescriptor.getName();
884 int prevSubTagNum = subTagNum;
885 while (subTagNum < subTags.length - 1 && subTags[subTagNum + 1].getName().equals(childElementName)) {
888 if (childElementName.equals(child.getLocalName())) {
889 // insert child just after anchor
890 // insert into the position specified by index
891 subTagNum = index == -1 || index > subTagNum - prevSubTagNum ? subTagNum : prevSubTagNum + index;
892 return (XmlTag)(subTagNum == -1 ? parent.addBefore(child, subTags[0]) : parent.addAfter(child, subTags[subTagNum]));
895 return (XmlTag)parent.add(child);
898 public static String getAttributeValue(XmlTag tag, String name) {
899 for (XmlAttribute attribute : tag.getAttributes()) {
900 if (name.equals(attribute.getName())) return attribute.getValue();
905 public static XmlTag findOnAnyLevel(XmlTag root, String[] chain) {
906 XmlTag curTag = root;
907 for (String s : chain) {
908 curTag = curTag.findFirstSubTag(s);
909 if (curTag == null) return null;
915 public static XmlTag findSubTag(XmlTag rootTag, String path) {
916 String[] pathElements = path.split("/");
918 XmlTag curTag = rootTag;
919 for (String curTagName : pathElements) {
920 curTag = curTag.findFirstSubTag(curTagName);
921 if (curTag == null) break;
927 public static XmlTag findSubTagWithValue(XmlTag rootTag, String tagName, String tagValue) {
928 if (rootTag == null) return null;
929 final XmlTag[] subTags = rootTag.findSubTags(tagName);
930 for (XmlTag subTag : subTags) {
931 if (subTag.getValue().getTrimmedText().equals(tagValue)) {
938 // Read the function name and parameter names to find out what this function does... :-)
940 public static XmlTag find(String subTag, String withValue, String forTag, XmlTag insideRoot) {
941 final XmlTag[] forTags = insideRoot.findSubTags(forTag);
943 for (XmlTag tag : forTags) {
944 final XmlTag[] allTags = tag.findSubTags(subTag);
946 for (XmlTag curTag : allTags) {
947 if (curTag.getName().equals(subTag) && curTag.getValue().getTrimmedText().equalsIgnoreCase(withValue)) {
958 public static String[][] getDefaultNamespaces(final XmlDocument document) {
959 final XmlFile file = getContainingFile(document);
961 final XmlTag tag = document.getRootTag();
962 if (tag == null) return null;
965 @NotNull final XmlFileNSInfoProvider[] nsProviders = Extensions.getExtensions(XmlFileNSInfoProvider.EP_NAME);
968 for (XmlFileNSInfoProvider nsProvider : nsProviders) {
969 final String[][] pairs = nsProvider.getDefaultNamespaces(file);
970 if (pairs != null && pairs.length > 0) {
972 for (final String[] nsMapping : pairs) {
973 if (nsMapping == null || nsMapping.length != 2 || nsMapping[0] == null || nsMapping[1] == null) {
974 LOG.debug("NSInfoProvider " + nsProvider + " gave wrong info about " + file.getVirtualFile());
975 continue NextProvider;
983 String namespace = getDtdUri(document);
985 if (namespace != null) {
986 boolean overrideNamespaceFromDocType = false;
989 final FileType fileType = file.getFileType();
990 overrideNamespaceFromDocType =
991 fileType == StdFileTypes.HTML || fileType == StdFileTypes.XHTML || fileType == StdFileTypes.JSPX || fileType == StdFileTypes.JSP;
994 if (!overrideNamespaceFromDocType) return new String[][]{new String[]{"", namespace}};
997 if ("taglib".equals(tag.getName())) {
998 return new String[][]{new String[]{"", TAGLIB_1_2_URI}};
1003 final Language language = file.getLanguage();
1004 if (language == HTMLLanguage.INSTANCE || language == XHTMLLanguage.INSTANCE) {
1005 return new String[][]{new String[]{"", XHTML_URI}};
1013 public static String getDtdUri(XmlDocument document) {
1014 XmlProlog prolog = document.getProlog();
1015 if (prolog != null) {
1016 return getDtdUri( prolog.getDoctype() );
1022 public static String getDtdUri(XmlDoctype doctype) {
1023 if (doctype != null) {
1024 String docType = doctype.getDtdUri();
1025 if (docType == null) {
1026 final String publicId = doctype.getPublicId();
1027 if (PsiTreeUtil.getParentOfType(doctype, XmlDocument.class) instanceof HtmlDocumentImpl &&
1029 publicId.indexOf("-//W3C//DTD HTML") != -1) {
1030 return HTML4_LOOSE_URI;
1032 else if (HtmlUtil.isHtml5Doctype(doctype)) {
1033 docType = doctype.getLanguage() instanceof HTMLLanguage
1034 ? Html5SchemaProvider.HTML5_SCHEMA_LOCATION
1035 : Html5SchemaProvider.XHTML5_SCHEMA_LOCATION;
1043 private static void computeTag(XmlTag tag,
1044 final Map<String, List<String>> tagsMap,
1045 final Map<String, List<MyAttributeInfo>> attributesMap,
1046 final boolean processIncludes) {
1050 final String tagName = tag.getName();
1052 List<MyAttributeInfo> list = attributesMap.get(tagName);
1054 list = new ArrayList<MyAttributeInfo>();
1055 final XmlAttribute[] attributes = tag.getAttributes();
1056 for (final XmlAttribute attribute : attributes) {
1057 list.add(new MyAttributeInfo(attribute.getName()));
1061 final XmlAttribute[] attributes = tag.getAttributes();
1062 ContainerUtil.sort(list);
1063 Arrays.sort(attributes, new Comparator<XmlAttribute>() {
1064 public int compare(XmlAttribute attr1, XmlAttribute attr2) {
1065 return attr1.getName().compareTo(attr2.getName());
1069 final Iterator<MyAttributeInfo> iter = list.iterator();
1070 list = new ArrayList<MyAttributeInfo>();
1072 while (iter.hasNext()) {
1073 final MyAttributeInfo info = iter.next();
1074 boolean requiredFlag = false;
1075 while (attributes.length > index) {
1076 if (info.compareTo(attributes[index]) != 0) {
1077 if (info.compareTo(attributes[index]) < 0) {
1080 if (attributes[index].getValue() != null) list.add(new MyAttributeInfo(attributes[index].getName(), false));
1084 requiredFlag = true;
1089 info.myRequired &= requiredFlag;
1092 while (attributes.length > index) {
1093 if (attributes[index].getValue() != null) {
1094 list.add(new MyAttributeInfo(attributes[index++].getName(), false));
1101 attributesMap.put(tagName, list);
1102 final List<String> tags = tagsMap.get(tagName) != null ? tagsMap.get(tagName) : new ArrayList<String>();
1103 tagsMap.put(tagName, tags);
1104 PsiFile file = tag.isValid() ? tag.getContainingFile() : null;
1105 processXmlElements(tag, new FilterElementProcessor(XmlTagFilter.INSTANCE) {
1106 public void add(PsiElement element) {
1107 XmlTag tag = (XmlTag)element;
1108 if (!tags.contains(tag.getName())) {
1109 tags.add(tag.getName());
1111 computeTag(tag, tagsMap, attributesMap, processIncludes);
1113 }, false, false, file, processIncludes);
1114 /*tag.processElements(new FilterElementProcessor(XmlTagFilter.INSTANCE) {
1115 public void add(PsiElement element) {
1116 XmlTag tag = (XmlTag)element;
1117 if (!tags.contains(tag.getName())) {
1118 tags.add(tag.getName());
1120 computeTag(tag, tagsMap, attributesMap);
1126 public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag) {
1127 return findXmlDescriptorByType(xmlTag, null);
1131 public static XmlElementDescriptor findXmlDescriptorByType(final XmlTag xmlTag, @Nullable XmlTag context) {
1132 String type = xmlTag.getAttributeValue("type", XML_SCHEMA_INSTANCE_URI);
1135 String ns = xmlTag.getNamespace();
1136 if (ourSchemaUrisList.indexOf(ns) >= 0) {
1137 type = xmlTag.getAttributeValue("type", null);
1141 XmlElementDescriptor elementDescriptor = null;
1143 final String namespaceByPrefix = findNamespaceByPrefix(findPrefixByQualifiedName(type), xmlTag);
1144 XmlNSDescriptor typeDecr = xmlTag.getNSDescriptor(namespaceByPrefix, true);
1146 if (typeDecr == null && namespaceByPrefix.length() == 0) {
1147 if (context != null) typeDecr = context.getNSDescriptor("", true);
1149 if (typeDecr == null) {
1150 final PsiFile containingFile = xmlTag.getContainingFile();
1151 if (containingFile instanceof XmlFile) {
1152 final XmlDocument document = ((XmlFile)containingFile).getDocument();
1153 if (document != null) typeDecr = (XmlNSDescriptor)document.getMetaData();
1158 if (typeDecr instanceof XmlNSDescriptorImpl) {
1159 final XmlNSDescriptorImpl schemaDescriptor = (XmlNSDescriptorImpl)typeDecr;
1160 elementDescriptor = schemaDescriptor.getDescriptorByType(type, xmlTag);
1164 return elementDescriptor;
1167 public static boolean collectEnumerationValues(final XmlTag element, final HashSet<String> variants) {
1168 return processEnumerationValues(element, new Processor<XmlTag>() {
1169 public boolean process(XmlTag xmlTag) {
1170 variants.add(xmlTag.getAttributeValue(VALUE_ATTR_NAME));
1176 public static boolean processEnumerationValues(final XmlTag element, final Processor<XmlTag> tagProcessor) {
1177 boolean exaustiveEnum = true;
1179 for (final XmlTag tag : element.getSubTags()) {
1180 @NonNls final String localName = tag.getLocalName();
1182 if (localName.equals(ENUMERATION_TAG_NAME)) {
1183 final String attributeValue = tag.getAttributeValue(VALUE_ATTR_NAME);
1184 if (attributeValue != null) tagProcessor.process(tag);
1186 else if (localName.equals("union")) {
1187 exaustiveEnum = false;
1188 processEnumerationValues(tag, tagProcessor);
1190 else if (!localName.equals("annotation")) {
1191 // don't go into annotation
1192 exaustiveEnum &= processEnumerationValues(tag, tagProcessor);
1195 return exaustiveEnum;
1203 * @param bodyText pass null to create collapsed tag, empty string means creating expanded one
1204 * @param enforceNamespacesDeep
1207 public static XmlTag createChildTag(final XmlTag xmlTag,
1210 @Nullable String bodyText,
1211 boolean enforceNamespacesDeep) {
1213 final String prefix = xmlTag.getPrefixByNamespace(namespace);
1214 if (prefix != null && prefix.length() > 0) {
1215 qname = prefix + ":" + localName;
1221 @NonNls StringBuilder tagStartBuilder = StringBuilderSpinAllocator.alloc();
1224 tagStartBuilder.append(qname);
1225 if (!StringUtil.isEmpty(namespace) && xmlTag.getPrefixByNamespace(namespace) == null &&
1226 !(StringUtil.isEmpty(xmlTag.getNamespacePrefix()) && namespace.equals(xmlTag.getNamespace()))) {
1227 tagStartBuilder.append(" xmlns=\"");
1228 tagStartBuilder.append(namespace);
1229 tagStartBuilder.append("\"");
1231 tagStart = tagStartBuilder.toString();
1234 StringBuilderSpinAllocator.dispose(tagStartBuilder);
1236 Language language = xmlTag.getLanguage();
1237 if (!(language instanceof HTMLLanguage)) language = StdFileTypes.XML.getLanguage();
1239 if (bodyText != null) {
1240 retTag = XmlElementFactory.getInstance(xmlTag.getProject())
1241 .createTagFromText("<" + tagStart + ">" + bodyText + "</" + qname + ">", language);
1242 if (enforceNamespacesDeep) {
1243 retTag.acceptChildren(new XmlRecursiveElementVisitor() {
1245 public void visitXmlTag(XmlTag tag) {
1246 final String namespacePrefix = tag.getNamespacePrefix();
1247 if (namespacePrefix.length() == 0) {
1249 if (prefix != null && prefix.length() > 0) {
1250 qname = prefix + ":" + tag.getLocalName();
1253 qname = tag.getLocalName();
1258 catch (IncorrectOperationException e) {
1262 super.visitXmlTag(tag);
1268 retTag = XmlElementFactory.getInstance(xmlTag.getProject()).createTagFromText("<" + tagStart + "/>", language);
1272 catch (IncorrectOperationException e) {
1279 public static Pair<XmlTagChild, XmlTagChild> findTagChildrenInRange(final PsiFile file, int startOffset, int endOffset) {
1280 PsiElement elementAtStart = file.findElementAt(startOffset);
1281 PsiElement elementAtEnd = file.findElementAt(endOffset - 1);
1282 if (elementAtStart instanceof PsiWhiteSpace) {
1283 startOffset = elementAtStart.getTextRange().getEndOffset();
1284 elementAtStart = file.findElementAt(startOffset);
1286 if (elementAtEnd instanceof PsiWhiteSpace) {
1287 endOffset = elementAtEnd.getTextRange().getStartOffset();
1288 elementAtEnd = file.findElementAt(endOffset - 1);
1290 if (elementAtStart == null || elementAtEnd == null) return null;
1292 XmlTagChild first = PsiTreeUtil.getParentOfType(elementAtStart, XmlTagChild.class);
1293 if (first == null) return null;
1295 if (first.getTextRange().getStartOffset() != startOffset) {
1296 //Probably 'first' starts with whitespace
1297 PsiElement elementAt = file.findElementAt(first.getTextRange().getStartOffset());
1298 if (!(elementAt instanceof PsiWhiteSpace) || elementAt.getTextRange().getEndOffset() != startOffset) return null;
1301 XmlTagChild last = first;
1302 while (last != null && last.getTextRange().getEndOffset() < endOffset) {
1303 last = PsiTreeUtil.getNextSiblingOfType(last, XmlTagChild.class);
1306 if (last == null) return null;
1307 if (last.getTextRange().getEndOffset() != elementAtEnd.getTextRange().getEndOffset()) {
1308 //Probably 'last' ends with whitespace
1309 PsiElement elementAt = file.findElementAt(last.getTextRange().getEndOffset() - 1);
1310 if (!(elementAt instanceof PsiWhiteSpace) || elementAt.getTextRange().getStartOffset() != endOffset) {
1315 return new Pair<XmlTagChild, XmlTagChild>(first, last);
1318 public static boolean isSimpleXmlAttributeValue(final String unquotedValue, final XmlAttributeValue context) {
1319 for (int i = 0; i < unquotedValue.length(); ++i) {
1320 final char ch = unquotedValue.charAt(i);
1321 if (!Character.isJavaIdentifierPart(ch) && ch != ':' && ch != '-') {
1322 final XmlFile file = PsiTreeUtil.getParentOfType(context, XmlFile.class);
1323 return file != null && !tagFromTemplateFramework(file.getRootTag());
1329 public static boolean toCode(String str) {
1330 for (int i = 0; i < str.length(); i++) {
1331 if (toCode(str.charAt(i))) return true;
1336 public static boolean toCode(char ch) {
1337 return "<&>\u00a0".indexOf(ch) >= 0;
1341 public static PsiNamedElement findRealNamedElement(@NotNull final PsiNamedElement _element) {
1342 PsiElement currentElement = _element;
1343 final XmlEntityRef lastEntityRef = PsiTreeUtil.getParentOfType(currentElement, XmlEntityRef.class);
1345 while (!(currentElement instanceof XmlFile)) {
1346 PsiElement dependingElement = currentElement.getUserData(XmlElement.DEPENDING_ELEMENT);
1347 if (dependingElement == null) dependingElement = currentElement.getContext();
1348 currentElement = dependingElement;
1349 if (dependingElement == null) break;
1352 if (currentElement != null) {
1353 final String name = _element.getName();
1354 if (_element instanceof XmlEntityDecl) {
1355 final XmlEntityDecl cachedEntity = XmlEntityRefImpl.getCachedEntity((PsiFile)currentElement, name);
1356 if (cachedEntity != null) return cachedEntity;
1359 final PsiNamedElement[] result = new PsiNamedElement[1];
1361 processXmlElements((XmlFile)currentElement, new PsiElementProcessor() {
1362 public boolean execute(@NotNull final PsiElement element) {
1363 if (element instanceof PsiNamedElement) {
1364 final String elementName = ((PsiNamedElement)element).getName();
1366 if (elementName.equals(name) && _element.getClass().isInstance(element)
1367 || lastEntityRef != null && element instanceof XmlEntityDecl &&
1368 elementName.equals(lastEntityRef.getText().substring(1, lastEntityRef.getTextLength() - 1))) {
1369 result[0] = (PsiNamedElement)element;
1384 private static class MyAttributeInfo implements Comparable {
1385 boolean myRequired = true;
1386 String myName = null;
1388 MyAttributeInfo(String name) {
1392 MyAttributeInfo(String name, boolean flag) {
1397 public int compareTo(Object o) {
1398 if (o instanceof MyAttributeInfo) {
1399 return myName.compareTo(((MyAttributeInfo)o).myName);
1401 else if (o instanceof XmlAttribute) {
1402 return myName.compareTo(((XmlAttribute)o).getName());
1408 public static String generateDocumentDTD(XmlDocument doc, boolean full) {
1409 final Map<String, List<String>> tags = new LinkedHashMap<String, List<String>>();
1410 final Map<String, List<MyAttributeInfo>> attributes = new LinkedHashMap<String, List<MyAttributeInfo>>();
1413 XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, true);
1414 final XmlTag rootTag = doc.getRootTag();
1415 computeTag(rootTag, tags, attributes, full);
1417 // For supporting not welformed XML
1418 for (PsiElement element = rootTag != null ? rootTag.getNextSibling() : null; element != null; element = element.getNextSibling()) {
1419 if (element instanceof XmlTag) {
1420 computeTag((XmlTag)element, tags, attributes, full);
1425 XmlEntityRefImpl.setNoEntityExpandOutOfDocument(doc, false);
1428 final StringBuilder buffer = new StringBuilder();
1429 for (final String tagName : tags.keySet()) {
1430 buffer.append(generateElementDTD(tagName, tags.get(tagName), attributes.get(tagName)));
1432 return buffer.toString();
1435 public static String generateElementDTD(String name, List<String> tags, List<MyAttributeInfo> attributes) {
1436 if (name == null || "".equals(name)) return "";
1437 if (name.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) return "";
1439 @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1441 buffer.append("<!ELEMENT ").append(name).append(" ");
1442 if (tags.isEmpty()) {
1443 buffer.append("(#PCDATA)>\n");
1447 final Iterator<String> iter = tags.iterator();
1448 while (iter.hasNext()) {
1449 final String tagName = iter.next();
1450 buffer.append(tagName);
1451 if (iter.hasNext()) {
1455 buffer.append(")*");
1458 buffer.append(">\n");
1460 if (!attributes.isEmpty()) {
1461 buffer.append("<!ATTLIST ").append(name);
1462 for (final MyAttributeInfo info : attributes) {
1463 buffer.append("\n ").append(generateAttributeDTD(info));
1465 buffer.append(">\n");
1467 return buffer.toString();
1470 StringBuilderSpinAllocator.dispose(buffer);
1474 private static String generateAttributeDTD(MyAttributeInfo info) {
1475 if (info.myName.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) return "";
1476 @NonNls final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
1478 buffer.append(info.myName).append(" ");
1479 //if ("id".equals(info.myName)) {
1480 // buffer.append("ID");
1482 //else if ("ref".equals(info.myName)) {
1483 // buffer.append("IDREF");
1485 buffer.append("CDATA");
1487 if (info.myRequired) {
1488 buffer.append(" #REQUIRED");
1491 buffer.append(" #IMPLIED");
1493 return buffer.toString();
1496 StringBuilderSpinAllocator.dispose(buffer);
1501 public static String trimLeadingSpacesInMultilineTagValue(@NonNls String tagValue) {
1502 return tagValue == null ? null : tagValue.replaceAll("\n\\s*", "\n");
1505 public static String findNamespaceByPrefix(final String prefix, XmlTag contextTag) {
1506 return contextTag.getNamespaceByPrefix(prefix);
1509 public static String findPrefixByQualifiedName(String name) {
1510 final int prefixEnd = name.indexOf(':');
1511 if (prefixEnd > 0) {
1512 return name.substring(0, prefixEnd);
1518 public static String findLocalNameByQualifiedName(String name) {
1519 return name == null ? null : name.substring(name.indexOf(':') + 1);
1523 public static XmlFile getContainingFile(PsiElement element) {
1524 while (!(element instanceof XmlFile) && element != null) {
1525 final PsiElement context = element.getContext();
1526 if (context == null) {
1527 //todo Dmitry Avdeev: either XmlExtension should work on any PsiFile (not just XmlFile), or you need to handle elements from JspJavaFile in some other way
1528 final XmlExtension extension = XmlExtension.getExtensionByElement(element);
1529 if (extension != null) {
1530 element = extension.getContainingFile(element);
1534 if (element == context) {
1535 LOG.error("Context==element: " + element.getClass());
1541 return (XmlFile)element;
1545 public static String getSubTagValue(XmlTag tag, final String subTagName) {
1546 final XmlTag subTag = tag.findFirstSubTag(subTagName);
1547 if (subTag != null) {
1548 return subTag.getValue().getTrimmedText();
1553 public static int getStartOffsetInFile(XmlTag xmlTag) {
1556 off += xmlTag.getStartOffsetInParent();
1557 final PsiElement parent = xmlTag.getParent();
1558 if (!(parent instanceof XmlTag)) break;
1559 xmlTag = (XmlTag)parent;
1564 public static XmlElement setNewValue(XmlElement tag, String value) throws IncorrectOperationException {
1565 if (tag instanceof XmlTag) {
1566 ((XmlTag)tag).getValue().setText(value);
1569 else if (tag instanceof XmlAttribute) {
1570 XmlAttribute attr = (XmlAttribute)tag;
1571 attr.setValue(value);
1575 throw new IncorrectOperationException();
1579 public static String decode(@NonNls String text) {
1580 if (text.length() == 0) return text;
1581 if (text.charAt(0) != '&' || text.length() < 3) {
1582 if (text.indexOf('<') < 0 && text.indexOf('>') < 0) return text;
1583 return text.replaceAll("<!\\[CDATA\\[", "").replaceAll("\\]\\]>", "");
1586 if (text.equals("<")) {
1589 if (text.equals(">")) {
1592 if (text.equals(" ")) {
1595 if (text.equals("&")) {
1598 if (text.equals("'")) {
1601 if (text.equals(""")) {
1604 if (text.startsWith(""") && text.endsWith(""")) {
1605 return "\"" + text.substring(6, text.length() - 6) + "\"";
1607 if (text.startsWith("&#")) {
1608 text = text.substring(3, text.length() - 1);
1610 return String.valueOf((char)Integer.parseInt(text));
1612 catch (NumberFormatException e) {
1620 public static String unescape(String text) {
1621 return StringUtil.unescapeXml(text);
1624 public static String escape(String text) {
1625 return StringUtil.escapeXml(text);
1628 @NonNls private static final String XML_PROLOG_START = "<?xml";
1629 @NonNls private static final byte[] XML_PROLOG_START_BYTES = CharsetToolkit.getUtf8Bytes(XML_PROLOG_START);
1630 @NonNls private static final String ENCODING = "encoding";
1631 @NonNls private static final byte[] ENCODING_BYTES = CharsetToolkit.getUtf8Bytes(ENCODING);
1632 @NonNls private static final String XML_PROLOG_END = "?>";
1633 @NonNls private static final byte[] XML_PROLOG_END_BYTES = CharsetToolkit.getUtf8Bytes(XML_PROLOG_END);
1636 public static String extractXmlEncodingFromProlog(final byte[] content) {
1637 return detect(content);
1641 private static String detect(final byte[] bytes) {
1643 if (CharsetToolkit.hasUTF8Bom(bytes)) {
1644 index = CharsetToolkit.UTF8_BOM.length;
1647 index = skipWhiteSpace(index, bytes);
1648 if (!ArrayUtil.startsWith(bytes, index, XML_PROLOG_START_BYTES)) return null;
1649 index += XML_PROLOG_START_BYTES.length;
1650 while (index < bytes.length) {
1651 index = skipWhiteSpace(index, bytes);
1652 if (ArrayUtil.startsWith(bytes, index, XML_PROLOG_END_BYTES)) return null;
1653 if (ArrayUtil.startsWith(bytes, index, ENCODING_BYTES)) {
1654 index += ENCODING_BYTES.length;
1655 index = skipWhiteSpace(index, bytes);
1656 if (index >= bytes.length || bytes[index] != '=') continue;
1658 index = skipWhiteSpace(index, bytes);
1659 if (index >= bytes.length || bytes[index] != '\'' && bytes[index] != '\"') continue;
1660 byte quote = bytes[index];
1662 StringBuilder encoding = new StringBuilder();
1663 while (index < bytes.length) {
1664 if (bytes[index] == quote) return encoding.toString();
1665 encoding.append((char)bytes[index++]);
1673 private static String detect(@NotNull String text) {
1676 index = skipWhiteSpace(index, text);
1677 if (!StringUtil.startsWith(text, index, XML_PROLOG_START)) return null;
1678 index += XML_PROLOG_START.length();
1679 while (index < text.length()) {
1680 index = skipWhiteSpace(index, text);
1681 if (StringUtil.startsWith(text, index, XML_PROLOG_END)) return null;
1682 if (StringUtil.startsWith(text, index, ENCODING)) {
1683 index += ENCODING.length();
1684 index = skipWhiteSpace(index, text);
1685 if (index >= text.length() || text.charAt(index) != '=') continue;
1687 index = skipWhiteSpace(index, text);
1688 if (index >= text.length()) continue;
1689 char quote = text.charAt(index);
1690 if (quote != '\'' && quote != '\"') continue;
1692 StringBuilder encoding = new StringBuilder();
1693 while (index < text.length()) {
1694 char c = text.charAt(index);
1695 if (c == quote) return encoding.toString();
1705 private static int skipWhiteSpace(int start, @NotNull byte[] bytes) {
1706 while (start < bytes.length) {
1707 char c = (char)bytes[start];
1708 if (!Character.isWhitespace(c)) break;
1713 private static int skipWhiteSpace(int start, @NotNull String text) {
1714 while (start < text.length()) {
1715 char c = text.charAt(start);
1716 if (!Character.isWhitespace(c)) break;
1723 public static String extractXmlEncodingFromProlog(String text) {
1724 return detect(text);
1727 public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1728 @Nullable @NonNls String[] attributeNames,
1729 @Nullable ElementFilter elementFilter,
1730 @NotNull PsiReferenceProvider provider) {
1731 registerXmlAttributeValueReferenceProvider(registrar, attributeNames, elementFilter, true, provider);
1734 public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1735 @Nullable @NonNls String[] attributeNames,
1736 @Nullable ElementFilter elementFilter,
1737 boolean caseSensitive,
1738 @NotNull PsiReferenceProvider provider) {
1739 registerXmlAttributeValueReferenceProvider(registrar, attributeNames, elementFilter, caseSensitive, provider,
1740 PsiReferenceRegistrar.DEFAULT_PRIORITY);
1743 public static void registerXmlAttributeValueReferenceProvider(PsiReferenceRegistrar registrar,
1744 @Nullable @NonNls String[] attributeNames,
1745 @Nullable ElementFilter elementFilter,
1746 boolean caseSensitive,
1747 @NotNull PsiReferenceProvider provider,
1749 if (attributeNames == null) {
1750 registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().and(new FilterPattern(elementFilter)), provider, priority);
1754 final StringPattern namePattern = caseSensitive
1755 ? StandardPatterns.string().oneOf(attributeNames)
1756 : StandardPatterns.string().oneOfIgnoreCase(attributeNames);
1758 .registerReferenceProvider(XmlPatterns.xmlAttributeValue().withLocalName(namePattern).and(new FilterPattern(elementFilter)), provider,
1762 public static void registerXmlTagReferenceProvider(PsiReferenceRegistrar registrar,
1763 @NonNls String[] names,
1764 @Nullable ElementFilter elementFilter,
1765 boolean caseSensitive,
1766 @NotNull PsiReferenceProvider provider) {
1767 if (names == null) {
1768 registrar.registerReferenceProvider(XmlPatterns.xmlTag().and(new FilterPattern(elementFilter)), provider,
1769 PsiReferenceRegistrar.DEFAULT_PRIORITY);
1774 final StringPattern namePattern =
1775 caseSensitive ? StandardPatterns.string().oneOf(names) : StandardPatterns.string().oneOfIgnoreCase(names);
1776 registrar.registerReferenceProvider(XmlPatterns.xmlTag().withLocalName(namePattern).and(new FilterPattern(elementFilter)), provider,
1777 PsiReferenceRegistrar.DEFAULT_PRIORITY);
1780 public interface DuplicationInfoProvider<T extends PsiElement> {
1782 String getName(@NotNull T t);
1785 String getNameKey(@NotNull T t, @NotNull String name);
1788 PsiElement getNodeForMessage(@NotNull T t);