我有一个类似于以下结构的XML文件:
<?xml version="1.0" encoding="utf-8"?>
<Root Attr1="Foo" Name="MyName" Attr2="Bar" >
<Parent1 Name="IS">
<Child1 Name="Kronos1">
<GrandChild1 Name="Word_1"/>
<GrandChild2 Name="Word_2"/>
<GrandChild3 Name="Word_3"/>
<GrandChild4 Name="Word_4"/>
</Child1>
<Child2 Name="Kronos2">
<GrandChild1 Name="Word_1"/>
<GrandChild2 Name="Word_2"/>
<GrandChild3 Name="Word_3"/>
<GrandChild4 Name="Word_4"/>
</Child2>
</Parent1>
</Root>
未定义元素,因为它们可以具有与其他文件不同的值。我所知道的是每个元素的“Name”属性,它总是被定义。我需要能够根据该名称操纵和/或删除所选元素中的数据。例如:removeElement("MyName.IS.Kronos1.Word_1")
会删除GrandChild1
Parent下方的Child1
元素。
我的问题是,在使用LINQ to XML查询时,我无法正确选择该元素。使用这个:
private IEnumerable<XElement> findElements(IEnumerable<XElement> docElements, string[] names)
{
// the string[] is an array from the desired element to be removed.
// i.e. My.Name.IS ==> array[ "My, "Name", "IS"]
IEnumerable<XElement> currentSelection = docElements.Descendants();
foreach (string name in names)
{
currentSelection =
from el in currentSelection
where el.Attribute("Name").Value == name
select el;
}
return currentSelection;
}
要找到我需要删除元素的位置,会产生以下结果:
<?xml version="1.0" encoding="utf-8"?>
<Root Attr1="Foo" Name="MyName" Attr2="Bar" >
<Parent1 Name="IS">
<Child1 Name="Kronos1">
<GrandChild2 Name="Word_2"/>
<GrandChild3 Name="Word_3"/>
<GrandChild4 Name="Word_4"/>
</Child1>
<Child2 Name="Kronos2">
<GrandChild2 Name="Word_2"/>
<GrandChild3 Name="Word_3"/>
<GrandChild4 Name="Word_4"/>
</Child2>
</Parent1>
</Root>
调试后,似乎我所做的只是重新搜索同一个文档,但每次都是不同的名称。如何根据多个父属性名称搜索和选择特定元素?
应该注意,XML的大小(意味着元素的级别)也是可变的。意味着可以只有2个级别(父母)或最多6个级别(Great-Great-GrandChildren)。但是,我还需要能够查看根节点的Name
属性。
答案 0 :(得分:1)
如果你采用递归方法,你可以这样做:
private XElement findElement(IEnumerable<XElement> docElements, List<string> names)
{
IEnumerable<XElement> currentElements = docElements;
XElement returnElem = null;
// WE HAVE TO DO THIS, otherwise we lose the name when we remove it from the list
string searchName = String.Copy(names[0]);
// look for elements that matchs the first name
currentElements =
from el in currentElements
where el.Attribute("Name").Value == searchName
select el;
// as long as there's elements in the List AND there are still names to look for:
if (currentElements.Any() && names.Count > 1)
{
// remove the name from the list (we found it above) and recursively look for the next
// element in the XML
names.Remove(names[0]);
returnElem = findElement(currentElements.Elements(), names);
}
// If we still have elements to look for, AND we're at the last name:
else if (currentElements.Any() && names.Count == 1)
{
// one last search for the final element
currentElements =
from el in currentElements
where el.Attribute("Name").Value == searchName
select el;
// we return the the first elements which happens to be the only one (if found) or null if not
returnElem = currentElements.First();
}
else
// we do this if we don't find the correct elements
returnElem = null;
// if we don't find the Element, return null and handle appropriately
// otherwise we return the result
return returnElem;
}
请注意,我传递的是列表而不是数组。这可以通过以下方式轻松完成:
List<string> elemNames= new List<string>("This.is.a.test".Split('.')); // or whatever your string is that you need to split
最后,我正在阅读文档,将其拆分为元素,并按如下方式调用函数:
XDocument doc = XDocument.Load(loadLocation);
IEnumerable<XElement> currentSelection = doc.Elements();
XElement foundElement = findElement(currentSelection, elemNames);
答案 1 :(得分:1)
这应该有效:
if (doc.Root.Attribute("Name").Value != names.First())
throw new InvalidOperationException("Sequence contains no matching element.");
var selection = doc.Root;
foreach (var next in names.Skip(1))
selection = selection.Elements().First(x => x.Attribute("Name").Value == next);
return selection;
如果您愿意,可以使用以下内容替换最新行:
var selection = names.Skip(1).Aggregate(doc.Root, (current, next) => current.Elements().First(x => x.Attribute("Name").Value == next));
如果在源代码中找不到匹配的元素,则.First()
方法会抛出异常。
最干净的方法是添加一个新功能:
XElement SelectChildElement(XElement current, string child)
{
if (current == null)
return null;
var elements = current.Elements();
return elements.FirstOrDefault(x => x.Attribute("Name").Value == child);
}
这样你只需按照以下方式使用它:
if (doc.Root.Attribute("Name").Value != names.First())
return null;
return names.Skip(1).Aggregate(doc.Root, SelectChildElement);
然后,如果您需要选择一个孩子,那么您可以使用方便的SelectChildElement()
。如果您想改为myElement.SelectChild(child)
,可以从extension调用它。
此外,当您在此处使用FirstOrDefault时,您不会获得异常,而是返回null
。
这样,它不必跟踪通常更昂贵的异常......
答案 2 :(得分:0)
您需要在每个新步骤中搜索当前所选元素的后代:
private IEnumerable<XElement> findElements(IEnumerable<XElement> docElements, string[] names)
{
IEnumerable<XElement> currentSelection = docElements;
IEnumerable<XElement> elements = currentSelection;
foreach (string name in names)
{
currentSelection =
from el in elements
where el.Attribute("Name").Value == name
select el;
elements = currentSelection.Elements();
}
return currentSelection;
}
我在LinqPad中测试了以下代码,一切正常。您也可以看到所有中间步骤。顺便说一下,LinqPad是测试你的linq查询的好工具。
string xml =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Root Attr1=\"Foo\" Name=\"MyName\" Attr2=\"Bar\" >" +
" <Parent1 Name=\"IS\">" +
" <Child1 Name=\"Kronos1\">" +
" <GrandChild1 Name=\"Word_1\"/>" +
" <GrandChild2 Name=\"Word_2\"/>" +
" <GrandChild3 Name=\"Word_3\"/>" +
" <GrandChild4 Name=\"Word_4\"/>" +
" </Child1>" +
" <Child2 Name=\"Kronos2\">" +
" <GrandChild1 Name=\"Word_1\"/>" +
" <GrandChild2 Name=\"Word_2\"/>" +
" <GrandChild3 Name=\"Word_3\"/>" +
" <GrandChild4 Name=\"Word_4\"/>" +
" </Child2>" +
" </Parent1>" +
"</Root>";
string search = "MyName.IS.Kronos1.Word_1";
string[] names = search.Split('.');
IEnumerable<XElement> currentSelection = XElement.Parse(xml).AncestorsAndSelf();
IEnumerable<XElement> elements = currentSelection;
currentSelection.Dump();
foreach (string name in names)
{
currentSelection =
from el in elements
where el.Attribute("Name").Value == name
select el;
elements = currentSelection.Elements();
currentSelection.Dump();
}
答案 3 :(得分:0)
使用此库来使用XPath:https://github.com/ChuckSavage/XmlLib/
string search = "MyName.IS.Kronos1.Word_1";
XElement node, root = node = XElement.Load(file);
// Skip(1) is to skip the root, because we start there and there can only ever be one root
foreach (string name in search.Split('.').Skip(1))
node = node.XPathElement("*[@Name={0}]", name);
node.Remove();
root.Save(file);