DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 17.10 Putting Together an XML Document

Problem

You have various pieces of a document in XML form that need to be put together to form a single XML document—this is the opposite of what was done in Recipe 17.9. In this case, you have received various pieces of an invoice in XML form. For example, one department sent the shipping information as an XML document, one sent the billing information in XML, and another sent invoice line items, also as an XML document. You need a way to put these XML pieces together to form a single XML invoice document.

Solution

In order to reconstitute the original invoice, we need to reverse the process used to create the pieces of the invoice using multiple XmlDocuments. There are three parts being sent back to us to help in reforming the original invoice XML: BillingEnvelope.xml, ShippingEnvelope.xml, and Fulfillment.xml. These are shown listed in the following sections:

BillingEnvelope XML
<BillingEnvelope invoiceDate="2003-10-05" invoiceNumber="INV-01">
    <billInfo>
        <name>Beerly Standing</name>
        <attn>Accounting</attn>
        <street>98 North Street</street>
        <city>Intox</city>
        <state>NH</state>
    </billInfo>
</BillingEnvelope>
ShippingEnvelope XML
<ShippingEnvelope invoiceDate="2003-10-05" invoiceNumber="INV-01">
    <shipInfo>
        <name>Beerly Standing</name>
        <attn>Receiving</attn>
        <street>47 South Street</street>
        <city>Intox</city>
        <state>NH</state>
    </shipInfo>
</ShippingEnvelope>
FulfillmentEnvelope XML
<FulfillmentEnvelope invoiceDate="2003-10-05" invoiceNumber="INV-01">
    <item partNum="98745">
        <productName>Brown Eyed Stout</productName>
        <quantity>12</quantity>
        <price>23.99</price>
        <shipDate>2003-12-20</shipDate>
    </item>
    <item partNum="34987">
        <productName>Diamond Pearl Lager</productName>
        <quantity>22</quantity>
        <price>35.98</price>
        <shipDate>2003-12-20</shipDate>
    </item>
    <item partNum="AK254">
        <productName>Job Site Ale</productName>
        <quantity>50</quantity>
        <price>12.56</price>
        <shipDate>2003-11-12</shipDate>
    </item>
</FulfillmentEnvelope>

To put these back together as a single invoice, we reverse the process we went through to break it apart, while inferring the invoice date and invoice number from the BillingEnvelope to help reestablish the invoice:

public static void ReceiveInvoice( )
{
    XmlDocument invoice = new XmlDocument( );
    XmlDocument billing = new XmlDocument( );
    XmlDocument shipping = new XmlDocument( );
    XmlDocument fulfillment = new XmlDocument( );

    // set up root invoice node
    XmlElement invoiceElement = invoice.CreateElement("Invoice");
    invoice.AppendChild(invoiceElement);
    
    // load the billing 
    billing.Load(@"..\..\BillingEnvelope.xml");
    // get the invoice date attribute
    XmlAttribute invDate =        (XmlAttribute)
      billing.DocumentElement.Attributes.GetNamedItem("invoiceDate");
    // get the invoice number attribute
    XmlAttribute invNum = (XmlAttribute)
      billing.DocumentElement.Attributes.GetNamedItem("invoiceNumber");
    // set up the invoice with this info
    invoice.DocumentElement.Attributes.SetNamedItem(invDate.Clone( ));
    invoice.DocumentElement.Attributes.SetNamedItem(invNum.Clone( ));
    // add the billInfo back in
    XmlNodeList billList = billing.SelectNodes("/BillingEnvelope/billInfo");
    foreach(XmlNode billInfo in billList)
    {
        invoice.DocumentElement.AppendChild(invoice.ImportNode(billInfo,true));
    }

    // load the shipping 
    shipping.Load(@"..\..\ShippingEnvelope.xml");
    // add the shipInfo back in
    XmlNodeList shipList = shipping.SelectNodes("/ShippingEnvelope/shipInfo");
    foreach(XmlNode shipInfo in shipList)
    {
        invoice.DocumentElement.AppendChild(invoice.ImportNode(shipInfo,true));
    }

    // load the items
    fulfillment.Load(@"..\..\FulfillmentEnvelope.xml");
    
    // Create an Items element in the Invoice to add these under
    XmlElement items = invoice.CreateElement("Items");

    // add the items back in under Items
    XmlNodeList itemList = fulfillment.SelectNodes("/FulfillmentEnvelope/item");
    foreach(XmlNode item in itemList)
    {
        items.AppendChild(invoice.ImportNode(item,true));
    }

    // add it in
    invoice.DocumentElement.AppendChild(items.Clone( ));

    // display Invoice XML
    Console.WriteLine("Invoice:\r\n{0}",invoice.OuterXml);

    // save our reconstitued invoice
    FileStream fileStream = File.Create(@"..\..\ReceivedInvoice.xml");
    byte [] bytes = Encoding.ASCII.GetBytes(invoice.OuterXml);
    fileStream.Write(bytes,0,bytes.Length);
    fileStream.Close( );
}

