Why is this program not accessing child nodes? - c#

Here it gets the XML document and individual nodes, and inserts the nodes into a dictionary.
//create the xml document obj
XmlDocument inputXMLDoc = new XmlDocument();
fileref.isValid = false;
//load the xml document
#region
try
{
inputXMLDoc.XmlResolver = null;
inputXMLDoc.Load( strfile );//load the xml file
string input = inputXMLDoc.OuterXml;//get the string
Console.WriteLine( "success,loaded XML" );
logger.Log( "loaded xml:" + strfile );
fileref.importList = new Dictionary<string, XmlNode>();
nodeNames = new List<string> { "OrderId", "CustomerId", "CustomerName", "Addresses", "OrderStatus", "DateOrdered", "PaymentTime", "IncludeVAT", "OrderTotalIncVat", "OrderTotalVat", "Currency", "TypeOfSaleId" };
try
{
int i = 0;
foreach( string name in nodeNames )
{
Console.WriteLine( "Adding xml node " + name );
if( inputXMLDoc.GetElementsByTagName( name ) != null )
{
XmlNodeList xlist = inputXMLDoc.GetElementsByTagName( name );
foreach( XmlNode node in xlist )
{
fileref.importList.Add( name, node );
//add individual node within nodelist
Console.WriteLine( name );
}
} //add specified node from XML doc
else
{
nodeNames.RemoveAt( i );
}
i++;
}
}
}
Later, the nodes are accessed to save the information to a web service. However, nodes with child nodes within are not showing up this way.
Invoices.Address address = new Invoices.Address();
XmlNodeList oNodeList = fileref.importList["Addresses"].SelectNodes("/Delivery/Street");
foreach (XmlNode xn in oNodeList)
{
address.Street = xn.InnerText;
}
Sample XML document
<?xml version="1.0" encoding="utf-8"?>
<InvoiceOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OrderId xmlns="http://24sevenOffice.com/webservices">35</OrderId>
<CustomerId xmlns="http://24sevenOffice.com/webservices">21</CustomerId>
<CustomerName xmlns="http://24sevenOffice.com/webservices">James Bond</CustomerName>
<Addresses xmlns="http://24sevenOffice.com/webservices">
<Delivery>
<Street>11 Shewell Walk</Street>
<State />
<PostalCode>CO1 1WG</PostalCode>
<PostalArea>Essex</PostalArea>
<Name />
<City>Colchester</City>
<Country>UK</Country>
</Delivery>
<Invoice>
<Street>10 Shewell Walk</Street>
<State />
<PostalCode>CO1 1WG</PostalCode>
<PostalArea>Essex</PostalArea>
<Name />
<City>Colchester</City>
<Country>UK</Country>
</Invoice>
</Addresses>
<OrderStatus xmlns="http://24sevenOffice.com/webservices">Offer</OrderStatus>
<DateOrdered xmlns="http://24sevenOffice.com/webservices">2015-06-15T14:00:00Z</DateOrdered>
<PaymentTime xmlns="http://24sevenOffice.com/webservices">14</PaymentTime>
<IncludeVAT xsi:nil="true" xmlns="http://24sevenOffice.com/webservices" />
<OrderTotalIncVat xmlns="http://24sevenOffice.com/webservices">480.0000</OrderTotalIncVat>
<OrderTotalVat xmlns="http://24sevenOffice.com/webservices">80.0000</OrderTotalVat>
<Currency xmlns="http://24sevenOffice.com/webservices">
<Symbol>LOCAL</Symbol>
</Currency>
<TypeOfSaleId xmlns="http://24sevenOffice.com/webservices">-100</TypeOfSaleId>
<InvoiceRows xmlns="http://24sevenOffice.com/webservices">
<InvoiceRow>
<ProductId>18</ProductId>
<RowId>4665754</RowId>
<Price>400.0000</Price>
<Name>17" Laptop Screen</Name>
<DiscountRate>0.0000</DiscountRate>
<Quantity>7.0000</Quantity>
<Cost>0.0000</Cost>
<InPrice>0.0000</InPrice>
</InvoiceRow>
</InvoiceRows>
</InvoiceOrder>

