XmlReader on node without value - c#

I'm trying to extract data from provided xml and add that to object using XmlReader, but I noticed on nodes without value, I get "\n " instead.
Example xml:
<Items>
<Item>
<NodeA>Some Value</NodeA>
<NodeB>N</NodeB>
<NodeC />
</Item>
<Item>
...
</Item>
</Items>
Part of my modified C#:
while (sub_reader.ReadToFollowing("Item"))
{
var item = new Item();
sub_reader.ReadToFollowing("NodeA");
sub_reader.Read();
item.NodeA = sub_reader.Value;
sub_reader.ReadToFollowing("NodeB");
sub_reader.Read();
item.NodeB = sub_reader.Value;
sub_reader.ReadToFollowing("NodeC");
sub_reader.Read();
item.NodeC = sub_reader.Value; //This return "\n "
this.Items.Add(item);
}
Is there any function/convenient way that work the above but return null or empty string when <NodeC /> happens? The real xml is much larger and I don't want to do if else on each of them.
Any suggestion is appreciated. Thanks!

Rather than calling Read followed by taking Value property, use ReadElementContentAsString method:
sub_reader.ReadToFollowing("NodeA");
item.NodeA = sub_reader.ReadElementContentAsString();
sub_reader.ReadToFollowing("NodeB");
item.NodeB = sub_reader.ReadElementContentAsString();
sub_reader.ReadToFollowing("NodeC");
item.NodeC = sub_reader.ReadElementContentAsString();

Using XDocument <NodeC/> return string.Empty. Here dotNetFiddle
string xml = #"<Items>
<Item>
<NodeA>Some Value</NodeA>
<NodeB>N</NodeB>
<NodeC />
</Item>
<Item>
<NodeA>Some 2223Value</NodeA>
<NodeB>2223N</NodeB>
<NodeC>12344</NodeC>
</Item>
</Items>";
XDocument doc = XDocument.Parse(xml);
var result = doc.Root.Descendants("NodeC");
foreach(var item in result)
{
Console.WriteLine(item.Value);
}
If you want to deserialize the XDocument to some object you can check this answer: How do I deserialize XML into an object using a constructor that takes an XDocument?
public static MyClass FromXml (XDocument xd)
{
XmlSerializer s = new XmlSerializer(typeof(MyClass));
return (MyClass)s.Deserialize(xd.CreateReader());
}

Related

Looping through items and read desired elements

