【问题标题】:How to efficiently parse concatenated XML documents from a file如何有效地从文件中解析连接的 XML 文档
【发布时间】:2010-11-22 06:17:39
【问题描述】:

我有一个由串联的有效 XML 文档组成的文件。我想有效地分离单个 XML 文档。

连接文件的内容将如下所示,因此连接文件本身不是有效的 XML 文档。

<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>
<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>
<?xml version="1.0" encoding="UTF-8"?>
<someData>...</someData>

每个单独的 XML 文档大约 1-4 KB,但可能有几百个。所有 XML 文档都对应相同的 XML Schema。

有什么建议或工具吗?我在 Java 环境中工作。

编辑:我不确定 xml 声明是否会出现在文档中。

编辑:假设所有 xml 文档的编码都是 UTF-8。

【问题讨论】:

  • 我们是否假设每个字符编码都保持相同?否则这将变得更加困难:-)
  • 所有文件都使用与文档本身相同的编码。如果他们说他们是 UTF-8 也没关系。如果连接的文档格式为 UTF-16,则它们都是 UTF-16。

标签: java xml parsing


【解决方案1】:

这是我对 C# 版本的回答。非常丑陋的代码:-\

public List<T> ParseMultipleDocumentsByType<T>(string documents)
    {
        var cleanParsedDocuments = new List<T>();
        var serializer = new XmlSerializer(typeof(T));
        var flag = true;
        while (flag)
        {
            if(documents.Contains(typeof(T).Name))
            {
                var startingPoint = documents.IndexOf("<?xml");
                var endingString = "</" +typeof(T).Name + ">";
                var endingPoing = documents.IndexOf(endingString) + endingString.Length;
                var document = documents.Substring(startingPoint, endingPoing - startingPoint);
                var singleDoc = (T)XmlDeserializeFromString(document, typeof(T));
                cleanParsedDocuments.Add(singleDoc);
                documents = documents.Remove(startingPoint, endingPoing - startingPoint);
            }
            else
            {
                flag = false;
            }
        }


        return cleanParsedDocuments;
    }

    public static object XmlDeserializeFromString(string objectData, Type type)
    {
        var serializer = new XmlSerializer(type);
        object result;

        using (TextReader reader = new StringReader(objectData))
        {
            result = serializer.Deserialize(reader);
        }

        return result;
    }

