parsing SOAP xml with namespaces - c#

I have been trying to parse some info and followed the wonderful advise from http://lakenine.com/reading-xml-with-namespaces-using-linq/ I'm sure this is close, but I'm not getting any results to display. No errors, just no results. Breakpoints and checking variables shows that docx has the proper info, but my for loop gets skipped right over. I have played with multiple variations and only manage to crash the code. I believe the issue is with the XPathSelectElements parameter, but dont know what else to try.
At this stage all I need is the token, but I will need to reuse the code later for returns that may have multiple results. Please advise and thank you in advance:
string sampleXML = String.Concat(
"<soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"",
" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"",
" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\">",
" <soap12:Body>",
" <BeginSessionV2Response xmlns=\"http://ws.jobboard.com/jobs/\">",
" <BeginSessionV2Result>ca5522fb93ef499f8ed010a5f4153af7-446298346-SB-4</BeginSessionV2Result>",
" </BeginSessionV2Response>",
" </soap12:Body>",
" </soap12:Envelope>"
);
XmlReader reader = XmlReader.Create(new StringReader(sampleXML));
System.Xml.XmlNameTable nameTable = reader.NameTable;
System.Xml.XmlNamespaceManager namespaceManager = new System.Xml.XmlNamespaceManager(nameTable);
namespaceManager.AddNamespace("soap12", "http://www.w3.org/2001/XMLSchema-instance/");
XElement docx = XElement.Load(reader);
string vbResultz = "start: ";
var sessionKey = from pn
in docx.XPathSelectElements("soap12:Body/BeginSessionV2Response/BeginSessionV2Result", namespaceManager)
select (string)pn;
foreach (string pn in sessionKey)
{
vbResultz += pn;
}
ViewBag.REsultz = vbResultz;
return View();
}

First, soap12 prefix you added have wrong uri. Add namespaces to namespaceManager this way :
namespaceManager.AddNamespace("soap12", "http://www.w3.org/2003/05/soap-envelope");
namespaceManager.AddNamespace("ns", "http://ws.jobboard.com/jobs/");
Then you can use them in XPath expression like this :
......
docx.XPathSelectElements("soap12:Body/ns:BeginSessionV2Response/ns:BeginSessionV2Result", namespaceManager)
......
Notice that <BeginSessionV2Response> has default namespace (xmlns attribute without prefix), hence that element and it's descendant without prefix considered inside default namespace. Therefore, we need to add ns prefix in XPath query expression above.

Related

Extracting XML Child Elements Where the Parents are in a Defaulted Namespace

