Read element within elements using XmlTextReader - c#

I am reading XML data and retrieving the values based on the element. There is one element named <UniqueColumns> which can have child element called <string>. I want to read those values and add it to the ObservableCollection<String>. If there are no values then don't anything. There are three scenarios as following:
Scenario - 1: More than 1 child elements.
<IndexId>4</IndexId>
<UniqueColumns>
<string>Dir_nbr</string>
<string>Dir_name</string>
</UniqueColumns>
<SelectedTableForUniqColumn>TBDirectory</SelectedTableForUniqColumn>
Scenario - 2: Only one child element.
<IndexId>4</IndexId>
<UniqueColumns>
<string>Dir_nbr</string>
</UniqueColumns>
<SelectedTableForUniqColumn>TBDirectory</SelectedTableForUniqColumn>
Scenario - 3: No child element.
<IndexId>4</IndexId>
<UniqueColumns/>
<SelectedTableForUniqColumn>TBDirectory</SelectedTableForUniqColumn>
Code:
//This is a user defined data object and it has parameter which is type of `ObservableCollection<String>`.
ExternalDatabaseTableRequestDO req = new ExternalDatabaseTableRequestDO();
using (XmlTextReader reader = new XmlTextReader(new StringReader(xmlData)))
{
while (reader.Read())
{
int result;
long res;
string parameterValue;
ObservableCollection<String> parameterValueList = new ObservableCollection<String>();
switch (reader.Name.ToLower())
{
case "indexid":
parameterValue = reader.ReadString();
if (!String.IsNullOrWhiteSpace(parameterValue) && Int32.TryParse(parameterValue, out result))
req.IndexId = result;
break;
case "uniquecolumns":
//need loop logic here but not sure how to do that.
if (reader.NodeType == XmlNodeType.Element) // This will give me parent element which is <UniqueColumns>
{
//Stuck here. How to read child elements if exists.
}
break;
case "selectedtableforuniqcolumn":
parameterValue = reader.ReadString();
req.SelectedTableForUniqColumn = parameterValue;
break;
}
}
}
return req;

How about using Linq2Xml?
var xDoc = XDocument.Load(filename);
//var xDoc = XDocument.Parse(xmlstring);
var strings = xDoc.XPathSelectElements("//UniqueColumns/string")
.Select(x => x.Value)
.ToList();

//This will give me all child element values if they exists
var columnList = XElement.Parse(xmlData).Descendants("string").ToList();
if (columnList != null)
{
foreach (var column in columnList)
parameterValueList.Add(column.Value);
}

Related

Need help retrieving XML data using Linq

