
Kiedy piszemy aplikacje, prędzej czy później nadejdzie czas kiedy będziemy musieli korzystać z przetwarzania dokumentów XML. Nie ważne czy będziemy je tworzyć czy przetwarzać już istniejące. Dokument XML jest niczym innym jak zwykłym plikiem tekstowym lub strumieniem w pamięci. A więc bez problemu możemy napisać klasę lub klasy parsujące tekst XML i go odpowiednio przerabiać. Jednak samodzielne pisanie takiej klasy mija się z celem, zwłaszcza kiedy mamy do przetworzenia na przykład jeden plik. Oczywiście na platformie .NET istnieją sposoby przetwarzania takich dokumentów. Jednym z nich jest korzystanie z przestrzeni nazw System.XML. Jednak przetwarzanie całych lub skomplikowanych dokumentów nie jest takie proste jak byśmy tego chcieli. Dlatego z pomocą przychodzi nam LINQ, stosunkowo młody produkt firmy Microsoft na platformę .NET. W przetwarzaniu XML za pomocą LINQ najbardziej będziemy się interesowali przestrzenią nazw System.Xml.Linq. Jednak zacznijmy od początku…
1. Standard XML
XML (Extensible Markup Language) jest standardem przetwarzania danych zalecanym przez konsorcjum W3C. XML jest spokrewniony z SGML (Standard Generalization Markup Language), ale w stosunku do niego jest o wiele bardziej uproszczony i pozbawionych wielu trudnych, rzadko używanych elementów.
XML tak samo jak język HTML jest językiem znaczników. Jednak w przeciwieństwie do HTML, XML pozwala na tworzenie własnych znaczników i ich atrybutów, gdzie w HTML ich struktura i nazwy są ściśle określone w standardzie HTML (np. <body>, <head>). W XML natomiast możemy tworzyć własne znacznik. Każdy taki element może być definiowany przez jego własny DTP (Document Type Definition) oraz arkusze stylów, które mogą być stosowane do jednego lub wielu dokumentów XML.
Mimo, że w XML można tworzyć własne znaczniki ich odpowiednie oznakowanie i położenie musi być ściśle zgodne ze standardami konsorcjum W3C. Podstawowym elementem w dokumencie XML-owym jest znacznik, który może posiadać dowolne atrybuty. Bardzo ważnym aspektem jest, że każdy znacznik musi być zamknięty. Jest to jeden z warunków koniecznych, aby dokument był zgodny ze standardami. Poniżej przedstawiam trzy przykłady znaczników w dokumencie:
- znacznik otwierający i zamykający z zawartością
Henryk Sienkiewicz - znacznik otwierający i zamykający z atrybutem i zawartością
2,75 - znacznik otwierający i zamykający z atrybutem bez zawartości
lub:
Jak wspomniałem wcześniej, dokument XML musi być ściśle zgodny ze standardami. Poniżej przedstawiam listę zagadnień, których znajomość jest nieodzowna przy tworzeniu własnego dokumentu:
- XML jest wrażliwy na wielkość znaków, dlatego znaczniki otwierające muszą być zapisane dokładnie w taki sam sposób jak zamykające,
- wartości atrybutów muszą być zapisane w cudzysłowach,
- niepusty element musi mieć znacznik otwierający i zamykający
- znaczniki muszą być zagnieżdżane w odpowiedniej kolejności. Zamykanie znaczników musi odbywać się w kierunku od ostatniego do pierwszego w stosunku do znaczników otwierających.
Do poprawnego zbudowania dokumentu jednak potrzebna jest odpowiednia budowa samego pliku lub plików. Do budowy poprawnego dokumentu XML zazwyczaj potrzebne są dwa pliki, lecz nie jest to wymagane. Pierwszym z nich jest to dokument XML, zawierający konkretne dane. Drugim plikiem jest plik DTD (Document Type Definition), który zawiera reguły, jak elementy XML, atrybuty i inne dane, które są definiowane i logicznie połączone w dokumencie.
Poniżej przedstawiam przykładowy, poprawny dokument XML (bez DTD):
John Doe 624253468 2546885 254842546 Jane Doe 54575485
Jak widać z przykładu powyżej dokument XML to hierarchiczny zbiór znaczników, gdzie na najwyższym poziomie może wystąpić tylko jeden znacznik, który jest elementem głównym dokumentu (ang. root).
Jak wspomniałem wcześniej dokument XML musi być ściśle związany ze standardami. Aby sprawdzić czy dany dokument XML jest prawidłowy pod względem składniowym i logicznym wykorzystuje się wspomniane wcześniej pliki DTD. Mechanizm ten jest odziedziczony po SGML-u i jest stosunkowo stary. Obecnie powszechnie wykorzystuje się również schematy XML (ang. XML Schema), który jest mechanizmem nowszym od DTD. Istnieje również trzeci mechanizm, który jest specyficzny dla firmy Microsoft o nazwie XDR (ang. XML Data Reduced), jednak wszędzie gdzie jest to możliwe powinno stosować się XSD (ang. XML Schema Definition) zalecany przez W3C. Wszystkie trzy mechanizmy pozwalają definiować ograniczenia co do zawartości dokumentu XML po przez określenie znaczników, które mogą wystąpić w dokumencie, znaczników opcjonalnych itd.
Dokument XML można również przedstawić jako drzewo DOM (ang. Document Object Model – Obiektowy model dokumentu), który reprezentuje wszystkie węzły i atrybuty w postaci drzewa. Poniżej przykład takiego drzewa:

