xml linq检索同一节点中的元素

时间:2016-03-16 20:35:53

标签: c# xml linq

我想知道是否有办法在使用LiNQ的XML文档中找到元素列表,其中存在对元素<new><old>。但是,这些元素都不必存在。没有<old>但只有<new>元素的XDoc。另外,我可以拥有一个没有<new><old>标记的巨大XML文件。

以下是一些示例(来自相同的XDoc)

<Profile>Your name is <old>Jason</old><new>Sam</new>. It is a pleasure to meet you</Profile>

或同一文档中的其他元素

<Age>Your age is <new>25</new></Age>

或者可能是这种情况

<Marriage><old>Married</old></Marriage>

所以最后我需要一个元组对{old,new}的列表,在这个例子中它将是:
{杰森,萨姆}
{{',25}
{已婚, ''}

我的第一次尝试是使用它:

var oldElement = xml.Descendants().AsEnumerable().Where(x => x.Name == "old");
var newElement = xml.Descendants().AsEnumerable().Where(x => x.Name == "new");

并通过循环遍历旧元素来加入两者。显然这不起作用,因为可能存在旧计数和新计数不匹配的情况(即,当存在大量新节点且没有旧节点时)

有没有办法在同一个父级下获得匹配的新旧对列表。在我们的示例中,配置文件节点具有匹配的父节点,而其他两个(Age)仅具有新节点,而(Marriage)仅具有旧节点。因此,最终结果将类似于上面的列表(如果没有新标记,则为空字符串)。

1 个答案:

答案 0 :(得分:3)

更新:

在回复您的评论时,在这种情况下查询会更复杂。在这种情况下,您可以使用两个子查询,例如q1q2,您可以使用LINQ Union()进行组合:

var raw = @"<Profile>
  <new>Your</new> name is 
  <old>Jason</old>
  <new>Sam</new>. It is a pleasure 
  <old>have</old>to meet you
  <old>Jason</old>
  <new>Mr. Sam</new>
</Profile>";
var doc = XDocument.Parse(raw);

//create Tuple from all <old> elements no matter it has matching <new> element or not
var q1 = from old in doc.Descendants("old")
         let nextSibling = old.NextNode
         let _new = nextSibling != null && nextSibling is XElement && ((XElement)nextSibling).Name == "new" ? ((XElement)nextSibling).Value : ""
         select Tuple.Create((string)old, _new);

//create Tuple from all <new> elements that don't have matching <old> element
var q2 = from _new in doc.Descendants("new")
         let prevSibling = _new.PreviousNode
         where prevSibling == null || !(prevSibling is XElement) || ((XElement)prevSibling).Name != "old"
         select Tuple.Create("", (string)_new);

var result = q1.Union(q2);

请注意,输出将在&#39;查询顺序&#39; 而不是&#39;文档顺序&#39; ,即结果q1将首先出现,然后是q2的结果:

(Jason, Sam)
(have, )
(Jason, Mr. Sam)
(, Your)

如果订单很重要,那么将涉及更多的条件逻辑。所以,在这种情况下,恕我直言,普通foreach循环比LINQ更易于管理:

var result = new List<Tuple<string, string>>();
foreach (var e in doc.Descendants().Where(o => o.Name == "old" || o.Name == "new"))
{
    XElement _old, _new;
    //if current element is an <old> ...
    if (e.Name == "old")
    {
        _old = e;
        //if the next sibling is a <new>
        if (e.NextNode != null && e.NextNode is XElement && ((XElement)e.NextNode).Name == "new")
        {
            _new = ((XElement)e.NextNode);
        }
        //else: <old> without matching <new>
        else _new = null;
        result.Add(Tuple.Create((string)_old, (string)_new));
    }
    //else: current element is a <new> ...
    else
    {
        //make sure we add current <new> to the result only if it doesn't have matching <old>
        //otherwise it would have been covered by the `if (e.Name == "old")` block
        if (e.PreviousNode == null || !(e.PreviousNode is XElement) || ((XElement)e.PreviousNode).Name != "old")
        {
            _new = e;
            _old = null;
            result.Add(Tuple.Create((string)_old, (string)_new));
        }
    }
}

INITIAL ANSWER:

假设<old><new>元素通过共享相同的父元素链接,并且父元素中最多有一对,您可以尝试这种方式:

var raw = @"<root>
<Profile>Your name is <old>Jason</old><new>Sam</new>. It is a pleasure to meet you</Profile>
<Age>Your age is <new>25</new></Age>
<Marriage><old>Married</old></Marriage>
</root>";

var doc = XDocument.Parse(raw);

var result = doc.Descendants()
                //filter element that has child <old> or <new> or both :
                .Where(o => o.Element("old") != null || o.Element("new") != null)
                //return tuple of old value - new value from current parent element :
                .Select(o => Tuple.Create((string)o.Element("old"), (string)o.Element("new")));

foreach (var r in result)
{
    Console.WriteLine(r);
}

输出

(Jason, Sam)
(, 25)
(Married, )