Get XML node value when previous node value conditions are true (without looping)

228 Views Asked by At

Sample XML -

<?xml version="1.0"?>
<Root>
  <PhoneType dataType="string">
    <Value>CELL</Value>
  </PhoneType>
  <PhonePrimaryYn dataType="string">
    <Value>Y</Value>
  </PhonePrimaryYn>
  <PhoneNumber dataType="string">
    <Value>555-555-5554</Value>
  </PhoneNumber>
  <PhonePrimaryYn dataType="string">
    <Value>Y</Value>
  </PhonePrimaryYn>
  <PhoneType dataType="string">
    <Value>HOME</Value>
  </PhoneType>
  <PhoneNumber dataType="string">
    <Value>555-555-5555</Value>
  </PhoneNumber>    
</Root>

Without looping through each nodelist, can someone tell me (either with LINQ-to-XML or by other means) how I can perform the following?

In the XML sample you see two groups of 'PhoneType', 'PhonePrimaryYn', and 'PhoneNumber' type code groups. The first group of three relate to each other. The second group of three relate to each other as well, and so on.

Lets say I want to know what the cell phone number is.

I get that cell phone number when the 'PhoneType' 'Value' is 'CELL', the 'PhonePrimaryYn' 'Value' is 'Y', I get that 'PhoneNumber' 'Value' of '555-555-5554'. You get the idea hopefully.

I'd like to know if it is possible to get that 'PhoneNumber' value (cell phone for example) without having to loop through each nodelist group of the particular type.

Do anyone have any ideas?

2

There are 2 best solutions below

3
On BEST ANSWER

UPDATE

Using an XDocument vs an XmlDocument, I believe this does what you're asking without using loops.

This is dependent on the elements being in the order of

<PhoneType> <PhonePrimaryYN> <PhoneNumber>

string xml = "<?xml version=\"1.0\"?>" +
    "<Root>" + 
    "  <PhoneType dataType=\"string\">" + 
    "    <Value>CELL</Value>" + 
    "  </PhoneType>" + 
    "  <PhonePrimaryYn dataType=\"string\">" + 
    "    <Value>Y</Value>" + 
    "  </PhonePrimaryYn>" + 
    "  <PhoneNumber dataType=\"string\">" + 
    "    <Value>555-555-5554</Value>" + 
    "  </PhoneNumber>" + 
    "  <PhonePrimaryYn dataType=\"string\">" + 
    "    <Value>Y</Value>" + 
    "  </PhonePrimaryYn>" + 
    "  <PhoneType dataType=\"string\">" + 
    "    <Value>HOME</Value>" + 
    "  </PhoneType>" + 
    "  <PhoneNumber dataType=\"string\">" + 
    "    <Value>555-555-5555</Value>" + 
    "  </PhoneNumber>    " +
    "  <PhoneType dataType=\"string\">" +
    "    <Value>CELL</Value>" +
    "  </PhoneType>" +
    "  <PhonePrimaryYn dataType=\"string\">" +
    "    <Value>Y</Value>" +
    "  </PhonePrimaryYn>" +
    "  <PhoneNumber dataType=\"string\">" +
    "    <Value>555-555-9999</Value>" +
    "  </PhoneNumber>" + 
    "</Root>";

XDocument xDoc = XDocument.Parse(xml);
if (xDoc.Root != null)
{
    var tmp = (from item in xDoc.Root.Descendants()
                where item.Name == "PhoneType" && item.Value == "CELL"
                select new
                            {
                                PhoneNumber = item.NextNode.NextNode
                            }).ToList();

    for (int i = 0; i < tmp.Count; i++)
    {
        Console.WriteLine(((XElement)tmp[i].PhoneNumber).Value);
    }
}

Results:

555-555-5554
555-555-9999

OLD ANSWER

When I loaded your sample XML into an XmlDocument I saw that it's InnerText property contained the following:

CELLY555-555-5554YHOME555-555-5555

From here, I thought Regex would be a good method for extracting CELL numbers using the pattern:

"CELL[NY](\\d{3}-\\d{3}-\\d{4})"

The pattern looks for the word "CELL", followed by a 'N' or 'Y', then the phone number in the format ###-###-#####. The phone number is in a capture group and if a match is found it can be accessed like this following example illustrates.

I added another CELL entry to show that you can get all CELL numbers in your XML. So the InnerText property, of the XmlDocument, now looks like

CELLY555-555-5554YHOME555-555-5555CELLY555-555-9999

XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("<?xml version=\"1.0\"?>" +
    "<Root>" + 
    "  <PhoneType dataType=\"string\">" + 
    "    <Value>CELL</Value>" + 
    "  </PhoneType>" + 
    "  <PhonePrimaryYn dataType=\"string\">" + 
    "    <Value>Y</Value>" + 
    "  </PhonePrimaryYn>" + 
    "  <PhoneNumber dataType=\"string\">" + 
    "    <Value>555-555-5554</Value>" + 
    "  </PhoneNumber>" + 
    "  <PhonePrimaryYn dataType=\"string\">" + 
    "    <Value>Y</Value>" + 
    "  </PhonePrimaryYn>" + 
    "  <PhoneType dataType=\"string\">" + 
    "    <Value>HOME</Value>" + 
    "  </PhoneType>" + 
    "  <PhoneNumber dataType=\"string\">" + 
    "    <Value>555-555-5555</Value>" + 
    "  </PhoneNumber>    " +
    "  <PhoneType dataType=\"string\">" +
    "    <Value>CELL</Value>" +
    "  </PhoneType>" +
    "  <PhonePrimaryYn dataType=\"string\">" +
    "    <Value>Y</Value>" +
    "  </PhonePrimaryYn>" +
    "  <PhoneNumber dataType=\"string\">" +
    "    <Value>555-555-9999</Value>" +
    "  </PhoneNumber>" + 
    "</Root>");

Match match = Regex.Match(xmlDocument.InnerText, "CELL[NY](\\d{3}-\\d{3}-\\d{4})");
while (match.Success)
{
    Console.WriteLine(match.Groups[1]);
    match = match.NextMatch();
}

Results:

555-555-5554
555-555-9999
1
On

Personally I would change how the XML works so you have all the information for one phone number in one block like this

<phoneStructure>
   <type>CELL</type>
   <value>0123</value>
   <primary>false</primary>
</phoneStructure>

Then you can use this XPath to select the entire phone structure where it is a cell phone or change it to primary and then read the value from there.

//PhoneStructure[Type='Cell']

If you need any more help with this let me know.