All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.xml.impl.ExternalDocumentValidator Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition xml-psi-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.xml.impl;

import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.codeInspection.InspectionProfile;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.ide.highlighter.XHtmlFileType;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.reference.SoftReference;
import com.intellij.xml.actions.validate.ErrorReporter;
import com.intellij.xml.actions.validate.ValidateXmlActionHandler;
import com.intellij.xml.util.XmlResourceResolver;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.xml.sax.SAXParseException;

import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.List;

/**
 * @author maxim
 */
public class ExternalDocumentValidator {
  private static final Logger LOG = Logger.getInstance("#com.intellij.xml.impl.ExternalDocumentValidator");
  private static final Key> validatorInstanceKey = Key.create("validatorInstance");

  public static final @NonNls String INSPECTION_SHORT_NAME = "CheckXmlFileWithXercesValidator";

  private ValidateXmlActionHandler myHandler;
  private Validator.ValidationHost myHost;

  private long myModificationStamp;
  private PsiFile myFile;
  @NonNls
  private static final String CANNOT_FIND_DECLARATION_ERROR_PREFIX = "Cannot find the declaration of element";
  @NonNls
  private static final String ELEMENT_ERROR_PREFIX = "Element";
  @NonNls
  private static final String ROOT_ELEMENT_ERROR_PREFIX = "Document root element";
  @NonNls
  private static final String CONTENT_OF_ELEMENT_TYPE_ERROR_PREFIX = "The content of element type";
  @NonNls
  private static final String VALUE_ERROR_PREFIX = "Value ";
  @NonNls
  private static final String ATTRIBUTE_ERROR_PREFIX = "Attribute ";
  @NonNls
  private static final String STRING_ERROR_PREFIX = "The string";
  @NonNls
  private static final String ATTRIBUTE_MESSAGE_PREFIX = "cvc-attribute.";

  private static class ValidationInfo {
    final PsiElement element;
    final String message;
    final Validator.ValidationHost.ErrorType type;

    private ValidationInfo(PsiElement element, String message, Validator.ValidationHost.ErrorType type) {
      this.element = element;
      this.message = message;
      this.type = type;
    }
  }

  private WeakReference> myInfos; // last jaxp validation result