I have something like this:
<ITEMS ASOF_DATE="6/2/2022" RECORDS="1" CREATE_DATE="6/3/2022" >
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomeValue>-808</SomeValue>
</ITEM>
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomeValue>-808</SomeValue>
<SomeOtherValue>-808</SomeOtherValue>
</ITEM>
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomethingElse>234</SomethingElse>
</ITEM>
</ITEMS>
It can have multiple <ITEM> items and those can have multiple elements under it for example first item has three elements, second one has four , third one has three in this example.
I want to loop through all of these <ITEM> items and read some specific elements that I am interested in. For example <port_name> and <bench_name>
But I can't figure out how to do the loop. For example root.SelectNodes("ITEMS") in my code below isn't what I am hoping to be able to loop through its items.
XmlDocument doc = new XmlDocument();
doc.Load(myFilePath.ToString());
XmlElement root = doc.DocumentElement;
var lineItems = root.InnerXml;
XmlNodeList nodes = root.SelectNodes("ITEMS");
foreach (XmlNode node in nodes)
{
var somethingElse = node.SelectNodes("ITEM");
}
Try using the Elements() or Descendants() methods to iterate your root element.
Edited in response to comment:
If you're looking for something specific, I have found that one of the easiest ways to do this is to write an Extension Method for XElement that suites your requirements. Here is an example.
static class Extensions
{
public static bool IsPortNameMatch(this XElement xElement, string name)
{
if (!string.Equals(xElement.Name.LocalName, "ITEM")) return false;
var portName = xElement.Element("port_name");
if (portName == null) return false;
return string.Equals((string)portName, name);
}
}
To test the extension, I have added a fourth ITEM to the source with a port_name of "UniquePortName". The new console output now reports this as "Found".
static void Main(string[] args)
{
var root = XElement.Parse(source);
foreach (var item in root.Elements("ITEM"))
{
foreach (var entry in item.Elements())
{
Console.WriteLine($"{entry.Name.LocalName} = {(string)entry}");
}
}
// Find something specific
var matchItem =
root.Descendants("ITEM")
.First(match => match.IsPortNameMatch("UniquePortName"));
Console.WriteLine($"FOUND:{Environment.NewLine}{matchItem.ToString()}");
}
const string source =
#"<ITEMS ASOF_DATE=""6/2/2022"" RECORDS=""1"" CREATE_DATE=""6/3/2022"" >
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomeValue>-808</SomeValue>
</ITEM>
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomeValue>-808</SomeValue>
<SomeOtherValue>-808</SomeOtherValue>
</ITEM>
<ITEM>
<port_name>95512M</port_name>
<bench_name>LEHSECUR</bench_name>
<SomethingElse>234</SomethingElse>
</ITEM>
<ITEM>
<port_name>UniquePortName</port_name>
<bench_name>LEHSECUR</bench_name>
<SomethingElse>234</SomethingElse>
</ITEM>
</ITEMS>";
}
Using XML Linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Data;
namespace ConsoleApplication23
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
foreach (XElement item in doc.Descendants("ITEM"))
{
foreach (XElement child in item.Elements())
{
Console.WriteLine("{0} = {1}", child.Name.LocalName, (string)child);
}
}
Console.ReadLine();
}
}
}

How to get enclosure url with XElement C# Console

I read multiple feed from many sources with C# Console, and i have this code where i load XML From sources:
XmlDocument doc = new XmlDocument();
doc.Load(sourceURLX);
XElement xdoc = XElement.Load(sourceURLX);
How to get enclosure url and show as variable?
If I understand your question correctly (I'm making a big assumption here) - you want to select an attribute from the root (or 'enclosing') tag, named 'url'?
You can make use of XPath queries here. Consider the following XML:
<?xml version="1.0" encoding="utf-8"?>
<root url='google.com'>
<inner />
</root>
You could use the following code to retrieve 'google.com':
String query = "/root[1]/#url";
XmlDocument doc = new XmlDocument();
doc.Load(sourceURLX);
String value = doc.SelectSingleNode(query).InnerText;
Further information about XPath syntax can be found here.
Edit: As you stated in your comment, you are working with the following XML:
<item>
<description>
</description>
<enclosure url="blablabla.com/img.jpg" />
</item>
Therefore, you can retrieve the url using the following XPath query:
/item[1]/enclosure[1]/#url
With xml like below
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>title</title>
<link>https://www.link.com</link>
<description>description</description>
<item>
<title>RSS</title>
<link>https://www.link.com/xml/xml_rss.asp</link>
<description>description</description>
<enclosure url="https://www.link.com/media/test.wmv"
length="10000"
type="video/wmv"/>
</item>
</channel>
</rss>
You will get url by reading attribute
var document = XDocument.Load(sourceURLX);
var url = document.Root
.Element("channel")
.Element("item")
.Element("enclosure")
.Attribute("url")
.Value;
To get multiple urls
var urls = document.Descendants("item")
.Select(item => item.Element("enclosure").Attribute("url").Value)
.ToList();
Using foreach loop
foreach (var item in document.Descendants("item"))
{
var title = item.Element("title").Value;
var link = item.Element("link").Value;
var description = item.Element("description").Value;
var url = item.Element("enclosure").Attribute("url").Value;
// save values to database
}

How find element in xdocument and read next elements after them

So i have next xml document:
<Items>
<Item>
<ID>123</ID>
<Name>Super Item</Name>
<Count>1</Count>
<Price>45</Price>
</Item>
<Item>
<ID>456</ID>
<Name>not super Item</Name>
<Count>10</Count>
<Price>5</Price>
</Item>
<Item>
<ID>789</ID>
<Name>Simple Item</Name>
<Count>6</Count>
<Price>10</Price>
</Item>
</Items>
So how can i find needed item by ID and read next values? Thanks in advance.
code:
XDocument doc = XDocument.Load (filePath);
foreach (var item in doc.Descendants("ID"))
{
if ((string)item.Element("ID") == "789")
{
How to read Name "Simple Item"?
How to read Count "6"?
How to read Price "10"?
}
}
By what you are asking you could formatt your xml like this:
<Items>
<Item id="123">
<Name>Super Item</Name>
<Count>1</Count>
<Price>45</Price>
</Item>
<Item id="456">
<Name>not super Item</Name>
<Count>10</Count>
<Price>5</Price>
</Item>
<Item id="789">
<Name>Simple Item</Name>
<Count>6</Count>
<Price>10</Price>
</Item>
</Items>
Then in code:
int yourId = 456;
XDocument doc = XDocument.Load("test.xml");
var result = from el in doc.Root.Elements("Item")
where el.Attribute("id").Value == yourId.ToString()
select el;
The id here is an attribute.And for reading its values 2 ways:
//1º
foreach (var item in result.Elements())
{
Console.WriteLine(item.Name + " = " + item.Value);
}
//2º - will print the element
Console.WriteLine(result);
It depends on what you want to do when you find those values. Here is a general method using a foreach loop to find the item with the specified ID and returning it's name:
private string GetItemName(string _id)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load("myXmlFile.xml");
foreach (XmlNode item in xDoc.SelectNodes("/Items/Item"))
{
if (item.SelectSingleNode("ID").InnerText == _id)
{
// we found the item! Now what do we do?
return item.SelectSingleNode("Name").InnerText;
}
}
return null;
}

