Laden...

Wie kann ich ein bestimmtes Attribut von allen XML Knoten rekursiv auslesen?

Erstellt von J4m3s90 vor 3 Jahren Letzter Beitrag vor 3 Jahren 453 Views
J
J4m3s90 Themenstarter:in
17 Beiträge seit 2021
vor 3 Jahren
Wie kann ich ein bestimmtes Attribut von allen XML Knoten rekursiv auslesen?

verwendetes Datenbanksystem: XML

Hallo zusammen,

folgende XML sei gegeben:


<Test>
   <REQUEST1 counter="77"  />
   <REQUEST2 counter="22"  />
   <REQUEST3 counter="33"  />
   <child1> 
		<REQUEST4 counter="44"  />   
   </child1>
</Test>

Ich schaffe es nicht alle Attribute mit dem Namen Counter auszulesen.
Die eingelesene XML Datei ändert sich bei jedem Aufruf, die Verschachtelungstiefe ist Variabel.
Wie schaffe ich es, alle Werte der Attribute auszulesen?

16.807 Beiträge seit 2008
vor 3 Jahren
  • Root finden
  • Elemente auslesen
  • Elemente der Elemente auslesen (also Rekursiv, am besten über Stack<T>)
  • Von jedem Element die Attribute laden und anschauen

Der Einfachheithalber kannst Du die System.Xml.Linq nehmen; hier mit XElement.

XmlDocument in so einem Fall nur, wenn Du wirklich eine sehr große XML Datei hast, die Du gesamt nicht in den Speicher bekommst.

XML gehört zu den besten und umfangreichsten Dokumentationen bei MS Docs.
Zu jeder Klasse gibt es ausführliche Beispiele, die Du Dir anschauen kannst.

J
J4m3s90 Themenstarter:in
17 Beiträge seit 2021
vor 3 Jahren

Hallo,
vielen Dank für die schnelle Antwort.
Habe das mal probiert so umzusetzen.

   
 public static void Main(string[] args)
        {
            XDocument doc = XDocument.Load("Message.xml");
            IEnumerable<XElement> node = doc.Elements();
            SearchForAttribute(node);
            Console.ReadLine();
        }
       public static void SearchForAttribute(IEnumerable<XElement> Elements)
        {
            foreach (var Element in Elements)
            {
                if(Element.Attribute("counter") != null)
                {
                    Console.WriteLine(Element.Attribute("counter").Value);
                }
                Console.WriteLine(Element.Name);
                if (Element.HasElements)
                {
                    SearchForAttribute(Element.Elements());
                }
            }
        }

Gibt es hier evtl. noch Verbesserungen oder Möglichkeiten Ressourcen zu schonen?

16.807 Beiträge seit 2008
vor 3 Jahren

Wie gesagt:

also Rekursiv, am besten über Stack<T>

damit löst man das stabiler, weil keine Stackoverflow Exception damit entstehen kann, wenn die Rekursion zu tief wird.

Und ansonsten seh ich den Sinn nicht, wieso Du die Console-Commands mit in die Methode haust. Gib doch die gefundenen Elemente via yield zurück.
Das ist auch vom Code-Aufbau dann super einfach mit dem Stack<T> zusammen.

public static void Main()
{
    XDocument doc = XDocument.Load("Message.xml");

    IEnumerable<XAttribute> attributes = GetCounterAttributes(doc);
    foreach (var foundAttribute in attributes)
    {
        Console.WriteLine(foundAttribute.Value);
    }
}

public static IEnumerable<XAttribute> GetCounterAttributes(XDocument doc)
{
    XElement? root = doc.Root;
    if (root is not null)
    {
        Stack<XElement> queue = new Stack<XElement>(root.Elements());
        while (queue.TryPop(out XElement element))
        {
            foreach (var childElement in element.Elements())
            {
                queue.Push(childElement);
            }

            XAttribute attr = element.Attribute("counter");
            if (attr is not null)
            {
                yield return attr;
            }
        }
    }
}

Oder Du arbeitest mit Descendants (ist gleich im ersten XML Beispiel in der Doc), dann brauchst Du gar keine Rekursion, sondern bekommst direkt alle Elemente.

IEnumerable<XElement> allElements = doc.Root.Descendants();
IEnumerable<XAttribute> allAttributes = allElements.SelectMany(el => el.Attributes());
IEnumerable<XAttribute> counterAttributes = allAttributes.Where(at => at.Name == "counter");
foreach (var foundAttribute in counterAttributes)
{
    Console.WriteLine(foundAttribute.Value);
}
J
J4m3s90 Themenstarter:in
17 Beiträge seit 2021
vor 3 Jahren

Danke, ich werde mir das gleich anschauen, mit dem Stack hatte ich nicht ganz verstanden in der Doc aber werde es noch mal suchen.

Und ansonsten seh ich den Sinn nicht, wieso Du die Console-Commands mit in die Methode haust.

Die Console-Commands gibt es nachher nicht mehr. Ich schreibe mir nur den Wert des Attributes in eine Variable und arbeite dann außerhalb damit weiter. War nur für mich die Kontrolle ob es funktioniert.
Ich durchsuche die XML Datei nach dem Attribute ( dies entspricht einem Zähler ) sobald ich den habe, kann die Funktion beendet werden. Das Attribute gibt es auch nur ein mal, ich weiß nur nicht wo.

16.807 Beiträge seit 2008
vor 3 Jahren

Stack vermeidet die Rekursion. Keine Raketentechnik.
Aber wenn Dir nur um da seine geht, gar nicht die Struktur, dann reicht das mit Linq:


IEnumerable<XElement> allElements = doc.Root.Descendants();
IEnumerable<XAttribute> allAttributes = allElements.SelectMany(el => el.Attributes());
List<XAttribute> counterAttributes = allAttributes.Where(at => at.Name == "counter").ToList();

if(counterAttributes.Count == 0)
{
  // keines gefunden
}
else if(counterAttributes.Count == 1)
{
  // eines gefunden
  XAttribute attribute = counterAttributes.Single();
}
else
{
  // hups, doch mehrfach gefunden?
}
J
J4m3s90 Themenstarter:in
17 Beiträge seit 2021
vor 3 Jahren

Perfekt, danke!
Jetzt habe ich es für zukünftige Projekte verstanden.
Habe leider nichts hilfreiches gefunden. Es wurde nirgends erwähnt, wie ich alle Elemente einer XML suche und dann dort jeweils nach einem Attribute suche.

Danke dir Abt!