XmlReader How to read properly? - c#

what is the best way to read xml like this one:
<Users>
<user name = "mail">
<supplier name = "supp1">
<project name = "proj1">
<subscribe name= "sub1"/>
<subscribe name = "sub2"/>
</project>
</supplier>
<supplier name = "supp2">
<project name = "proj2">
<subscribe name = "sub3"/>
</project>
<project name = "proj3">
<subscribe name= "sub4"/>
<subscribe name = "sub5"/>
</project>
<project name = "proj4"/>
</supplier>
<supplier name = "supp3"/>
<supplier name = "supp5">
<project name = "proj4"/>
<supplier name = "supp4"/>
</user>
</Users>
For now I am using
While(reader.Read())
{
if (((reader.NodeType == XmlNodeType.EndElement) && (reader.Name == "user")))
break;
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name =="supplier"))
{
foreach (TreeNode tree in TreeView1.Nodes)
{
if (reader.GetAttribute(0) == tree.Text)
{
TreeView1.SelectedNode = tree;
TreeView1.SelectedNode.Checked = true;
Get_projects(reader, tree);
break;
}
}
}
}
this is the main after that is get_projects(...):
private void Get_projects(XmlReader reader, TreeNode tree)
{
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.EndElement) && (reader.Name == "supplier")) break;
//(reader.IsEmptyElement && reader.Name == "supplier")
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "project"))
{
foreach (TreeNode projTree in tree.Nodes)
{
if (reader.GetAttribute(0) == projTree.Text)
{
TreeView1.SelectedNode = projTree;
TreeView1.SelectedNode.Checked = true;
Get_subscribes(reader, projTree);
break;
}
}
}
}
}
the Get_subscribes(reader, projTree):
private void Get_subscribes(XmlReader reader, TreeNode tree)
{
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.EndElement) && (reader.Name == "project") ||
(reader.IsEmptyElement && reader.Name == "project")) break;
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "subscribe"))
{
foreach (TreeNode subTree in tree.Nodes)
{
if (reader.GetAttribute(0) == subTree.Text)
{
TreeView1.SelectedNode = subTree;
TreeView1.SelectedNode.Checked = true;
break;
}
}
}
}
}
It doesn't work, so I am wondering is there a better way or what am i missing?

I will give you sample to read properly
<ApplicationPool>
<Accounts>
<Account>
<NameOfKin></NameOfKin>
<StatementsAvailable>
<Statement></Statement>
</StatementsAvailable>
</Account>
</Accounts>
</ApplicationPool>
static IEnumerable<XElement> SimpleStreamAxis(string inputUrl, string elementName)
{
using (XmlReader reader = XmlReader.Create(inputUrl))
{
reader.MoveToContent();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == elementName)
{
XElement el = XNode.ReadFrom(reader) as XElement;
if (el != null)
{
yield return el;
}
}
}
}
}
}
using (XmlReader reader = XmlReader.Create(inputUrl))
{
reader.ReadStartElement("theRootElement");
while (reader.Name == "TheNodeIWant")
{
XElement el = (XElement) XNode.ReadFrom(reader);
}
reader.ReadEndElement();
}
Source: Reading Xml with XmlReader in C#
Hope this help.

I would consider the reverse approach i.e.: instead of reading XML and checking if a node exists in TreeView I would prefer to use XPath to check if a node exists in XML document.
In order to do so you have to traverse TreeView nodes and for each node build XPath query
e.g.: /Users/user/supplier[#name='supp1']/project[#name='proj1'].
Having XPath query you can create an instance of XPathDocument based on your XMLReader and run the query. If something is found, you will check current node in TreeView.

you can try XPath to read informations you need
XMLDocument doc = new XMLDocument();
doc.Load(your_xml_file_path);
XMLNodeList list = doc.SelectNodes(#"//project"); //get all project element

Related

XML element with multiple different Text elements

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();

Why is the XmlReader skipping elements?

Please note this question is specific to XmlReader and not whether to use XDocument or XmlReader.
I have an XML fragment as:
private string GetXmlFragment()
{
return #"<bookstore>
<book genre='novel' ISBN='10-861003-324'>
<title>The Handmaid's Tale</title>
<price>19.95</price>
</book>
<book genre='novel' ISBN='1-861001-57-5'>
<title>Pride And Prejudice</title>
<price>24.95</price>
</book>
</bookstore>";
}
I also have an extension method as:
public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
reader.MoveToElement();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name.Equals(elementName, StringComparison.InvariantCulture))
{
yield return XNode.ReadFrom(reader) as XElement;
}
}
}
I then try to get the two book elements by doing:
var xmlReaderSettings = new XmlReaderSettings
{
CheckCharacters = false,
ConformanceLevel = ConformanceLevel.Fragment,
IgnoreComments = true,
IgnoreWhitespace = true,
IgnoreProcessingInstructions = true
};
using (var stringReader = new StringReader(this.GetXmlFragment()))
using (var xmlReader = XmlReader.Create(stringReader, xmlReaderSettings))
{
xmlReader.GetElement("book").Count().ShouldBe(2);
}
However I only get the first element, debugging shows that as soon as I get the first element the reader jumps to the title of the second book element.
The solution is inspired from HERE
Any help is much appreciated.
The problem is that, if there is no intervening whitespace, the call to XNode.ReadFrom() will leave the XML reader positioned right at the next element. The while condition then immediately consumes this element before we can check it. The fix is to not call XmlReader.Read() immediately afterwards, but to continue checking for nodes (as the read has been done implicitly):
while (reader.Read()) {
while (reader.NodeType == XmlNodeType.Element
&& reader.Name.Equals(elementName, StringComparison.InvariantCulture)) {
yield return XNode.ReadFrom(reader) as XElement;
}
}
(In case it's not clear, the if in the loop has been changed to a while.)
public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
while (!reader.EOF)
if (reader.NodeType == XmlNodeType.Element && reader.Name == "book")
yield return XNode.ReadFrom(reader) as XElement;
else
reader.Read();
}
The code is skipping every other book tag because the book tags immediately follow each other . The read method leave the reader at the next book tag and then the read method moves before reading the 2nd book tag skipping the element. Try the code below which I developed and always works.
public static IEnumerable<XElement> GetElement(XmlReader reader, string elementName)
{
List<XElement> books = new List<XElement>();
while (!reader.EOF)
{
if(reader.Name != "book")
{
reader.ReadToFollowing("book");
}
if(!reader.EOF)
{
books.Add((XElement)XElement.ReadFrom(reader));
}
}
return books;
}
Like others have said, XNode.ReadFrom is advancing your reader to the next book open tag (if there is no whitespace between them) then reader.Read will advance to the inner text of that tag.
See here for more information:
https://stackoverflow.com/a/26788230/3850405
Fix for your extension method:
public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
reader.MoveToElement();
reader.Read();
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name.Equals(elementName, StringComparison.InvariantCulture))
{
yield return XNode.ReadFrom(reader) as XElement;
}
else
{
reader.Read();
}
}
}

