我想知道是否有办法在使用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)仅具有旧节点。因此,最终结果将类似于上面的列表(如果没有新标记,则为空字符串)。
答案 0 :(得分:3)
更新:
在回复您的评论时,在这种情况下查询会更复杂。在这种情况下,您可以使用两个子查询,例如q1
和q2
,您可以使用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, )