XmlDocument SelectSingleNode - c#

On Stack Overflow there is a document explaining the use of XmlDocument and how to select a node.
C# XmlDocument SelectSingleNode without attribute
The code presented is the code I am using as follows.
XmlDocument doc = new XmlDocument();
doc.Load("C:\\FileXml.xml")
string Version = doc.DocumentElement.SelectSingleNode("/Version").InnerText;
Console.Write(Version); //I want to see 3
The Xml file is shown below "in its entirety".
<CharacterObject xmlns="http://www.w3.org/2005/Atom">
<Version>3</Version>
<Path>C:\\FilePath\FileName.xml</Path>
</CharacterObject>
The error that I am receiving is that SelectSingleNode above returns null. It returned null when I searched for CharacterObject as well. No matter what XML node I search for the function SelectSingleNode returns null. This means I must be using SingleSelectNode incorrectly just not sure how.
I would like SelectSingleNode to return the node so that InnerText will return the Version information in the XML Node. I'm just having a problem in usage of reading the information from the nodes.

According to documentation on XmlDocument.DocumentElement - it is a root xml element. So in your case it is CharacterObject already.
When you call .SelectSingleNode('/CharacterObject') for it - you are requesting an CharacterObject element inside the root CharacterObject - which is not there at all.
You can simply use XmlDocument.DocumentElement.InnerText to achieve the result you want.

This particular problem has a solution. This might be due to the namespace attribute in the XML root node itself. Eliminating this attribute solves my issue.

Related

Xml with Namespace but without xmlns

