Loop through multiple subnodes in XML - c#

<Sections>
<Classes>
<Class>VI</Class>
<Class>VII</Class>
</Classes>
<Students>
<Student>abc</Student>
<Student>def</Student>
</Students>
</Sections>
I have to loop through Classes to get 'Class' into an array of strings. I have to also loop through 'Students' to get 'Student' put into an array of strings.
XDocument doc.Load("File.xml");
string str1;
foreach(XElement mainLoop in doc.Descendants("Sections"))
{
foreach(XElement classLoop in mainLoop.Descendants("Classes"))
str1 = classLoop.Element("Class").Value +",";
//Also get Student value
}
is not working to get all the classes. Also, I need to rewrite this without using LINQ to XML, i.e using XmlNodeList and XmlNodes.
XmlDocument doc1 = new XmlDocument();
doc1.Load("File.xml");
foreach(XmlNode mainLoop in doc.SelectNodes("Sections")) ??
Not sure how to go about it.

The XPath is straightforward. To get the results into an array you can either use LINQ or a regular loop.
var classNodes = doc.SelectNodes("/Sections/Classes/Class");
// LINQ approach
string[] classes = classNodes.Cast<XmlNode>()
.Select(n => n.InnerText)
.ToArray();
var studentNodes = doc.SelectNodes("/Sections/Students/Student");
// traditional approach
string[] students = new string[studentNodes.Count];
for (int i = 0; i < studentNodes.Count; i++)
{
students[i] = studentNodes[i].InnerText;
}

Not sure about rewriting it for XmlNodes but for your Classes and Students you can simply:
XDocument doc.Load("File.xml");
foreach(XElement c in doc.Descendants("Class"))
{
// do something with c.Value;
}
foreach(XElement s in doc.Descendants("Student"))
{
// do something with s.Value;
}

With LINQ to XML:
XDocument doc = XDocument.Load("file.xml");
var classNodes = doc.Elements("Sections").Elements("Classes").Elements("Class");
StringBuilder result = new StringBuilder();
foreach( var c in classNodes )
result.Append(c.Value).Append(",");
With XPath:
XmlDocument doc = new XmlDocument();
doc.Load("file.xml");
var classNodes = doc.SelectNodes("/Sections/Classes/Class/text()");
StringBuilder result = new StringBuilder();
foreach( XmlNode c in classNodes )
result.Append(c.Value).Append(",");

Related

iterating XmlDocument with xpath

How do we iterate an XmlDocument using an xpath?
I'm attempting to return a list of nodes by xpath:
public static List<string> Filter(string xpath, string input, string ns, string nsUrl)
{
var bytes = Encoding.UTF8.GetBytes(input); //i believe this unescapes the string
var stream = new MemoryStream(bytes);
var doc = new XmlDocument();
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace(ns, nsUrl);
var links = new List<string>();
var nodes = doc.SelectNodes(xpath, namespaceManager);
using (var reader = new XmlTextReader(stream))
{
reader.Namespaces = false;
doc.Load(reader);
}
foreach (XmlNode node in nodes)
{
if (IsNullOrWhiteSpace(node.InnerText))
{
continue;
}
links.Add(node.InnerText);
}
return links;
}
however, the count is always 0 !
I'm using this xpath. notice how i am using only 1 namespace:
/ns0:Visit/ns0:DocumentInterface/ns0:Documents/ns0:Document/ns0:BinaryData
The header of the file looks like this:
<ns0:Visit xmlns:ns0="http://NameSpace.ExternalSchemas.Patient"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I'm certain that I am using the right xpath because I tested it against my payload:
I'm calling the function this way:
var links = Filter(xpath, xml, "ns0", "http://NameSpace.ExternalSchemas.Patient");
How do we iterate an XmlDocument using an xpath? Perhaps the XmlDocument should be an XDocument instead?

Extracting XML tags values

I have a list of XML files that I need to extract 3 values from each file.
The XML looks somewhat like :
<ClinicalDocument xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" moodCode="EVN" xmlns="urn:hl7-org:v3">
<title>Summary</title>
<recordTarget>
<patientRole>
<patient>
<name>
<given>John</given>
<given>S</given>
<family>Doe</family>
</name>
<birthTime value="19480503" />
I'm trying to extract given name, family name and birth time.
Initially I'm trying to print out the values using:
XmlDocument doc2 = new XmlDocument();
doc2.Load(#"Z:\\DATA\\file.XML");
XmlElement root = doc2.DocumentElement;
XmlNodeList list = root.GetElementsByTagName("name");
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(list.Item(i).Value);
}
I'm not getting any value printed, but when I debug and check the inner values of "list" I can see what I need from that tag.
How can I extract the needed information?
Your code and all other answers ignore the default namespace xmlns="urn:hl7-org:v3"
I find Linq2Xml easier to use, so I'll post an answer using it..
var xDoc = XDocument.Load(filename);
var #namespace = "urn:hl7-org:v3";
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xDoc.CreateNavigator().NameTable);
namespaceManager.AddNamespace("ns", #namespace);
XNamespace ns = #namespace;
var names = xDoc.XPathSelectElements("//ns:patient/ns:name", namespaceManager).ToList();
var list = names.Select(p => new
{
Given = string.Join(", ", p.Elements(ns + "given").Select(x => (string)x)),
Family = (string)p.Element(ns + "family"),
BirthTime = new DateTime(1970,1,1).AddSeconds( (int)p.Parent.Element(ns + "birthTime").Attribute("value"))
})
.ToList();
Try this instead:
XmlDocument doc2 = new XmlDocument();
doc2.Load(#"Path\To\XmlFile.xml");
XmlElement root = doc2.DocumentElement;
XmlNodeList list = root.GetElementsByTagName("name");
var names = list[0].ChildNodes;
for (int i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i].InnerText);
}
Output:
John
S
Doe
There are 2 issues with your code:
The first being that you were iterating around the name element, which only has a Count of 1 (as there is only one of these). That's why I included list[0],ChildNodes, to get all the children of the name element (given, given and family).
To retrieve the text inside each element, ("John", "S", "Doe"), you should use InnerText instead of Value
It's not clear from your example XML if there is only ever one <name> element or if there could be multiple. The following assumes there might be multiple. It also grabs the birthdate.
for (int i = 0; i < list.Count; i++)
{
var xmlNode = list.Item(i).FirstChild;
while (xmlNode != null)
{
Console.WriteLine(xmlNode.InnerText);
xmlNode = xmlNode.NextSibling;
}
}
XmlNodeList birthDates = root.GetElementsByTagName("birthTime");
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(birthDates[i].Attributes["value"].Value);
}
If there are multiple <patient> elements in your xml you could do:
using System;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
class Program
{
static void Main()
{
var doc = XDocument.Load("a.xml");
var nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("x", "urn:hl7-org:v3");
var patients = doc.XPathSelectElements("//x:patient", nsm);
foreach (var patient in patients)
{
Console.WriteLine(patient.XPathSelectElement("./x:name/x:given[1]", nsm).Value);
Console.WriteLine(patient.XPathSelectElement("./x:name/x:given[2]", nsm).Value);
Console.WriteLine(patient.XPathSelectElement("./x:name/x:family", nsm).Value);
Console.WriteLine(patient.XPathSelectElement("./x:birthTime", nsm).Attribute("value").Value);
}
}
}
Why do you need to add the name space explicitly even if it's a default name space in the xml? see: this answer

Can't get attribute value with C# XPath

