C# XML querying using XPath containing a namespace - c#

In C# I'm struggling to understand how to query XML using XPath that includes a namespace.
XML:
<?xml version="1.0" encoding="utf-8"?>
<SomeEntity xmlns="http://www.example.com/Schemas/SomeEntity/2023/01">
<Child>
<TextValue>Some text</TextValue>
</Child>
</SomeEntity>
XPath:
/SomeEntity[#xmlns="http://www.example.com/Schemas/SomeEntity/2023/01"]/Child/TextValue
C#:
XmlNodeList nodes = doc.SelectNodes(xpath); \\ doc being of type XmlDocument
SelectNodes always results in an empty XmlNodeList.
What's the best way in C# to resolve XPath queries that include a namespace in this way?
I get the same result when using XDocument:
var doc = XDocument.Parse(xml);
string xpath = #"/SomeEntity[#xmlns=""http://www.example.com/Schemas/SomeEntity/2023/01""]/Child/TextValue";
var results = doc.XPathSelectElements(xpath);

It is better to use LINQ to XML.
There is no need to hardcode the default namespace. The GetDefaultNamespace() call gets it for you.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<SomeEntity xmlns='http://www.example.com/Schemas/SomeEntity/2023/01'>
<Child>
<TextValue>Some text</TextValue>
</Child>
</SomeEntity>");
XNamespace ns = xdoc.Root.GetDefaultNamespace();
string TextValue = xdoc.Descendants(ns + "TextValue")?.FirstOrDefault().Value;
Console.WriteLine("TextValue='{0}'", TextValue);
}
Output
TextValue='Some text'

For querying with the xmlns attibute condition, with #xmlns is not working. Instead, you need namespace-uri().
Pre-requisites:
Must add the XML namespace for query.
Provide the namespace prefix in the query.
For XmlDocument
string xpath = #"//se:SomeEntity[namespace-uri()='http://www.example.com/Schemas/SomeEntity/2023/01']/se:Child/se:TextValue";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("se", "http://www.example.com/Schemas/SomeEntity/2023/01");
var nodes = doc.DocumentElement.SelectNodes(xpath, mgr);
Console.WriteLine(nodes.Count);
Console.WriteLine(nodes.Item(0).FirstChild.Value);
For XDocument
using System.Linq;
using System.Xml.XPath;
using System.Xml.Linq;
string xpath = #"//se:SomeEntity[namespace-uri()='http://www.example.com/Schemas/SomeEntity/2023/01']/se:Child/se:TextValue";
var xDoc = XDocument.Parse(xml);
var mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("se", "http://www.example.com/Schemas/SomeEntity/2023/01");
var results = xDoc.XPathSelectElements(xpath, mgr);
Console.WriteLine(results.FirstOrDefault()?.Value);

Related

C# How to insert XPathnavigator into XPathnavigator

