Search code examples
javaxmlxml-validation

saxreader validation fails when reading xml from java.io.Reader instead of providing path to xml file


I have simple xml file contacts.xml located in subfolder xml-files of actual folder.

<contacts xsi:noNamespaceSchemaLocation="contacts.xsd"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <contact> 
        <firstname>AAA</firstname> 
        <lastname>BBB</lastname>
    </contact> 
</contacts>

Schema file is also located in subfolder xml-files.

Code for parsing file:

  SAXParserFactory factory = SAXParserFactory.newInstance();
  factory.setValidating(true);

  SAXParser parser = factory.newSAXParser();                              
  parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");

  SAXReader reader = new SAXReader(parser.getXMLReader());
  reader.setValidation(true);
  reader.read("xml-files/contacts.xml");

I want to use SAXReader's read method which takes java.io.Reader as parameter like this

reader.read(new FileReader("xml-files/contacts.xml"));

but I get exception

org.dom4j.DocumentException: Error on line 2 of document : cvc-elt.1: Cannot find the declaration of element 'contacts'. Nested exception: cvc-elt.1: Cannot find the declaration of element 'contacts'.

Using custom entityresolver revealed that in the first case xsd files is being loaded from path file:///e:/devel/xsd/xml-files/contacts.xsd and in second case file:///e:/devel/xsd/contacts.xsd.

Is there any way to set to SAXReader the folder where xsd file should be located?


Solution

  • Inspired by Java: How to prevent 'systemId' in EntityResolver#resolveEntity(String publicId, String systemId) from being absolutized to current working directory I used my own implementation of EntityResolver2

    private static class EntityResolver2Impl implements EntityResolver2 {
        private File xmlFile;
        public EntityResolver2Impl(File xmlFile) {
            this.xmlFile = xmlFile;
        }
    
        @Override
        public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException {
            return null;
        }
    
        @Override
        public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException {
            File entityPath = new File(xmlFile.getParent(), systemId);
            return new InputSource(new FileReader(entityPath));
        }
    
        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            return null;
        }
    }
    

    And the calling code looks like this

     File xmlFile = new File("xml-files/contacts.xml");
     SAXParserFactory factory = SAXParserFactory.newInstance();
    
     factory.setValidating(true);
    
     SAXParser parser = factory.newSAXParser();                              
     parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
    
     SAXReader reader = new SAXReader(parser.getXMLReader());
     reader.setEntityResolver(new EntityResolver2Impl(xmlFile));
     reader.setValidation(true);
     reader.read(new FileReader(xmlFile));
    

    This code can handle also cases where path to xsd contains relative paths leading to parent folders (../..) etc.