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

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

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

568 C H A P T E R 1 3 X M L

The processing of each of the nodes is done using a simple case statement on the node type of the current node:

switch (reader->NodeType)

{

case XmlNodeType::Comment: //...Process a comment break;

case XmlNodeType::Element: //...Process an element break;

//...etc.

}

The processing of most of the node types in the preceding example involves simply writing either the name or the value to the console. One exception is the Element tag. It starts off like the other node type by writing its name to the console, but then it continues on to check if it has attributes. If it does, it moves through each of the attributes and writes them to the console as well. When it has finished processing the attributes, it moves the element back as the current node using the MoveToElement() method. You might think you have just broken the forward-only property, but in reality, attributes are only part of an element, so therefore the element is still the current node.

It is possible for an element to be empty using the syntax <tag/>, so you have to then check to see if the element is empty. If it is, you write the element’s end tag to the console:

case XmlNodeType::Element: Console::WriteLine("{0}Element node: Name='{1}'",

indent(reader->Depth), reader->Name);

if (reader->HasAttributes)

{

while (reader->MoveToNextAttribute())

{

Console::WriteLine("{0}Attribute node: Name='{1}' Value='{2}'", indent(reader->Depth), reader->Name, reader->Value);

}

reader->MoveToElement();

}

if (reader->IsEmptyElement)

{

Console::WriteLine("{0}End Element node: Name='{1}'", indent(reader->Depth), reader->Name);

}

break;

Figure 13-1 shows the results of ReadXML.exe. It’s hard to believe so much information is contained within such a small XML file.

C H A P T E R 1 3 X M L

569

Figure 13-1. A console output of the XML monster file

Validating an XML File

The XmlReader class in conjunction with the XmlReaderSettings class can be used to verify that an XML file is well formed—in other words, that it follows all the syntax rules of an XML file. These classes don’t verify, though, that the XML file is valid.

A valid XML file needs the nodes to be in a specific order, number, and type. You can use the following two standards for checking validity:

Document type definition (DTD)

XML schema definition (XSD)

570 C H A P T E R 1 3 X M L

Validating an XML file requires a DTD or a XSD schema file. Monsters.dtd (see Listing 13-3) is an example of a DTD for the Monsters.xml file. DTD is an older method of validating XML and is becoming more or less obsolete. But since it is still used, I thought I’d show an example.

Listing 13-3. The Monsters.dtd File

<!ELEMENT MonsterList (Monster)+ >

<!ELEMENT Monster (Name, HitDice, Weapon+) > <!ELEMENT Name (#PCDATA) >

<!ELEMENT HitDice EMPTY >

<!ATTLIST HitDice Dice CDATA #IMPLIED Default CDATA #IMPLIED > <!ELEMENT Weapon (#PCDATA) >

<!ATTLIST Weapon Number CDATA #IMPLIED Damage CDATA #IMPLIED >

You will also need to make this minor change to the XML file so that it knows where to find the DTD file:

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE MonsterList SYSTEM "Monsters.dtd"> <!-- Monster List -->

The XSD is very different from the DTD. It is much more verbose, but since it is defined using XML it is a little easier to read. In addition, it is far more powerful. On the other hand, the application code is virtually the same for both standards, so we won’t go into the details of the schema definitions. But just to give you an idea of what a schema definition looks like, Listing 13-4 is the XSD equivalent to Listing 13-3, which incidentally was auto-generated by clicking in the XML create schema toolbar button while the Monsters.xml file was being displayed in the Visual Studio 2005 code window.

Listing 13-4. The Monsters.xsd File

<?xml version="1.0" encoding="utf-8"?> <xs:schema attributeFormDefault="unqualified"

elementFormDefault="qualified"

xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="MonsterList">

<xs:complexType>

<xs:sequence>

<xs:element maxOccurs="unbounded" name="Monster"> <xs:complexType>

<xs:sequence>

<xs:element name="Name" type="xs:string" /> <xs:element name="HitDice">

<xs:complexType>

<xs:attribute name="Dice" type="xs:string" use="required" />

<xs:attribute name="Default" type="xs:unsignedByte" use="required" />

</xs:complexType>

</xs:element>

C H A P T E R 1 3 X M L

571

<xs:element maxOccurs="unbounded" name="Weapon"> <xs:complexType>

<xs:simpleContent>

<xs:extension base="xs:string">

<xs:attribute name="Number" type="xs:unsignedByte" use="required" />

<xs:attribute name="Damage" type="xs:string" use="required" />

</xs:extension>

</xs:simpleContent>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:schema>

To verify an XML file, you need to add an instance of the XmlReaderSettings class within the Create() method of the XmlReader class. The XmlReaderSettings class basically extends the functionality of the XmlReader class by adding verification logic.

Note The XmlReaderSettings class extends features of the XmlReader besides those of validation, but these features are beyond the scope of this book.

The XmlReaderSettings class has a few properties and a method to extend the XmlReader class with validation support:

IgnoreComments is a Boolean value to specify whether validation should ignore comments. The default is false.

IgnoreWhiteSpace is a Boolean value to specify whether validation should ignore insignificant white space. The default is false.

ProhibitDtd is a Boolean value to specify whether DTD validation is prohibited. The default is true.

Reset() is a method that resets the instance of the XmlReaderSettings back to default values.

Schemas is an XmlSchemaSet containing the collection of schemas used for validation.

ValidationType is a ValidationType enumerator to which type of validation, DTD, Schema (XSD), or None, should be done. The default is None.

Caution If you want to validate using DTD, you must both set ProhibitDTD to false and set ValidationType to DTD.

572 C H A P T E R 1 3 X M L

Listing 13-5 shows in a minimal fashion how to validate an XML file with a DTD.

Listing 13-5. Validating the Monsters.xml File

using namespace System; using namespace System::Xml;

using namespace System::Xml::Schema;

ref class ValidateXML

{

public:

ValidateXML(String ^filename)

{

XmlReader ^vreader; try

{

XmlReaderSettings ^settings = gcnew XmlReaderSettings(); settings->ProhibitDtd = false;

settings->ValidationType = ValidationType::DTD; vreader = XmlReader::Create("Monsters.xml", settings);

while(vreader->Read())

{

// ... Process nodes just like XmlTextReader()

}

Console::WriteLine("Finished Processing");

}

catch (Exception ^e)

{

Console::WriteLine(e->Message);

}

finally

{

if (vreader->ReadState != ReadState::Closed)

{

vreader->Close();

}

}

}

};

void main()

{

gcnew ValidateXML("Monsters.xml");

}

As you can see, there isn’t much difference between implementing a simple XmlReader and a validated XmlReader. In fact, the only difference is that an instance of the XmlReaderSettings class is created and passed as a parameter to the XmlReader class’s Create() method.

When you run this on the Monsters.xml file listed earlier, “Finished Processing” displays on the console. To test that validation is happening, change the Easy Monster to have its HitDice element placed after the Weapon element, as shown in Listing 13-6.

C H A P T E R 1 3 X M L

573

Listing 13-6. Invalid Monsters.xml File

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE MonsterList SYSTEM "Monsters.dtd"> <!-- Monster List -->

<MonsterList>

<!-- Easy Monster --> <Monster>

<Name>Goblin</Name>

<Weapon Number="1" Damage="1d4">Dagger</Weapon> <HitDice Dice="1d8" Default="4" />

</Monster>

<!-- The rest of the document --> </MonsterList>

Now the program ValidateXML.exe will abort, as shown in Figure 13-2.

Figure 13-2. Aborting the Monsters.xml file

What happens if you want to handle the problems in the invalid XML file yourself, instead of just throwing the exception? You can override the exception being thrown by providing a handler to ValidationEventHandler of the XmlReaderSettings class. Within this handler, you can do whatever processing is necessary for the validation error.

ValidationEventHandler is triggered whenever a validation error occurs. The code for the handler is similar to all the other event handlers you’ve seen so far in this book. It takes two parameters: a pointer to an Object (which in this case you ignore), and a pointer to ValidationEventArgs. ValidationEventArgs provides in its properties information to tell you what caused the validation event to trigger.

Notice that you also need to import the System::Xml::Schema namespace:

using namespace System::Xml::Schema; ref class ValidateXML

{

public:

void ValidationHandler (Object ^sender, ValidationEventArgs ^vea)

{

Console::WriteLine(vea->Message);

}

//...the rest of class

};

Delegating the event handler follows the same process you’ve seen before:

XmlReaderSettings ^settings = gcnew XmlReaderSettings(); settings->ProhibitDtd = false;

settings->ValidationType = ValidationType::DTD; settings->ValidationEventHandler +=

gcnew ValidationEventHandler(this, &ValidateXML::ValidationHandler); vreader = XmlReader::Create("Monsters.xml", settings);

or

574 C H A P T E R 1 3 X M L

XmlSchemaSet^ sc = gcnew XmlSchemaSet;

sc->Add( L"urn:monster-schema", L"Monsters.xsd" );

XmlReaderSettings ^settings = gcnew XmlReaderSettings(); settings->ValidationType = ValidationType::Schema; settings->Schemas = sc;

settings->ValidationEventHandler +=

gcnew ValidationEventHandler(this, &ValidateXML::ValidationHandler); vreader = XmlReader::Create("Monsters.xml", settings);

Now when you execute the application, you get the same message displayed to the console, as that is the logic I placed in the handler, but the program continues on to the end of the file without an exception being thrown.

Writing a New XML Stream

There will come a time when you’ll need to generate some XML to be sent to some other application or stored off for later use by the current application. An easy way of doing this is through the XmlWriter class and optional XmlWriterSettings class.

Note You can also use XmlTextWriter, but Microsoft recommends that you use XmlWriter instead. The benefits are more or less the same as those for XmlReader, which we discussed earlier.

Just like its counterpart XmlReader, XmlWriter is an abstract class and you create an instance using its Create() method. You can also pass as an argument a settings class. The XmlWriterSettings class is primarily used to tell XmlWriter how to format the output of its XML stream. Here are some of the more common properties you will set:

Encoding is an Encoding enum class that represents the character encoding to use.

Indent is a Boolean value that represents whether to indent elements. The default value is false.

IndentChars is a String that represents what set of characters to use for indenting. This value is used when Indent is set to true.

NewLineChars is a String that represents what set of characters to use for a line break. This value is used when NormalizeNewLines is set to true.

NewLineHandling is a NewLineHandling enum class that represents whether the new lines are Entitize (preserve new line characters that would not be otherwise preserved by a normalizing

XmlReader), None (unchanged), or Replaced.

NewLineOnAttribute is a Boolean value that specifies whether to write attributes on a new line. The default value is false.

OmitXmlDeclaration is a Boolean value that specifies whether to omit the XML declaration. The default value is false, which means the declaration is written.

The XmlWriter class is implemented as a forward-only XML stream writer. There aren’t many commonly used properties when it comes to the XmlWriter class. Most likely you will only deal with a couple:

C H A P T E R 1 3 X M L

575

Settings, which returns the XmlWriterSettings associated with the XmlWriter.

WriteState, which is a WriteState enum class of the current state of the XmlWriter. Possible states are: Attribute value being written, Closed method was called, Content being written, Element start tag being written, Error, Prolog value being written, and Start (meaning a write method has yet to be called).

Instead of properties, the XmlWriter class depends on a number of methods. Some of the more common methods are as follows:

Close() closes the streams associated with the XmlWriter.

Create() creates an instance of XmlWriter.

Flush() flushes the write buffers.

WriteAttributes() writes all attributes at the current location.

WriteAttributeString() writes an attribute.

WriteBase64() encodes the specified binary bytes as Base64 and then writes them out.

WriteBinHex() encodes the specified binary bytes as BinHex and then writes them out.

WriteCharEntity() writes out a char entity for the specified Unicode character. For example, a © symbol would generate a char entity of ©.

WriteChars() writes out a text buffer at a time.

WriteComment() writes out a comment.

WriteDocType() writes out a DOCTYPE declaration.

WriteElementString() writes out an element.

WriteEndAttribute() writes out an end attribute, closing the previous WriteStartAttribute.

WriteEndDocument() writes out end attributes and elements for those that remain open and then closes the document.

WriteEndElement() writes out an empty element (if empty) or a full end element.

WriteEntityRef() writes out an entity reference.

WriteFullEndElement() writes out a full end element.

WriteName() writes out a valid XML name.

WriteNode() writes out everything from the XmlReader to the XmlWriter and advances the XmlReader to the next sibling.

WriteStartAttribute() writes out the start of an attribute.

WriteStartDocument() writes out the start of a document.

WriteStartElement() writes out the start tag of an element.

WriteString() writes out the specified string.

WriteValue() writes out a simple-typed value.

WriteWhitespace() writes out specified white space.

576 C H A P T E R 1 3 X M L

As you can see from the preceding lists, there is a write method for every type of node that you want to add to your output file. Therefore, the basic idea of writing an XML file using the XmlWriter class is to open the file, write out all the nodes of the file, and then close the file.

The example in Listing 13-7 shows how to create an XML monster file containing only a Goblin.

Listing 13-7. Programmatically Creating a Goblin

using namespace System; using namespace System::Xml;

void main()

{

XmlWriter ^writer; try

{

XmlWriterSettings ^settings = gcnew XmlWriterSettings(); settings->Indent = true;

settings->IndentChars = (" "); settings->NewLineOnAttributes = true;

writer = XmlWriter::Create("Goblin.xml", settings);

writer->WriteStartDocument();

writer->WriteStartElement("MonsterList");

writer->WriteComment("Program Generated Easy Monster"); writer->WriteStartElement("Monster");

writer->WriteStartElement("Name"); writer->WriteString("Goblin"); writer->WriteEndElement();

writer->WriteStartElement("HitDice"); writer->WriteAttributeString("Dice", "1d8"); writer->WriteAttributeString("Default", "4"); writer->WriteEndElement();

writer->WriteStartElement("Weapon"); writer->WriteAttributeString("Number", "1"); writer->WriteAttributeString("Damage", "1d4"); writer->WriteString("Dagger"); writer->WriteEndElement();

//The folling not needed with WriteEndDocument

//writer->WriteEndElement();

//writer->WriteEndElement();

writer->WriteEndDocument();

C H A P T E R 1 3 X M L

577

writer->Flush();

}

catch (Exception ^e)

{

Console::WriteLine("XML Writer Aborted -- {0}", e->Message);

}

finally

{

if (writer->WriteState != WriteState::Closed)

{

writer->Close();

}

}

}

This may seem like a lot of work to create just one monster in an XML file, but remember that all monsters have basically the same structure; therefore, you could create almost any number of Monster elements by removing the hard-coding and placing Weapon elements in a loop, as opposed to the expanded version shown in the preceding code. You, of course, also need some way of providing the monster information that you want placed in the XML file. (A random generator would be cool— tough to code, but cool.)

The Create() method of the XmlWriter class has several overloads. It can take as a parameter either a stream, filename, StringBuilder, TextWriter, or another XmlWriter. Along with each of these, Create() can also take an instance of an XmlWriterSettings class. I showed the constructor using a filename in the previous example. When using the filename, the constructor will automatically create the file or, if the filename exists, the constructor truncates it. In either case, you are writing to an empty file.

XmlWriter ^writer;

writer = XmlWriter::Create("Goblin.xml");

If you plan on allowing someone to read the generated XML, you might, as I stated earlier, want to consider passing to the Create() method an instance of the XmlWriterSettings class. In the previous example I used my favorite settings. First, I told XmlWriterSettings that I am going to indent the output with three spaces, instead of one long continuous stream of XML text:

XmlWriterSettings ^settings = gcnew XmlWriterSettings(); settings->Indent = true;

settings->IndentChars = (" ");

Then I told it to put each attribute on a new line:

settings->NewLineOnAttributes = true;

writer = XmlWriter::Create("Goblin.xml", settings);

Okay, now to actually write the XML, the first thing you need to do is start the document using the WriteStartDocument() method. This method adds the following standard XML header to the XML document:

<?xml version="1.0" encoding="utf-8"?>

Next, you simply write the XML document. You use the WriteStartElement(), WriteString(), and WriteEndElement() methods to add elements, and for attributes you use the WriteAttributeString()