Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

188 C H A P T E R 6 X M L P R O C E S S I N G

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. Optionally, you can then call XmlDocument.Save to persist changes.

To create a node, you use one of the XmlDocument methods starting with the word Create, depending on the type of node. This ensures 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);

nameNode = doc.CreateElement("productName");

C H A P T E R 6 X M L P R O C E S S I N G

189

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:

<?xml version="1.0"?> <products>

<product id="1001">

<productName>Gourmet Coffee</productName> <productPrice>0.99</productPrice>

</product> <product id="1002">

<productName>Blue China Tea Pot</productName> <productPrice>102.99</productPrice>

</product>

</products>

6-3. Quickly Append Nodes in an XML Document

Problem

You need to add nodes to an XML document without requiring lengthy, verbose code.

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;

190 C H A P T E R 6 X M L P R O C E S S I N G

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);

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);

C H A P T E R 6 X M L P R O C E S S I N G

191

// 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);

//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;

192 C H A P T E R 6 X M L P R O C E S S I N G

namespace Apress.VisualCSharpRecipes.Chapter06

{

public class Recipe06_04

{

private static void Main()

{

// 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 price = ((XmlElement)product).GetElementsByTagName("productPrice")[0]; Console.WriteLine("Price is " + price.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.

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.

C H A P T E R 6 X M L P R O C E S S I N G

193

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 that includes order and client information, in two different namespaces (http://mycompany/OrderML and http://mycompany/ClientML):

<?xml version="1.0" ?>

<ord:order xmlns:ord="http://mycompany/OrderML" xmlns:cli="http://mycompany/ClientML">

<cli:client>

<cli:firstName>Sally</cli:firstName>

<cli:lastName>Sergeyeva</cli:lastName>

</cli:client>

<ord:orderItem itemNumber="3211"/> <ord:orderItem itemNumber="1155"/>

</ord:order>

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()

{

// 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)

194 C H A P T E R 6 X M L P R O C E S S I N G

{

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.

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.

<?xml version="1.0"?>

<Order id="2004-01-30.195496"> <Client id="ROS-930252034">

<Name>Remarkable Office Supplies</Name> </Client>

<Items>

<Item id="1001">

<Name>Electronic Protractor</Name> <Price>42.99</Price>

C H A P T E R 6 X M L P R O C E S S I N G

195

</Item>

<Item id="1002"> <Name>Invisible Ink</Name> <Price>200.25</Price>

</Item>

</Items>

</Order>

Basic XPath syntax uses a pathlike notation. For example, the path /Order/Items/Item indicates an <Item> element that is nested inside an <Items> element, which, in turn, is nested in a root <Order> 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()

{

// 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 http://www.w3.org/TR/xpath.

196 C H A P T E R 6 X M L P R O C E S S I N G

Table 6-1. XPath Expression Syntax

Expression

Description

Example

/Starts an absolute path that selects from the root node.

/Order/Items/Item selects all

Item elements that are children of an Items element, which is itself a child of the root Order element.

//Starts a relative path that selects nodes anywhere.

//Item/Name selects all the Name elements that are children of an Item element, regardless of where they appear in the document.

@Selects an attribute of a node.

*Selects any element in the path.

|Combines multiple paths.

.Indicates the current (default) node.

..

Indicates the parent node.

[ ]

Defines selection criteria that can test

 

a contained node or an attribute value.

starts-with Retrieves elements based on what text a contained element starts with.

position

Retrieves elements based on position.

/Order/@id selects the attribute named id from the root Order element.

/Order/* selects both Items and Client nodes because both are contained by a root Order element.

/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.

If the current node is an Order, the expression ./Items refers to the related items for that order.

//Name/.. selects any element that is parent to a Name, which includes the Client and Item elements.

/Order[@id="2004-01-30.195496"] selects the Order elements with the indicated 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.

/Order/Items/Item[starts-with (Name, "C")] finds all Item elements that have a Name element that starts with the letter C.

/Order/Items/Item[position ()=2] selects the second Item element.

count

Counts elements. You specify the name

 

of the child element to count or

 

an asterisk (*) for all children.

/Order/Items/Item[count(Price) = 1] retrieves Item elements that have exactly one nested Price element.

Note XPath expressions and all element and attribute names you use inside them are always case-sensitive, because XML itself is case-sensitive.

C H A P T E R 6 X M L P R O C E S S I N G

197

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 the XmlWriter and XmlReader are abstract classes, which means you cannot create an instance of them directly. Instead, you need to create an instance of a derived class, such as XmlTextWriter. In .NET 2.0, the preferred convention is not to create a derived class directly. Instead, you should call the Create method of the 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 information about the current node through XmlReader properties, including its Name, Value, and

NodeType.

Соседние файлы в предмете Программирование на C++