Use XPath with XML namespace - c#

I want to process the xml below with XPath:
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="Cloud" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2014-01.2.3">
<Role name="Worker">
<Instances count="2" />
<ConfigurationSettings>
<Setting name="setting1" value="value1" />
<Setting name="setting2" value="value2" />
</ConfigurationSettings>
<Certificates>
</Certificates>
</Role>
</ServiceConfiguration>
Tere's a xmlns for the root element.
My code is this:
XElement doc = XElement.Load(xmlFilePath);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("prefix", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration");
XElement settingDiagConnectionString = doc.XPathSelectElement("//prefix:ServiceConfiguration/Role/ConfigurationSettings/Setting[1]", ns); // <===== always return null.
settingDiagConnectionString.SetAttributeValue("value", "hello, world!");
But the settingDiagConnectionString is always null.
Why?
Add 1
Thanks har07.
After adding prefix for every element, the following code works:
XmlDocument doc = new XmlDocument();
doc.Load(cscfgPath);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("prefix", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration");
XmlNode settingDiagConnectionString = doc.SelectNodes("/prefix:ServiceConfiguration/prefix:Role/prefix:ConfigurationSettings/prefix:Setting[1]", ns)[0];
settingDiagConnectionString.Attributes["value"].Value = "hello,world!";
But the following code still don't work. The settingDiagConnectionString is still null. Why?
XElement doc = XElement.Load(cscfgPath);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("prefix", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration");
XElement settingDiagConnectionString = doc.XPathSelectElement("/prefix:ServiceConfiguration/prefix:Role/prefix:ConfigurationSettings/prefix:Setting[1]", ns);
settingDiagConnectionString.SetAttributeValue("value", "hello, world!");

Default namespace has different nature. The element where the default namespace declared and all of it's descendant without different namespace declaration considered in the same default namespace. Therefore, you need to use the prefix for all descendats as well. This XPath worked fine for me :
XElement doc = XElement.Parse(xml);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("prefix", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration");
XElement settingDiagConnectionString = doc.XPathSelectElement("//prefix:Role/prefix:ConfigurationSettings/prefix:Setting[1]", ns);
settingDiagConnectionString.SetAttributeValue("value", "hello, world!");
UPDATE :
Responding to your update. That's because you're using XElement. In this case, doc it self already represents <ServiceConfiguration> element so you don't need to include "ServiceConfiguration" in the XPath :
/prefix:Role/prefix:ConfigurationSettings/prefix:Setting[1]
..or use XDocument instead of XElement if you want to make it work using the same XPath as for XmlDocument.

Related

How can I get value of element attribute in XML

Please how can I get value of attribute Value of element StatusCode in this XML:
<LogoutResponse
ID="_f525259e-7e91-4282-9dc3-a0da65a4a17a"
Version="2.0"
IssueInstant="2021-05-17T15:41:55Z"
InResponseTo="_5089729f-5cc0-4a66-a3c1-e710cde92897"
Destination="https://idp.xyz/logout.aspx"
xmlns="urn:oasis:names:tc:SAML:2.0:protocol">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://abc.xyz/api</Issuer>
<Status>
<StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</Status>
</LogoutResponse>
Due to the namespaces, this gets a little messy:
var doc = new XmlDocument();
doc.LoadXml(#"<LogoutResponse
ID=""_f525259e-7e91-4282-9dc3-a0da65a4a17a""
Version=""2.0""
IssueInstant=""2021-05-17T15:41:55Z""
InResponseTo=""_5089729f-5cc0-4a66-a3c1-e710cde92897""
Destination=""https://idp.xyz/logout.aspx""
xmlns=""urn:oasis:names:tc:SAML:2.0:protocol"">
<Issuer xmlns=""urn:oasis:names:tc:SAML:2.0:assertion"">https://abc.xyz/api</Issuer>
<Status>
<StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" />
</Status>
</LogoutResponse>");
var ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("oasis", "urn:oasis:names:tc:SAML:2.0:protocol");
var attr = (XmlAttribute)doc.SelectSingleNode(
"/oasis:LogoutResponse/oasis:Status/oasis:StatusCode/#Value", ns);
System.Console.WriteLine(attr.Value);
You can try with XmlDocument.
Note: since you have a name space (xmlns ...) all elements in your xml
by default in name space. hence you are getting object reference
error. I have updated the code and .netfiddle. please have a look
var xml = #"<LogoutResponse
ID=""_f525259e-7e91-4282-9dc3-a0da65a4a17a""
Version=""2.0""
IssueInstant=""2021-05-17T15:41:55Z""
InResponseTo=""_5089729f-5cc0-4a66-a3c1-e710cde92897""
Destination=""https://idp.xyz/logout.aspx""
xmlns=""urn:oasis:names:tc:SAML:2.0:protocol"">
<Issuer xmlns=""urn:oasis:names:tc:SAML:2.0:assertion"">https://abc.xyz/api</Issuer>
<Status>
<StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Success"" />
</Status>
</LogoutResponse>";
var doc = new System.Xml.XmlDocument();
doc.LoadXml(xml);
var nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("ns", "urn:oasis:names:tc:SAML:2.0:protocol");
var result = doc.SelectSingleNode("/ns:LogoutResponse/ns:Status/ns:StatusCode/#Value", nsManager).InnerText;
Console.WriteLine(result);
By using LINQ to XML API.
It is available in the .Net Framework since 2007.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<LogoutResponse xmlns='urn:oasis:names:tc:SAML:2.0:protocol'
Destination='https://idp.xyz/logout.aspx' ID='_f525259e-7e91-4282-9dc3-a0da65a4a17a'
InResponseTo='_5089729f-5cc0-4a66-a3c1-e710cde92897' IssueInstant='2021-05-17T15:41:55Z' Version='2.0'>
<Issuer xmlns='urn:oasis:names:tc:SAML:2.0:assertion'>https://abc.xyz/api</Issuer>
<Status>
<StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'></StatusCode>
</Status>
</LogoutResponse>");
XNamespace ns1 = "urn:oasis:names:tc:SAML:2.0:protocol";
string StatusCodeValue = xdoc.Descendants(ns1 + "StatusCode")
.Attributes("Value")
.FirstOrDefault()?.Value;
Console.WriteLine("StatusCodeValue='{0}'", StatusCodeValue);
}
Output
StatusCodeValue='urn:oasis:names:tc:SAML:2.0:status:Success'

How to get xml tag which is in DataSet1 tag(C#)? [duplicate]

I'm loading a string into an XML document that contains the following structure:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Include="clsWorker.cs" />
</ItemGroup>
</Project>
Then I'm loading all into an XmlDocument:
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(Xml);
Then the following problem occurs:
XmlNode Node = xmldoc.SelectSingleNode("//Compile"); // returns null
When I remove the xmlns attribute from the root element (Project), it works fine.
How do I get SelectSingleNode to return the relevant element?
You should use an XmlNamespaceManager in your call to SelectSingleNode():
XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable);
ns.AddNamespace("msbld", "http://schemas.microsoft.com/developer/msbuild/2003");
XmlNode node = xmldoc.SelectSingleNode("//msbld:Compile", ns);
Taken right from the documentation of SelectSingleNode() on the MSDN:
Note
If the XPath expression does not include a prefix, it is assumed that the
namespace URI is the empty namespace. If your XML includes a default
namespace, you must still add a prefix and namespace URI to the
XmlNamespaceManager; otherwise, you will not get a node selected. For
more information, see Select Nodes Using XPath Navigation.
And the immediately following sample code is
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ab", "http://www.lucernepublishing.com");
XmlNode book = doc.SelectSingleNode("//ab:book", nsmgr);
It's not as if this would be "hidden knowledge". ;-)
This way you don't need to specify namespace:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml("your xml");
XmlNode node = xmlDoc.SelectSingleNode("/*[local-name() = 'Compile']");
XmlNode nodeToImport = xmlDoc2.ImportNode(node, true);
xmlDoc2.AppendChild(nodeToImport);
Since the 'ItemGroup' may have multiple 'Compile' children, and you specifically want the 'Compile' children of 'Project/ItemGroup', the following will return all of the desired 'Compile' children and no others:
XmlDocument projectDoc = new XmlDocument();
projectDoc.Load(projectDocPath);
XmlNamespaceManager ns = new XmlNamespaceManager(projectDoc.NameTable);
ns.AddNamespace("msbld", "http://schemas.microsoft.com/developer/msbuild/2003");
XmlNodeList xnList = projectDoc.SelectNodes(#"/msbld:Project/msbld:ItemGroup/msbld:Compile", ns);
Note that the 'msbld:' namespace specification needs to precede each node level.

I Encounter error when SelectingSingleNode

EDIT: I think the reason is because of "UniversalShipment" xmlns. There is a link but i remove it for confidentiality. I can't pass through that node. help
This is my XML.
<UniversalInterchange xmlns="" version="1.0">
<Header>
<SenderID>1</SenderID>
<RecipientID>2</RecipientID>
</Header>
<Body>
<UniversalShipment xmlns="" version="1.1">
<Shipment>
<CustomizedFieldCollection>
<CustomizedField>
<Key>Documents Checked</Key>
<DataType>Boolean</DataType>
<Value>false</Value>
</CustomizedField>
<CustomizedField>
<Key>Date Completed</Key>
<DataType>DateTime</DataType>
<Value></Value>
</CustomizedField>
</CustomizedFieldCollection>
</Shipment>
</UniversalShipment>
</Body>
</UniversalInterchange>
I received null when getting singlenode. But when i try "Body" only in single node it add to bottom. if i tried to add it to UniversalShipment "Body/UniversalShipment" it encounters and error.
XmlDocument doc=new XmlDocument();
doc.Load("sample.xml");
XmlNode customizedNode = doc.CreateElement("CustomizedField");
XmlNode keyNode = doc.CreateElement("Key");
XmlNode dataNode = doc.CreateElement("DataType");
XmlNode valueNode = doc.CreateElement("Value");
keyNode.InnerText = "hi";
dataNode.InnerText = "hello";
valueNode.InnerText = "bye";
customizedNode.AppendChild(keyNode);
customizedNode.AppendChild(dataNode);
customizedNode.AppendChild(valueNode);
doc.DocumentElement.SelectSingleNode("Body/UniversalShipment/Shipment/CustomizedFieldCollection").AppendChild(customizedNode);
doc.Save("sample.xml");
If you have default namespaces you will need to use an XmlNamespaceManager - however, you have two default namespaces so it's a little more complicated.
As you have completely removed the namespace URIs from your question I've invented my own:
<UniversalInterchange xmlns="firstdefaultnamespace" version="1.0">
<Header>
<SenderID>1</SenderID>
<RecipientID>2</RecipientID>
</Header>
<Body>
<UniversalShipment xmlns="seconddefaultnamespace" version="1.1">
<Shipment>
<CustomizedFieldCollection>
<CustomizedField>
<Key>Documents Checked</Key>
<DataType>Boolean</DataType>
<Value>false</Value>
</CustomizedField>
<CustomizedField>
<Key>Date Completed</Key>
<DataType>DateTime</DataType>
<Value></Value>
</CustomizedField>
</CustomizedFieldCollection>
</Shipment>
</UniversalShipment>
</Body>
</UniversalInterchange>
If your first default namespace has a URI of firstdefaultnamespace and your second default namespace has a URI of seconddefaultnamespace you can do this:
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("ns1", "firstdefaultnamespace");
ns.AddNamespace("ns2", "seconddefaultnamespace");
doc.DocumentElement.SelectSingleNode("ns1:Body/ns2:UniversalShipment/ns2:Shipment/ns2:CustomizedFieldCollection", ns).AppendChild(customizedNode);
However, you will have problems when you save your XML with the new XmlNodes - you are not creating it with any namespace so it will be saved with a new default namespace, which will override the default namespace on the UniversalShipment element.
I would strongly suggest you read more about XML namespacing.
If you want to create your elements and keep them within the inner default namespace, you'll need to do something like this:
const string FirstNamespaceUri = "firstdefaultnamespace";
const string SecondNamespaceUri = "seconddefaultnamespace";
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("ns1", FirstNamespaceUri);
ns.AddNamespace("ns2", SecondNamespaceUri);
XmlNode customizedNode = doc.CreateElement("CustomizedField", SecondNamespaceUri);
XmlNode keyNode = doc.CreateElement("Key", SecondNamespaceUri);
XmlNode dataNode = doc.CreateElement("DataType", SecondNamespaceUri);
XmlNode valueNode = doc.CreateElement("Value", SecondNamespaceUri);
keyNode.InnerText = "hi";
dataNode.InnerText = "hello";
valueNode.InnerText = "bye";
customizedNode.AppendChild(keyNode);
customizedNode.AppendChild(dataNode);
customizedNode.AppendChild(valueNode);
doc.DocumentElement.SelectSingleNode("ns1:Body/ns2:UniversalShipment/ns2:Shipment/ns2:CustomizedFieldCollection", ns).AppendChild(customizedNode);
XPath expression is not formed correctly in you case. Check this link for XPath guide: http://www.w3schools.com/xsl/xpath_syntax.asp
Working Xpath:
doc.DocumentElement.SelectSingleNode("Body/UniversalShipment/Shipment/CustomizedFieldCollection").AppendChild(customizedNode);
If namespaces are specified in the XML, XmlNamespaceManager can be passed to XmlNode.SelectSingleNode as explained here!

How do I modify xml internal attributes

I am trying to edit a xml file.
XmlDocument myXmlDocument = new XmlDocument();
myXmlDocument.Load(#"C:\\Users\\Vahid\\Desktop\\HG\\HG\\HG\\singleM.kml");
XmlNode myNode = myXmlDocument.SelectSingleNode(
"/kml/Document/Placemark/Point/coordinates");
myNode.Value = coordinates;
myXmlDocument.Save(#"C:\\Users\\Vahid\\Desktop\\HG\\HG\\HG\\singleM.kml");
and this is my xml (.kml) file:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>change.kml</name>
<Style id="sn_ylw-pushpin"></Style>
<Placemark>
<Point>
<coordinates>0, 0,0</coordinates>
</Point>
<name>12</name>
</Placemark>
</Document>
</kml>
Xml namespaces:
XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDocument.NameTable);
ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2");
XmlNode myNode = myXmlDocument.SelectSingleNode("/kml:kml/kml:Document/kml:Placemark/kml:Point/kml:coordinates", ns);
myNode.InnerText = coordinates;
Note that there is nothing special about "kml" / "kml:" here - it could just as well be:
XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDocument.NameTable);
ns.AddNamespace("x", "http://www.opengis.net/kml/2.2");
XmlNode myNode = myXmlDocument.SelectSingleNode("/x:kml/x:Document/x:Placemark/x:Point/x:coordinates", ns);
myNode.InnerText = coordinates;
The important point is that each of your elements are in the namespace http://www.opengis.net/kml/2.2; the AddNamespace is just adding an alias for that, so that we can talk about the namespace conveniently - we then write the xpath using the alias, and pass the namespace-manager to the SelectSingleNode methods.

C# XPathSelectElements returns null?

I have an XML document:
<xsd:form-definition xmlns:xsd="http://...m.xsd"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="....xsd" ...>
<xsd:page>
<xsd:formant source-name="label" id="guid1" />
<xsd:formant source-name="label id="guid2" />
<xsd:formant source-name="label" id="guid3">
<xsd:value>2013-04-24</xsd:value>
</xsd:formant>
</xsd:page>
</xsd:form-definition>
and by C# code I want to iterate through the specific elements and get the id attribute and value (if exist) - lets say labels.
To do so, I try the code
XDocument xml = (document load);
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("f", "http://m.xsd");
foreach (XElement e in xml.XPathSelectElements("//f:formant[#source-name = 'label']", ns))
{
....
}
but the foreach loop does not return any elements. Why ?
It works for me. Check that your namespaces f and xsd match exactly. In your example they don't match. Also, there are some other syntax errors in your example, e.g. the source-name value of the second formant doesn't end with a double quote.
XDocument xml = XDocument.Parse(
#"<xsd:form-definition xmlns:xsd=""http://m.xsd""
xmlns:ds=""http://www.w3.org/2000/09/xmldsig#""
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">
<xsd:page>
<xsd:formant source-name=""label"" id=""guid1"" />
<xsd:formant source-name=""label2"" id=""guid2"" />
<xsd:formant source-name=""label"" id=""guid3"">
<xsd:value>2013-04-24</xsd:value>
</xsd:formant>
</xsd:page>
</xsd:form-definition>");
XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("f", "http://m.xsd");
foreach (XElement e in xml.XPathSelectElements(
"//f:formant[#source-name = 'label']", ns))
{
Console.WriteLine(e);
}
Console.ReadLine();

Categories