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")を削除します。GrandChild1Child1

私の問題は、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属性も確認できるようにする必要があります。

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);

そして、子供を 1 人選択する必要がある場合は、便利なオプションを利用できSelectChildElement()ます。myElement.SelectChild(child)代わりにやりたい場合は、拡張機能から呼び出すことができます。

また、ここで FirstOrDefault を使用すると、例外は発生しませんが、null代わりに返されます。

このように、多くの場合、よりコストがかかる例外を追跡する必要はありません...

于 2012-06-05T18:19:49.020 に答える
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 に答える
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 に答える