【讨论】:

    【解决方案2】:

    我没有 Java 答案,但这是我用 C# 解决这个问题的方法。

    我创建了一个名为 XmlFileStreams 的类来扫描源文档中的 XML 文档声明并将其逻辑分解为多个文档:

    class XmlFileStreams {
    
        List<int> positions = new List<int>();
        byte[] bytes;
    
        public XmlFileStreams(string filename) {
            bytes = File.ReadAllBytes(filename);
            for (int pos = 0; pos < bytes.Length - 5; ++pos)
                if (bytes[pos] == '<' && bytes[pos + 1] == '?' && bytes[pos + 2] == 'x' && bytes[pos + 3] == 'm' && bytes[pos + 4] == 'l')
                    positions.Add(pos);
            positions.Add(bytes.Length);
        }
    
        public IEnumerable<Stream> Streams {
            get {
                if (positions.Count > 1)
                    for (int i = 0; i < positions.Count - 1; ++i)
                        yield return new MemoryStream(bytes, positions[i], positions[i + 1] - positions[i]);
            }
        }
    
    }
    

    使用 XmlFileStreams:

    foreach (Stream stream in new XmlFileStreams(@"c:\tmp\test.xml").Streams) {
        using (var xr = XmlReader.Create(stream, new XmlReaderSettings() { XmlResolver = null, ProhibitDtd = false })) {
            // parse file using xr
        }
    }
    

    有几个注意事项。

    1. 它将整个文件读入内存进行处理。如果文件非常大,这可能是个问题。
    2. 它使用简单的暴力搜索来查找 XML 文档边界。

    【讨论】:

      【解决方案3】:

      由于您不确定声明是否始终存在,您可以删除所有声明(&lt;\?xml version.*\?&gt; 等正则表达式可以找到这些声明),前置 &lt;doc-collection&gt;,附加 &lt;/doc-collection&gt;,这样生成的字符串将是一个有效的 xml 文档。在其中,您可以使用(例如)XPath 查询/doc-collection/* 检索单独的文档。如果组合文件足够大以至于内存消耗成为问题,您可能需要使用流式解析器,例如 Sax,但原理保持不变。

      在我遇到的类似场景中,我只是直接使用 xml-parser 读取连接的文档:虽然连接的文件可能不是有效的 xml document,但它是有效的 xml fragment(除非重复声明) - 所以,一旦你剥离声明,如果你的解析器支持解析片段,那么你也可以直接读取结果。所有顶级元素都将成为连接文档的根元素。

      简而言之,如果你去掉所有声明,你将得到一个有效的 xml 片段,它可以直接解析,也可以用一些标签包围它。

      【讨论】:

        【解决方案4】:

        正如 Eamon 所说,如果你知道 东西会一直存在,那就停下来吧。

        如果失败,请查找结束文档级标记。也就是说,扫描文本,计算你的深度。每次看到以“”结尾的标签时,将深度计数加 1。每次看到以“”开头的标签时,减 1。每次减 1 时,检查您现在是否为零。如果是这样,您已经到达了 XML 文档的末尾。

        【讨论】:

        • 为什么不直接找?
        • 再一次,为什么不删除处理指令,将其他所有内容添加到更大的标签中?处理指令不再有用,因为所有文件都使用与大文档相同的编码。随着它们的消失,包含一个超级标签只会再次将其转换为有效的 XML。
        • 这取决于最终的要求是什么。问题被表述为,我如何拆分它们?所以这就是我试图回答的问题。在不知道原始海报试图对输出做什么的情况下,我不知道将它们全部包装在一个大标签中是否是一种可行的解决方案。如果是,那就太好了,去吧。在这个方向上可能还有其他潜在的解决方案。就像文件都共享一个通用的顶级标签一样,也许您可​​以将它们全部组合在一个这样的标签下,即去掉除第一个以外的所有文件的开始标签和除最后一个以外的所有文件的结束标签。
        • 我最终在启动根元素时中断了。
        【解决方案5】:

        不要分裂!在它周围添加一个大标签!然后又变成了一个XML文件:

        <BIGTAG>
        <?xml version="1.0" encoding="UTF-8"?>
        <someData>...</someData>
        <?xml version="1.0" encoding="UTF-8"?>
        <someData>...</someData>
        <?xml version="1.0" encoding="UTF-8"?>
        <someData>...</someData>
        </BIGTAG>
        

        现在,使用 /BIGTAG/SomeData 将为您提供所有 XML 根。


        如果处理指令有问题,您可以随时使用 RegEx 删除它们。删除所有处理指令比使用 RegEx 查找所有根节点更容易。 如果所有文档的编码都不同,请记住这一点:整个文档本身必须使用某种编码类型进行编码,因此它包含的所有 XML 文档都将使用相同的编码,无论每个标题告诉您什么。如果大文件被编码为 UTF-16,那么 XML 处理指令是否说 XML 本身是 UTF-8 并不重要。它不会是 UTF-8,因为整个文件是 UTF-16。因此,这些 XML 处理指令中的编码是无效的。

        通过将它们合并到一个文件中,您改变了编码...


        RegEx,我的意思是正则表达式。您只需删除 如果您尝试其他字符串操作技术,使用正则表达式应该不会太难并且稍微复杂一些。

        【讨论】:

        • 以“xml”或“XML”开头的处理指令是为 XML 标准保留的,因此像这样将它们用作“自定义”PI 并不真正有效。
        • 我认为除了处理指令之外,这在很大程度上是正确的
        • 如果所有 xml 文档没有使用相同的编码,这将不起作用。
        • 你需要去掉那些 。可能在“转储 xml”阶段。
        • 这就是我建议拆分的原因 - 它更简单,可能更快,而且不难做到正确。
        猜你喜欢
        • 2012-04-29
        • 1970-01-01
        • 1970-01-01
        • 2011-03-11
        • 2015-05-16
        • 2017-06-24
        • 1970-01-01
        • 1970-01-01
        • 2020-07-10
        相关资源
        最近更新 更多