【问题标题】:Java - SaxParser / DocumentBuilder "failing" to get correct tag bodiesJava - SaxParser / DocumentBuilder“未能”获得正确的标签主体
【发布时间】:2012-12-30 03:44:05
【问题描述】:

我遇到了需要读取多个 xml 文件并从中构建单个模型的情况。遗憾的是,这些文件是由我绝对无法更改的遗留系统生成的。

其中一个给我带来麻烦的 XML 文件看起来或多或少像这样(经过更改以删除专有数据):

<resource lang="en" dataId="900">
 numbered content here, 900-919 ...

    <string name="920-name">Document Shredder</string>
    <string name="920-desc">A machine ideal for destroying documents that deserve it. It can cross-shred anything from tissue paper to small netbooks with minimal noise. Remember, hackers can't access the documents if you've shredded the drives.</string>
    <string name="920-cat">office,appliance</string>
    <string name="921-name">Plastic Ladle</string>
    <string name="921-desc">This is a big plastic ladle, ideal for soups and sauces.</string>
    <string name="921-cat">kitchen,utensils</string>

... similar numbered content here, 922-934 ...

    <string name="935-name">Green Laser Pointer</string>
    <string name="935-desc">A High-Powered green laser pointer, ideal for irritating cats.</string>
    <string name="935-cat">office,tool</string>
    <string name="936-name">Black Metal Filing Cabinet</string>
    <string name="936-desc">A large, metal cabinet (black) built to store hanging file folders.</string>
    <string name="936-cat">office,storage</string>

... similar numbered content here, 937-994
</resource>

我将其解析为List&lt;CString&gt;,其中CString.java 是:

public class CString {
    public String name;
    public String desc;

    @Override
    public String toString() {
        return "CString {!name: " + name + " !body: " + body + "}\n";
    }
}

我尝试过使用DocumentBuilder,当它不起作用时,就使用普通的SaxParser。但是,无论我如何处理它,当我回顾我的CStrings 时,我有一些正文实际上包含文档不同部分的未解析标签。例如,打印出我前面提到的List&lt;CString&gt; 可能会产生类似的结果:

[ CStrings for 900-919 ...

, CString {!name: 920-name !body: Document Shredder}
, CString {!name: 920-desc !body: irritating cats.</string>
    <string name="935-cat">office,tool</string>
    <string name="936-name">Black Metal Filing Cabinet</e. Remember, hackers can't access the documents if you've shredded the drives.}
, CString {!name: 920-cat !body: office,appliance}
, CString {!name: 921-name !body: Plastic Ladle}
, CString {!name: 921-desc !body: This is a big plastic ladle, ideal for soups and sauces.}
, CString {!name: 921-cat !body: kitchen,utensils}

... CStrings for 922-934 ... 

, CString {!name: 935-name !body: Green Laser Pointer}
, CString {!name: 935-desc !body: A High-Powered green laser pointer, ideal for irritating cats.}
, CString {!name: 935-cat !body: office,tool}
, CString {!name: 936-name !body: Black Metal Filing Cabinet}
, CString {!name: 936-desc !body: A large, metal cabinet (black) built to store hanging file folders.}
, CString {!name: 936-cat !body: office,storage}

... CStrings for 937-994
]

在我的代码的SaxParser 版本中,我的DefaultHandler 中有以下characters 方法:

