Retrieving element attributes for multiple nodes - c#

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;
}

Related

Get all value of attribute from each Element from XML

I have an xml file which looks like this:
<HeadercardUnit EndTime="2065-25-45 20:32:44" StartTime="2065-25-45 20:32:23" Rejects="NO" MilliSec="1" Currency="USD" DeclaredDepositAmount="0" denomvalue="1" DepositID="" CustomerID="" HeaderCardID="">
<Counter Number="2" Currency="USD" Output="Stacked" Quality="Accepted" Issue="2006" Value="5" DenomID="" DenomName="5 USD-2006"/>
<Counter Number="31" Currency="USD" Output="Stacked" Quality="Accepted" Issue="2000" Value="1" DenomID="" DenomName="1 USD-2000"/>
<Sum Number="33" Currency="USD" Output="Stacked" Sum="41.00"/>
</HeadercardUnit>
I try to parse it with this code:
string[] content = Directory.GetFiles(Directory.GetCurrentDirectory() + #"\", "*.xml");
XDocument xdoc = XDocument.Load(content[0]);
XElement xml1 = XElement.Load(content[0]);
string xml2 = xml1.ToString();
//Console.WriteLine(xml2);
XElement xml = XElement.Parse(xml2);
var counter = xdoc.Descendants("Counter").Count();
var data = from bps in xdoc.Root.Descendants("Machine")
let Param = bps.Element("ParameterSection")
let Opt = Param?.Element("Operator")
let Hcl = Param?.Element("HeadercardUnit")
let Count = Hcl?.Element("Counter")
select new
{
Type = (string)bps.Attribute("Type"),
SerialNum = (string)bps.Attribute("SerialNumber"),
Startime = (string)Param?.Attribute("StartTime"),
Endtime = (string)Param?.Attribute("EndTime"),
Opt = (string)Opt?.Value,
Number = (string)Count?.Attribute("Number")
};
foreach (var pcl in data)
{
MessageBox.Show(counter.ToString());
for (int i = 0; i < counter; i++)
{
LogService(string.Format("{0},{1},{2},{3},{4},{5}",
pcl.Type, pcl.SerialNum, pcl.Startime, pcl.Endtime, pcl.Opt, pcl.Number));
}
}
The result only give me one line which is looping two time because the counter tag have two elements looks like this:
BPSC1,309322,2065-25-45 20:32:23,2065-25-45 20:32:44,User1,2
BPSC1,309322,2065-25-45 20:32:23,2065-25-45 20:32:44,User1,2
It's a little hard to give a definite answer given you've omitted a portion of the XML that would allow this to be reproduced. However, this line:
let Count = Hcl?.Element("Counter")
Gets the first Counter element. If you want all of them (as you suggest), then you need to iterate through those:
from Count in Hcl.Elements("Counter")
This will then create an object in data for each Counter element.
i added the line inspired by #Charles Mager
from bps in xdoc.Root.Descendants("Machine")
from Countx in bps.Descendants("Counter")
then i can call all attributes

Why is this program not accessing child nodes?

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.

XDocument does not load Xml string properly

I'm trying to do the following: load an Xml string into a XDocument object, but when I try to access elements through Descendants method it return nothing when I tried to see the value of inner elements in Visual Studio it does not recognize it as Xml so what is the problem here?
string xml = #"<ArrayOfKeyValueOfstringint xmlns=""http://schemas.microsoft.com/2003/10/Serialization/Arrays"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
<KeyValueOfstringint>
<Key>crscmprsn_ttlprt1</Key>
<Value>1</Value>
</KeyValueOfstringint>
<KeyValueOfstringint>
<Key>ptntmntrfrm_ttlprt1</Key>
<Value>1</Value>
</KeyValueOfstringint>
</ArrayOfKeyValueOfstringint>";
var xdoc = XDocument.Parse(xml);
IEnumerable<XElement> elements = xdoc.Descendants("KeyValueOfstringint");
var lst = new List<KeyValuePair<string,int>>();
foreach (var item in elements)
{
var k = item.Element("Key").Value;
int v = int.Parse(item.Element("Value").Value);
var kvp = new KeyValuePair<string,int>(k,v);
lst.Add(kvp);
}
You need to specify namespace to get your elements:
var ns = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
var elements = xdoc.Descendants(ns + "KeyValueOfstringint");
For more information about xml namespaces take a look at: Working with XML Namespaces

Parse XML issue

I am parsing a huge XML using for-loops,SelectNodes,Attributes.GetNamedItem etc.
I came across an issue of how to parse the identical nodes CustLoyalty that are identical as shown in the abstract below. The issue is how to get the identical noded values since they are not exclusively inside a parent node
<Customer>
<PersonName>
<NamePrefix>Ms</NamePrefix>
<GivenName>Fra</GivenName>
<Surname>etti</Surname>
</PersonName>
<Telephone FormattedInd="false" PhoneLocationType="6" PhoneNumber="10" PhoneTechType="1"/>
<Telephone FormattedInd="false" PhoneLocationType="6" PhoneNumber="49" PhoneTechType="3"/>
<Email DefaultInd="true" EmailType="1">z#z</Email>
<Address Type="1">
<AddressLine>alace</AddressLine>
<StateProv StateCode="NY"/>
<CountryName Code="GB"/>
</Address>
<CustLoyalty MembershipID="3" ProgramID="Guest"/>
<CustLoyalty MembershipID="6" ProgramID="Freq"/>
<CustLoyalty MembershipID="56" ProgramID="teID"/>
<CustLoyalty MembershipID="N6" ProgramID="ID"/>
</Customer>
My code goes something like that:
XmlNodeList CustomerList = ProfileList[v].SelectNodes("df:Customer", mgr);
for (int w = 0; w < CustomerList.Count; w++)
{
XmlNodeList PersonNameList = CustomerList[w].SelectNodes("df:PersonName", mgr);
for (int x = 0; x < PersonNameList.Count; x++)
{
XmlNode NamePrefixNode = PersonNameList[x].SelectSingleNode("df:NamePrefix", mgr);
string NamePrefix = NamePrefixNode.InnerText;
XmlNode GivenNameNode = PersonNameList[x].SelectSingleNode("df:GivenName", mgr);
string GivenName = GivenNameNode.InnerText;
XmlNode SurnameNode = PersonNameList[x].SelectSingleNode("df:Surname", mgr);
string Surname = SurnameNode.InnerText;
myProfiles.GivenName = GivenName;
myProfiles.Surname = Surname;
myProfiles.NamePrefix = NamePrefix;
}
XmlNode TelephoneNode = CustomerList[w].SelectSingleNode("df:Telephone", mgr);
if (TelephoneNode != null)
{
string PhoneNumber = TelephoneNode.Attributes.GetNamedItem("PhoneNumber").Value;
myProfiles.Telephone = PhoneNumber;
}..........
Let's say that you are parsing it with XDocument object. Beware that XDocument can throw exception if your input isn't valid html and element xCostumer can have null value if element with name "Customer" is not in xDoc on top level in element hierarchy.
XDocument xDoc = XDocument.Parse(YourStringHoldingXmlContent);
XElement xCustomer = xDoc.Element("Customer");
foreach (XElement CustLoayalty in xCustomer.Elements("CustLoyalty"))
{
Console.WriteLine(CustomLoaylty.Value.ToString());
}
you can do the following
1- you define a class CustomLoyalty
public class CustomLoyalty
{
public string Membership{get;set;}
public string Program{get;set;}
}
2- declare a list call it uniqueCustomLoyalty
private List<CustomLoyalty> uniqueCustomLoyalty=new List<CustomLoyalty>();
3- while you are looping on the custom loyalty for each customer do this
foreach(var data in customLoyaltiesList)
{
// customLoyaltiesList is the list of nodes of type custom loyalty
// assume that the current object of customloyalty called detail
CustomLoyalty detail=new CustomLoyalty(){
Membership=data.Attributes.GetNamedItem("MembershipID").Value, // the code to get the value of membership ID according to the method you are using
Program=data.Attributes.GetNamedItem("ProgramID").Value,
};
// check if the list contains the current customloyalty
var exists=uniqueCustomLoyalty.FirstOrDefault(t=>MemberShip=detail.MemberShip && t.Program=detail.Program);
if(exists==null) // list doesn't contain this data
uniqueCustomLoyalty.Add(detail); // add the detail to the list to compare it with the rest of the parsing data
else{
// the data is not unique, you can do what ever you want
}
}
hope this will help you
regards

XMLReader reading XML file based on attribute value

I am trying to read the following file, I can read the attributes, but I can't go into the specific element (Address in this case) and read its elements based on the attribute of that (Address) element. Shortly I need to distinguish between work and home addresses. I need to do this with XMLReader class. Can you help?
<Address Label="Work">
<Name>Name1</Name>
<Street>PO 1</Street>
<City>City1</City>
<State>State 1</State>
</Address>
<Address Label="Home">
<Name>Name2</Name>
<Street>PO 2</Street>
<City>City2</City>
<State>State 2</State>
</Address>"
Okay, here are some notes to think about. XMLReader in the sense i understand you use it (with no code example) is that you iterate over the document, since the XMLReader is forward-only, and read-only.
Because of this you need to iterate until you find the node you need. In the example below i find the address element labeled "work" and extract that entire node. Then query on this node as you want.
using (var inFile = new FileStream(path, FileMode.Open))
{
using (var reader = new XmlTextReader(inFile))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name == "Address" && reader.GetAttribute(0) == "Work")
{
// Create a document, which will contain the address element as the root
var doc = new XmlDocument();
// Create a reader, which only will read the substree <Address> ... until ... </Address>
doc.Load(reader.ReadSubtree());
// Use XPath to query the nodes, here the "Name" node
var name = doc.SelectSingleNode("//Address/Name");
// Print node name and the inner text of the node
Console.WriteLine("Node: {0}, Inner text: {1}", name.Name, name.InnerText);
}
break;
}
}
}
}
Edit
Made an example that not uses LINQ
XML:
<Countries>
<Country name ="ANDORRA">
<state>Andorra (general)</state>
<state>Andorra</state>
</Country>
<Country name ="United Arab Emirates">
<state>Abu Z¸aby</state>
<state>Umm al Qaywayn</state>
</Country>
Java:
public void datass(string file)
{
string file = HttpContext.Current.Server.MapPath("~/App_Data/CS.xml");
XmlDocument doc = new XmlDocument();
if (System.IO.File.Exists(file))
{
//Load the XML File
doc.Load(file);
}
//Get the root element
XmlElement root = doc.DocumentElement;
XmlNodeList subroot = root.SelectNodes("Country");
for (int i = 0; i < subroot.Count; i++)
{
XmlNode elem = subroot.Item(i);
string attrVal = elem.Attributes["name"].Value;
Response.Write(attrVal);
XmlNodeList sub = elem.SelectNodes("state");
for (int j = 0; j < sub.Count; j++)
{
XmlNode elem1 = sub.Item(j);
Response.Write(elem1.InnerText);
}
}
}
Using XPath you can easily write concise expressions to navigate an XML document.
You would do something like
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(myXMLString);
XmlNode homeAddress = xDoc.SelectSingleNode("//Address[#Label='Work']");
Then do whatever you want with homeAddress.
Read more here on w3schools on XPath.

Categories