In the
previous example, you didn’t actually change the
underlying document. In fact, there’s nothing there
that you couldn’t have done with an
XmlReader
. Unlike an XmlReader
,
however, the DOM allows you to change an existing XML document.
Suppose you decided to stop validating the inventory records. In
order to make this change, you would need to remove the
DOCTYPE
node from all of the XML files. How would
you go about doing this?
The short answer is
XmlNode.RemoveChild( )
. This method removes the
node passed in from the object tree. You can read in all the XML
files in the current directory, and remove the
XmlDocumentType
node. Then you can serialize the
file back out (with the extension .new
so you don’t overwrite
the original) and check that the DOCTYPE
node is
gone:
string currentDirectory = Environment.CurrentDirectory; string [ ] files = Directory.GetFiles(currentDirectory, "*.xml"); foreach (string file in files) { XmlDocument document = new XmlDocument( ); document.Load(file); XmlDocumentType documentType = document.DocumentType; document.RemoveChild(documentType); document.Save(file + ".new"); }
This
process can be repeated with any type of XmlNode
.
For example, you could remove the inventory element, leaving an empty
document, except for the XML declaration. Or you could use
RemoveAll( )
to remove everything in the document
entirely, while leaving the empty file in place:
document.Load(file); document.RemoveAll( );
If you remove the document element, the document is no longer
well-formed. XmlDocument.Save( )
will throw an
XmlException
.
A more common case, given our example, would be to change the quantity of a particular item in stock. If you look back at the purchase order DTD from Chapter 2, you can see that the item elements are identical. You could write a small program to read in the store inventory and a purchase order, and decrement the inventory by the number of items sold in the PO.
Let’s build the program, starting with a class
called SellItems
. To begin, because
you’re dealing with the same inventory file for all
of the purchase orders, you can just store it as an instance
variable:
private XmlDocument inventory;
In the Main( )
method, all you need do is
instantiate a new SellItems
object, passing the
list of purchase order files that appeared on the command line:
static void Main(string [ ] args) { new SellItems(args); }
The constructor creates the inventory XmlDocument
and loads it from a file:
private SellItems(string [ ] files) { inventory = new XmlDocument( ); inventory.Load("inventory.xml");
Next, loop through the purchase order file names, calling
SellItemsFromPoFile( )
for each one:
foreach (string filename in files) { SellItemsFromPoFile(filename); }
Finally, save the inventory document with all changes:
inventory.Save("inventory.xml"); }
The SellItemsFromPoFile( )
method will create and
load an individual purchase order from the list. For efficiency, each
purchase order XmlDocument
shares the same
XmlNameTable
with the others, and with the
inventory XmlDocument
:
private void SellItemsFromPoFile(string filename) { XmlDocument po = new XmlDocument(inventory.NameTable); po.Load(filename);
This
XPath expression selects each item
element from
the purchase order:
XmlNodeList elements = po.SelectNodes("//items/item");
This loop calls SellItemsFromElement( )
for each
item
element that the XPath expression returned:
foreach (XmlElement element in elements) { SellItemsFromElement(element); } }
Next is SellItemsFromElement( )
itself, the method
that actually decrements the inventory. First, you get the product
code and the quantity sold from the purchase order’s
item
element:
private void SellItemsFromElement(XmlElement poItem) { string productCode = poItem.GetAttribute("productCode"); int quantitySold = Int32.Parse( poItem.GetAttribute("quantity"));
Now, you search for the same product code in the inventory’s item elements. Again, XPath is discussed in the next chapter; for now, don’t worry too much about the XPath syntax:
string xPathExpression = "//items/item[@productCode='" + productCode + "']"; XmlElement inventoryItem = (XmlElement)inventory.SelectSingleNode(xPathExpression);
If the XPath expression does not return a single matching node, a
NullReferenceException
will be thrown. It might be
smart to wrap this call in a try...catch
block to
handle this better, and avoid abnormal termination of the program.
Here you’re getting the quantity attribute from the inventory document, subtracting from it the amount in the purchase order document, and setting the inventory document’s quantity attribute to the new decremented amount:
int quantity = Int32.Parse(inventoryItem.GetAttribute("quantity")); quantity -= quantitySold; inventoryItem.SetAttribute("quantity", quantity.ToString( )); }
And that’s it, a simple inventory maintenance
program. Granted, it’s not a good idea to keep your
inventory in a flat XML file; but if you think of the various ways
you can construct an XmlDocument
, you could
actually be reading XML from a relational database, or some sort of
web service, or almost anything you can imagine.
Example 5-3 shows the complete program.
using System; using System.Xml; public class SellItems { private XmlDocument inventory; static void Main(string [ ] args) { new SellItems(args); } private SellItems(string [ ] files) { XmlDocument inventory = new XmlDocument( ); inventory.Load("inventory.xml"); foreach (string filename in files) { SellItemsFromPoFile(filename); } inventory.Save("inventory.xml "); } private void SellItemsFromPoFile(string filename) { XmlDocument po = new XmlDocument(inventory.NameTable); po.Load(filename); XmlNodeList elements = po.SelectNodes("//items/item"); foreach (XmlElement element in elements) { SellItemsFromElement(element); } } private void SellItemsFromElement(XmlElement poItem) { string productCode = poItem.GetAttribute("productCode"); int quantitySold = Int32.Parse( poItem.GetAttribute("quantity")); string xPathExpression = "//items/item[@productCode='" + productCode + "']"; XmlElement inventoryItem = (XmlElement)inventory.SelectSingleNode(xPathExpression); int quantity = Int32.Parse(inventoryItem.GetAttribute("quantity")); quantity -= quantitySold; inventoryItemElement.SetAttribute("quantity", quantity.ToString( )); } }
18.191.195.111