Лично я бы использовал XmlDocument
и знакомые SelectNodes
:
foreach(XmlElement el in doc.DocumentElement.SelectNodes(
"Category[@Name='Tasties']/Category[@Name='Pasta']/Category[@Name='Chicken']/Recipe")) {
Console.WriteLine(el.GetAttribute("Name"));
}
Для LINQ-to-XML, я бы предположил (не проверено) что-то вроде:
var q = from c1 in doc.Root.Elements("Category")
where c1.Attribute("Name").Value == "Tasties"
from c2 in c1.Elements("Category")
where c2.Attribute("Name").Value == "Pasta"
from c3 in c2.Elements("Category")
where c3.Attribute("Name").Value == "Chicken"
from recipe in c3.Elements("Recipe")
select recipe.Attribute("Name").Value;
foreach (string name in q) {
Console.WriteLine(name);
}
Редактировать: если вы хотите, чтобы выбор категории был более гибким:
string[] categories = { "Tasties", "Pasta", "Chicken" };
XDocument doc = XDocument.Parse(xml);
IEnumerable<XElement> query = doc.Elements();
foreach (string category in categories) {
string tmp = category;
query = query.Elements("Category")
.Where(c => c.Attribute("Name").Value == tmp);
}
foreach (string name in query.Descendants("Recipe")
.Select(r => r.Attribute("Name").Value)) {
Console.WriteLine(name);
}
Теперь это должно работать для любого количества уровней, выбирая все рецепты на выбранном уровне или ниже.
Редактировать для обсуждения (комментарии), почему Where
имеет локальную tmp
переменную:
Это может быть немного сложным, но я пытаюсь сделать вопрос справедливым; -p
По сути, foreach
(с "захваченным" итератором lvalue) выглядит так:
class SomeWrapper {
public string category;
public bool AnonMethod(XElement c) {
return c.Attribute("Name").Value == category;
}
}
...
SomeWrapper wrapper = new SomeWrapper(); // note only 1 of these
using(var iter = categories.GetEnumerator()) {
while(iter.MoveNext()) {
wrapper.category = iter.Current;
query = query.Elements("Category")
.Where(wrapper.AnonMethod);
}
}
Это может быть неочевидно, но, поскольку Where
не оценивается сразу, значение category
(через предикат AnonMethod
) проверяется намного позже. Это печальное следствие точных деталей спецификации C #. Введение tmp
( в рамках foreach) означает, что захват происходит за одну итерацию:
class SecondWrapper {
public string tmp;
public bool AnonMethod(XElement c) {
return c.Attribute("Name").Value == tmp;
}
}
...
string category;
using(var iter = categories.GetEnumerator()) {
while(iter.MoveNext()) {
category = iter.Current;
SecondWrapper wrapper = new SecondWrapper(); // note 1 per iteration
wrapper.tmp = category;
query = query.Elements("Category")
.Where(wrapper.AnonMethod);
}
}
И поэтому не имеет значения, оцениваем ли мы сейчас или позже. Сложный и грязный. Вы можете понять, почему я одобряю изменение спецификации !!!