2009-12-17 9 views
5

Digamos que tengo este xml:Como llegar padre y sólo un nodo hijo

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

Quiero escribir una consulta LINQ que devolverá la categoría y es categoría principal si tiene alguna.

Por ejemplo, si yo estaba buscando "Business News" Yo lo querría para devolver un XElement que contiene lo siguiente:

<category text="Business"> 
    <category text="Business News" /> 
</category> 

Si sólo búsqueda de "negocios", sólo querría

<category text="Business" /> 

Hasta ahora, lo mejor que puedo hacer es usar LINQ para obtener el elemento que estoy buscando, luego verifico si el padre del nodo que encontré es el nodo raíz y ajustarlo en consecuencia. ¿Hay una mejor manera?

+0

Buena pregunta Evan. – Tebo

Respuesta

3

La parte fácil es para obtener la ruta al elemento :

IEnumerable<XElement> elementsInPath = 
    doc.Element("categories") 
     .Descendants() 
     .Where(p => p.Attribute("text").Value == "Design") 
     .AncestorsAndSelf() 
     .InDocumentOrder() 
     .ToList(); 

el InDocumentOrder() está ahí para obtener la colección en el orden de raíz, hijo, nieto. ToList() está ahí para evitar efectos no deseados en el siguiente paso.

Ahora, la parte menos bella, que tal vez se podría hacer de una manera más elegante:

var newdoc = new XDocument(); 
XContainer elem = newdoc; 
foreach (var el in elementsInPath)) 
{ 
    el.RemoveNodes(); 
    elem.Add(el); 
    elem = elem.Elements().First(); 
} 

Eso es todo. Como cada elemento de XElement mantiene a su hijo, debemos eliminar los elementos secundarios de cada nodo en la ruta, y luego reconstruir el árbol.

0

no he probado esto, pero debería ser algo como esto:

XDocument xmlFile; 

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

El operador devuelve el ?? XElement padre, y si eso es null la 'c'.

Editar: Esta solución devuelve lo que quiere, pero no estoy seguro de si es la mejor, ya que se pone muy complicada:

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

Eso no funciona, simplemente devuelve todo el documento. El uso de ?? es una buena idea sin embargo. – Evan

+0

OK, el próximo paso, lo probaré! :) –

+0

En realidad devuelve el padre, con todos los hijos, no el documento completo. –

1

El problema es mucho más fácil si se genera un iterador:

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

utilizarlo en cualquier lugar que tendría que utilizar una consulta LINQ, por ejemplo:

List<XElement> elms = FindElement(d, "Visual Arts").ToList(); 

o

foreach (XElement elm in FindElements(d, "Visual Arts")) 
{ 
    ... 
} 

Editar:

Veo ahora que lo que el código anterior proporciona no es lo que pidió el interrogador. Pero lo que preguntó el interrogador es un poco extraño, me parece, ya que el XElement que quiere devolver es un objeto completamente nuevo, no algo en el documento existente.

Aún así, el honor es servir.Mirada en mis obras, hijos de los poderosos, y la desesperación:

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

Esto devuelve todos los elementos del elemento primario, como la respuesta de Sander. –

+0

La consulta revisada funciona y es más concisa que la que actualmente tiene la calificación más alta. Buen trabajo. –

1

Dada la entrada, y los requisitos como se ha dicho, esto va a hacer lo que quiere:

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(); 
     } 
    } 
0
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()); 

Salida:

<category text="Business">... 

Éste funcionará incluso si no hay dicho elemento, o no existe dicho atributo.

+0

Siempre me da "La enumeración no arrojó ningún resultado". –

Cuestiones relacionadas