The reason your code doesn't work is likely that you're ignoring the namespace of the elements you're looking for. There are many questions covering how to do that, such as this one.
That said, XmlDocument is a creaky old API and the newer LINQ to XML is a huge improvement - I'd suggest you look into that.
I'm also not sure the dictionary is pulling its weight for such a small number of elements. You can simply query what you need straight from the XML. For example, to get all your fields as typed values:
var doc = XDocument.Parse(strfile);
var order = doc.Elements("InvoiceOrder").Single();
XNamespace ns = "http://24sevenOffice.com/webservices";
var orderId = (int)order.Element(ns + "OrderId");
var customerId = (int)order.Element(ns + "CustomerId");
var customerName = (string)order.Element(ns + "CustomerName");
var orderStatus = (string)order.Element(ns + "OrderStatus");
var dateOrdered = (DateTime)order.Element(ns + "DateOrdered");
var paymentTime = (int)order.Element(ns + "PaymentTime");
var totalIncVat = (decimal)order.Element(ns + "OrderTotalIncVat");
var totalVat = (decimal)order.Element(ns + "OrderTotalVat");
var currency = (string)order.Elements(ns + "Currency").Elements(ns + "Symbol").SingleOrDefault();
var typeOfSaleId = (int)order.Element(ns + "TypeOfSaleId");
You can use a similar technique to get map your addresses to your strongly typed Address class:
var deliveryAddress = order.Elements(ns + "Addresses")
.Elements(ns + "Delivery")
.Select(e => new Invoice.Address
{
Street = (string)e.Element(ns + "Street"),
// ....
})
.Single();

The problem you have is with namespaces. If you specify the namespace for each of those elements then it seems to work. I came to this conclusion with a bit of googling and some experimentation so my explanation might not be spot on so I advise researching the issue further yourself to understand it correctly.
This code will work:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(inputXMLDoc.NameTable);
nsmgr.AddNamespace("ns", "http://24sevenOffice.com/webservices");
var oNodeList = importList["Addresses"].SelectNodes("//ns:Delivery/ns:Street",nsmgr);
The reason is (I think) that in your XML document you are specifying a default namespace for your elements (xmlns="http://24sevenOffice.com/webservices") and in your xpath you are not specifying that same namespace. In my code I create a namespace manager with that namespace in and prefix it to the two elements which it now considers to match the ones in your document that have these namespaces.

Related

Need Help About the Xml File generated by my code

