在c#中将包含多个XML文件的单个大文件读入多个XML记录

本文关键字:文件 XML 记录 单个大 包含多 | 更新日期: 2023-09-27 18:10:47

我有一个文件,它有效地包含了相同格式的多个XML文件,因此该文件本身不是有效的XML;例如:

<?xml version='1.0' encoding='UTF-8'?>
<Proposal xmlns="a namespace">
    <ASubnode>Text</ASubNode>
    <LotsOfOtherNodes />
</Proposal>
<?xml version='1.0' encoding='UTF-8'?>
<Proposal xmlns="a namespace">
    <ASubnode>Text</ASubNode>
    <LotsOfOtherNodes />
</Proposal>
....

我想处理所有的Proposal节点,一次一个;例如:

foreach (var proposal in file)
    do something

我不能使用XmlReader,因为它在到达中间XML声明节点时抛出异常。我可能会将整个文件读入字符串,然后使用Split方法,但这些文件的大小都是gb,因此作为一种选项,这并不是特别有吸引力。似乎我可以一次一行地读取文件,通过正则表达式搜索适当的节点,但是文件不是像上面那样每行一个节点的行格式,而是包含多个节点的非常长的行,并且在节点文本中随机换行。

有没有一种方法可以在不手工制作文本解析器的情况下实现这一点?

在c#中将包含多个XML文件的单个大文件读入多个XML记录

您有两个选择:

  1. 告诉XmlReader不要这么挑剔。设置XmlReaderSettings。一致性级别到一致性级别片段。这将使解析器忽略没有根节点的事实。

    var settings = new XmlReaderSettings();
    settings.ConformanceLevel = ConformanceLevel.Fragment;
    using (var reader = XmlReader.Create(textReader, settings))
    {
         ...
    }
    
  2. 用'root'元素包装你的XML文件,这样你的文档将只有一个根节点

 <?xml version='1.0' encoding='UTF-8'?>
 <root>
     <Proposal xmlns="a namespace">
         <ASubnode>Text</ASubNode>
         <LotsOfOtherNodes />
     </Proposal>
     <?xml version='1.0' encoding='UTF-8'?>
     <Proposal xmlns="a namespace">
         <ASubnode>Text</ASubNode>
         <LotsOfOtherNodes />
     </Proposal>
 ....
 </root>

您可以逐行读取文本而无需实际解析xml,因为xml文档的标题是相同的:

IEnumerable<XDocument> GetDocuments(Stream bulkStream)
{
    var reader = new StreamReader(bulkStream);
    var sb = new StringBuilder();   
    var firstLine = reader.ReadLine();
    string line = firstLine;    
    while(line != null)
    {
        sb.Clear();
        sb.Append(firstLine);
        while((line = reader.ReadLine()) != null && line != firstLine)
        {
            sb.Append(line);
        }
        yield return XDocument.Parse(sb.ToString());
    }
}

更新:即使声明可以在一行之间开始,下面也可以工作:

IEnumerable<XDocument> GetDocuments(Stream bulkStream)
{
    const string decl = @"<?xml version='1.0' encoding='UTF-8'?>";
    var sb = new StringBuilder();   
    bool start = true;
    foreach(var line in GetLines(bulkStream).Where(l => !string.IsNullOrWhiteSpace(l)))
    {
        if (start)
        {
            if (line == decl)
                start = false;
            sb.AppendLine(line);
        }
        else
        {
            if (line == decl)
            {
                sb.ToString().Dump();
                yield return XDocument.Parse(sb.ToString());
                sb.Clear();
                start = true;
                sb.AppendLine(line);
            }
            else
                sb.AppendLine(line);
        }
    }
    sb.ToString().Dump();
    yield return XDocument.Parse(sb.ToString());
}
IEnumerable<string> GetLines(Stream bulkStream)
{
    const string decl = @"<?xml version='1.0' encoding='UTF-8'?>";
    var reader = new StreamReader(bulkStream);
    string line;
    while((line = reader.ReadLine()) != null)
    {
        if (line.Contains(decl))
        {
            var declIndex = line.IndexOf(decl);
            yield return line.Substring(0, declIndex);
            yield return decl;
            yield return line.Substring(declIndex + decl.Length);
        }
        else
        {
            yield return line;
        }
    }
}