Odczytywanie dokumentu XML w C#

Zacznijmy może od tego, że XML to skrót od od angielskich słów „Extensible Markup Language”, które można przetłumaczyć jako „Rozszerzalny Język Znaczników”. Do czego służy ów język? Jego zadaniem jest przechowywanie w sposób uporządkowany różnych danych. Nie należy jednak mylić go z bazą danych, bo po prostu nią nie jest. Dokumenty XML ze względu na swoją specyfikę, nie są uzależnione od żadnej konkretnej platformy. Można ich używać właściwie wszędzie, od komputera PC, aż do różnych innych dziwnych tworów typu inteligentna lodówka 😉

Jak zbudowany jest dokument XML?

Budowa dokumentów XML, jest bardzo intuicyjna i prosta. Całość opiera się, jak wskazuje sama nazwa – na znacznikach. Stwórzmy w takim razie, jakiś prosty dokument XML zawierający np. listę zakupów:

<?xml version="1.0" encoding="UTF-8"?>
<list>
        <item>Chleb</item>
	<item>Masło</item>
	<item>Szynka</item>
</list>

Każdy dokument XML powinien zaczynać się linijką, w której określana jest wersja standardu, z którego korzystamy, oraz kodowanie, w jakim został on zapisany. Powyższy przykład stosuje się do tego zalecenia. Dalsza część powyższego dokumentu opiera się o dwa znaczniki: <list>, oraz <item>. Pierwszy znacznik jest „rodzicem” dla znaczników znajdujących się w jego wnętrzu. My nazwaliśmy go list – w sumie logiczne, bo w jego wnętrzu znajduje się lista zakupów 😉 Z kolei drugi znacznik przechowuje informacje o pojedynczych produktach. Znaczniki inaczej nazywane są też węzłami. Nigdy nie może dojść do sytuacji, kiedy jakiś znacznik nie jest domknięty. Nazewnictwo znaczników jest sprawą dowolną.

Atrybuty

Oprócz znaczników, język XML udostępnia nam też tzw. atrybuty. Dzięki nim możemy nadać jakieś unikalne cechy znacznikowi, którego używamy dla różnych podobnych danych. Przykład:

<?xml version="1.0" encoding="UTF-8"?>
<list>
    <item price="10" type="food">Chleb</item>
	<item price="15" type="tool">Klucz</item>
	<item price="10" type="food">Szynka</item>
</list>

Powyższy dokument dostarcza nam informacje, nie tylko o tym co należy kupić. Dzięki niemu wiemy również, że np. klucz kosztuje 15zł i jest narzędziem. Jednak to jeszcze nie koniec. Możemy pokusić się o rozbudowanie naszego przykładu, o podział listy zakupów na dni tygodnia:

<?xml version="1.0" encoding="UTF-8"?>
<list>
	<weekday name="Poniedziałek">
		<item price="10" type="food">Chleb</item>
		<item price="14" type="food">Masło</item>
		<item price="13" type="food">Szynka</item>
	</weekday>
	<weekday name="Wtorek">
		<item price="20" type="food">Chleb razowy</item>
		<item price="5" type="tool">Długopis</item>
		<item price="10" type="tool">Młotek</item>
	</weekday>
</list>

Teraz wiemy, co należy kupić w poniedziałek, a co we wtorek 😉 Analogicznie możemy rozbudowywać naszą listę dalej. Możemy nawet podzielić ją dodatkowo na to, w jakim sklepie, jakie rzeczy trzeba kupić:

<?xml version="1.0" encoding="UTF-8"?>
<list>
    <weekday name="Poniedziałek">
        <shop name="FirstShop">
            <item price="10" type="food">Chleb</item>
            <item price="14" type="food">Masło</item>
            <item price="13" type="food">Szynka</item>
        </shop>
    </weekday>
    <weekday name="Wtorek">
        <shop name="FirstShop">
            <item price="20" type="food">Chleb razowy</item>
        </shop>
        <shop name="SecondShop">
            <item price="10" type="tool">Młotek</item>
            <item price="5" type="tool">Długopis</item>
        </shop>
    </weekday>
</list>

Chyba dalsze tłumaczenie jest zbędne, wystarczy popatrzeć na przykłady, żeby załapać o co chodzi. Skoro wszystko jasne, przejdźmy teraz do tego, jak odczytać powyższy przykład przy użyciu C#.

Odczytywanie danych z pliku XML w C#

Obsługę XML’a przez C# zawdzięczamy przestrzeni nazw System.Xml. Utwórzmy zatem nową klasę, której będziemy używać do odczytywania pliku XML:

using System.Xml;

namespace XmlInterpreter
{
    public class XmlReader
    {
        public XmlDocument XmlDoc;
        public XmlReader(string PathFile)
        {
            XmlDoc = new XmlDocument();
            XmlDoc.Load(PathFile);
        }
    }
}

Kod póki co, jest bardzo prosty. Tworzymy nowy obiekt klasy XmlDocument oraz ładujemy plik XML do pamięci, przy pomocy metody, jaką ta klasa udostępnia.

Czasami zdarza się, że potrzebujemy wyciągnąć z XML’a jedynie wartości z poszczególnych znaczników, bez względu na ich hierarchię. Do tego celu potrzebujemy dwóch informacji: Ile takich znaczników znajduje się w dokumencie oraz jaką noszą nazwę. Liczbę znaczników o danej nazwie z powodzeniem zwróci poniższa metoda:

public int GetNumberOfItems(string name)
{
    return XmlDoc.GetElementsByTagName(name).Count;
}

Z kolei do wyciągnięcia wartości ze znacznika, o danym indeksie z powodzeniem można wykorzystać poniższą metodę:

public string GetElementValue(int number, string name)
{
    return XmlDoc.GetElementsByTagName(name).Item(number).InnerText;
}

Jak użyć tych dwóch metod? Bardzo prosto. Skoro wiemy jaka jest ilość elementów „item” w dokumencie, to do odczytania wartości przy pomocy metody GetElementValue(int number , string name) wystarczy zwykła pętla for:

static void Main(string[] args)
{
    XmlReader xmlReader = new XmlReader("myXml.xml");
    int i = xmlReader.GetNumberOfItems("item");
    for (int a = 0; a != i; a++)
    {
        Console.WriteLine("Value:" + xmlReader.GetElementValue(a, "item"));
    }
    Console.ReadKey();
 }

Powyższy kod wyświetli nam „na sucho” wartości znajdujące się wewnątrz znaczników item:item

Podobnie sprawa ma się z atrybutami. Poniższa metoda zwróci wartość danego atrybutu dla danego znacznika o danym indeksie:

public string GetAttributeValue(int number, string ElementName, string AttributeName)
{
    XmlNode Node = XmlDoc.GetElementsByTagName(ElementName).Item(number);
    return Node.Attributes.GetNamedItem(AttributeName).InnerText;
}

Przykład użycia:

static void Main(string[] args)
{
    XmlReader xmlReader = new XmlReader("myXml.xml");
    int i = xmlReader.GetNumberOfItems("item");
    for (int a = 0; a != i; a++)
    {
        Console.WriteLine("Value:" + xmlReader.GetElementValue(a, "item") + " Attribute:" + xmlReader.GetAttributeValue(a, "item", "type"));
    }
    Console.ReadKey();
}

item

Wszystko pięknie ładnie, ale… Czasami takie odczytywanie dokumentu XML nie wystarczy. Nie wynika z niego, które rzeczy gdzie mają zostać kupione. Nie wynika też, kiedy mają być kupione. Na szczęście temu też można zaradzić 😉

Odczytywanie wartości dla wybranego węzła

Załóżmy, że chcemy odczytać z naszego XML’a listę sklepów jakie odwiedzimy tylko w poniedziałek. Wcześniejsze sposoby do tego się nie nadają. Jak, więc temu zaradzić? To, również nie jest tak trudne, jak mogłoby się wydawać. Oto metoda, która zrealizuje takie zadanie:

public string[] GetShopsForSelectedDay(string Day)
{
    List<string> ListOfShops = new List<string>();
    XmlNodeList Shops = XmlDoc.SelectNodes("//weekday[@name = '" + Day + "']//shop");
    foreach (XmlNode xn in Shops)
    {
        if (xn.HasChildNodes)
        {
            ListOfShops.Add(xn.Attributes["name"].InnerText);
        }
     }
     return ListOfShops.ToArray();
}

