2009-12-17 5 views
5

Können sagen, ich habe diese xml:Wie Eltern bekommen und nur ein Kind Knoten

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

Ich möchte eine LINQ-Abfrage schreiben, die die Kategorie zurückkehren und es ist übergeordnete Kategorie, wenn es irgendwelche hat.

Zum Beispiel, wenn ich war auf der Suche nach „Business News“ Ich möchte es ein XElement zurückzukehren enthielt folgende:

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

Wenn ich für „Business“ nur suchen, würde ich will nur

Das Beste, was ich tun kann, ist LINQ zu verwenden, um das Element zu erhalten, nach dem ich suche, dann überprüfen, ob das Eltern des Knotens, den ich gefunden habe, der Wurzelknoten ist und entsprechend anpassen. Gibt es einen besseren Weg?

+0

Schöne Frage Evan. – Tebo

Antwort

3

Der einfache Teil den Pfad zu dem Element zu erhalten ist, :

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

der InDocumentOrder() gibt es die Sammlung in der Reihenfolge der Wurzel, Kinder, Enkel zu bekommen. Die ToList() ist da, um im nächsten Schritt unerwünschte Effekte zu vermeiden.

Nun, die weniger schönen Teil, der in eine elegantere Weise geschehen, vielleicht könnte:

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

Das ist es. Da jedes XElement sein Kind behält, müssen wir die untergeordneten Elemente von jedem Knoten im Pfad entfernen und dann die Struktur neu erstellen.

0

ich nicht getestet, aber es sollte wie folgt sein:

XDocument xmlFile; 

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

Der ?? Operator gibt die übergeordnete XElement, und wenn die null die ‚c‘.

bearbeiten: Diese Lösung gibt, was Sie wollen, aber ich bin nicht sicher, ob es das beste ist, weil es ziemlich kompliziert wird:

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

Das funktioniert nicht, es gibt nur das gesamte Dokument zurück. Die Verwendung von ?? ist aber eine gute Idee. – Evan

+0

OK, nächster Schritt, ich werde es testen! :) –

+0

Es gibt tatsächlich die Eltern, mit allen Kindern, nicht das gesamte Dokument zurück. –

1

Das Problem ist viel einfacher, wenn Sie einen Iterator bauen:

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

Verwenden Sie es irgendwo Sie möchten eine Linq-Abfrage verwenden, zum Beispiel:

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

oder

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

Bearbeiten:

Ich sehe jetzt, dass das, was der obige Code vorsieht, nicht das ist, was der Fragesteller verlangt hat. Aber was der Fragesteller verlangte, ist ein wenig seltsam, scheint mir, da die XElement, die er zurückgeben will, ein völlig neues Objekt ist, nicht etwas im vorhandenen Dokument.

Noch ist die Ehre zu dienen.Bestaunen auf meine Werke, ihr Gewaltigen und Verzweiflung:

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

Dadurch werden alle Elemente des übergeordneten Elements zurückgegeben, wie bei Sander. –

+0

Die überarbeitete Abfrage funktioniert und ist prägnanter als die derzeit am höchsten bewertete Abfrage. Gut gemacht. –

1

die Eingabe gegeben, und die Anforderungen wie gesagt, dies tun, was Sie wollen:

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

Ausgang:

<category text="Business">... 

Dieser wird funktionieren, auch wenn es kein solches Element oder kein solches Attribut gibt.

+0

Immer gibt mir "Enumeration ergab keine Ergebnisse". –