Как получить родителя и только один дочерний узел

Позволяет говорят, что у меня есть этот 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 для получения элемента, который я ищу, затем проверяю, является ли родитель узла, который я нашел, корневым узлом, и корректируйтесь соответственно. Существует ли лучший путь?

5
задан Justin Johnson 7 March 2010 в 09:45
поделиться

5 ответов

Легкая часть - получить путь к элементу:

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 сохраняет своего дочернего элемента, мы должны удалить дочерние элементы из каждого узла на пути, а затем перестроить дерево.

3
ответ дан 14 December 2019 в 19:15
поделиться

Учитывая введенные данные и заявленные требования, это будет делать то, что вы хотите:

    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();
        }
    }
1
ответ дан 14 December 2019 в 19:15
поделиться
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">...

Этот будет работать, даже если такого элемента нет или нет такого атрибута.

0
ответ дан 14 December 2019 в 19:15
поделиться

Проблема намного проще, если вы создадите итератор:

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();
1
ответ дан 14 December 2019 в 19:15
поделиться

Я не тестировал это, но это должно быть примерно так:

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
                           );
0
ответ дан 14 December 2019 в 19:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: