.NET search sub-elements in xml - c#

I'm trying to load contents of a XML file into a list of custom types using Linq following the instructions in the official microsoft dotNet api:
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.find?view=netframework-4.8
My xml file looks like this:
<directives>
<dir directive="Question" response="Response"></dir>
<dir directive="Q2" response="Response2"></dir>
<dir directive="Q3" response="Response3"></dir>
</directives>
and the importing code is pretty much the same as in the example linked above.
public static void ImportDirectives()
{
// Create XML elements from a source file.
XElement xTree = XElement.Load(AppDomain.CurrentDomain.BaseDirectory + "directives.xml");
// Create an enumerable collection of the elements.
IEnumerable<XElement> elements = xTree.Elements();
// Evaluate each element and set set values in the book object.
foreach (XElement el in elements)
{
Directive dir = new Directive();
dir.directive = el.Attribute("directive").Value;
IEnumerable<XElement> props = el.Elements();
foreach (XElement p in props)
{
if (p.Name.ToString().ToLower() == "response")
{
dir.response = p.Value;
}
}
Dir.Add(dir);
}
}
The code works fine if I remove the root element but only add the root element into my list if I add one.
I'd prefer having a root element just to make my XML look proper.
How would I access the elements within the root element using this code?

When you add root then xml like
<Root>
<directives>
<dir directive="Question" response="Response"></dir>
<dir directive="Q2" response="Response2"></dir>
<dir directive="Q3" response="Response3"></dir>
</directives>
</Root>
When you fetch first Elements() then
<directives>
<dir directive="Question" response="Response"></dir>
<dir directive="Q2" response="Response2"></dir>
<dir directive="Q3" response="Response3"></dir>
</directives>
Again fetch Elements() then you'll get Node
<dir directive="Question" response="Response"></dir>
Then you access attribute and values
public static void ImportDirectives()
{
// Create XML elements from a source file.
XElement xTree = XElement.Load(AppDomain.CurrentDomain.BaseDirectory + "directives.xml");
// Create an enumerable collection of the elements.
IEnumerable<XElement> elements = xTree.Elements();
// Evaluate each element and set set values in the book object.
foreach (XElement el in elements.Elements())
{
Directive dir = new Directive();
dir.directive = el.Attribute("directive").Value;
IEnumerable<XElement> props = el.Elements();
foreach (XElement p in props)
{
if (p.Name.ToString().ToLower() == "response")
{
dir.response = p.Value;
}
}
Dir.Add(dir);
}
}

If you are sure that your XML Schema is going to be fixed, you can access the attribute values of the XML elements directly.
I have attached a sample code.
public static void ImportDirectives()
{
string fileName = AppDomain.CurrentDomain.BaseDirectory + "directives.xml";
// Create XML elements from a source file.
XElement xTree = XElement.Load(fileName);
// Create an enumerable collection of the elements.
IEnumerable<XElement> elements = xTree.Elements();
// Evaluate each element and set set values in the book object.
foreach (XElement el in elements)
{
string directive = el.Attribute("directive").Value;
string response = el.Attribute("response").Value;
Console.WriteLine(directive + ":" + response);
}
}

Related

XML - where node has same name but different values

