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

Visual CSharp 2005 Recipes (2006) [eng]

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

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

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

//End the document.

w.WriteEndElement();

w.WriteEndDocument();

w.Flush();

fs.Close();

Console.WriteLine("Document created. " + "Press Enter to read the document."); Console.ReadLine();

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

199

fs = 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.

XmlReader r = XmlReader.Create(fs);

// Read all nodes. while (r.Read())

{

if (r.NodeType == XmlNodeType.Element)

{

Console.WriteLine(); Console.WriteLine("<" + r.Name + ">");

if (r.HasAttributes)

{

for (int i = 0; i < r.AttributeCount; i++)

{

Console.WriteLine("\tATTRIBUTE: " + r.GetAttribute(i));

}

}

}

else if (r.NodeType == XmlNodeType.Text)

{

Console.WriteLine("\tVALUE: " + r.Value);

}

}

Console.ReadLine();

}

}

}

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.

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 false.)

6-8. Validate an XML Document Against a Schema

Problem

You need to validate the content of an XML document by ensuring that it conforms to an XML schema.

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

Solution

When you call XmlReader.Create, supply an XmlReaderSettings object that indicates you want to perform validation. Then, move through the document one node at a time by calling XmlReader.Read, catching 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 http://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 the 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 <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" />

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

201

Here is the complete schema for the product catalog XML:

<?xml version="1.0"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<!-- Define the complex type product. --> <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>

<!-- 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"/>

<xsd:element name="products"> <xsd:complexType>

<xsd:sequence>

<xsd:element name="product" type="product" maxOccurs="unbounded" />

</xsd:sequence>

</xsd:complexType>

</xsd:element>

</xsd:sequence>

</xsd:complexType>

</xsd:element>

</xsd:schema>

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

using System; using System.Xml;

using System.Xml.Schema;

202 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 ConsoleValidator

{

// Set to true if at least one error exists. private bool failed;

public bool Failed

{

get {return failed;}

}

public bool ValidateXml(string xmlFilename, string schemaFilename)

{

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

//the targetNamespace value from the schema. schemas.Add(null, schemaFilename);

//Specify an event handler for validation errors. settings.ValidationEventHandler += ValidationEventHandler;

//Create the validating reader.

XmlReader validator = XmlReader.Create(xmlFilename, settings);

failed = false; try

{

// Read all XML data. while (validator.Read()) {}

}

catch (XmlException err)

{

//This happens if the XML document includes illegal characters

//or tags that aren't properly nested or closed. Console.WriteLine("A critical XML error has occurred."); Console.WriteLine(err.Message);

failed = true;

}

finally

{

validator.Close();

}

return !failed;

}

private void ValidationEventHandler(object sender, ValidationEventArgs args)

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

203

{

failed = true;

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

{

private static void Main()

{

ConsoleValidator consoleValidator = new ConsoleValidator(); Console.WriteLine("Validating ProductCatalog.xml.");

bool success = consoleValidator.ValidateXml("ProductCatalog.xml", "ProductCatalog.xsd");

if (!success)

Console.WriteLine("Validation failed.");

else

Console.WriteLine("Validation succeeded.");

Console.ReadLine();

}

}

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

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

<catalogName>Acme Fall 2003 Catalog</catalogName> <expiryDate>Jan 1, 2004</expiryDate>

<products>

<product id="1001">

<productName>Magic Ring</productName> <productPrice>$342.10</productPrice> <inStock>true</inStock>

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

<productName>Flying Carpet</productName> <productPrice>982.99</productPrice> <inStock>Yes</inStock>

</product>

</products>

</productCatalog>

If you attempt to validate this document, the success variable will be set to false, and the output will indicate each error:

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

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 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(); schemas.Add(null, schemaFilename); doc.Schemas = schemas;

//Validate the document.

doc.Validate(new ValidationEventHandler(ValidationEventHandler));

6-9. Use XML Serialization with Custom Objects

Problem

You need to use XML as a serialization format. However, you don’t want to process the XML directly in your code—instead, you want to interact with the data using custom objects.

Solution

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.

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.

Note

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

205

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.

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 serializ- able—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.

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;

using System.Xml.Serialization;

namespace Apress.VisualCSharpRecipes.Chapter06

{

[XmlRoot("productCatalog")] public class ProductCatalog

{

[XmlElement("catalogName")] 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. [XmlArray("products")]

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

[XmlArrayItem("product")] public Product[] Products;

public ProductCatalog()

{

// Default constructor for deserialization.

}

public ProductCatalog(string catalogName, DateTime expiryDate)

{

this.CatalogName = catalogName; this.ExpiryDate = expiryDate;

}

}

public class Product

{

[XmlElement("productName")] public string ProductName;

[XmlElement("productPrice")] public decimal ProductPrice;

[XmlElement("inStock")] public bool InStock;

[XmlAttributeAttribute(AttributeName="id", DataType="integer")] public string Id;

public Product()

{

// Default constructor for serialization.

}

public Product(string productName, decimal productPrice)

{

this.ProductName = productName; this.ProductPrice = productPrice;

}

}

}

Notice 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 document:

using System; using System.Xml;

using System.Xml.Serialization; using System.IO;

namespace Apress.VisualCSharpRecipes.Chapter06

{

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

207

public class Recipe06_09

{

private static void Main()

{

// 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); catalog = (ProductCatalog)serializer.Deserialize(fs);

// Serialize the order to the console window. serializer.Serialize(Console.Out, catalog); Console.ReadLine();

}

}

}

6-10. Create a Schema for a .NET Class

Problem

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

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