XML node Challenge 3

teknovation
teknovation used Ask the Experts™
on
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

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Russ SuterSenior Software Developer

Commented:
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 SotoRetired
Distinguished Expert 2017

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

Commented:
@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.
Become a Microsoft Certified Solutions Expert

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

Fernando SotoRetired
Distinguished Expert 2017

Commented:
@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 Developer

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

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

Author

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 Architect

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

Author

Commented:
Yes sorry both - i want to be able to access specific xml tags in both addresses
Karrtik IyerSoftware Architect

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

Author

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 Architect

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

Author

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 Architect

Commented:
Can you share your XML that you used, because the XML that you gave me had a node called AddressName?

Author

Commented:
its the same xml, there is an addressname
Karrtik IyerSoftware Architect

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

Author

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.
Software Architect
Commented:
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

Author

Commented:
thanks so much it worked!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial