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

com.intellij.xml.actions.validate.ValidateXmlActionHandler 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-2014 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.actions.validate;

import com.intellij.javaee.UriUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.util.XmlResourceResolver;
import org.apache.xerces.impl.Constants;
import org.apache.xerces.jaxp.JAXPConstants;
import org.apache.xerces.jaxp.SAXParserFactoryImpl;
import org.apache.xerces.util.XMLGrammarPoolImpl;
import org.apache.xerces.xni.grammars.XMLGrammarPool;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.StringReader;
import java.util.Arrays;

/**
 * @author Mike
 */
public class ValidateXmlActionHandler {
  private static final Logger LOG = Logger.getInstance("#com.intellij.xml.actions.validate.ValidateXmlAction");
  @NonNls private static final String SCHEMA_FULL_CHECKING_FEATURE_ID = "http://apache.org/xml/features/validation/schema-full-checking";
  private static final String GRAMMAR_FEATURE_ID = Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY;

  private static final Key GRAMMAR_POOL_KEY = Key.create("GrammarPoolKey");
  private static final Key GRAMMAR_POOL_TIME_STAMP_KEY = Key.create("GrammarPoolTimeStampKey");
  private static final Key DEPENDENT_FILES_KEY = Key.create("GrammarPoolFilesKey");
  private static final Key KNOWN_NAMESPACES_KEY = Key.create("KnownNamespacesKey");

  private Project myProject;
  private XmlFile myFile;
  private ErrorReporter myErrorReporter;
  private SAXParser myParser;
  private XmlResourceResolver myXmlResourceResolver;
  private final boolean myForceChecking;
  @NonNls
  private static final String ENTITY_RESOLVER_PROPERTY_NAME = "http://apache.org/xml/properties/internal/entity-resolver";

  public ValidateXmlActionHandler(boolean _forceChecking) {
    myForceChecking = _forceChecking;
  }

  public void setErrorReporter(ErrorReporter errorReporter) {
    myErrorReporter = errorReporter;
  }

  public VirtualFile getProblemFile(SAXParseException ex) {
    String publicId = ex.getPublicId();
    String systemId = ex.getSystemId();
    if (publicId == null) {
      if (systemId != null) {
        if (systemId.startsWith("file:/")) {
          VirtualFile file = VirtualFileManager.getInstance()
            .findFileByUrl(systemId.startsWith("file://") ? systemId : systemId.replace("file:/", "file://"));
          if (file != null) return file;
        }
        final String path = myXmlResourceResolver.getPathByPublicId(systemId);
        if (path != null) return UriUtil.findRelativeFile(path,null);
        final PsiFile file = myXmlResourceResolver.resolve(null, systemId);
        if (file != null) return file.getVirtualFile();
      }
      return myFile.getVirtualFile();
    }
    final String path = myXmlResourceResolver.getPathByPublicId(publicId);
    if (path != null) return UriUtil.findRelativeFile(path,null);
    return null;
  }

  public enum ProblemType { WARNING, ERROR, FATAL }

  public String buildMessageString(SAXParseException ex) {
    String msg = "(" + ex.getLineNumber() + ":" + ex.getColumnNumber() + ") " + ex.getMessage();
    final VirtualFile file = getProblemFile(ex);

    if ( file != null && !file.equals(myFile.getVirtualFile())) {
      msg = file.getName() + ":" + msg;
    }
    return msg;
  }

  public void doValidate(XmlFile file) {
    myProject = file.getProject();
    myFile = file;

    myXmlResourceResolver = new XmlResourceResolver(myFile, myProject, myErrorReporter);
    myXmlResourceResolver.setStopOnUnDeclaredResource( myErrorReporter.isStopOnUndeclaredResource() );

    try {
      try {
        myParser = createParser();
      }
      catch (Exception e) {
        filterAppException(e);
      }

      if (myParser == null) return;

      myErrorReporter.startProcessing();
    }
    catch (XmlResourceResolver.IgnoredResourceException ignore) {
    }
    catch (Exception exception) {
      filterAppException(exception);
    }
  }

