C# XML Select single sub node in node by value - c#

I have the following problem, I want to select the book with the author "Johnny Dapp33", which unfortunately does not work.
XML Code:
<employees xmlns:bar="http://www.bar.org">
<employee id="Test1">
<name>Johnny Dapp</name>
<author>Al Pacino</author>
</employee>
<employee id="Test2">
<name>Johnny Dapp33</name>
<author>Al Pacino</author>
</employee>
</employees>
I would have tried it via ".SelectSingleNode", unfortunately I always fail with the XPath.
Thank you for your help!

Let's say we have a file called Employees.xml in our project directory.
We can load the xml file in our memory by using this assignment:
XmlDocument doc = new XmlDocument();
doc.Load("Employees.xml");
Second we try to find a single node (presuambly) by its id in a structure employees/employee (this is our path), no we have to add the search param (id in this case) like this:
XmlNode singleNode = doc.SelectSingleNode("/employees/employee[#id='Test1']");
Console.WriteLine(singleNode.OuterXml);
However if we only know the name we are looking for we can also search for that specific value like this. We search in the employee node for the node value of name with the value of Johnny Dapp33:
XmlNode singleNode = doc.SelectSingleNode("descendant::employee[name='Johnny Dapp33']");
Console.WriteLine(singleNode.OuterXml);

While dealing with XMl, it is better to use LINQ to XML API.
It is available in the .Net Framework since 2007.
c#
void Main()
{
const string filePath = #"e:\Temp\WizardZZ.xml";
XDocument xdoc = XDocument.Load(filePath);
var employee = xdoc.Descendants("employee")
.Where(d => d.Elements("name").FirstOrDefault().Value.Equals("Johnny Dapp33"));
Console.WriteLine(employee);
}
Output
<employee id="Test2">
<name>Johnny Dapp33</name>
<author>Al Pacino</author>
</employee>

If you need to use XPath (and there must be a very strong reason for it), you can use it with XElement:
var xml = """
<employees xmlns:bar="http://www.bar.org">
<employee id="Test1">
<name>Johnny Dapp</name>
<author>Al Pacino</author>
</employee>
<employee id="Test2">
<name>Johnny Dapp33</name>
<author>Al Pacino</author>
</employee>
</employees>
""";
var x = XElement.Parse(xml);
var employees = x.XPathSelectElements("/employee[name='Johnny Dapp33']");
if (employees is not null)
{
foreach (var employee in employees)
{
WriteLine((string)employee.Element("name") ?? "[name] not found");
}
}
else
{
WriteLine("did not find any employees");
}

Related

Reading XML File - reading a child node which has any number of subnodes

So I'm currentlty trying to parse an XML file which looks like so:
<employees>
<employee>
<id>1</id>
<projects>
<projectID>7</projectID>
<projectID>3</projectID>
</projects>
</employee>
<employee>
<id>2</id>
<projects>
<projectID>4</projectID>
</projects>
</employee>
</employees>
I'm trying to read in each employee and any number of projects which appear. The Employee object is a string and list(int).
Currently I have:
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlNodeList xmlNodes = doc.DocumentElement.SelectNodes("/employees/employee");
foreach (XmlNode xmlNode in xmlNodes)
{
string id;
List<int> projects = new List<int>();
id = xmlNode.SelectSingleNode("id").InnerText;
//this is the bit. What I have works but it feels like it could
//be majorly refined. Is there a better way to construct the foreach below?
foreach (XmlNode node in xmlNode.ChildNodes.Item(1))
//index 1 is the projects node
{
projects.Add(int.Parse(node.InnerText));
}
//
Employee e = new Employee(id, projects);
e.Add(e);
}
If the XML file itself is an issue it can be changed to accomodate the parsing.
Thank you.
It will be much easier with LINQ to XML:
var xDoc = XDocument.Load(path);
var employees = (from e in xDoc.Root.Elements("employee")
let projects = e.Element("projects")
.Elements("projectID")
.Select(p => (int)p)
.ToList()
let id = (string)e.Element("id")
select new Employee(id, projects)).ToList();
You need using System.Linq and using System.Xml.Linq to make it work.

Linq to XML... null and missing elements