I have the below XML and I've been trying to extract the FirstName, LastName and OtherName for a while now I'm running into all sort of problems.
<OmdCds xmlns="cds"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cdsd="cds_dt"
xsi:schemaLocation="cds ontariomd_cds.xsd">
<PatientRecord>
<Demographics>
<Names>
<cdsd:LegalName namePurpose="L">
<cdsd:FirstName>
<cdsd:Part>SARAH</cdsd:Part>
<cdsd:PartType>GIV</cdsd:PartType>
<cdsd:PartQualifier>BR</cdsd:PartQualifier>
</cdsd:FirstName>
<cdsd:LastName>
<cdsd:Part>GOMEZ</cdsd:Part>
<cdsd:PartType>FAMC</cdsd:PartType>
<cdsd:PartQualifier>BR</cdsd:PartQualifier>
</cdsd:LastName>
<cdsd:OtherName>
<cdsd:Part>GABRIELA</cdsd:Part>
<cdsd:PartType>GIV</cdsd:PartType>
<cdsd:PartQualifier>BR</PartQualifier>
I currently trying to extract with the below c# code but still can't extract the above data. I'm getting a nullreferenceexception.
XmlDocument doc = new XmlDocument();
doc.Load(folder + "\\" + o.ToString());
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("cdsd", "http://www.w3.org/2001/XMLSchema-instance");
XmlNode firstName = doc.DocumentElement.SelectSingleNode("/PatientRecord/Demographics/Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part", namespaceManager);
string fName = firstName.InnerText;
MessageBox.Show(fName);
I can see in the local watch item under doc.DocumentElement, all the InnerXML and InnerText. The InnerXML look something like this...
<PatientRecord xmlns=\"cds\"><Demographics><Names><cdsd:LegalName namePurpose=\"L\" xmlns:cdsd=\"cds_dt\"><cdsd:FirstName><cdsd:Part>SARAH</cdsd:Part><cdsd:PartType>GIV</cdsd:PartType><cdsd:PartQualifier>BR</cdsd:PartQualifier></cdsd:FirstName>
You have 3 namespace definitions in the document:
cds - as a default namespace
http://www.w3.org/2001/XMLSchema-instance- with the xsi prefix
cds_dt - with the cdsd prefix
I am wondering that you don't get an error message, because cds and cds_dt are no URIs and namspaces need to be URIs.
If you try to understand an element name you need to replaces the prefix with the actual namespace.
<PatientRecord> reads as {cds}:PatientRecord
<cdsd:LegalName> reads as {cds_dt}:LegalName
Now in XPath 1.0 the same happens with registered namespaces. But XPath does not have a default namespace. So elements without one are not expanded with a default namespace.
You need to register namespace prefixes on the namespace manager. The prefix does not need to be the same as in the document.
namespaceManager.AddNamespace("cdsd", "cds_dt");
namespaceManager.AddNamespace("cds", "cds");
Now you can use the registered namespaces in XPath:
doc.DocumentElement.SelectSingleNode(
"cds:PatientRecord/cds:Demographics/cds:Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part",
namespaceManager
);
If the first character of an XPath expression is a slash the expression is relative to the document, otherwise to the current context node. You call SelectSingleNode() on the doc.DocumentElement - the OmdCds element node. PatientRecord is a child node, so you can start with it or use . for the current context node.
PatientRecord, Demographics and Names are in the cds namespace. This is because of the default namespace declaration on the OmdCds element (xmlns="cds"). The others are in the cdsd namespace, not xsi. You'll have to add them and use them in the XPATH:
namespaceManager.AddNamespace("cdsd", "cdsd");
namespaceManager.AddNamespace("cds", "cds");
XmlNode firstName = doc.DocumentElement.SelectSingleNode(
"/cds:PatientRecord/cds:Demographics/cds:Names/cdsd:LegalName/cdsd:FirstName/cdsd:Part",
namespaceManager);
BTW, you're getting a NullReferenceException because you're making the false assumption that your query will always return a node. You are now seeing what happens when it does not return a node. Always check for null whenever it's possible that a query returns no value.
Instead XmlDocument class you can use Linq to XML, is easy. You need using the System.Xml.Linq namspace, for example:
XDocument xdoc = XDocument.Load("path");
IEnumerable<XElement> nodes = (from p in xdoc.Descendants()
where p.Name.LocalName == "FirstName"
select p).Elements();
foreach (XElement nodeFirstName in nodes)
{
foreach (XElement parts in nodeFirstName.Elements())
{
string strExtracted = parts.Name.LocalName + " " + parts.Value;
}
}
The LocalName property is used beacuse elements have a prefix "cdsd"

How to get specific Values from a xml file with same Name in a element?

