Reading a XML File using XPath Expression in C# - c#

I am currently having a problem with reading a XML file using XPath expression. I have used the XmlDocument class. When I try reading a particular node from the XML, I get an empty list. The node which I am trying to read is the ID below ProductionRequest.
Here is the XML file which I tried to read:
<?xml version="1.0" encoding="iso-8859-1"?>
<ProductionSchedule xmlns="http://www.wbf.org/xml/b2mml-v02">
<ID>00000020000000</ID>
<Location>
<EquipmentID>8283</EquipmentID>
<EquipmentElementLevel>Site</EquipmentElementLevel>
<Location>
<EquipmentID>0</EquipmentID>
<EquipmentElementLevel>Area</EquipmentElementLevel>
</Location>
</Location>
<ProductionRequest>
<ID>0009300000000</ID>
<ProductProductionRuleID>W001</ProductProductionRuleID>
<StartTime>2017-04-20T23:57:20</StartTime>
<EndTime>2017-04-20T24:00:00</EndTime>
</ProductionRequest>
</ProductionSchedule>
This is the code which I used to read the above XML
using System;
using System.Xml.Linq;
using System.Xml;
using System.Xml.XPath;
namespace XML
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
string fullName = "F:\\Programming\\XML\\Example XML.xml";
XmlDocument xreader = new XmlDocument();
xreader.Load(fullName);
XmlNode root = xreader.DocumentElement;
XmlNodeList xnList1 =
xreader.SelectNodes("/ProductionSchedule/ProductionRequest/ID");
}
}
}
I could not find the cause for this problem. Could anyone help me in this regard. Looking forward for valuable inputs.

Your xml contains namespace http://www.wbf.org/xml/b2mml-v02 at root level node <ProductionSchedule>
And you are using the XPath expression /ProductionSchedule/ProductionRequest/ID but this XPath expression is not suitable for this xml document and that's why you can't get any desired value.
You need to use the below XPath expression to get the id's of all <ProductionRequest> node.
XmlNodeList xnList1 = xreader.SelectNodes("//*[name()='ProductionSchedule']/*[name()='ProductionRequest']/*[name()='ID']");
OR you can add namespace manually like
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xreader.NameTable);
nsmgr.AddNamespace("x", "http://www.wbf.org/xml/b2mml-v02");
XmlNodeList xnList1 = xreader.SelectNodes("//x:ProductionSchedule/x:ProductionRequest/x:ID", nsmgr);
And finally, you can read id from any of the parent nodes in variable xnList1 like
foreach (XmlNode id in xnList1)
{
Console.WriteLine(id.InnerText);
}
Output:

Related

Specflow C# - load/edit/save xml file