I am trying to retrieve data from an XML file and return the parsed data in a list. Depending on what I use to access the data (Element or Attributes) I either get null (in case of Element) or something I cannot decipher (in case of Attributes).
XML Looks like this:
<DATA_RESPONSE>
<HEADER>
<MSGID>IS20101P:091317125610:98::34:0</MSGID>
</HEADER>
<DATA>
<ROW ID='IS20101P' PE_NAME='APP-029' PE_ID='4' CODE='4829' DATA='5,1,500,1' />
<ROW ID='IS20101P' PE_NAME='APPS-029' PE_ID='4' CODE='4829' DATA='4,1,500,1' />
...
</DATA>
<SUMMARY>
</SUMMARY>
<ERRORS>
</ERRORS>
</DATA_RESPONSE>
I am using the following to get the data. I read the file and store XML in a string and call a method with this string as argument:
public static Hashtable GetIDSData(string sXMLString)
{
Hashtable result = new Hashtable();
result.Add("Success", false);
result.Add("ErrorMessage", "");
result.Add("ID", "");
result.Add("PE_NAME", "");
result.Add("PE_ID", "");
result.Add("CODE", "");
result.Add("DATA", "");
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID")?.ToString(),
PEName = Address.Attributes("PE_NAME")?.ToString(),
PEID = Address.Attributes("PE_ID")?.ToString(),
Code = Address.Attributes("CODE")?.ToString(),
Data = Address.Attributes("DATA")?.ToString(),
}).ToList();
foreach (var AddressInfo in AddressInfoList)
{
if (string.IsNullOrEmpty(AddressInfo.Code))
{
result["Success"] = false;
result["ErrorMessage"] = "Invalid Code; code is empty.";
}
else
{
result["Success"] = true;
result["ErrorMessage"] = "";
result["ID"] = AddressInfo.ID;
result["PE_NAME"] = AddressInfo.PEName;
result["PE_ID"] = AddressInfo.PEID;
result["CODE"] = AddressInfo.Code;
result["DATA"] = AddressInfo.Data;
}
}
return result;
}
In Linq section, if I use Address.Element("ID").Value, I get null returned.
There is no namespace used in XML.
First off, the GetIDSData() method does not compile as is, because at the line xmlDoc.InnerXml = sXMLString, xmlDoc has not been defined.
I'm assuming you want xmlDoc to be an XmlDocument loaded with the contents of the sXMLString parameter, so I'm changing that line to:
XmlDocument xmlDoc = new XmlDocument {InnerXml = sXMLString};
Also, your root variable is never used, so I removed it for clarity.
Now as for the main part of your question, given your current syntax, you are calling .ToString() on a collection of attributes, which is obviously not what you want. To fix this, when you're iterating the AddressInfoList, You want to fetch the attribute values like:
ID = Address.Attributes("ID")?.Single().Value
or
ID = address.Attribute("ID")?.Value
...rather than Address.Attributes("ID")?.ToString() as you have above.
You are not selecting values of attributes. In your code you are selecting attributes. Not sure what are you trying to achieve, but here is my modified version of your code that loads all elements into DataTable
public static DataTable GetIDSData(string sXMLString)
{
DataTable result = new DataTable();
result.Columns.Add("Success");
result.Columns.Add("ErrorMessage");
result.Columns.Add("ID");
result.Columns.Add("PE_NAME");
result.Columns.Add("PE_ID");
result.Columns.Add("CODE");
result.Columns.Add("DATA");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.InnerXml = sXMLString;
XmlElement root = xmlDoc.DocumentElement;
XDocument doc = XDocument.Parse(sXMLString);
XmlNode node = xmlDoc.SelectSingleNode("DATA_RESPONSE/DATA");
if (node != null)
{
var AddressInfoList = doc.Root.Descendants("ROW").Select(Address => new
{
ID = Address.Attributes("ID").Select(i=>i.Value) ,
PEName = Address.Attributes("PE_NAME").Select(i=>i.Value),
PEID = Address.Attributes("PE_ID").Select(i=>i.Value),
Code = Address.Attributes("CODE").Select(i=>i.Value),
Data = Address.Attributes("DATA").Select(i=>i.Value),
}).ToList();
AddressInfoList.ForEach(e =>
{
e.Code.ToList().ForEach(c =>
{
DataRow row = result.NewRow();
if (!string.IsNullOrEmpty(c))
{
row["Success"] = true;
row["ErrorMessage"] = "";
row["ID"] = e.ID.First();
row["PE_NAME"] = e.PEName.First();
row["PE_ID"] = e.PEID.First();
row["CODE"] = e.Code.First();
row["DATA"] = e.Data.First();
}
else
{
row["Success"] = false;
row["ErrorMessage"] = "Invalid Code; code is empty.";
}
result.Rows.Add(row);
});});
result.Dump();
return result;
}
return result;
}
And this is the result that you will get in your datatable.
ID = Address.Attributes("ID")?.ToString(),
You want to use Attribute(name) (without s) instead:
ID = Address.Attributes("ID")?.Value,

How to get value from a specific child element in XML using XmlReader?