I don't know how to extract values from this specific XML document, and am looking for some help as I'm not very experienced on xml parsing.
I have to use XDocument.Load to load the file.
Actually i am using
doc = XDocument.Load(uri);
challenge = GetValue(doc, "Challenge");
this works without any problems, but how to get the inner values of the Element Rights ? (multiple "Name")
At the end of the day i need to now
Phone = x
Dial = x
HomeAuto = x
BoxAdmin = x
It’s also possible that some of the entries (Phone,Dial,HomeAuto,BoxAdmin) is missing. This
is dynamic.
Here is my xml File:
<SessionInfo>
<SID>68eba0c8cef752a7</SID>
<Challenge>37a5fe9f</Challenge>
<BlockTime>0</BlockTime>
<Rights>
<Name>Phone</Name>
<Access>2</Access>
<Name>Dial</Name>
<Access>2</Access>
<Name>HomeAuto</Name>
<Access>2</Access>
<Name>BoxAdmin</Name>
<Access>2</Access>
</Rights>
</SessionInfo>
Edit: (Add GetValue method)
public string GetValue(XDocument doc, string name)
{
XElement info = doc.FirstNode as XElement;
return info.Element(name).Value;
}
NB: this solution uses extension methods, so the using directives are important or you won't see the required functions.
using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Collections.Generic;
namespace StackOverflow
{
class Program
{
const string xml = "<SessionInfo><SID>68eba0c8cef752a7</SID><Challenge>37a5fe9f</Challenge><BlockTime>0</BlockTime><Rights><Name>Phone</Name><Access>2</Access><Name>Dial</Name><Access>2</Access><Name>HomeAuto</Name><Access>2</Access><Name>BoxAdmin</Name><Access>2</Access></Rights></SessionInfo>";
static void Main(string[] args)
{
XDocument doc = XDocument.Parse(xml); //loads xml from string above rather than file - just to make it easy for me to knock up this sample for you
string nameOfElementToFind = "Name";
IEnumerable<XElement> matches = doc.XPathSelectElements(string.Format("//*[local-name()='{0}']",nameOfElementToFind));
//at this stage you can reference any value from Matches by Index
Console.WriteLine(matches.Count() > 2 ? "Third name is: " + matches.ElementAt(2).Value : "There less than 3 values");
//or can loop through
foreach (XElement match in matches)
{
Console.WriteLine(match.Value);
//or if you also wanted the related access info (this is a bit loose / assumes the Name will always be followed by the related Value
//Console.WriteLine("{0}: {1}", match.Value, match.XPathSelectElement("./following-sibling::*[1]").Value);
}
Console.WriteLine("Done");
Console.ReadKey();
}
}
}
The important bit here is the line IEnumerable<XElement> matches = doc.XPathSelectElements(string.Format("//*[local-name()=\'{0}\']",nameOfElementToFind));. After the string.format takes place the XPath is //*[local-name()='Name']. This XPath statement says to find all nodes with the name Name. The local-name() function's there because we haven't said what schema's being used, in this instance we want any element called Name, regardless of schema.
XmlNamespaceManager nm = new XmlNamespaceManager(new NameTable());
nm.AddNamespace("eg", "http://Example/Namespace/Replace/With/Your/Docs/Namespace");
IEnumerable<XElement> matches = document.XPathSelectElements("//eg:Name", nm);
The double forward-slash says to search anywhere in the document. To limit it to Rights you could say /eg:SessionInfo/eg:Rights/eg:Name. In case you're unfamiliar with it, XPath's an awesome language / essential if you want to get the most out of working with XML docs. If you have any questions about it please give us a shout, or have a look around online; there are great tutorials out there.

Getting the right node in Linq to XML