Below is my controller Code which generate the XML file and save this file and specific location
List<propertyfile> data = ws.GetPropertyFiles();
XmlDocument writer = new XmlDocument();
// Create XML declaration
XmlNode declaration = writer.CreateNode(XmlNodeType.XmlDeclaration, null, null);
writer.AppendChild(declaration);
// Make the root element first
XmlElement root = writer.CreateElement("Channel");
writer.AppendChild(root);
//XmlElement rot = writer.CreateElement("item");
//writer.AppendChild(rot);
//Server.MapPath //HttpContext.Current.Server.MapPath("~")
string wallFolderPath = Server.MapPath("~/assets/wall/");
var newGuid = Guid.NewGuid().ToString();
string filename = wallFolderPath+ "wallview_" +newGuid+".xml";
var pathlocal = ServerUrlPath+"assets/wall/" + "wallview_" + newGuid + ".xml";
ViewBag.wallpath = pathlocal;
System.IO.File.Create(filename).Dispose();
foreach (var a in data)
{
if (a.Path != null && a.ThumbnailPath != null)
{
XmlElement id = writer.CreateElement("item");
XmlElement name = writer.CreateElement("title");
name.InnerText = a.Name;
XmlElement age = writer.CreateElement("media:description");
var e_path = a.Path;
XmlElement anchor = writer.CreateElement("a");
e_path = e_path.Substring(2, e_path.Length - 2);
e_path= ServerUrlPath + e_path;
anchor.SetAttribute("href", e_path);
age.AppendChild(anchor);
//age.SetAttribute("href", e_path);
XmlElement linq = writer.CreateElement("link");
var e_linq = a.Path;
e_linq = e_linq.Substring(2, e_linq.Length - 2);
linq.InnerText = ServerUrlPath + e_linq;
XmlElement thumb = writer.CreateElement("media:thumbnail");
var e_thumbnail = a.ThumbnailPath;
e_thumbnail = e_thumbnail.Substring(2, e_thumbnail.Length - 2);
e_thumbnail = ServerUrlPath + e_thumbnail;
thumb.SetAttribute("url", e_thumbnail);
XmlElement content = writer.CreateElement("media:content");
content.SetAttribute("url", e_thumbnail);
id.AppendChild(name);
id.AppendChild(age);
id.AppendChild(linq);
id.AppendChild(thumb);
id.AppendChild(content);
root.AppendChild(id);
writer.AppendChild(root);
}
}
writer.Save(filename);
Result(XML File Generated)
<Channel>
<item>
<title>RedbrushAlpha.png</title>
<description>
<a href="http://localhost:2023/Files/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.png"/>
</description>
<link>
http://localhost:2023/Files/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.png
</link>
<thumbnail url="http://localhost:2023/FilesThumbs/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.Jpeg"/>
<content url="http://localhost:2023/FilesThumbs/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.Jpeg"/>
</item>
But I Need file generate similar to this with rss tag and tag like Below is the xml file code and i need to create file similar to this
<?xml version="1.0" ?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<item>
<title>mastercard.png</title>
<media:description><a href=' http://localhost:2023/Files/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.png'</media:description>
<link>http://localhost:2023/Files/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.png</link>
<media:thumbnail url="http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg" />
<media:content url="http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg" />
</item>
</channel>
</rss>
hi please check updated code with some changes you will now get expect output
you just need to update value of tag and your output will be ready
XmlDocument writer = new XmlDocument();
// Create XML declaration
XmlNode declaration = writer.CreateNode(XmlNodeType.XmlDeclaration, null, null);
writer.AppendChild(declaration);
//add rss node over here
XmlElement rssElement = writer.CreateElement("rss");
rssElement.SetAttribute("version", "2.0");
rssElement.SetAttribute("xmlns:media", "http://search.yahoo.com/mrss/");
rssElement.SetAttribute("xmlns:atom", "http://www.w3.org/2005/Atom");
//Channel
XmlElement channelElement = writer.CreateElement("Channel");
XmlElement itemElement = writer.CreateElement("item");
//title
XmlElement titleElement = writer.CreateElement("title");
titleElement.InnerText = "mastercard.png";
itemElement.AppendChild(titleElement);
//media:description
XmlElement media_DescriptionElement = writer.CreateElement("media", "description", "media:description");
media_DescriptionElement.InnerText = "<a href=' http://localhost:2023/Files/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.png";
itemElement.AppendChild(media_DescriptionElement);
//link
XmlElement linkElement = writer.CreateElement("link");
linkElement.InnerText = "http://localhost:2023/Files/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.png";
itemElement.AppendChild(linkElement);
//media:thumbnail
XmlElement media_ThumbnailElement = writer.CreateElement("media", "thumbnail", "media:thumbnail");
media_ThumbnailElement.SetAttribute("url", "http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg");
itemElement.AppendChild(media_ThumbnailElement);
//media:content
XmlElement media_ContentElement = writer.CreateElement("media", "content", "media:content");
media_ContentElement.SetAttribute("url", "http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg");
itemElement.AppendChild(media_ContentElement);
channelElement.AppendChild(itemElement);
rssElement.AppendChild(channelElement);
writer.AppendChild(rssElement);
Output
<?xml version="1.0"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom">
<Channel>
<item>
<title>mastercard.png</title>
<media:description xmlns:media="media:description">&lt;a href=' http://localhost:2023/Files/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.png</media:description>
<link>http://localhost:2023/Files/aa1989f3-f4bd-489d-abca-b0c7cdfa4ae7.png</link>
<media:thumbnail url="http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg" xmlns:media="media:thumbnail" />
<media:content url="http://localhost:2023/FilesThumbs/054ee47b-7ecf-42d1-bfcc-06ac5f84b6d4.Jpeg" xmlns:media="media:content" />
</item>
</Channel>
</rss>
Well, first and foremost, although it doesn't really change anything, it's important to realize that both XDocument and XElement accepts params, i.e. a list of items to add. This will make your code much more readable. For example:
var xdoc = new XDocument(
new XElement("root",
new XElement("foo"),
new XElement("bar")
);
);
That way the hierarchy is very clear, and you're not throwing variables all over the place.
Second, you literally made your root <channel>, rather than <rss>, so simply make <rss> your root element in your XDocument and then add <channel> to that, rather than directly to the XDocument as you're doing now.
Third, you can add attributes to elements using XAttribute. However, for namespaces, you want to persist these in a variable, so you can utilize them later as you're building your XML document. For example:
var media = XNamespace.Get("http://search.yahoo.com/mrss/");
var atom = XNamespace.Get("http://www.w3.org/2005/Atom");
var xdoc = new XDocument(
new XElement("rss",
new XAttribute("version", "2.0"),
new XAttribute(XNamespace.Xmlns + "media", media),
new XAttribute(XNamespace.Xmlns + "atom", atom),
new XElement("channel",
...
)
)
);
Then, when you need to use one of these namespaces, you concatentate it to the element name. For example, with media:thumbnail:
new XElement(media + "thumbnail",
new XAttribute("url", e_thumbnail)
)
Finally, if you use ToString on the XDocument, it will strip the XML declaration, for some boneheaded reason. Instead, you need to use a TextWriter to get the output:
var sb = new StringBuilder();
using (TextWriter writer = new Utf8StringWriter(sb))
{
xdoc.Save(writer);
}
var xmlString = sb.ToString();
FWIW, this is why you should name your XDocument variable something like xdoc rather than writer. For one, XDocument doesn't actually "write" anything, but also, writer is generally reserved for actual "writers" like TextWriter. That's all semantic, of course, but especially when dealing with XML, which usually involves voluminous amounts of code, you want it to be as readable and understandable as possible.
One last little tip. When building XML in the way I've shown here, you can pass actual enumerables just as well as single items. For example, let's say you created your list of channel items separately, such that you have a List<XElement> called items. You can actually just do:
new XElement("channel",
new XElement("title", "My Awesome Channel"),
new XElement("link", "https://foo.com/rss"),
new XElement("description", "Best channel ever!"),
items
)
EDIT
I completely forgot about Utf8StringWriter. That's a derived class of StringWriter, because StringWriter stupidly returns UTF16, and there's no way to modify that without deriving from it. The implementation is simple, but you'll need to add this to your project:
internal sealed class Utf8StringWriter : StringWriter
{
public Utf8StringWriter(StringBuilder sb)
: base(sb)
{
}
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
}
If you want to put this in a class library or something and share it between projects, just change internal to public.