  private void runJaxpValidation(final XmlElement element, Validator.ValidationHost host) {
    final PsiFile file = element.getContainingFile();

    if (myFile == file &&
        file != null &&
        myModificationStamp == file.getModificationStamp() &&
        !ValidateXmlActionHandler.isValidationDependentFilesOutOfDate((XmlFile)file) &&
        SoftReference.dereference(myInfos)!=null // we have validated before
        ) {
      addAllInfos(host,myInfos.get());
      return;
    }

    if (myHandler==null)  myHandler = new ValidateXmlActionHandler(false);
    final Project project = element.getProject();

    final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
    if (document==null) return;
    final List results = new LinkedList();

    myHost = new Validator.ValidationHost() {
      @Override
      public void addMessage(PsiElement context, String message, int type) {
        addMessage(context, message, type==ERROR?ErrorType.ERROR : type==WARNING?ErrorType.WARNING : ErrorType.INFO);
      }

      @Override
      public void addMessage(final PsiElement context, final String message, @NotNull final ErrorType type) {
        final ValidationInfo o = new ValidationInfo(context, message, type);
        results.add(o);
      }
    };

    myHandler.setErrorReporter(new ErrorReporter(myHandler) {
      @Override
      public boolean isStopOnUndeclaredResource() {
        return true;
      }

      @Override
      public void processError(final SAXParseException e, final ValidateXmlActionHandler.ProblemType warning) {
        try {
          ApplicationManager.getApplication().runReadAction(new Runnable() {
            @Override
            public void run() {
              if (e.getPublicId() != null) {
                return;
              }

              final VirtualFile errorFile = myHandler.getProblemFile(e);
              if (!Comparing.equal(errorFile, file.getVirtualFile()) && errorFile != null) {
                return; // error in attached schema
              }

              if (document.getLineCount() < e.getLineNumber() || e.getLineNumber() <= 0) {
                return;
              }

              Validator.ValidationHost.ErrorType problemType = getProblemType(warning);
              int offset = Math.max(0, document.getLineStartOffset(e.getLineNumber() - 1) + e.getColumnNumber() - 2);
              if (offset >= document.getTextLength()) return;
              PsiElement currentElement = PsiDocumentManager.getInstance(project).getPsiFile(document).findElementAt(offset);
              PsiElement originalElement = currentElement;
              final String elementText = currentElement.getText();

              if (elementText.equals("") || elementText.equals("=")) {
                currentElement = currentElement.getPrevSibling();
              }

              // Cannot find the declaration of element
              String localizedMessage = e.getLocalizedMessage();

              // Ideally would be to switch one messageIds
              int endIndex = localizedMessage.indexOf(':');
              if (endIndex < localizedMessage.length() - 1 && localizedMessage.charAt(endIndex + 1) == '/') {
                endIndex = -1;  // ignore : in http://
              }
              String messageId = endIndex != -1 ? localizedMessage.substring(0, endIndex ):"";
              localizedMessage = localizedMessage.substring(endIndex + 1).trim();

              if (localizedMessage.startsWith(CANNOT_FIND_DECLARATION_ERROR_PREFIX) ||
                  localizedMessage.startsWith(ELEMENT_ERROR_PREFIX) ||
                  localizedMessage.startsWith(ROOT_ELEMENT_ERROR_PREFIX) ||
                  localizedMessage.startsWith(CONTENT_OF_ELEMENT_TYPE_ERROR_PREFIX)
                  ) {
                addProblemToTagName(currentElement, originalElement, localizedMessage, warning);
                //return;
              } else if (localizedMessage.startsWith(VALUE_ERROR_PREFIX)) {
                addProblemToTagName(currentElement, originalElement, localizedMessage, warning);
              } else {
                if (messageId.startsWith(ATTRIBUTE_MESSAGE_PREFIX)) {
                  @NonNls String prefix = "of attribute ";
                  final int i = localizedMessage.indexOf(prefix);

                  if (i != -1) {
                    int messagePrefixLength = prefix.length() + i;
                    final int nextQuoteIndex = localizedMessage.indexOf(localizedMessage.charAt(messagePrefixLength), messagePrefixLength + 1);
                    String attrName = nextQuoteIndex == -1 ? null : localizedMessage.substring(messagePrefixLength + 1, nextQuoteIndex);

                    XmlTag parent = PsiTreeUtil.getParentOfType(originalElement,XmlTag.class);
                    currentElement = parent.getAttribute(attrName,null);

                    if (currentElement != null) {
                      currentElement = ((XmlAttribute)currentElement).getValueElement();
                    }
                  }

                  if (currentElement!=null) {
                    assertValidElement(currentElement, originalElement,localizedMessage);
                    myHost.addMessage(currentElement,localizedMessage, problemType);
                  } else {
                    addProblemToTagName(originalElement, originalElement, localizedMessage, warning);
                  }
                }
                else if (localizedMessage.startsWith(ATTRIBUTE_ERROR_PREFIX)) {
                  final int messagePrefixLength = ATTRIBUTE_ERROR_PREFIX.length();

                  if ( localizedMessage.charAt(messagePrefixLength) == '"' ||
                       localizedMessage.charAt(messagePrefixLength) == '\''
                     ) {
                    // extract the attribute name from message and get it from tag!
                    final int nextQuoteIndex = localizedMessage.indexOf(localizedMessage.charAt(messagePrefixLength), messagePrefixLength + 1);
                    String attrName = nextQuoteIndex == -1 ? null : localizedMessage.substring(messagePrefixLength + 1, nextQuoteIndex);

                    XmlTag parent = PsiTreeUtil.getParentOfType(originalElement,XmlTag.class);
                    currentElement = parent.getAttribute(attrName,null);

                    if (currentElement!=null) {
                      currentElement = SourceTreeToPsiMap.treeElementToPsi(
                        XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(
                          SourceTreeToPsiMap.psiElementToTree(currentElement)
                        )
                      );
                    }
                  } else {
                    currentElement = PsiTreeUtil.getParentOfType(currentElement, XmlTag.class, false);
                  }

                  if (currentElement!=null) {
                    assertValidElement(currentElement, originalElement,localizedMessage);
                    myHost.addMessage(currentElement,localizedMessage, problemType);
                  } else {
                    addProblemToTagName(originalElement, originalElement, localizedMessage, warning);
                  }
                } else if (localizedMessage.startsWith(STRING_ERROR_PREFIX)) {
                  if (currentElement != null) {
                    myHost.addMessage(currentElement,localizedMessage, Validator.ValidationHost.ErrorType.WARNING);
                  }
                }
                else {
                  currentElement = getNodeForMessage(currentElement != null ? currentElement:originalElement);
                  assertValidElement(currentElement, originalElement,localizedMessage);
                  if (currentElement!=null) {
                    myHost.addMessage(currentElement,localizedMessage, problemType);
                  }
                }
              }
            }
          });
        }
        catch (Exception ex) {
          if (ex instanceof ProcessCanceledException) throw (ProcessCanceledException)ex;
          if (ex instanceof XmlResourceResolver.IgnoredResourceException) throw (XmlResourceResolver.IgnoredResourceException)ex;
          LOG.error(ex);
        }
      }

    });

    myHandler.doValidate((XmlFile)element.getContainingFile());

    myFile = file;
    myModificationStamp = myFile.getModificationStamp();
    myInfos = new WeakReference>(results);

    addAllInfos(host,results);
  }

