• Zwiększ rozmiar czcionki
  • Domyślny  rozmiar czcionki
  • Zmniejsz rozmiar czcionki
Home Artykuły .NET LINQ to XML, cz. 4 - Transformacje i serializacja

LINQ to XML, cz. 4 - Transformacje i serializacja

Email Drukuj PDF
Ocena użytkowników: / 1
SłabyŚwietny 

Poprzednie odcinki cyklu stanowią solidną podstawę do rozpoczęcia "prawdziwej" pracy z technologią Linq to Xml. Ta "prawdziwa" praca to transformacje i serializacja dokumentów Xmlowych - jedne z najczęstszych zastosowań technologii Linq to Xml. Przy okazji poznamy nowe elementy języka C#, które świetnie uzupełniają Linq to Xml.

Technologia Linq to Xml doskonale nadaje się do prostych transformacji dokumentów Xmlowych. Nie oznacza to, że Linq to Xml zastępuje Xslt. Xslt jest wciąż standardem w tym zakresie. Często jednak zdarza się, że trzeba tylko nieznacznie zmienić dokument Xmlowy. Zwykle w takim przypadku zastosowanie Xslt to "przewaga formy nad treścią" - czas potrzebny na parsowanie, kompilację oraz czas samej kompilacja arkusza jest niewspółmiernie duży w stosunku do pożądanego efektu. Z drugiej strony użycie interfejsu DOM jest zazwyczaj dość uciążliwe. Linq to Xml znakomicie wypełnia tę lukę. Jak? Zobaczmy na przykładach.
(W przykładach będziemy używać tego samego dokumentu co w poprzednich odcinkach cyklu, który wygląda tak:

<?xml version="1.0"?>
<wycieczka>
  <segment>
    <wylot kod-lotniska="SEA" data="2008-10-09T15:25Z">Seattle-Tacoma International</wylot>
    <przylot kod-lotniska="LHR" data="2008-10-10T02:20Z">London Heathrow</przylot>
  </segment>
  <segment>
    <wylot kod-lotniska="LHR" data="2008-10-10T05:35Z">London Heathrow</wylot>
    <przylot kod-lotniska="WRO" data="2008-10-10T10:05Z">Wroclaw Strachowice</przylot>
  </segment>
</wycieczka>

Na dobry początek spróbujmy coś prostego - usuńmy z dokumentu węzły tekstowe zawierające nazwy lotnisk. W Linq to Xml można to zrobić za pomocą jednej linijki kodu, która wygląda tak:

xDoc.Elements("wycieczka").Elements("segment").Elements().Nodes().Remove()

Krótkie i nawet działa!

Zróbmy coś co wymaga więcej niż jednej linijki kodu - spróbujmy rozdzielić atrybut data na dwa atrybuty - data i godzina zawierające odopowiednio datę (dzień) i godzinę. Można to osiągnąć w następujący sposób:

foreach(var element in
xDoc.Elements("wycieczka").Elements("segment").Elements()) {
element.Add(new XAttribute("godzina",
element.Attribute("data").Value.Substring(11, 5)));
element.Attribute("data").Value =
element.Attribute("data").Value.Substring(0, 10); }

Po tej rozgrzewce spróbujmy zrobić coś bardziej skomplikowanego. Mianowicie zbudujmy xhtmlową tabelkę zawierającą szczegóły dotyczące wycieczki. Z Linq to Xml można to zrobić:

var element = new XElement("table",
new XElement("tr",
new XElement("th",
new XText("Wylot")),
new XElement("th",
new XText("Przylot"))),
from segment in xDoc.Elements("wycieczka").Elements("segment")
select new
XElement("tr", from przelot in segment.Elements()
select new XElement("td",
new XText(string.Format("{0} ({1}) {2}",
(string)przelot,
(string)przelot.Attribute("kod-lotniska"),
(string)przelot.Attribute("data"))))));

Wygląda to troche bardziej skomplikowanie niż poprzednie fragmenty. Co się tu dzieje? Zacznijmy od początku - najpierw tworzymy element o nazwie "table" oraz wiersz nagłówkowy tabeli. Ten kod jest bardzo podobny do kodu z pierwszego artykułu cyklu zawierającego opis "ręcznego" (tj. bezpośrednio w programie) tworzenia dokumentów Xmlowych za pomocą Linq to Xml. To co najbardziej interesujące dzieje się w drugiej części fragmentu kodu gdzie wykorzystujemy następujący konstruktor klasy XElement:

XElement(XName name, params object[] content)

który pozwala na przekazanie tablicy węzłów, które będą dziećmi tworzonego elementu. Konstruktor ten w połączeniu z zapytaniem Linqowym umożliwia stworzenie nowych elementów na podstawie przetwarzanego dokumentu Xmlowego. W naszym przykładzie - dla każdego elementu "segment" w źródłowym dokumencie tworzymy nowy element "tr", w którym z kolei tworzymy (przy wykorzystaniu tej samej techniki) tyle elelementów "td" ile węzeł "segment" ma elementów-dzieci. Stworzony węzeł "td" ma jako dziecko węzeł tekstowy zawierający odpowiednie informacje pobrane z dokumentu źródłowego.

Drugim ważnym zastosowaniem Linq to Xml jest serializacja dokumentów Xmlowych. Serializacja i deserializacja to procesy umożliwiające konwersje dokumentów Xmlowych odpowiednio z i do postaci obiektowej. Tak jak w przypadku transformacji Linq to Xml nie stara się konkurować z istniejącym na platformie .NET technologiami do serializacji (XmlSerializer, DataContractSerializer) ale jest bardzo poręczny w przypadkach gdy trzeba dokonać serializacji małych obiektów bezpośrednio w kodzie.

Zacznijmy od deserializacji - elementy "wylot" i "przylot" można przedstawić w sposób obiektowy za pomocą następującej klasy (warto zauważyć w jaki sposób zdefiniowane są pola - wykorzystują one wprowadzone w C# 3.0 tzw. "automatic properties". Dzięki nim nie trzeba definiować prywatnych zmiennych w klasie do przechowywania wartości pola - kompilator zrobi to za nas):

public class Przelot {
public string KodLotniska { get; set; }
public DateTime Data { get; set; }
public string NazwaLotniska { get; set; }
}

Kod realizujący taką serializację jest dzięki Linq to Xml stosunkowo prosty. Z dokumentu Xmlowego wybieramy elementy "wylot" i "przylot" i dla każdego takiego elementu tworzymy obiekt klasy Przelot. Oto przykład:

IEnumerable<Przelot> przeloty =
from przelot in xDoc.Elements("wycieczka").Elements("segment").Elements()
select new Przelot() {
Data = (DateTime)przelot.Attribute("data"),
KodLotniska = (string)przelot.Attribute("kod-lotniska"),
NazwaLotniska = (string)przelot
};

W powyższym fragmencie jest zastosowany nowy sposób inicjalizacji obiektu po jego stworzeniu - mimo, że klasa Przelot nie posiada żadnego poza domyślnym konstruktora możemy w prosty sposób zainicjalizować pola stworzonego obiektu dzięki nowej składni wprowadzonej w C# 3.0. Ten nowy, poręczny sposób inicjalizacji obiektów nazywa się "object initializers".

Inną nowością w C# 3.0 są klasy anonimowe. Pozwalają one na stworzenie obiektów "ad hoc" bez konieczności definiowania klas. Za pomocą składni podobnej do "object initizalizers" tworzymy obiekt nie nazwany, w którym tworzymy właściwości dynamicznie przez przypisanie wartości danej właściwości. Aby odwołać się później do takiego obiektu deklarujemy zmienną za pomocą słowa kluczowego "var" (pozwalając w ten sposób kompilatorowi określić rzeczywisty typ zmiennej ponieważ sami go tak naprawdę nie znamy) i odwołujemy się do obiektu za pomocą nazw właściwości użytych podczas tworzenia obiektu. Poniższy fragment przedstawia zastosowanie klas anonimowych do deserializacji dokumentów Xmlowych za pomocą Linq to Xml:

var przeloty =
from przelot in xDoc.Elements("wycieczka").Elements("segment").Elements()
select new {
Date = (DateTime)przelot.Attribute("data"),
AirportCode = (string)przelot.Attribute("kod-lotniska"),
AirportName = (string)przelot
};
foreach(var przelot in przeloty) {
Console.WriteLine(string.Format("{0} ({1}) {2}",
przelot.AirportName, przelot.AirportCode, przelot.Date));
}

Jak widać z powyższych przykładów, że deserializacja dokumentów Xmlowych do obiektów za pomocą Linq to Xml nie jest skomplikawana. A jak jest z serializacją? Najlepiej chyba pokazać to na przykładzie. Załóżmy, że mamy tablicę zawierającą obiekty typu Przelot (definicja klasy Przelot taka jak w jednym z wcześniejszych przykładów). Zdefiniujmy ją w następujący sposób:

var przeloty = new[] {
new {
Wylot = new Przelot() {
KodLotniska = "SEA",
Data = DateTime.Parse("2008-10-09T15:25Z"),
NazwaLotniska = "Seattle-Tacoma International"
},
Przylot = new Przelot() {
KodLotniska = "LHR",
Data = DateTime.Parse("2008-10-10T02:20Z"),
NazwaLotniska = "London Heathrow"
},
},
new {
Wylot = new Przelot() {
KodLotniska = "LHR",
Data = DateTime.Parse("2008-10-10T05:35Z"),
NazwaLotniska = "London Heathrow"
},
Przylot = new Przelot() {
KodLotniska = "WRO",
Data = DateTime.Parse("2008-10-10T10:05Z"),
NazwaLotniska = "Wroclaw Strachowice"
}
}
};

Tablica zdefiniowana jest za pomocą "wodotrysków" pochodzących z C# 3.0. Mianowicie jest to tablica obiektów anonimowych, z których każdy zawiera informacje o wylocie i przylocie będącymi instancjami klasy Przelot. Instancje klasy Przelot inicjalizowane są za pomocą funkcjonalności "object initializers". Zadanie polega na zamianie powyższej tablicy na dokument Xmlowy taki jak ten używany w przykładach w naszym cyklu artykułów. Voila - oto kod:

XDocument xDoc = new XDocument(
new XDeclaration("1.0", string.Empty, string.Empty),
new XElement("wycieczka",
from przelot in przeloty
select new XElement("segment",
new XElement("wylot",
new XAttribute("kod-lotniska", przelot.Wylot.KodLotniska),
new XAttribute("data", przelot.Wylot.Data),
new XText(przelot.Wylot.NazwaLotniska)),
new XElement("przylot",
new XAttribute("kod-lotniska", przelot.Przylot.KodLotniska),
new XAttribute("data", przelot.Przylot.Data),
new XText(przelot.Przylot.NazwaLotniska)))));

Kod może wydawać się odrobinę znajomy... Szczególnie jak się spojrzy na kod z pierwszego odcinka cyklu oraz na przykładowy kod pokazujący w jaki sposób dokonać transformacji dokumentu Xmlowego za pomocą Linq to Xml. Podobieństwo do kodu z pierwszego odcinku cyklu nie powinno dziwić - w końcu budujemy praktycznie ten sam dokument z tą tylko różnicą, że zamiast wstawiać wszystkie wartości ręcznie chcemy je pobrać z tablicy. Musimy zatem iterować po tej tablicy do czego używamy technologii Linq co z kolei przypomina kod z tego artykułu. Uważni jednak zauważą jedną dość istotną różnicę. Mianowicie przykładowa transformacja przedstawiona na początku artykułu iterowała po elementach z przestrzeni nazw System.Xml.Linq. Natomiast w tym przypadku iterujemy po "normalnej" tablicy obiektów. Jak to jest możliwe? Otóż w tamym przykładzie używaliśmy Linq to Xml podczas gdy w tym przykładzie została użyta technologia Linq to... Objects. Tak oto Linq spotkał Linqa...

Artykuł zamieszczono dzięki uprzejmości xml.com.pl

Poprawiony: wtorek, 20 października 2009 20:35  

Dodaj swój komentarz

Imię:
Adres e-mail:
Strona WWW:
Treść (możesz używać HTML):
JoomlaWatch Stats 1.2.9 by Matej Koval

Użytkownicy

Naszą witrynę przegląda teraz 4 gości