I am working on a program in C# that edits open-document files on xml level. For example it adds rows to tables.
So I load the content.xml into an XmlDocument "doc" and traverse the xml structure.
Say I have the <table:table-row> node in an XmlNode "row" and now I want to add a <table:table-cell> node to it. So I call
XmlDocument doc = new XmlDocument();
doc.Load(filename);
...
XmlNode row = ...;
...
XmlNode cell = doc.CreateElement("table:table-cell");
row.Append(cell);
...
doc.Save(filename);
The problem is that, in the file, the new node only contains
<table-cell>...</table-cell>
C# just decides to ignore what I told it to and does something else without even telling me (at first I overlooked the problem and was wondering why it didn't work although the generated xml looked okay).
From what I gathered out so far, the problem has to do with the fact that "table:" is a namespace. When I also supply a NamespaceURI to CreateElement, I get
<table:table-cell table:xmlns="THE_URI" >... - but the original document did not have this xmlns, so I don't want it either...
I tried to use an XmlTextWriter and setting writer.Settings.Namespaces = false, because I thought, this should suppress the output of the xmlns, but it only caused an exception - the document has some namespaces, which are forbidden if Namespaces is set to false... (wtf!? suppressing the output of xmlns seems a billion times more logical than throwing an exception if an xmlns is present...)
In some similar discussions I read that you should set the cell.Name manually, but this property is read-only...
Others suggest to change it on text-file level (that's tinkering and it would be slow)
Can anyone give me a hint?
Every namespace should have at least one xmlns definition with a URI. This is the ultimate differentiation between two tags.
You can however have the xmlns attribute declared only once in the file (in the beginning).
See
Creating a specific XML document using namespaces in C#
The table: parts are not namespaces. They are "namespace prefixes". They are an alias for the actual namespace. They must be declared somewhere. If they are not declared at all in your source XML, then it is not valid XML, and you shouldn't expect to be able to process it.
Are you sure that what you have loaded is the entire XML document? They haven't left off parts to make it simpler? Those parts may be the ones that contain the definition of table:.

XML namespaces and XPath

I have an application that has to load XML document and output nodes depending on XPath.
Suppose I start with a document like this:
<aaa>
...[many nodes here]...
<bbb>text</bbb>
...[many nodes here]...
<bbb>text</bbb>
...[many nodes here]...
</aaa>
With XPath //bbb
So far everything is nice.
And selection doc.SelectNodes("//bbb"); returns the list of required nodes.
Then someone uploads a document with one node like <myfancynamespace:foo/> and extra namespace in the root tag, and everything breaks.
Why? //bbb does not give a damn about myfancynamespace, theoretically it should even be good with //myfancynamespace:foo, as there is no ambiguity, but the expression returns 0 results and that's it.
Is there a workaround for this behavior?
I do have a namespace manager for the document, and I am passing it to the Xpath query. But the namespaces and the prefixes are unknown to me, so I can't add them before the query.
Do I have to pre-parse the document to fill the namespace manager before I do any selections? Why on earth such behavior, it just doesn't make sense.
EDIT:
I'm using:
XmlDocument and XmlNamespaceManager
EDIT2:
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
//I wish I could:
//nsmgr.AddNamespace("magic", "http://magicnamespaceuri/
//...
doc.LoadXML(usersuppliedxml);
XmlNodeList nodes = doc.SelectNodes(usersuppliedxpath, nsmgr);//usersuppliedxpath -> "//bbb"
//nodes.Count should be > 0, but with namespaced document they are 0
EDIT3:
Found an article which describes the actual scenario of the issue with one workaround, but not very pretty workaround: http://codeclimber.net.nz/archive/2008/01/09/How-to-query-a-XPath-doc-that-has-a-default.aspx
Almost seems that stripping the xmlns is the way to go...
You're missing the whole point of XML namespaces.
But if you really need to perform XPath on documents that will use an unknown namespace, and you really don't care about it, you will need to strip it out and reload the document. XPath will not work in a namespace-agnostic way, unless you want to use the local-name() function at every point in your selectors.
private XmlDocument StripNamespace(XmlDocument doc)
{
if (doc.DocumentElement.NamespaceURI.Length > 0)
{
doc.DocumentElement.SetAttribute("xmlns", "");
// must serialize and reload for this to take effect
XmlDocument newDoc = new XmlDocument();
newDoc.LoadXml(doc.OuterXml);
return newDoc;
}
else
{
return doc;
}
}
<myfancynamespace:foo/> is not necessarily the same as <foo/>.
Namespaces do matter. But I can understand your frustration as they usually tend to breaks codes as various implementation (C#, Java, ...) tend to output it differently.
I suggest you change your XPath to allow for accepting all namespaces. For example instead of
//bbb
Define it as
//*[local-name()='bbb']
That should take care of it.
You should describe a bit more detailed what you want to do. The way you ask your question it make no sense at all. The namespace is just a part of the name. Nothing more, nothing less. So your question is the same as asking for an XPath query to get all tags ending with "x". That's not the idea behind XML, but if you have strange reasons to do so: Feel free to iterate over all nodes and implement it yourself. The same applies to functionality you are requesting.
You could use the LINQ XML classes like XDocument. They greatly simplify working with namespaces.

Change the XPath-root of an XmlDocument in .net (C#)?

When selecting from an XmlDocument by e.g. the XPath-method SelectSingleNode we get an XmlNode that consist of the first matching node, lets call it <node1>. If we do further selection on <node1> then one might expect that the XPath-root now is this node, but this is incorrect, the root is still the same as in the original XmlDocument. Here's an example:
XmlDocument xd = new XmlDocument();
xd.LoadXml(#"<root>
<subroot>
<elm>test1</elm>
<elm>test2</elm>
<elm>test3</elm>
</subroot>
</root>");
XmlNode xnSubRoot = xd.SelectSingleNode("/root/subroot");
//This is the XPath I want to be able to use, but it returns null.
XmlNode xnElm = xnSubRoot.SelectSingleNode("/subroot/elm");
//This works, but the XPath-root is the same as in the original document.
xnElm = xnSubRoot.SelectSingleNode("/root/subroot/elm");
Is there any way to "fix" the root of xnSubRoot so that I can use the XPath I want? The reason for my question is because I have a case where I'm calling a webservice that returns an XmlNode where the OuterXml-property shows a structure of "/Data/SubElement/..." and so on, but when running XPath "/Data" then null is returned, only "/SubElement" works, i.e. the XPath-root seems to be one level lower than the actual document-root.
I'm sure there is a perfectly reasonable explanation for this, or that I'm missing something vital. However I really can't seem to find anything, even though I've read http://msdn.microsoft.com/en-us/library/d271ytdx(VS.80).aspx.
N.B. I do realize that it would be possible to use the XPath "//subroot/elm", but then I might also get other elements further down in the XML structure.
Since your selecting from the Root/SubElement Try this:
XmlNode xnElm = xnSubRoot.SelectSingleNode("elm");
It will return the first child elm node of the current node.
Edit (from additionals informations provided in comments):
In this specific case, you are obtaning a XmlNode (which is your Data node) from a WebService call. All XPath requests on that XmlNode will be relative to it.
I would suggest that you modify all your XPaths to use a selector like webServiceNode.SelectSingleNode("SubElement/SubSubElement"); . There is no reason to specify absolute XPaths queries here.
This works:
XmlNode xnSubRoot = xd.SelectSingleNode("/root/subroot");
XmlNode xnElm = xnSubRoot.SelectSingleNode("elm");
And so does this:
XmlNode xnRoot = xd.SelectSingleNode("/root");
XmlNode xnElm = xnRoot.SelectSingleNode("subroot/elm");

Adding element to existing XML node

Where am i going wrong???
I have an xml file with OppDetails as a tag already as shown below
<OppDetails>
<OMID>245414</OMID>
<ClientName>Best Buy</ClientName>
<OppName>International Rate Card</OppName>
<CTALinkType>AO,IO,MC,TC</CTALinkType>
</OppDetails>
</OppFact>
Now i am trying to add another element to it but getting an error in AppendChild method please help
XmlNode rootNode = xmlDoc.SelectSingleNode("OppDetails");
XmlElement xmlEle = xmlDoc.CreateElement("CTAStartDate");
xmlEle.InnerText = ExcelUtility.GetCTAStartDate();
rootNode.AppendChild(xmlEle);
xmlDoc.Save("C:\\test.xml");
XmlElement xmlEle = xmlDoc.DocumentElement["OppDetails"];
XmlElement eleNew = xmlDoc.CreateElement("CTAStartDate");
eleNew.InnerText = ExcelUtility.GetCTAStartDate();
xmlEle.AppendChild(eleNew);
xmlDoc.Save("C:\\test.xml");
It is hard to tell without a full sample, but a common reason for SelectNodes / SelectSingleNode returning null is xml namespaces. If you xml makes use of element namespaces, you'll probably need to use an XmlNamespaceManager along with your query, and define a suitable alias for the namespace you want.
Is rootNode null?
From MSDN on SelectSingleNode:
The first XmlNode that matches the
XPath query or a null reference
(Nothing in Visual Basic) if no
matching node is found.
If rootNode is null, it indicates that the node could not be found, and trying to use the null rootNode would cause the exception you are seeing.
The exception you've reported means that you have not located the root element. When SelectSingleNode can't find the requested node, it returns null. You didn't check for that.
Read root node and add the new element in to the root node. I think you are trying to append in XML document.

Parsing XML document with XPath, C#

So I'm trying to parse the following XML document with C#, using System.XML:
<root xmlns:n="http://www.w3.org/TR/html4/">
<n:node>
<n:node>
data
</n:node>
</n:node>
<n:node>
<n:node>
data
</n:node>
</n:node>
</root>
Every treatise of XPath with namespaces tells me to do the following:
XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDoc.NameTable);
mgr.AddNamespace("n", "http://www.w3.org/1999/XSL/Transform");
And after I add the code above, the query
xmlDoc.SelectNodes("/root/n:node", mgr);
Runs fine, but returns nothing. The following:
xmlDoc.SelectNodes("/root/node", mgr);
returns two nodes if I modify the XML file and remove the namespaces, so it seems everything else is set up correctly. Any idea why it work doesn't with namespaces?
Thanks alot!
As stated, it's the URI of the namespace that's important, not the prefix.
Given your xml you could use the following:
mgr.AddNamespace( "someOtherPrefix", "http://www.w3.org/TR/html4/" );
var nodes = xmlDoc.SelectNodes( "/root/someOtherPrefix:node", mgr );
This will give you the data you want. Once you grasp this concept it becomes easier, especially when you get to default namespaces (no prefix in source xml), since you instantly know you can assign a prefix to each URI and strongly reference any part of the document you like.
The URI you specified in your AddNamespace method doesn't match the one in the xmlns declaration.
If you declare prefix "n" to represent the namespace "http://www.w3.org/1999/XSL/Transform", then the nodes won't match when you do your query. This is because, in your document, the prefix "n" refers to the namespace "http://www.w3.org/TR/html4/".
Try doing mgr.AddNamespace("n", "http://www.w3.org/TR/html4/"); instead.

Categories

Resources