  private void filterAppException(Exception exception) {
    if (!myErrorReporter.filterValidationException(exception)) {
      LOG.error(exception);
    }
  }

  public void doParse() {
    try {
      myParser.parse(new InputSource(new StringReader(myFile.getText())), new DefaultHandler() {
        @Override
        public void warning(SAXParseException e) throws SAXException {
          if (myErrorReporter.isUniqueProblem(e)) myErrorReporter.processError(e, ProblemType.WARNING);
        }

        @Override
        public void error(SAXParseException e) throws SAXException {
          if (myErrorReporter.isUniqueProblem(e)) myErrorReporter.processError(e, ProblemType.ERROR);
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
          if (myErrorReporter.isUniqueProblem(e)) myErrorReporter.processError(e, ProblemType.FATAL);
        }

        @Override
        public InputSource resolveEntity(String publicId, String systemId) {
          final PsiFile psiFile = myXmlResourceResolver.resolve(null, systemId);
          if (psiFile == null) return null;
          return new InputSource(new StringReader(psiFile.getText()));
        }

        @Override
        public void startDocument() throws SAXException {
          super.startDocument();
          myParser.setProperty(
            ENTITY_RESOLVER_PROPERTY_NAME,
            myXmlResourceResolver
          );
        }
      });

      final String[] resourcePaths = myXmlResourceResolver.getResourcePaths();
      if (resourcePaths.length > 0) { // if caches are used
        final VirtualFile[] files = new VirtualFile[resourcePaths.length];
        for (int i = 0; i < resourcePaths.length; ++i) {
          files[i] = UriUtil.findRelativeFile(resourcePaths[i], null);
        }

        myFile.putUserData(DEPENDENT_FILES_KEY, files);
        myFile.putUserData(GRAMMAR_POOL_TIME_STAMP_KEY, calculateTimeStamp(files, myProject));
      }
       myFile.putUserData(KNOWN_NAMESPACES_KEY, getNamespaces(myFile));
    }
    catch (SAXException e) {
      LOG.debug(e);
    }
    catch (Exception exception) {
      filterAppException(exception);
    }
    catch (StackOverflowError error) {
      // http://issues.apache.org/jira/browse/XERCESJ-589
    }
  }

  protected SAXParser createParser() throws SAXException, ParserConfigurationException {
      if (!needsDtdChecking() && !needsSchemaChecking() && !myForceChecking) {
        return null;
      }

      SAXParserFactory factory = new SAXParserFactoryImpl();
      boolean schemaChecking = false;

      if (hasDtdDeclaration()) {
        factory.setValidating(true);
      }

      if (needsSchemaChecking()) {
        factory.setValidating(true);
        factory.setNamespaceAware(true);
        //jdk 1.5 API
        try {
          factory.setXIncludeAware(true);
        } catch(NoSuchMethodError ignore) {}
        schemaChecking = true;
      }

      SAXParser parser = factory.newSAXParser();

      parser.setProperty(ENTITY_RESOLVER_PROPERTY_NAME, myXmlResourceResolver);

      if (schemaChecking) { // when dtd checking schema refs could not be validated @see http://marc.theaimsgroup.com/?l=xerces-j-user&m=112504202423704&w=2
        XMLGrammarPool grammarPool = getGrammarPool(myFile, myForceChecking);

        parser.getXMLReader().setProperty(GRAMMAR_FEATURE_ID, grammarPool);
      }

      try {
        if (schemaChecking) {
          parser.setProperty(JAXPConstants.JAXP_SCHEMA_LANGUAGE,JAXPConstants.W3C_XML_SCHEMA);
          parser.getXMLReader().setFeature(SCHEMA_FULL_CHECKING_FEATURE_ID, true);
          
          if (Boolean.TRUE.equals(Boolean.getBoolean(XmlResourceResolver.HONOUR_ALL_SCHEMA_LOCATIONS_PROPERTY_KEY))) {
            parser.getXMLReader().setFeature("http://apache.org/xml/features/honour-all-schemaLocations", true);
          }

          parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/warn-on-undeclared-elemdef",Boolean.TRUE);
          parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/warn-on-duplicate-attdef",Boolean.TRUE);
        }

        parser.getXMLReader().setFeature("http://apache.org/xml/features/warn-on-duplicate-entitydef",Boolean.TRUE);
        parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/unparsed-entity-checking",Boolean.FALSE);
      } catch(SAXNotRecognizedException ex) {
        // it is possible to continue work with configured parser
        LOG.info("Xml parser installation seems screwed", ex);
      }

      return parser;
  }

