views:

493

answers:

4

New to XML. I have a 3rd party webservice that supplies an XML document that I have to update the element values and pass back. The core issue issue is I get an NullReferenceException error when calling the node.RemoveAll() method in the code below. I'm calling the RemoveAll() method because each element has the xsi:nil attribute when it is supplied to me, and if I don't remove it before updating the element value, the XML won't validate by the webservice.

The XML document supplied by the 3rd party webservice is as follows:

<?xml version="1.0" encoding="utf-16"?>
<TaskData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schema.sample.com/application/1/520800B"&gt;
  <Global>
    <RequestInfo xmlns="http://schema.sample.com/application/1/Types"&gt;
      <Requestor xsi:nil="true" />
      <Date_init xsi:nil="true" />
      <Shipto xsi:nil="true" />
      <Customer xsi:nil="true" />
      <Contact xsi:nil="true" />
      <Requestor_Email xsi:nil="true" />      
    </RequestInfo>    
   </Global>
  </TaskData>

Other solutions I've seen have used the XmlNamespaceManager, but I haven't been able to make it work. This xml document has a namespace specified for the TaskData element, and a different namespace for the RequestInfo element. I tried specifying the XmlNamespaceManager variable for each namespace, but got the same results....hovering over the nsmgr variable while in break mode reveals that the "children could not be evaluated" and that the DefaultNamespace property is an empty string.

Public Sub testxml()

    Dim doc As New XmlDocument
    doc.Load("c:\temp\sample.xml")

    Dim nsmgr As XmlNamespaceManager = New XmlNamespaceManager(doc.NameTable)
    nsmgr.AddNamespace("s", "http://schema.sample.com/application/1/520800B")

    Dim node As XmlNode = doc.SelectSingleNode("s:Requestor", nsmgr)
    node.RemoveAll()
    node.InnerText = "Your Name Goes Here"

    doc.Save("c:\temp\sample.xml")

End Sub
A: 

I'm not sure whether I have understood your task correctly. But if you have to just remove the xsi:nil="true" part, why don't you load it as a string and invoke a

string.replace("xsi:nil=\"true\"", "")

Of course that's not the cleanest solution, but I'm not yet comfortable with the XML handling API of C#, so I'd have to consult the MSDN to get accustomed. Maybe this solves already your issue.

Juri
Thanks, Juri. The problem I'm having is that the variable "node" is not getting set, and when the RemoveAll() method is called, it throws the exception. Can you see what is wrong with my XPath syntax so that the "node" variable gets set to the correct element?
love2fly
+1  A: 

The problem is with this statement:

doc.SelectSingleNode("s:Requestor", nsmgr)

what you need to do is

doc.SelectSingleNode("//s:Requestor",nsmgr)

"s:Requestor" means give me the node underneath the current node name s:Requestor

"//s:Requestor" means give me all nodes in the document named s:Requestor

if you want to ignore the namespace you could do

doc.SelectSingleNode("//*[local-name()='Requestor']")
Greg
Fabulous Greg! The option to ignore the NS you suggested worked and is what I prefer, since I can't control what namespace the 3rd party webservice assigns. I tried the "//s:Requestor" but it didn't work.
love2fly
A: 

There are two errors in your code. One is the XPath used for selecting the node (as Greg pointed out). The second one is the name space. I may be wrong, but as I interpret the XML document the TaskData element has the following namespace declaration:

xmlns="http://schema.sample.com/application/1/520800B"

...which sets up the namespace for elements without prefixes.

But then the RequestInfo tag has this namespace declaration:

xmlns="http://schema.sample.com/application/1/Types"

So within the RequestInfo tag, there is another namespace for tags without prefixes. In your code you use the first of these two namespaces to match a tag that resides in the second, which will not work out. There are two ways to solve it. One is to simply change the namespace in your code:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("s", "http://schema.sample.com/application/1/Types");
XmlNode node = doc.SelectSingleNode("//s:Requestor", nsmgr);

The second one is to define both namespaces, and use an XPath expression that points out the full path to the tag:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("r", "http://schema.sample.com/application/1/520800B");
nsmgr.AddNamespace("s", "http://schema.sample.com/application/1/Types");
XmlNode node = doc.SelectSingleNode(@"/r:TaskData/r:Global/s:RequestInfo/s:Requestor", nsmgr);

Both these cases lead to the same result; node is not null.

Fredrik Mörk
Fredrik, I like having the ability to specify the full path for the element. Since this is my first post, I don't have enough reputation to vote on your answer yet, but it is very helpful, thank you!
love2fly
A: 

You should be able to opt out of any namespace calls in your selectelement call, i prefer to loop through my document anyway.. but that's just me.