Let's say I have the following XML file
<root></root>
and the following file
<child>
<more-childeren> </more-childeren>
</child>
How do I insert the second file into the first file to create the following file:
<root>
<child>
<more-childeren> </more-childeren>
</child>
</root>
I am receiving the second file as a XPathNavigator. What would be the fastest way to insert the XPathNavigator into the XML file?
If you work with XPathNavigators over editable tree structures like XmlDocument/XmlNode then use the AppendChild method taking an XPathNavigator https://learn.microsoft.com/en-us/dotnet/api/system.xml.xpath.xpathnavigator.appendchild?view=net-5.0#System_Xml_XPath_XPathNavigator_AppendChild_System_Xml_XPath_XPathNavigator_. That is at least the most convenient and API supported way, "the fastest" is a different criteria you would need to test.
A simple example working for me with .NET framework is
XmlDocument doc = new XmlDocument();
doc.LoadXml(#"<root></root>");
XPathNavigator nav = doc.DocumentElement.CreateNavigator();
XPathDocument doc2;
using (XmlReader xr = XmlReader.Create(new StringReader(#"<child>
<more-childeren> </more-childeren>
</child>")))
{
doc2 = new XPathDocument(xr);
}
XPathNavigator nav2 = doc2.CreateNavigator();
nav2.MoveToFirstChild();
nav.AppendChild(nav2);
doc.Save(Console.Out);
The call nav2.MoveToFirstChild(); seems to be crucial to not get the exception you mention in the comments.
Try following xml linq :
string xml1 = "<root></root>";
string xml2 = "<child><more-childeren> </more-childeren></child>";
XDocument doc1 = XDocument.Parse(xml1);
XElement root = doc1.Root;
root.Add(XElement.Parse(xml2));

Getting XML node using XPath returns null

I am trying to get a node in a simple XML, but no matter what I try I always get null.
I am guessing that the issue is the namespace.
I am simply trying to get the value of the ID element, 331377697.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("E:\\0323.xml");
XmlNamespaceManager manager = new XmlNamespaceManager(xmlDoc.NameTable);
manager.AddNamespace("cac", "http://xxxxxx Some URL XXXX");
manager.AddNamespace("cbc", "http://xxxxxx Some URL XXXX");
string query = "/StandardBusinessDocument/Invoice/cbc:ID";
XmlNode xmlNode = xmlDoc.SelectSingleNode(query, manager);
The XML:
<StandardBusinessDocument xmlns="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader">
<StandardBusinessDocumentHeader>
...
</StandardBusinessDocumentHeader>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:ccts="urn:un:unece:uncefact:documentation:2"
...
</Invoice>
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:CustomizationID>1234</cbc:CustomizationID>
<cbc:ProfileID>1234564</cbc:ProfileID>
<cbc:ID>331377697</cbc:ID>
<cbc:IssueDate>2017-03-23</cbc:IssueDate>
<cbc:InvoiceTypeCode listID="UNCL1001">380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode listID="ISO4217">NOK</cbc:DocumentCurrencyCode>
<cac:OrderReference>
<cbc:ID>146136</cbc:ID>
</cac:OrderReference>
...
You must specify the exact namespaces.
Also you have to specify namespace prefixes for all elements in the XPath.
manager.AddNamespace("nsDoc", "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader");
manager.AddNamespace("nsInvoice", "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2");
manager.AddNamespace("cbc", "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2");
string query = "/nsDoc:StandardBusinessDocument/nsInvoice:Invoice/cbc:ID";
It's supposed to work.

XPath issue with SelectSingleNode?

I'm trying to get a node from an XML string in C# using SelectSingleNode. The XML string comes from an external source.
string logonXML = #"<attrs xmlns=""http://www.sap.com/rws/bip\"">
<attr name=""userName"" type=""string""></attr>
<attr name=""password"" type=""string""></attr>
<attr name=""auth"" type=""string"" possibilities=""secEnterprise,secLDAP,secWinAD,secSAPR3"">secEnterprise</attr>
</attrs>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(logonXML);
XmlNode root = doc.DocumentElement;
XmlNode usernameXML = root.SelectSingleNode("//attr[#name='userName']");
Debug.WriteLine(usernameXML.OuterXml);
However usernameXML is null. I've tried using both the doc and root with a couple variations of XPath queries, but can't seem to find the node. What is wrong with this XPath? Or am I using the library wrong?
You need to take into account the XML namespace that's defined on your root node!
Try something like this:
string logonXML = #"<attrs xmlns=""http://www.sap.com/rws/bip"">
<attr name=""userName"" type=""string""></attr>
<attr name=""password"" type=""string""></attr>
<attr name=""auth"" type=""string"" possibilities=""secEnterprise,secLDAP,secWinAD,secSAPR3"">secEnterprise</attr>
</attrs>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(logonXML);
// define the XML namespace(s) that's in play here
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", "http://www.sap.com/rws/bip");
// select including the XML namespace manager
XmlNode usernameXML = doc.SelectSingleNode("/ns:attrs/ns:attr[#name='userName']", nsmgr);
string test = usernameXML.InnerText;

C#: Read XML Attribute

Using C#2.0 and VIsualStudio2005
I'm trying to access the "DisplayName" inside "MonitorResponseRecord"
from an XML file like the one Below:
<Magellan xsi:schemaLocation="http://tempuri.org/XMLSchema.xsd ..\Schema\Configuration.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://tempuri.org/XMLSchema.xsd">
<SchemaVersion>1.0</SchemaVersion>
<MonitorScope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="CleanStationChemicalManifoldFeed5" xmlns="http://tempuri.org/XMLSchema.xsd">
<PersonalSafety>
<MonitorResponseRecord Enabled="true" DisplayName="ChemicalManifoldFeed5ControllerFault">
<ExpressionMonitor>
<Expression>(ChemicalManifold.Feed5.DispenseValve = Open) and ((ChemicalManifold.Feed5.ViolatedRegion = HighProcess) or (ChemicalManifold.Feed5.ViolatedRegion = LowProcess) or (ChemicalManifold.Feed5.ViolatedRegion = Minimum))</Expression>
<TestInterval>0.1</TestInterval>
<MinimumTimeBetweenResponses>5</MinimumTimeBetweenResponses>
</ExpressionMonitor>
<Response>
<PostAlarm>
<AlarmName>ChemicalManifold_Feed5_ControllerFault</AlarmName>
<Parameter1 />
<Parameter2>Alarm Region = {ChemicalManifold.Feed5.ViolatedRegion}</Parameter2>
<Parameter3>{RecipeName}-{StepName}</Parameter3>
<Parameter4>FlowSetpoint = {ChemicalManifold.Feed5.Setpoint}-LPM, ActualFlow = {ChemicalManifold.Feed5.FlowMeter}-LPM</Parameter4>
</PostAlarm>
<ResponseEvent>
<TargetResource />
<Event>PA</Event>
<Reason>ChemicalSupply</Reason>
</ResponseEvent>
</Response>
</MonitorResponseRecord>
</PersonalSafety>
<PersonalSafety>
<MonitorResponseRecord Enabled="true" DisplayName = "PressureValveFailure">
...
...
...and soon
My current C# attempt is as follows, BUT it always hangs up when I try to XmlDocument.Load("");
XmlDocument doc = new XmlDocument();
doc.Load("../UMC0009.Configuration.Root.xml");
string attrVal = doc.SelectSingleNode("MonitorResponseRecord/#DisplayName").Value;
BUUUUT won't work :/ any help out there?
UPDATE:
the exception I recieve is as follows, and occures at the doc.Load("...") line:
{"Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function."} System.Exception {System.Xml.XPath.XPathException}
Your XPath query will be relative to the document root:
doc.SelectSingleNode("MonitorResponseRecord/#DisplayName")
To make it search anywhere in the doc prefix it with double slash:
doc.SelectSingleNode("//MonitorResponseRecord/#DisplayName")
If that still doesn't work I would try the above example after stripping out all those namespace declarations on the two root nodes.
Otherwise, with the namespace declarations you may find you need to define XML namespace mappings and use prefixes in your XPath like:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("x", "http://tempuri.org/XMLSchema.xsd");
doc.SelectSingleNode("//x:MonitorResponseRecord/#DisplayName")
What about:
XmlDocument doc = new XmlDocument();
doc.Load("UMC0009.Configuration.Root.xml");
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", "http://tempuri.org/XMLSchema.xsd");
string attrVal = doc.SelectSingleNode("//ns:MonitorResponseRecord/#DisplayName", nsmgr).Value;
Using a namespace manager, specify your namespace URI and use it in your XPath.
It works for me.

How to read XML in C# using Xpath

I have this XML
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetSKUsPriceAndStockResponse xmlns="http://tempuri.org/">
<GetSKUsPriceAndStockResult>
<RequestStatus>
<DateTime>2/28/2012 5:28:05 PM</DateTime>
<Message>S200</Message>
</RequestStatus>
<SKUsDetails>
<SKUDetails>
<SKU>N82E16834230265</SKU>
<Model>X54C-NS92</Model>
<Stock>true</Stock>
<Domain>newegg.com</Domain>
<SalePrice>439.99</SalePrice>
<ShippingCharge>0.00</ShippingCharge>
<Currency>USD</Currency>
</SKUDetails>
</SKUsDetails>
</GetSKUsPriceAndStockResult>
</GetSKUsPriceAndStockResponse>
</soap:Body>
</soap:Envelope>
How can I read <SKUDetails> Node using XPath?. What will be XNamespace for above XML?
Manipulate XML data with XPath and XmlDocument (C#)
or
its better to use LINQ to XML as your are using .net 4.0 and there is no need to learn XPath to traverse the xml tree.
Not sure about the xpath expression but you can code like this
string fileName = "data.xml";
XPathDocument doc = new XPathDocument(fileName);
XPathNavigator nav = doc.CreateNavigator();
// Compile a standard XPath expression
XPathExpression expr;
expr = nav.Compile("/GetSKUsPriceAndStockResponse/GetSKUsPriceAndStockResult/SKUsDetails/SKUDetails");
XPathNodeIterator iterator = nav.Select(expr);
try
{
while (iterator.MoveNext())
{
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
as #Kirill Polishchuk answered - SKUDetails is defined in http://tempuri.org/
he shows you how to get using XDocument
you can use alsow XmlDocument like this:
var dom = new XmlDocument();
dom.Load("data.xml");
var mgr = new XmlNamespaceManager(dom.NameTable);
mgr.AddNamespace("a", "http://tempuri.org/");
var res = dom.SelectNodes("//a:SKUDetails", mgr);
SKUsDetails is defined in http://tempuri.org/ namespace. You can use this code to select SKUsDetails using XPath:
var doc = XDocument.Load("1.xml");
var mgr = new XmlNamespaceManager(doc.CreateReader().NameTable);
mgr.AddNamespace("a", "http://tempuri.org/");
var node = doc.XPathSelectElement("//a:SKUsDetails", mgr);
To select SKUDetails use: //a:SKUsDetails/a:SKUDetails

Categories

Resources