xml to json using newtonsoft issue when loop over array of one element

i get error only when in the source xml there is a collection of only one element,
in the sample for the first element i get correctly the subelements (item) but in the second element there's only one child so the code throw exception (Cannot access child value on Newtonsoft.Json.Linq.JProperty)
Thanks
JObject firstLevels = new JObject();
string sourceXML = "<Root>
<FirstLevel id=\"1\" name=\"1\">
<Item id=\"1\" name=\"1.1\" />
<Item id=\"2\" name=\"1.2\" />
</FirstLevel>
<FirstLevel id=\"2\" name=\"2\">
<Item id=\"1\" name=\"2.1\" />
</FirstLevel>
</Root>";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sourceXML);
XmlNodeList nodeList;
nodeList = xmlDoc.SelectNodes("Root/FirstLevel");
JArray jarray = new JArray();
foreach (XmlNode node in nodeList)
{
string json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(node);
jarray.Add(JObject.Parse(json));
}
firstLevels["result"] = jarray;
foreach(var first in firstLevels["result"].Children<JObject>())
{
Console.WriteLine(first["FirstLevel"]["#name"].Value<string>());
foreach (var item in first["FirstLevel"]["Item"])
Console.WriteLine(" -- " + item["#name"].Value<string>());
}
So you're doing this:
string json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(node);
For these two nodes:
<FirstLevel id=\"1\" name=\"1\">
<Item id=\"1\" name=\"1.1\" />
<Item id=\"2\" name=\"1.2\" />
</FirstLevel>
<FirstLevel id=\"2\" name=\"2\">
<Item id=\"1\" name=\"2.1\" />
</FirstLevel>
Now, in the second case JSON .Net can't know whether to create a collection with a single element in it or just a single element (i.e. { a: {}} or { a: [{}]}) so it converts it to a single object.
This is not really what you're expecting though. You need it to always be an array to make your JSON consistent. There is this question already around this stuff: JSON.Net Xml Serialization misunderstands arrays that could be worth a read.
You, therefore, have a few choices I think:
Adjust your XML to give JSON .Net a hint on What to Do
To do this, you need to add a namespace to the root node (xmlns:json='http://james.newtonking.com/projects/json') and an attribute to the nodes you want to participate in the array (json:Array='true').
For example:
string sourceXML = "<Root xmlns:json='http://james.newtonking.com/projects/json'><FirstLevel id=\"1\" name=\"1\"><Item json:Array='true' id=\"1\" name=\"1.1\" /><Item json:Array='true' id=\"2\" name=\"1.2\" /></FirstLevel><FirstLevel id=\"2\" name=\"2\"><Item json:Array='true' id=\"1\" name=\"2.1\" /></FirstLevel></Root>";
Details here: http://james.newtonking.com/projects/json/help/?topic=html/ConvertingJSONandXML.html
Put in a Dummy Node to Force JSON .NET to Serialize to an Array
I don't really like this but if you cannot adjust the XML changing your code to something like this will do the trick:
foreach (XmlNode node in nodeList)
{
string json;
if (node.SelectNodes("Item").Count == 1)
{
// Append a dummy node and then strip it out - horrible!
node.AppendChild(xmlDoc.CreateNode("element", "Item", ""));
json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(node).Replace(",null]", "]");
}
else
{
json = Newtonsoft.Json.JsonConvert.SerializeXmlNode(node);
}
jarray.Add(JObject.Parse(json));
}
Check When you Read
Of course, you could just check the type when you handle your data, like so:
foreach (var first in firstLevels["result"].Children<JObject>())
{
Console.WriteLine(first["FirstLevel"]["#name"].Value<string>());
if (first["FirstLevel"]["Item"] is Newtonsoft.Json.Linq.JObject)
{
Console.WriteLine(" -- " + first["FirstLevel"]["Item"]["#name"].Value<string>());
}
else
{
foreach (var item in first["FirstLevel"]["Item"])
Console.WriteLine(" -- " + item["#name"].Value<string>());
}
}
You don't need to do this xml to json conversion manually
string sourceXML = "<Root><FirstLevel id=\"1\" name=\"1\"><Item id=\"1\" name=\"1.1\" /><Item id=\"2\" name=\"1.2\" /></FirstLevel><FirstLevel id=\"2\" name=\"2\"><Item id=\"1\" name=\"2.1\" /></FirstLevel></Root>";
var json = JsonConvert.SerializeObject(XDocument.Parse(sourceXML));
or to get a json array
var json2 = JsonConvert.SerializeObject(XDocument.Parse(sourceXML)
.Descendants("FirstLevel"));

xml root element missing

I have this xml that i am reading from a url which when i viewsource looks like this:
<xml>
<root>
<item>
<id>1</id>
<name>Testing</name>
</item>
<item>
<id>2</id>
<name>Testing2</name>
</item>
</root>
</xml>
when i ran the code below it keep saying root element is missing? i do have a root element.
public void myfunction()
{
WebRequest request = WebRequest.Create("http://www.site.com/file.xml");
WebResponse response = request.GetResponse();
Stream dataStream = response.GetResponseStream();
string[] arr = XDocument.Load(dataStream).Root.Descendants("Name").Elements().Select(element => element.Value).ToArray(); //error says root element missing
foreach (var item in arr)
{
MessageBox.Show(item.ToString());
}
}
There is no Name element in your xml file. Xml is case-sensitive. You should use lower-case name:
string[] arr = XDocument.Load(dataStream).Root
.Descendants("name")
.Select(name => (string)name)
.ToArray();
BTW your name elements do not have nested elements.
UPDATE: If you want to get values of name elements, then simply cast these elements to string. You can also use Select(name => name.Value) here. Just don't try to get nested elements of name - they don't have any.

Categories

Resources