替换大型XML文件的一部分

本文关键字:一部分 文件 XML 大型 替换 | 更新日期: 2023-09-27 18:02:47

我有一个大的XML文件,我需要用另一个元素替换具有某个名称的元素(以及所有内部元素(。例如,如果此元素e:

<a>
<b></b>
<e>
   <b></b>
   <c></c>
</e>
</a>

e替换为elem后:

<a>
<b></b>
<elem></elem>
</a>

更新:我尝试使用XDocument,但xml大小超过2gb,并且我有SystemOutOfMemoryException

更新2:我的代码,但xml不能转换

XmlReader reader = XmlReader.Create("xml_file.xml");
XmlWriter wr = XmlWriter.Create(Console.Out);
while (reader.Read())
   {
       if (reader.NodeType == XmlNodeType.Element && reader.Name == "e")
       {
           wr.WriteElementString("elem", "val1");
           reader.ReadSubtree();
       }
            wr.WriteNode(reader, false);
   }
wr.Close();

更新3:

<a>
<b></b>
<e>
   <b></b>
   <c></c>
</e>
<i>
  <e>
    <b></b>
    <c></c>
  </e>
</i> 
</a>

替换大型XML文件的一部分

从这篇博客文章中获得灵感,您基本上可以像示例代码一样,将XmlReader的内容直接流式传输到XmlWriter,但可以处理所有节点类型。与您的示例代码一样,使用WriteNode将添加节点和所有子节点,因此您将无法处理源XML中的每个子节点。

此外,您需要确保读取到要跳过的元素的末尾——ReadSubtree为此创建了一个XmlReader,但它实际上并不进行任何读取。您需要确保将其读到底。

生成的代码可能如下所示:

using (var reader = XmlReader.Create(new StringReader(xml), rs))
using (var writer = XmlWriter.Create(Console.Out, ws))
{
    while (reader.Read())
    {
        switch (reader.NodeType)
        {
            case XmlNodeType.Element:
                var subTreeReader = reader.ReadSubtree();
                if (HandleElement(reader, writer))
                {
                    ReadToEnd(subTreeReader);
                }
                else
                {
                    writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
                    writer.WriteAttributes(reader, true);
                    if (reader.IsEmptyElement)
                    {
                        writer.WriteEndElement();
                    }
                }
                break;
            case XmlNodeType.Text:
                writer.WriteString(reader.Value);
                break;
            case XmlNodeType.Whitespace:
            case XmlNodeType.SignificantWhitespace:
                writer.WriteWhitespace(reader.Value);
                break;
            case XmlNodeType.CDATA:
                writer.WriteCData(reader.Value);
                break;
            case XmlNodeType.EntityReference:
                writer.WriteEntityRef(reader.Name);
                break;
            case XmlNodeType.XmlDeclaration:
            case XmlNodeType.ProcessingInstruction:
                writer.WriteProcessingInstruction(reader.Name, reader.Value);
                break;
            case XmlNodeType.DocumentType:
                writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
                break;
            case XmlNodeType.Comment:
                writer.WriteComment(reader.Value);
                break;
            case XmlNodeType.EndElement:
                writer.WriteFullEndElement();
                break;
        }
    }    
}
private static void ReadToEnd(XmlReader reader)
{
    while (!reader.EOF)
    {
        reader.Read();
    }
}

显然,将您的逻辑放入HandleElement中,如果元素被处理(因此被忽略(,则返回true。示例代码中逻辑的实现是:

private static bool HandleElement(XmlReader reader, XmlWriter writer)
{
    if (reader.Name == "e")
    {
        writer.WriteElementString("element", "val1");
        return true;
    }
    return false;
}

以下是一个工作演示:https://dotnetfiddle.net/FFIBU4

试试这个(看到C#标签:D(:

        XElement elem = new XElement("elem");
        IEnumerable<XElement> listElementsToBeReplaced = xDocument.Descendants("e");
        foreach (XElement replaceElement in listElementsToBeReplaced)
        {
            replaceElement.AddAfterSelf(elem);
        }
        listElementsToBeReplaced.Remove();

我会用正则表达式替换它,将e元素与其所有内容匹配,并以结束标记结束,然后用新的elem元素替换它。通过这种方式,您可以在任何具有搜索/替换功能的编辑器中进行搜索/替换,该编辑器支持正则表达式并以任何语言编程。

string xml = @"<a>
<b></b>
<e>
<b></b>
<c></c>
</e>
</a>";
string patten = @"<e[^>]*>['s'S]*?(((?'Open'<e[^>]*>)['s'S]*?)+((?'-Open'</e>)['s'S]*?)+)*(?(Open)(?!))</e>";
Console.WriteLine(Regex.Replace(xml,patten,"<ele></ele>"));

使用正则表达式,也可以使用LinqToXml

// example data:
XDocument xmldoc = XDocument.Parse(
@"
<a>
<b></b>
<e>
   <b></b>
   <c></c>
</e>
<c />
<e>
   <b></b>
   <c></c>
   <c></c>
</e>
</a>
");
            // you can use xpath, then you need to add:
            // using System.Xml.XPath;
            List<XElement> elementsToReplace = xmldoc.XPathSelectElements("a/e").ToList();
            // or pure linq-to-sql:
            // elementsToReplace = xmldoc.Elements("a").Elements("e").ToList();
            foreach (XElement elem in elementsToReplace)
            {
                // setting Value of XElement to an empty string causes the resulting xml to look like this:
                // <elem></elem>
                // and not like this:
                // <elem />
                elem.ReplaceWith(new XElement("elem", ""));
                // if you don't mind self closing tags, then:
                // elem.ReplaceWith(new XElement("elem"));
            }

我没有衡量这场比赛的表现,但有传言说这场比赛差别不大。

XPath语法,如果需要:http://www.w3schools.com/xpath/xpath_syntax.asp