Visual C# 2010 Recipes solution_2

Chia sẻ: Up Upload | Ngày: | Loại File: PDF | Số trang:95

lượt xem

Visual C# 2010 Recipes solution_2

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Khi bạn đang phải đối mặt với một Visual C # 2010 vấn đề, cuốn sách này có thể chứa một công thức cung cấp giải pháp hoặc ít nhất là điểm bạn đi đúng hướng. Ngay cả khi bạn chỉ đơn giản là tìm cách để mở rộng kiến thức của bạn của thư viện NET. Framework, Visual C # 2010 Recipes là tài nguyên hoàn hảo để giúp bạn .

Chủ đề:

Nội dung Text: Visual C# 2010 Recipes solution_2

  1. CHAPTER 6 ■■■ XML Processing One of the most remarkable aspects of the Microsoft .NET Framework is its deep integration with XML. In many .NET applications, you won’t even be aware you’re using XML technologies—they’ll just be used behind the scenes when you serialize a Microsoft ADO.NET DataSet, call a web service, or read application settings from a Web.config configuration file. In other cases, you’ll want to work directly with the System.Xml namespaces to manipulate Extensible Markup Language (XML) data. Common XML tasks don’t just include parsing an XML file, but also include validating it against a schema, applying an Extensible Stylesheet Language (XSL) transform to create a new document or Hypertext Markup Language (HTML) page, and searching intelligently with XPath. In .NET 3.5, Microsoft added LINQ to XML, which integrates XML handling into the LINQ model for querying data sources. You can use the same keywords and syntax to query XML as you would a collection or a database. The recipes in this chapter describe how to do the following: • Read, parse, and manipulate XML data (recipes 6-1, 6-2, 6-3, and 6-7) • Search an XML document for specific nodes, either by name (recipe 6-4), by namespace (recipe 6-5), or by using XPath (recipe 6-6) • Validate an XML document with an XML schema (recipe 6-8) • Serialize an object to XML (recipe 6-9), create an XML schema for a class (recipe 6- 10), and generate the source code for a class based on an XML schema (recipe 6- 11) • Transform an XML document to another document using an XSL Transformations (XSLT) stylesheet (recipe 6-12) • Use LINQ to XML to load, create, query and modify XML trees (recipes 6-13, 6-14, 6-15, and 6-16). 6-1. Show the Structure of an XML Document in a TreeView Problem You need to display the structure and content of an XML document in a Windows-based application. 261
  2. CHAPTER 6 ■ XML PROCESSING Solution Load the XML document using the System.Xml.XmlDocument class. Create a reentrant method that converts a single XmlNode into a System.Windows.Forms.TreeNode, and call it recursively to walk through the entire document. How It Works The .NET Framework provides several different ways to process XML documents. The one you use depends in part upon your programming task. One of the most fully featured classes is XmlDocument, which provides an in-memory representation of an XML document that conforms to the W3C Document Object Model (DOM). The XmlDocument class allows you to browse through the nodes in any direction, insert and remove nodes, and change the structure on the fly. For details of the DOM specification, go to ■ Note The XmlDocument class is not scalable for very large XML documents, because it holds the entire XML content in memory at once. If you want a more memory-efficient alternative, and you can afford to read and process the XML piece by piece, consider the XmlReader and XmlWriter classes described in recipe 6-7. To use the XmlDocument class, simply create a new instance of the class and call the Load method with a file name, a Stream, a TextReader, or an XmlReader object. It is also possible to read the XML from a simple string with the LoadXML method. You can even supply a string with a URL that points to an XML document on the Web using the Load method. The XmlDocument instance will be populated with the tree of elements, or nodes, from the source document. The entry point for accessing these nodes is the root element, which is provided through the XmlDocument.DocumentElement property. DocumentElement is an XmlElement object that can contain one or more nested XmlNode objects, which in turn can contain more XmlNode objects, and so on. An XmlNode is the basic ingredient of an XML file. Common XML nodes include elements, attributes, comments, and contained text. When dealing with an XmlNode or a class that derives from it (such as XmlElement or XmlAttribute), you can use the following basic properties: • ChildNodes is an XmlNodeList collection that contains the first level of nested nodes. • Name is the name of the node. • NodeType returns a member of the System.Xml.XmlNodeType enumeration that indicates the type of the node (element, attribute, text, and so on). • Value is the content of the node, if it’s a text or CDATA node. • Attributes provides a collection of node objects representing the attributes applied to the element. • InnerText retrieves a string with the concatenated value of the node and all nested nodes. 262
  3. CHAPTER 6 ■ XML PROCESSING • InnerXml retrieves a string with the concatenated XML markup for all nested nodes. • OuterXml retrieves a string with the concatenated XML markup for the current node and all nested nodes. The Code The following example walks through every element of an XmlDocument using the ChildNodes property and a recursive method. Each node is displayed in a TreeView control, with descriptive text that either identifies it or shows its content. using System; using System.Windows.Forms; using System.Xml; using System.IO; namespace Apress.VisualCSharpRecipes.Chapter06 { public partial class Recipe06_01 : System.Windows.Forms.Form { public Recipe06_01() { InitializeComponent(); } // Default the file name to the sample document. private void Recipe06_01_Load(object sender, EventArgs e) { txtXmlFile.Text = Path.Combine(Application.StartupPath, @"..\..\ProductCatalog.xml"); } private void cmdLoad_Click(object sender, System.EventArgs e) { // Clear the tree. treeXml.Nodes.Clear(); // Load the XML document. XmlDocument doc = new XmlDocument(); try { doc.Load(txtXmlFile.Text); } catch (Exception err) { MessageBox.Show(err.Message); return; } 263
  4. CHAPTER 6 ■ XML PROCESSING // Populate the TreeView. ConvertXmlNodeToTreeNode(doc, treeXml.Nodes); // Expand all nodes. treeXml.Nodes[0].ExpandAll(); } private void ConvertXmlNodeToTreeNode(XmlNode xmlNode, TreeNodeCollection treeNodes) { // Add a TreeNode node that represents this XmlNode. TreeNode newTreeNode = treeNodes.Add(xmlNode.Name); // Customize the TreeNode text based on the XmlNode // type and content. switch (xmlNode.NodeType) { case XmlNodeType.ProcessingInstruction: case XmlNodeType.XmlDeclaration: newTreeNode.Text = ""; break; case XmlNodeType.Element: newTreeNode.Text = ""; break; case XmlNodeType.Attribute: newTreeNode.Text = "ATTRIBUTE: " + xmlNode.Name; break; case XmlNodeType.Text: case XmlNodeType.CDATA: newTreeNode.Text = xmlNode.Value; break; case XmlNodeType.Comment: newTreeNode.Text = ""; break; } // Call this routine recursively for each attribute. // (XmlAttribute is a subclass of XmlNode.) if (xmlNode.Attributes != null) { foreach (XmlAttribute attribute in xmlNode.Attributes) { ConvertXmlNodeToTreeNode(attribute, newTreeNode.Nodes); } } 264
  5. CHAPTER 6 ■ XML PROCESSING // Call this routine recursively for each child node. // Typically, this child node represents a nested element // or element content. foreach (XmlNode childNode in xmlNode.ChildNodes) { ConvertXmlNodeToTreeNode(childNode, newTreeNode.Nodes); } } } } Usage As an example, consider the following simple XML file (which is included with the sample code as the ProductCatalog.xml file): Freeman and Freeman Unique Catalog 2010 2012-01-01 Gourmet Coffee Beans from rare Chillean plantations. 0.99 true Blue China Tea Pot A trendy update for tea drinkers. 102.99 true Figure 6-1 shows how this file will be rendered in the Recipe06_01 form. 265
  6. CHAPTER 6 ■ XML PROCESSING Figure 6-1. The displayed structure of an XML document 6-2. Insert Nodes in an XML Document Problem You need to modify an XML document by inserting new data, or you want to create an entirely new XML document in memory. Solution Create the node using the appropriate XmlDocument method (such as CreateElement, CreateAttribute, CreateNode, and so on). Then insert it using the appropriate XmlNode method (such as InsertAfter, InsertBefore, or AppendChild). How It Works Inserting a node into the XmlDocument class is a two-step process. You must first create the node, and then you insert it at the appropriate location. You can then call XmlDocument.Save to persist changes. 266
  7. CHAPTER 6 ■ XML PROCESSING To create a node, you use one of the XmlDocument methods starting with the word Create, depending on the type of node. This ensures that the node will have the same namespace as the rest of the document. (Alternatively, you can supply a namespace as an additional string argument.) Next, you must find a suitable related node and use one of its insertion methods to add the new node to the tree. The Code The following example demonstrates this technique by programmatically creating a new XML document: using System; using System.Xml; namespace Apress.VisualCSharpRecipes.Chapter06 { public class Recipe06_02 { private static void Main() { // Create a new, empty document. XmlDocument doc = new XmlDocument(); XmlNode docNode = doc.CreateXmlDeclaration("1.0", "UTF-8", null); doc.AppendChild(docNode); // Create and insert a new element. XmlNode productsNode = doc.CreateElement("products"); doc.AppendChild(productsNode); // Create a nested element (with an attribute). XmlNode productNode = doc.CreateElement("product"); XmlAttribute productAttribute = doc.CreateAttribute("id"); productAttribute.Value = "1001"; productNode.Attributes.Append(productAttribute); productsNode.AppendChild(productNode); // Create and add the subelements for this product node // (with contained text data). XmlNode nameNode = doc.CreateElement("productName"); nameNode.AppendChild(doc.CreateTextNode("Gourmet Coffee")); productNode.AppendChild(nameNode); XmlNode priceNode = doc.CreateElement("productPrice"); priceNode.AppendChild(doc.CreateTextNode("0.99")); productNode.AppendChild(priceNode); // Create and add another product node. productNode = doc.CreateElement("product"); productAttribute = doc.CreateAttribute("id"); productAttribute.Value = "1002"; productNode.Attributes.Append(productAttribute); productsNode.AppendChild(productNode); 267
  8. CHAPTER 6 ■ XML PROCESSING nameNode = doc.CreateElement("productName"); nameNode.AppendChild(doc.CreateTextNode("Blue China Tea Pot")); productNode.AppendChild(nameNode); priceNode = doc.CreateElement("productPrice"); priceNode.AppendChild(doc.CreateTextNode("102.99")); productNode.AppendChild(priceNode); // Save the document (to the console window rather than a file). doc.Save(Console.Out); Console.ReadLine(); } } } When you run this code, the generated XML document looks like this: Gourmet Coffee 0.99 Blue China Tea Pot 102.99 6-3. Quickly Append Nodes in an XML Document Problem You need to add nodes to an XML document without requiring lengthy, verbose code. 268
  9. CHAPTER 6 ■ XML PROCESSING Solution Create a helper function that accepts a tag name and content, and can generate the entire element at once. Alternatively, use the XmlDocument.CloneNode method to copy branches of an XmlDocument. How It Works Inserting a single element into an XmlDocument requires several lines of code. You can shorten this code in several ways. One approach is to create a dedicated helper class with higher-level methods for adding elements and attributes. For example, you could create an AddElement method that generates a new element, inserts it, and adds any contained text—the three operations needed to insert most elements. The Code Here’s an example of one such helper class: using System; using System.Xml; namespace Apress.VisualCSharpRecipes.Chapter06 { public class XmlHelper { public static XmlNode AddElement(string tagName, string textContent, XmlNode parent) { XmlNode node = parent.OwnerDocument.CreateElement(tagName); parent.AppendChild(node); if (textContent != null) { XmlNode content; content = parent.OwnerDocument.CreateTextNode(textContent); node.AppendChild(content); } return node; } public static XmlNode AddAttribute(string attributeName, string textContent, XmlNode parent) { XmlAttribute attribute; attribute = parent.OwnerDocument.CreateAttribute(attributeName); attribute.Value = textContent; parent.Attributes.Append(attribute); 269
  10. CHAPTER 6 ■ XML PROCESSING return attribute; } } } You can now condense the XML-generating code from recipe 6-2 with the simpler syntax shown here: public class Recipe06_03 { private static void Main() { // Create the basic document. XmlDocument doc = new XmlDocument(); XmlNode docNode = doc.CreateXmlDeclaration("1.0", "UTF-8", null); doc.AppendChild(docNode); XmlNode products = doc.CreateElement("products"); doc.AppendChild(products); // Add two products. XmlNode product = XmlHelper.AddElement("product", null, products); XmlHelper.AddAttribute("id", "1001", product); XmlHelper.AddElement("productName", "Gourmet Coffee", product); XmlHelper.AddElement("productPrice", "0.99", product); product = XmlHelper.AddElement("product", null, products); XmlHelper.AddAttribute("id", "1002", product); XmlHelper.AddElement("productName", "Blue China Tea Pot", product); XmlHelper.AddElement("productPrice", "102.99", product); // Save the document (to the console window rather than a file). doc.Save(Console.Out); Console.ReadLine(); } } Alternatively, you might want to take the helper methods such as AddAttribute and AddElement and make them instance methods in a custom class you derive from XmlDocument. Another approach to simplifying writing XML is to duplicate nodes using the XmlNode.CloneNode method. CloneNode accepts a Boolean deep parameter. If you supply true, CloneNode will duplicate the entire branch, with all nested nodes. Here is an example that creates a new product node by copying the first node: // (Add first product node.) // Create a new element based on an existing product. product = product.CloneNode(true); 270
  11. CHAPTER 6 ■ XML PROCESSING // Modify the node data. product.Attributes[0].Value = "1002"; product.ChildNodes[0].ChildNodes[0].Value = "Blue China Tea Pot"; product.ChildNodes[1].ChildNodes[0].Value = "102.99"; // Add the new element. products.AppendChild(product); Notice that in this case, certain assumptions are being made about the existing nodes (for example, that the first child in the item node is always the name, and the second child is always the price). If this assumption is not guaranteed to be true, you might need to examine the node name programmatically. 6-4. Find Specific Elements by Name Problem You need to retrieve a specific node from an XmlDocument, and you know its name but not its position. Solution Use the XmlDocument.GetElementsByTagName method, which searches an entire document and returns a System.Xml.XmlNodeList containing any matches. How It Works The XmlDocument class provides a convenient GetElementsByTagName method that searches an entire document for nodes that have the indicated element name. It returns the results as a collection of XmlNode objects. The Code The following code demonstrates how you could use GetElementsByTagName to calculate the total price of items in a catalog by retrieving all elements with the name productPrice: using System; using System.Xml; namespace Apress.VisualCSharpRecipes.Chapter06 { public class Recipe06_04 { private static void Main() { 271
  12. CHAPTER 6 ■ XML PROCESSING // Load the document. XmlDocument doc = new XmlDocument(); doc.Load(@"..\..\ProductCatalog.xml"); // Retrieve all prices. XmlNodeList prices = doc.GetElementsByTagName("productPrice"); decimal totalPrice = 0; foreach (XmlNode price in prices) { // Get the inner text of each matching element. totalPrice += Decimal.Parse(price.ChildNodes[0].Value); } Console.WriteLine("Total catalog value: " + totalPrice.ToString()); Console.ReadLine(); } } } Notes You can also search portions of an XML document by using the XmlElement.GetElementsByTagName method. It searches all the descendant nodes looking for matches. To use this method, first retrieve an XmlNode that corresponds to an element. Then cast this object to an XmlElement. The following example demonstrates how to find the price node under the first product element: // Retrieve a reference to the first product. XmlNode product = doc.GetElementsByTagName("products")[0]; // Find the price under this product. XmlNode productPrice = ((XmlElement)product).GetElementsByTagName("productPrice")[0]; Console.WriteLine("Price is " + productPrice.InnerText); If your elements include an attribute of type ID, you can also use a method called GetElementById to retrieve an element that has a matching ID value. 6-5. Get XML Nodes in a Specific XML Namespace Problem You need to retrieve nodes from a specific namespace using an XmlDocument. 272
  13. CHAPTER 6 ■ XML PROCESSING Solution Use the overload of the XmlDocument.GetElementsByTagName method that requires a namespace name as a string argument. Additionally, supply an asterisk (*) for the element name if you want to match all tags. How It Works Many XML documents contain nodes from more than one namespace. For example, an XML document that represents a scientific article might use a separate type of markup for denoting math equations and vector diagrams, or an XML document with information about a purchase order might aggregate client and order information with a shipping record. Similarly, an XML document that represents a business- to-business transaction might include portions from both companies, written in separate markup languages. A common task in XML programming is to retrieve the elements found in a specific namespace. You can perform this task with the overloaded version of the XmlDocument.GetElementsByTagName method that requires a namespace name. You can use this method to find tags by name or to find all the tags in the specified namespace if you supply an asterisk for the tag name parameter. The Code As an example, consider the following compound XML document, which includes order and client information, in two different namespaces (http://mycompany/OrderML and http://mycompany/ClientML): Sally Sergeyeva Here is a simple console application that selects all the tags in the http://mycompany/OrderML namespace: using System; using System.Xml; namespace Apress.VisualCSharpRecipes.Chapter06 { public class Recipe06_05 { private static void Main() 273
  14. CHAPTER 6 ■ XML PROCESSING { // Load the document. XmlDocument doc = new XmlDocument(); doc.Load(@"..\..\Order.xml"); // Retrieve all order tags. XmlNodeList matches = doc.GetElementsByTagName("*", "http://mycompany/OrderML"); // Display all the information. Console.WriteLine("Element \tAttributes"); Console.WriteLine("******* \t**********"); foreach (XmlNode node in matches) { Console.Write(node.Name + "\t"); foreach (XmlAttribute attribute in node.Attributes) { Console.Write(attribute.Value + " "); } Console.WriteLine(); } Console.ReadLine(); } } } The output of this program is as follows: Element Attributes ******* ********** ord:order http://mycompany/OrderML http://mycompany/ClientML ord:orderItem 3211 ord:orderItem 1155 6-6. Find Elements with an XPath Search Problem You need to search an XML document for nodes using advanced search criteria. For example, you might want to search a particular branch of an XML document for nodes that have certain attributes or contain a specific number of nested child nodes. 274
  15. CHAPTER 6 ■ XML PROCESSING Solution Execute an XPath expression using the SelectNodes or SelectSingleNode method of the XmlDocument class. How It Works The XmlNode class defines two methods that perform XPath searches: SelectNodes and SelectSingleNode. These methods operate on all contained child nodes. Because the XmlDocument inherits from XmlNode, you can call XmlDocument.SelectNodes to search an entire document. The Code For example, consider the following XML document, which represents an order for two items. This document includes text and numeric data, nested elements, and attributes, and so is a good way to test simple XPath expressions. Remarkable Office Supplies Electronic Protractor 42.99 Invisible Ink 200.25 Basic XPath syntax uses a pathlike notation. For example, the path /Order/Items/Item indicates an element that is nested inside an element, which in turn is nested in a root element. This is an absolute path. The following example uses an XPath absolute path to find the name of every item in an order: using System; using System.Xml; namespace Apress.VisualCSharpRecipes.Chapter06 { public class Recipe06_06 { private static void Main() { 275
  16. CHAPTER 6 ■ XML PROCESSING // Load the document. XmlDocument doc = new XmlDocument(); doc.Load(@"..\..\orders.xml"); // Retrieve the name of every item. // This could not be accomplished as easily with the // GetElementsByTagName method, because Name elements are // used in Item elements and Client elements, and so // both types would be returned. XmlNodeList nodes = doc.SelectNodes("/Order/Items/Item/Name"); foreach (XmlNode node in nodes) { Console.WriteLine(node.InnerText); } Console.ReadLine(); } } } The output of this program is as follows: Electronic Protractor Invisible Ink Notes XPath provides a rich and powerful search syntax, and it is impossible to explain all the variations you can use in a short recipe. However, Table 6-1 outlines some of the key ingredients in more advanced XPath expressions and includes examples that show how they would work with the order document. For a more detailed reference, refer to the W3C XPath recommendation, at Table 6-1. XPath Expression Syntax Expression Description Example / Starts an absolute path /Order/Items/Item selects all Item elements that are children that selects from the root of an Items element, which is itself a child of the root Order node. element. // Starts a relative path that //Item/Name selects all the Name elements that are children of selects nodes anywhere. an Item element, regardless of where they appear in the document. 276
  17. CHAPTER 6 ■ XML PROCESSING Expression Description Example @ Selects an attribute of a /Order/@id selects the attribute named id from the root node. Order element. * Selects any element in the /Order/* selects both Items and Client nodes because both path. are contained by a root Order element. | Combines multiple paths. /Order/Items/Item/Name|Order/Client/Name selects the Name nodes used to describe a Client and the Name nodes used to describe an Item. . Indicates the current If the current node is an Order, the expression ./Items refers (default) node. to the related items for that order. .. Indicates the parent node. //Name/.. selects any element that is parent to a Name, which includes the Client and Item elements. [] Defines selection criteria /Order[@id="2004-01-30.195496"] selects the Order that can test a contained elements with the indicated attribute value. node or an attribute value. /Order/Items/Item[Price > 50] selects products higher than $50 in price. /Order/Items/Item[Price > 50 and Name="Laser Printer"] selects products that match two criteria. starts- Retrieves elements based /Order/Items/Item[starts-with(Name, "C")] finds all Item with on what text a contained elements that have a Name element that starts with the letter element starts with. C. position Retrieves elements based /Order/Items/Item[position ()=2] selects the second Item on position. element. count Counts elements. You /Order/Items/Item[count(Price) = 1] retrieves Item specify the name of the elements that have exactly one nested Price element. child element to count or an asterisk (*) for all children. ■ Note XPath expressions and all element and attribute names you use inside them are always case-sensitive, because XML itself is case-sensitive. 277
  18. CHAPTER 6 ■ XML PROCESSING 6-7. Read and Write XML Without Loading an Entire Document into Memory Problem You need to read XML from a stream or write it to a stream. However, you want to process the information one node at a time, rather than loading it all into memory with an XmlDocument. Solution To write XML, create an XmlWriter that wraps a stream and use Write methods (such as WriteStartElement and WriteEndElement). To read XML, create an XmlReader that wraps a stream, and call Read to move from node to node. How It Works The XmlWriter and XmlReader classes read or write XML directly from a stream one node at a time. These classes do not provide the same features for navigating and manipulating your XML as XmlDocument, but they do provide higher performance and a smaller memory footprint, particularly if you need to deal with large XML documents. Both XmlWriter and XmlReader are abstract classes, which means you cannot create an instance of them directly. Instead, you should call the Create method of XmlWriter or XmlReader and supply a file or stream. The Create method will return the right derived class based on the options you specify. This allows for a more flexible model. Because your code uses the base classes, it can work seamlessly with any derived class. For example, you could switch to a validating reader (as shown in the next recipe) without needing to modify your code. To write XML to any stream, you can use the streamlined XmlWriter. It provides Write methods that write one node at a time. These include the following: • WriteStartDocument, which writes the document prologue, and WriteEndDocument, which closes any open elements at the end of the document. • WriteStartElement, which writes an opening tag for the element you specify. You can then add more elements nested inside this element, or you can call WriteEndElement to write the closing tag. • WriteElementString, which writes an entire element, with an opening tag, a closing tag, and text content. • WriteAttributeString, which writes an entire attribute for the nearest open element, with a name and value. Using these methods usually requires less code than creating an XmlDocument by hand, as demonstrated in recipes 6-2 and 6-3. To read the XML, you use the Read method of the XmlReader. This method advances the reader to the next node and returns true. If no more nodes can be found, it returns false. You can retrieve 278
  19. CHAPTER 6 ■ XML PROCESSING information about the current node through XmlReader properties, including its Name, Value, and NodeType. To find out whether an element has attributes, you must explicitly test the HasAttributes property and then use the GetAttribute method to retrieve the attributes by name or index number. The XmlTextReader class can access only one node at a time, and it cannot move backward or jump to an arbitrary node, which gives much less flexibility than the XmlDocument class. The Code The following console application writes and reads a simple XML document using the XmlWriter and XmlReader classes. This is the same XML document created in recipes 6-2 and 6-3 using the XmlDocument class. using System; using System.Xml; using System.IO; using System.Text; namespace Apress.VisualCSharpRecipes.Chapter06 { public class Recipe06_07 { private static void Main() { // Create the file and writer. FileStream fs = new FileStream("products.xml", FileMode.Create); // If you want to configure additional details (like indenting, // encoding, and new line handling), use the overload of the Create // method that accepts an XmlWriterSettings object instead. XmlWriter w = XmlWriter.Create(fs); // Start the document. w.WriteStartDocument(); w.WriteStartElement("products"); // Write a product. w.WriteStartElement("product"); w.WriteAttributeString("id", "1001"); w.WriteElementString("productName", "Gourmet Coffee"); w.WriteElementString("productPrice", "0.99"); w.WriteEndElement(); // Write another product. w.WriteStartElement("product"); w.WriteAttributeString("id", "1002"); w.WriteElementString("productName", "Blue China Tea Pot"); w.WriteElementString("productPrice", "102.99"); w.WriteEndElement(); 279
Đồng bộ tài khoản