70-562: Working with XML data

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ć:

1

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: &lt;!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 ;)

Tagi: , , , , ,

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


Eastgroup.pl na facebooku