Im using Visual studio 2019 and I would like to load, edit and save xml file.
xml file:
<?xml version="1.0" encoding="utf-8"?>
<Firstdelivery25 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.acm.nl/Operations/2018/09">
<Firstdelivery>
<MessageId xmlns="http://www.acm.nl/DataModel/2018/09">
<Source>ABC</Source>
</MessageId>
</Firstdelivery>
</Firstdelivery25>
Code in Visual studio:
using System.Linq;
using System.Xml.Linq;
using System.Xml;
using TechTalk.SpecFlow;
XDocument xdoc = XDocument.Load(#"C:\\XML files\\25.xml");
var element = xdoc.Elements("Source").FirstOrDefault();
if (element != null)
{
element.Value = "DEF";
}
xdoc.Save(#"C:\\VB_25.xml");
When I execute this, the test is passed successfully.
However, when I open the new created VB_25.xml file, the old value is still there.
Any help/suggestions/tips would be appreciated.
You can try to modifty XML Data by using XPathNavigator.
Here is a simple demo you can refer to.
http://www.acm.nl/DataModel/2018/09 is the xmlns of MessageId.
XmlDocument document = new XmlDocument();
document.Load("test.xml");
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
manager.AddNamespace("id", "http://www.acm.nl/DataModel/2018/09");
foreach (XPathNavigator nav in navigator.Select("//id:Source", manager))
{
nav.SetValue("Tessssssst");
}
document.Save(#"new.xml");
var element = xdoc.Elements("Source").FirstOrDefault();
Given your provided XML elements will always be null and so you do nothing with your XML and that's why the result looks like the input.
Reason1: Elements() is looking for direct children only and <Source> is not a direct child in your doc.
See https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xcontainer.elements?view=net-5.0
Reason2: <Source> tag has a different namespace than the doc itself and so the query is looking in the wrong namespace.
Solution:
XNamespace ns = "http://www.acm.nl/DataModel/2018/09";
var element = xdoc.Descendants(ns + "Source").FirstOrDefault();
See https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xcontainer.descendants?view=net-5.0 which is searching for children including their children.
If you expect a node to exist, I'd also recommend to use First() instead of FirstOrDefault(). This way you'll get an exception that would have shown you the reason for your problem.

Adding a root node to Xml Document having a existing root node in C#

I have an xml document that may look like this
<Stuff>
<SomeStuff></SomeStuff>
</Stuff>
and I want to add a new root to this document, to make it look like this
<Root>
<Stuff>
<SomeStuff></SomeStuff>
</Stuff>
</Root>
This is what i tried
string inputXml = " <Stuff>
<SomeStuff></SomeStuff>
</Stuff>";
XmlDocument firstLossRootNode = new XmlDocument();
firstLossRootNode.LoadXml("<Root />");
var economyDocument = = XDocument.Parse(inputXml);
firstLossRootNode.DocumentElement.AppendChild(economyDocument.Document);
I was trying to Append it has a child but i was getting compile time error
Is there a way to do this in c# using Xdocument class.
In your example, you are using the XmlDocument class for your firstLossRootNode, but the XDocument class for your economyDocument. Is this by design? If not, the following code will do what you are trying to do:
using System;
using System.Xml.Linq;
string inputXml = "<Stuff><SomeStuff></SomeStuff></Stuff>";
XDocument firstLossRootNode = XDocument.Parse("<Root />");
XDocument economyDocument = XDocument.Parse(inputXml);
firstLossRootNode.Root.Add(economyDocument.FirstNode);

Modify a xml node with spacing and quotes as attricutes

I am trying to access to the child node of an XML but the my first XML node has spacing and quotes as attributes.
var xml = #"<Envelope xsd "http">
<Catalog>
<Price>
<Value Default ="yes">P1</Value>
</Price>
</Catalog>
</Envelope>";
Im trying to change the attribute value of Default from "yes" to "1" but node always returns null.
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var node = doc.SelectSingleNode("/*/Catalog/Price/Value");
Any ideas?
I do not think that is valid xml, did you perhaps mean the following
using System;
using System.Globalization;
using System.Xml;
namespace ConsoleApplication9
{
class Program
{
private static void Main(string[] args)
{
//Valid XML
string xml = #"<Envelope xsd='http'>
<Catalog>
<Price>
<Value Default='yes'>P1</Value>
</Price>
</Catalog>
</Envelope>";
var doc = new XmlDocument();
doc.LoadXml(xml);
//Select the Value Node
XmlNode node = doc.SelectSingleNode("/*/Catalog/Price/Value");
//Set the Default attribute to 1
node.Attributes["Default"].Value = 1.ToString(CultureInfo.InvariantCulture);
//Check the output
Console.WriteLine(doc.InnerXml.ToString(CultureInfo.InvariantCulture));
//Press enter to exit
Console.ReadLine();
}
}
}
Just saying.
See http://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx.
Use // instead of /* since // gets the root of the document
This might appear a little hardcoded but it should work:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
Namespace ns= "http"; //set the namespace of the root node here
//the following is where you change the value to 1
doc.Document.Descendants(ns+"Envelope").FirstorDefault().Descendants(ns+"Catalog").Descendants(ns+"Price").FirstorDefault().Elements("Value").Attribute("Default").SetValue("1");
Also, the xml looks a little wrong to me, as someone mentioned, the root node needs to be corrected.

XML SelectNode() returns nothing. Why does namespace matter?

I have code to get the nodes of a root element:
xmlNodes = rootElement.SelectNodes("DefinitionName");
It's not returning nodes that exist. In the debugger, I can expand rootElement to find DefinitionName. Apparently the problem is the fact that the file has a namespace defined (see XML below). MSDN says that I have to do something like this to get nodes to return:
Note: This has nothing to do with my code. This is the example from MSDN:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ab", "http://www.lucernepublishing.com");
XmlNodeList nodelist = doc.SelectNodes("//ab:book", nsmgr);
I have two questions:
Why does the namespace matter? If I want a node, and it exists, just give it to me.
My app processes many XML files. How am I supposed to specify the namespace (nsmgr.AddNamespace())? Do I need to parse the file to get that first?
I can't help but feeling that I'm taking the long, angst-filled way of doing this.
This is the XML:
<?xml version="1.0" encoding="utf-8"?>
<SessionStateInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
z:Id="1" z:Type="Company.Apps.MoreHere.Session.SessionStateInfo"
z:Assembly="assembly info here"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">
<CoaterNumber>25</CoaterNumber>
<DefinitionName z:Id="2">Two Line</DefinitionName>
<EnableManualMode>true</EnableManualMode>
<SessionStateInfo ....
xmlns="http://schemas.datacontract.org/2004/07/MoreHere.Session">
means that this element and all its descendants are in the http://schemas.datacontract.org/2004/07/MoreHere.Session namespace. Since unprefixed names in an XPath always refer to elements in no namespace, you will need to bind this URI to a prefix and use that prefix in your XPath, even though no prefix is in use in the document.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("mhs", "http://schemas.datacontract.org/2004/07/MoreHere.Session");
xmlNodes = rootElement.SelectNodes("mhs:DefinitionName", nsmgr);
If you know that the element(s) you are looking for will always have the same local name but may or may not have a namespace (or may have different namespaces) then you can use XPath tricks like
rootElement.SelectNodes("*[local-name() = 'DefinitionName']");
It matters because if there's a namespace attached, then "DefinitionName" is not enough. Imagine you have been given a list of people, all with the first name John:
John Smith
John Jones
John Murphy
What you're doing is the equivalent of asking for "John," instead of "John Smith" for example.
This isn't exactly the answer to the question but it may be an alternative solution using XDocument
using System;
using System.Dynamic;
using System.Xml.Linq;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
XDocument document = XDocument.Load("SessionStateInfo.xml");
XNamespace nameSpace = document.Root.GetDefaultNamespace();
XElement node = document.Descendants(nameSpace + "DefinitionName").FirstOrDefault();
if (node != null)
{
Console.WriteLine("Cool! XDocument rocks! value: {0}", node.Value);
}
else
{
Console.WriteLine("Spoot! Didn't find it!");
}
}
}
}
This seems to work if the default namespace is specified or not.

Get nodes from xml files

How to parse the xml file?
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>link</loc>
<lastmod>2011-08-17T08:23:17+00:00</lastmod>
</sitemap>
<sitemap>
<loc>link</loc>
<lastmod>2011-08-18T08:23:17+00:00</lastmod>
</sitemap>
</sitemapindex>
I am new to XML, I tried this, but it seems to be not working :
XmlDocument xml = new XmlDocument(); //* create an xml document object.
xml.Load("sitemap.xml");
XmlNodeList xnList = xml.SelectNodes("/sitemapindex/sitemap");
foreach (XmlNode xn in xnList)
{
String loc= xn["loc"].InnerText;
String lastmod= xn["lastmod"].InnerText;
}
The problem is that the sitemapindex element defines a default namespace. You need to specify the namespace when you select the nodes, otherwise it will not find them. For instance:
XmlDocument xml = new XmlDocument();
xml.Load("sitemap.xml");
XmlNamespaceManager manager = new XmlNamespaceManager(xml.NameTable);
manager.AddNamespace("s", "http://www.sitemaps.org/schemas/sitemap/0.9");
XmlNodeList xnList = xml.SelectNodes("/s:sitemapindex/s:sitemap", manager);
Normally speaking, when using the XmlNameSpaceManager, you could leave the prefix as an empty string to specify that you want that namespace to be the default namespace. So you would think you'd be able to do something like this:
// WON'T WORK
XmlDocument xml = new XmlDocument();
xml.Load("sitemap.xml");
XmlNamespaceManager manager = new XmlNamespaceManager(xml.NameTable);
manager.AddNamespace("", "http://www.sitemaps.org/schemas/sitemap/0.9"); //Empty prefix
XmlNodeList xnList = xml.SelectNodes("/sitemapindex/sitemap", manager); //No prefixes in XPath
However, if you try that code, you'll find that it won't find any matching nodes. The reason for this is that in XPath 1.0 (which is what XmlDocument implements), when no namespace is provided, it always uses the null namespace, not the default namespace. So, it doesn't matter if you specify a default namespace in the XmlNamespaceManager, it's not going to be used by XPath, anyway. To quote the relevant paragraph from the Official XPath Specification:
A QName in the node test is expanded into an expanded-name using the
namespace declarations from the expression context. This is the same
way expansion is done for element type names in start and end-tags
except that the default namespace declared with xmlns is not used: if
the QName does not have a prefix, then the namespace URI is null (this
is the same way attribute names are expanded). It is an error if the
QName has a prefix for which there is no namespace declaration in the
expression context.
Therefore, when the elements you are reading belong to a namespace, you can't avoid putting the namespace prefix in your XPath statements. However, if you don't want to bother putting the namespace URI in your code, you can just use the XmlDocument object to return the URI of the root element, which in this case, is what you want. For instance:
XmlDocument xml = new XmlDocument();
xml.Load("sitemap.xml");
XmlNamespaceManager manager = new XmlNamespaceManager(xml.NameTable);
manager.AddNamespace("s", xml.DocumentElement.NamespaceURI); //Using xml's properties instead of hard-coded URI
XmlNodeList xnList = xml.SelectNodes("/s:sitemapindex/s:sitemap", manager);
Sitemap has 2 sub nodes "loc" and "lastmod". The nodes that you are accessing are "name" and "url". that is why you are not getting any result. Also in your XML file the last sitemap tag is not closed properly with a corresponding Kindly try xn["loc"].InnerText and see if you get the desired result.
I would definitely use LINQ to XML instead of the older XmlDocument based XML API. You can accomplish what you are looking to do using the following code. Notice, I changed the name of the element that I am trying to get the value of to 'loc' and 'lastmod', because this is what is in your sample XML ('name' and 'url' did not exist):
XElement element = XElement.Parse(XMLFILE);
IEnumerable<XElement> list = element.Elements("sitemap");
foreach (XElement e in list)
{
String LOC= e.Element("loc").Value;
String LASTMOD = e.Element("lastmod").Value;
}

Categories