Im trying to parse an XML file containing all the uploaded videos on a certain channel. Im attempting to get tbe value of the URL attribute in one of the <media:content> nodes and put it in the ViewerLocation field. However there are several of them. My current code is this:
var videos = from xElem in xml.Descendants(atomNS + "entry")
select new YouTubeVideo()
{
Title = xElem.Element(atomNS + "title").Value,
Description = xElem.Element(atomNS + "content").Value,
DateUploaded = xElem.Element(atomNS + "published").Value,
ThumbnailLocation = xElem.Element(mediaNS + "group").Element(mediaNS + "content").Attribute("url").Value,
ViewerLocation = xElem.Element(mediaNS + "group").Element(mediaNS + "content").Attribute("url").Value
};
It gets me the first node in the XML for entry with the name <media:content> as you would expect. However, the first entry in the XML isn't what I want. I want the second.
Below is the relevant XML.
<!-- I currently get the value held in this node -->
<media:content
url='http://www.youtube.com/v/ZTUVgYoeN_b?f=gdata_standard...'
type='application/x-shockwave-flash' medium='video'
isDefault='true' expression='full' duration='215' yt:format='5'/>
<!-- What i actually want is this one -->
<media:content
url='rtsp://rtsp2.youtube.com/ChoLENy73bIAEQ1kgGDA==/0/0/0/video.3gp'
type='video/3gpp' medium='video'
expression='full' duration='215' yt:format='1'/>
<media:content
url='rtsp://rtsp2.youtube.com/ChoLENy73bIDRQ1kgGDA==/0/0/0/video.3gp'
type='video/3gpp' medium='video'
expression='full' duration='215' yt:format='6'/>
I want the second node because it has a type of 'video/3gpp'. How would I go about selecting that one? My logic would be
if attribute(type == "video/3gpp") get this value.
But i do not know how to express this in Linq.
Thanks,
Danny.
Probably something like;
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
Edit: I didn't quite know how to expand and explain this one without assuming the OP had no knowledge of Linq. You want to make your original query;
from xElem in xml.Descendants(atomNS + "entry")
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
select new YouTubeVideo() {
...
}
You can interrogate attributes of a node, just like you can look at the elements of the document. If there are multiple elements with that attribute, you could then (assuming you always want the first you find)..
( from xElem in xml.Descendants(atomNS + "entry")
where xElem.Element(atomNS + "content").Attribute("type").Value == "video/3gpp"
select new YouTubeVideo() {
...
}).First();
I changed the original post, as I believe the node you're querying is the Element(atomNS + "content"), not the top level xElem
Using XPath from this Xml Library (Just because I know how to use it) with associated Get methods:
string videoType = "video/3gpp";
XElement root = XElement.Load(file); // or .Parse(xmlstring)
var videos = root.XPath("//entry")
.Select(xElem => new YouTubeVideo()
{
Title = xElem.Get("title", "title"),
Description = xElem.Get("content", "content"),
DateUploaded = xElem.Get("published", "published"),
ThumbnailLocation = xElem.XGetElement("group/content[#type={0}]/url", "url", videoType),
ViewerLocation = xElem.XGetElement("group/content[#type={0}]/url", "url", videoType)
});
If the video type doesn't change, you can replace the XGetElement's with:
xElem.XGetElement("group/content[#type='video/3gpp']/url", "url")
Its a lot cleaner not having to specify namespaces using the library. There is the Microsoft's XPathSelectElements() and XPathSelectElement() you can look into, but they require you to specify the namespaces and don't have the nice Get methods imo. The caveat is that the library isn't a complete XPath implementation, but it does work with the above.

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

SelectSingleNode returning null for known good xml node path using XPath

