【问题标题】:C#: Get XML node content with all parent tagsC#:获取带有所有父标签的 XML 节点内容
【发布时间】:2018-04-17 17:56:27
【问题描述】:

有一个 XML 文件:

<favorites>
    <movies>
        <movie title="The Godfather" year="1974" />
        <movie title="The Terminator" year="1984" />
        <movie title="Dark Knight" year="2008" />
    </movies>

    <books>
        <book title = "1984" author="George Orwell" />
        <book title = "Robinson Crusoe"" author="Daniel Defoe"/>
        <book title = "Frankenstein" author="Mary Shelly" />
    </books>

    <music>
        <artist title = "Beatles" genre="rock" />
        <artist title = "Queen" genre="rock" />
        <artist title = "Metallica" rock="heavy metal" />
    </music>
</favorites>

我需要过滤一个特定的节点(音乐)所有的父子节点。 结果应该是:

<favorites>
  <music>
    <artist title="Beatles" genre="rock" />
    <artist title="Queen" genre="rock" />
    <artist title="Metallica" rock="heavy metal" />
  </music>
</favorites>

我找到了使用 System.XML 和 LINQ 过滤 XML 的解决方案:

 XDocument xdoc = XDocument.Parse(xmlString);    
 xdoc.Descendants()
     .Where(x => x.Name != "favorites" & x.Name != "music" & x.Name != "artist")
     .Remove();    
 Console.WriteLine(xdoc.ToString());

工作示例在这里:https://dotnetfiddle.net/NnA4h7#

问题是我必须在查询中指定所有父节点和子节点。

有没有办法只指定一个想要的节点(电影、书籍、音乐)来获得相同的结果?

【问题讨论】:

    标签: c# xml linq


    【解决方案1】:

    这是另一种更简单的方法:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.xml";
            static void Main(string[] args)
            {
                XDocument doc = XDocument.Load(FILENAME);
    
                XElement favorites = doc.Descendants("favorites").FirstOrDefault();
    
                favorites.ReplaceNodes(favorites.Element("music")));
            }
        }
    }
    

    【讨论】:

    • 谢谢。这个例子没问题,但如果是 之间的另一个节点,例如收藏夹 -> myfavorites -> 音乐,则不起作用,在这种情况下,我们仍然需要指定所有父节点才能获得结果.. . 但好的一点是我们不需要指定子节点。
    • 它会起作用的。你不懂代码。 favorites.Element("music") 将找到收藏夹的任何子元素,并且只有一个子元素。
    • 请查看示例 dotnetfiddle.net/vAmP8z ,有一个带有额外中间节点的结果 - 它只给出 而没有更多...
    • 我更新了代码以使用 ReplaceNodes 而不是 ReplaceWith。您的示例与用户 XML 不匹配。如果我使用 favorites.Descendants("music") (将元素更改为后代),我的代码将适用于您的示例。
    • 现在可以了,谢谢。稍后我会尝试找出哪个选项更有用。
    【解决方案2】:

    基本上你需要移除所有节点,除了音乐的父节点、音乐本身及其后代。

    var music = xdoc.Descendants().Where(x => x.Name == "music");
    xdoc.Descendants().Where(x =>
        !music.AncestorsAndSelf().Contains(x)
        && !music.Descendants().Contains(x)
    ).Remove();
    Console.WriteLine(xdoc.ToString());
    

    返回:

    <favorites>
      <music>
        <artist title="Beatles" genre="rock" />
        <artist title="Queen" genre="rock" />
        <artist title="Metallica" rock="heavy metal" />
      </music>
    </favorites>
    

    更新:支持收藏夹和音乐之间的多个嵌套级别。针对大文件运行进行了优化:

    XDocument xdoc = XDocument.Parse(xmlString);
    var music = xdoc.Descendants().Where(x => x.Name == "music");
    var parents = music.Ancestors().Reverse()
        .Select(a=>new XElement(a.Name.LocalName)).ToArray();
    XElement root = new XElement(xdoc.Root.Name.LocalName);
    XDocument xdoc2 = new XDocument(root);
    XElement lastAdded = root;
    for (int i = 1; i < parents.Count(); i++)
    {
        lastAdded.Add(parents[i]);
        lastAdded = parents[i];
    }
    lastAdded.Add(music);
    Console.WriteLine(xdoc2.ToString());
    

    【讨论】:

    • 谢谢。看起来优雅并且适用于小型 xml 数据,但大型数据存在一些性能问题......我有几千个元素的 xml 文件,这个删除查询需要 10 多分钟,但仍然没有结果:(跨度>
    • 我最初的答案是基于您删除元素的代码。如果您使用大数据,我们需要使用不同的方法。由于节点知道它的祖先,我们可以做的一件事是创建新的 xml 来重现路径,直到音乐节点。我已经更新了我的答案。
    • 完美运行!我相信这是完成这项任务的最佳解决方案之一。
    猜你喜欢
    • 2014-03-11
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多