11

如何在解析文档时强制 SAX 解析器(特别是 Java 中的 Xerces)使用 DTD,而输入文档中没有任何文档类型?这甚至可能吗?

以下是我的场景的更多细节:

我们有一堆符合由多个不同系统生成的相同 DTD 的 XML 文档(我都无法更改)。其中一些系统将文档类型添加到其输出文档中,而其他系统则没有。有些使用命名字符实体,有些则不使用。有些使用命名字符实体而不声明文档类型。 我知道这不是犹太教,但这是我必须处理的。

我正在开发需要用 Java 解析这些文件的系统。目前,它通过首先将 XML 文档作为流读取来处理上述情况,尝试检测它是否定义了 doctype,如果还没有,则添加一个 doctype 声明。问题是这段代码有问题,我想用更干净的东西替换它。

文件很大,所以我不能使用基于 DOM 的解决方案。我也在尝试解析字符实体,因此使用 XML 模式无济于事。

如果您有解决方案,请您直接发布而不是链接到它吗?如果将来有一个带有死链接的正确解决方案,它对 Stack Overflow 并没有多大好处。

4

1 回答 1

2

如果文档没有,我认为设置 DOCTYPE 是不明智的。可能的解决方案是写一个假的,就像你已经做的那样。如果您使用的是 SAX,则可以使用这个假 InputStream 和假 DefaultHandler 实现。(仅适用于 latin1 单字节编码)

我知道这个解决方案也很丑陋,但它只有一个适用于大数据流。

这是一些代码。

private enum State {readXmlDec, readXmlDecEnd, writeFakeDoctipe,  writeEnd};

private class MyInputStream extends InputStream{

    private final InputStream is;
    private StringBuilder sb = new StringBuilder();
    private int pos = 0;
    private String doctype = "<!DOCTYPE register SYSTEM \"fake.dtd\">";
    private State state = State.readXmlDec;

    private MyInputStream(InputStream source) {
        is = source;
    }
    @Override
    public int read() throws IOException {
        int bit;

        switch (state){ 
            case readXmlDec:
                bit = is.read();
                sb.append(Character.toChars(bit));
                if(sb.toString().equals("<?xml")){
                    state = State.readXmlDecEnd;
                }
                break;
            case readXmlDecEnd:
                bit = is.read();
                if(Character.toChars(bit)[0] == '>'){
                    state = State.writeFakeDoctipe;
                }
                break;
            case writeFakeDoctipe:
                bit =  doctype.charAt(pos++);
                if(doctype.length() == pos){
                    state = State.writeEnd;
                }
                break;
            default:
                bit = is.read();
                break;
        }
        return bit;
    }

    @Override
    public void close() throws IOException {
        super.close();
        is.close();
    }
}

private static class MyHandler extends DefaultHandler {

    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException {
        System.out.println("resolve "+ systemId);
        // get real dtd
        InputStream is = ClassLoader.class.getResourceAsStream("/register.dtd");
        return new InputSource(is);
    }

 ... // rest of code
}
于 2012-12-27T15:42:53.280 回答