6

I am using woodstox to implement a StAX parser for XML files. Assume that I have a valid XML file with matching DTD somewhere in a common directory in my filesystem.

/path/to/test.xml
/path/to/test.dtd

The XML references to its DTD using a relative system identifier declaration as follows:

<!DOCTYPE test SYSTEM "test.dtd">

From a validation viewpoint, everything seems fine to me. (Is it? xmllint does not complain.) However, when I am trying to parse the file with the code below, woodstox throws a java.io.FileNotFoundException since it cannot find the relative DTD file. It seems to me that the implementation tries to access the DTD file relative to the working directory instead of relative to the XML file object.

import java.io.FileInputStream;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

public class Test {

    public static void main( String[] args ) throws Exception {

        FileInputStream fileInputStream = new FileInputStream( args[0] );
        XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xmlInputFactory.createXMLStreamReader(fileInputStream);

        while( xsr.hasNext() ) {
            if( xsr.next() == XMLStreamConstants.DTD ) {
                System.err.println( xsr.getText() );
            }
        }
    }
}
  1. Is this intentional?
  2. Is there a convenient way to convince the StAX parser to load the DTD relative to a given XML file instead of relative to the working directory?
4

2 に答える 2

3

パーサーがDTDを見つけるのを助けるために、XMLResolverインターフェースの独自の実装( SAXの世界ではEntityResolverとして知られています)を提供する必要があります。はあなたのためにそれをXMLInputFactoryする方法を持ってsetXMLResolver()います。

主題に関するいくつかのより多くの情報:

また、内部を調べて、パーサーがシステムURIを解決する必要があるときに何が起こっているのかを正確に理解することもお勧めします。たとえば、Woodstoxには、(およびSAXとStAXの間のプロキシ)の内部(およびデフォルト)実装がありますXMLResolver。DTDの「ファイル名」で何が行われるかを見ると、なぜそれが正常に機能しているのかがわかります。EntityResolverXMLResolver

于 2012-05-21T15:11:23.757 に答える
2

@Pavel Vellerの答えは正しいです。使用中の具体的な例を次に示します。

/**
 * Responsible for parsing the specified XML file and creating objects for
 * insertion into the MySQL database.
 * 
 * @author cameronhudson
 *
 */
public class Parser {

  /**
   * Creates a new XMLStreamReader from the specified file.
   * 
   * @param file The relative path of the file to load.
   * @return An XMLStreamReader to be used for parsing.
   */
  private static XMLStreamReader getXmlReader(String filename) {

    // Initialize an XMLStreamReader
    XMLStreamReader reader;

    // Instantiate an XMLInputFactory and set an XMLResolver
    XMLInputFactory factory = XMLInputFactory.newInstance();
    factory.setXMLResolver(new XMLResolver() {

      @Override
      public Object resolveEntity(String publicID, String systemID,
          String baseURI, String namespace) throws XMLStreamException {

        /*
         * The systemID argument is the same dtd file specified in the xml file
         * header. For example, if the xml header is <!DOCTYPE dblp SYSTEM
         * "dblp.dtd">, then systemID will be "dblp.dtd".
         * 
         */
        return Parser.filenameToStream(systemID);
      }

    });

    // Get the XML file as an InputStream.
    InputStream stream = Parser.filenameToStream(filename);

    // Instantiate a new XMLStreamReader.
    try {
      reader = factory.createXMLStreamReader(stream);
    } catch (XMLStreamException e) {
      System.err.println(e);
      return null;
    }
    return reader;
  }

  /**
   * Converts a local resource filename into a path dependent on the runtime
   * environment.
   * 
   * @param filename The local path of the resource within /src/main/resources/.
   * @return An input stream of the file.
   */
  private static InputStream filenameToStream(String filename) {
    return Thread.currentThread().getContextClassLoader()
        .getResourceAsStream(filename);
  }

}
于 2019-02-25T23:34:57.470 に答える