3

我有一个类似于以下结构的 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>

未定义元素,因为它们可以具有与其他文件不同的值。我所知道的是事先定义的每个元素的“名称”属性。我需要能够根据该名称操作和/或删除选定元素中的数据。例如: removeElement("MyName.IS.Kronos1.Word_1")将删除父项 GrandChild1下的元素。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 个级别(曾祖孙)。但是,我还需要能够查看根节点的Name属性。

4

4 回答 4

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);
于 2012-06-05T18:12:07.400 回答
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)调用它。

此外,当您在此处使用 FirstOrDefault 时,您不会收到异常,而是会null返回。

这样,它就不必跟踪通常成本更高的异常......

于 2012-06-05T18:19:49.020 回答
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();
}
于 2012-06-04T22:14:07.943 回答
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);
于 2012-06-04T23:11:44.197 回答