I have the following elements as part of an XML document:
<RegisterEntry>
<EntryNumber>3</EntryNumber>
<EntryDate>2009-01-30</EntryDate>
<EntryType>Registered Charges</EntryType>
<EntryText>REGISTERED CHARGE dated 30 December 2008.</EntryText>
</RegisterEntry>
<RegisterEntry>
<EntryNumber>4</EntryNumber>
<EntryType>Registered Charges</EntryType>
<EntryText>REGISTERED CHARGE dated 30 December 2008.</EntryText>
</RegisterEntry>
I am using XmlReader to iterate through the document. The RegisterEntry is an XMLNodeType.Element and the four enclosed in this element are XmlNodeType.Text. How can I assign each of these Text values to a different variable as the XmlReader returns an empty string for Node.Name on a NodeType.Text. Also, the repeated elements do not always have the same number of text elements. Code below:
XmlTextReader reader = new XmlTextReader(fName);
if(reader.NodeType == XmlNodeType.Element && reader.Name =="RegisterEntry")
{
propEntryNo = "";
propEntryDate = "";
propEntryType = "";
propEntryText = "";
while(reader.Read())
{
if(reader.NodeType == XmlNodeType.Text && reader.Name == "EntryNumber" && reader.HasValue)
{
propEntryNo = reader.Value;
}
if (reader.NodeType == XmlNodeType.Text && reader.Name == "EntryDate" && reader.HasValue)
{
propEntryDate = reader.Value;
}
if (reader.NodeType == XmlNodeType.Text && reader.Name == "EntryType" && reader.HasValue)
{
propEntryType = reader.Value;
}
if (reader.NodeType == XmlNodeType.Text && reader.Name == "EntryText" && reader.HasValue)
{
propEntryText += reader.Value + ",";
}
if(reader.NodeType == XmlNodeType.EndElement && reader.Name == "RegisterEntry")
{
add variable values to list
break;
}
}
}
In each of the if statements above the NodeType returns as Text and the Name as an empty string.
The XML element and the text inside are different nodes!
You have to read the content of the XML element first. Simple example:
switch (reader.Name)
{
// found a node with name = "EntryNumber" (type = Element)
case "EntryNumber":
// make sure it's not the closing tag
if (reader.IsStartElement())
{
// read the text inside the element, which is a seperate node (type = Text)
reader.Read();
// get the value of the text node
propEntryNo = reader.Value;
}
break;
// ...
}
Another option would be ReadElementContentAsString
switch (reader.Name)
{
case "EntryNumber":
propEntryNo = reader.ReadElementContentAsString();
break;
// ...
}
Of course, these simple examples assume that the XML is in the expected format. You should include appropriate checks in your code.
As for the other suggested solutions:
You could XmlDocument or XDocument or instead. The handling is easier, but the memory overhead is bigger (see also).
Deserializing the XML into objects is another option. But I feel handling errors caused by an unexpected format is trickier then.
You can use XDocument to list your RegisterEntry child node like
class Program
{
static void Main(string[] args)
{
XDocument doc = XDocument.Load(#"C:\Users\xxx\source\repos\ConsoleApp4\ConsoleApp4\Files\XMLFile14.xml");
var registerEntries = doc.Descendants("RegisterEntry");
var result = (from e in registerEntries
select new
{
EntryNumber = e.Element("EntryNumber") != null ? Convert.ToInt32(e.Element("EntryNumber").Value) : 0,
EntryDate = e.Element("EntryDate") != null ? Convert.ToDateTime(e.Element("EntryDate").Value) : (DateTime?)null,
EntryType = e.Element("EntryType") != null ? e.Element("EntryType").Value : "",
EntryText = e.Element("EntryText") != null ? e.Element("EntryText").Value : "",
}).ToList();
foreach (var entry in result)
{
Console.WriteLine($"EntryNumber: {entry.EntryNumber}");
Console.WriteLine($"EntryDate: {entry.EntryDate}");
Console.WriteLine($"EntryType: {entry.EntryType}");
Console.WriteLine($"EntryText: {entry.EntryText}");
Console.WriteLine();
}
Console.ReadLine();
}
}
Output:
You can also make certain operations on your list like.
//If you want to get all `EntryText` in xml to be comma separated then you can do like
string propEntryText = string.Join(", ", result.Select(x => x.EntryText));
//Get first register entry from xml
var getFirstRegisterEntry = result.FirstOrDefault();
//Get last register entry from xml
var getLastRegisterEntry = result.LastOrDefault();
//Get register entry from xml with specific condition
var getSpecificRegisterEntry = result.Where(x => x.EntryNumber == 3).SingleOrDefault();
Related
I have a code which retrieves only one value from the xml file using xmlreader.
<recordset>
<itemidlist>
<itemid idtype = "plant">787484545</itemid>
<itemid idtype = "seed">659988222</itemid>
</itemidlist>
<itemidlist>
<itemid idtype = "plant">90327328</itemid>
<itemid idtype = "seed">099849999</itemid>
</itemidlist>
<itemidlist>
<itemid idtype = "plant">34545488</itemid>
<itemid idtype = "seed">787555444</itemid>
</itemidlist>
</recordset>
And C# coded this entry:(s is the xml file)
using (var reader = XmlReader.Create(s))
{
var nodecount = 0;
var plant = "";
var seed = "";
var typeid = false;
var typeid2 = false;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "recordset")
{
nodecount++;
}
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "itemid")
{
var idtype = reader.GetAttribute("idtype");
typeid = idtype == "plant";
typeid2 = idtype == "seed";
}
while (typeid && reader.NodeType == XmlNodeType.Element &&
reader.Name == "itemid")
{
plant = reader.ReadInnerXml();
}
while (typeid2 && reader.NodeType == XmlNodeType.Element &&
reader.Name == "itemid")
{
seed = reader.ReadInnerXml();
}
}
}
and this happens when a add to datagridview:
Only one record was found:
Plant Seed
787484545 659988222
You're only setting on seed and one plant variable, so if any more than one value is found it will be overwritten. How do you propose to return multiple values?
Any reason not to use LINQ to XML for this?
var doc = XDocument.Load(s);
var plants = doc.Descendants("itemid")
.Where(x => (string) x.Attribute("idtype") == "plant")
.Select(x => x.Value);
var seeds = doc.Descendants("itemid")
.Where(x => (string) x.Attribute("idtype") == "seed")
.Select(x => x.Value);
See this fiddle for a working demo.
Consider the following XML which I have to parse.
<root>
<item>
<itemId>001</itemId>
<itemName>test 1</itemName>
<description/>
</item>
</root>
I have to parse each of its tag and store it into a table as follows:
TAG_NAME TAG_VALUE IsContainer
------------ -------------- -----------
root null true
item null true
itemId 001 false
itemName test 1 false
description null false
/item null true
/root null true
Now to get this done, I am using XmlReader as this allows us to parse each & every node.
I am doing it as follows:
I created the following class to contain each tag's data
public class XmlTag
{
public string XML_TAG { get; set; }
public string XML_VALUE { get; set; }
public bool IsContainer { get; set; }
}
I am trying to get the list of tags(including closing ones) as follows:
private static List<XmlTag> ParseXml(string path)
{
var tags = new List<XmlTag>();
using (var reader = XmlReader.Create(path))
{
while (reader.Read())
{
var tag = new XmlTag();
bool shouldAdd = false;
switch (reader.NodeType)
{
case XmlNodeType.Element:
shouldAdd = true;
tag.XML_TAG = reader.Name;
//How do I get the VALUE of current reader?
//How do I determine if the current node contains children nodes to set IsContainer property of XmlTag object?
break;
case XmlNodeType.EndElement:
shouldAdd = true;
tag.XML_TAG = string.Format("/{0}", reader.Name);
tag.XML_VALUE = null;
//How do I determine if the current closing node belongs to a node which had children.. like ROOT or ITEM in above example?
break;
}
if(shouldAdd)
tags.Add(tag);
}
}
return tags;
}
but I am having difficulty determining the following:
How to determine if current ELEMENT contains children XML nodes? To set IsContainer property.
How to get the value of current node value if it is of type XmlNodeType.Element
Edit:
I have tried to use LINQ to XML as follows:
var xdoc = XDocument.Load(#"SampleItem.xml");
var tags = (from t in xdoc.Descendants()
select new XmlTag
{
XML_TAG = t.Name.ToString(),
ML_VALUE = t.HasElements ? null : t.Value,
IsContainer = t.HasElements
}).ToList();
This gives me the XML tags and their values but this does not give me ALL the tags including the closing ones. That's why I decided to try XmlReader. But If I have missed anything in LINQ to XML example, please correct me.
First of all, as noted by Jon Skeet in the comments you should probably consider using other tools, like XmlDocument possibly with LINQ to XML (EDIT: an example with XmlDocument follows).
Having said that, here is the simplest solution for what you have currently (note that it's not the cleanest possible code, and it doesn't have much validation):
private static List<XmlTag> ParseElement(XmlReader reader, XmlTag element)
{
var result = new List<XmlTag>() { element };
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
element.IsContainer = true;
var newTag = new XmlTag() { XML_TAG = reader.Name };
if (reader.IsEmptyElement)
{
result.Add(newTag);
}
else
{
result.AddRange(ParseElement(reader, newTag));
}
break;
case XmlNodeType.Text:
element.XML_VALUE = reader.Value;
break;
case XmlNodeType.EndElement:
if (reader.Name == element.XML_TAG)
{
result.Add(new XmlTag()
{
XML_TAG = string.Format("/{0}", reader.Name),
IsContainer = element.IsContainer
});
}
return result;
}
}
return result;
}
private static List<XmlTag> ParseXml(string path)
{
var result = new List<XmlTag>();
using (var reader = XmlReader.Create(path))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
result.AddRange(ParseElement(
reader,
new XmlTag() { XML_TAG = reader.Name }));
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
result.Add(new XmlTag()
{
XML_TAG = string.Format("/{0}",current.Name)
});
}
}
}
return result;
}
An example using XmlDocument. This will give slightly different result for self-enclosing tags (<description/> in your case). You can change this behaviour easily, depending on what you want.
private static IEnumerable<XmlTag> ProcessElement(XElement current)
{
if (current.HasElements)
{
yield return new XmlTag()
{
XML_TAG = current.Name.ToString(),
IsContainer = true
};
foreach (var tag in current
.Elements()
.SelectMany(e => ProcessElement(e)))
{
yield return tag;
}
yield return new XmlTag()
{
XML_TAG = string.Format("/{0}", current.Name.ToString()),
IsContainer = true
};
}
else
{
yield return new XmlTag()
{
XML_TAG = current.Name.ToString(),
XML_VALUE = current.Value
};
yield return new XmlTag()
{
XML_TAG = string.Format("/{0}",current.Name.ToString())
};
}
}
And using it:
var xdoc = XDocument.Load(#"test.xml");
var tags = ProcessElement(xdoc.Root).ToList();
Here is my xml code:
<?xml version="1.0" encoding="utf-8"?>
<updater>
<version>1.0.7</version>
<Enabled>true</Enabled>
<item>
<url>some url</url>
<name>file name</name>
</item>
<item>
<url>other url</url>
<name>other file name</name>
</item>
</updater>
how can i get the value of url and name inside of both item elements? The full code have 9 elements with the name item. Please make the solution fit with this code:
XmlTextReader reader = null;
try
{
string xmlURL = "someurl";
reader = new XmlTextReader(xmlURL);
reader.MoveToContent();
string elementName = "";
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "updater"))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element) elementName = reader.Name;
else
{
if ((reader.NodeType == XmlNodeType.Text) && (reader.HasValue))
{
switch (elementName)
{
case "url":
if (nummer >= urls.Length)
Array.Resize(ref urls, urls.Length + 1);
urls[nummer] = reader.Value.ToString();
MessageBox.Show(urls[nummer]);
break;
case "name":
if (nummer >= names.Length)
Array.Resize(ref names, names.Length + 1);
names[nummer] = reader.Value.ToString();
MessageBox.Show(names[nummer]);
break;
}
nummer++;
}
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
Any help will be appreciated. thanks in advance.
PS. If i'm unclear somewhere, or if you need more information then just explain what's needed.
You can use LINQ to XML:
var xdoc = XDocument.Load(path_to_xml);
var items = from i in xdoc.Root.Elements("item")
select new {
Url = (string)i.Element("url"),
Name = (string)i.Element("name")
};
This will give list of anonymous objects corresponding to your item elements. Each object will have strongly-typed properties for url and name:
foreach(var item in items)
{
// use item.Url or item.Name
}
XDocument doc = XDocument.Load("Xml.xml");
IEnumerable<XElement> items = doc.Descendants("updater").Elements("item")
.Select(x => new { Url = x.Element("url").Value,
Name = x.Element("name").Value });
I am actually trying to read this piece of XML.
http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/xml/351352?res=3hourly&key=99b9f578-ad3d-446c-9d29-0bbee028b483
I was wondering how I could read only the node Period with the value="2012-11-15Z"
So the one below :
This is the code I use
using (XmlReader reader = XmlReader.Create("http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/xml/351352?res=3hourly&key=99b9f578-ad3d-446c-9d29-0bbee028b483"))
{
reader.MoveToContent();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Period")
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Rep")
{
first.Text = reader.GetAttribute("T");
}
}
}
}
}
What is the way for me to read only this node ?
Should I write
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Period" && reader.GetAttribute("value") == "2012-11-15Z")
This doesn't seem to work ..
Can someone help me ?
You can easily do that with LINQ to XML:
XDocument xdoc = XDocument.Load(path_to_xml);
var period = xdoc.Descendants("Period")
.Where(p => (string)p.Attribute("value") == "2012-11-15Z")
.SingleOrDefault();
It will return XElement, but you can select any data from period. E.g. T attributes:
List<int> tList = xdoc.Descendants("Period")
.Where(p => (string)p.Attribute("value") == "2012-11-15Z")
.SelectMany(p => p.Elements())
.Select(rep => (int)rep.Attribute("T"))
.ToList();
var query = xdoc.Descendants("Period")
.Where(p => (string)p.Attribute("value") == "2012-11-15Z")
.SelectMany(p => p.Elements())
.Select(rep => new {
T = (int)rep.Attribute("T"),
D = (string)rep.Attribute("D") })
.ToList();
Last query will return List of strongly-typed anonymous objects with integer property T and string property D:
foreach(var x in query)
// use x.T and x.D
Try using xpath to lookup the value like this
XmlDocument doc = new XmlDocument();
using (XmlReader reader = XmlReader.Create("http://datapoint.metoffice.gov.uk/public/data/val/wxfcs/all/xml/351352?res=3hourly&key=99b9f578-ad3d-446c-9d29-0bbee028b483"))
{
doc.Load(reader);
XmlNodeList list = doc.SelectNodes("//Period[#value='2012-11-15Z']");
Console.WriteLine(list.Count);
}
I am a new to programming, and have a serious problem and cant get out of it.
I have 5 XML URLs. such as http://www.shopandmiles.com/xml/3_119_3.xml
This is an XML URL which I have to get values and write to database in related columns.
My column names and XML tag names do match.
When I write the below code, reader element miss null xml values. Some tags do not have value inside. I have to add them null to linkedlist because after that code, i am going through the linked list but the order doesnt match if ı cant add a value for null xml values. So column names and data inside doesnt match. i lose the order. My all code is here, you can also check comment in the code if that helps. Thank you all.
public void WebServiceShopMilesCampaignsXMLRead(string URL)
{
XmlReader reader = XmlReader.Create(URL);
LinkedList<string> linkedList = new LinkedList<string>();
List<ShopAndMilesCampaigns> shopMileCampaigns = new List<ShopAndMilesCampaigns>();
try
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Text:
linkedList.AddLast(reader.Value);
break;
}
}
}
catch (XmlException exception)
{
Console.WriteLine("XML okurken bir sorun oluştu, hata detayı --> " + exception.Message);
}
LinkedListNode<string> node = linkedList.First;
while (node != null)
{
ShopAndMilesCampaigns shopMilesCampaign = new ShopAndMilesCampaigns();
shopMilesCampaign.Name = node.Value; // Null values mixes up the order because i cant add as null with reader.read above
node = node.Next;
shopMilesCampaign.Summary = node.Value;
node = node.Next;
shopMilesCampaign.AccountName = node.Value;
node = node.Next;
shopMilesCampaign.Category = node.Value;
node = node.Next;
shopMilesCampaign.Sector = node.Value;
node = node.Next;
shopMilesCampaign.Details = node.Value;
node = node.Next;
shopMilesCampaign.Image = node.Value;
node = node.Next;
shopMilesCampaign.Status = 1;
node = node.Next;
shopMileCampaigns.Add(shopMilesCampaign);
}
foreach (ShopAndMilesCampaigns shopMileCampaign in shopMileCampaigns)
{
shopMileCampaign.Insert();
}
}
I found the answer. Here it is to let you know.
If the XmlNodeType is equal to Element, then the loop continues to read from the XML data and looks for Whitesapces and end Element of XML tag. The below code gives me the exact value of XML tag even it is empty.
public LinkedList<string> AddToLinkedList(XmlReader reader)
{
LinkedList<string> linkedList = new LinkedList<string>();
try
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
reader.Read();
Start:
if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Element)
{
reader.Read();
goto Start;
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
linkedList.AddLast("");
}
else
{
linkedList.AddLast(reader.Value);
}
break;
}
}
}