Unable to get list from xml using xPathNavigator - c#

List<string> list = new List<string>();
foreach (XPathNavigator node in nav.Select("configuration/company/work/worktime"))
{
string day = getAttribute(node, "day");
string time = getAttribute(node, "time");
string worktype = ?? // how to get worktype attribute valuefrom parent node
list.Add(day,time,worktype); // add to list
}
</configuration>
<company>
<work worktype="homeWork">
<worktime day="30" time="10:28"></worktime>
<worktime day="25" time="10:50"></worktime>
</work>
<work worktype="officeWork">
<worktime day="12" time="09:28"></worktime>
<worktime day="15" time="12:28"></worktime>
</work>
</company>
</configuration>
need output as :
list[0] = homeWork,30,10:28
list[1] = homeWork,25,10:50
list[2] = officeWork,12,09:28
list[3] = officeWork,15,12:28
I am trying to get the list from XML but failed to get output like given above (using xpath navigator, how can I access parent node to get worktype attribute, and other remaining inner node attribute?

I'd suggest using LINQ to XML over XPath, but if you must use XPathNavigator then you need to iterate each work element followed by each of its worktime child elements. This way you can use the worktype from the parent context:
foreach (XPathNavigator work in nav.Select("configuration/company/work"))
{
var workType = work.GetAttribute("worktype", string.Empty);
foreach (XPathNavigator worktime in work.Select("worktime"))
{
var day = worktime.GetAttribute("day", string.Empty);
var time = worktime.GetAttribute("time", string.Empty);
list.Add($"{workType}, {day}, {time}");
}
}
See this fiddle for a working demo.

Use a nested loop. Initially retrieve the work nodes with configuration/company/work. Retrieve the worktype attribute and store in a variable. Then loop through the child worktype nodes and add a string to the list for each one

Use Net Library enhanced xml (linq xml)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("work").Select(x => new {
worktype = (string)x.Attribute("worktype"),
worktime = x.Elements("worktime").Select(y => new {
day = (int)y.Attribute("day"),
time = (DateTime)y.Attribute("time")
}).ToList()
}).ToList();
}
}
}

Related

I have a xml which is having multiple siteserver element i want to group all the siteserver element to Siteservers parent node

Currently XML is getting generated like this:
<Sites>
<SiteServer>
<ID>4</ID>
<SiteID>4</SiteID>
<ServerName>New server</ServerName>
<IPAddress>1022216522</IPAddress>
<PDPServer>true</PDPServer>
<bFTPS>false</bFTPS>
</SiteServer>
<SiteServer>
<ID>4</ID>
<SiteID>4</SiteID>
<ServerName>New server</ServerName>
<IPAddress>1022216522</IPAddress>
<PDPServer>true</PDPServer>
<bFTPS>false</bFTPS>
</SiteServer>
</Sites>
I need to add a parent node before siteserver name siteservers. group all the siteserver to siteservers.
<Sites>
<SiteServers>
<SiteServer>
<ID>4</ID>
<SiteID>4</SiteID>
<ServerName>New server</ServerName>
<IPAddress>1022216522</IPAddress>
<PDPServer>true</PDPServer>
<bFTPS>false</bFTPS>
</SiteServer>
<SiteServer>
<ID>4</ID>
<SiteID>4</SiteID>
<ServerName>New server</ServerName>
<IPAddress>1022216522</IPAddress>
<PDPServer>true</PDPServer>
<bFTPS>false</bFTPS>
</SiteServer>
</SiteServers>
</Sites>
I have tried adding child node and group by but it did not work.
var xml = XDocument.Load(#"C:\Users\rbarnwal\source\repos\testXML\testXML\ExportXml2.xml");
var result = new XElement(xml.Root.Name, xml.Root.Elements().GroupBy(x => x.Name).Select(x =>
new XElement("Parent", new XAttribute("name", x.Key))));
var result2 = new XElement(xml.Root.Name, xml.Root.Attributes().ToArray(),
xml.Root.Elements().GroupBy(x => x.Element("SiteServer").Value.Trim()).Select(x =>
new XElement("SiteServers"))
);
Hope this will help. From my XML journies I am used to build xml files from inside out.
var doc = new XDocument();
var root = new XElement("Sites"); // Sites node
var siteServers = new XElement("SiteServers"); // SiteServers node
List<XElement> siteServerList = new List<XElement>(); // list of SiteServer nodes
//I am not sure, where you are getting ID, siteID etc., so I will pretend like you are adding them by foreach, like from list, or something.
foreach( var site in sites)
{
XElement siteServer = new XElement("SiteServer");
siteServer.Add(new XElement("ID", site.ID));
siteServer.Add(new XElement("SiteID", site.siteID));
// etc...
siteServerList.Add(siteServer);
}
foreach(var siteServer in siteServerList)
{
siteServers.Add(siteServer);
}
root.Add(siteServers);
doc.Add(root);
doc.Save("YourPath");
Not simple. Try code below :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement sites = doc.Descendants("Sites").FirstOrDefault();
var groups = doc.Descendants("SiteServer").GroupBy(x => (int)x.Element("ID")).ToList();
XElement newSites = new XElement("Sites");
foreach(var group in groups)
{
XElement newSiteServers = new XElement("SiteServers", group);
newSites.Add(newSiteServers);
}
sites.ReplaceWith(newSites);
}
}
}

Selecting the value of XML elements

I don't have much experience with XML files but I'm trying to append a tutorial I found online to suite my needs and I'm not getting the results I would expect.
https://support.microsoft.com/en-us/help/307548/how-to-read-xml-from-a-file-by-using-visual-c
I've searched around but everything I've found doesn't make sense to me.
My XML looks like this for the most part:
<US>
<!-- Kentucky Start -->
<State>Kentucky KY
<City>Newport
<Street>Pavilion Parkway<Number>130<PostalCode>41071</PostalCode></Number></Street>
</City>
<City>Corbin
<Street>Highway 90<Number>7351<PostalCode>40701</PostalCode></Number></Street>
</City>
</State>
</US>
I'm trying to populate a listbox with the value of each state but my code either returns white space or just the text within the XML tag
e.g.
State..
State..
repeated for each element.
while (reader.Read()) {
switch (reader.NodeType) {
case XmlNodeType.Element: // The node is an element.
// Skip over root element
if (reader.Name.Equals("US")) {
reader.MoveToNextAttribute();
}
else {
if(reader.Name.Equals("State")) {
lbState.Items.Add(reader.Name);
lbState.Items.Add(reader.Value);
}
}
break;
reader.Name returns "State"
reader.Value returns "Whitespace"
I don't understand why reader.Value does not return Kentucky KY...
I've seen other examples that use string builder, is this a bad approach?
Use reader.ReadString() instead of reader.Value
Try xml linq :
sing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("State").Select(x => new {
cities = x.Elements("City").Select(y => new {
state = (string)x,
city = (string)y,
streets = y.Elements("Street").Select(z => (string)z).ToList()
}).ToList()
}).SelectMany(x => x.cities).ToList();
}
}
}
You can use XmDocument (see: https://msdn.microsoft.com/en-us/library/system.xml.xmldocument(v=vs.110).aspx) and then use an xpath expression to get the right elements from your document:
Also it is better to encapsulate the name of the state (that is, if you own the xml document) like this:
<name>Kentucy KY</name>
So you can do the following:
var items = new List<string>();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("yourxml");
var xmlNodes = xmlDocument.SelectNodes("//State/Name");
foreach (XmlNode node in xmlNodes)
{
items.Add(xmlNode.Value);
}

C# XML child node of similar sibling

I'm trying to modify an XML document that we didn't create initially. Snippit from XML is below:
<DEALS>
<DEAL>
<LOANS>
<LOAN LoanRoleType="SubjectLoan">
<BUYDOWN>
<BUYDOWN_RULE>
<BuydownInformation>0</BuydownInformation>
</BUYDOWN_RULE>
</BUYDOWN>
</LOAN>
<LOAN LoanRoleType="SubjectLoan">
<LOAN_IDENTIFIERS>
<LOAN_IDENTIFIER>
...
</LOAN_IDENTIFIER>
<LOAN_IDENTIFIER>
<SellerLoanIdentifier>1234567890</SellerLoanIdentifier>
</LOAN_IDENTIFIER>
</LOAN_IDENTIFIERS>
</LOAN>
</LOANS>
</DEAL>
<DEAL>
...Same format as above...
</DEAL>
</DEALS>
The first LOAN element of each DEAL will never contain LOAN_IDENTIFIERS. I need to get the SellerLoanIdentifier's InnerText, and then put it into <BuydownInformation> of the first LOAN element. I've tried nested loops and can't seem to get it to differentiate between the two LOAN elements (the second loop isn't even seeing the LOAN elements). I'm thinking it also might have to do with the fact that they both carry the exact same attribute but can't find anything online up to this point to help.
XmlDocument xmlExport = new XmlDocument();
xmlExport.Load(fileDestination);
string loanNumber = "";
XmlNodeList loan_XMLDeals = xmlExport.GetElementsByTagName("DEAL");
Logger.WriteDebug("Found " + loan_XMLDeals.Count + " Deals");
foreach (XmlNode loan_XMLDeal in loan_XMLDeals)
{
XmlNodeList loan_XMLLoans = loan_XMLDeal.SelectNodes("LOAN");
Logger.WriteDebug("Found " + loan_XMLLoans.Count + " Loan categories");
foreach (XmlNode loan_XMLCategory in loan_XMLLoans)
{
if(loan_XMLCategory.SelectSingleNode("SellerLoanIdentifier") != null)
{
loanNumber = loan_XMLCategory.SelectSingleNode("SellerLoanIdentifier").ToString();
Logger.WriteDebug("Got loan number " + loanNumber);
}
}
}
This becomes a lot easier with linq to xml. This means ditching the (old) XmlDocument and replacing it with the friendlier XDocument.
Instead of searching the whole document for the target, you need to start from the context of where you found the SellerLoanIdentifier. You can walk back up to the LOAN element, find its previous sibling, then search that for the BuydownInformation. Because this was all scoped within a single LOANS entry, you can be sure you're targeting the right element.
So...
var doc = XDocument.Load(fileDestination);
//we're going to select a sequence of items that contain 2 values...
//the element we want to change and the value we want to store in it
var changes= doc.Root
.Elements("DEAL")
.Descendants("SellerLoanIdentifier")
//from each SellerLoanIdentifier in DEAL elements
.Select(e => new{
//the node we want to change
//in this case we get the parent LOAN
//element, take the last of the elements
//that precede it in the document
//(e.g. the previous sibling which
//contains the target node)
//and find in it a descendant of type
//BuydownInformation
nodeToChange = e.Ancestors("LOAN")
.Single()
.ElementsBeforeSelf()
.Last()
.Descendants("BuydownInformation")
.Single(),
//the string value of the current element
val = (string)e
});
//then apply the changes back to the document
foreach(var change in changes)
{
change.nodeToChange.Value = change.val;
}
var newXmlString = doc.ToString();
There are assumptions here about the shape of your data that may not hold true, but it should be relatively easy to modify.
Try this simple XML Linq code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<XElement> loans = doc.Descendants("LOANS").ToList();
foreach (XElement loan in loans)
{
string sellerLoanIdentifier = (string)loan.Descendants("SellerLoanIdentifier").FirstOrDefault();
XElement buydownInformation = loan.Descendants("BuydownInformation").FirstOrDefault();
buydownInformation.Value = sellerLoanIdentifier;
}
}
}
}