I am trying to read an xml file (and later import the data in to a sql data base) which contains employees names address' etc.
The issue I am having is that in the xml the information for the address for the employee the node names are all the same.
<Employee>
<EmployeeDetails>
<Name>
<Ttl>Mr</Ttl>
<Fore>Baxter</Fore>
<Fore>Loki</Fore>
<Sur>Kelly</Sur>
</Name>
<Address>
<Line>Woof Road</Line>
<Line>Woof Lane</Line>
<Line>Woofington</Line>
<Line>London</Line>
</Address>
<BirthDate>1985-09-08</BirthDate>
<Gender>M</Gender>
<PassportNumber>123756rt</PassportNumber>
</EmployeeDetails>
</Employee>
I all other items are fine to extract and I have tried to use Linq to iterate through each "Line" node but it always just gives be the first Line and not the others.
var xAddreesLines = xEmployeeDetails.Descendants("Address").Select(x => new
{
address = (string)x.Element("Line").Value
});
foreach (var item in xAddreesLines)
{
Console.WriteLine(item.address);
}
I need to able to when I'm importing to my sql db that address line is separate variable
eg
var addressline1 = first <line> node
var addressline2 = second <line> node etc etc.
Any advice would be most welcome.
This should give you the expected output:-
var xAddreesLines = xdoc.Descendants("Address")
.Elements("Line")
.Select(x => new { address = (string)x });
You need to simply fetch the Line elements present inside Address node and you can project them. Also note there is no need to call the Value property on node when you use explicit conversion.
You can do it like this:
using System.Xml;
.
.
.
XmlDocument doc = new XmlDocument();
doc.Load("source.xml");
// if you have the xml in a string use doc.LoadXml(stringvar)
XmlNamespaceManager nsmngr = new XmlNamespaceManager(doc.NameTable);
XmlNodeList results = doc.DocumentElement.SelectNodes("child::Employee", nsmngr);
foreach (XmlNode result in results)
{
XmlNode namenode = result.SelectSingleNode("Address");
XmlNodeList types = result.SelectNodes("line");
foreach (XmlNode type in types)
{
Console.WriteLine(type.InnerText);
}
XmlNode fmtaddress = result.SelectSingleNode("formatted_address");
}
Refer to this question for the original source.

linq to xml, read xml file

