【问题标题】:How do I extract info deep inside XML using C# and LINQ?如何使用 C# 和 LINQ 在 XML 深处提取信息?
【发布时间】:2011-05-12 00:58:21
【问题描述】:

这是我在 StackOverflow 上的第一篇文章,请多多包涵。如果我的代码示例有点长,我先道歉。

使用 C# 和 LINQ,我试图在一个更大的 XML 文件中识别一系列第三级 id 元素(在本例中为 000049)。每个第三级id 都是独一无二的,我想要的那些是基于每个级别的一系列后代信息。更具体地说,如果type == Alocation type(old) == vaultlocation type(new) == out,那么我想选择那个id。下面是我正在使用的 XML 和 C# 代码。

一般来说,我的代码有效。如下所述,它将返回 000049 的 id 两次,这是正确的。然而,我发现了一个漏洞。如果我删除第一个包含type == Ahistory 块,我的代码仍然会返回两次000049 的id,而它应该只返回一次。我知道为什么会这样,但我想不出更好的方法来运行查询。有没有更好的方法来运行我的查询以获得我想要的输出并仍然使用 LINQ?

我的 XML:

<?xml version="1.0" encoding="ISO8859-1" ?>
<data type="historylist">
    <date type="runtime">
        <year>2011</year>
        <month>04</month>
        <day>22</day>
        <dayname>Friday</dayname>
        <hour>15</hour>
        <minutes>24</minutes>
        <seconds>46</seconds>
    </date>
    <customer>
        <id>0001</id>
        <description>customer</description>
        <mediatype>
            <id>kit</id>
            <description>customer kit</description>
            <volume>
                <id>000049</id>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>03</hour>
                        <minutes>00</minutes>
                        <seconds>02</seconds>
                    </date>
                    <userid>batch</userid>
                    <type>OD</type>
                    <location type="old">
                        <repository>vault</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>06</hour>
                        <minutes>43</minutes>
                        <seconds>33</seconds>
                    </date>
                    <userid>vaultred</userid>
                    <type>A</type>
                    <location type="old">
                        <repository>vault</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>06</hour>
                        <minutes>43</minutes>
                        <seconds>33</seconds>
                    </date>
                    <userid>vaultred</userid>
                    <type>S</type>
                    <location type="old">
                        <repository>vault</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>06</hour>
                        <minutes>45</minutes>
                        <seconds>00</seconds>
                    </date>
                    <userid>batch</userid>
                    <type>O</type>
                    <location type="old">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>site</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>11</hour>
                        <minutes>25</minutes>
                        <seconds>59</seconds>
                    </date>
                    <userid>ihcmdm</userid>
                    <type>A</type>
                    <location type="old">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>site</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
                <history>
                    <date type="optime">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                        <hour>11</hour>
                        <minutes>25</minutes>
                        <seconds>59</seconds>
                    </date>
                    <userid>ihcmdm</userid>
                    <type>S</type>
                    <location type="old">
                        <repository>out</repository>
                        <slot>0</slot>
                    </location>
                    <location type="new">
                        <repository>site</repository>
                        <slot>0</slot>
                    </location>
                    <container>0001.kit.000049</container>
                    <date type="movedate">
                        <year>2011</year>
                        <month>04</month>
                        <day>22</day>
                        <dayname>Friday</dayname>
                    </date>
                </history>
            </volume>
            ...

我的 C# 代码:

IEnumerable<XElement> caseIdLeavingVault =
    from volume in root.Descendants("volume")
    where
        (from type in volume.Descendants("type")
         where type.Value == "A"
         select type).Any() &&
        (from locationOld in volume.Descendants("location")
         where
             ((String)locationOld.Attribute("type") == "old" &&
              (String)locationOld.Element("repository") == "vault") &&
             (from locationNew in volume.Descendants("location")
              where
                  ((String)locationNew.Attribute("type") == "new" &&
                   (String)locationNew.Element("repository") == "out")
              select locationNew).Any()
         select locationOld).Any()
    select volume.Element("id");

    ...

foreach (XElement volume in caseIdLeavingVault)
{
    Console.WriteLine(volume.Value.ToString());
}

谢谢。


好吧,伙计们,我又被难住了。鉴于同样的情况和下面@Elian 的解决方案(效果很好),我需要"optime""movedate" 日期用于选择history 用于选择id。那有意义吗?我希望以这样的方式结束:

select new { 
    id = volume.Element("id").Value, 

    // this is from "optime"
    opYear = <whaterver>("year").Value, 
    opMonth = <whatever>("month").Value, 
    opDay = <whatever>("day").Value, 

    // this is from "movedate"
    mvYear = <whaterver>("year").Value, 
    mvMonth = <whatever>("month").Value, 
    mvDay = <whatever>("day").Value 
} 

我尝试了很多不同的组合,但&lt;date type="optime"&gt;&lt;date type="movedate"&gt;Attributes 一直妨碍我,我似乎无法得到我想要的。


好的。我找到了一个很好用的solution

select new {
    caseId = volume.Element("id").Value,

    // this is from "optime"
    opYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("year").Value,
    opMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("month").Value,
    opDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("day").Value,

    // this is from "movedate"
    mvYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("year").Value,
    mvMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("month").Value,
    mvDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("day").Value
};

但是,当它找到没有"movedate"id 时,它确实会失败。其中一些存在,所以现在我正在努力。


嗯,昨天下午晚些时候,我终于找到了我一直想要的解决方案:

