Позволяет говорят, что у меня есть этот xml:
<categories>
<category text="Arts">
<category text="Design"/>
<category text="Visual Arts"/>
</category>
<category text="Business">
<category text="Business News"/>
<category text="Careers"/>
<category text="Investing"/>
</category>
<category text="Comedy"/>
</categories>
Я хочу записать запрос LINQ, который возвратит категорию, и это - родительская категория, если это имеет кого-либо.
Например, если бы я искал "Business News", то я хотел бы, чтобы она возвратила XElement, содержащий следующее:
<category text="Business">
<category text="Business News" />
</category>
Если бы я только ищу "Бизнес", я просто хотел бы
<category text="Business" />
До сих пор лучшее, которое я могу сделать, использовать LINQ для получения элемента, который я ищу, затем проверяю, является ли родитель узла, который я нашел, корневым узлом, и корректируйтесь соответственно. Существует ли лучший путь?
Легкая часть - получить путь к элементу:
IEnumerable<XElement> elementsInPath =
doc.Element("categories")
.Descendants()
.Where(p => p.Attribute("text").Value == "Design")
.AncestorsAndSelf()
.InDocumentOrder()
.ToList();
InDocumentOrder () предназначен для получения коллекции в следующем порядке: корень, потомок, внук. ToList () предназначен для предотвращения нежелательных эффектов на следующем этапе.
Теперь менее красивая часть, которую, возможно, можно было бы сделать более элегантным способом:
var newdoc = new XDocument();
XContainer elem = newdoc;
foreach (var el in elementsInPath))
{
el.RemoveNodes();
elem.Add(el);
elem = elem.Elements().First();
}
Вот и все. Поскольку каждый XElement сохраняет своего дочернего элемента, мы должны удалить дочерние элементы из каждого узла на пути, а затем перестроить дерево.
Учитывая введенные данные и заявленные требования, это будет делать то, что вы хотите:
public static class MyExtensions
{
public static string ParentAndSelf(this XElement self, XElement parent)
{
self.Elements().Remove();
if (parent != null && parent.Name.Equals(self.Name))
{
parent.Elements().Remove();
parent.Add(self);
return parent.ToString();
}
else
return self.ToString();
}
}
class Program
{
[STAThread]
static void Main()
{
string xml =
@"<categories>
<category text=""Arts"">
<category text=""Design""/>
<category text=""Visual Arts""/>
</category>
<category text=""Business"">
<category text=""Business News""/>
<category text=""Careers""/>
<category text=""Investing""/>
</category>
<category text=""Comedy""/>
</categories>";
XElement doc = XElement.Parse(xml);
PrintMatch(doc, "Business News");
PrintMatch(doc, "Business");
}
static void PrintMatch(XElement doc, string searchTerm)
{
var hit = (from category in doc
.DescendantsAndSelf("category")
where category.Attributes("text")
.FirstOrDefault()
.Value.Equals(searchTerm)
let parent = category.Parent
select category.ParentAndSelf(parent)).SingleOrDefault();
Console.WriteLine(hit);
Console.WriteLine();
}
}
var text = "Car";
var el = from category in x.Descendants("category")
from attribute in category.Attributes("text")
where attribute.Value.StartsWith(text)
select attribute.Parent.Parent;
Console.WriteLine(el.FirstOrDefault());
Вывод:
<category text="Business">...
Этот будет работать, даже если такого элемента нет или нет такого атрибута.
Проблема намного проще, если вы создадите итератор:
public static IEnumerable<XElement> FindElements(XElement d, string test)
{
foreach (XElement e in d.Descendants()
.Where(p => p.Attribute("text").Value == test))
{
yield return e;
if (e.Parent != null)
{
yield return e.Parent;
}
}
}
Используйте его везде, где вы бы использовали запрос Linq, например:
List<XElement> elms = FindElement(d, "Visual Arts").ToList();
или
foreach (XElement elm in FindElements(d, "Visual Arts"))
{
...
}
Редактировать:
Теперь я вижу, что то, что предоставляет приведенный выше код, не является тем, о чем просил вопрошающий. Но то, о чем спрашивал спрашивающий, мне кажется немного странным, поскольку XElement
, который он хочет вернуть, является совершенно новым объектом, а не чем-то в существующем документе.
Тем не менее, честь для меня. обслуживать. Взгляните на мои работы, могущественные, и отчаивайтесь:
XElement result = doc.Descendants()
.Where(x => x.Attribute("text").Value == test)
.Select(
x => x.Parent != null && x.Parent.Attribute("text") != null
? new XElement(
x.Parent.Name,
new XAttribute("text", x.Parent.Attribute("text").Value),
new XElement(
x.Name,
new XAttribute("text", x.Attribute("text").Value)))
: new XElement(
x.Name,
new XAttribute("text", x.Attribute("text").Value)))
.FirstOrDefault();
Я не тестировал это, но это должно быть примерно так:
XDocument xmlFile;
return from c in xmlFile.Descendants("category")
where c.Attribute("text").Value == "Business News"
select c.Parent ?? c;
Оператор ??
возвращает родительский XElement, и если это null
буква «с».
Изменить : это решение возвращает то, что вы хотите, но я не уверен, что оно лучшее, потому что оно становится довольно сложным:
var cat = from c in doc.Descendants("category")
where c.Attribute("text").Value == "Business News"
let node = c.Parent ?? c
select c.Parent == null
? c // Parent null, just return child
: new XElement(
"category",
c.Parent.Attributes(), // Copy the attributes
c // Add single child
);