Looping through items and read desired elements - c#

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

Related

How to clone single XML from hierarchy based on matching XmlNode in C#

Here is the XML structure:
<root>
<listOfItems>
<item>
<lineItem>1</lineItem>
<itemDetail>
<partNum>A1</partNum>
<color>red</color>
<qty>4</qty>
</itemDetail>
</item>
<item>
<lineItem>2</lineItem>
<itemDetail>
<partNum>B2</partNum>
<color>blue</color>
<qty>2</qty>
</itemDetail>
</item>
<item>
<lineItem>3</lineItem>
<itemDetail>
<partNum>C3</partNum>
<color>green</color>
<qty>1</qty>
</itemDetail>
</item>
</listOfItems>
</root>
Knowing that the partNum is B2, how would I be able to clone the entire item B2 belongs to so I have 2 identical B2 items.
You could use the CloneNode function to copy the node and AppendChild to attach it to the relevant place in the hierarchy.
// find the node
var target = doc.SelectSingleNode("root/listOfItems/item/itemDetail/partNum[text()='B2']");
// clone
var clonedNode = target.ParentNode.CloneNode(true);
// attach
target.ParentNode.ParentNode.AppendChild(clonedNode);
Here is a System.Xml.Linq solution.
//Load the XML Document
XDocument xdoc = XDocument.Load(xDocPath);
//Find the XMLNode
XElement xB2 = xdoc.Root.Element("listOfItems").Elements("item").FirstOrDefault(it => it.Element("itemDetail").Element("partNum").Value.Equals("B2"));
//Clone the XMLNode
XElement xB2Copy = new XElement(xB2);
The XElement xB2 is linked to the xdoc.
The XElement xB2Copy is not linked to xdoc.
You would have to add it first, here are some examples.
xdoc.Root.Element("listOfItems").Add(xB2Copy);
xB2.AddAfterSelf(xB2Copy);
xB2.AddBeforeSelf(xB2Copy);
Try following :
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication166
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement listOfItems = doc.Descendants("listOfItems").FirstOrDefault();
XElement itemB = listOfItems.Elements("item").Where(x => x.Descendants("partNum").Any(y => (string)y == "B2")).FirstOrDefault();
listOfItems.Add(XElement.Parse(itemB.ToString()));
}
}
}

C# XDocument Read all Nodes from XML-File

I have an XML-file like this:
<Inventory>
<Item>
<Name>Super Mario Bros</Name>
<Count>14</Count>
<Price>29,99</Price>
<Comment><No Comments on this Product></Comment>
<Artist>N/A</Artist>
<Publisher>Nintendo</Publisher>
<Genre>Video Games</Genre>
<Year>1985</Year>
<ProductID>001</ProductID>
</Item>
<Item>
<Name>The Legend of Zelda</Name>
<Count>12</Count>
<Price>34,99</Price>
<Comment><No Comments on this Product></Comment>
<Artist>N/A</Artist>
<Publisher>Nintendo</Publisher>
<Genre>Video Games</Genre>
<Year>1986</Year>
<ProductID>002</ProductID>
</Item>
<Item>
<Name>Street Fighter</Name>
<Count>82</Count>
<Price>19,99</Price>
<Comment><No Comments on this Product></Comment>
<Artist>N/A</Artist>
<Publisher>Nintendo</Publisher>
<Genre>Video Games</Genre>
<Year>1987</Year>
<ProductID>003</ProductID>
</Item>
</Inventory>
(There are more Items, but they are all the same, except for the values.)
Now I want to iterate through each Item and extract every value from each node. Here's what I've tried so far:
var xDocument = XDocument.Load(FilePath_CSVToXML);
string xml = xDocument.ToString();
StringBuilder sb = new StringBuilder();
foreach (XElement xe in xDocument.Descendants("Inventory")) {
sb.Append(xe);
}
Console.WriteLine(sb.ToString());
Console.ReadLine();
The code above properly displays the XML-file, but it keeps the Nodes. (Name, Count, Price, etc.) I only want the values.
You need to use the Value property, i.e. sb.Append(xe.Value).
Try code below. The innertag of Comment doesn't need angle brackets so remove.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication49
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("Item").Select(x => new {
name = (string)x.Element("Name"),
count = (int)x.Element("Count"),
price = (decimal)x.Element("Price"),
comment = (string)x.Element("Comment"),
artist = (string)x.Element("Artist"),
publisher = (string)x.Element("Publisher"),
genre = (string)x.Element("Genre"),
year = (int)x.Element("Year"),
productID = (string)x.Element("ProductID")
}).ToList();
}
}
}

XmlReader on node without value

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

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

How to get node by name?

It's a simple task, but I can't make it work. Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<product>
<item1></item1>
<item2></item2>
<item3></item3>
</product>
I'd like to get all nodes within product. Following two attempts return no nodes, I don't see why:
XDocument meteoDoc = XDocument.Load("data.xml");
foreach (var item in meteoDoc.Descendants("product")) {//...}
foreach (var item in meteoDoc.Descendants().Where(x => x.Name == "product").Nodes()) {//...}
The following, as expected, would return me all nodes:
foreach (var item in meteoDoc.DescendantNodes()) { //...}
Thx for any tipps, I can't see the problem... :-/
Your first attempt is asking for all the Descendants called product. Your second attempt is finding the direct child nodes of all descendants called product.
It's possible that it's looking for descendants within the root element... if you know that the root element is called product, you could just use:
foreach (var item in meteoDoc.Root.Descendants())
(I can't test your existing code to find out exactly why it's not working for you right now, I'm afraid.)
Note that Descendants will only find descendant elements - if you want all descendant nodes, you need the DescendantNodes method.
Use this:
XDocument meteoDoc = XDocument.Load("data.xml");
foreach (var item in meteoDoc.Root.Descendants())
{
// ...
}
Try this:
foreach (var item in meteoDoc.Descendants("product"))
{
foreach (var prod in item.Descendants())
{
// do something
}
}
I have made a few change to xml, in this way the data is more well-formed and i can give you a piece of code that accomplish your task.
static void Main(string[] args)
{
//Data.xml
/*
<?xml version="1.0" encoding="UTF-8"?>
<product>
<item>
<name>test</name>
</item>
<item>
<name>test2</name>
</item>
<item>
<name>test3</name>
</item>
</product>
*/
XDocument meteoDoc = XDocument.Load("data.xml");
var result = from c in meteoDoc.Descendants("item")
select new { Name = c.Element("name").Value };
foreach (var item in result)
{
Console.WriteLine(item.Name);
}

Categories

Resources