This is my first time attempting to parse XML using C# and feel like I'm running in circles right now. I am calling a WCF web service which, based upon user input, conducts a search through a database against company names. It returns the results in an XML document with each entry formatted as it is below.
Given this XML structure, how would I go about obtaining the values for the d:AccountId and d:Name nodes using C#?
<entry>
<id></id>
<title type=\"text\"></title>
<updated></updated>
<author><name /></author>
<link rel=\"edit\" title=\"Account\" href=\"AccountSet\" />
<category term=\"Microsoft.Crm.Sdk.Data.Services.Account\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />
<content type=\"application/xml\">
<m:properties>
<d:neu_UniqueId></d:neu_UniqueId>
<d:AccountId m:type=\"Edm.Guid\"></d:AccountId>
<d:Name></d:Name>
</m:properties></content>
</entry>
Here is my first attempt. The program threw an exception at the node3 variable.
try
{
WebRequest myWebRequest = WebRequest.Create(URL);
myWebRequest.PreAuthenticate = true;
myWebRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;
//myWebRequest.Headers.Add("Access-Control-Allow-Origin", url);
WebResponse myWebResponse = myWebRequest.GetResponse();
Stream myFileStreamResult = myWebResponse.GetResponseStream();
Encoding encoder = System.Text.Encoding.GetEncoding("utf-8");
StreamReader readStream = new StreamReader(myFileStreamResult, encoder);
results = readStream.ReadToEnd();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(results);
XmlNodeList parentNode = xmlDoc.GetElementsByTagName("entry");
foreach (XmlNode childNode in parentNode)
{
string node = childNode.ToString();
string node2 = childNode.Value;
string node3 = childNode.Attributes["title"].Value;
string node7 = childNode.Attributes["m:properties"].Value;
string node8 = childNode.Attributes["m:properties\\d:AccountId"].Value;
string node9 = childNode.Attributes["m:properties\\d:Name"].Value;
string node10 = childNode.Attributes["m:properties\\d:AccountId"].Value;
}
}
Assuming the API reliably returns that XML structure you can simply specify the path to the node as "/entry/m:properties" then call get children. After that you'll want to loop over those nodes, checking for the nodes you want.
Currently your foreach loop is attempting all of those operations on the <id></id> node which causes an Exception because there is no "title" attribute.
So to give some sample code, you're looking for something like this;
XmlNode props = root.SelectSingleNode("/entry/m:properties");
for (int i = 0; i < props.ChildNodes.Count; i++)
{
if (propes.ChildNodes[i].Name = "node I want")
{
//do something
}
}
Or if you specifically only want those two values, then just use SelectSingleNode with the full path to that node. From your question it sounds like there is no reason to use iteration. So you could simply do;
string accountName = root.SelectSingleNode("/entry/m:properties/d:Name").InnerXml;
to get the account name.
using Linq2Xml will probably be a little easier.
var xml = "<entry xmlns:m=\"ns1\" xmlns:n=\"ns2\" xmlns:d=\"ns3\">" +
"<id></id>" +
"<title type=\"text\"></title>" +
"<updated></updated>" +
"<author><name /></author>" +
"<link rel=\"edit\" title=\"Account\" href=\"AccountSet\" />" +
"<category term=\"Microsoft.Crm.Sdk.Data.Services.Account\" scheme=\"http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\" />" +
"<content type=\"application/xml\">" +
"<m:properties>" +
"<d:neu_UniqueId></d:neu_UniqueId>" +
"<d:AccountId m:type=\"Edm.Guid\">Account ID</d:AccountId>" +
"<d:Name>Account Name</d:Name>" +
"</m:properties></content>" +
"</entry>";
const string namespaceM = "ns1";
const string namespaceD = "ns3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
{
using (var reader = XmlReader.Create(stream))
{
var document = XDocument.Load(reader, LoadOptions.None);
var contentNode = document.Elements().First().Elements().First(e => e.Name.LocalName == "content");
var propertiesNode = contentNode.Elements().First(d => d.Name.LocalName == "properties" && d.Name.Namespace == namespaceM);
var accountIdNode = propertiesNode.Elements().First(d => d.Name.LocalName == "AccountId" && d.Name.Namespace == namespaceD);
var nameNode = propertiesNode.Elements().First(d => d.Name.LocalName == "Name" && d.Name.Namespace == namespaceD);
var accountIdText = accountIdNode.Value;
var nameText = nameNode.Value;
}
}
Related
I have a Xamarin (C#) project, where I am trying to loop through some XML, but for some reason my code is not working.
This is what I have now:
DeviceList = new List<DeviceInfo>();
string ResultStatus = "";
string ResultDevice = "";
var result = Encoding.Default.GetString(e.Result);
result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + result;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(result);
string xPathStatus = "ed_listdevices";
var nodes = xmlDoc.SelectNodes(xPathStatus);
foreach (XmlNode xNode in nodes) {
ResultStatus = xNode.SelectSingleNode("//status").InnerText;
ResultDevice = xNode.SelectSingleNode("//device").InnerText;
}
if (ResultStatus.ToLower() == "ok") {
XmlDocument deviceDoc = new XmlDocument();
deviceDoc.LoadXml(result);
var deviceNodes = deviceDoc.SelectNodes(xPathStatus + "/device");
//foreach(XmlNode dNode in deviceNodes) {
for (int i = 0; i < deviceNodes.Count; i++) {
DeviceList.Add(new DeviceInfo() {
DeviceID = deviceNodes[i].SelectSingleNode("//id").InnerXml,
DeviceName = deviceNodes[i].SelectSingleNode("//name").InnerXml,
DeviceExtraName = "",
DeviceOnlineStatus = deviceNodes[i].SelectSingleNode("//status").InnerXml,
Location = deviceNodes[i].SelectSingleNode("//address").InnerXml,
Time = deviceNodes[i].SelectSingleNode("//time").InnerXml
});
}
}
When I step though the code I get the "ResultStatus" and "ResultDevice" correctly, and when I get to the
for (int i = 0; i < deviceNodes.Count; i++)
the "deviceNodes" variable have a count of 91, and I can see all the individual xml elements that I am suppose to get from the webservice I am calling.
However, when I loop through deviceNodes[i] I only get values from the very first XML element (yes, the value of "i" does change). In other words, my DeviceList is filled with 91 entries with the same values.
Am I missing something obvious?? What am I doing wrong since this isn't working??
PS: I have also tried using a foreach (XmlNode node in deviceNodes) but the result was the same.
"//" in the selector tells it to search from the root node of the document. If you want to search locally under the "current" node, remove the "//"
I have created a XML string and Looping that to get value. But its not entering in foreach loop. But in my other code same loop code is working.
my code is :
XML string:
<SuggestedReadings>
<Suggestion Text="Customer Centricity" Link="http://wdp.wharton.upenn.edu/book/customer-centricity/?utm_source=Coursera&utm_medium=Web&utm_campaign=custcent" SuggBy="Pete Fader�s" />
<Suggestion Text="Global Brand Power" Link="http://wdp.wharton.upenn.edu/books/global-brand-power/?utm_source=Coursera&utm_medium=Web&utm_campaign=glbrpower" SuggBy="Barbara Kahn�s" />
</SuggestedReadings>
Code Is:
string str = CD.SRList.Replace("&", "&");
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(str);
XmlNode SuggestionListNode = xmlDoc.SelectSingleNode("/SuggestedReadings/Suggestion");
foreach (XmlNode node in SuggestionListNode)
{
COURSESUGGESTEDREADING CSR = new COURSESUGGESTEDREADING();
var s = db.COURSESUGGESTEDREADINGS.OrderByDescending(o => o.SRID);
CSR.SRID = (s == null ? 0 : s.FirstOrDefault().SRID) + 1;
CSR.COURSEID = LibId;
CSR.TEXT = node.Attributes.GetNamedItem("Text").Value;
CSR.LINK = node.Attributes.GetNamedItem("Link").Value; ;
CSR.SUGBY = node.Attributes.GetNamedItem("SuggBy").Value; ;
CSR.ACTIVEFLAG = "Y";
CSR.CREATEDBY = CD.CreatedBy;
CSR.CREATEDDATE = DateTime.Now;
db.COURSESUGGESTEDREADINGS.Add(CSR);
}
You should use SelectNodes, not SelectSingleNode, since you are trying to get multiple rows out of the XML document.
Use this:
XmlNodeList SuggestionListNode = xmlDoc.SelectNodes("//Suggestion");
foreach (XmlNode node in SuggestionListNode)
{
}
You can try this.
XDocument xdoc = XDocument.Load("data.xml");
var xmlData = from lv1 in xdoc.Descendants("Suggestion")
select new {
Text = lv1.Attribute("Text").Value,
Link = lv1.Attribute("Link").Value,
SuggBy = lv1.Attribute("SuggBy").Value
};
foreach (var item in xmlData){
// your logic here
}
I am parsing an xml file using the following which works well but my question is how do I get the value from the tag <themename> because I am using oXmlNodeList for my loop. Just don't want to be writing a separate routine for one tag.
XmlDocument xDoc = new XmlDocument();
List<active_theme> listActiveTheme = new List<active_theme>();
string path = AppDomain.CurrentDomain.BaseDirectory + #"themes\test\configuration.xml";
xDoc.Load(AppDomain.CurrentDomain.BaseDirectory + #"themes\test\configuration.xml");
//select a list of Nodes matching xpath expression
XmlNodeList oXmlNodeList = xDoc.SelectNodes("configuration/masterpages/masterpage");
Guid newGuid = Guid.NewGuid();
foreach (XmlNode x in oXmlNodeList)
{
active_theme activetheme = new active_theme();
string themepage = x.Attributes["page"].Value;
string masterpage = x.Attributes["name"].Value;
activetheme.active_theme_id = newGuid;
activetheme.theme = "Dark";
activetheme.page = themepage;
portalContext.AddToActiveTheme(activetheme);
}
portalContext.SaveChanges();
XML File for theme configuration
<configuration>
<themename>Dark Theme</themename>
<masterpages>
<masterpage page="Contact" name="default.master"></masterpage>
<masterpage page="Default" name="default.master"></masterpage>
<masterpage page="Blue" name="blue.master"></masterpage>
<masterpage page="red" name="red.master"></masterpage>
</masterpages>
</configuration>
I'd use LINQ to XML to start with. Something like this:
var doc = XDocument.Load(...);
var themeName = doc.Root.Element("themename").Value;
Guid themeGuid = Guid.NewGuid();
foreach (var element in doc.Root.Element("masterpages").Elements("masterpage"))
{
ActiveTheme theme = new ActiveTheme
{
ThemeName = themeName,
ActiveThemeId = themeGuid,
Page = element.Attribute("page").Value,
MasterPage = element.Attribute("name").Value
};
portalContent.AddToActiveTheme(theme);
}
portalContext.SaveChanges();
My goal is to save the data contained in the ValueReference node, TimeInstant attribute, and timePosition node into variables. I am able to get the value of the valueReference node (the un-commented section works), but not the other two.
Any help would be greatly appreciated.
Here is the code that I am working on:
public void LinqToXml()
{
XNamespace sosNamespace = XNamespace.Get("http://www.opengis.net/sos/2.0");
XNamespace fesNamespace = XNamespace.Get("http://www.opengis.net/fes/2.0");
XNamespace gmlNamespace = XNamespace.Get("http://www.opengis.net/gml/2.0");
var root = XElement.Load(#"C:\Working Directory\OGCSOS.Service\OGCSOS.Service\Resources\GetObservation_TemporalFilter.xml");
var a = (from level in root.Descendants(sosNamespace + "temporalFilter")
select new
{
valueReference = (string)level.Descendants(fesNamespace + "After")
.Elements(fesNamespace + "ValueReference")
.First(),
/*timeInstant = (string)level.Descendants(fesNamespace + "After")
.Elements(gmlNamespace + "TimeInstant")
.Attributes(gmlNamespace + "id")
.First()*/
/*timePosition = (string)level.Descendants(fesNamespace + "After")
.Elements(gmlNamespace + "TimeInstant")
.First()*/
}).ToList();
And here is the XML I am trying to read:
<?xml version="1.0" encoding="UTF-8"?>
<sos:GetObservation xmlns="http://www.opengis.net/sos/2.0" service="SOS" version="2.0.0"
xmlns:sos="http://www.opengis.net/sos/2.0" xmlns:fes="http://www.opengis.net/fes/2.0"
xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:swe="http://www.opengis.net/swe/2.0"
xmlns:swes="http://www.opengis.net/swes/2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/sos/2.0
http://schemas.opengis.net/sos/2.0/sos.xsd">
<!--identifier of an offering-->
<offering>HG.Logger#DJK001</offering>
<!--identifier of an observed property-->
<observedProperty>HG</observedProperty>
<!--optional temporal filter restricting the results which shall be returned-->
<temporalFilter>
<fes:After>
<fes:ValueReference>phenomenonTime</fes:ValueReference>
<gml:TimeInstant gml:id="startPosition">
<gml:timePosition>2008-03-01T17:44:15.000+00:00</gml:timePosition>
</gml:TimeInstant>
</fes:After>
</temporalFilter>
<featureOfInterest>DJK001</featureOfInterest>
</sos:GetObservation>
Your gml namespace is not correct, after changing it to
XNamespace gmlNamespace = XNamespace.Get("http://www.opengis.net/gml/3.2");
you can use
timeInstant = level.Descendants(fesNamespace + "After")
.First()
.Element(gmlNamespace + "TimeInstant")
.Attribute(gmlNamespace + "id")
.Value,
timePosition = level.Descendants(fesNamespace + "After")
.First()
.Element(gmlNamespace + "TimeInstant")
.Value
You should do it like this
XNamespace sosNamespace = "http://www.opengis.net/sos/2.0";
XNamespace fesNamespace = "http://www.opengis.net/fes/2.0";
XNamespace gmlNamespace = "http://www.opengis.net/gml/3.2";
//you had used 2.0 instead of 3.2
var root = XElement.Load(#"C:\WorkingDirectory\OGCSOS.Service\OGCSOS.Service\Resources\GetObservation_TemporalFilter.xml");
var yourList=root.Descendants(sosNamespace+"temporalFilter").Descendants(fesNamespace+"After").Select(x=>
new
{
ValueReference=x.Element(fesNamespace+"ValueReference").Value,
timeInstant=x.Element(gmlNamespace+"TimeInstant").Attribute(gmlNamespace+"id").Value,
timePosition=x.Element(gmlNamespace+"TimeInstant").Element(gmlNamespace+"timePosition").Value
}
);
yourList contains all the data
you can also use good old XPath
var doc = new XPathDocument("1.xml");
var nav = doc.CreateNavigator();
var mng = new XmlNamespaceManager(nav.NameTable);
mng.AddNamespace("sos", "http://www.opengis.net/sos/2.0");
mng.AddNamespace("fes", "http://www.opengis.net/fes/2.0");
mng.AddNamespace("gml", "http://www.opengis.net/gml/3.2");
var valueReference = nav.SelectSingleNode("//sos:GetObservation/sos:temporalFilter/fes:After/fes:ValueReference[1]", mng).TypedValue;
var TimeInstant = nav.SelectSingleNode("//sos:GetObservation/sos:temporalFilter/fes:After/gml:TimeInstant/#gml:id", mng).TypedValue;
var timePosition = nav.SelectSingleNode("//sos:GetObservation/sos:temporalFilter/fes:After/gml:TimeInstant/gml:timePosition[1]", mng).TypedValue;
TRY THIS CODE:
XmlDocument xmlSnippet = new XmlDocument();
xmlSnippet.Load(#"C:\Working Directory\OGCSOS.Service\OGCSOS.Service\Resources\GetObservation_TemporalFilter.xml");
//Selects all the similar nodes by tag Name.......
XmlNodeList xmlSnippetNodes = xmlSnippet.GetElementsByTagName("fes:After");
//Checking if any any xmlSnippetNode has matched.
if (xmlSnippetNodes != null)
{
//Checks if the matched xmlSnippetNode that has fes:After attribute is not NULL
//Stores the value of the matched tag.
var valueReference= xmlSnippetNodes.Item(0).Value;
var timeInstance=xmlSnippetNodes.Item(1).Attributes["gml:id"].Value;
var timePosition =xmlSnippetNodes.Item(1).InnerXml.Name;
//Return True if updated correctly.
isUpdated = true;
}
I want to update the xml document and i need to return the updated xml in string. I am trying like below. when i save the document it expects the file name. but i dont want to save this as file. i just want to get the updated xml in string.
string OldXml = #"<Root>
<Childs>
<first>this is first</first>
<second>this is second </second>
</Childs
</Root>";
XmlDocument NewXml = new XmlDocument();
NewXml.LoadXml(OldXml );
XmlNode root = NewXml.DocumentElement;
XmlNodeList allnodes = root.SelectNodes("*");
foreach (XmlNode eachnode in allnodes)
{
if (eachnode.Name == "first")
{
eachnode.InnerText = "1";
}
}
NewXml.Save();
string newxml = NewXml.OuterXml;
You don't need to call Save method because string is immutable, your problem is in root.SelectNodes("*"), it just get child nodes, not all level of nodes. You need to go one more level:
foreach (XmlNode eachnode in allnodes)
{
var firstNode = eachnode.ChildNodes.Cast<XmlNode>()
.SingleOrDefault(node => node.Name == "first");
if (firstNode != null)
{
firstNode.InnerText = "1";
}
}
string newxml = NewXml.OuterXml;
It would be strongly recommended using LINQ to XML, it's simpler:
var xDoc = XDocument.Parse(OldXml);
foreach (var element in xDoc.Descendants("first"))
element.SetValue(1);
string newXml = xDoc.ToString();
Your iteration never reaches a node called "first". Else it would work fine without saving the NewXml.
You could however use a XElement and iterate over all descendants.
string OldXml = #"<Root>
<Childs>
<first>this is first</first>
<second>this is second </second>
</Childs>
</Root>";
var NewXml = XElement.Parse(OldXml);
foreach (var node in NewXml.Descendants())
{
if (node.Name.LocalName == "first")
{
node.Value = "1";
}
}
var reader = NewXml.CreateReader();
reader.MoveToContent();
string newxml = reader.ReadInnerXml();