update copyright
[fedora-idea.git] / xml / impl / src / com / intellij / codeInsight / daemon / impl / analysis / XmlHighlightVisitor.java
blobc347c0f4fe6b4e6d8527e71b067b29e127452902
1 /*
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.codeInsight.daemon.impl.analysis;
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.intention.IntentionAction;
25 import com.intellij.codeInspection.InspectionProfile;
26 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
27 import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection;
28 import com.intellij.codeInspection.htmlInspections.XmlEntitiesInspection;
29 import com.intellij.idea.LoggerFactory;
30 import com.intellij.lang.ASTNode;
31 import com.intellij.lang.dtd.DTDLanguage;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.editor.markup.TextAttributes;
34 import com.intellij.openapi.progress.ProgressManager;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Key;
37 import com.intellij.openapi.util.TextRange;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
40 import com.intellij.psi.*;
41 import com.intellij.psi.html.HtmlTag;
42 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
43 import com.intellij.psi.meta.PsiMetaData;
44 import com.intellij.psi.templateLanguages.OuterLanguageElement;
45 import com.intellij.psi.util.PsiTreeUtil;
46 import com.intellij.psi.util.PsiUtilBase;
47 import com.intellij.psi.xml.*;
48 import com.intellij.util.SmartList;
49 import com.intellij.xml.XmlAttributeDescriptor;
50 import com.intellij.xml.XmlElementDescriptor;
51 import com.intellij.xml.XmlExtension;
52 import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
53 import com.intellij.xml.util.HtmlUtil;
54 import com.intellij.xml.util.XmlTagUtil;
55 import com.intellij.xml.util.XmlUtil;
56 import org.jetbrains.annotations.Nullable;
58 import java.text.MessageFormat;
59 import java.util.HashSet;
60 import java.util.List;
61 import java.util.Set;
62 import java.util.StringTokenizer;
64 /**
65 * @author Mike
67 public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightVisitor, Validator.ValidationHost {
68 private static final Logger LOG = LoggerFactory.getInstance().getLoggerInstance("com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor");
69 public static final Key<String> DO_NOT_VALIDATE_KEY = Key.create("do not validate");
70 private List<HighlightInfo> myResult;
72 private static boolean ourDoJaxpTesting;
74 private static final TextAttributes NONEMPTY_TEXT_ATTRIBUTES = new TextAttributes() {
75 public boolean isEmpty() {
76 return false;
80 private void addElementsForTag(XmlTag tag,
81 String localizedMessage,
82 HighlightInfoType type,
83 IntentionAction quickFixAction) {
84 addElementsForTagWithManyQuickFixes(tag, localizedMessage, type, quickFixAction);
87 private void addElementsForTagWithManyQuickFixes(XmlTag tag,
88 String localizedMessage,
89 HighlightInfoType type, IntentionAction... quickFixActions) {
90 bindMessageToTag(tag, type, -1, localizedMessage, quickFixActions);
93 @Override public void visitXmlToken(XmlToken token) {
94 if (token.getTokenType() == XmlTokenType.XML_NAME || token.getTokenType() == XmlTokenType.XML_TAG_NAME) {
95 PsiElement element = token.getPrevSibling();
96 while(element instanceof PsiWhiteSpace) element = element.getPrevSibling();
98 if (element instanceof XmlToken) {
99 if (((XmlToken)element).getTokenType() == XmlTokenType.XML_START_TAG_START) {
100 PsiElement parent = element.getParent();
102 if (parent instanceof XmlTag && !(token.getNextSibling() instanceof OuterLanguageElement)) {
103 checkTag((XmlTag)parent);
106 } else {
107 PsiElement parent = token.getParent();
109 if (parent instanceof XmlAttribute && !(token.getNextSibling() instanceof OuterLanguageElement)) {
110 checkAttribute((XmlAttribute) parent);
116 private void checkTag(XmlTag tag) {
117 if (ourDoJaxpTesting) return;
119 if (myResult == null) {
120 checkTagByDescriptor(tag);
123 if (myResult == null) {
124 if (tag.getUserData(DO_NOT_VALIDATE_KEY) == null) {
125 final XmlElementDescriptor descriptor = tag.getDescriptor();
127 if (tag instanceof HtmlTag &&
128 ( descriptor instanceof AnyXmlElementDescriptor ||
129 descriptor == null
132 return;
135 checkReferences(tag);
140 private void bindMessageToTag(final XmlTag tag, final HighlightInfoType warning, final int messageLength, final String localizedMessage, IntentionAction... quickFixActions) {
141 XmlToken childByRole = XmlTagUtil.getStartTagNameElement(tag);
143 bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
144 childByRole = XmlTagUtil.getEndTagNameElement(tag);
145 bindMessageToAstNode(childByRole, warning, 0, messageLength, localizedMessage, quickFixActions);
148 private void bindMessageToAstNode(final PsiElement childByRole,
149 final HighlightInfoType warning,
150 final int offset,
151 int length,
152 final String localizedMessage, IntentionAction... quickFixActions) {
153 if(childByRole != null) {
154 final TextRange textRange = childByRole.getTextRange();
155 if (length == -1) length = textRange.getLength();
156 final int startOffset = textRange.getStartOffset() + offset;
158 HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
159 warning,
160 childByRole, startOffset, startOffset + length,
161 localizedMessage, HighlightInfo.htmlEscapeToolTip(localizedMessage)
164 if (highlightInfo == null) {
165 highlightInfo = HighlightInfo.createHighlightInfo(
166 warning,
167 new TextRange(startOffset, startOffset + length),
168 localizedMessage,
169 localizedMessage, NONEMPTY_TEXT_ATTRIBUTES
173 for (final IntentionAction quickFixAction : quickFixActions) {
174 if (quickFixAction == null) continue;
175 QuickFixAction.registerQuickFixAction(highlightInfo, textRange, quickFixAction, null);
177 addToResults(highlightInfo);
181 private void checkTagByDescriptor(final XmlTag tag) {
182 String name = tag.getName();
184 XmlElementDescriptor elementDescriptor = null;
186 final PsiElement parent = tag.getParent();
187 if (parent instanceof XmlTag) {
188 XmlTag parentTag = (XmlTag)parent;
189 final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
191 if (parentDescriptor != null) {
192 elementDescriptor = XmlExtension.getExtension(tag.getContainingFile()).getElementDescriptor(tag, parentTag, parentDescriptor);
195 if (parentDescriptor != null &&
196 elementDescriptor == null &&
197 parentTag.getUserData(DO_NOT_VALIDATE_KEY) == null &&
198 !XmlUtil.tagFromTemplateFramework(tag)
200 if (tag instanceof HtmlTag) {
201 //XmlEntitiesInspection inspection = getInspectionProfile(tag, HtmlStyleLocalInspection.SHORT_NAME);
202 //if (inspection != null /*&& isAdditionallyDeclared(inspection.getAdditionalEntries(XmlEntitiesInspection.UNKNOWN_TAG), name)*/) {
203 return;
207 addElementsForTag(
208 tag,
209 XmlErrorMessages.message("element.is.not.allowed.here", name),
210 getTagProblemInfoType(tag),
211 null
213 return;
216 if (elementDescriptor instanceof AnyXmlElementDescriptor ||
217 elementDescriptor == null
219 elementDescriptor = tag.getDescriptor();
222 if (elementDescriptor == null) return;
224 else {
225 //root tag
226 elementDescriptor = tag.getDescriptor();
228 if (elementDescriptor == null) {
229 addElementsForTag(tag, XmlErrorMessages.message("element.must.be.declared", name), HighlightInfoType.WRONG_REF, null);
230 return;
234 XmlAttributeDescriptor[] attributeDescriptors = elementDescriptor.getAttributesDescriptors(tag);
235 Set<String> requiredAttributes = null;
237 for (XmlAttributeDescriptor attribute : attributeDescriptors) {
238 if (attribute != null && attribute.isRequired()) {
239 if (requiredAttributes == null) {
240 requiredAttributes = new HashSet<String>();
242 requiredAttributes.add(attribute.getName(tag));
246 if (requiredAttributes != null) {
247 for (final String attrName : requiredAttributes) {
248 if (tag.getAttribute(attrName, "") == null &&
249 !XmlExtension.getExtension(tag.getContainingFile()).isRequiredAttributeImplicitlyPresent(tag, attrName)) {
251 final InsertRequiredAttributeFix insertRequiredAttributeIntention = new InsertRequiredAttributeFix(
252 tag, attrName, null);
253 final String localizedMessage = XmlErrorMessages.message("element.doesnt.have.required.attribute", name, attrName);
254 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
255 final LocalInspectionToolWrapper toolWrapper =
256 (LocalInspectionToolWrapper)profile.getInspectionTool(RequiredAttributesInspection.SHORT_NAME, tag);
257 if (toolWrapper != null) {
258 RequiredAttributesInspection inspection = (RequiredAttributesInspection)toolWrapper.getTool();
259 reportOneTagProblem(
260 tag,
261 attrName,
262 localizedMessage,
263 insertRequiredAttributeIntention,
264 HighlightDisplayKey.find(RequiredAttributesInspection.SHORT_NAME),
265 inspection,
266 XmlEntitiesInspection.NOT_REQUIRED_ATTRIBUTE
273 if (elementDescriptor instanceof Validator) {
274 //noinspection unchecked
275 ((Validator<XmlTag>)elementDescriptor).validate(tag,this);
279 private void reportOneTagProblem(final XmlTag tag,
280 final String name,
281 final String localizedMessage,
282 final IntentionAction basicIntention,
283 final HighlightDisplayKey key,
284 final XmlEntitiesInspection inspection,
285 final int type) {
286 boolean htmlTag = false;
288 if (tag instanceof HtmlTag) {
289 htmlTag = true;
290 if(isAdditionallyDeclared(inspection.getAdditionalEntries(type), name)) return;
293 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(tag.getProject()).getInspectionProfile();
294 final IntentionAction intentionAction = inspection.getIntentionAction(name, type);
295 if (htmlTag && profile.isToolEnabled(key, tag)) {
296 addElementsForTagWithManyQuickFixes(
297 tag,
298 localizedMessage,
299 SeverityRegistrar.getInstance(tag.getProject()).getHighlightInfoTypeBySeverity(profile.getErrorLevel(key, tag).getSeverity()),
300 intentionAction,
301 basicIntention);
302 } else if (!htmlTag) {
303 addElementsForTag(
304 tag,
305 localizedMessage,
306 HighlightInfoType.ERROR,
307 basicIntention
312 private static boolean isAdditionallyDeclared(final String additional, String name) {
313 name = name.toLowerCase();
314 if (!additional.contains(name)) return false;
316 StringTokenizer tokenizer = new StringTokenizer(additional, ", ");
317 while (tokenizer.hasMoreTokens()) {
318 if (name.equals(tokenizer.nextToken())) {
319 return true;
323 return false;
326 private static HighlightInfoType getTagProblemInfoType(XmlTag tag) {
327 return tag instanceof HtmlTag && XmlUtil.HTML_URI.equals(tag.getNamespace()) ? HighlightInfoType.WARNING : HighlightInfoType.WRONG_REF;
330 @Override public void visitXmlAttribute(XmlAttribute attribute) {}
332 private void checkAttribute(XmlAttribute attribute) {
333 XmlTag tag = attribute.getParent();
335 if (attribute.isNamespaceDeclaration()) {
336 checkReferences(attribute.getValueElement());
337 return;
339 final String namespace = attribute.getNamespace();
341 if (XmlUtil.XML_SCHEMA_INSTANCE_URI.equals(namespace)) {
342 checkReferences(attribute.getValueElement());
343 return;
346 XmlElementDescriptor elementDescriptor = tag.getDescriptor();
347 if (elementDescriptor == null ||
348 elementDescriptor instanceof AnyXmlElementDescriptor ||
349 ourDoJaxpTesting) {
350 return;
353 XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute);
355 final String name = attribute.getName();
357 if (attributeDescriptor == null) {
358 if (!XmlUtil.attributeFromTemplateFramework(name, tag)) {
359 final String localizedMessage = XmlErrorMessages.message("attribute.is.not.allowed.here", name);
360 final HighlightInfo highlightInfo = reportAttributeProblem(tag, name, attribute, localizedMessage);
361 if (highlightInfo != null) {
362 final XmlFile xmlFile = (XmlFile)tag.getContainingFile();
363 if (xmlFile != null) {
364 XmlExtension.getExtension(xmlFile).createAddAttributeFix(attribute, highlightInfo);
369 else {
370 checkDuplicateAttribute(tag, attribute);
372 if (tag instanceof HtmlTag &&
373 attribute.getValueElement() == null &&
374 !HtmlUtil.isSingleHtmlAttribute(name)
376 final String localizedMessage = XmlErrorMessages.message("empty.attribute.is.not.allowed", name);
377 reportAttributeProblem(tag, name, attribute, localizedMessage);
380 doCheckRefs(attribute, attribute.getReferences(), 1);
384 @Nullable
385 private HighlightInfo reportAttributeProblem(final XmlTag tag,
386 final String localName,
387 final XmlAttribute attribute,
388 final String localizedMessage) {
390 final RemoveAttributeIntentionFix removeAttributeIntention = new RemoveAttributeIntentionFix(localName,attribute);
392 if (!(tag instanceof HtmlTag)) {
393 final HighlightInfoType tagProblemInfoType = HighlightInfoType.WRONG_REF;
394 IntentionAction[] quickFixes = new IntentionAction[]{removeAttributeIntention};
396 final ASTNode node = SourceTreeToPsiMap.psiElementToTree(attribute);
397 assert node != null;
398 final ASTNode child = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
399 assert child != null;
400 final HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
401 tagProblemInfoType, child,
402 localizedMessage
404 addToResults(highlightInfo);
406 for (IntentionAction quickFix : quickFixes) {
407 QuickFixAction.registerQuickFixAction(highlightInfo, quickFix);
410 return highlightInfo;
413 return null;
416 private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) {
417 if (tag.getUserData(DO_NOT_VALIDATE_KEY) != null) {
418 return;
421 final XmlAttribute[] attributes = tag.getAttributes();
422 ProgressManager progressManager = ProgressManager.getInstance();
423 final PsiFile containingFile = tag.getContainingFile();
424 final XmlExtension extension = containingFile instanceof XmlFile ?
425 XmlExtension.getExtension(containingFile) :
426 XmlExtension.DEFAULT_EXTENSION;
427 for (XmlAttribute tagAttribute : attributes) {
428 progressManager.checkCanceled();
429 if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) {
430 final String localName = attribute.getLocalName();
432 if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive
434 HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
435 getTagProblemInfoType(tag),
436 XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(SourceTreeToPsiMap.psiElementToTree(attribute)),
437 XmlErrorMessages.message("duplicate.attribute", localName));
438 addToResults(highlightInfo);
440 IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute);
442 QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction);
447 @Override public void visitXmlDocument(final XmlDocument document) {
448 if (document.getLanguage() == DTDLanguage.INSTANCE) {
449 final PsiMetaData psiMetaData = document.getMetaData();
450 if (psiMetaData instanceof Validator) {
451 //noinspection unchecked
452 ((Validator<XmlDocument>)psiMetaData).validate(document, this);
457 @Override public void visitXmlTag(XmlTag tag) {
460 @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
461 final PsiElement parent = value.getParent();
462 if (!(parent instanceof XmlAttribute)) {
463 checkReferences(value);
464 return;
467 XmlAttribute attribute = (XmlAttribute)parent;
469 XmlTag tag = attribute.getParent();
471 XmlElementDescriptor elementDescriptor = tag.getDescriptor();
472 XmlAttributeDescriptor attributeDescriptor = elementDescriptor != null ? elementDescriptor.getAttributeDescriptor(attribute):null;
474 if (attributeDescriptor != null && value.getUserData(DO_NOT_VALIDATE_KEY) == null) {
475 String error = attributeDescriptor.validateValue(value, attribute.getValue());
477 if (error != null) {
478 addToResults(HighlightInfo.createHighlightInfo(
479 getTagProblemInfoType(tag),
480 value,
481 error));
482 return;
486 checkReferences(value);
489 private void checkReferences(PsiElement value) {
490 if (value == null) return;
492 doCheckRefs(value, value.getReferences());
495 private void doCheckRefs(final PsiElement value, final PsiReference[] references) {
496 doCheckRefs(value, references, 0);
499 private void doCheckRefs(final PsiElement value, final PsiReference[] references, int start) {
500 ProgressManager progressManager = ProgressManager.getInstance();
501 for (int i = start; i < references.length; ++i) {
502 PsiReference reference = references[i];
503 progressManager.checkCanceled();
504 if (reference == null) {
505 continue;
507 if (!reference.isSoft()) {
508 if(hasBadResolve(reference)) {
509 String description = getErrorDescription(reference);
511 final int startOffset = reference.getElement().getTextRange().getStartOffset();
512 final TextRange referenceRange = reference.getRangeInElement();
514 // logging for IDEADEV-29655
515 if (referenceRange.getStartOffset() > referenceRange.getEndOffset()) {
516 LOG.error("Reference range start offset > end offset: " + reference +
517 ", start offset: " + referenceRange.getStartOffset() + ", end offset: " + referenceRange.getEndOffset());
520 HighlightInfo info = HighlightInfo.createHighlightInfo(
521 getTagProblemInfoType(PsiTreeUtil.getParentOfType(value, XmlTag.class)),
522 startOffset + referenceRange.getStartOffset(),
523 startOffset + referenceRange.getEndOffset(),
524 description
526 addToResults(info);
527 if (reference instanceof QuickFixProvider) ((QuickFixProvider)reference).registerQuickfix(info, reference);
533 public static String getErrorDescription(final PsiReference reference) {
534 String message;
535 if (reference instanceof EmptyResolveMessageProvider) {
536 message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
538 else {
539 //noinspection UnresolvedPropertyKey
540 message = PsiBundle.message("cannot.resolve.symbol");
543 String description;
544 try {
545 description = MessageFormat.format(message, reference.getCanonicalText());
546 } catch(IllegalArgumentException ex) {
547 // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
548 description = message;
549 LOG.warn(XmlErrorMessages.message("plugin.reference.message.problem",reference.getClass().getName(),message));
551 return description;
554 public static boolean hasBadResolve(final PsiReference reference) {
555 if (reference instanceof PsiPolyVariantReference) {
556 return ((PsiPolyVariantReference)reference).multiResolve(false).length == 0;
558 return reference.resolve() == null;
561 @Override public void visitXmlDoctype(XmlDoctype xmlDoctype) {
562 if (xmlDoctype.getUserData(DO_NOT_VALIDATE_KEY) != null) return;
563 checkReferences(xmlDoctype);
566 private void addToResults(final HighlightInfo info) {
567 if (myResult == null) myResult = new SmartList<HighlightInfo>();
568 myResult.add(info);
571 public static void setDoJaxpTesting(boolean doJaxpTesting) {
572 ourDoJaxpTesting = doJaxpTesting;
575 public void addMessage(PsiElement context, String message, int type) {
576 if (message != null && message.length() > 0) {
577 if (context instanceof XmlTag && XmlExtension.getExtension(context.getContainingFile()).shouldBeHighlightedAsTag((XmlTag)context)) {
578 addElementsForTag((XmlTag)context, message, type == ERROR ? HighlightInfoType.ERROR : type == WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO, null);
580 else {
581 addToResults(HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message));
586 public void addMessage(final PsiElement context, final String message, final ErrorType type, final IntentionAction... fixes) {
587 if (message != null && message.length() > 0) {
588 final PsiFile containingFile = context.getContainingFile();
589 final HighlightInfoType defaultInfoType = type == ErrorType.ERROR ? HighlightInfoType.ERROR : type == ErrorType.WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO;
591 if (context instanceof XmlTag && XmlExtension.getExtension(containingFile).shouldBeHighlightedAsTag((XmlTag)context)) {
592 addElementsForTagWithManyQuickFixes((XmlTag)context, message, defaultInfoType, fixes);
594 else {
595 final PsiElement contextOfFile = containingFile.getContext();
596 final HighlightInfo highlightInfo;
598 if (contextOfFile != null) {
599 final int offsetInRealDocument = PsiUtilBase.findInjectedElementOffsetInRealDocument(context);
600 highlightInfo = HighlightInfo.createHighlightInfo(defaultInfoType, context.getTextRange().shiftRight(offsetInRealDocument), message);
601 } else {
602 highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message);
605 if (fixes != null) {
606 for (final IntentionAction quickFixAction : fixes) {
607 if (quickFixAction == null) continue;
608 QuickFixAction.registerQuickFixAction(highlightInfo, quickFixAction);
611 addToResults(highlightInfo);
616 public static void visitJspElement(OuterLanguageElement text) {
617 PsiElement parent = text.getParent();
619 if (parent instanceof XmlText) {
620 parent = parent.getParent();
623 parent.putUserData(DO_NOT_VALIDATE_KEY, "");
626 public boolean suitableForFile(final PsiFile file) {
627 return file instanceof XmlFile;
630 public void visit(final PsiElement element, final HighlightInfoHolder holder) {
631 element.accept(this);
633 List<HighlightInfo> result = myResult;
634 holder.addAll(result);
635 myResult = null;
638 public boolean analyze(Runnable action, final boolean updateWholeFile, final PsiFile file) {
639 try {
640 action.run();
642 finally {
643 myResult = null;
645 return true;
648 public HighlightVisitor clone() {
649 return new XmlHighlightVisitor();
652 public int order() {
653 return 1;
656 public static String getUnquotedValue(XmlAttributeValue value, XmlTag tag) {
657 String unquotedValue = StringUtil.stripQuotesAroundValue(value.getText());
659 if (tag instanceof HtmlTag) {
660 unquotedValue = unquotedValue.toLowerCase();
663 return unquotedValue;