run highlight visitors for injected fragments
[fedora-idea.git] / xml / impl / src / com / intellij / codeInsight / daemon / impl / analysis / XmlHighlightVisitor.java
blob691940fa6f07bba0ca94cbe9e55ea92a1131328d
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;
395 final ASTNode node = SourceTreeToPsiMap.psiElementToTree(attribute);
396 assert node != null;
397 final ASTNode child = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node);
398 assert child != null;
399 final HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
400 tagProblemInfoType, child,
401 localizedMessage
403 addToResults(highlightInfo);
405 QuickFixAction.registerQuickFixAction(highlightInfo, removeAttributeIntention);
407 return highlightInfo;
410 return null;
413 private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) {
414 if (tag.getUserData(DO_NOT_VALIDATE_KEY) != null) {
415 return;
418 final XmlAttribute[] attributes = tag.getAttributes();
419 final PsiFile containingFile = tag.getContainingFile();
420 final XmlExtension extension = containingFile instanceof XmlFile ?
421 XmlExtension.getExtension(containingFile) :
422 XmlExtension.DEFAULT_EXTENSION;
423 for (XmlAttribute tagAttribute : attributes) {
424 ProgressManager.checkCanceled();
425 if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) {
426 final String localName = attribute.getLocalName();
428 if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive
430 HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(
431 getTagProblemInfoType(tag),
432 XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(SourceTreeToPsiMap.psiElementToTree(attribute)),
433 XmlErrorMessages.message("duplicate.attribute", localName));
434 addToResults(highlightInfo);
436 IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute);
438 QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction);
443 @Override public void visitXmlDocument(final XmlDocument document) {
444 if (document.getLanguage() == DTDLanguage.INSTANCE) {
445 final PsiMetaData psiMetaData = document.getMetaData();
446 if (psiMetaData instanceof Validator) {
447 //noinspection unchecked
448 ((Validator<XmlDocument>)psiMetaData).validate(document, this);
453 @Override public void visitXmlTag(XmlTag tag) {
456 @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
457 final PsiElement parent = value.getParent();
458 if (!(parent instanceof XmlAttribute)) {
459 checkReferences(value);
460 return;
463 XmlAttribute attribute = (XmlAttribute)parent;
465 XmlTag tag = attribute.getParent();
467 XmlElementDescriptor elementDescriptor = tag.getDescriptor();
468 XmlAttributeDescriptor attributeDescriptor = elementDescriptor != null ? elementDescriptor.getAttributeDescriptor(attribute):null;
470 if (attributeDescriptor != null && value.getUserData(DO_NOT_VALIDATE_KEY) == null) {
471 String error = attributeDescriptor.validateValue(value, attribute.getValue());
473 if (error != null) {
474 addToResults(HighlightInfo.createHighlightInfo(
475 getTagProblemInfoType(tag),
476 value,
477 error));
478 return;
482 checkReferences(value);
485 private void checkReferences(PsiElement value) {
486 if (value == null) return;
488 doCheckRefs(value, value.getReferences());
491 private void doCheckRefs(final PsiElement value, final PsiReference[] references) {
492 doCheckRefs(value, references, 0);
495 private void doCheckRefs(final PsiElement value, final PsiReference[] references, int start) {
496 for (int i = start; i < references.length; ++i) {
497 PsiReference reference = references[i];
498 ProgressManager.checkCanceled();
499 if (reference == null) {
500 continue;
502 if (!reference.isSoft()) {
503 if(hasBadResolve(reference)) {
504 String description = getErrorDescription(reference);
506 final int startOffset = reference.getElement().getTextRange().getStartOffset();
507 final TextRange referenceRange = reference.getRangeInElement();
509 // logging for IDEADEV-29655
510 if (referenceRange.getStartOffset() > referenceRange.getEndOffset()) {
511 LOG.error("Reference range start offset > end offset: " + reference +
512 ", start offset: " + referenceRange.getStartOffset() + ", end offset: " + referenceRange.getEndOffset());
515 HighlightInfo info = HighlightInfo.createHighlightInfo(
516 getTagProblemInfoType(PsiTreeUtil.getParentOfType(value, XmlTag.class)),
517 startOffset + referenceRange.getStartOffset(),
518 startOffset + referenceRange.getEndOffset(),
519 description
521 addToResults(info);
522 if (reference instanceof QuickFixProvider) ((QuickFixProvider)reference).registerQuickfix(info, reference);
528 public static String getErrorDescription(final PsiReference reference) {
529 String message;
530 if (reference instanceof EmptyResolveMessageProvider) {
531 message = ((EmptyResolveMessageProvider)reference).getUnresolvedMessagePattern();
533 else {
534 //noinspection UnresolvedPropertyKey
535 message = PsiBundle.message("cannot.resolve.symbol");
538 String description;
539 try {
540 description = MessageFormat.format(message, reference.getCanonicalText());
541 } catch(IllegalArgumentException ex) {
542 // unresolvedMessage provided by third-party reference contains wrong format string (e.g. {}), tolerate it
543 description = message;
544 LOG.warn(XmlErrorMessages.message("plugin.reference.message.problem",reference.getClass().getName(),message));
546 return description;
549 public static boolean hasBadResolve(final PsiReference reference) {
550 if (reference instanceof PsiPolyVariantReference) {
551 return ((PsiPolyVariantReference)reference).multiResolve(false).length == 0;
553 return reference.resolve() == null;
556 @Override public void visitXmlDoctype(XmlDoctype xmlDoctype) {
557 if (xmlDoctype.getUserData(DO_NOT_VALIDATE_KEY) != null) return;
558 checkReferences(xmlDoctype);
561 private void addToResults(final HighlightInfo info) {
562 if (myResult == null) myResult = new SmartList<HighlightInfo>();
563 myResult.add(info);
566 public static void setDoJaxpTesting(boolean doJaxpTesting) {
567 ourDoJaxpTesting = doJaxpTesting;
570 public void addMessage(PsiElement context, String message, int type) {
571 if (message != null && message.length() > 0) {
572 if (context instanceof XmlTag && XmlExtension.getExtension(context.getContainingFile()).shouldBeHighlightedAsTag((XmlTag)context)) {
573 addElementsForTag((XmlTag)context, message, type == ERROR ? HighlightInfoType.ERROR : type == WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO, null);
575 else {
576 addToResults(HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message));
581 public void addMessage(final PsiElement context, final String message, final ErrorType type, final IntentionAction... fixes) {
582 if (message != null && message.length() > 0) {
583 final PsiFile containingFile = context.getContainingFile();
584 final HighlightInfoType defaultInfoType = type == ErrorType.ERROR ? HighlightInfoType.ERROR : type == ErrorType.WARNING ? HighlightInfoType.WARNING : HighlightInfoType.INFO;
586 if (context instanceof XmlTag && XmlExtension.getExtension(containingFile).shouldBeHighlightedAsTag((XmlTag)context)) {
587 addElementsForTagWithManyQuickFixes((XmlTag)context, message, defaultInfoType, fixes);
589 else {
590 final PsiElement contextOfFile = containingFile.getContext();
591 final HighlightInfo highlightInfo;
593 if (contextOfFile != null) {
594 final int offsetInRealDocument = PsiUtilBase.findInjectedElementOffsetInRealDocument(context);
595 highlightInfo = HighlightInfo.createHighlightInfo(defaultInfoType, context.getTextRange().shiftRight(offsetInRealDocument), message);
596 } else {
597 highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, context, message);
600 if (fixes != null) {
601 for (final IntentionAction quickFixAction : fixes) {
602 if (quickFixAction == null) continue;
603 QuickFixAction.registerQuickFixAction(highlightInfo, quickFixAction);
606 addToResults(highlightInfo);
611 public static void visitJspElement(OuterLanguageElement text) {
612 PsiElement parent = text.getParent();
614 if (parent instanceof XmlText) {
615 parent = parent.getParent();
618 parent.putUserData(DO_NOT_VALIDATE_KEY, "");
621 public boolean suitableForFile(final PsiFile file) {
622 return file instanceof XmlFile;
625 public void visit(final PsiElement element, final HighlightInfoHolder holder) {
626 element.accept(this);
628 List<HighlightInfo> result = myResult;
629 holder.addAll(result);
630 myResult = null;
633 public boolean analyze(Runnable action, final boolean updateWholeFile, final PsiFile file) {
634 try {
635 action.run();
637 finally {
638 myResult = null;
640 return true;
643 public HighlightVisitor clone() {
644 return new XmlHighlightVisitor();
647 public int order() {
648 return 1;
651 public static String getUnquotedValue(XmlAttributeValue value, XmlTag tag) {
652 String unquotedValue = StringUtil.stripQuotesAroundValue(value.getText());
654 if (tag instanceof HtmlTag) {
655 unquotedValue = unquotedValue.toLowerCase();
658 return unquotedValue;