The code reconstitutes the invoice and saves it as ReceivedInvoice.xml, the contents of which are shown here:

<Invoice invoiceDate="2003-10-05" invoiceNumber="INV-01">
    <billInfo>
        <name>Beerly Standing</name>
        <attn>Accounting</attn>
        <street>98 North Street</street>
        <city>Intox</city>
        <state>NH</state>
    </billInfo>
    <shipInfo>
        <name>Beerly Standing</name>
        <attn>Receiving</attn>
        <street>47 South Street</street>
        <city>Intox</city>
        <state>NH</state>
    </shipInfo>
    <Items>
        <item partNum="98745">
            <productName>Brown Eyed Stout</productName>
            <quantity>12</quantity>
            <price>23.99</price>
            <shipDate>2003-12-20</shipDate>
        </item>
        <item partNum="34987">
            <productName>Diamond Pearl Lager</productName>
            <quantity>22</quantity>
            <price>35.98</price>
            <shipDate>2003-12-20</shipDate>
        </item>
        <item partNum="AK254">
            <productName>Job Site Ale</productName>
            <quantity>50</quantity>
            <price>12.56</price>
            <shipDate>2003-11-12</shipDate>
        </item>
    </Items>
</Invoice>

Discussion

In the Solution code, the first thing we did was to create a set of XmlDocuments for the Invoice, BillingEnvelope, ShippingEnvelope, and FulfillmentEnvelope. Then we created the new root Invoice element in the invoice XmlDocument:

XmlDocument invoice = new XmlDocument( );
XmlDocument billing = new XmlDocument( );
XmlDocument shipping = new XmlDocument( );
XmlDocument fulfillment = new XmlDocument( );

// set up root invoice node
XmlElement invoiceElement = invoice.CreateElement("Invoice");
invoice.AppendChild(invoiceElement);

Next, we processed the BillingEnvelope first, taking the invoice date and number from it and adding it to the Invoice. Then we added the billing information back in to the invoice:

// load the billing 
billing.Load(@"..\..\BillingEnvelope.xml");
// get the invoice date attribute
XmlAttribute invDate = (XmlAttribute)
 billing.DocumentElement.Attributes.GetNamedItem("invoiceDate");
// get the invoice number attribute
XmlAttribute invNum = (XmlAttribute)
 billing.DocumentElement.Attributes.GetNamedItem("invoiceNumber");
// set up the invoice with this info
invoice.DocumentElement.Attributes.SetNamedItem(invDate.Clone( ));
invoice.DocumentElement.Attributes.SetNamedItem(invNum.Clone( ));
// add the billInfo back in
XmlNodeList billList = billing.SelectNodes("/BillingEnvelope/billInfo");
foreach(XmlNode billInfo in billList)
{
    invoice.DocumentElement.AppendChild(invoice.ImportNode(billInfo,true));
}

The ShippingEnvelope came next:

// load the shipping 
shipping.Load(@"..\..\ShippingEnvelope.xml");
// add the shipInfo back in
XmlNodeList shipList = shipping.SelectNodes("/ShippingEnvelope/shipInfo");
foreach(XmlNode shipInfo in shipList)
{
    invoice.DocumentElement.AppendChild(invoice.ImportNode(shipInfo,true));
}

And finally, the items from the FulfillmentEnvelope were placed back under an Items element under the main Invoice element:

// load the items
fulfillment.Load(@"..\..\FulfillmentEnvelope.xml");
    
// Create an Items element in the Invoice to add these under
XmlElement items = invoice.CreateElement("Items");

// add the items back in under Items
XmlNodeList itemList = fulfillment.SelectNodes("/FulfillmentEnvelope/item");
foreach(XmlNode item in itemList)
{
    items.AppendChild(invoice.ImportNode(item,true));
}

// add it in
invoice.DocumentElement.AppendChild(items.Clone( ));

One item to be aware of when dealing with multiple XmlDocuments is that when you take a node from one XmlDocument, you cannot just append it as a child to a node in a different XmlDocument because the node has the context of the original XmlDocument. If you try to do this, you will get the following exception message:

The node to be inserted is from a different document context.

To fix this, use the XmlDocument.ImportNode method, which will make a copy (deep or shallow) of the node you are bringing over to the new XmlDocument, as shown, when we add the shipping information like so:

invoice.DocumentElement.AppendChild(invoice.ImportNode(shipInfo,true));

This line takes the shipInfo node, clones it deeply, then it appends it to the main invoice node.

See Also

See the "XmlDocument Class," "XmlElement Class," and "XmlAttribute Class" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section