Link to home
Start Free TrialLog in
Avatar of teknovation
teknovation

asked on

XML node Challenge 3

All,

If there are two similar parent node or tag - which in this case is Address, the only difference is the first Address tag is meant for SHIP to address which is showned in the <AddressTypeCode> with value of "ST" and BILL TO address shown with "BT" value. How can I extract the tags based on the <AddressTypeCode> value?

If <AddressTypeCode> child tag in the <Address> parent tag equals ST= then show like this:

The ship to address is: Pepsi Distribution Center, ABC 123, Safeway 7th Street, Suite 1, Detroit, MI, 12345, USA
If <AddressTypeCode> child tag in the <Address> parent tag equals BT= then show like this:
The bill to address is: Main Headquarters, Attn: John, 147 Dupont Ave, Dock 123, Orlando, FL, 12345, USA

XML Code:
<?xml version="1.0" encoding="UTF-8"?>
<Orders xmlns="www.test-inc.com">
	<Order>
		<Header>
			<Address>
				<AddressTypeCode>ST</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressName>Pepsi Distribution Center</AddressName>
				<AddressAlternateName>ABC 123</AddressAlternateName>
				<Address1>Safeway 7th Street</Address1>
				<Address2>Suite 1</Address2>
				<City>Detroit</City>
				<State>MI</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
			</Address>
			<Address>
				<AddressTypeCode>BT</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressLocationNumber>15513432</AddressLocationNumber>
				<AddressName>Main Headquarters</AddressName>
				<AddressAlternateName>Attn: John</AddressAlternateName>
				<Address1>147 Dupont Ave</Address1>
				<Address2>Dock 123</Address2>
				<City>Orlando</City>
				<State>FL</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
				<Contact>
					<ContactTypeCode>BD</ContactTypeCode>
					<ContactName>Jane Doe</ContactName>
					<PrimaryPhone>111-222-3333</PrimaryPhone>
					<PrimaryFax>111-222-3333</PrimaryFax>
					<PrimaryEmail>buyer@TEST.com</PrimaryEmail>
				</Contact>
			</Address>
		</Header>

Open in new window



C# code: My attempt to write it
 // Ship to Address  
                        var addresses = xdoc.XPathSelectElements("//testinc:Address", nsmgr);//Get all the address childs : 2 in this case 
                        List<string> listofAddresses = new List<string>();//List to store the addresses 
                        foreach (XElement address in addresses)//For each line item
                        { 
                             //something here?
                        }

Open in new window

Avatar of Russ Suter
Russ Suter

You have a good start. Just run a logic check inside the foreach statement. Check to see if an element <AddressTypeCode> exists as a child of the XElement "address" and if so, what its value is. You can then act accordingly.
Hi teknovation;

See if this will meet your needs.
// Load Document
XDocument xdoc = XDocument.Load("Path and file name of the XML doc");
// Get a ref to the XML Namespace
XNamespace ns = xdoc.Root.Name.Namespace;

// Get the Ship to Address  
var addresses = xdoc.Root.Descendants(ns + "Address").ToList();

// If you want the Ship To address it will be element 0 and the Bill To address is element 1
// XDocuments are process in document order 
int idx = 0;  // 0 = ST and 1 = BT
// Get the proper address
XElement addInfo = addresses[idx];
Console.WriteLine("{0} {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8} ",
    (idx == 0) ? "The ship to address is:" : " The bill to address is:",
    addInfo.Element(ns + "AddressName").Value, 
    addInfo.Element(ns + "AddressAlternateName").Value,
    addInfo.Element(ns + "Address1").Value,
    addInfo.Element(ns + "Address2").Value,
    addInfo.Element(ns + "City").Value,
    addInfo.Element(ns + "State").Value,
    addInfo.Element(ns + "PostalCode").Value,
    addInfo.Element(ns + "Country").Value
    );

Open in new window

@Fernando - Your code assumes that the ship to address will come first. There is absolutely no guarantee of that in the XML. You will need to look at the actual element value to correctly determine if it's a bill to or ship to address.
@Russ - The question states, "the only difference is the first Address tag is meant for SHIP to address which is showned in the <AddressTypeCode> with value of "ST" and BILL TO address shown with "BT" value", I have taken that to mean that the first of the two nodes is Ship to.
In this case yes but you can't guarantee the input. The safer approach would be to check the item value. The XML would still be 100% valid if the two address elements appeared in the reverse order but the output would be wrong. Coding to assumptions is never a good idea.
Something like below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Q28909699_TEKNOVATION_CleanCode
{
    class Program
    {
        static void Main(string[] args)
        {
            string filepath = @"E:\tsip\codeblocks_examples\expertsexchange\TotalEESolution\Q28909699_TEKNOVATION_CleanCode\TestAddress.xml";
            XDocument xdoc = XDocument.Load(filepath);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
            XNamespace ns = "www.test-inc.com";            
            var stadd = (from saddress in xdoc.Descendants(ns + "Address")
                        where saddress.Element(ns + "AddressTypeCode").Value == "ST"
                        select new
                        {
                            Address = string.Join(",", saddress.Elements().Where(y => y.Name != ns + "AddressTypeCode" && y.Name != ns + "Contact").Select(z => z.Value).ToList())
                        });
            var btadd = (from saddress in xdoc.Descendants(ns + "Address")
                        where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                        select new
                        {
                            Address = string.Join(",", saddress.Elements().Where(y => y.Name != ns + "AddressTypeCode" && y.Name != ns + "Contact").Select(z => z.Value).ToList())
                        });
            //Assumption is that there shall always be one ship to address and bill to address at any case, else First() might fail.
            Console.WriteLine("The ship to address is: {0}", stadd.AsEnumerable().First().Address);
            Console.WriteLine("The bill to address is: {0}", btadd.AsEnumerable().First().Address);
            Console.ReadLine();           
        }
    }
}

Open in new window

Output:
User generated image
XML Used:
<?xml version="1.0" encoding="UTF-8"?>
<Orders xmlns="www.test-inc.com">
	<Order>
		<Header>
			<Address>
				<AddressTypeCode>ST</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressName>Pepsi Distribution Center</AddressName>
				<AddressAlternateName>ABC 123</AddressAlternateName>
				<Address1>Safeway 7th Street</Address1>
				<Address2>Suite 1</Address2>
				<City>Detroit</City>
				<State>MI</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
			</Address>
			<Address>
				<AddressTypeCode>BT</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressLocationNumber>15513432</AddressLocationNumber>
				<AddressName>Main Headquarters</AddressName>
				<AddressAlternateName>Attn: John</AddressAlternateName>
				<Address1>147 Dupont Ave</Address1>
				<Address2>Dock 123</Address2>
				<City>Orlando</City>
				<State>FL</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
				<Contact>
					<ContactTypeCode>BD</ContactTypeCode>
					<ContactName>Jane Doe</ContactName>
					<PrimaryPhone>111-222-3333</PrimaryPhone>
					<PrimaryFax>111-222-3333</PrimaryFax>
					<PrimaryEmail>buyer@TEST.com</PrimaryEmail>
				</Contact>
			</Address>
		</Header>
    </Order>      
    </Orders>
                                

Open in new window

Avatar of teknovation

ASKER

This is great Karrtik - but is there a way to just pull out 1 field at a time in this format?

I just quickly mock this up for the purpose of example, so the data isnt exactly matching the file output. But just to give you an idea on what I need.

The ship to address is: Main Headquarters
The Location Code Qualifier is: 51
The AddressAlternateName is: ABC 123  
The Address Name is Safeway 7th Street
What about contact details in Bill to address, do you want that to be also printed, and if it would help given an XML (since you said the first one in the question is just a mock up) what would be the expected output? In that contact details there is a contact code type as well as "BD". So before I suggest a modified version of my solution, wanted to what is  your exact output requirement given an XML?
<Contact>
					<ContactTypeCode>BD</ContactTypeCode>
					<ContactName>Jane Doe</ContactName>
					<PrimaryPhone>111-222-3333</PrimaryPhone>
					<PrimaryFax>111-222-3333</PrimaryFax>
					<PrimaryEmail>buyer@TEST.com</PrimaryEmail>
				</Contact>

Open in new window

Yes sorry both - i want to be able to access specific xml tags in both addresses
Something like below, which produces the below shown output, the previous code to join all the elements to one string is also present, so if you do not want it you can remove that.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Q28909699_TEKNOVATION_CleanCode
{
    class Program
    {
        static void Main(string[] args)
        {
            string filepath = @"E:\tsip\codeblocks_examples\expertsexchange\TotalEESolution\Q28909699_TEKNOVATION_CleanCode\TestAddress.xml";
            XDocument xdoc = XDocument.Load(filepath);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
            XNamespace ns = "www.test-inc.com";
            nsmgr.AddNamespace("testinc", "www.test-inc.com");
            var stadd = (from saddress in xdoc.Descendants(ns + "Address")
                        where saddress.Element(ns + "AddressTypeCode").Value == "ST"
                        select new
                        {
                            Address = string.Join(",", saddress.Elements().Where(y => y.Name != ns + "AddressTypeCode" && y.Name != ns + "Contact").Select(z => z.Value).ToList())
                        });
            var btadd = (from saddress in xdoc.Descendants(ns + "Address")
                        where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                        select new
                        {
                            Address = string.Join(",", saddress.Elements().Where(y => y.Name != ns + "AddressTypeCode" && y.Name != ns + "Contact").Select(z => z.Value).ToList())
                        });
            //Assumption is that there shall always be one ship to address and bill to address at any case, else First() might fail
            Console.WriteLine("The ship to address is: {0}", stadd.AsEnumerable().First().Address);
            Console.WriteLine("The bill to address is: {0}", btadd.AsEnumerable().First().Address);

            var staddelements = (from saddress in xdoc.Descendants(ns + "Address")
                        where saddress.Element(ns + "AddressTypeCode").Value == "ST"
                        select saddress.Descendants()).FirstOrDefault();
            Console.WriteLine();
            Console.WriteLine("*****************************Shipping Address Start**********************************");
            foreach (var line in staddelements){
                Console.WriteLine(line.Name.LocalName + " : " + line.Value);

            }
            Console.WriteLine("*****************************Shipping Address End**********************************");
            Console.WriteLine();
            Console.WriteLine("*****************************Billing Address Start**********************************");
            var btaddelements = (from saddress in xdoc.Descendants(ns + "Address")
                         where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                         select saddress.Descendants()).FirstOrDefault();

            foreach (var line in btaddelements)
            {
                Console.WriteLine(line.Name.LocalName + " : " + line.Value);
            }
            Console.WriteLine("*****************************Billing Address End**********************************");
            Console.ReadLine();           
        }
    }
}

Open in new window

XML File
<?xml version="1.0" encoding="UTF-8"?>
<Orders xmlns="www.test-inc.com">
	<Order>
		<Header>
			<Address>
				<AddressTypeCode>ST</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressName>Pepsi Distribution Center</AddressName>
				<AddressAlternateName>ABC 123</AddressAlternateName>
				<Address1>Safeway 7th Street</Address1>
				<Address2>Suite 1</Address2>
				<City>Detroit</City>
				<State>MI</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
			</Address>
			<Address>
				<AddressTypeCode>BT</AddressTypeCode>
				<LocationCodeQualifier>51</LocationCodeQualifier>
				<AddressLocationNumber>15513432</AddressLocationNumber>
				<AddressName>Main Headquarters</AddressName>
				<AddressAlternateName>Attn: John</AddressAlternateName>
				<Address1>147 Dupont Ave</Address1>
				<Address2>Dock 123</Address2>
				<City>Orlando</City>
				<State>FL</State>
				<PostalCode>12345</PostalCode>
				<Country>USA</Country>
				<Contact>
					<ContactTypeCode>BD</ContactTypeCode>
					<ContactName>Jane Doe</ContactName>
					<PrimaryPhone>111-222-3333</PrimaryPhone>
					<PrimaryFax>111-222-3333</PrimaryFax>
					<PrimaryEmail>buyer@TEST.com</PrimaryEmail>
				</Contact>
			</Address>
		</Header>
    </Order>      
    </Orders>
                                

Open in new window

Output: User generated image
wow this works - but far more advance that I wanted.

How can I just say pull one node from the Address child xml nodes?

Instead of doing a for each, I need to just grab say the "Contact Name" or "primary phone" value?
Something like below.
            var btaddreselements = (from saddress in xdoc.Descendants(ns + "Address")
                                 where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                                 select saddress).FirstOrDefault();
            //Address Name
            var addressname = btaddreselements.Descendants(ns + "AddressName").FirstOrDefault();
            Console.WriteLine(addressname.Name.LocalName + " : " + addressname.Value);
            //Contact Name
            var contactName = btaddreselements.Descendants(ns + "ContactName").FirstOrDefault();
            Console.WriteLine(contactName.Name.LocalName + " : " + contactName.Value);
            //PrimaryPhone
            var primaryphone = btaddreselements.Descendants(ns + "PrimaryPhone").FirstOrDefault();
            Console.WriteLine(primaryphone.Name.LocalName + " : " + primaryphone.Value);

Open in new window

hmm the address name is null.    

   var staddelements = (from saddress in xdoc.Descendants(ns + "Address")
                                             where saddress.Element(ns + "AddressTypeCode").Value == "ST"
                                             select saddress.Descendants()).FirstOrDefault();  
                       
                       
                        var btaddelements = (from saddress in xdoc.Descendants(ns + "Address")
                                             where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                                             select saddress.Descendants()).FirstOrDefault();

                        var addressname = btaddelements.Descendants(ns + "AddressName").FirstOrDefault(); 
                        Console.WriteLine(addressname.Name.LocalName + " : " + addressname.Value);

Open in new window

Can you share your XML that you used, because the XML that you gave me had a node called AddressName?
its the same xml, there is an addressname
I have posted the XML that I used, and using that the above code, here is the output I get for AddressName. Please check your namespace ns and see what it is set to, based on my XML I have set it to "www.test-inc.com".
 User generated imageUser generated image
This strange:

         var staddelements = (from saddress in xdoc.Descendants(ns + "Address")
                                             where saddress.Element(ns + "AddressTypeCode").Value == "ST"
                                             select saddress.Descendants()).FirstOrDefault();  
                       
                       
                        var btaddelements = (from saddress in xdoc.Descendants(ns + "Address")
                                             where saddress.Element(ns + "AddressTypeCode").Value == "BT"
                                             select saddress.Descendants()).FirstOrDefault();
                           

                            foreach (var line in btaddelements)
                            {
                                Console.WriteLine(line.Name.LocalName + " : " + line.Value);
                            }
                             var addressname = btaddelements.Descendants(ns + "Address1").FirstOrDefault();
Console.WriteLine(addressname.Name.LocalName + " : " + addressname.Value); ;


This whole line of code produces what I want except the very last line where it's bolded the var addressname is NULL. It produces the line by line item as shown for the foreach statement.
ASKER CERTIFIED SOLUTION
Avatar of Karrtik Iyer
Karrtik Iyer
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
thanks so much it worked!