I've spent so much time with this already, still can't get the value of NTE attribute. Can someone please help?
C#
StreamReader sr = new StreamReader(resp.GetResponseStream());
XPathDocument xmlDoc = new XPathDocument(sr); // holds xml document
XPathNavigator xmlNav = xmlDoc.CreateNavigator(); //evaluates XPath expressions
XPathNodeIterator node = xmlNav.Select("/DATA2SC/CALL");
string dne = xmlNav.GetAttribute("NTE", "");
Console.WriteLine(dne);
sr.Close();
XML
<?xml version="1.0"?>
<DATA2SC PIN="00000">
<CALL
TR_NUM="00000001"
STATUS="WAITING_FOR_APPROVAL"
NTE="$15.00">
<PROBLEM>
Text
</PROBLEM>
</CALL>
</DATA2SC>
Can you try this code please ?
I already check and it's work
StreamReader sr = new StreamReader("c:\\x.xml");
XPathDocument xmlDoc = new XPathDocument(sr); // holds xml document
XPathNavigator xmlNav = xmlDoc.CreateNavigator(); //evaluates XPath expressions
var node = xmlNav.SelectSingleNode("/DATA2SC/CALL");
string dne = node.GetAttribute("NTE", "");
Console.WriteLine(dne);
OR
XDocument docXmlWorld = XDocument.Load("c:\\x.xml");
foreach (var node1 in docXmlWorld.Descendants("DATA2SC"))
{
foreach (var node2 in node1.Descendants("CALL"))
{
string dne = node2.Attribute("NTE").Value;
Console.Out.WriteLine(dne);
}
}
Or you can do like this too:
XDocument docXmlWorld = XDocument.Load("c:\\x.xml");
//Get the first child => [DATA2SC]
XElement elementNodeDATA2SC = docXmlWorld.Element("DATA2SC");
//Get the first child => [CALL]
XElement elementNodeCALL = elementNodeDATA2SC.Element("CALL");
//Get the attribute NTE from [CALL] node
string dne = elementNodeCALL.Attribute("NTE").Value;
Console.Out.WriteLine(dne);
The Select method, returns a collection of all nodes with the specified XPath.
You can use SelectSingleNode, to select the first node.
var node = xmlNav.SelectSingleNode("/DATA2SC/CALL");
string dne = node.GetAttribute("NTE", "");

XDocument XPath + Regex element locator

Is there an equivalent XmlDocument node locator for Elements in XDocument?
private const string InvalidDateTest =
"[text() = \"0000-00-00\" or text() = \" - - \" or text() = \"- - \"]";
xmlDocument.SelectNodes("//DeterminedDate/Value" + InvalidDateTest);
sauced it out myself:
using System.Xml.XPath;
xDocument.XPathSelectElements("//DeterminedDate/Value" + InvalidDateTest);
XDocument + Xpath sample :
using(var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable) xmlFile.XPathEvaluate("/REETA/AFFIDAVIT/COUNTY_NAME");
foreach(var band in query.Cast < XElement > ())
{
Console.WriteLine(band.Value);
}
xmlFile.Save("books.xml");
}
To discriminate the nodes you need to simply chain extensions to the descendant call which will filter out what is needed:
string Data = #"<?xml version=""1.0""?>
<Notifications>
<Alerts>
<Alert>1</Alert>
<Alert>2</Alert>
<Alert>3</Alert>
</Alerts>
</Notifications>";
XDocument.Parse(Data)
.Descendants("Alert")
.Where (node => int.Parse(node.Value) > 1 && int.Parse(node.Value) < 3)
.ToList()
.ForEach(al => Console.WriteLine ( al.Value ) ); // 2 is the result

Enumerating Linq.Xelement

How to adjust this code to work when RESPONSE is no more string but Linq.Xelement?
String response = "anyxml data";
XmlDocument xmlDocument = LoadXMLDocument(response);
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("fql_query_response");
if (nodeList != null && nodeList.Count > 0)
{
if (nodeList[0].HasChildNodes)
{
XmlNodeList results = xmlDocument.GetElementsByTagName("event_member");
Dictionary<string, EventUser> eventUserDict = new Dictionary<string, EventUser>();
foreach (XmlNode node in results)
{
myuids.Add(Int64.Parse(node.FirstChild.InnerText));
}
}
Do you mean you want to create an XmlDocument from an XElement?
The simplest way to do that may well be this:
XmlDocument doc = new XmlDocument();
using (XmlReader reader = element.CreateReader())
{
doc.Load(reader);
}
However, I have to say the code would probably be simpler if you just converted it all to LINQ to XML, which is generally a nicer API to start with. Is there any reason why you want to stay with XmlDocument?
You can use XElement.ToString() to create an XML string from the XElement which you can load into your XmlDocument:
XmlDocument xmlDocument = LoadXMLDocument(yourXElement.ToString());

Categories