XPath not working as expected - c#

I have an XML document in this format:
<?xml version="1.0" encoding="utf-8" ?>
<SupportedServices>
<Service>
<name>Google Weather</name>
<active>Yes</active>
</Service>
...
</SupportedServices>
And I'm trying to parse the XML file like so:
public void InitializeDropDown(string XmlFile, string xpath)
{
XmlDocument doc = new XmlDocument();
doc.Load(XmlFile);
var rootNode = doc.DocumentElement;
var serviceList = rootNode.SelectNodes(xpath);
Parallel.ForEach(serviceList.Cast<XmlNode>(), service =>
{
if (Properties.Settings.Default.ServiceActive &&
Properties.Settings.Default.ServiceName == service.InnerText)
{
WeatherServicesCBO.Items.Add(service.InnerText);
}
});
}
The issue I'm having is both values (name & active) are selected so it would look like Google WeatherYes, when All I'm wanting is Google Weather. Can someone tell me what's wrong with my XPath (which is here):
InitializeDropDown("SupportedWeatherServices.xml", "descendant::Service[name]");

The XPath should be //Service/name
var serviceList = rootNode.SelectNodes("//Service/name");
or descendant::Service/name, if you like this syntax more.

Related

trying to delete xmlns namespaces of nodes

I've got some problems with xml messages and c#.
The problem is a root element with no namespaces and all the namespaces are in the nodes.
I've got a part of the script running to delete the namespaces so I can read all the xml messages that will be sent to the webserver.
The message that gives the problems:
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetOrderResponseRequest xmlns="http://www.edibulb.nl/XML/Order:2">
<Header>
<UserName xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">FBT_000390</UserName>
<Password xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">1FWcgwrx9</Password>
<MessageID xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" schemeDataURI="8719604082016">8719604082016100376</MessageID>
<MessageDateTime xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" format="304">20170523090413+02:00</MessageDateTime>
</Header>
<Body>
<AgentParty>
<PrimaryID xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" schemeID="251" schemeAgencyName="EBC">8719604178115</PrimaryID>
</AgentParty>
<GetOrderResponseDetails>
<MutationDateTime xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2" format="304">20170510000000+02:00</MutationDateTime>
<BuyerParty xmlns="urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2">
<PrimaryID xmlns="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:3" schemeID="251" schemeAgencyName="EBC">8719604082016</PrimaryID>
</BuyerParty>
</GetOrderResponseDetails>
</Body>
</GetOrderResponseRequest>
</soap:Body>
</soap:Envelope>
And here is the part of the script to translate on the webservice
the code below works perfectly fine if there are prefixes involved.
but it wont work with the xml defined above.
Here is the class that I call from the webservice.
First I check in the xml string if there are any prefixes.
RemoveNamespace remove = new RemoveNamespace();
public string orderrequest(string xmldoc, string ivbglns, bool success)
{
if (success == true)
{
if (xmldoc.Contains(":UserName"))
{
string xdoc = remove.removeall(xmldoc);
docx = new XmlDocument();
docx.LoadXml(xdoc);
}
else if(xmldoc.Contains("<UserName xmlns"))
{
string xdoc = remove.removexlmns(xmldoc);
docx = new XmlDocument();
docx.LoadXml(xmldoc);
}
// rest of the code for the response
}
}
and below the RemoveNameSpace part:
public string removeall(string xdoc)
{
string docx = RemoveAllNamespaces(xdoc);
return docx;
}
public static string RemoveAllNamespaces(string xmldoc)
{
XElement documentwithoutns = XRemoveAllNamespaces(XElement.Parse(xmldoc));
return documentwithoutns.ToString();
}
private static XElement XRemoveAllNamespaces(XElement Xmldoc)
{
if (!Xmldoc.HasElements)
{
XElement element = new XElement(Xmldoc.Name.LocalName);
element.Value = Xmldoc.Value;
foreach (XAttribute attribute in Xmldoc.Attributes())
element.Add(attribute);
return element;
}
return new XElement(Xmldoc.Name.LocalName, Xmldoc.Elements().Select(el => XRemoveAllNamespaces(el)));
}
public string removexlmns(string xdoc)
{
string pattern = "\\s+xmlns\\s*(:\\w)?\\s*=\\s*\\\"(?<url>[^\\\"]*)\\\"";
MatchCollection matchcol = Regex.Matches(xdoc, pattern);
foreach (Match m in matchcol)
{
xdoc = xdoc.Replace(m.ToString(), "");
}
return xdoc;
}
The error it returns is: The Prefix "cannot be redefined from" to 'urn:ebl:edibulb:xml:data:draft:ReusableAggregateBusinessInformationEntity:2' within the same start element tag.
I'm in search for a solution for this. The xml from above is a message thats beyond my control.
with Kind regards
Stephan
I would very strongly suggest you use the namespaces in whatever XML processing you are doing after this. Stop trying to remove them!
If you must remove them, it's worth noting that XElement.Name is mutable. You can remove all the namespace declarations and set all the names to their local names.
var doc = XDocument.Parse(xml);
doc.Descendants()
.Attributes()
.Where(x => x.IsNamespaceDeclaration)
.Remove();
foreach (var element in doc.Descendants())
{
element.Name = element.Name.LocalName;
}
See this fiddle for a demo.

