Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.
Jak wiadomo .NET ma szeroko pojęte wsparcie dla XML. Implementacja XML to wydajność, niezawodności i skalowalność a w połączeniu z ADO.NET możliwość korzystania z XML jako źródła danych.
Klasy XML
Klasy XML są dostępne w System.Xml.dll czyli potrzebujemy dyrektywy using System.Xml. System.Data.dll rozszerza wspomnianą przestrzeń o chociażby klasę XmlDataDocument. Chciałbym teraz po krótce przyjrzeć się podstawowym klasom XML w .NET Frameworku. Każda z tych klas oferuje różne stopnie funkcjonalności, dlatego ważne jest aby odpowiednio przeanalizować swój problem i wybrać odpowiednią z nich. Poniższy rysunek pokazuje ogólnie obiekty o których będziemy mówić:

XmlDocument i XmlDataDocument
Klasy te możemy wykorzystywać np. do nawigacji i edycji węzłów XML. XmlDataDocument dziedziczy po XmlDocument i reprezentuje relacyjne dane. XmlDataDocument może przedstawiać swoje dane jako DataSet zarówno jako relacyjny i nierelacyjny widok danych. Klasy te dostarczają wiele metod które są opisane i ładnie zebrane w TK w ładnej tabelce. Większość z nich jest intuicyjna i po samej nazwie możemy wnioskować do czego służą.
XPathDocument
Klasa służy tylko do odczytu z pamięci podręcznej XMLDocument, wykorzystywana do bardzo szybkich zapytań XPath.
XmlConvert
Klasa ta zawiera wiele statycznych metod konwersji pomiędzy typami XSD a zawartymi w CLR. Klasa te jest szczególnie ważna podczas pracy ze źródłami danych, które umożliwiają nazwy które nie są dozwolone w XML. Jeśli mamy kolumnę w bazie danych która nazywa się List Price to próba stworzenia np. atrybutu o takich nazwie wyrzuci nam wyjątek. XMLConnvert zakoduje nam spacje na _0x0020_ dzięki czemu otrzymamy nazwę List_x0020_Price którą jest już prawidłową nazwą i możemy ją odkodować używając metode XmlConvert.DecodeName. Również klasa ta posiada wiele metod statycznych które potrafią konwertować ciągi znaków na typy numeryczne.
XPathNavigator
Klasa ta zapewnia skuteczną nawigację w dokumencie xml używając do tego XPath. Klasa ta wspiera Extensible Stylesheet Language Transformations (XSLT).
XmlNodeReader
Klasa ta umożliwia dostęp do danych a dokładnie mówiąc wejście w dowolny węzeł pliku XML.
XmlTextReader
Klasa udostępnia podstawowy dostęp do danych. Nie zapisuje wyników swojej pracy w pamięci podręcznej. XmlTextReader nie wykonuje walidacji dokumentu, ale sprawdza dane XML pod kątem poprawności “uformowania” ich.
XmlTextWriter
Pozwala zapisywać dane do w postaci XML do pliku zapewniając przy tym, że będą zgodne ze standardem W3C XML 1.0. Klasa ta zwiera wsparcie dla przestrzeni nazw i rozwiązywania problemów z nimi związanymi.
XmlReader
Klasa służy również do odczytu i waliduje dane zgodnie z DTD, XDR lub XDS. Sam konstruktor oczekuje źródła pliku “sprawdzonych” danych.
XslTransform
Klasa ta umożliwia przekształcenie dokumentu XML z wykorzystaniem arkusza stylów XSL. Obsługuje ona składnie w wersji 1.0 i oferuje dwie metody Load i Transform.
Praca z dokumentem XML
Z pewnością istnieje wiele metod pracy z dokumentem XML w .NET Frameworku. Dzisiaj pokażemy sobie w jaki sposób można wykonać podstawowy zapis, odczyt, wyszukiwanie danych. Teraz multum przykładów, kodu zawartego w TK który pokazuję realizacje tych i wielu innych zadań.
Tworzenie nowego dokumentu
Aby stworzyć dokument XML musimy zacząć od utworzenia obiektu XMLDocument. Zawiera on m.in metodę typu CreateElement i CreateAttribute które pozwalają nam tworzyć poszczególne węzły. Spójrzmy na poniższy kod, który wraz z komentarzami mówi sam za siebie ;)
1: //C#
2: protected void Button1_Click(object sender, EventArgs e)
3: {
4: //Declare and create new XmlDocument
5: XmlDocument xmlDoc = new XmlDocument();
6: XmlElement el;
7: int childCounter;
8: int grandChildCounter;
9: //Create the xml declaration first
10: xmlDoc.AppendChild(
11: xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null));
12: //Create the root node and append into doc
13: el = xmlDoc.CreateElement("myRoot");
14: xmlDoc.AppendChild(el);
15: //Child Loop
16: for (childCounter = 1; childCounter <= 4; childCounter++)
17: {
18: XmlElement childelmt;
19: XmlAttribute childattr;
20: //Create child with ID attribute
21: childelmt = xmlDoc.CreateElement("myChild");
22: childattr = xmlDoc.CreateAttribute("ID");
23: childattr.Value = childCounter.ToString();
24: childelmt.Attributes.Append(childattr);
25: //Append element into the root element
26: el.AppendChild(childelmt);
27: for (grandChildCounter = 1; grandChildCounter <= 3; grandChildCounter++)
28: {
29: //Create grandchildren
30: childelmt.AppendChild(xmlDoc.CreateElement("GrandChild"));
31: }
32: }
33: //Save to file
34: xmlDoc.Save(MapPath("XmlDocumentTest.xml"));
35: Label lbl = GetLabel(275, 20);
36: lbl.Text = "XmlDocumentTest.xml Created";
37: }
Kod ten wygeneruje następujący dokument XML:
1: <?xml version="1.0" encoding="utf-8"?>
2: <myRoot>
3: <myChild ID="1">
4: <GrandChild />
5: <GrandChild />
6: <GrandChild />
7: </myChild>
8: <myChild ID="2">
9: <GrandChild />
10: <GrandChild />
11: <GrandChild />
12: </myChild>
13: <myChild ID="3">
14: <GrandChild />
15: <GrandChild />
16: <GrandChild />
17: </myChild>
18: <myChild ID="4">
19: <GrandChild />
20: <GrandChild />
21: <GrandChild />
22: </myChild>
23: </myRoot>
Parsowanie dokumentu przy użyciu DOM i XPathNavigator
Oba przykłady pokazują jak rekurencyjnie analizować dokument XML. Jednak trzeba pamiętać, że XPathNavigator udostępnia nam szereg metod z których możemy dodatkowo korzystać.
1: //C#
2: Label lbl = new Label();
3: protected void Button2_Click(object sender, EventArgs e)
4: {
5: lbl = GetLabel(275, 20);
6: XmlDocument xmlDoc = new XmlDocument();
7: xmlDoc.Load(MapPath("XmlDocumentTest.xml"));
8: RecurseNodes(xmlDoc.DocumentElement);
9: }
10: public void RecurseNodes(XmlNode node)
11: {
12: //start recursive loop with level 0
13: RecurseNodes(node, 0);
14: }
15: public void RecurseNodes(XmlNode node, int level)
16: {
17: string s;
18: s = string.Format("{0} <b>Type:</b>{1} <b>Name:</b>{2} <b>Attr:</b> ",
19: new string('-', level), node.NodeType, node.Name);
20: foreach (XmlAttribute attr in node.Attributes)
21: {
22: s += string.Format("{0}={1} ", attr.Name, attr.Value);
23: }
24: lbl.Text += s + "<br>";
25: foreach (XmlNode n in node.ChildNodes)
26: {
27: RecurseNodes(n, level + 1);
28: }
29: }
1: //C#
2: protected void Button3_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: XmlDocument xmlDoc = new XmlDocument();
6: xmlDoc.Load(MapPath("XmlDocumentTest.xml"));
7: XPathNavigator xpathNav = xmlDoc.CreateNavigator();
8: xpathNav.MoveToRoot();
9: RecurseNavNodes(xpathNav);
10: }
11: public void RecurseNavNodes(XPathNavigator node)
12: {
13: //start recursive loop with level 0
14: RecurseNavNodes(node, 0);
15: }
16: public void RecurseNavNodes(XPathNavigator node, int level)
17: {
18: string s = null;
19: s = string.Format("{0} <b>Type:</b>{1} <b>Name:</b>{2} <b>Attr:</b> ",
20: new string('-', level), node.NodeType, node.Name);
21: if (node.HasAttributes)
22: {
23: node.MoveToFirstAttribute();
24: do
25: {
26: s += string.Format("{0}={1} ", node.Name, node.Value);
27: } while (node.MoveToNextAttribute());
28: node.MoveToParent();
29: }
30: lbl.Text += s + "<br>";
31: if (node.HasChildren)
32: {
33: node.MoveToFirstChild();
34: do
35: {
36: RecurseNavNodes(node, level + 1);
37: } while (node.MoveToNext());
38: node.MoveToParent();
39: }
40: }
Wyszukiwanie danych przy użyciu DOM
DOM wspiera metody GetElementByID i the GetElementsByTagName do wyszukiwania danych w dokumencie XML. GetElementByID lokalizuje element na podstawie jego unikatowego ID. Spójrzmy na plik DTD zdefiniowany następująco:
1: <?xml version="1.0" encoding="utf-8"?>
2: <!DOCTYPE myRoot [
3: <!ELEMENT myRoot ANY>
4: <!ELEMENT myChild ANY>
5: <!ELEMENT myGrandChild EMPTY>
6: <!ATTLIST myChild
7: ChildID ID #REQUIRED
8: >
9: ]>
10: <myRoot>
11: <myChild ChildID="ref-1">
12: <myGrandChild/>
13: <myGrandChild/>
14: <myGrandChild/>
15: </myChild>
16: <myChild ChildID="ref-2">
17: <myGrandChild/>
18: <myGrandChild/>
19: <myGrandChild/>
20: </myChild>
21: <myChild ChildID="ref-3">
22: <myGrandChild/>
23: <myGrandChild/>
24: <myGrandChild/>
25: </myChild>
26: <myChild ChildID="ref-4">
27: <myGrandChild/>
28: <myGrandChild/>
29: <myGrandChild/>
30: </myChild>
31: </myRoot>
Każde “dziecko” ma swoje Id i powiedzmy że chcemy znaleźć element o ID=ref-3. Realizuje to poniższy kod:
1: //C#
2: protected void Button4_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: string s;
6: //Declare and create new XmlDocument
7: XmlDocument xmlDoc = new XmlDocument();
8: xmlDoc.Load(MapPath("XmlSample.xml"));
9: XmlNode node;
10: node = xmlDoc.GetElementById("ref-3");
11: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1} <b>Attr:</b>",
12: node.NodeType, node.Name);
13: foreach (XmlAttribute a in node.Attributes)
14: {
15: s += string.Format("{0}={1} ", a.Name, a.Value);
16: }
17: lbl.Text = s + "<br>";
18: }
Do powyższego zadania również użyteczna może być metoda SelectSingleNode. Świetnie się nadaje do tego celu i spójrzmy na realizacje tego samego problemy z wykorzystaniem wspomnianej metody:
1: //C#
2: protected void Button5_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: string s;
6: //Declare and create new XmlDocument
7: XmlDocument xmlDoc = new XmlDocument();
8: xmlDoc.Load(MapPath("XmlSample.xml"));
9: XmlNode node;
10: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-3']");
11: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1} <b>Attr:</b>",
12: node.NodeType, node.Name);
13: foreach (XmlAttribute a in node.Attributes)
14: {
15: s += string.Format("{0}={1} ", a.Name, a.Value);
16: }
17: lbl.Text = s + "<br>";
18: }
Aby zwrócić węzły o danych nazwach możemy wykorzystać metodę GetElementsByTagName. Powiedzmy, że chcemy zwrócić dane z węzłów o nazwie myGrandChild:
1: //C#
2: protected void Button6_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: string s;
6: //Declare and create new XmlDocument
7: XmlDocument xmlDoc = new XmlDocument();
8: xmlDoc.Load(MapPath("XmlSample.xml"));
9: XmlNodeList elmts;
10: elmts = xmlDoc.GetElementsByTagName("myGrandChild");
11: foreach (XmlNode node in elmts)
12: {
13: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1}",
14: node.NodeType, node.Name);
15: lbl.Text += s + "<br>";
16: }
17: }
Ale nie jest to jedyny sposób realizacji tego zdania. Równie dobrze możemy wykorzystać do tego celu SelectNodes w następujący sposób (w sumie różnica w jednej linijce):
1: //C#
2: protected void Button7_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: string s;
6: //Declare and create new XmlDocument
7: XmlDocument xmlDoc = new XmlDocument();
8: xmlDoc.Load(MapPath("XmlSample.xml"));
9: XmlNodeList elmts;
10: elmts = xmlDoc.SelectNodes("//myGrandChild");
11: foreach (XmlNode node in elmts)
12: {
13: s = string.Format("<b>Type:</b>{0} <b>Name:</b>{1}",
14: node.NodeType, node.Name);
15: lbl.Text += s + "<br>";
16: }
17: }
GetElementByTag ogranicza się do nazwy, natomiast SelectNodes pozwala nam na większą elastyczność w poszukiwaniu węzła.
Zapisywanie do pliku przy użyciu XmlTextWriter
Co tu się dużo rozpisywać. Poniższy przykład tworzy listę pracowników i zapisuje dwóch z nich na ta listę. Kod realizujący to zadanie efekt poniżej:
1: //C#
2: protected void Button10_Click(object sender, EventArgs e)
3: {
4: XmlTextWriter xmlWriter = new
5: XmlTextWriter(MapPath("EmployeeList.xml"),
6: System.Text.Encoding.UTF8);
7: xmlWriter.Formatting = Formatting.Indented;
8: xmlWriter.Indentation = 5;
9: xmlWriter.WriteStartDocument();
10: xmlWriter.WriteComment("XmlTextWriter Test Date: " +
11: DateTime.Now.ToShortDateString());
12: xmlWriter.WriteStartElement("EmployeeList");
13: //New Employee
14: xmlWriter.WriteStartElement("Employee");
15: xmlWriter.WriteAttributeString("EmpID", "1");
16: xmlWriter.WriteAttributeString("LastName", "JoeLast");
17: xmlWriter.WriteAttributeString("FirstName", "Joe");
18: xmlWriter.WriteAttributeString("Salary", XmlConvert.ToString(50000));
19: xmlWriter.WriteElementString("HireDate",
20: XmlConvert.ToString(DateTime.Parse("1/1/2003"),
21: XmlDateTimeSerializationMode.Unspecified));
22: xmlWriter.WriteStartElement("Address");
23: xmlWriter.WriteElementString("Street1", "123 MyStreet");
24: xmlWriter.WriteElementString("Street2", "");
25: xmlWriter.WriteElementString("City", "MyCity");
26: xmlWriter.WriteElementString("State", "OH");
27: xmlWriter.WriteElementString("ZipCode", "12345");
28: //Address
29: xmlWriter.WriteEndElement();
30: //Employee
31: xmlWriter.WriteEndElement();
32: //New Employee
33: xmlWriter.WriteStartElement("Employee");
34: xmlWriter.WriteAttributeString("EmpID", "2");
35: xmlWriter.WriteAttributeString("LastName", "MaryLast");
36: xmlWriter.WriteAttributeString("FirstName", "Mary");
37: xmlWriter.WriteAttributeString("Salary", XmlConvert.ToString(40000));
38: xmlWriter.WriteElementString("HireDate",
39: XmlConvert.ToString(DateTime.Parse("1/2/2003"),
40: XmlDateTimeSerializationMode.Unspecified));
41: xmlWriter.WriteStartElement("Address");
42: xmlWriter.WriteElementString("Street1", "234 MyStreet");
43: xmlWriter.WriteElementString("Street2", "");
44: xmlWriter.WriteElementString("City", "MyCity");
45: xmlWriter.WriteElementString("State", "OH");
46: xmlWriter.WriteElementString("ZipCode", "23456");
47: //Address
48: xmlWriter.WriteEndElement();
49: //Employee
50: xmlWriter.WriteEndElement();
51: //EmployeeList
52: xmlWriter.WriteEndElement();
53: xmlWriter.Close();
54: Response.Redirect("EmployeeList.xml");
55: }
1: <?xml version="1.0" encoding="utf-8"?>
2: <!--XmlTextWriter Test Date: 8/16/2006-->
3: <EmployeeList>
4: <Employee EmpID="1" LastName="JoeLast" FirstName="Joe" Salary="50000">
5: <HireDate>2003-01-01T00:00:00</HireDate>
6: <Address>
7: <Street1>123 MyStreet</Street1>
8: <Street2 />
9: <City>MyCity</City>
10: <State>OH</State>
11: <ZipCode>12345</ZipCode>
12: </Address>
13: </Employee>
14: <Employee EmpID="2" LastName="MaryLast" FirstName="Mary" Salary="40000">
15: <HireDate>2003-01-02T00:00:00</HireDate>
16: <Address>
17: <Street1>234 MyStreet</Street1>
18: <Street2 />
19: <City>MyCity</City>
20: <State>OH</State>
21: <ZipCode>23456</ZipCode>
22: </Address>
23: </Employee>
24: </EmployeeList>
Co jest fajne XMLTextWriter posiada właściwości opowiadające za formatowanie i wcięcia.
Odczytywanie z pliku przy pomocy XmlTextReader
Klasa XmlTextReader jest używana do odczytywania danych węzeł po węźle. Wadą używania tej klasy jest to, że odczyt jest typu “forward-only”. Poniższy kod pokazuje informacje o każdym węźle oczywiście na podstawie wcześniejszych danych:
1: //C#
2: protected void Button11_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: XmlTextReader xmlReader = new
6: XmlTextReader(MapPath("EmployeeList.xml"));
7: while (xmlReader.Read())
8: {
9: switch( xmlReader.NodeType)
10: {
11: case XmlNodeType.XmlDeclaration:
12: case XmlNodeType.Element:
13: case XmlNodeType.Comment:
14: {
15: string s;
16: s = String.Format("{0}: {1} = {2}<br>",
17: xmlReader.NodeType,
18: xmlReader.Name,
19: xmlReader.Value);
20: lbl.Text += s;
21: break;
22: }
23: case XmlNodeType.Text:
24: {
25: string s;
26: s = String.Format(" - Value: {0}<br>",
27: xmlReader.Value);
28: lbl.Text += s;
29: break;
30: }
31: }
32: if (xmlReader.HasAttributes)
33: {
34: while (xmlReader.MoveToNextAttribute())
35: {
36: string s;
37: s = String.Format(" - Attribute: {0} = {1}<br>",
38: xmlReader.Name, xmlReader.Value);
39: lbl.Text += s;
40: }
41: }
42: }
43: xmlReader.Close();
44: }
Modyfikowanie dokumentu
Usunięcie węzła realizuje dwa zadania…znaleźć węzeł i go usunąć. Dodanie węzła to utworzenie go, znalezienie odpowiedniego miejsca i wstawienie go tam. Jak to zrealizować pokazuje poniższy przykład:
1: //C#
2: protected void Button12_Click(object sender, EventArgs e)
3: {
4: lbl = GetLabel(275, 20);
5: //Declare and load new XmlDocument
6: XmlDocument xmlDoc = new XmlDocument();
7: xmlDoc.Load(MapPath("XmlSample.xml"));
8: //delete a node
9: XmlNode node;
10: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-3']");
11: node.ParentNode.RemoveChild(node);
12: //create a node and add it
13: XmlElement newElement =
14: xmlDoc.CreateElement("myNewElement");
15: node = xmlDoc.SelectSingleNode("//myChild[@ChildID='ref-1']");
16: node.ParentNode.InsertAfter(newElement, node);
17: xmlDoc.Save(MapPath("XmlSampleModified.xml"));
18: Response.Redirect("XmlSampleModified.xml");
19: }
Tyle na dzisiaj ;) Zasypałem Was kodem ale widocznie w TK uznali, że na przykładzie najlepiej to wszystko będzie widoczne. W TK również jest wspomniane o LINQ to XML. Możecie sobie doczytać o tych podstawach a i myślę, że warto rozszerzyć swoją wiedze o tym mechanizmie który wydaję mi się jest o wiele bardziej przejrzysty i “czystszy” w kodzie ;)