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
Trang 2■ ■ ■
261
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 9), create an XML schema for a class (recipe
10), and generate the source code for a class based on an XML schema (recipe
Trang 3The 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
www.w3c.org
■ 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
Trang 4263
• 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
// Default the file name to the sample document
private void Recipe06_01_Load(object sender, EventArgs e)
// Load the XML document
XmlDocument doc = new XmlDocument();
Trang 5// 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
}
// Call this routine recursively for each attribute
// (XmlAttribute is a subclass of XmlNode.)
}
Trang 6265
// Call this routine recursively for each child node
// Typically, this child node represents a nested element
<productName>Blue China Tea Pot</productName>
<description>A trendy update for tea drinkers.</description>
Trang 7266
Figure 6-1 The displayed structure of an XML document
6-2 Insert Nodes in an XML Document
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
Trang 8267
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
// 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");
Trang 10269
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
public static XmlNode AddElement(string tagName,
string textContent, XmlNode parent)
public static XmlNode AddAttribute(string attributeName,
string textContent, XmlNode parent)
Trang 11// 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);
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);
Trang 12The 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
Trang 13272
// Load the document
XmlDocument doc = new XmlDocument();
doc.Load(@" \ \ProductCatalog.xml");
// Retrieve all prices
XmlNodeList prices = doc.GetElementsByTagName("productPrice");
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
Trang 14273
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):
Trang 15274
{
// Load the document
XmlDocument doc = new XmlDocument();
doc.Load(@" \ \Order.xml");
// Retrieve all order tags
XmlNodeList matches = doc.GetElementsByTagName("*",
Trang 16For 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
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:
Trang 17276
// 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)
a more detailed reference, refer to the W3C XPath recommendation, at www.w3.org/TR/xpath
Table 6-1 XPath Expression Syntax
/ 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
Trang 18| 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
(default) node
If the current node is an Order, the expression /Items refers
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
that can test a contained
node or an attribute value
/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
starts-with
Retrieves elements based
on what text a contained
element starts with
/Order/Items/Item[starts-with(Name, "C")] finds all Item elements that have a Name element that starts with the letter
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
Trang 19278
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
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
Trang 20XmlTextReader 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
// 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
Trang 21fs = new FileStream("products.xml", FileMode.Open);
// If you want to configure additional details (like comments,
// whitespace handling, or validation), use the overload of the Create
// method that accepts an XmlReaderSettings object instead
Often, when using the XmlReader, you are searching for specific nodes rather than processing every
element, as in this example The approach used in this example does not work as well in this situation It forces you to read element tags, text content, and CDATA sections separately, which means you need to explicitly keep track of where you are in the document A better approach is to read the entire node and
text content at once (for simple text-only nodes) by using the ReadElementString method You can also use methods such as ReadToDescendant, ReadToFollowing, and ReadToNextSibling, all of which allow you
to skip some nodes
Trang 22281
For example, you can use ReadToFollowing("Price"); to skip straight to the next Price element,
without worrying about whitespace, comments, or other elements before it (If a Price element cannot
be found, the XmlReader moves to the end of the document, and the ReadToFollowing method returns
any validation exceptions To find all the errors in a document without catching exceptions, handle the
ValidationEventHandler event on the XmlReaderSettings object given as parameter to XmlReader
How It Works
An XML schema defines the rules that a given type of XML document must follow The schema includes rules that define the following:
• The elements and attributes that can appear in a document
• The data types for elements and attributes
• The structure of a document, including what elements are children of other
elements
• The order and number of child elements that appear in a document
• Whether elements are empty, can include text, or require fixed values
XML schema documents are beyond the scope of this chapter, but you can learn much from a
simple example This recipe uses the product catalog first presented in recipe 6-1
At its most basic level, XML Schema Definition (XSD) defines the elements that can occur in an XML document XSD documents are themselves written in XML, and you use a separate predefined element
(named <element>) in the XSD document to indicate each element that is required in the target
document The type attribute indicates the data type Here is an example for a product name:
<xsd:element name="productName" type="xsd:string" />
And here is an example for the product price:
<xsd:element name="productPrice" type="xsd:decimal" />
The basic schema data types are defined at www.w3.org/TR/xmlschema-2 They map closely to NET data types and include string, int, long, decimal, float, dateTime, boolean, and base64Binary—to name
a few of the most frequently used types
Both productName and productPrice are simple types because they contain only character data
Elements that contain nested elements are called complex types You can nest them together using a
Trang 23282
<sequence> tag, if order is important, or an <all> tag if it is not Here is how you might model the
<product> element in the product catalog Notice that attributes are always declared after elements, and they are not grouped with a <sequence> or <all> tag because order is never important:
<xsd:complexType name="product">
<xsd:sequence>
<xsd:element name="productName" type="xsd:string"/>
<xsd:element name="productPrice" type="xsd:decimal"/>
<xsd:element name="inStock" type="xsd:boolean"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:integer"/>
</xsd:complexType>
By default, a listed element can occur exactly one time in a document You can configure this
behavior by specifying the maxOccurs and minOccurs attributes Here is an example that allows an
unlimited number of products in the catalog:
<xsd:element name="product" type="product" maxOccurs="unbounded" />
Here is the complete schema for the product catalog XML:
<xsd:element name="productName" type="xsd:string"/>
<xsd:element name="description" type="xsd:string"/>
<xsd:element name="productPrice" type="xsd:decimal"/>
<xsd:element name="inStock" type="xsd:boolean"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:integer"/>
</xsd:complexType>
<! This is the structure the document must match
It begins with a productCatalog element that nests other elements >
<xsd:element name="productCatalog">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="catalogName" type="xsd:string"/>
<xsd:element name="expiryDate" type="xsd:date"/>
Trang 24The XmlReader class can enforce these schema rules, providing you explicitly request a validating
reader when you use the XmlReader.Create method (Even if you do not use a validating reader, an
exception will be thrown if the reader discovers XML that is not well formed, such as an illegal character, improperly nested tags, and so on.)
Once you have created your validating reader, the validation occurs automatically as you read
through the document As soon as an error is found, the XmlReader raises a ValidationEventHandler
event with information about the error on the XmlReaderSettings object given at creation time If you
want, you can handle this event and continue processing the document to find more errors If you do
not handle this event, an XmlException will be raised when the first error is encountered, and processing
will be aborted
The Code
The next example shows a utility class that displays all errors in an XML document when the ValidateXml
method is called Errors are displayed in a console window, and a final Boolean variable is returned to
indicate the success or failure of the entire validation operation
// Set to true if at least one error exists
private bool failed;
public bool Failed
// Set the type of validation
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
// Load the schema file
XmlSchemaSet schemas = new XmlSchemaSet();
settings.Schemas = schemas;
// When loading the schema, specify the namespace it validates
// and the location of the file Use null to use
Trang 25// Create the validating reader
XmlReader validator = XmlReader.Create(xmlFilename, settings); failed = false;
// Display the validation error
Console.WriteLine("Validation error: " + args.Message);
Console.WriteLine();
}
}
}
Here is how you would use the class to validate the product catalog:
public class Recipe06_08
Trang 26If the document is valid, no messages will appear, and the success variable will be set to true But
consider what happens if you use a document that breaks schema rules, such as the
ProductCatalog_Invalid.xml file shown here:
If you attempt to validate this document, the success variable will be set to false, and the output
will indicate each error:
Validating ProductCatalog_Invalid.xml
Validation error: The 'expiryDate' element has an invalid value according to
its data type [path information truncated]
Validation error: The 'productPrice' element has an invalid value according to
Trang 27286
its data type [path information truncated]
Validation error: The 'inStock' element has an invalid value according to its
data type [path information truncated]
Validation failed
Finally, if you want to validate an XML document and load it into an in-memory XmlDocument, you need to take a slightly different approach The XmlDocument provides its own Schemas property, along with
a Validate method that checks the entire document in one step When you call Validate, you supply a
delegate that points to your validation event handler
Here is how it works:
XmlDocument doc = new XmlDocument();
doc.Load(@" \ \Product_Catalog.xml");
// Specify the schema information
XmlSchemaSet schemas = new XmlSchemaSet();
Use the System.Xml.Serialization.XmlSerializer class to transfer data from your object to XML, and
vice versa You can also mark up your class code with attributes to customize its XML representation
Trang 28287
How It Works
The XmlSerializer class allows you to convert objects to XML data, and vice versa This process is used
natively by web services and provides a customizable serialization mechanism that does not require a
single line of custom code The XmlSerializer class is even intelligent enough to correctly create arrays
when it finds nested elements
The only requirements for using XmlSerializer are as follows:
• The XmlSerializer serializes only properties and public variables
• The classes you want to serialize must include a default zero-argument
constructor The XmlSerializer uses this constructor when creating the new
object during deserialization
• All class properties must be readable and writable This is because XmlSerializer
uses the property get accessor to retrieve information and the property set
accessor to restore the data after deserialization
■ Note You can also store your objects in an XML-based format using NET serialization and System.Runtime Serialization.Formatters.Soap.SoapFormatter In this case, you simply need to make your class
serializable—you do not need to provide a default constructor or ensure all properties are writable However, this gives you no control over the format of the serialized XML
To use XML serialization, you must first mark up your data objects with attributes that indicate the
desired XML mapping You can find these attributes in the System.Xml.Serialization namespace and
include the following:
• XmlRoot specifies the name of the root element of the XML file By default,
XmlSerializer will use the name of the class You can apply this attribute to the
class declaration
• XmlElement indicates the element name to use for a property or public variable By
default, XmlSerializer will use the name of the property or public variable
• XmlAttribute indicates that a property or public variable should be serialized as an
attribute, not an element, and specifies the attribute name
• XmlEnum configures the text that should be used when serializing enumerated
values If you don’t use XmlEnum, the name of the enumerated constant will be
used
• XmlIgnore indicates that a property or public variable should not be serialized
Trang 29288
The Code
For example, consider the product catalog first shown in recipe 6-1 You can represent this XML
document using ProductCatalog and Product objects Here’s the class code that you might use: using System;
public string CatalogName;
// Use the date data type (and ignore the time portion in the
// serialized XML)
[XmlElement(ElementName="expiryDate", DataType="date")]
public DateTime ExpiryDate;
// Configure the name of the tag that holds all products
// and the name of the product tag itself
Trang 30Notice that these classes use the XML serialization attributes to rename element names (using
Pascal casing in the class member names and camel casing in the XML tag names), indicate data types
that are not obvious, and specify how <product> elements will be nested in the <productCatalog>
Using these custom classes and the XmlSerializer object, you can translate XML into objects, and vice versa The following is the code you would need to create a new ProductCatalog object, serialize the
results to an XML document, deserialize the document back to an object, and then display the XML
// Create the product catalog
ProductCatalog catalog = new ProductCatalog("New Catalog",
DateTime.Now.AddYears(1));
Product[] products = new Product[2];
products[0] = new Product("Product 1", 42.99m);
products[1] = new Product("Product 2", 202.99m);
catalog.Products = products;
// Serialize the order to a file
XmlSerializer serializer = new XmlSerializer(typeof(ProductCatalog));
FileStream fs = new FileStream("ProductCatalog.xml", FileMode.Create);
serializer.Serialize(fs, catalog);
fs.Close();
catalog = null;
// Deserialize the order from the file
fs = new FileStream("ProductCatalog.xml", FileMode.Open);
Trang 31You need to create an XML schema based on one or more C# classes This will allow you to validate XML
documents before deserializing them with the XmlSerializer
Solution
Use the XML Schema Definition Tool (xsd.exe) command-line utility included with the NET
Framework Specify the name of your assembly as a command-line argument, and add the
/t:[TypeName] parameter to indicate the types you want to convert
How It Works
Recipe 6-9 demonstrated how to use the XmlSerializer to serialize NET objects to XML and deserialize
XML into NET objects But if you want to use XML as a way to interact with other applications, business processes, or non–.NET Framework applications, you’ll need an easy way to validate the XML before you attempt to deserialize it You will also need to define an XML schema document that defines the
structure and data types used in your XML format so that other applications can work with it One quick
solution is to generate an XML schema using the xsd.exe command-line utility
The xsd.exe utility is included with the NET Framework If you have installed Microsoft Visual Studio NET, you will find it in a directory like C:\Program Files\Microsoft Visual Studio
.NET\FrameworkSDK\Bin The xsd.exe utility can generate schema documents from compiled assemblies
You simply need to supply the file name and indicate the class that represents the XML document with
the / t:[TypeName] parameter
Usage
For example, consider the ProductCatalog and Product classes shown in recipe 6-9 You could create the
XML schema for a product catalog with the following command line:
xsd Recipe06-09.exe /t:ProductCatalog
You need to specify only the ProductCatalog class on the command line because this class
represents the actual XML document The generated schema in this example will represent a complete
Trang 32291
product catalog, with contained product items It will be given the default file name schema0.xsd You
can now use the validation technique shown in recipe 6-8 to test whether the XML document can be
successfully validated with the schema
6-11 Generate a Class from a Schema
Problem
You need to create one or more C# classes based on an XML schema You can then create an XML
document in the appropriate format using these objects and the XmlSerializer
Solution
Use the xsd.exe command-line utility included with the NET Framework Specify the name of your
schema file as a command-line argument, and add the /c parameter to indicate you want to generate
class code
How It Works
Recipe 6-10 introduced the xsd.exe command-line utility, which you can use to generate schemas based
on class definitions The reverse operation—generating C# source code based on an XML schema
document—is also possible This is primarily useful if you want to write a certain format of XML
document but you do not want to manually create the document by writing individual nodes with the
XmlDocument class or the XmlWriter class Instead, by using xsd.exe, you can generate a set of full NET
objects You can then serialize these objects to the required XML representation using the
XmlSerializer, as described in recipe 6-9
To generate source code from a schema, you simply need to supply the file name of the schema
document and add the /c parameter to indicate you want to generate the required classes
Usage
For example, consider the schema shown in recipe 6-8 You can generate C# code for this schema with the following command line:
xsd ProductCatalog.xsd /c
This will generate one file (ProductCatalog.cs) with two classes: Product and ProductCalalog These
classes are similar to the ones created in recipe 6-9, except that the class member names match the XML
document exactly Optionally, you can add the /f parameter If you do, the generated classes will be
composed of public fields If you do not, the generated classes will use public properties instead (which simply wrap private fields)
Trang 33Use the System.Xml.Xsl.XslCompiledTransform class Load the XSLT stylesheet using the
XslCompiledTransform.Load method, and generate the output document by using the Transform method
and supplying a source document
orders.xml document shown in recipe 6-6 into an HTML document with a table, and then displays the
results To perform this transformation, you’ll need the following XSLT stylesheet:
Order <b><xsl:value-of select="Client/@id"/></b>
for <xsl:value-of select="Client/Name"/></p>
Trang 34The orders.xslt stylesheet contains two template elements (as children of the root stylesheet
element) The first template matches the root Order element When the XSLT processor finds an Order
element, it outputs the tags necessary to start an HTML table with appropriate column headings and
inserts some data about the client using the value-of command, which outputs the text result of an
XPath expression In this case, the XPath expressions (Client/@id and Client/Name) match the id attribute and the Name element
Next, the apply-templates command branches off and performs processing of any contained Item elements This is required because there might be multiple Item elements Each Item element is matched using the XPath expression Items/Item The root Order node is not specified because Order is the current
node Finally, the initial template writes the tags necessary to end the HTML document
If you execute this transform on the sample orders.xml file shown in recipe 6-6, you will end up with
the following HTML document:
Trang 35294
To apply an XSLT stylesheet in NET, you use the XslCompiledTransform class The following code
shows a Windows-based application that programmatically applies the transformation and then
displays the transformed file in a window using the WebBrowser control:
XslCompiledTransform transform = new XslCompiledTransform();
// Load the XSTL stylesheet
Trang 36295
Figure 6-2 The stylesheet output for orders.xml
In this example, the code uses the overloaded version of the Transform method that saves the result
document directly to disk, although you could receive it as a stream and process it inside your
application instead The following code shows an alternate approach that keeps the document content
in memory at all times (with no external results file) The XslCompiledTransform writes the results to an XmlWriter that wraps a StringBuilder The content is then copied from the StringBuilder into the
WebBrowser through the handy WebBrowser.DocumentText property The results are identical
StringBuilder htmlContent = new StringBuilder();
XmlWriter results = XmlWriter.Create(htmlContent);
transform.Transform(@" \ \orders.xml", results);
webBrowser1.DocumentText = htmlContent.ToString();
Trang 37support a range of data sources, as shown in Table 6-2
Table 6-2 Overloads of the XElement.Load Method
Method Description
Load(Stream) Loads the XML data from a stream
Load(String) Loads the XML from a file, the name of which is obtained from the string
Load(TextReader) Loads the XML from a System.IO.TextReader
Load(XMLReader) Loads the XML from a System.Xml.XmlReader (see recipe 6-7)
You can control some of the load options by using the System.Xml.Linq.LoadOptions enumeration
as an additional argument to the Load method—see the NET Framework documentation for details
Trang 38// Define the path to the sample file
string filename = @" \ \ProductCatalog.xml";
// Load the XML using the file name
Console.WriteLine("Loading using file name");
XElement root = XElement.Load(filename);
// Write out the XML
Console.WriteLine(root);
Console.WriteLine("Press enter to continue");
Console.ReadLine();
// Load via a stream to the file
Console.WriteLine("Loading using a stream");
FileStream filestream = File.OpenRead(filename);
// Load via a textreader
Console.WriteLine("Loading using a TextReader");
TextReader reader = new StreamReader(filename);
// Load via an xmlreader
Console.WriteLine("Loading using an XmlReader");
XmlReader xmlreader = new XmlTextReader(new StreamReader(filename));
Trang 39Create a new instance of System.Xml.Linq.XElement for the root element Child nodes and attributes can
be added by passing instances of XElement or XAttribute as constructor arguments or by calling the Add
instance method
How It Works
There are two ways to create an XML tree using LINQ to XML The first is to create an instance of
XElement and pass in instances of XElement for child nodes and XAttribute for attributes of the root node
as constructor arguments A simple example follows:
XElement root = new XElement("myrootnode",
new XAttribute("first_attribute", "first_attribute_value"),
new XAttribute("second_attribute", "second_attribute_value"),
new XElement("first_element", "first_element"),
new XElement("second_element", "second_element",
new XAttribute("nested_attribute", "nested_attribute_value"))
);
This approach looks confusing, but starts to make sense once you start to write code yourself You
can supply any number of constructor arguments, and each XElement you create to represent a child
node will accept constructor arguments itself to represent its own children and attributes The preceding example is equivalent to the following XML:
Trang 40299
The second approach to create an XML tree with XML to LINQ is to create individual instances of
XElement and XAttribute and add them to their parent node using the XElement.Add instance method
See the example in this recipe for a demonstration
In addition to XElement and XAttribute, the System.Xml.Linq namespace includes classes that
represent other XML types—including XComment, XDeclaration, and XCData The NET Framework
contains a full description of each type available, but of particular interest are XDocument and
XDeclaration—these classes allow you to create the standard XML declaration at the start of your data
The Code
The following example creates one element using the constructor arguments and adds another using the
XElement.Add method The root element is then added to an instance of XDocument along with an
XDeclaration The XDocument is written out to the console via an XMLTextWriter, which ensures that the XML header is included (it is omitted if the XDocument instance is passed to Console.WriteLine)