  private static Validator.ValidationHost.ErrorType getProblemType(ValidateXmlActionHandler.ProblemType warning) {
    return warning == ValidateXmlActionHandler.ProblemType.WARNING ? Validator.ValidationHost.ErrorType.WARNING : Validator.ValidationHost.ErrorType.ERROR;
  }

  private static PsiElement getNodeForMessage(final PsiElement currentElement) {
    PsiElement parentOfType = PsiTreeUtil.getNonStrictParentOfType(
      currentElement,
      XmlTag.class,
      XmlProcessingInstruction.class,
      XmlElementDecl.class,
      XmlMarkupDecl.class,
      XmlEntityRef.class,
      XmlDoctype.class
    );

    if (parentOfType == null) {
      if (currentElement instanceof XmlToken) {
        parentOfType = currentElement.getParent();
      }
      else {
        parentOfType = currentElement;
      }
    }
    return parentOfType;
  }

  private static void addAllInfos(Validator.ValidationHost host,List highlightInfos) {
    for (ValidationInfo info : highlightInfos) {
      host.addMessage(info.element, info.message, info.type);
    }
  }

  private PsiElement addProblemToTagName(PsiElement currentElement,
                                     final PsiElement originalElement,
                                     final String localizedMessage,
                                     final ValidateXmlActionHandler.ProblemType problemType) {
    currentElement = PsiTreeUtil.getParentOfType(currentElement,XmlTag.class,false);
    if (currentElement==null) {
      currentElement = PsiTreeUtil.getParentOfType(originalElement,XmlElementDecl.class,false);
    }
    if (currentElement == null) {
      currentElement = originalElement;
    }
    assertValidElement(currentElement, originalElement,localizedMessage);

    if (currentElement!=null) {
      myHost.addMessage(currentElement,localizedMessage, getProblemType(problemType));
    }

    return currentElement;
  }

  private static void assertValidElement(PsiElement currentElement, PsiElement originalElement, String message) {
    if (currentElement==null) {
      XmlTag tag = PsiTreeUtil.getParentOfType(originalElement, XmlTag.class);
      LOG.error("The validator message:" + message + " is bound to null node,\n" + "initial element:" + originalElement.getText() + ",\n" +
                "parent:" + originalElement.getParent() + ",\n" + "tag:" + (tag != null ? tag.getText() : "null") + ",\n" +
                "offset in tag: " + (originalElement.getTextOffset() - (tag == null ? 0 : tag.getTextOffset())));
    }
  }

  public static synchronized void doValidation(final XmlDocument document, final Validator.ValidationHost host) {
    final PsiFile containingFile = document.getContainingFile();
    if (containingFile == null) {
      return;
    }

    if (containingFile.getViewProvider() instanceof TemplateLanguageFileViewProvider) {
      return;
    }

    final FileType fileType = containingFile.getViewProvider().getFileType();
    if (fileType != XmlFileType.INSTANCE && fileType != XHtmlFileType.INSTANCE) {
      return;
    }

    for(Language lang: containingFile.getViewProvider().getLanguages()) {
      if ("ANT".equals(lang.getID())) return;
    }

    final XmlTag rootTag = document.getRootTag();
    if (rootTag == null) return;

    String namespace = rootTag.getNamespace();
    if (XmlUtil.ANT_URI.equals(namespace)) return;

    final Project project = document.getProject();

    final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile();
    final InspectionToolWrapper toolWrapper =
      profile.getInspectionTool(INSPECTION_SHORT_NAME, containingFile);

    if (toolWrapper == null) return;
    if (!profile.isToolEnabled(HighlightDisplayKey.find(INSPECTION_SHORT_NAME), containingFile)) return;

    SoftReference validatorReference = project.getUserData(validatorInstanceKey);
    ExternalDocumentValidator validator = SoftReference.dereference(validatorReference);

    if(validator == null) {
      validator = new ExternalDocumentValidator();
      project.putUserData(validatorInstanceKey,new SoftReference(validator));
    }

    validator.runJaxpValidation(document,host);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy