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

teknovationAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Russ SuterSenior Software DeveloperCommented:
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.
Fernando SotoRetiredCommented:
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

Russ SuterSenior Software DeveloperCommented:
@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.
PMI ACP® Project Management

Prepare for the PMI Agile Certified Practitioner (PMI-ACP)® exam, which formally recognizes your knowledge of agile principles and your skill with agile techniques.

Fernando SotoRetiredCommented:
@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.
Russ SuterSenior Software DeveloperCommented:
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.
Karrtik IyerSoftware ArchitectCommented:
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:
1-6-2016-11-00-23-PM.png
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

teknovationAuthor Commented:
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
Karrtik IyerSoftware ArchitectCommented:
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

teknovationAuthor Commented:
Yes sorry both - i want to be able to access specific xml tags in both addresses
Karrtik IyerSoftware ArchitectCommented:
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: 1-8-2016-7-01-12-PM.png
teknovationAuthor Commented:
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?
Karrtik IyerSoftware ArchitectCommented:
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

teknovationAuthor Commented:
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

Karrtik IyerSoftware ArchitectCommented:
Can you share your XML that you used, because the XML that you gave me had a node called AddressName?
teknovationAuthor Commented:
its the same xml, there is an addressname
Karrtik IyerSoftware ArchitectCommented:
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".
 1-8-2016-9-59-42-PM.png1-8-2016-10-00-11-PM.png
teknovationAuthor Commented:
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.
Karrtik IyerSoftware ArchitectCommented:
It is because you have mixed two solutions of mine, one for loop which returns btaddelements (which contains the descendants already in the select part of linq query, select saddress.Descendants()) and on this btaddelements you are trying to search descendants which do not exist since the elements are themselves descendants. But  if you look at my solution to Select particluar element, the variable returned is btaddresselements contains only select saddress, hence descendants can be queried for which address name shall be returned properly.
Anyway we can achieve what you want  using btaddelements query results,  just modify your code  above to below, instead of searching in descendants, you directly search in the elements using where clause as shown in below.
  var address1 = btaddelements.Where( x => x.Name == ns + "Address1").FirstOrDefault();
            Console.WriteLine(address1.Name.LocalName + " : " + address1.Value); 
            Console.ReadLine();

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
teknovationAuthor Commented:
thanks so much it worked!
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
XML

From novice to tech pro — start learning today.