Finding a set of nodes in an XML [duplicate]

Can't get any result in feeds.
feedXML has the correct data.
XDocument feedXML = XDocument.Load(#"http://search.twitter.com/search.atom?q=twitter");
var feeds = from entry in feedXML.Descendants("entry")
select new
{
PublicationDate = entry.Element("published").Value,
Title = entry.Element("title").Value
};
What am I missing?
You need to specify the namespace:
// This is the default namespace within the feed, as specified
// xmlns="..."
XNamespace ns = "http://www.w3.org/2005/Atom";
var feeds = from entry in feedXML.Descendants(ns + "entry")
...
Namespace handling is beautifully easy in LINQ to XML compared with everything other XML API I've ever used :)
You need to specify a namespace on both the Descendents and Element methods.
XDocument feedXML = XDocument.Load(#"http://search.twitter.com/search.atom?q=twitter");
XNamespace ns = "http://www.w3.org/2005/Atom";
var feeds = from entry in feedXML.Descendants(ns + "entry")
select new
{
PublicationDate = entry.Element(ns + "published").Value,
Title = entry.Element(ns + "title").Value
};
If you look at the XML returned by the HTTP request, you will see that it has an XML namespace defined:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" ...>
<id>tag:search.twitter.com,2005:search/twitter</id>
...
</feed>
XML is just like C#, if you use an element name with the wrong namespace, it is not considered to be the same element! You need to add the required namepsace to your query:
private static string AtomNamespace = "http://www.w3.org/2005/Atom";
public static XName Entry = XName.Get("entry", AtomNamespace);
public static XName Published = XName.Get("published", AtomNamespace);
public static XName Title = XName.Get("title", AtomNamespace);
var items = doc.Descendants(AtomConst.Entry)
.Select(entryElement => new FeedItemViewModel()
new {
Title = entryElement.Descendants(AtomConst.Title).Single().Value,
...
});
The issue is in feedXML.Descendants("entry"). This is returning 0 results
According to the documentation you need to put in a fully qualified XName

Get XmlNodeList if a particular element value or its attribute value is present in a given list of strings

I would like to get XmlNodeList from a huge XML file.
Conditions:
I have a List of unique ID values, say IDList
Case I: Collect all the nodes where element called ID has value from IDList.
Case II: Collect all nodes where one of the attribute called idName of element ID has value from IDList.
In short, extract only the nodes which match with the values given in the IDList.
I did this using some loops like load this XML to XmlDocument to iterate over all nodes and ID value but what I am looking for is some sophisticated method to do it faster and in quick way.
Because looping isn't a solution for a large XML file.
My try:
try
{
using (XmlReader reader = XmlReader.Create(URL))
{
XmlDocument doc = new XmlDocument();
doc.Load(reader);
XmlNodeList nodeList = doc.GetElementsByTagName("idgroup");
foreach (XmlNode xn in nodeList)
{
string id = xn.Attributes["id"].Value;
string value = string.Empty;
if (IDList.Contains(id))
{
value = xn.ChildNodes[1].ChildNodes[1].InnerText; // <value>
if (!string.IsNullOrEmpty(value))
{
listValueCollection.Add(value);
}
}
}
}
}
catch
{}
XML (XLIFF) structure:
<XLIFF>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2">
<file date="2013-07-17">
<body>
<id idName="test_001" >
<desc-group name="test_001">
<desc type="text"/>
</desc-group>
<result-unit idName="test_001_text">
<source>abcd</source>
<result>xyz</result>
</result-unit>
</id>
</body>
</file>
</xliff>
Collect all the nodes like above where idName matches.
EDIT
This is a test that can parse the example you are giving. It attempts to reach the result node directly, so that it stays as efficient as possible.
[Test]
public void TestXPathExpression()
{
var idList = new List<string> { "test_001" };
var resultsList = new List<string>();
// Replace with appropriate method to open your URL.
using (var reader = new XmlTextReader(File.OpenRead("fixtures\\XLIFF_sample_01.xlf")))
{
var doc = new XmlDocument();
doc.Load(reader);
var root = doc.DocumentElement;
// This is necessary, since your example is namespaced.
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("x", "urn:oasis:names:tc:xliff:document:1.2");
// Go directly to the node from which you want the result to come from.
foreach (var nodes in idList
.Select(id => root.SelectNodes("//x:file/x:body/x:id[#idName='" + id + "']/x:result-unit/x:result", nsmgr))
.Where(nodes => nodes != null && nodes.Count > 0))
resultsList.AddRange(nodes.Cast<XmlNode>().Select(node => node.InnerText));
}
// Print the resulting list.
resultsList.ForEach(Console.WriteLine);
}
You can extract only those nodes you need by using an XPath query. A brief example on how you 'd go about it:
using (XmlReader reader = XmlReader.Create(URL))
{
XmlDocument doc = new XmlDocument();
doc.Load(reader);
foreach(var id in IDList) {
var nodes = doc.SelectNodes("//xliff/file/body/id[#idName='" + id + "']");
foreach(var node in nodes.Where(x => !string.IsNullOrEmpty(x.ChildNodes[1].ChildNodes[1].InnerText)))
listValueCollection.Add(node.ChildNodes[1].ChildNodes[1].InnerText);
}
}
The xpath expression is of course an example. If you want, you can post an example of your XML so I can give you something more accurate.

Parsing specific part of XML file

I have a .gpx XML file with the following sample:
<trk>
<name>Test</name>
<trkseg>
<trkpt lon="-84.89032996818423" lat="32.75810896418989">
<ele>225.0</ele>
<time>2011-04-02T11:57:48.000Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:cad>0</gpxtpx:cad>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
</trkseg>
</trk>
I'm using Linq to XML to parse this but I'm having a difficult time parsing the extensions section. Here's the code I'm using:
var gpxDoc = LoadFromStream(document);
var gpx = GetGpxNameSpace();
var gpxtpx = XNamespace.Get("gpxtpx");
var tracks = from track in gpxDoc.Descendants(gpx + "trk")
select new
{
Name = DefaultStringValue(track, gpx, "name"),
Description = DefaultStringValue(track, gpx, "desc"),
Segments = (from trkSegment in track.Descendants(gpx + "trkseg")
select new
{
TrackSegment = trkSegment,
Points = (from trackpoint in trkSegment.Descendants(gpx + "trkpt")
select new
{
Lat = Double(trackpoint.Attribute("lat").Value),
Lng = Double(trackpoint.Attribute("lon").Value),
Ele = DefaultDoubleValue(trackpoint, gpx, "ele"),
Time = DefaultDateTimeValue(trackpoint, gpx, "time"),
Extensions = (
from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault()
})
})
};
Here's the relevant helper code:
private static double? DefaultIntValue(XContainer element, XNamespace ns, string elementName)
{
var xElement = element.Element(ns + elementName);
return xElement != null ? Convert.ToInt32(xElement.Value) : (int?)null;
}
private XNamespace GetGpxNameSpace()
{
var gpx = XNamespace.Get("http://www.topografix.com/GPX/1/1");
return gpx;
}
The actual error I'm getting is
The following error occurred: Object reference not set to an instance of an object.
and it bombs on this code:
Extensions = (from ext in trackpoint.Descendants(gpx + "extensions").Descendants(gpxtpx + "TrackPointExtension")
select new
{
Cad = DefaultIntValue(ext, gpxtpx, "cad")
}).SingleOrDefault();
I just don't know how to fix it.
Since you never declare the namespace (xmlns:gpxtpx="http://www.topografix.com/GPX/1/1") it is never going to match. The xml fragment you provided is not well formed due to the lack of the namespace.
If the fragment posted is snipped from a larger document, consider switching to XML API's rather than string manipulation. If that is the entirety of the XML you receive from an outside system, add it to a root node which you can declare the schema in:
<root xmlns:gpxtpx="http://www.topografix.com/GPX/1/1">
<!-- put your xml fragment here -->
</root>

Retrieving element attributes for multiple nodes

I am having the hardest time figuring this out, I have a XML doc that has multiple nodes with the same name. Within those nodes are even more nodes with same name but different attributes and thats what I want to capture. Here is an example of the XML:
<?xml version="1.0" encoding="utf-8"?>
<TopologyDefinition xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/online/managementsystems/topologydefinition/2009/11">
<Topology Name="testenv">
<DataCenters>
<DataCenter Name="FL" Type="Active">
<Machines>
<Machine Name="FLVMServer1" VmHost="FLVHost100">
<IPBindings>
<IPBinding VirtualNetworkType="Data" IP="192.168.0.10" />
</IPBindings>
</Machine>
<Machine Name="FLVMServer2" VmHost="FLVHost200">
<IPBindings>
<IPBinding VirtualNetworkType="Data" IP="192.168.0.20" />
</IPBindings>
</Machine>
</DataCenter>
<DataCenter Name="RI" Type="Passive">
<Machines>
<Machine Name="RIVMServer1" VmHost="RIVHost100">
<IPBindings>
<IPBinding VirtualNetworkType="Data" IP="192.168.2.10" />
</IPBindings>
</Machine>
<Machine Name="RIVMServer2" VmHost="RIVHost200">
<IPBindings>
<IPBinding VirtualNetworkType="Data" IP="192.168.2.20" />
</IPBindings>
</Machine>
</DataCenter>
</DataCenters>
</Topology>
</TopologyDefinition>
I need to capture for all DC's the following:
Machine Name
VmHost
IP
I've tried XPATH, I've tried iterating through each node as well with no luck
ServerInfoClass serverInfo = new ServerInfoClass();
XmlDocument doc = new XmlDocument();
doc.Load(FilePath);
XmlNodeList dcElemList = doc.GetElementsByTagName("DataCenter");
for(int j = 0; j < dcElemList.Count; j++)
{
XmlNodeList elemList = doc.GetElementsByTagName("Machine");
for (int i = 0; i < elemList.Count; i++)
{
serverInfo.ServerName = elemList[i].Attributes["Name"].Value;
serverInfo.VmHost = elemList[i].Attributes["VmHost"].Value;
XmlNodeList ipList = doc.GetElementsByTagName("IPBindings");
for (int x = 0; x < ipList.Count; x++) ;
{
//serverInfo.IPAddress = ipList[x].Attributes["IP"].Value;
}
OutPut(serverInfo.ServerName, serverInfo.VmHost, serverInfo.IPAddress);
}
}
If you put that into an XDocument, you can query it like this:
XDocument document = // ... your document.
var ns = document.Root.Name.Namespace;
var results = from dcNode in document.Descendants(ns + "DataCenter")
let Name = dcNode.Attribute("Name").Value
let Type = dcNode.Attribute("Type").Value
let Machines = dcNode.Descendants(ns + "Machine").Select(mNode =>
new {
Name = mNode.Attribute("Name").Value,
VmHost = mNode.Attribute("VmHost").Value,
Bindings = mNode.Descendants(ns + "IPBinding").Attributes("IP").Select(x => x.Value).ToArray()
})
select new { Name, Type, Machines };
I'd recommend making a helper extension method for getting attribute values, which also checks for nulls.
One of the keys here is to remember that your XML elements live in a namespace, and you need to supply the full namespace when querying. That is why it is handy here to extract the ns namespace instance from the root node at first.
Your example isn't well-formed. You're missing the </Machines> tags.
Another way to get do it is using LINQ to XML:
XNamespace ns = "http://schemas.microsoft.com/online/managementsystems/topologydefinition/2009/11";
foreach (var machine in XElement.Load(#"c:\mydata.xml").Descendants(ns + "Machine"))
{
string name = machine.Attribute("Name").Value;
string vmHost = machine.Attribute("VmHost").Value;
XElement ipBinding = machine.Descendants(ns + "IPBinding").Single();
string vnType = ipBinding.Attribute("VirtualNetworkType").Value;
string ip = ipBinding.Attribute("IP").Value;
}

Categories