Here's the XML string.
<?xml version="1.0" encoding="utf-16"?>
<questionresponses>
<question id="dd7e3bce-57ee-497a-afe8-e3d8d25e2671">
<text>Question 1?</text>
<response>abcdefg</response>
<correctresponse>123</correctresponse>
</question>
<question id="efc43b1d-048f-4ba9-9cc0-1cc09a7eeaf2">
<text>Question 2?</text>
<response>12345678</response>
<correctresponse>123</correctresponse>
</question>
</questionresponses>
So how could I get value of <response> element by given question Id? Say, if I give id value = "dd7e3bce-57ee-497a-afe8-e3d8d25e2671", I'd like to have string value abcdefg returned as result.
var xmlstr = "content from above xml example";
using (var reader = XmlReader.Create(new StringReader(xmlstr)))
{
while(reader.Read())
{
if(reader.IsStartElement())
{
var attr = reader["id"];
if(attr != null && attr == "dd7e3bce-57ee-497a-afe8-e3d8d25e2671")
{
if(reader.ReadToDescendant("response"))
{
result = reader.Value; // <= getting empty string? So what's wrong?
break;
}
}
}
}
}
you might need to do like this , problem i think is reader is not moving to text and because of that you are getting empty
if(reader.ReadToDescendant("response"))
{
reader.Read();//this moves reader to next node which is text
result = reader.Value; //this might give value than
break;
}
Above one is working for me you can try out at your end
I would use LINQ2XML..
XDocument doc=XDocument.Parse(xmlstr);
String response=doc.Elements("question")
.Where(x=>x.Attribute("id")==id)
.Single()
.Element("response")
.Value;
if (reader.NodeType == XmlNodeType.Element)
{
if(reader.Name == "response")
{
reader.read();
var res = reader.Value;
}
}
//it works for me !!!!
You can use this function to get a response for specific questions from XML stored in QuestionXML.xml.
private string getResponse(string questionID)
{
string response = string.Empty;
using (StreamReader sr = new StreamReader("QuestionXML.xml", true))
{
XmlDocument xmlDoc1 = new XmlDocument();
xmlDoc1.Load(sr);
XmlNodeList itemNodes = xmlDoc1.GetElementsByTagName("question");
if (itemNodes.Count > 0)
{
foreach (XmlElement node in itemNodes)
{
if (node.Attributes["id"].Value.ToString() == questionID.Trim())
{
response = node.SelectSingleNode("response").InnerText;
break;
}
}
}
}
return response;
}

How can C# look return an XML node with a specific element value?