How to get the name of the child node in xml

My XML looks like this
<Location>
<AChau>
<ACity>
<EHouse/>
<FHouse/>
<GHouse/>
</ACity>
<BCity>
<HHouse/>
<IHouse/>
<JHouse/>
<KHouse/>
</BCity>
</AChau>
</Location>
I find a number of ways, I am here to find the closest answer
Get All node name in xml in silverlight
But it reads all the descendants, I need is from "Location" get "AChau"
From "Location/AChau" get "ACity" "BCity"
From "Location/AChau/ACity" get "EHouse" "FHouse" "GHouse"
How can I read only child node?
Assuming that you have an XElement, you can extract the array of names of its children using the following code:
string[] names = xElem.Elements().Select(e => e.Name.LocalName).ToArray();
For example, this code with your XML:
public static MyXExtensions
{
public static string[] ChildrenNames(this XElement xElem)
{
return xElem.Elements().Select(e => e.Name.LocalName).ToArray();
}
}
string[] names1 = xDoc.Root.ChildrenNames();
string[] names2 = xDoc.Root.Element("AChau").ChildrenNames();
string[] names3 = xDoc.XPathSelectElement("Location/AChau/ACity").ChildrenNames();
will return the following arrays respectively:
["AChau"]
["ACity", "BCity"]
["EHouse", "FHouse", "GHouse"]
If you're using XElement to get your data from xml - then all you need is FirstNode property and Elements method.
FirstNode returns first child node of element and Elements returns all direct child nodes of element.
This works if you always want the first node name under the root:
string xml = #"<Location>
<AChau>
<ACity>
<EHouse/>
<FHouse/>
<GHouse/>
</ACity>
<BCity>
<HHouse/>
<IHouse/>
<JHouse/>
<KHouse/>
</BCity>
</AChau>
</Location>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode root = doc.DocumentElement;
XmlNode first = root.FirstChild;
Try this xml linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string xml =
"<Location>" +
"<AChau>" +
"<ACity>" +
"<EHouse/>" +
"<FHouse/>" +
"<GHouse/>" +
"</ACity>" +
"<BCity>" +
"<HHouse/>" +
"<IHouse/>" +
"<JHouse/>" +
"<KHouse/>" +
"</BCity>" +
"</AChau>" +
"</Location>";
XElement location = XElement.Parse(xml);
var results = location.Descendants("AChau").Elements().Select(x => new
{
city = x.Name.LocalName,
houses = string.Join(",",x.Elements().Select(y => y.Name.LocalName).ToArray())
}).ToList();
}
}
}
​