var caseIdLeavingSite =
    from volume in root.Descendants("volume")
    where volume.Elements("history").Any(
        h => h.Element("type").Value == "A" &&
        h.Elements("location").Any(l => l.Attribute("type").Value == "old" && ((l.Element("repository").Value == "site") ||
                                                                               (l.Element("repository").Value == "init"))) &&
        h.Elements("location").Any(l => l.Attribute("type").Value == "new" && l.Element("repository").Value == "toVault")
        )
    select new {
        caseId = volume.Element("id").Value,
        opYear = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("year").Value,
        opMonth = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("month").Value,
        opDay = volume.Descendants("date").Where(t => t.Attribute("type").Value == "optime").First().Element("day").Value,
        mvYear = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
                 (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("year").Value) : "0",
        mvMonth = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
                  (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("month").Value) : "0",
        mvDay = (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").Any() == true) ? 
                (volume.Descendants("date").Where(t => t.Attribute("type").Value == "movedate").First().Element("day").Value) : "0"
   };

这满足了@Elian 帮助的要求,并获取了必要的额外日期信息。通过使用三元运算符?:,它还解释了"movedate" 没有元素的少数情况。

现在,如果有人知道如何提高效率,我仍然很感兴趣。谢谢。

【问题讨论】:

    标签: c# .net xml linq linq-to-xml


    【解决方案1】:

    我想你想要这样的东西:

    IEnumerable<XElement> caseIdLeavingVault =
        from volume in document.Descendants("volume")
        where volume.Elements("history").Any(
            h => h.Element("type").Value == "A" &&
                h.Elements("location").Any(l => l.Attribute("type").Value == "old" && l.Element("repository").Value == "vault") &&
                h.Elements("location").Any(l => l.Attribute("type").Value == "new" && l.Element("repository").Value == "out")
            )
        select volume.Element("id");
    

    您的代码独立检查卷是否具有A 类型的&lt;history&gt; 元素和(不一定相同)具有所需&lt;location&gt; 元素的&lt;history&gt; 元素。

    上面的代码检查是否存在一个&lt;history&gt; 元素,它既是A 类型,又包含所需的&lt;location&gt; 元素。

    更新: Abatishchev 提出了一个使用 xpath 查询而不是 LINQ to XML 的解决方案,但他的查询过于简单,并且不能完全返回您所要求的内容。下面的 xpath 查询可以解决问题,但它也有点长:

    data/customer/mediatype/volume[history[type = 'A' and location[@type = 'old' and repository = 'vault'] and location[@type = 'new' and repository = 'out']]]/id
    

    【讨论】:

    • @Elian 感谢您的回答。我试试看。
    • @Elian 鉴于相同的场景和您的解决方案,我将如何选择同一卷的id 以及yearmonthday?我尝试了很多组合,但&lt;date type="optime"&gt;&lt;date type="movedate"&gt; 总是妨碍我。我需要两者都用于跟踪目的。
    • @meffordm:给定一个&lt;volume&gt; 元素,你想要哪个日期?
    • @Elian 我需要"optime""movedate" 的日期,用于选择history 用于选择id。那有意义吗?我希望以这样的方式结束:select new { id = volume.Element("id").Value, year = &lt;whaterver&gt;("year").Value, month = &lt;whatever&gt;("month").value, day = &lt;whatever&gt;("day").Value }我尝试了很多不同的组合,但我似乎无法得到我想要的。
    【解决方案2】:

    当您可以使用简单的 XPath 查询时,您为什么使用如此复杂且昂贵的 LINQ to XML 查询:

    using System.Xml;
    
    string xml = @"...";
    string xpath = "data/customer/mediatype/volume/history/type[text()='A']/../location[@type='old' or @type='new']/../../id";
    
    var doc = new XmlDocument();
    doc.LoadXml(xml); // or use Load(path);
    
    var nodes = doc.SelectNodes(xpath);
    
    foreach (XmlNode node in nodes)
    {
        Console.WriteLine(node.InnerText); // 000049
    }
    

    或者如果您不需要 XML DOM 模型:

    using System.Xml.XPath;
    
    XPathDocument doc = null;
    using (var stream = new StringReader(xml))
    {
        doc = new XPathDocument(stream); // specify just path to file if you have such one
    }
    var nav = doc.CreateNavigator();
    XPathNodeIterator nodes = (XPathNodeIterator)nav.Evaluate(xpath);
    foreach (XPathNavigator node in nodes)
    {
        Console.WriteLine(node.Value);
    }
    

    【讨论】:

    • 您的 xpath 查询不会做同样的事情,尽管我猜您是对的,在这种情况下 xpath 查询会更短。
    • @Elian:可能不是,我在 XPath 方面不太擅长,但我只是大致展示了这个想法。
    • @Elian:我更新了我的任务,但仍然不正确,你打败了我。太好了!
    • @Gabe:你说得对,OP 的任务肯定很复杂,我没有正确理解它,无论如何,Elian 比我更快地创建了正确的任务。
    • @abatishchev: :-) 很高兴看到 xpath 和 linq 查询的结构完全相同。 Linq 更冗长,但有一个明显的好处是语义更清晰。您必须知道非空节点集到 true 的隐式转换才能理解 xpath 查询。
    猜你喜欢
    • 2017-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-03
    • 2011-12-11
    • 1970-01-01
    • 2014-05-08
    • 1970-01-01
    相关资源
    最近更新 更多