  public static XMLGrammarPool getGrammarPool(XmlFile file, boolean forceChecking) {
    final XMLGrammarPool previousGrammarPool = getGrammarPool(file);
    XMLGrammarPool grammarPool = null;

    // check if the pool is valid
    if (!forceChecking && !isValidationDependentFilesOutOfDate(file)) {
      grammarPool = previousGrammarPool;
    }

    if (grammarPool == null) {
      grammarPool = new XMLGrammarPoolImpl();
      file.putUserData(GRAMMAR_POOL_KEY,grammarPool);
    }
    return grammarPool;
  }

  @Nullable
  public static XMLGrammarPool getGrammarPool(XmlFile file) {
    return file.getUserData(GRAMMAR_POOL_KEY);
  }

  public static boolean isValidationDependentFilesOutOfDate(XmlFile myFile) {
    final VirtualFile[] files = myFile.getUserData(DEPENDENT_FILES_KEY);
    final Long grammarPoolTimeStamp = myFile.getUserData(GRAMMAR_POOL_TIME_STAMP_KEY);
    String[] ns = myFile.getUserData(KNOWN_NAMESPACES_KEY);

    if (!Arrays.equals(ns, getNamespaces(myFile))) {
      return true;
    }

    if (grammarPoolTimeStamp != null && files != null) {
      long dependentFilesTimestamp = calculateTimeStamp(files,myFile.getProject());

      if (dependentFilesTimestamp == grammarPoolTimeStamp.longValue()) {
        return false;
      }
    }

    return true;
  }

  private static String[] getNamespaces(XmlFile file) {
    XmlTag rootTag = file.getRootTag();
    if (rootTag == null) return ArrayUtil.EMPTY_STRING_ARRAY;
    return ContainerUtil.mapNotNull(rootTag.getAttributes(), new Function() {
      @Override
      public String fun(XmlAttribute attribute) {
        return attribute.getValue();
      }
    }, ArrayUtil.EMPTY_STRING_ARRAY);
  }

  private static long calculateTimeStamp(final VirtualFile[] files, Project myProject) {
    long timestamp = 0;

    for(VirtualFile file:files) {
      if (file == null || !file.isValid()) break;
      final PsiFile psifile = PsiManager.getInstance(myProject).findFile(file);

      if (psifile != null && psifile.isValid()) {
        timestamp += psifile.getViewProvider().getModificationStamp();
      } else {
        break;
      }
    }
    return timestamp;
  }

  private boolean hasDtdDeclaration() {
    XmlDocument document = myFile.getDocument();
    if (document == null) return false;
    XmlProlog prolog = document.getProlog();
    if (prolog == null) return false;
    XmlDoctype doctype = prolog.getDoctype();
    if (doctype == null) return false;

    return true;
  }

  private boolean needsDtdChecking() {
    XmlDocument document = myFile.getDocument();
    if (document == null) return false;

    return (document.getProlog()!=null && document.getProlog().getDoctype()!=null);
  }

  private boolean needsSchemaChecking() {
    XmlDocument document = myFile.getDocument();
    if (document == null) return false;
    XmlTag rootTag = document.getRootTag();
    if (rootTag == null) return false;

    XmlAttribute[] attributes = rootTag.getAttributes();
    for (XmlAttribute attribute : attributes) {
      if (attribute.isNamespaceDeclaration()) return true;
    }

    return false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy