
Niecały tydzień temu napisałem posta o LINQ to XML. Wtedy jednak zająłem się tylko tym czym jest XML, co to jest LINQ oraz jak wygląda przetwarzanie dokumentów XML przy pomocy przestrzeni nazw System.Xml. Dzisiaj jako kontynuacja wpisu pokażę już konkretne zapytania XLINQ oraz jak ogólnie wygląda przestrzeń nazw System.Xml.Linq.
4. LINQ to XML (XLINQ)
LINQ to XML jest nowym podejściem pracy z dokumentami XML. Dzięki LINQ to XML można łatwo załadować dokument XML do pamięci i równie łatwo można poprzez zapytania odczytywać dokument oraz go modyfikować. Po przetworzeniu można łatwo dokument zapisać z pamięci na dysk.
Ogromną zaletą LINQ to XML jest to, że można przetwarzać dokumenty XML-owe o wiele łatwiej niż to miało miejsce poprzednio czyli na przykład przy pomocy .NET-owej przestrzeni nazw System.Xml. Dzięki LINQ obsługa tych dokumentów stała się o wiele prostsza, przetwarzanie pojedynczych węzłów stało się o wiele bardziej łatwiejsze, a kod stał się o wiele bardziej czytelniejszy i krótszy.
LINQ to XML jest dostępne w przestrzeni nazw System.Xml.Linq, która to przestrzeń dostarcza wszystkie potrzebne klasy do pracy z dokumentem XML. Poniżej przedstawiam diagram klas tej przestrzeni nazw:

Jak widać powyżej przestrzeń nazw XLINQ jest dość złożona. Jednak aby sprawnie posługiwać się dokumentami XML za pomocą LINQ to XML trzeba poznać trzy najważniejsze klasy: XElement, XAttribute oraz XDocument. Klasa XElement reprezentuje element XML. Jak widać z diagramu powyżej pochodzi ona z abstrakcyjnej klasy XContainer, która to pochodzi z klasy XNode. Hierarchia tych klas jest taka, ponieważ każdy element w XML jest węzłem. Klasa XElement jest jedną z najważniejszych klas, ponieważ z jej pomocą można tworzyć i manipulować elementami XML tzn. można tworzyć elementy, dodawać i modyfikować atrybuty elementów oraz manipulować zawartością elementów tj. dodawać, usuwać oraz modyfikować elementy potomne.
Klasa XAttribute służy do manipulowania atrybutami elementów dokumentu XML. Jak wiadomo atrybut w XML jest to para nazwa/wartość przypisana konkretnemu elementowi. Praca z atrybutami w LINQ to XML jest bardzo podobna do pracy z elementami XML. Jednak należy pamiętać, że atrybut nie jest węzłem XML co doskonale widać z diagramu wyżej. Ponadto każdy atrybut musi posiadać nazwę, która jest unikatowa dla danego elementu.
Klasa XDocument umożliwia prace z poprawnymi dokumentami XML włączając w to deklaracje i komentarze. Klasa ta dziedziczy z klasy XContainer, czyli może posiadać elementy i węzły lecz może posiadać tylko jeden główny obiekt XElement, który jest węzłem głównym (ang. Root Element).
5. Programowanie w LINQ to XML (XLINQ)
5.1. Tworzenie drzew
Jedną z podstawowych rzeczy przy obsłudze dokumentów XML jest ich tworzenie. W XLINQ tworzenie drzew XML jest bardzo proste. Poniżej przedstawiam prosty pseudo-kod do tworzenia drzew XML:
XElement employee = new XElement(XName,
new XElement(XName,
new XElement(XName name),
new XElement(XName name),
new XElement(XName name),
new XElement(XName name)
);
Jak widać z powyższego kodu, za pomocą klasy XElement tworzymy nowe elementy drzewa XML. Deklaracja nagłówka klasy prezentuje się następująco:
XElement(XName name, params object[] content)
Z powyższej definicji widać, że konstruktor klasy przyjmuje dwa parametry: pierwszy, jest to nazwa elementu tworzonego, a drugim parametrem jest tablica obiektów, które mają się znaleźć w klasie. Obiektem tym może być zarówno atrybut jak i nowy element potomny.
5.2. Wczytywanie drzew XML
Wczytywanie drzewa XML możemy zrealizować z różnych źródeł. Podstawowa składnia wczytywania drzewa wygląda następująco:
XElement employees = XElement.Load(object);
Metoda ta jako parametr przyjmuje obiekty typu Stream, String (ścieżka do pliku), XmlReader, TextReader.
5.3. Serializacja dokumentów XML
Proces serializacji polega na zapisaniu obiektu na dysk twardy lub do pamięci. Kiedy proces przetwarzania dokumentu XML zostanie zakończony, wyniki pracy można zapisać do pliku. Proces ten uzyskuje się dzięki metodzie Save() klasy XDocument lub XElement. Do metody tej możemy przekazać albo ścieżkę do fizycznego miejsca na dysku, gdzie ma być zapisany dokument, obiekt XmlWriter lub TextWriter. Poniżej krótki przykład:
XElement employees = XElement.Parse(@"
Scott
Mr. SciFi
Gaming
04/17/92
M
M
");
using (StringWriter sw = new StringWriter())
{
employees.Save(sw, true);
}
5.4. Wykonywanie zapytań LINQ w dokumentach XML
Kiedy dokument XML zostanie już wczytany można wykonywać zapytania XLINQ. Najlepszym sposobem do wyciągnięcia wszystkich elementów jest odwołanie się do głównego elementu dokumentu Root. Następnie należy się odwołać do interesującego nas elementu i wybrać elementy które nas interesują. Poniżej przedstawiam przykład:
var doc = XDocument.Load("planety.xml");
var q = from p in doc.Root.Elements("Planet")
select p.Element("Name").Value;
foreach (var s in q)
{
rtbXml.Text += s.ToString()+"\n"; //RichTextBox
}
Taka składnia wyrażenia jest dobra, kiedy nie musimy zbyt głęboko wchodzić w strukturę dokumentu. Natomiast kiedy musimy przejść przez wiele poziomów dokumentu z pomocą przychodzi metoda Descendats(). Poniżej krótki kod, przedstawiający użycia tej metody. Kod ten wyświetla wszystkie elementy które są zagnieżdżone drugim lub większym poziomie dokumentu względem elementu głównego:
var doc = XDocument.Load("planety.xml");
var q = from p in doc.Descendants("Moon")
select p.Element("Name");
foreach (var s in q)
{
rtbXml.Text += s.ToString() + "\n";
}
Dla porównania ten sam efekt możemy uzyskać za pomocą takiego wyrażenia:
var doc = XDocument.Load("planety.xml");
var q = from p in doc.Root.Elements("Planet").Elements("Moons").Elements("Moon")
select p.Element("Name");
foreach (var s in q)
{
rtbXml.Text += s.ToString() + "\n";
}
Oczywiście z kodu XML można wyciągać również konkretne, interesujące się dane. Poniżej krótki przykład wyszukiwania elementu:
var doc = XDocument.Load("planety.xml");
var q = from XText t in doc.DescendantNodes().OfType()
where t.Value == "Moon"
select t;
rtbXml.Text = q.First().ToString();
Kiedy elementy w dokumencie zawierają charakterystyczną, unikalną cechę w dokumencie, odnalezienie takiego węzła nie stanowi problemu:
var doc = XDocument.Load("planety.xml");
var q = from p in doc.Root.Elements("Planet")
where p.Attribute("id").Value == "3"
select p;
rtbXml.Text = q.First().ToString();
5.5. Edytowanie dokumentu XML
Jedną z podstawowych operacji na dokumencie jest usuwanie węzłów. Wykonanie tego zadania realizuje się za pomocą metody Remove(). Metoda ta nie przyjmuje żadnych parametrów. Jest to metoda wywołana z wyniku zapytania. Po zakończeniu edycji należy zapisać plik:
var doc = XDocument.Load("planety2.xml");
var q = from p in doc.Root.Elements("Planet")
where p.Attribute("id").Value == "2"
select p;
q.Remove();
doc.Save("planety2.xml");
rtbXml.Text = "Element został usunięty";
Edycja węzłów jest bardzo podobna. Wybieramy interesujący nas węzeł do edycji i za pomocą metody SetValue() . Metoda ta, podobnie jak przy usuwaniu, jest wywołana ze zmiennej przechowującej wynik zapytania:
var doc = XDocument.Load("planety2.xml");
var q = (from p in doc.Descendants("Planet")
where p.Element("Name").Value == "Mercury"
select p).Single();
q.Element("Name").SetValue("Mercury po zmianie");
doc.Save("planety2.xml");
rtbXml.Text = "Element został zmieniony";
W powyższym kodzie przy zapytaniu została użyta metoda Single(), która gwarantuje zwrócenie tylko jednego rezultatu. Zabezpiecza to nas przed przypadkowym zmienieniem większej ilości danych w dokumencie.
Ostatnią czynnością przy edycji dokumentów jest dodawanie nowych elementów. Operację tę realizujemy za pomocą metody Add() obiektu XDocument. Nowy element możemy dodać w dowolnym stopniu zagnieżdżenia. W poniższym przykładzie, nowy element jest dodawany jest w węźle głównym dokumentu Root:
var doc = XDocument.Load("planety2.xml");
doc.Root.Add(new XElement("Planet", new XAttribute("id", "5"), new XElement("Name", "Jupiter")));
doc.Save("planety2.xml");
rtbXml.Text = "Element został dodany";
Nowy element dodawany jest przez new XElement, nowy atrybut przez new XAttribute. XElement jako parametry przyjmuje nazwę elementu i jego wartość, lub nowe zagnieżdżone elementy. XAttribute przyjmuje nazwę atrybutu i jego wartość.
5.6. Walidacja dokumentu XML
Ostatnią czynnością dla upewnienia się, czy w dokumencie zostały wprowadzone dobre zmiany jest walidacja dokumentu. Walidacja polega na sprawdzenie dokumentu czy jest zgodny ze schematem. Dzięki wbudowanemu mechanizmowi w VS za pomocą paru linijek kodu, można sprawdzić poprawność dokumentu XML. Do walidacji dokumentu wymagane jest użycia klasy XmlSchemaSet z przestrzeni nazw System.Xml.Schema oraz użycia metody Validate() obiektu XDocument.
Do wpisu dołączam przykładowy projekt wykonany w Visual Studio 2010 prezentujący wymienione powyżej przykłady. Aby pobrać plik z solucją kliknij TUTAJ!

Świetny tekst! Tego właśnie potrzebowałem do wygodniejszej pracy z XML’em.