XML richtig einlesen ?!

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von TRiViUM.

    XML richtig einlesen ?!

    Hallo liebe Community,ich möchte eine XML-Datei einlesen, die folgenden Inhalt hat:
    Spoiler anzeigen

    HTML-Quellcode

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <Messages>
    3. <Message ID="1">
    4. <Text>
    5. <DE>ABC</DE>
    6. <EN>ABC</EN>
    7. </Text>
    8. <ResetMode>0</ResetMode>
    9. <Action>0</Action>
    10. </Message>
    11. <Message ID="2">
    12. <Text>
    13. <DE>ABC</DE>
    14. <EN>ABC</EN>
    15. </Text>
    16. <ResetMode>0</ResetMode>
    17. <Action>0</Action>
    18. </Message>
    19. <Message ID="3">
    20. <Text>
    21. <DE>ABC</DE>
    22. <EN>ABC</EN>
    23. </Text>
    24. <ResetMode>0</ResetMode>
    25. <Action>0</Action>
    26. </Message>
    27. </Messages>


    Um nach dem Einlesen einfacher auf den Inhalt zugreifen zu können habe ich dazu erstmal eine Klasse Message erstellt, welche folgende Eigenschaften besitzt:
    Spoiler anzeigen

    C#-Quellcode

    1. /// <summary>
    2. /// Ruft die ID ab, die der Meldung zugeordnet ist, oder legt diese fest.
    3. /// </summary>
    4. public string ID { get; set; }
    5. /// <summary>
    6. /// Ruft das Wörterbuch mit den anzuzeigenden Texten in unterschiedlichen Sprachen ab, oder legt dieses fest.
    7. /// </summary>
    8. public Dictionary<string, string> Text;
    9. /// <summary>
    10. /// Ruft ab, wie diese Meldung zu behandeln ist, oder legt dies fest.
    11. /// </summary>
    12. public int Action { get; set; }
    13. /// <summary>
    14. /// Ruft die Betriebsart ab, in der die Meldung zurückgesetzt werden kann, oder legt dies fest.
    15. /// Dies hat nur Auswirkungen auf Meldungen, dessen Action das Flag SysStop besitzt.
    16. /// </summary>
    17. public int ResetMode { get; set; }


    Diese Klasse soll quasi einen Knotenpunkt Message repräsentieren.

    Die Elemente, die im XML-Dokument so zu sagen unter dem XML-Pfad Messages/Message/Text auftauchen (aktuell DE, EN) sind beliebig erweiterbar.
    Hier sollen Übersetzungstexte rein, weshalb die vorhandenen Sprachen eigentlich unbekannt sind und somit iteriert werden sollen.
    Die gefundenen Sprachen sollen dann im zuvor instanzierten Message-Object der Dictionary hinzugefügt werden.

    Leider bekomme ich es einfach nicht auf die Reihe, alle Infos sauber auszulesen, ohne dass ich versehentlich einen Knoten überspringe.
    Aktuell nutze ich den XmlReader zum Auslesen, da dieser schneller sein soll als andere Objekte und das XML-Dokument etwas größer werden kann (z.B. 500 Message-Einträge).

    Hier mein aktueller Versuch:
    Spoiler anzeigen

    C#-Quellcode

    1. internal List<Message> ReadMessages(string path = "D:\\Test.xml")
    2. {
    3. var messageList = new List<Message>();
    4. using (var reader = XmlReader.Create( path))
    5. {
    6. try
    7. {
    8. // Haupt-Knoten für Meldungen öffnen
    9. reader.ReadStartElement("Messages");
    10. while (reader.Read())
    11. {
    12. // Meldungs-Knoten einlesen
    13. if ( reader.Name != "Message" && reader.NodeType != XmlNodeType.Element) continue;
    14. // Meldungs-ID einlesen
    15. var msgid = reader.GetAttribute("ID");
    16. // Meldungstexte einlesen
    17. reader.ReadStartElement("Text");
    18. var nodeType = reader.NodeType;
    19. //using (var innerReader = reader.ReadSubtree())
    20. //{
    21. // while (innerReader.Read())
    22. // {
    23. // var abc = reader.Name;
    24. // var value = reader.ReadElementContentAsString();
    25. // }
    26. //}
    27. }
    28. }
    29. catch (Exception ex) { throw ex; }
    30. }
    31. return messageList;
    32. }


    Sobald ich das Element Text einlesen möchte, fliegt er mit einer Exception raus, weil das Element Text nicht gefunden wurde.

    Wie kann ich das bewerkstelligen?

    BTW:
    Die XML-Struktur habe ich mir selber "ausgedacht" und kann natürlich geändert werden, sofern mein Vorhaben mit der aktuellen Struktur gar nicht Sinnvoll wäre...

    Vielen Dank für Eure Unterstützung! :saint:
    @TRiViUM Warum willst Du das Multi-Language-Konzept von Microsoft neu erfinden?
    Warum nicht so:
    Programme mehrsprachig erstellen
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Warum willst Du das Multi-Language-Konzept von Microsoft neu erfinden?

    Nee, das habe ich gar nicht vor.

    Meine Software soll eine XML-Datei einlesen, in der Meldungen hinterlegt sind, die das Programm anzeigen kann.
    Zu jeder Meldung gibts ja noch weitere Eigenschaften, die das Verhalten meiner Software bei bestimmten Meldungen beeinflussen sollen.

    Das mit der Sprache habe ich dort mit aufgenommen, weil ich hier das erste mal mit mehreren Sprachen in meiner Software arbeite.
    Aber eigentlich geht's mir mittlerweile eher ums Prinzip, wie man sowas in der Praxis einlesen würde.

    Ob ich das mit der Sprache so lasse sei mal dahin gestellt, aber ich möchte mich mehr mit XML befassen ^^

    TRiViUM schrieb:

    Meine Software soll eine XML-Datei einlesen, in der Meldungen hinterlegt sind, die das Programm anzeigen kann.
    Probiere es mal anders herum:
    Erstell Dir eine Datenklasse mit all Deinen Properties und serialisiere die per XML.
    Dann ist das Einlesen bereits erledigt.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Erstell Dir eine Datenklasse mit all Deinen Properties und serialisiere die per XML

    Das habe ich bereits schon versucht.
    Allerdings gibt es bei Dictionary<string, string> eine Exception, weil dieses Objekt scheinbar nicht Serialisiert werden kann:

    C#-Quellcode

    1. Das Element Core.Message.Text vom Typ System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] kann nicht serialisiert werden, da es IDictionary implementiert


    Was ich probiert habe:

    C#-Quellcode

    1. var msgList = new List<Core.Message>();
    2. for(int i = 0; i < 10; i++)
    3. {
    4. var msg = new Core.Message();
    5. msg.ID = i.ToString();
    6. msg.Action = 0;
    7. msg.Text.Add("DE", "Deutsch");
    8. msg.Text.Add("EN", "English");
    9. msgList.Add(msg);
    10. }
    11. string xml = "";
    12. var xs = new XmlSerializer(typeof(List<Core.Message>));
    13. using (var sw = new StringWriter())
    14. {
    15. using (XmlWriter writer = XmlWriter.Create(sw))
    16. {
    17. xs.Serialize(writer, msgList);
    18. xml = sw.ToString();
    19. }
    20. }

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „TRiViUM“ ()

    @TRiViUM Ich habe da ein SerializableDictionary gefunden: stackoverflow.com/questions/12…erializing-net-dictionary
    und meine Umsetzung (läuft):
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Runtime.Serialization;
    4. using System.Xml;
    5. using System.Xml.Serialization;
    6. namespace CrdApp.Settings
    7. {
    8. /// <summary>
    9. /// Klasse zur XML-Serialisierung eines Dictionaries
    10. /// </summary>
    11. /// <typeparam name="TKey"></typeparam>
    12. /// <typeparam name="TVal"></typeparam>
    13. [Serializable()]
    14. public class SerializableDictionary<TKey, TVal>
    15. : Dictionary<TKey, TVal>, IXmlSerializable, ISerializable
    16. {
    17. #region Constants
    18. private const string ItemNodeName = "Item";
    19. private const string KeyNodeName = "Key";
    20. private const string ValueNodeName = "Value";
    21. #endregion Constants
    22. #region Properties
    23. /// <summary>
    24. /// Kurze Beschreibung für das Dictionary,
    25. /// muss nach dem Lesen erneut gesetzt werden.
    26. /// </summary>
    27. public string Comment { get; set; }
    28. #endregion Properties
    29. #region Private Members
    30. private XmlSerializer keySerializer = null;
    31. private XmlSerializer valueSerializer = null;
    32. #endregion Private Members
    33. #region Constructors
    34. /// <summary>
    35. /// Standardkonstruktor der Klasse SerializableDictionary
    36. /// </summary>
    37. public SerializableDictionary()
    38. : base()
    39. {
    40. }
    41. /// <summary>
    42. /// Parameter-Konstruktor der Klasse SerializableDictionary
    43. /// </summary>
    44. public SerializableDictionary(IDictionary<TKey, TVal> dictionary)
    45. : base(dictionary)
    46. {
    47. }
    48. /// <summary>
    49. /// Parameter-Konstruktor der Klasse SerializableDictionary
    50. /// </summary>
    51. public SerializableDictionary(IEqualityComparer<TKey> comparer)
    52. : base(comparer)
    53. {
    54. }
    55. /// <summary>
    56. /// Parameter-Konstruktor der Klasse SerializableDictionary
    57. /// </summary>
    58. public SerializableDictionary(int capacity)
    59. : base(capacity)
    60. {
    61. }
    62. /// <summary>
    63. /// Parameter-Konstruktor der Klasse SerializableDictionary
    64. /// </summary>
    65. public SerializableDictionary(IDictionary<TKey, TVal> dictionary, IEqualityComparer<TKey> comparer)
    66. : base(dictionary, comparer)
    67. {
    68. }
    69. /// <summary>
    70. /// Parameter-Konstruktor der Klasse SerializableDictionary
    71. /// </summary>
    72. public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer)
    73. : base(capacity, comparer)
    74. {
    75. }
    76. /// <summary>
    77. /// Streaming-Konstruktor der Klasse SerializableDictionary
    78. /// </summary>
    79. protected SerializableDictionary(SerializationInfo info, StreamingContext context)
    80. {
    81. int itemCount = info.GetInt32("ItemCount");
    82. for (int i = 0; i < itemCount; i++)
    83. {
    84. KeyValuePair<TKey, TVal> kvp = (KeyValuePair<TKey, TVal>)info.GetValue($"Item{i}", typeof(KeyValuePair<TKey, TVal>));
    85. this.Add(kvp.Key, kvp.Value);
    86. }
    87. }
    88. #endregion Constructors
    89. #region ISerializable Members
    90. /// <summary>
    91. /// Füllt eine System.Runtime.Serialization.SerializationInfo mit den Daten,
    92. /// die zum Serialisieren des Zielobjekts erforderlich sind.
    93. /// </summary>
    94. /// <param name="info">
    95. /// Die mit Daten zu füllende System.Runtime.Serialization.SerializationInfo.
    96. /// </param>
    97. /// <param name="context">Das Ziel (siehe StreamingContext) dieser Serialisierung.</param>
    98. void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    99. {
    100. info.AddValue("ItemCount", this.Count);
    101. int itemIdx = 0;
    102. foreach (KeyValuePair<TKey, TVal> kvp in this)
    103. {
    104. info.AddValue($"Item{itemIdx}", kvp, typeof(KeyValuePair<TKey, TVal>));
    105. itemIdx++;
    106. }
    107. }
    108. #endregion ISerializable Members
    109. #region IXmlSerializable Members
    110. /// <summary>
    111. /// Konvertiert ein Objekt in seine XML-Darstellung.
    112. /// </summary>
    113. /// <param name="writer">
    114. /// Der System.Xml.XmlWriter-Stream, in den das Objekt serialisiert wird.
    115. /// </param>
    116. void IXmlSerializable.WriteXml(XmlWriter writer)
    117. {
    118. if (this.Count > 0 && !string.IsNullOrWhiteSpace(this.Comment))
    119. {
    120. // wenn das Dict leer ist, knallt es beim Einlesen!
    121. writer.WriteComment(this.Comment);
    122. }
    123. foreach (KeyValuePair<TKey, TVal> kvp in this)
    124. {
    125. writer.WriteStartElement(ItemNodeName);
    126. writer.WriteStartElement(KeyNodeName);
    127. this.KeySerializer.Serialize(writer, kvp.Key);
    128. writer.WriteEndElement();
    129. writer.WriteStartElement(ValueNodeName);
    130. this.ValueSerializer.Serialize(writer, kvp.Value);
    131. writer.WriteEndElement();
    132. writer.WriteEndElement();
    133. }
    134. }
    135. /// <summary>
    136. /// Generiert ein Objekt aus seiner XML-Darstellung.
    137. /// </summary>
    138. /// <param name="reader">
    139. /// Der System.Xml.XmlReader-Stream, aus dem das Objekt deserialisiert wird.
    140. /// </param>
    141. void IXmlSerializable.ReadXml(XmlReader reader)
    142. {
    143. if (reader.IsEmptyElement)
    144. {
    145. return;
    146. }
    147. // Move past container
    148. if (!reader.Read())
    149. {
    150. throw new XmlException("Error in Deserialization of Dictionary");
    151. }
    152. while (reader.NodeType != XmlNodeType.EndElement)
    153. {
    154. reader.ReadStartElement(ItemNodeName);
    155. reader.ReadStartElement(KeyNodeName);
    156. TKey key = (TKey)this.KeySerializer.Deserialize(reader);
    157. reader.ReadEndElement();
    158. reader.ReadStartElement(ValueNodeName);
    159. TVal value = (TVal)this.ValueSerializer.Deserialize(reader);
    160. reader.ReadEndElement();
    161. reader.ReadEndElement();
    162. this.Add(key, value);
    163. reader.MoveToContent();
    164. }
    165. // Read End Element to close Read of containing node
    166. reader.ReadEndElement();
    167. }
    168. /// <summary>
    169. /// Diese Methode ist reserviert und sollte nicht verwendet werden.
    170. /// </summary>
    171. /// <returns></returns>
    172. System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
    173. {
    174. return null;
    175. }
    176. #endregion IXmlSerializable Members
    177. #region Private Properties
    178. /// <summary>
    179. /// Serializer zum Einlesen eines Value-Elements
    180. /// </summary>
    181. private XmlSerializer ValueSerializer
    182. {
    183. get
    184. {
    185. if (this.valueSerializer == null)
    186. {
    187. this.valueSerializer = new XmlSerializer(typeof(TVal));
    188. }
    189. return this.valueSerializer;
    190. }
    191. }
    192. /// <summary>
    193. /// Serializer zum Einlesen eines Key-Elements
    194. /// </summary>
    195. private XmlSerializer KeySerializer
    196. {
    197. get
    198. {
    199. if (this.keySerializer == null)
    200. {
    201. this.keySerializer = new XmlSerializer(typeof(TKey));
    202. }
    203. return this.keySerializer;
    204. }
    205. }
    206. #endregion Private Properties
    207. }
    208. }

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    TRiViUM schrieb:

    Warum benötigt man dennoch so eine erweiterte Klasse?
    Weil das Serialisierungs-Zeugs eben nur als Interface verfügbar ist, nicht aber als implementierter Code.
    Als ich das brauchte, bab ich gesucht und gefunden und genutzt.
    Tiefergehende Gedanken hab ich mir da wohl nicht gemacht.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Jo, werde ich jetzt vermutlich auch so machen.

    Aber noch mal kurz zurück zum "manuellen" Auslesen der XML-Datei:

    HTML-Quellcode

    1. <Message ID="1">
    2. <Text>
    3. <DE>Deutsch</DE>
    4. <EN>English</EN>
    5. </Text>
    6. <Action>0</Action>
    7. <ResetMode>0</ResetMode>
    8. </Message>


    Wie würde ich das Attribute ID auslesen, bis zum Knoten Text kommen und alle dort befindlichen "Elemente" auslesen können? (rein interessehalber)...

    Offenbar habe ich noch nicht ganz verstanden, wie die XmlReader.Read()-Funktion funktioniert bzw. was für dieses Objekt ein Knoten bedeutet und was nicht.

    Was ich suche ist quasi eine Erklärung, welche Funktionen der Klasse XmlReader den Cursor auf die Einzelnen Knotenpunkte setzen kann, die ich in meinem XML-Dokument habe.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „TRiViUM“ ()

    TRiViUM schrieb:

    (rein interessehalber)
    Ich gehe immer den anderen Weg. ;)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!