public void characters(char ch[], int start, int length) throws SAXException {
    String value = new String(ch, start, length).trim();
    switch(currentQName.toString()) { // currentQName is a StringBuilder that holds just the current xml element's name
        case "string":
            if (value.contains("</string")) {
                System.err.println("!!! Parse Error !!! " + value);
            }
}

正如您可能已经猜到的那样,产生:

!!! Parse Error !!! irritating cats.</string>
        <string name="935-cat">office,tool</string>
        <string name="936-name">Black Metal Filing Cabinet</e. Remember, hackers can't access the documents if you've shredded the drives.

我通常不会问这种深奥的问题,尤其是当我无法提供具体的数据和代码时,但谷歌搜索似乎没有任何我能够确定的东西,当然还有代码不会抛出(或抑制)任何异常。

我注意到的一件事是,当数据有误时,如上面 920-desc 的 CString 所示,在这种情况下,错误数据的长度为 138 个字符,而且并非巧合的是,好的数据恰好包含 139 个字符变成它应该是的样子。这让我觉得这是某种缓冲问题。但是,无论我是让DocumentBuilder 管理缓冲区,还是尝试使用直接的SaxParser 更手动地管理它们,我仍然每次都在相同的地方得到完全相同的错误文本。最后,在处理较短的字符串、name 和 cat 时,我从未注意到任何错误的文本,我认为这也指向 char 缓冲区问题。

任何想法都会有所帮助!

【问题讨论】:

  • 这很奇怪。我唯一能想到的是,如果 xml 缺少一些双引号 " 或一些标签右括号或包含一些未加引号的特殊字符,如角括号和&amp; 之类的字符。您的处理程序对我来说看起来是正确的。或者,也许缓冲区在解析时被修改。还要检查您发布的解析错误,那里有一些奇怪的东西:`文件柜。记住,`
  • 问题出在源文件中。 DocumentBuilder 每天被数十万人使用,因此出现这种明显错误的可能性微乎其微。我最好的建议是您在十六进制编辑器中查看源文件,看看实际内容是什么。
  • 另外:您不是从服务器检索这些文件,是吗?如果是,请查看检索文件的代码。
  • @parsifal 文件从服务器下载到目录,然后从本地系统解析。读完文件后,我还检查了我把它们写回来的地方,据我所知,它们似乎是完整的。至于查看原件的十六进制内容,您会寻找什么样的东西?
  • 十六进制编辑器将向您显示一般的“不属于的字符”,例如来自错误编码或超出 XML 允许范围的字符。虽然,老实说,那些应该导致解析器抛出异常。它还可以让您查看文本,而不会被您认为正确的标记所误导。

标签: java xml-parsing saxparser


【解决方案1】:

几乎可以肯定,您没有格式良好的 XML(您的 cmets 认为绝对不允许更改源系统是一个不祥之兆,但您并不是唯一一个陷入这种困境的人。)

看看这个问题How to parse badly formed XML in Java?

如果我是你,我会使用原始字符串操作和/或正则表达式来直接提取数据或将其修复为格式良好的 XML。顺便说一句,JAXB 在处理 Java 中的 XML 方面要好得多(但仍然需要格式良好)

【讨论】:

  • 这完全可信,而且我不相信源系统,只要我能抛出它。如果 xml 通过验证器,例如 validator.w3.org/check,还有哪些其他迹象表明它的格式不正确?
  • 嗯,如果它通过了 w3c,我会改变主意并说它可能格式正确...除非巧合的是,错误的格式恰好可以制作格式正确的文档。否则,可能是字符编码问题?
  • 我从问题中取出了文件,并删除了带有935-desc 的标签之后的所有内容(保持格式良好,只需删除935-cat 及以上,并留下关闭的&lt;/resource&gt; 标签) .如果我将文件保持在几乎正好 16 KB 以下,问题就会消失,但之后我添加的每个字符都会开始一次重新创建一个字符。我确定这真的很难理解,对此感到抱歉。
【解决方案2】:

我在代码中发现了一个特殊字符被不必要地清理的地方(我想是为了解决以前的源代码格式不佳的问题)。

这是之前进行所有剥离的方法:

private static InputSource getCleanSource(File file) {
    InputSource source = null;
    try {
        InputStream stream = new FileInputStream(file);
        String fileText = readFile(stream); // Gets file content as text from InputStream

        CharsetDecoder utf8Decoder = Charset.forName("UTF-8").newDecoder();
        utf8Decoder.onMalformedInput(CodingErrorAction.IGNORE);
        utf8Decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        CharBuffer parsed = utf8Decoder.decode(ByteBuffer.wrap(readFile(stream).getBytes()));

        fileText = "<?xml version=\"1.1\" encoding=\"UTF-8\" ?>\n" + // put a good header
                parsed
                .replaceAll("<\\?.*?\\?>", "") // remove bad <?xml> tags
                .replaceAll("--+","--") // can't have <!--- text --->
                .replaceFirst("(?s)^.+?<\\?", "<?") // remove bad stuff before <?xml> tag
                .replaceAll("[^\\x20-\\x7e\\x0A]", "") // remove bad characters
                .replaceAll("[\\x0A]", " ") // remove line breaks
                ;
        Reader reader = new StringReader(fileText);
        source = new InputSource(reader);
    } catch (Throwable t) {
        System.err.println("Unknown trouble parsing: " + file.getName());
        t.printStackTrace();
    }

    return source;
}

在审查和调整后,如果我将此方法更改为:

private static InputSource getCleanSource(File file) {
    InputSource source = null;
    try {
        InputStream stream = new FileInputStream(file);
        String fileText = readFile(stream) // Gets file content as text from InputStream
                .replaceAll("--+","--") // can't have <!--- text --->
                .replaceFirst("(?s)^.+?<\\?", "<?") // remove bad stuff before <?xml> tag
                ;
        Reader reader = new StringReader(fileText);
        source = new InputSource(reader);
    } catch (Throwable t) {
        System.err.println("Unknown trouble parsing: " + file.getName());
        t.printStackTrace();
    }

    return source;
}

我还没有时间回去尝试找出哪些神秘字符或标签被清理过程吞噬了。我不得不假设源系统最初提供的有效 xml 比现在需要如此积极的清理要少得多,但我认为我永远无法确定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-14
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多