6

我正在使用woodstox 为XML 文件实现StAX 解析器。假设我在文件系统的公共目录中的某处有一个有效的 XML 文件,该文件具有匹配的 DTD。

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

XML 使用相对系统标识符声明对其 DTD 的引用,如下所示:

<!DOCTYPE test SYSTEM "test.dtd">

从验证的角度来看,对我来说一切都很好。(是吗?xmllint 不会抱怨。)但是,当我尝试使用下面的代码解析文件时,woodstox 会抛出java.io.FileNotFoundException,因为它找不到相关的 DTD 文件。在我看来,该实现尝试访问相对于工作目录而不是相对于 XML 文件对象的 DTD 文件。

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. 这是故意的吗?
  2. 有没有一种方便的方法可以说服 StAX 解析器加载相对于给定 XML 文件而不是相对于工作目录的 DTD?
4

2 回答 2

3

您将需要提供您自己的XMLResolver接口实现(在 SAX 世界中它被称为EntityResolver)来帮助解析器找到 DTD。XMLInputFactorysetXMLResolver()可以为你做的方法。

有关该主题的更多信息:

深入了解一下解析器需要解析 SYSTEM URI 时究竟发生了什么也是一个好主意。例如,Woodstox 有一个内部(和默认)实现XMLResolver(以及SAX和 StAX之间的代理EntityResolverXMLResolver)。看看它对你的 DTD“文件名”做了什么,你就会明白为什么它是这样工作的。

于 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 回答