Reading XML giving null

<tours xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://api.contiki.com/schemas/v2/detailed_tours.xsd">
<tour>
<id>290</id>
<name>Peru Uncovered</name>
<lowest_price>
<code>11D15a</code>
</lowest_price>
</tour>
</tours>
I want to read Id, name and code.
I am trying this code
XmlTextReader reader = new XmlTextReader(downloadfolder);
XmlDocument doc = new XmlDocument();
XmlNode node = doc.ReadNode(reader);
foreach (XmlNode chldNode in node.ChildNodes)
{
string employeeName = chldNode.Attributes["name"].Value;
}
But i am getting null. Can anyone tell me how can i read the values? i can not use Linq as i am working in SSIS 2008 project which does not support linq.
Updated Answer
XmlTextReader reader = new XmlTextReader(downloadfolder);
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element: // The node is an element.
string node = reader.Name;
if (node == "id")
{
string id = reader.ReadString();
}
if (node == "name")
{
string name = reader.ReadString();
}
if (node == "code")
{
string code = reader.ReadString();
}
break;
}
I can read the values but how can i add these as a row in my data table?
here node type of "name" is element.
below code can be used for node type checking
XmlTextReader reader = new XmlTextReader ("<file name>");
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
Reference : http://support.microsoft.com/en-us/kb/307548

XML read text inside two elements

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 });

something wrong reading XML LINQ

This is my source XML :
<?xml version="1.0" encoding="UTF-8"?>
<cteProc xmlns="http://www.portalfiscal.inf.br/cte" versao="1.04">
<CTe>
<infCte versao="1.04" Id="CTe35121004211559000111570010000118991000119858">
<ide>
<cUF>35</cUF>
<cCT>00011985</cCT>
<CFOP>7358</CFOP>
<natOp>PRESTACAO DE SERVICO DE TRANSPORTE</natOp>
<forPag>1</forPag>
<mod>57</mod>
<serie>1</serie>
<nCT>11899</nCT>
<dhEmi>2012-10-01T09:34:45</dhEmi>
</ide>
<compl>
<emit>
<rem>
<dest>
<vPrest>
<imp>
<infCTeNorm>
</infCte>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
</CTe>
<protCTe versao="1.04">
</cteProc>
I have read this file with this code :
XmlTextReader reader = new XmlTextReader(#"C:\Separados\56000858_v01.04-procCTe.xml");
XmlNodeType type;
while (reader.Read())
{
type = reader.NodeType;
if (type == XmlNodeType.Element)
{
if (reader.Name == "cUF")
{ reader.Read(); Xmunini = reader.Value; textBox1.Text = Xmunini;}
if (reader.Name == "cCT")
{ reader.Read(); vtprest = reader.Value; textBox2.Text = vtprest;}
if (reader.Name == "natOp")
{ reader.Read(); UFIni = reader.Value; textBox3.Text = UFIni; }
if (reader.Name == "nCT")
{ reader.Read(); NCT = reader.Value; textBox4.Text = NCT;}
But, i have read in many post here , the method with LINQ is more efficient, i have try write this :
var custs45 = from c in XElement.Load(#"C:\Separados\56000858_v01.04-procCTe.xml").Elements("Cte")
select new {
CFOP = c.Element("CFOP").Value,
xMunIni = c.Element("xMunIni").Value
};
My question is how to assign CFOP and Xmunini to a variable?? i have write this but do not show anything
string CFF;
foreach (var valores in custs45)
{
CFF = valores.CFOP.ToString() ;
}
You can't find nodes, because root node has namespace delcared. Here is the solution:
XDocument xdoc = XDocument.Load(path_to_xml);
XNamespace ns = "http://www.portalfiscal.inf.br/cte";
string CFF = (string)xdoc.Descendants(ns + "CFOP").Single();
Also you have typo in CTe element name. And you are missing that CFOP is not direct child of CTe. And there is still no xMunIni element in your xml.
Your original query should look like this:
XNamespace ns = "http://www.portalfiscal.inf.br/cte";
var custs45 = from ide in XElement.Load(path_to_xml).Descendants(ns + "ide")
select new
{
CFOP = (string)ide.Element(ns + "CFOP"),
xMunIni = (string)ide.Element(ns + "xMunIni")
};

Categories

Resources