Как получить родительский и только один дочерний узел - PullRequest
5 голосов
/ 17 декабря 2009

Допустим, у меня есть этот 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 ]

3 голосов
/ 18 декабря 2009

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

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

1 голос
/ 18 декабря 2009

С учетом ввода и требований, как указано, это будет делать то, что вы хотите:

    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 голос
/ 18 декабря 2009

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

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"))
{
   ...
}

Edit:

Теперь я вижу, что код, приведенный выше, не тот, о котором просил спрашивающий. Но то, что спрашивал спрашивающий, немного странно, как мне кажется, поскольку 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();
0 голосов
/ 18 декабря 2009
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 голосов
/ 17 декабря 2009

Я не проверял это, но должно быть что-то вроде этого:

XDocument xmlFile;

return from c in xmlFile.Descendants("category")
       where c.Attribute("text").Value == "Business News"
       select c.Parent ?? c;

Оператор ?? возвращает родительский XElement, а если это null, то 'c'.

Редактировать : Это решение возвращает то, что вы хотите, но я не уверен, что оно лучшее, потому что это довольно сложно:

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
                           );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...