Consider this simple XML document. The serialized XML shown here is the result of an XmlSerializer from a complex POCO object whose schema I have no control over.
<My_RootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="">
<id root="2.16.840.1.113883.3.51.1.1.1" extension="someIdentifier" xmlns="urn:hl7-org:v3" />
<creationTime xsi:nil="true" xmlns="urn:hl7-org:v3" />
</My_RootNode>
The goal is to extract the value of the extension attribute on the id node. In this case, we are using the SelectSingleNode method, and given an XPath expression as such:
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/id");
//idNode is evaluated to null at this point in the debugger!
string msgID = idNode.Attributes.GetNamedItem("extension").Value;
The problem is that the SelectSingleNode method returns null for the given XPath expression.
Question: any ideas on this XPath query's correctness, or why this method call + XPath expression would return a null value? Perhaps the namespaces are part of the problem?
I strongly suspect the problem is to do with namespaces. Try getting rid of the namespace and you'll be fine - but obviously that won't help in your real case, where I'd assume the document is fixed.
I can't remember offhand how to specify a namespace in an XPath expression, but I'm sure that's the problem.
EDIT: Okay, I've remembered how to do it now. It's not terribly pleasant though - you need to create an XmlNamespaceManager for it. Here's some sample code that works with your sample document:
using System;
using System.Xml;
public class Test
{
static void Main()
{
XmlDocument doc = new XmlDocument();
XmlNamespaceManager namespaces = new XmlNamespaceManager(doc.NameTable);
namespaces.AddNamespace("ns", "urn:hl7-org:v3");
doc.Load("test.xml");
XmlNode idNode = doc.SelectSingleNode("/My_RootNode/ns:id", namespaces);
string msgID = idNode.Attributes["extension"].Value;
Console.WriteLine(msgID);
}
}
If you want to ignore namespaces completely, you can use this:
static void Main(string[] args)
{
string xml =
"<My_RootNode xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"\">\n" +
" <id root=\"2.16.840.1.113883.3.51.1.1.1\" extension=\"someIdentifier\" xmlns=\"urn:hl7-org:v3\" />\n" +
" <creationTime xsi:nil=\"true\" xmlns=\"urn:hl7-org:v3\" />\n" +
"</My_RootNode>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode idNode = doc.SelectSingleNode("/*[local-name()='My_RootNode']/*[local-name()='id']");
}
This should work in your case without removing namespaces:
XmlNode idNode = myXmlDoc.GetElementsByTagName("id")[0];
Sorry, you forgot the namespace. You need:
XmlNamespaceManager ns = new XmlNamespaceManager(myXmlDoc.NameTable);
ns.AddNamespace("hl7","urn:hl7-org:v3");
XmlNode idNode = myXmlDoc.SelectSingleNode("/My_RootNode/hl7:id", ns);
In fact, whether here or in web services, getting null back from an XPath operation or anything that depends on XPath usually indicates a problem with XML namespaces.
Just to build upon solving the namespace issues, in my case I've been running into documents with multiple namespaces and needed to handle namespaces properly. I wrote the function below to get a namespace manager to deal with any namespace in the document:
private XmlNamespaceManager GetNameSpaceManager(XmlDocument xDoc)
{
XmlNamespaceManager nsm = new XmlNamespaceManager(xDoc.NameTable);
XPathNavigator RootNode = xDoc.CreateNavigator();
RootNode.MoveToFollowing(XPathNodeType.Element);
IDictionary<string, string> NameSpaces = RootNode.GetNamespacesInScope(XmlNamespaceScope.All);
foreach (KeyValuePair<string, string> kvp in NameSpaces)
{
nsm.AddNamespace(kvp.Key, kvp.Value);
}
return nsm;
}
Well... I had the same issue and it was a headache. Since I didn't care much about the namespace or the xml schema, I just deleted this data from my xml and it solved all my issues. May not be the best answer? Probably, but if you don't want to deal with all of this and you ONLY care about the data (and won't be using the xml for some other task) deleting the namespace may solve your problems.
XmlDocument vinDoc = new XmlDocument();
string vinInfo = "your xml string";
vinDoc.LoadXml(vinInfo);
vinDoc.InnerXml = vinDoc.InnerXml.Replace("xmlns=\"http://tempuri.org\/\", "");
The rule to keep in mind is: if your document specifies a namespace, you MUST use an XmlNamespaceManager in your call to SelectNodes() or SelectSingleNode(). That's a good thing.
See the article Advantages of namespaces . Jon Skeet does a great job in his answer showing how to use XmlNamespaceManager. (This answer should really just be a comment on that answer, but I don't quite have enough Rep Points to comment.)
just use //id instead of /id. It works fine in my code
Roisgoen's answer worked for me, but to make it more general, you can use a RegEx:
//Substitute "My_RootNode" for whatever your root node is
string strRegex = #"<My_RootNode(?<xmlns>\s+xmlns([\s]|[^>])*)>";
var myMatch = new Regex(strRegex, RegexOptions.None).Match(myXmlDoc.InnerXml);
if (myMatch.Success)
{
var grp = myMatch.Groups["xmlns"];
if (grp.Success)
{
myXmlDoc.InnerXml = myXmlDoc.InnerXml.Replace(grp.Value, "");
}
}
I fully admit that this is not a best-practice answer, but but it's an easy fix and sometimes that's all we need.

Categories

Resources