How to get an element that has : in its name?

I need to get the CountryName from this XML: http://api.hostip.info/?ip=12.215.42.19
The response XML is:
<HostipLookupResultSet version="1.0.1"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.hostip.info/api/hostip-1.0.1.xsd">
<gml:description>This is the Hostip Lookup
Service</gml:description>
<gml:name>hostip</gml:name>
<gml:boundedBy>
<gml:Null>inapplicable</gml:Null>
</gml:boundedBy>
<gml:featureMember>
<Hostip>
<ip>12.215.42.19</ip>
<gml:name>Sugar Grove, IL</gml:name>
<countryName>UNITED STATES</countryName>
<countryAbbrev>US</countryAbbrev>
<!-- Co-ordinates are available as lng,lat -->
<ipLocation>
<gml:pointProperty>
<gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:coordinates>-88.4588,41.7696</gml:coordinates>
</gml:Point>
</gml:pointProperty>
</ipLocation>
</Hostip>
</gml:featureMember>
</HostipLookupResultSet>
Problem is I can't include : in the Descendants method because it throws:
XmlException: The ':' chracater,
hexadecimal value 0x3A, cannot be
included in a name.
Thanks
try this
var descendants = from i in XDocument.Load(xml).Descendants("Hostip")
select i.Element("countryName");
Update
complete code for downloading the xml and finding the name of countryName
string xml;
using(var web = new WebClient())
{
xml = web.DownloadString("http://api.hostip.info/?ip=12.215.42.19");
}
var descendants = from i in XDocument.Parse(xml).Descendants("Hostip")
select i.Element("countryName");
A small example on how to apply namespaces in LINQ to XML:
XElement doc = XElement.Load("test.xml");
XNamespace ns = "http://www.opengis.net/gml";
var firstName = doc.Descendants(ns + "name").First().Value;
You need to reference the gml namespace; once you've done that you should be able to navigate using the tag names that appear to the right of "gml:"
UPDATE
I'm not sure what context you're applying this to, but here's a sample console app that works:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace LinqToXmlSample
{
class Program
{
static void Main(string[] args)
{
XElement x = XElement.Load("http://api.hostip.info/?ip=12.215.42.19");
foreach (XElement hostip in x.Descendants("Hostip"))
{
string country = Convert.ToString(hostip.Element("countryName").Value);
Console.WriteLine(country);
}
Console.ReadLine();
}
}
}
var gml = (XNamespace)"http://www.opengis.net/gml";
var doc = XDocument.Load(...) or XDocument.Parse(...);
var name = doc.Descendants(gml + "featureMember").Descendants("countryName").First().Value;
Or you could go brute force and strip all the namespaces:
void RemoveNamespace(XDocument xdoc)
{
foreach (XElement e in xdoc.Root.DescendantsAndSelf())
{
if (e.Name.Namespace != XNamespace.None)
{
e.Name = XNamespace.None.GetName(e.Name.LocalName);
}
if (e.Attributes().Any(a => a.IsNamespaceDeclaration || a.Name.Namespace != XNamespace.None))
{
e.ReplaceAttributes(e.Attributes().Select(a => a.IsNamespaceDeclaration ? null : a.Name.Namespace != XNamespace.None ? new XAttribute(XNamespace.None.GetName(a.Name.LocalName), a.Value) : a));
}
}
}

Categories

Resources