I have an XML file
<Person>
<PersonItem id="0">
<Time>1/8/2014</Time>
<Step><![CDATA[Normal]]></Step>
<HasAddress/>
<Address/>
</PersonItem>
<PersonItem id="1">
<Time>1/8/2014 3:21:45 PM</Time>
<Step><![CDATA[Normal]]></Step>
<HasAddress/>
<Address/>
</PersonItem>
<PersonItem id="2">
<Time>1/8/2014</Time>
<Step><![CDATA[Normal]]></Step>
<HasAddress>Main</HasAddress>
<Address>
<AddressItem id="0" location=5>
<Address>15 Oak</Address>
</AddressItem>
<AddressItem id="1" location=7>
<Address>12 Maple</Address>
</AddressItem>
<AddressItem id="2" location=8>
<Address>30 Beech</Picture>
</AddressItem>
</Address>
</PersonItem>
</Person>
I want to put to retrieve the information and send some of it to a database. I've tried several different ways of dealing with this and I believe I'm close. Here is the Linq I tried.
public void DoIt(fileName)
{
XElement xml = XElement.Load(fileName);
var items = from item in xml.Elements("PersonItem")
where (from x in item.Elements("HasAddress")
where x.Element("HasAddress") != null
select x).Any()
select item;
Array.ForEach(items.ToArray(),
o=>Console.WriteLine(o.Element("Time").Value));
Console.ReadLine();
}
The problem is nothing is being returned.
Could be just a typo but in your xml file there is this tag error.
<Address>30 Beech</Picture>
which should be:
<Address>30 Beech</Address>
Try this:
XElement xml = XElement.Load(fileName);
var items = xml.Descendants("PersonItem")
.Where(x => (string)x.Element("HasAddress") != null)
.Select(x => x);
XDocument xml = XDocument.Load("Input.xml");
var items = from item in xml.Root.Elements("PersonItem")
where !string.IsNullOrEmpty((string)item.Element("HasAddress"))
select item;
For your sample XML document returns only the last PersonItem element.

Accessing xml elements using LINQ to XML

I have a xml document like this and I need to access the "employees", "employee" elements so I am trying to use linq's XDocument class to get the employee elements but it always returns empty value.
Sample xml:
<organization>
<metadata>
</metadata>
<main>
<otherInfo>
</otherInfo>
<employeeInfo>
<employees>
<employee>
<id>1</id>
<name>ABC</name>
</employee>
<employee>
<id>2</id>
<name>ASE</name>
</employee>
<employee>
<id>3</id>
<name>XYZ</name>
</employee>
</employees>
</employeeInfo>
</main>
</organization>
C# code:
XDocument xDoc = XDocument.Parse(xmlString);
var allEmployees = from d in xDoc.Descendants("employeeInfo")
from ms in d.Elements("employees")
from m in ms.Elements("employee")
select m;
It kind of depends on what information you need. Your select returns an IEnumerable list.
This code will print out each employee
string xmlString = #"<organization>
<metadata>
</metadata>
<main>
<otherInfo>
</otherInfo>
<employeeInfo>
<employees>
<employee>
<id>1</id>
<name>ABC</name>
</employee>
<employee>
<id>2</id>
<name>ASE</name>
</employee>
<employee>
<id>3</id>
<name>XYZ</name>
</employee>
</employees>
</employeeInfo>
</main>
</organization>";
XDocument xDoc = XDocument.Parse(xmlString);
var allEmployees = from d in xDoc.Descendants("employeeInfo")
from ms in d.Elements("employees")
from m in ms.Elements("employee")
select m;
foreach (var emp in allEmployees) {
Console.WriteLine(emp);
}
Console.Read();
XDocument xDoc = XDocument.Parse(xmlString);
var allEmployees = (from r in xDoc.Descendants("employee")
select new
{
Id = r.Element("id").Value,
Name = r.Element("name").Value
}).ToList();
foreach (var r in allEmployees)
{
Console.WriteLine(r.Id + " " + r.Name);
}
Just use Descendants("Employee");
XDocument xDoc = XDocument.Parse(xmlString);
var allEmployees = xDoc.Descendants("employee").ToList();

Getting an XElement with a namespace via XPathSelectElements

I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}

Reading XML nodes with LINQ

I have a trouble reading from XML file using LINQ.
Here is my XML file
<?xml version="1.0" encoding="utf-8"?>
<Employees>
<Employee>
<Name Type="First">Jack</Name>
<Name Type="Last">Black</Name>
</Employee>
<Employee>
<Name Type="First">John</Name>
<Name Type="Last">Blue</Name>
</Employee>
<Employee>
<Name Type="First">Dan</Name>
<Name Type="Last">Red</Name>
</Employee>
<Employee>
<Name Type="First">Patrick</Name>
<Name Type="Last">Green</Name>
</Employee>
</Employees>
The code I am using is following
XElement doc = XElement.Load("xmldoc.xml");
var query = from x in doc.Elements("Employee") where x.Element("Name").Attribute("Type").Value == "First" select x;
foreach (XElement item in query)
{
Console.WriteLine(item.Element("Name").Value);
}
This code returns me all first names but when i change attribute value from first to last it comes blank.
When i switch name nodes it retuns last names. For me it looks like for each employe query returns values from first name node and ignoring the second one. Could you please help me fix this?
The problem is that the x.Element("Name") call will return the first Name element. You actually need to query all the Name elements and filter for the one with the Last attribute value.
Try this instead:
var query = from x in doc.Elements("Employee").Elements("Name")
where x.Attribute("Type").Value == "Last"
select x;
foreach (XElement item in query)
{
Console.WriteLine(item.Value);
}

Categories