Na koniec wprowadzenia do XML wymienię kilka obecnie szeroko stosowanych zastosowań standardu XML:
- zapis dokumentów przy pomocy zbioru znaczników zamiast stosowania skomercjalizowanych technologii (Word, PDF),
- wymiana danych, zarówno do wymiany danych między aplikacjami jak i całymi systemami,
- przechowywanie danych,
- realizacja operacji bazodanowych (np. w Microsoft Access czy SQL Server, wynikiem zapytania SQL może być dokument XML).
2. Klasyczne przetwarzanie XML (przestrzeń nazw System.Xml)
Większość funkcji związanych z przetwarzaniem dokumentów XML znajduje się w przestrzeni nazw System.Xml, która to realizuje przetwarzanie dokumentów XML w obowiązujących standardach (wersja 1.0) m.in.: przestrzenie nazw XML, schematy XML do sprawdzania poprawności dokumentów XML, DOM poziom 2 i inne.
Podstawową klasą do odczytu dokumentów XML jest XmlTextReader, która to zapewnia szybki, nie buforowany dostęp do strumienia danych XML, w którym poruszać się można tylko do przodu. Oznacza to, że cały dokument nie musi być ładowany do pamięci, a jedynie jest odczytywany jeden węzeł. Przy odczytywaniu kolejnych węzłów właściwości obiektu XmlTextReader odpowiadają aktualnie przetwarzanemu węzłowi.
Poniżej przedstawiam przykład użycia obiektu klasy XmlTextReader:
XmlTextReader rdr = new XmlTextReader("C:\Employees.Xml");
while (rdr.Read())
{
XmlNodeType nt = rdr.NodeType;
switch (nt)
{
case XmlNodeType.Element:
break;
case XmlNodeType.Attribute:
break;
case XmlNodeType.Comment:
break;
case XmlNodeType.Whitespace:
break;
}
}
Przestrzeń System.Xml pozwala nam również na zapis dokumentów XML. Służy do tego klasa XmlTextWriter, która zapewnia szybki, nie buforowany zapis danych XML w strumieniu lub pliku, w którym poruszać się można tylko do przodu. Korzystanie z tej klasy polega na wywołaniu metod, które wyprowadzają dane XML. W ten sposób decydujemy dokładnie w którym momencie co ma być wyprowadzone do strumienia lub pliku.
Poniżej przedstawiam przykład użycia klasy XmlTextWriter:
XmlTextWriter wrt = new XmlTextWriter(@"C:\Employees.Xml", Encoding.UTF8);
wrt.WriteStartDocument();
wrt.WriteComment("This is an example");
wrt.WriteStartElement("Employees");
wrt.WriteStartElement("Employee");
wrt.WriteStartElement("FirstName");
wrt.WriteString("Scott");
wrt.WriteEndElement();
wrt.WriteEndElement();
wrt.WriteEndElement();
Jak widzimy w powyższym przykładzie, zapis danych do pliku XML, zwłaszcza przy większej ilości danych może sprawiać problemy, zwłaszcza kiedy mamy do czynienia z wielopoziomowymi dokumentami, gdzie poziom zagłębienia jest duży.
Oczywiście przestrzeń nazw System.Xml zawiera o wiele więcej klas i metod do obsługi dokumentów XML, sprawdzania poprawności dokumentów oraz wiele więcej.
3. Projekt LINQ
LINQ (Language Integrated Query) jest to część platformy .NET i jest to zestaw standardowych operatorów zapytań które dostarczają bazową architekturę zapytań do nawigowania, filtrowania i wykonywania zapytań praktycznie z każdym źródłem danych. Takim źródłem danych może być relacyjna baza danych (LINQ to SQL), obiekt DataSet (LINQ to DataSet), dokument XML (LINQ to XML) lub kolekcja danych przechowywana w pamięci.
Jak wspomniałem wcześniej LINQ to zestaw operatorów zapytania, które wprowadzają udogodnienia wprost do języka programowania (Visual Basic, C#), dlatego dzięki LINQ nie trzeba już zapisywać zapytań SQL jako ciąg znaków nic nie znaczących dla kompilatora. Dzięki temu mamy kontrolę nad poprawnością zapisu zapytania już na etapie kodowania aplikacji.
Bardzo ważnym aspektem wyników zwracanych przez zapytanie LINQ jest to, że odpytywane źródło danych musi implementować generyczną wersję interfejsu IEnumerable
Poniżej przedstawiam przykład zapytania LINQ. Pierwsze jest to tzw. Query Syntax. Jest to forma bardziej czytelna, która ma wiele wspólnego z zapytaniami SQL:
var query =
from c in contact
where c.FirstName.StartsWith("S")
&& c.LastName.StartsWith("K")
orderby c.LastName
select new { c.FirstName, c.LastName, c.EmailAddress };
Drugą formą tego zapytania jest tzw. Method Syntax. Każde zapytanie zapisane w formie Query Syntax, przy uruchamianiu w środowisku .NET jest zawsze tłumaczone na Method Syntax. Do zapisu zapytań w tej postaci korzysta się z wyrażeń Lambda:
IEnumerablequery = contact.Where(a => a.FirstName.StartsWith("S") && a.LastName.StartsWith("K")).OrderBy(a => a.LastName);

Zacny fragment magisterki, może nie upowszechniaj jej jeszcze bo Cię za plagiat ścigną:P