I am struggling to read this xml file in linq to xml. Can someone help me here.
I need to read each track information.
<playlist version="1" xmlns="http://xspf.org/ns/0/" xmlns:jwplayer="http://developer.longtailvideo.com/trac/wiki/FlashFormats">
<title>Some title here</title>
<creator>Some creater</creator>
<info>somesite.com</info>
<trackList>
<track>
<title>Title 1</title>
<creator>Creater 1</creator>
<location>location 1</location>
</track>
<track>
<title>Title 2</title>
<creator>Creater 2</creator>
<location>location 2</location>
</track>
</trackList>
</playlist>
This is what I am trying to do.
XElement xelement1 = XElement.Load(#"pathtoxmlfile\my.xml");
IEnumerable<XElement> tracks= xelement1.Elements();
// Read the entire XML
foreach (var track in tracks.Descendants("track"))
{
Console.WriteLine(track );
Console.ReadLine();
}
I am using C#.
Thanks
Parminder
Why do you load your document into XElement instead of XDocument?
You have to use XNamespace instance within your query because your document uses default namespace xmlns="http://xspf.org/ns/0/".
var ns = XNamespace.Get("http://xspf.org/ns/0/");
You can use LINQ query to get a collection with your data extracted from XML document. Then you can iterate over that collection and do whatever you need.
var tracks = (from t in xDoc.Root.Element(ns + "trackList").Elements(ns + "track")
select new
{
Title = (string)t.Element(ns + "title"),
Creator = (string)t.Element(ns + "creator"),
Location = (string)t.Element(ns + "location")
}).ToList();
tracks will be a List<T> where T is anonymous type with 3 string properties: Title, Creator and Location.
You forget to include namespace name, do the following changes:
XNamespace defNs = "http://xspf.org/ns/0/";
And
foreach (var track in tracks.Descendants(defNs + "track"))
XElement tracks = XElement.Load(#"pathtoxmlfile\my.xml");
foreach (var track in tracks.Descendants("track"))
{
Console.WriteLine((string)track.Element("title"));
Console.WriteLine((string)track.Element("creator"));
Console.WriteLine((string)track.Element("location"));
}

Reading multiple child nodes of xml file

I have created an Xml file with example contents as follows:
<?xml version="1.0" encoding="utf-8" ?>
<Periods>
<PeriodGroup name="HER">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
</PeriodGroup>
<PeriodGroup name="CAFG">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
<Period>
<PeriodName>Anglo-Saxon</PeriodName>
<StartDate>410</StartDate>
<EndDate>800</EndDate>
</Period>
</PeriodGroup>
</Periods>
I need to be able to read the Period node children within a selected PeriodGroup. I guess the PeriodName could be an attribute of Period if that is more sensible.
I have looked at loads of examples but none seem to be quite right and there seems to be dozens of different methods, some using XmlReader, some XmlTextReader and some not using either. As this is my first time reading an Xml file, I thought I'd ask if anyone could give me a pointer. I've got something working just to try things out, but it feels clunky. I'm using VS2010 and c#. Also, I see a lot of people are using LINQ-Xml, so I'd appreciate the pros and cons of using this method.
string PG = "HER";
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("./Xml/XmlFile.xml"));
string text = string.Empty;
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup");
foreach (XmlNode node in xnl)
{
text = node.Attributes["name"].InnerText;
if (text == PG)
{
XmlNodeList xnl2 = doc.SelectNodes("/Periods/PeriodGroup/Period");
foreach (XmlNode node2 in xnl2)
{
text = text + "<br>" + node2["PeriodName"].InnerText;
text = text + "<br>" + node2["StartDate"].InnerText;
text = text + "<br>" + node2["EndDate"].InnerText;
}
}
Response.Write(text);
}
You could use an XPath approach like so:
XmlNodeList xnl = doc.SelectNodes(string.Format("/Periods/PeriodGroup[#name='{0}']/Period", PG));
Though prefer LINQ to XML for it's readability.
This will return Period node children based on the PeriodGroup name attribute supplied, e.g. HER:
XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));
var nodes = (from n in xml.Descendants("Periods")
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
select n.Element("PeriodGroup").Descendants().Elements()).ToList();
Results:
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
The query is pretty straightforward
from n in xml.Descendants("Periods")
Will return a collection of the descendant elements for the element Periods.
We then use where to filter this collection of nodes based on attribute value:
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
Will then filter down the collection to PeriodGroup elements that have a name attribute with a value of HER
Finally, we select the PeriodGroup element and get it's descendant nodes
select n.Element("PeriodGroup").Descendants().Elements()
EDIT (See comments)
Since the result of this expression is just a query, we use .ToList() to enumerate the collection and return an object containing the values you need. You could also create anonymous types to store the element values for example:
var nodes = (from n in xml.Descendants("Period").
Where(r => r.Parent.Attribute("name").Value == "HER")
select new
{
PeriodName = (string)n.Element("PeriodName").Value,
StartDate = (string)n.Element("StartDate").Value,
EndDate = (string)n.Element("EndDate").Value
}).ToList();
//Crude demonstration of how you can reference each specific element in the result
//I would recommend using a stringbuilder here..
foreach (var n in nodes)
{
text += "<br>" + n.PeriodName;
text += "<br>" + n.StartDate;
text += "<br>" + n.EndDate;
}
This is what the nodes object will look like after the query has run:
Since the XmlDocument.SelectNodes method actually accepts an XPath expression, you're free to go like this:
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup[#name='" + PG + "']/Period");
foreach (XmlNode node in xnl) {
// Every node here is a <Period> child of the relevant <PeriodGroup>.
}
You can learn more on XPath at w3schools.
go thru this
public static void XMLNodeCheck(XmlNode xmlNode)
{
if (xmlNode.HasChildNodes)
{
foreach (XmlNode node in xmlNode)
{
if (node.HasChildNodes)
{
Console.WriteLine(node.Name);
if (node.Attributes.Count!=0)
{
foreach (XmlAttribute att in node.Attributes)
{
Console.WriteLine("----------" + att.Name + "----------" + att.Value);
}
}
XMLNodeCheck(node);//recursive function
}
else
{
if (!node.Equals(XmlNodeType.Element))
{
Console.WriteLine(node.InnerText);
}
}
}
}
}

I can't get value from xml

I have xml
<?xml version="1.0" encoding="UTF-8"?>
<info lang="ru" xmlns:x="http://www.yandex.ru/xscript">
<region id="213" lon="37.617671" lat="55.755768" zoom="10">
<title>Москва</title>
</region>
<traffic lon="37.617671" lat="55.755768" zoom="10"region="213">
<length>489164.0</length>
<level>6</level>
<icon>yellow</icon>
<timestamp>1365162401</timestamp>
<time>15:46</time>
<url>http://maps.yandex.ru/moscow_traffic</url>
<title>Москва</title>
</traffic>
</info>
And I need to get value from "level"
public void GetText(string filename)
{
try
{
XDocument xDocument = LoadPage(filename);
if (xDocument.Root == null) return;
XElement elem = xDocument.Root.Element("info");
if (elem != null)
foreach (var el in elem.Elements("traffic"))
{
Name = el.Element("level").Value;
};
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
This block of code works good with another xml. It can't find "info", and elem=null. What's wrong with this code. Or how can I get this value in other way. Thanks!
This is the problem:
XElement elem = xDocument.Root.Element("info");
In the XML you've given us, xDocument.Root is the info element. Just change that to:
XElement elem = xDocument.Element("info");
and that will check that the root element really is info.
Another alternative would be:
foreach (var el in xDocument.Elements("info")
.Elements("traffic"))
That way you just won't go into the loop body if Elements(info) returns an empty collection.
EDIT: If you need it to work on documents where sometimes info is the root element and sometimes it's not, you might want to use:
foreach (var el in xDocument.Descendants("info")
.Take(1)
.Elements("traffic"))
(It's pretty odd to be in that situation though.)

How to return XML as separate values using XDocument and XElement

I am trying to get values from an XML document using XDocument and XElement. I am trying to get three values, however when I try to return them, they are merged together as one value. Here is the XML I am searching:
<create_maint_traveler>
<Paths>
<outputPath value="D:\Intercim\DNC_Share\itcm\DataInput\MCDHeaderDrop\" />
<outputPath_today value="D:\Intercim\DNC_Share\itcm\DataInput\Today\" />
<log value="D:\Intercim\DNC_Share\itcm\Log\CreateMaintLog.log" />
</Paths>
</create_maint_traveler>
Here is how I am querying the values:
XDocument config = XDocument.Load(XML);
foreach (XElement node in config.Root.Elements("Paths"))
{
if (node.Name == "outputPath") outputPath = node.Value;
if (node.Name == "outputPath_today") outputPath = node.Value;
if (node.Name == "log") outputPath = node.Value;
}
When I output to a file, i find that the returned value is
D:\Intercim\DNC_Share\itcm\DataInput\MCDHeaderDrop\D:\Intercim\DNC_Share\itcm\DataInput\Today\D:\Intercim\DNC_Share\itcm\Log\CreateMaintLog.log
Or there will be nothing returned. I had the values in the XML file outside of the tags before which returned the one long value. I am confused about how to return the outputPath, outputPath_today and log values seperatly. Any help is appreciated.
Try:
var xDoc = XDocument.Load(XML);
var paths = xDoc.Root.Elements("Paths");
var res = from p in paths
select new
{
outputPath = p.Element("outputPath").Attribute("value").Value,
outputPath_today = p.Element("outputPath_today").Attribute("value").Value,
log = p.Element("log").Attribute("value").Value
};
foreach(path in res)
{
System.Console.WriteLine(path.outputPath);
System.Console.WriteLine(path.outputPath_today);
System.Console.WriteLine(path.log);
// or do anything you want to do with those properties
}
You will get values of outputPath, outputPath_today and log into an IEnumerable of anonymous objects. These objects each will have property outputPath, outputPath_today and log with values populated from XML.

Categories

Resources