c02a672d756bfe156adcffdae971b866c6593650
[idea/community.git] / xml / impl / src / com / intellij / codeInsight / daemon / impl / analysis / XmlHighlightVisitor.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.codeInsight.daemon.impl.analysis;
17
18 import com.intellij.codeInsight.daemon.*;
19 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
20 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
21 import com.intellij.codeInsight.daemon.impl.HighlightVisitor;
22 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
23 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
24 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
25 import com.intellij.codeInsight.intention.IntentionAction;
26 import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
27 import com.intellij.codeInspection.InspectionProfile;
28 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
29 import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection;
30 import com.intellij.codeInspection.htmlInspections.XmlEntitiesInspection;
31 import com.intellij.idea.LoggerFactory;
32 import com.intellij.lang.ASTNode;
33 import com.intellij.lang.dtd.DTDLanguage;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.editor.markup.TextAttributes;
36 import com.intellij.openapi.progress.ProgressManager;
37 import com.intellij.openapi.util.Comparing;
38 import com.intellij.openapi.util.Key;
39 import com.intellij.openapi.util.TextRange;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
42 import com.intellij.psi.*;
43 import com.intellij.psi.html.HtmlTag;
44 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
45 import com.intellij.psi.meta.PsiMetaData;
46 import com.intellij.psi.templateLanguages.OuterLanguageElement;
47 import com.intellij.psi.util.PsiTreeUtil;
48 import com.intellij.psi.util.PsiUtilBase;
49 import com.intellij.psi.xml.*;
50 import com.intellij.util.SmartList;
51 import com.intellij.xml.XmlAttributeDescriptor;
52 import com.intellij.xml.XmlElementDescriptor;
53 import com.intellij.xml.XmlExtension;
54 import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
55 import com.intellij.xml.util.HtmlUtil;
56 import com.intellij.xml.util.XmlTagUtil;
57 import com.intellij.xml.util.XmlUtil;
58 import org.jetbrains.annotations.NotNull;
59 import org.jetbrains.annotations.Nullable;
60
61 import java.text.MessageFormat;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.StringTokenizer;
66
67 /**
68  * @author Mike
69  */
70 public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightVisitor, Validator.ValidationHost {
71   private static final Logger LOG = LoggerFactory.getInstance().getLoggerInstance("com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor");
72   public static final Key<String> DO_NOT_VALIDATE_KEY = Key.create("do not validate");
73   private List<HighlightInfo> myResult;
74
75   private static boolean ourDoJaxpTesting;
76
77   private static final TextAttributes NONEMPTY_TEXT_ATTRIBUTES = new TextAttributes() {
78     public boolean isEmpty() {
79       return false;
80     }
81   };
82
83   private void addElementsForTag(XmlTag tag,
84                                  String localizedMessage,
85                                  HighlightInfoType type,
86                                  IntentionAction quickFixAction) {
87     addElementsForTagWithManyQuickFixes(tag, localizedMessage, type, quickFixAction);
88   }
89
90   private void addElementsForTagWithManyQuickFixes(XmlTag tag,
91                                                    String localizedMessage,
92                                                    HighlightInfoType type, IntentionAction... quickFixActions) {
93     bindMessageToTag(tag, type, -1, localizedMessage, quickFixActions);
94   }
95
96   @Override public void visitXmlToken(XmlToken token) {
97     if (token.getTokenType() == XmlTokenType.XML_NAME || token.getTokenType() == XmlTokenType.XML_TAG_NAME) {
98       PsiElement element = token.getPrevSibling();
99       while(element instanceof PsiWhiteSpace) element = element.getPrevSibling();
100
101       if (element instanceof XmlToken) {
102         if (((XmlToken)element).getTokenType() == XmlTokenType.XML_START_TAG_START) {
103           PsiElement parent = element.getParent();
104
105           if (parent instanceof XmlTag && !(token.getNextSibling() instanceof OuterLanguageElement)) {
106             checkTag((XmlTag)parent);
107           }
108         }
109       } else {
110         PsiElement parent = token.getParent();
111
112         if (parent instanceof XmlAttribute && !(token.getNextSibling() instanceof OuterLanguageElement)) {
113           checkAttribute((XmlAttribute) parent);
114         }
115       }
116     }
117   }
118
119   private void checkTag(XmlTag tag) {
120     if (ourDoJaxpTesting) return;
121
122     if (myResult == null) {
123       checkTagByDescriptor(tag);
124     }
125
126     if (myResult == null) {
127       if (tag.getUserData(DO_NOT_VALIDATE_KEY) == null) {
128         final XmlElementDescriptor descriptor = tag.getDescriptor();
129
130         if (tag instanceof HtmlTag &&
131             ( descriptor instanceof AnyXmlElementDescriptor ||
132               descriptor == null
133             )
134            ) {
135           return;
136         }
137
138         checkReferences(tag);
139       }
140     }
141   }
142
143   private void bindMessageToTag(final XmlTag tag, final HighlightInfoType warning, final int messageLength, final String localizedMessage, IntentionAction... quickFixActions) {
144     XmlToken childByRole = XmlTagUtil.getStartTagNameElement(tag);
145
146     bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
147     childByRole = XmlTagUtil.getEndTagNameElement(tag);
148     bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
149   }
150
151   private void bindMessageToAstNode(final PsiElement childByRole,
152                                     final HighlightInfoType warning,
153                                     final int offset,
154                                     int length,
155                                     final String localizedMessage, IntentionAction... quickFixActions) {
156     if(childByRole != null) {
157       final TextRange textRange = childByRole.getTextRange();
158       if (length == -1) length = textRange.getLength();
159       final int startOffset = textRange.getStartOffset() + offset;
160
161       HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
162         warning,
163         childByRole, startOffset, startOffset + length,
164         localizedMessage, HighlightInfo.htmlEscapeToolTip(localizedMessage)
165       );
166
167       if (highlightInfo == null) {
168         highlightInfo = HighlightInfo.createHighlightInfo(
169           warning,
170           new TextRange(startOffset, startOffset + length),
171           localizedMessage,
172           localizedMessage, NONEMPTY_TEXT_ATTRIBUTES
173         );
174       }
175
176       for (final IntentionAction quickFixAction : quickFixActions) {
177         if (quickFixAction == null) continue;
178         QuickFixAction.registerQuickFixAction(highlightInfo, textRange, quickFixAction, null);
179       }
180       addToResults(highlightInfo);
181     }
182   }
183
184   private void checkTagByDescriptor(final XmlTag tag) {
185     String name = tag.getName();
186
187     XmlElementDescriptor elementDescriptor = null;
188
189     final PsiElement parent = tag.getParent();
190     if (parent instanceof XmlTag) {
191       XmlTag parentTag = (XmlTag)parent;
192       final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
193
194       if (parentDescriptor != null) {
195         elementDescriptor = XmlExtension.getExtension(tag.getContainingFile()).getElementDescriptor(tag, parentTag, parentDescriptor);
196       }
197
198       if (parentDescriptor != null &&
199           elementDescriptor == null &&
200           parentTag.getUserData(DO_NOT_VALIDATE_KEY) == null &&
201           !XmlUtil.tagFromTemplateFramework(tag)
202       ) {
203         if (tag instanceof HtmlTag) {
204           //XmlEntitiesInspection inspection = getInspectionProfile(tag, HtmlStyleLocalInspection.SHORT_NAME);
205           //if (inspection != null /*&& isAdditionallyDeclared(inspection.getAdditionalEntries(XmlEntitiesInspection.UNKNOWN_TAG), name)*/) {
206             return;
207           //}
208         }
209
210         addElementsForTag(
211           tag,
212           XmlErrorMessages.message("element.is.not.allowed.here", name),
213           getTagProblemInfoType(tag),
214           null
215         );
216         return;
217       }
218
219       if (elementDescriptor instanceof AnyXmlElementDescriptor ||
220           elementDescriptor == null
221          ) {
222         elementDescriptor = tag.getDescriptor();
223       }
224
225       if (elementDescriptor == null) return;
226     }
227     else {
228       //root tag
229       elementDescriptor = tag.getDescriptor();
230
231      if (elementDescriptor == null) {
232        addElementsForTag(tag, XmlErrorMessages.message("element.must.be.declared", name), HighlightInfoType.WRONG_REF, null);
233        return;
234       }
235     }
236
237     XmlAttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributesDescriptors(tag);
238     Set<String> requiredAttributes = null;
239
240     for (XmlAttributeDescriptor attribute : attributeDescriptors) {
241       if (attribute != null && attribute.isRequired()) {
242         if (requiredAttributes == null) {
243           requiredAttributes = new HashSet<String>();
244         }
245         requiredAttributes.add(attribute.getName(tag));
246       }
247     }
248
249     if (requiredAttributes != null) {
250       for (final String attrName : requiredAttributes) {
251         if (tag.getAttribute(attrName, "") == null &&
252             !XmlExtension.getExtension(tag.getContainingFile()).isRequiredAttributeImplicitlyPresent(tag, attrName)) {
253
254           final InsertRequiredAttributeFix insertRequiredAttributeIntention = new InsertRequiredAttributeFix(
255               tag, attrName, null);
256           final String localizedMessage = XmlErrorMessages.message("element.doesnt.have.required.attribute", name, attrName);
257           final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
258           final LocalInspectionToolWrapper toolWrapper =
259             (LocalInspectionToolWrapper)profile.getInspectionTool(RequiredAttributesInspection.SHORT_NAME, tag);
260           if (toolWrapper != null) {
261             RequiredAttributesInspection inspection = (RequiredAttributesInspection)toolWrapper.getTool();
262             reportOneTagProblem(
263               tag,
264               attrName,
265               localizedMessage,
266               insertRequiredAttributeIntention,
267               HighlightDisplayKey.find(RequiredAttributesInspection.SHORT_NAME),
268               inspection,
269               XmlEntitiesInspection.NOT_REQUIRED_ATTRIBUTE
270             );
271           }
272         }
273       }
274     }
275
276     if (elementDescriptor instanceof Validator) {
277       //noinspection unchecked
278       ((Validator<XmlTag>)elementDescriptor).validate(tag,this);
279     }
280   }
281
282   private void reportOneTagProblem(final XmlTag tag,
283                                    final String name,
284                                    final String localizedMessage,
285                                    final IntentionAction basicIntention,
286                                    final HighlightDisplayKey key,
287                                    final XmlEntitiesInspection inspection,
288                                    final int type) {
289     boolean htmlTag = false;
290
291     if (tag instanceof HtmlTag) {
292       htmlTag = true;
293       if(isAdditionallyDeclared(inspection.getAdditionalEntries(type), name)) return;
294     }
295
296     final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
297     final IntentionAction intentionAction = inspection.getIntentionAction(name, type);
298     if (htmlTag && profile.isToolEnabled(key, tag)) {
299       addElementsForTagWithManyQuickFixes(
300         tag,
301         localizedMessage,
302         isInjectedHtmlTagForWhichNoProblemsReporting((HtmlTag)tag) ?
303           HighlightInfoType.INFORMATION : 
304           SeverityRegistrar.getInstance(tag.getProject()).getHighlightInfoTypeBySeverity(profile.getErrorLevel(key, tag).getSeverity()),
305         intentionAction,
306         basicIntention);
307     } else if (!htmlTag) {
308       addElementsForTag(
309         tag,
310         localizedMessage,
311         HighlightInfoType.ERROR,
312         basicIntention
313       );
314     }
315   }
316
317   private static boolean isAdditionallyDeclared(final String additional, String name) {
318     name = name.toLowerCase();
319     if (!additional.contains(name)) return false;
320
321     StringTokenizer tokenizer = new StringTokenizer(additional, ", ");
322     while (tokenizer.hasMoreTokens()) {
323       if (name.equals(tokenizer.nextToken())) {
324         return true;
325       }
326     }
327
328     return false;
329   }
330
331   private static HighlightInfoType getTagProblemInfoType(XmlTag tag) {
332     if (tag instanceof HtmlTag && XmlUtil.HTML_URI.equals(tag.getNamespace())) {
333       if (isInjectedHtmlTagForWhichNoProblemsReporting((HtmlTag)tag)) return HighlightInfoType.INFORMATION;
334       return HighlightInfoType.WARNING;
335     }
336     return HighlightInfoType.WRONG_REF;
337   }
338
339   private static boolean isInjectedHtmlTagForWhichNoProblemsReporting(HtmlTag tag) {
340     PsiElement context = tag.getContainingFile().getContext();
341     if (context != null && skipValidation(context)) return true;
342     return false;
343   }
344
345   private static boolean skipValidation(PsiElement context) {
346     return context.getUserData(DO_NOT_VALIDATE_KEY) != null;
347   }
348   
349   public static void setSkipValidation(@NotNull PsiElement element) {
350     element.putUserData(DO_NOT_VALIDATE_KEY, "");
351   }
352
353   @Override public void visitXmlAttribute(XmlAttribute attribute) {}
354
355   private void checkAttribute(XmlAttribute attribute) {
356     XmlTag tag = attribute.getParent();
357
358     if (attribute.isNamespaceDeclaration()) {
359       checkReferences(attribute.getValueElement());
360       return;
361     }
362     final String namespace = attribute.getNamespace();
363
364     if (XmlUtil.XML_SCHEMA_INSTANCE_URI.equals(namespace)) {
365       checkReferences(attribute.getValueElement());
366       return;
367     }
368
369     XmlElementDescriptor elementDescriptor = tag.getDescriptor();
370     if (elementDescriptor == null ||
371         elementDescriptor instanceof AnyXmlElementDescriptor ||
372         ourDoJaxpTesting) {
373       return;
374     }
375
376     XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute);
377
378     final String name = attribute.getName();
379
380     if (attributeDescriptor == null) {
381       if (!XmlUtil.attributeFromTemplateFramework(name, tag)) {
382         final String localizedMessage = XmlErrorMessages.message("attribute.is.not.allowed.here", name);
383         final HighlightInfo highlightInfo = reportAttributeProblem(tag, name, attribute, localizedMessage);
384         if (highlightInfo != null) {
385           final XmlFile xmlFile = (XmlFile)tag.getContainingFile();
386           if (xmlFile != null) {
387             XmlExtension.getExtension(xmlFile).createAddAttributeFix(attribute, highlightInfo);
388           }
389         }
390       }
391     }
392     else {
393       checkDuplicateAttribute(tag, attribute);
394
395       if (tag instanceof HtmlTag &&
396           attribute.getValueElement() == null &&
397           !HtmlUtil.isSingleHtmlAttribute(name)
398          ) {
399         final String localizedMessage = XmlErrorMessages.message("empty.attribute.is.not.allowed", name);
400         reportAttributeProblem(tag, name, attribute, localizedMessage);
401       }
402
403       // we skip resolve of attribute references since there is separate check when taking attribute descriptors
404       PsiReference[] attrRefs = attribute.getReferences();
405       doCheckRefs(attribute, attrRefs, attribute.getNamespacePrefix().length() > 0 ? 2 : 1);
406     }
407   }
408
409   @Nullable
410   private HighlightInfo reportAttributeProblem(final XmlTag tag,
411                                                final String localName,
412                                                final XmlAttribute attribute,
413                                                final String localizedMessage) {
414
415     final RemoveAttributeIntentionFix removeAttributeIntention = new RemoveAttributeIntentionFix(localName,attribute);
416
417     if (!(tag instanceof HtmlTag)) {
418       final HighlightInfoType tagProblemInfoType = HighlightInfoType.WRONG_REF;
419
420       final ASTNode node = SourceTreeToPsiMap.psiElementToTree(attribute);
421       assert node != null;
422       final ASTNode child = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
423       assert child != null;
424       final HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
425         tagProblemInfoType, child,
426         localizedMessage
427       );
428       addToResults(highlightInfo);
429
430       QuickFixAction.registerQuickFixAction(highlightInfo, removeAttributeIntention);
431
432       return highlightInfo;
433     }
434
435     return null;
436   }
437
438   private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) {
439     if (skipValidation(tag)) {
440       return;
441     }
442
443     final XmlAttribute[] attributes = tag.getAttributes();
444     final PsiFile containingFile = tag.getContainingFile();
445     final XmlExtension extension = containingFile instanceof XmlFile ?
446                                    XmlExtension.getExtension(containingFile) :
447                                    XmlExtension.DEFAULT_EXTENSION;
448     for (XmlAttribute tagAttribute : attributes) {
449       ProgressManager.checkCanceled();
450       if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) {
451         final String localName = attribute.getLocalName();
452
453         if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive
454
455         HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
456           getTagProblemInfoType(tag),
457           XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(SourceTreeToPsiMap.psiElementToTree(attribute)),
458           XmlErrorMessages.message("duplicate.attribute", localName));
459         addToResults(highlightInfo);
460
461         IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute);
462
463         QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction);
464       }
465     }
466   }
467
468   @Override public void visitXmlDocument(final XmlDocument document) {
469     if (document.getLanguage() == DTDLanguage.INSTANCE) {
470       final PsiMetaData psiMetaData = document.getMetaData();
471       if (psiMetaData instanceof Validator) {
472         //noinspection unchecked
473         ((Validator<XmlDocument>)psiMetaData).validate(document, this);
474       }
475     }
476   }
477
478   @Override public void visitXmlTag(XmlTag tag) {
479   }
480
481   @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
482     final PsiElement parent = value.getParent();
483     if (!(parent instanceof XmlAttribute)) {
484       checkReferences(value);
485       return;
486     }
487
488     XmlAttribute attribute = (XmlAttribute)parent;
489
490     XmlTag tag = attribute.getParent();
491
492     XmlElementDescriptor elementDescriptor = tag.getDescriptor();
493     XmlAttributeDescriptor attributeDescriptor = elementDescriptor != null ? elementDescriptor.getAttributeDescriptor(attribute):null;
494
495     if (attributeDescriptor != null && value.getUserData(DO_NOT_VALIDATE_KEY) == null) {
496       String error = attributeDescriptor.validateValue(value, attribute.getValue());
497
498       if (error != null) {
499         addToResults(HighlightInfo.createHighlightInfo(
500             getTagProblemInfoType(tag),
501             value,
502             error));
503         return;
504       }
505     }
506
507     checkReferences(value);
508   }
509
510   private void checkReferences(PsiElement value) {
511     if (value == null) return;
512
513     doCheckRefs(value, value.getReferences());
514   }
515
516   private void doCheckRefs(final PsiElement value, final PsiReference[] references) {
517     doCheckRefs(value, references, 0);
518   }
519
520   private void doCheckRefs(final PsiElement value, final PsiReference[] references, int start) {
521     for (int i = start; i < references.length; ++i) {
522       PsiReference reference = references[i];
523       ProgressManager.checkCanceled();
524       if (reference == null) {
525         continue;
526       }
527       if (!reference.isSoft()) {
528         if(hasBadResolve(reference)) {
529           String description = getErrorDescription(reference);
530
531           final int startOffset = reference.getElement().getTextRange().getStartOffset();
532           final TextRange referenceRange = reference.getRangeInElement();
533
534           // logging for IDEADEV-29655
535           if (referenceRange.getStartOffset() > referenceRange.getEndOffset()) {
536             LOG.error("Reference range start offset > end offset:  " + reference +
537             ", start offset: " + referenceRange.getStartOffset() + ", end offset: " + referenceRange.getEndOffset());
538           }
539
540           HighlightInfo info = HighlightInfo.createHighlightInfo(
541             getTagProblemInfoType(PsiTreeUtil.getParentOfType(value, XmlTag.class)),
542             startOffset + referenceRange.getStartOffset(),
543             startOffset + referenceRange.getEndOffset(),
544             description
545           );
546           addToResults(info);
547           if (reference instanceof QuickFixProvider) ((QuickFixProvider)reference).registerQuickfix(info, reference);
548           UnresolvedReferenceQuickFixProvider.registerReferenceFixes(reference, new QuickFixActionRegistrarImpl(info));
549         }
550       }
551     }
552   }
553
554   public static String getErrorDescription(final PsiReference reference) {
555     String message;
556     if (reference instanceof EmptyResolveMessageProvider) {
557       message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
558     }
559     else {
560       //noinspection UnresolvedPropertyKey
561       message = PsiBundle.message("cannot.resolve.symbol");
562     }
563
564     String description;
565     try {
566       description = MessageFormat.format(message, reference.getCanonicalText());
567     } catch(IllegalArgumentException ex) {
568       // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
569       description = message;
570       LOG.warn(XmlErrorMessages.message("plugin.reference.message.problem",reference.getClass().getName(),message));
571     }
572     return description;
573   }
574
575   public static boolean hasBadResolve(final PsiReference reference) {
576     if (reference instanceof PsiPolyVariantReference) {
577       return ((PsiPolyVariantReference)reference).multiResolve(false).length == 0;
578     }
579     return reference.resolve() == null;
580   }
581
582   @Override public void visitXmlDoctype(XmlDoctype xmlDoctype) {
583     if (skipValidation(xmlDoctype)) return;
584     checkReferences(xmlDoctype);
585   }
586
587   private void addToResults(final HighlightInfo info) {
588     if (myResult == null) myResult = new SmartList<HighlightInfo>();
589     myResult.add(info);
590   }
591
592   public static void setDoJaxpTesting(boolean doJaxpTesting) {
593     ourDoJaxpTesting = doJaxpTesting;
594   }
595
596   public void addMessage(PsiElement context, String message, int type) {
597     if (message != null && message.length() > 0) {
598       if (context instanceof XmlTag && XmlExtension.getExtension(context.getContainingFile()).shouldBeHighlightedAsTag((XmlTag)context)) {
599         addElementsForTag((XmlTag)context, message, type == ERROR ? HighlightInfoType.ERROR : type == WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO, null);
600       }
601       else {
602         addToResults(HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message));
603       }
604     }
605   }
606
607   public void addMessage(final PsiElement context, final String message, final ErrorType type, final IntentionAction... fixes) {
608     if (message != null && message.length() > 0) {
609       final PsiFile containingFile = context.getContainingFile();
610       final HighlightInfoType defaultInfoType = type == ErrorType.ERROR ? HighlightInfoType.ERROR : type == ErrorType.WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO;
611
612       if (context instanceof XmlTag && XmlExtension.getExtension(containingFile).shouldBeHighlightedAsTag((XmlTag)context)) {
613         addElementsForTagWithManyQuickFixes((XmlTag)context, message, defaultInfoType, fixes);
614       }
615       else {
616         final PsiElement contextOfFile = containingFile.getContext();
617         final HighlightInfo highlightInfo;
618
619         if (contextOfFile != null) {
620           final int offsetInRealDocument = PsiUtilBase.findInjectedElementOffsetInRealDocument(context);
621           highlightInfo = HighlightInfo.createHighlightInfo(defaultInfoType, context.getTextRange().shiftRight(offsetInRealDocument), message);
622         } else {
623           highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message);
624         }
625
626         if (fixes != null) {
627           for (final IntentionAction quickFixAction : fixes) {
628             if (quickFixAction == null) continue;
629             QuickFixAction.registerQuickFixAction(highlightInfo, quickFixAction);
630           }
631         }
632         addToResults(highlightInfo);
633       }
634     }
635   }
636
637   public static void visitJspElement(OuterLanguageElement text) {
638     PsiElement parent = text.getParent();
639
640     if (parent instanceof XmlText) {
641       parent = parent.getParent();
642     }
643
644     parent.putUserData(DO_NOT_VALIDATE_KEY, "");
645   }
646
647   public boolean suitableForFile(final PsiFile file) {
648     return file instanceof XmlFile;
649   }
650
651   public void visit(final PsiElement element, final HighlightInfoHolder holder) {
652     element.accept(this);
653
654     List<HighlightInfo> result = myResult;
655     holder.addAll(result);
656     myResult = null;
657   }
658
659   public boolean analyze(Runnable action, final boolean updateWholeFile, final PsiFile file) {
660     try {
661       action.run();
662     }
663     finally {
664       myResult = null;
665     }
666     return true;
667   }
668
669   public HighlightVisitor clone() {
670     return new XmlHighlightVisitor();
671   }
672
673   public int order() {
674     return 1;
675   }
676
677   public static String getUnquotedValue(XmlAttributeValue value, XmlTag tag) {
678     String unquotedValue = StringUtil.stripQuotesAroundValue(value.getText());
679
680     if (tag instanceof HtmlTag) {
681       unquotedValue = unquotedValue.toLowerCase();
682     }
683
684     return unquotedValue;
685   }
686 }