Tak wygląda, natomiast jej użycie:

foreach (string shop in xmlReader.GetShopsForSelectedDay("Poniedziałek"))
        Console.WriteLine("Poniedziałek: " + shop);
foreach (string shop in xmlReader.GetShopsForSelectedDay("Wtorek"))
         Console.WriteLine("Wtorek: " + shop);

W wyniku działania kodu, otrzymamy informację, że w poniedziałek odwiedzimy jedynie sklep „FirstShop”, natomiast we wtorek sklepy „FirstShop” i „SecondShop” – wszystko się zgadza 🙂 To może teraz spróbujmy pobrać nazwy przedmiotów tylko dla sklepu numer dwa we wtorek:

public string[] GetItemsForSelectedShopInDay(string Day, string Shop)
{
    List<string> ListOfItems = new List<string>();
    XmlNodeList items = XmlDoc.SelectNodes("//weekday[@name = '" + Day + "']//shop[@name = '" + Shop + "']");
    foreach (XmlNode xn in items)
    {
        if (xn.HasChildNodes)
        {
            foreach (XmlNode item in xn.ChildNodes)
                ListOfItems.Add(item.InnerText);
         }
     }
     return ListOfItems.ToArray();
}

Użycie metody jest identyczne, jak wcześniej:

foreach (string item in xmlReader.GetItemsForSelectedShopInDay("Wtorek","SecondShop"))
    Console.WriteLine("Wtorek, sklep drugi: " + item);

Zwraca nam ona wartości: „Młotek” i „Długopis” – czyli wszystko w jak najlepszym porządku.

Jak zapewne zauważyłeś, całość operacji sprowadza się do wybrania odpowiedniego węzła w linijce XmlNodeList items = XmlDoc.SelectNodes(„//weekday[@name = ‚” + Day + „‚]//shop[@name = ‚” + Shop + „‚]”);, gdzie po ukośnikach podajemy nazwę znacznika, a po znaku @ nazwę atrybutu. Z kolei wyciąganie wartości niższych w hierarchii sprowadza się do użycia odpowiedniej instrukcji. W przypadku atrybutów jest to xn.Attributes[„name”].InnerText, natomiast w przypadku wartości pomiędzy znacznikami jest to pętla foreach (XmlNode item in xn.ChildNodes); Znając te zasady możemy odczytać z pliku XML dosłownie wszystko 😉

Cóż, to byłoby chyba na tyle. Zapisem pliku XML z poziomu C# zajmę się w osobnym wpisie. Jak zwykle zachęcam do zadawania pytań i zgłaszania ewentualnych błędów merytorycznych zawartych we wpisie w komentarzach.

7,754 total views, 1 views today

8 przemyśleń nt. „Odczytywanie dokumentu XML w C#

  1. Zastanawiam się dlaczego używasz tutaj XmlDocument do odczytu czy nie lepiej skorzystać z XDocument i Linq2Xml? API jest przyjemniejsze w użyciu i da się obejść bez XPath’ów (które mają tą samą tendencji co regexy do bycia write-only, plus poza XMLQuire niewiele jest darmowych narzędzi do pisania/debugowania XPathów)

    • Dlaczego? Po prostu jeszcze do innych „narzędzi” nie dotarłem. Ostatnio potrzebowałem odczytywać xml’a w projekcie i na szybko znalazłem klasę XmlDocument, którą zacząłem się bawić. Zdała egzamin, więc postanowiłem coś o tym napisać. Niemniej jednak, widzę, że przydałoby się zgłębić bardziej w ten temat 🙂

      Pozdrawiam!

  2. Cześć! Przy okazji omawiania XML-a warto wspomnieć o opcji w Visual Studio > Menu > Edit > Paste Special > Paste XML/JSON As Classes. Fajna sprawa 🙂
    Pozdrawiam,
    Maciej

  3. Używanie tego: „XmlDoc.GetElementsByTagName(name).Count;” po to żeby wyliczyć ile jest elementów o danej nazwie a następnie iterować po całym XmlDocu jest bez sensu. Metoda GetElementsByTagName(string) od razu zwraca Ci to czego potrzebujesz 🙂

Możliwość komentowania jest wyłączona.