Using xpath select how do I get the value of this element in example?

with this XML
<?xml version="1.0" encoding="UTF-8"?>
<createTransactionResponse xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<refId>999999999</refId>
<messages>
<resultCode>Ok</resultCode>
<message>
<code>I00001</code>
<text>Successful.</text>
</message>
</messages>
<transactionResponse>
<responseCode>1</responseCode>
<authCode>HH1D69</authCode>
<avsResultCode>Y</avsResultCode>
<cvvResultCode>P</cvvResultCode>
<cavvResultCode>2</cavvResultCode>
<transId>2228993425</transId>
<refTransID />
<transHash>916EE7527B17B62F62AA72B4C71F8322</transHash>
<testRequest>0</testRequest>
<accountNumber>XXXX0015</accountNumber>
<accountType>MasterCard</accountType>
<messages>
<message>
<code>1</code>
<description>This transaction has been approved.</description>
</message>
</messages>
<userFields>
<userField>
<name>MerchantDefinedFieldName1</name>
<value>MerchantDefinedFieldValue1</value>
</userField>
<userField>
<name>favorite_color</name>
<value>blue</value>
</userField>
</userFields>
</transactionResponse>
</createTransactionResponse>
I would like to get "resultCode", from here
<messages><resultCode>Ok</resultCode><message>
But the xpath I'm using does not give me the value of resultCode Ok.
What am I doing wrong?
--
static void Main(string[] args)
{
string myXML = #"<?xml version=""1.0"" encoding=""utf-8""?><createTransactionResponse xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns=""AnetApi/xml/v1/schema/AnetApiSchema.xsd""><refId>999999999</refId><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages><transactionResponse><responseCode>1</responseCode><authCode>HH1D69</authCode><avsResultCode>Y</avsResultCode><cvvResultCode>P</cvvResultCode><cavvResultCode>2</cavvResultCode><transId>2228993425</transId><refTransID /><transHash>916EE7527B17B62F62AA72B4C71F8322</transHash><testRequest>0</testRequest><accountNumber>XXXX0015</accountNumber><accountType>MasterCard</accountType><messages><message><code>1</code><description>This transaction has been approved.</description></message></messages><userFields><userField><name>MerchantDefinedFieldName1</name><value>MerchantDefinedFieldValue1</value></userField><userField><name>favorite_color</name><value>blue</value></userField></userFields></transactionResponse></createTransactionResponse>";
string myValue = XMLSelect(myXML, "createTransactionResponse/messages/message/resultCode");
//myValue should = "Ok" but it does not :(
}
public static string XMLSelect(string _xmldoc, string _xpath)
{
string returnedValue = string.Empty;
XmlDocument doc = new XmlDocument();
try
{
doc.LoadXml(_xmldoc);
XmlElement root = doc.DocumentElement;
returnedValue = (string)doc.SelectNodes(_xpath)[0].InnerText;
}
catch (Exception ex)
{
return "";
}
return returnedValue;
}
Your first problem is that the XML you originally gave isn't valid. You're masking that by returning "" whenever you encounter an exception, so you don't have any information any more.
So the first thing to do IMO is remove the spurious exception handling:
public static string XMLSelect(string _xmldoc, string _xpath)
{
string returnedValue = string.Empty;
XmlDocument doc = new XmlDocument();
doc.LoadXml(_xmldoc);
XmlElement root = doc.DocumentElement;
return (string)doc.SelectNodes(_xpath)[0].InnerText;
}
Now, the problem with the XPath is that all the elements in your XML are in a namespace of AnetApi/xml/v1/schema/AnetApiSchema.xsd - so you'll probably want an XmlNamespaceManager. It's slightly tricky to separate that properly given the way that you've split the load from the XPath, but for the moment you could just introduce an alias of ns for the right namespace.
Next, your XPath is incorrect in that it looks for message/resultCode, when those two elements are peers. You don't want the message part.
Here's a short but complete program which fixes all those problems:
using System;
using System.Xml;
public class Program
{
static void Main(string[] args)
{
// As per the question
string myXML = "...";
string myValue = XMLSelect(myXML, "ns:createTransactionResponse/ns:messages/ns:resultCode");
Console.WriteLine(myValue);
}
public static string XMLSelect(string _xmldoc, string _xpath)
{
string returnedValue = string.Empty;
XmlDocument doc = new XmlDocument();
doc.LoadXml(_xmldoc);
var nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("ns", "AnetApi/xml/v1/schema/AnetApiSchema.xsd");
XmlElement root = doc.DocumentElement;
return (string)doc.SelectNodes(_xpath, nsm)[0].InnerText;
}
}
If you could use LINQ to XML instead, and simply work on the document instead of passing in the XML text and an XPath selector, it would be simpler:
XDocument doc = XDocument.Parse(xml);
XNamespace ns = "AnetApi/xml/v1/schema/AnetApiSchema.xsd";
string code = (string) doc.Root
.Element(ns + "messages")
.Element(ns + "resultCode");
Console.WriteLine(code);

Reading an XML document with XMLDocument C# 'password' string not returning

I'm trying to read the text in between <keyMaterial></keyMaterial>
I tried using //WLANProfile/MSM/security/sharedKey as the element route, seen in the code below. It refuses to return a value. I have run through the debugger and after the breakpoint at the line: XmlNodeList sharedKeyNodes = wifiProfile.SelectNodes("//WLANProfile/MSM/security/sharedKey");
the SharedKeyNodes object doesn't return a count for. I know it's just a matter of figuring out the element route so I'm not coming here completely hopeless...
System.Xml.XPathNodeList
My XML looks like this:
<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
<name>nosignal</name>
<SSIDConfig>
<SSID>
<hex>6E6F7369676E616C</hex>
<name>nosignal</name>
</SSID>
<nonBroadcast>true</nonBroadcast>
</SSIDConfig>
<connectionType>ESS</connectionType>
<connectionMode>auto</connectionMode>
<autoSwitch>false</autoSwitch>
<MSM>
<security>
<authEncryption>
<authentication>WPA2PSK</authentication>
<encryption>AES</encryption>
<useOneX>false</useOneX>
<FIPSMode xmlns="http://www.microsoft.com/networking/WLAN/profile/v2">false</FIPSMode>
</authEncryption>
<sharedKey>
<keyType>passPhrase</keyType>
<protected>true</protected>
<keyMaterial>01000000D</keyMaterial>
</sharedKey>
</security>
</MSM>
</WLANProfile>
[ EDIT with the help of LB the new code looks like this and it WORKS! ]
[ For anyone that is struggling with a similar problem. ]
My Class is:
class ProfileManager
{
public static string readProfile() {
XmlDocument wifiProfile = new XmlDocument();
string path = #"C:\temp\nosignal.xml";
string password = "";
wifiProfile.Load(path);
XmlNamespaceManager mgr = new XmlNamespaceManager(wifiProfile.NameTable);
mgr.AddNamespace("ns", "http://www.microsoft.com/networking/WLAN/profile/v1");
XmlNodeList sharedKeyNodes = wifiProfile.SelectNodes("//ns:WLANProfile/ns:MSM/ns:security/ns:sharedKey", mgr);
foreach (XmlNode itemNode in sharedKeyNodes)
{
XmlNode keyMaterialNode = itemNode.SelectSingleNode("ns:keyMaterial", mgr);
if (keyMaterialNode != null)
{
password = keyMaterialNode.InnerText;
}
}
return password;
}
}
I'm close, but still just a bit stuck. Any help would be appreciated!!! Thank you!
You don't use the default XmlNamespace "http://www.microsoft.com/networking/WLAN/profile/v1"
wifiProfile.Load(path);
XmlNamespaceManager mgr = new XmlNamespaceManager(wifiProfile.NameTable);
mgr.AddNamespace("ns", "http://www.microsoft.com/networking/WLAN/profile/v1");
XmlNodeList sharedKeyNodes = wifiProfile.SelectNodes("//ns:WLANProfile/ns:MSM/ns:security/ns:sharedKey",mgr);

How to edit an Xml file's element values

I have this Xml file, and I want to edit any of the elements like homepage or search_provider.
<?xml version="1.0" encoding="utf-8"?>
<Preferences>
<personal>
<homepage>http://duckduckgo.com</homepage>
<search_provider>DuckDuckGo</search_provider>
<search_provider_url>http://duckduckgo.com/?q=</search_provider_url>
</personal>
</Preferences>
The following is the C# code I'm using to attempt to change the homepage element. Let's say I run saveSetting("homepage", "http://google.com");
public static void saveSetting(String settingName, String newvalue)
{
XmlDocument xml = new XmlDocument();
xml.Load(userSettingsFile);
foreach (XmlElement element in xml.SelectNodes("Preferences"))
{
foreach (XmlElement oldsettingname in element)
{
element.SelectSingleNode(settingName);
XmlNode settingtosave = xml.CreateElement(settingName);
settingtosave.InnerText = newvalue;
element.ReplaceChild(settingtosave, oldsettingname);
xml.Save(userSettingsFile);
}
}
}
Now, while this works to an extent and does change the specified value, it also deletes the entire personal element.
<?xml version="1.0" encoding="utf-8"?>
<Preferences>
<homepage>http://google.com</homepage>
</Preferences>
Hopefully someone can help me out! I've been searching for the last two days for a solution and this is the closest I've come to getting the code to work the way I need it to.
You can just use LINQ to XML like this:
public static void saveSetting(String settingName, String newvalue)
{
var xmlDocument = XDocument.Load("path");
var element = xmlDocument.Descendants(settingName).FirstOrDefault();
if (element != null) element.Value = newvalue;
xmlDocument.Save("path");
}
See this documentation for more details: Modifying Elements, Attributes, and Nodes in an XML Tree
keep your current code and just modify a few lines:
public static void saveSetting(String settingName, String newvalue)
{
XmlDocument xml = new XmlDocument();
xml.Load(userSettingsFile);
foreach (XmlElement element in xml.SelectNodes("Preferences"))
{
if(element.Name.Equals(settingName))
{
element.InnerText = newvalue;
break;
}
}
xml.Save(userSettingsFile);
}

How to process webdav server XML request in C#

I am writing a custom WebDAV server in C#. One of the client test programs I am using is NetDrive and it claims and appears to be a WebDAV compliant client. My problem is I am receiving a request on the server in the following format:
<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<allprop/>
</propfind>
But other clients do this:
<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>
The two different namespace formats keep on fooing up my logic to look for the "allprop" element. My code looks a bit like this:
string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><allprop/></propfind>"; //Hardcode to make all the StackOverflow users' lives easier
XPathDocument doc = new XPathDocument(new StringReader(xml));
XPathNavigator nav = doc.CreateNavigator();
XPathNodeIterator it = nav.Select("/propfind/*");
Now, I know I need to put in some type of namespace manager for the "DAV:", so I tried this:
XmlNamespaceManager nsman = new XmlNamespaceManager(nav.NameTable);
nsman.AddNamespace("", "DAV");
XPathNodeIterator it = nav.Select("/propfind/*", nsman);
But I'm getting no nodes in my iterator for the first XML file. It seems the default namespace isn't working like I thought it should.
What am I doing wrong? How do I query this XML for the existence of an allprop node when the namespace may be the default, or may be explicitly named?
You are using the wrong namespace in your code. Unforunalely the WebDAV specs use
'DAV:' as the namespace for WebDAV nodes and attributes (this seems to be caused by
a missunderstanding of the XML namespace mechanism).
I ended up looking for the namespace URI (DAV:) and adding it if it didn't exist. Then I just did a namespace-qualified SELECT and it worked in all my test cases:
XPathDocument document = new XPathDocument(xml);
XPathNavigator navigator = document.CreateNavigator();
//Get namespaces & add them to the search
bool hasDAV = false;
string davPrefix = "D";
XmlNamespaceManager nsman = new XmlNamespaceManager(navigator.NameTable);
foreach (KeyValuePair<string, string> nskvp in navigator.GetNamespacesInScope(XmlNamespaceScope.All))
{
if (string.Compare(nskvp.Value, "DAV:", StringComparison.InvariantCultureIgnoreCase) == 0)
{
hasDAV = true;
davPrefix = nskvp.Key;
}
nsman.AddNamespace(nskvp.Key, nskvp.Value);
}
if (!hasDAV)
nsman.AddNamespace(davPrefix , "DAV:");
XPathNodeIterator iterator = navigator.Select("/" + davPrefix + ":" + WebDavXML.PropFind + "/*", nsman);

Categories

Resources