A commercial application uses XML to hold a list of variables it uses. I do not have control over the format of the XML. I can use any version of .Net.
Trying to write simpler code to assign a UserVar node to an object I've created. Right now I locate the node of the section of UserVars which contains all of the individual UserVars, iterate through each UserVar looking for the element "Name" and then see if it matches my desired variable name.
For example I want the variable "Changed" I will get an AcmeVar object (my creation) with the properties Name and Width set to "Changed" and 1. But I have to manually iterate through the code.
Seems like I'm doing this the hard way. Ideally I'd love to use Linq to return a UserVar node that has the matching element Name. The similar questions on Stackoverflow don't follow a similar pattern or at least not from what I can see. Not all variables use all of the element types.
Sample: XML
<?xml version="1.0" encoding="UTF-8"?>
<Application>
<Vars>
<UserVars>
<UserVar>
<Name>"Quantity"</Name>
<Width>4</Width>
<VarValue>"1"</VarValue>
</UserVar>
<UserVar>
<Name>"Printers"</Name>
<Width>255</Width>
</UserVar>
<UserVar>
<Name>"Changed"</Name>
<Width>1</Width>
</UserVar>
<UserVar>
<Name>"Weight"</Name>
<VarValue>"450.1"</VarValue>
</UserVar>
</UserVars>
</Vars>
</Application>
Current Code:
public static bool GetVariable(string xmlDocNm, string varName, out AcmeVariable acmeVar)
{
// Returns true if found without error
bool result = false;
acmeVar = new AcmeVariable ();
try {
XPathDocument doc = new XPathDocument(xmlDocNm);
XPathNavigator nav = doc.CreateNavigator();
// Compile a standard XPath expression
XPathExpression expr;
expr = nav.Compile(AcmeConst.XPathInternalVariable);
XPathNodeIterator iterator = nav.Select(expr);
// Iterate on the node set
try {
bool variableFound;
bool skipNode;
char[] CharsToTrim = { '\"' }; //
while (iterator.MoveNext()) {
variableFound = false;
skipNode = false;
XPathNavigator nav2 = iterator.Current.Clone();
if (nav2.MoveToFirstChild()) {
// nav2 points to the first element in an UserVar Node
acmeVar = new AcmeVariable (); //Start with a fresh Acme Variable
if (nav2.LocalName == AcmeConst.AttrName) {
variableFound = true;
skipNode = nav2.Value.Trim(CharsToTrim) != varName;
}
if (!skipNode) {
AssignXMLNavNodetoAcmeVar(nav2, acmeVar);
while (nav2.MoveToNext() && !skipNode) {
if (nav2.LocalName == AcmeConst.AttrName) {
variableFound = true;
skipNode = nav2.Value.Trim(CharsToTrim) != varName;
}
AssignXMLNavNodetoAcmeVar(nav2, acmeVar);
}
}
}
if (variableFound && !skipNode) {
result = true;
break; //We have found the variable and collected all elements
}
else {
acmeVar = null;
}
}
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
acmeVar = null;
result = false;
}
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
acmeVar = null;
result = false;
}
return result;
}
Try this:
var queryValue = "Quantity";
var xDoc = XDocument.Load(#"UserVars.xml");//This is your xml path value
var userVar = xDoc.Descendents("UserVar").Where(x => x.Element("Name").Value == queryValue )
.FirstOrDefault();
var name = userVar.Element("Name").Value ?? string.Empty;
var width = userVar.Element("Width").Value ?? string.Empty;
var varValue = userVar.Element("VarValue").Value ?? string.Empty;
I just want to make comment with your XML, especially in the part where <Name>"Quantity"</Name> element value were enclosed with ""
But if you have no bound with the xml, you just need to escape those ". eg. var queryValue = #""Quantity"";
Assuming that your key is Name, and all nodes will contain that, then this should work:
string valImLookingFor = "\"Changed\"";
XDocument doc = XDocument.Load("file"); // or XDocument doc = XDocument.Parse(xmlString);
var node = doc.Descendants("UserVar").Where(x => x.Element("Name").Value == valImLookingFor).First();
That should get you your node, then you can pull out the subnodes values you need.

Next node in xml with same element name

I've been working on this problem for a few hours now and I have searched all around with no luck for a solution :(
What I am trying to do is print out names of the nodes, what i have is the amount of nodes that exist so I know how many times to loop but am having the hardest of times retrieving the values
What I have tried:
int num = Convert.ToInt32(queuecount);
var jobs = QueueXML.SelectSingleNode(xpathjobsfilename).InnerText;
PreviousQueue = jobs.ToString();
//foreach(loop < num)
//{
// if (CurrentQueue == PreviousQueue)
// {
// }
// else
// {
// resultsListView.Items.Clear();
// resultsListView.Items.Add(jobs[num]);
// }
// loop++;
//}
foreach (char JobName in jobs.ToString())
{
if (CurrentQueue == PreviousQueue)
{
}
else
{
resultsListView.Items.Clear();
resultsListView.Items.Add(jobs[num]);
}
}
Edit: Example XML
<jobs>
<job>
<timeleft>0:00:00</timeleft>
<mb>1419.60536003</mb>
<msgid></msgid>
<filename>Extended_Final</filename>
<mbleft>1274.33209419</mbleft>
<id>nzo_i7qxxq</id>
</job>
<job>
<timeleft>0:00:00</timeleft>
<mb>9.22459220886</mb>
<msgid></msgid>
<filename>Video2</filename>
<mbleft>9.22459220886</mbleft>
<id>2m3dv5</id>
</job>
</jobs>
I want to retrieve the job details for each individual jobs
Use this code to loop through your job-nodes.
XmlDocument doc = new Windows.Data.Xml.Dom.XmlDocument();
doc.Load(#"/path/to/xml/file");
foreach (XmlNode job in doc.SelectNodes("/jobs/job"))
{
string filename = job.SelectSingleNode("filename").InnerText;
double mbleft = double.Parse(job.SelectSingleNode("mbleft").InnerText);
}
I am not quite sure what you want to do with it. If you want to use that information throughout your program, I'd create a job datatype and parse the XML document to a List<Job>. In any case the above code will enable you to access the information you are after.
Use LINQ2XML
XElement doc=XElement.Load("yourXMLfile.xml");
string timeleft,mb,msgid,filename,mbleft,id;
foreach(XElement elm in doc.Descendants().Elements("job"))
{
timeleft = elm.Element("timeleft").Value; //time left value
mb = elm.Element("mb").Value; //mb value
msgid = elm.Element("msgid").Value; //msgid value
filename = elm.Element("filename").Value; //filename value
mbleft = elm.Element("mbleft").Value; //mbleft value
id = elm.Element("id").Value; //id value
}
Below is the code to add individual job details to a List of dictionary.
It eliminates selecting single node in the loop when there are lot of child nodes. And, more generic.
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(yourXmlString);
if (doc.HasChildNodes)
{
System.Xml.XmlNodeList jobLst = doc.DocumentElement.ChildNodes;
System.Collections.Generic.Dictionary<string, string> jobDescription;
var lstjobDescription = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, string>>();
string name;
for (int i = 0; i < jobLst.Count; i++)
{
var responseDoc = new System.Xml.XmlDocument();
responseDoc.LoadXml(jobLst[i].OuterXml);
jobDescription = new System.Collections.Generic.Dictionary<string, string>();
foreach (System.Xml.XmlNode node in responseDoc.SelectNodes("//job/*")) //select all nodes of Job
{
jobDescription.Add(node.Name, node.InnerText);
}
lstjobDescription.Add(jobDescription);
}
}

Reading XML in C#

I have the following xml
<Objects>
<Object>
<ViewAngle>90</ViewAngle>
<ViewMode>ThirdPerson</ViewMode>
<Top>50</Top>
<Left>100</Left>
</Object>
</Objects>
I have the following code to read this xml
XmlDataDocument doc = new XmlDataDocument();
doc.Load(xmlPath);
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes("/Objects/Object");
foreach (XmlNode node in nodes)
{
if (node.InnerXml.Contains("View"))
{
string viewType=node["View"].InnerText;
//..... other stuffs
}
if (node.InnerXml.Contains("ViewAngle"))
{
string viewAngle=node["ViewAngle"].InnerText;
//..... other stuffs
}
if (node.InnerXml.Contains("ViewMode"))
{
string viewMode=node["ViewMode"].InnerText;
//..... other stuffs
}
}
Above code is working fine if my xml contains one more tag i.e. <View>3dView</View>. But if it does not contains the View tag, then it produces error.
The InnerXml.Contains() is a simple string method that checks for the sub-string is present in the string or not.
In my case the Contains("View") returns true as View is present in ViewAngle and in ViewMode that is why it is going inside the if block of View but when it tries to read the node["View"], it produces error as View node is not exists in the xml.
How to resolve this error?
You are missing the fact that XML is structured data, not string. This is completely the wrong approach:
if (node.InnerXml.Contains("View")) {
// ...
}
You want this:
XmlNode child = node.SelectSingleNode("./View");
if (child != null) {
// now do something with 'child' ...
}
Try this loop instead:
foreach (XmlNode node in nodes) {
foreach (XmlNode prop in node.ChildNodes) {
if (prop.NodeType != XmlNodeType.Element)
continue;
switch (prop.Name) {
case "View":
string viewType = prop.InnerText;
// ...
break;
case "ViewAngle":
string viewAngle = prop.InnerText;
// ...
break;
case "ViewMode":
string viewMode = prop.InnerText;
// ...
break;
}
}
}
If the View element is not present, it cannot be found. You can use the GetElementsByTagName("View") method instead. This will return null if the view element is not there.
You can make some modifications in this function and use it acordingly
XDocument xDoc=XDocument.Load(#"C:\File.xml");
Process(xDoc.Root);
public static void Process(XElement element)
{
if (element.HasElements)
{
Console.WriteLine("<" + element.Name.LocalName + ">");
foreach (XElement child in element.Elements())
{
Process(child);
}
Console.WriteLine("<" + element.Name.LocalName + ">");
}
else
{
Console.WriteLine("<" + element.Name.LocalName);
if(element.HasAttributes)
{
foreach (XAttribute attr in element.Attributes())
{
Console.WriteLine(attr.Name +"="+attr.Value);
}
}
Console.WriteLine(">" + element.Value + "</" + element.Name.LocalName + ">");
}
}
if trageting 3.5 or above u can use linq
XDocument XDoc = XDocument.Load(#"C:\UrXMLFile.xml");
var q = from b in xml.Descendants("product")
select new
{
View = (string)b.Element("View") ?? "3dView",
ViewAngle = (double?)b.Element("ViewAngle") ?? 90.0,
ViewMode = (string)b.Element("ViewMode") ?? ThirdPerson,
Top = (double?)b.Element("Top") ?? 0.0,
Left = (double?)b.Element("Left") ?? 0.0
};
So if View is not found it have a default value always. Also u can create ur class object in select new. Note: I have not compiled it.

Categories