Reading Sub-Elements (child nodes) with XMLreader using C# - c#

First: This is NOT a duplicate of Reading Child Nodes with XMLReader
(Not the same language, Couldn't get it to help me).
I'm pretty new to XMLreading, I am trying to reach the Sub Element of a specific Element but I am having hard time reaching it, here is an example:
The XML element:
<Group ExerciseNumber="0" Name="R33-IOS1_IOS1" ExerciseName="Unallocated" Status="offline" StatusStatistics="0/1">
<Clients>
<Client ClientName="R33-IOS1_IOS1" MachineName="R33-IOS1" ClientType="HC0" ClientStatus="Disconnected" />
</Clients>
<GroupAppendedData GroupID="201" Type="IOS" DomeType="None" ConnectedTo="" ForceType="Enemy" />
</Group>
I am trying to reach the "Client" element from the specific "Group" element, This is my C# code:
while (reader.Read())
{
if (reader.Name.Equals("Group"))
{
name = reader.GetAttribute("Name");
// Now I need to reach the specific "MachineName" attribute of the "Client" sub-element but don't know how.
}
}
reader.Close();
Notes:
It's important that the client element reading will be in the same loop iteration (if possible, if not I will have to think of another design for my generating class).
*Editing the XML is not an option.
Thank you.

LINQ to XML would be easier to use than XmlReader, this will give you machine names for all Clients in the document:
var machineNames = XElement.Parse("data.xml")
.Descendants("Client")
.Select(client => client.Attribute("MachineName").Value);
Edit - this returns both names in every iteration:
var query = XElement.Load("data.xml")
.Descendants("Client")
.Select(client => new {
MachineName = client.Attribute("MachineName").Value,
GroupName = client.Ancestors("Group").Select(g => g.Attribute("Name").Value).First()
});
foreach (var x in query)
Console.WriteLine($"GroupName: {x.GroupName}, MachineName: {x.MachineName}");

As suggested, unless you have a very good reason to use XmlReader then using a higher level API such as LINQ to XML would be preferred.
Here's a solution using query syntax:
var clients = from #group in doc.Descendants("Group")
let groupName = (string) #group.Attribute("Name")
from client in #group.Descendants("Client")
select new
{
GroupName = groupName,
ClientName = (string) client.Attribute("ClientName"),
MachineName = (string) client.Attribute("MachineName"),
};
See this fiddle for a working example.

try ReadFrom() method in xml linq
while (reader.Read())
{
if (reader.Name.Equals("Group"))
{
XElement group = (XElement)XDocument.ReadFrom(reader);
}
}
reader.Close();​

Related

Get certain xml node and save the value

Considering the following XML:
<Stations>
<Station>
<Code>HT</Code>
<Type>123</Type>
<Names>
<Short>H'bosch</Short>
<Middle>Den Bosch</Middle>
<Long>'s-Hertogenbosch</Long>
</Names>
<Country>NL</Country>
</Station>
</Stations>
There are multiple nodes. I need the value of each node.
I've got the XML from a webpage (http://webservices.ns.nl/ns-api-stations-v2)
Login (--) Pass (--)
Currently i take the XML as a string and parse it to a XDocument.
var xml = XDocument.Parse(xmlString);
foreach (var e in xml.Elements("Long"))
{
var stationName = e.ToString();
}
You can retrieve "Station" nodes using XPath, then get each subsequent child node using more XPath. This example isn't using Linq, which it looks like you possibly are trying to do from your question, but here it is:
XmlDocument xml = new XmlDocument();
xml.Load(xmlStream);
XmlNodeList stations = xml.SelectNodes("//Station");
foreach (XmlNode station in stations)
{
var code = station.SelectSingleNode("Code").InnerXml;
var type = station.SelectSingleNode("Type").InnerXml;
var longName = station.SelectSingleNode("Names/Long").InnerXml;
var blah = "you should get the point by now";
}
NOTE: If your xmlStream variable is a String, rather than a Stream, use xml.LoadXml(xmlStream); for line 2, instead of xml.Load(xmlStream). If this is the case, I would also encourage you to name your variable to be more accurately descriptive of the object you're working with (aka. xmlString).
This will give you all the values of "Long" for every Station element.
var xml = XDocument.Parse(xmlStream);
var longStationNames = xml.Elements("Long").Select(e => e.Value);

LINQ and XmlNodes elements

I am trying to return the the attribute values from this XML, which is a collection of XmlNodes called from a Sharepoint Webmethod.
XML Data
<Lists xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Announcements/AllItems.aspx" MobileDefaultViewUrl="" ID="{E6172717-EB95-4845-B8CB-8161832565C6}" Title="Announcements" Description="Use the Announcements list to post messages on the home page of your site." ImageUrl="/_layouts/images/itann.gif" Name="{E6172717-EB95-4845-B8CB-8161832565C6}" BaseType="0" FeatureId="00bfea71-d1ce-42de-9c63-a44004ce0104" />
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Calendar/calendar.aspx" MobileDefaultViewUrl="" ID="{C0735477-BE48-4DDF-9D93-3E1F8E993CEC}" Title="Calendar" Description="Use the Calendar list to keep informed of upcoming meetings, deadlines, and other important events." ImageUrl="/_layouts/images/itevent.gif" Name="{C0735477-BE48-4DDF-9D93-3E1F8E993CEC}" BaseType="0" FeatureId="00bfea71-ec85-4903-972d-ebe475780106" />
///... Several more like this
</Lists>
I have been following a few different guides, just been going through like this one on DiC, and I've managed to get the examples to work.
public List<Dictionary<string, XmlAttribute>> GetListData(XmlNode collection)
{
#region Test
string nodeInput = Convert.ToString(collection.OuterXml);
TextReader sr = new StringReader(nodeInput);
//from <List> node, decendant of <Lists>
var lists = (from list in XElement.Load(sr).Descendants("List")
//where the baseType element value equals 0
where int.Parse(list.Element("BaseType").Value) == 0
//Output the titles values to a list
select list.Element("Title").Value).ToList();
}
#endregion
I've been trying to adapt a few of the examples to my data to get more of an idea how it works, but this query has not returned any results unlike I expected. I've written besides each line in a comment what I thought the command was doing, could someone illuminate my mistake?
Solution
Very easy to find once I knew namespace was the issue.
http://msdn.microsoft.com/en-us/library/bb669152.aspx
C# unlike VB requires the namespace even when the nodes aren't prefixed by it.
So I needed an XNamespace
XNamespace nameSpace = "http://schemas.microsoft.com/sharepoint/soap/";
XElement node = XElement.Parse(nodeInput);
var lists = from list in node.Descendants(nameSpace + "List")
select list;
foreach (var list in lists)
{
var doc = list.Document;
}
Your code should be
XNamespace ns = "http://schemas.microsoft.com/sharepoint/soap/";
var lists = (from list in XElement.Parse(nodeInput).Descendants(ns + "List")
where (int)list.Attribute(ns + "BaseType") == 0
select (string)list.Attribute(ns + "Title")).ToList();
Solution
Very easy to find once I knew namespace was the issue.
http://msdn.microsoft.com/en-us/library/bb669152.aspx
C# unlike VB requires the namespace even when the nodes aren't prefixed by it.
So I needed an XNamespace
XNamespace nameSpace = "http://schemas.microsoft.com/sharepoint/soap/";
XElement node = XElement.Parse(nodeInput);
var lists = from list in node.Descendants(nameSpace + "List")
select list;
foreach (var list in lists)
{
var doc = list.Document;
}

LINQ: How to return all child elements?

For an application I am working on, I have to display data from an XML File. There's a few transformations being done, but eventually the end result will be displayed in a treeview. When a user then clicks on a node, I want to pop up the details in a listview.
When no node has been selected, I basically use LINQ to grab the details of the first item I encounter.
Here's a simplified version of my XML
<root>
<parent label="parent1">
<child label="child1">
<element1>data</element1>
<element2>data</element2>
...
</child>
<child label="child2">
<element1>data</element1>
<element2>data</element2>
...
</child>
</parent>
</root>
And here's the code used to grab it (After selecting the parent-node that the treeview has been set to by means of an XPAthSelectStatement):
protected void listsSource_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
XElement rootElement = XElement.Load(MapPath(TreeSource.DataFile));
rootElement = rootElement.XPathSelectElement("//parent[#label='parent1']");
XElement parentElement;
parentElement = rootElement;
var query = (from itemElement in parentElement.Descendants("child")
select new
{
varElement1 = itemElement.Element("element1").Value,
varElement2 = itemElement.Element("element2").Value,
...
}).Take(1);
e.result = Query;
}
This works a treat, and I can read out the varElement1 and varElement2 values from there. However, when I try and implement a similar mechanism for when the user actually did select a node, I seem to run into a wall.
My approach was to use another XPatchSelectStatement to get to the actual node:
parentElement = rootElement.XPathSelectElement("//child[#label='" + tvwChildren.SelectedNode.Text + "']");
But I am kind of stumped on how to now get a proper LINQ query built up to read in all elements nested under the child node. I tried using parentElement.Elements(), but that was yielding an error. I also looked at using Nodes(), but with similar results.
I suppose I could use a foreach loop to access the nodes, but then I'm not sure how to get the results into a LINQ query so I can return the same e.Result = query back.
I'm fairly new to LINQ, as you might have guessed, so any hints would be very much appreciated.
Here's the query that will give you the child element (given that there is only one child element with the specified label):
var childElement = rootNode.Descendants("child")
.Single(e=>e.Attribute("label").Value == "child1");
If you have more than one child elements with label="child1" but those elements are under different parent elements you can use the same approach to get first the parent element and then the child element.
Having the above, you can use this query to get all element nodes under the child node:
var elements = childElement.Descendants().Select(e=>e.Value);
I think data binding is much easier in this case.
XDocument doc = XDocument.Load(filePath);
if (doc.Root == null)
{
throw new ApplicationException("invalid data");
}
tvwChildren.Source=doc;
But if you want in this way hope following one helps(not the exact solution)
XElement root = XElement.Load("Employees.xml");
TreeNode rootNode = new TreeNode(root.Name.LocalName);
treeView1.Nodes.Add(rootNode);
foreach(XElement employee in root.Elements())
{
TreeNode employeeNode = new TreeNode("Employee ID :" + employee.Attribute("employeeid").Value);
rootNode.Nodes.Add(employeeNode);
if (employee.HasElements)
{
foreach(XElement employeechild in employee.Descendants())
{
TreeNode childNode = new TreeNode(employeechild.Value);
employeeNode.Nodes.Add(childNode);
}
}
}
And you can try Resharper tool for create better linq statements. It shows possible ones and you can easily convert each for,foreach loops into linq statements.
I'm not entirely sure I understand what you're trying to do, but it sounds like it could be this:
var data =
from p in xml.Root.Elements("parent")
where p.Attribute("label").Value == "parent1"
from c in p.Elements("child")
where c.Attribute("label").Value == "child2"
from d in c.Elements()
select d.Value;
Let me know if that helps.
Using this Xml library you can write your XPath like:
XElement child = rootElement.XPathElement(
"//parent[#label={0}]/child[#label={1}]", "parent1", "child2");

Best way to query XDocument with LINQ?

I have an XML document that contains a series of item nodes that look like this:
<data>
<item>
<label>XYZ</label>
<description>lorem ipsum</description>
<parameter type="id">123</parameter>
<parameter type="name">Adam Savage</parameter>
<parameter type="zip">90210</parameter>
</item>
</data>
and I want to LINQ it into an anonymous type like this:
var mydata =
(from root in document.Root.Elements("item")
select new {
label = (string)root.Element("label"),
description = (string)root.Element("description"),
id = ...,
name = ...,
zip = ...
});
What's the best way to pull each parameter type according to the value of its 'type' attribute? Since there are many parameter elements you wind up with root.Elements("parameter") which is a collection. The best way I can think to do it is like this by method below but I feel like there must be a better way?
(from c in root.Descendants("parameter") where (string)c.Attribute("type") == "id"
select c.Value).SingleOrDefault()
I would use the built-in query methods in LINQ to XML instead of XPath. Your query looks fine to me, except that:
If there are multiple items, you'd need to find the descendants of that instead; or just use Element if you're looking for direct descendants of the item
You may want to pull all the values at once and convert them into a dictionary
If you're using different data types for the contents, you might want to cast the element instead of using .Value
You may want to create a method to return the matching XElement for a given type, instead of having several queries.
Personally I don't think I'd even use a query expression for this. For example:
static XElement FindParameter(XElement element, string type)
{
return element.Elements("parameter")
.SingleOrDefault(p => (string) p.Attribute("type") == type);
}
Then:
var mydata = from item in document.Root.Elements("item")
select new {
Label = (string) item.Element("label"),
Description = (string) item.Element("description"),
Id = (int) FindParameter(item, "id"),
Name = (string) FindParameter(item, "name"),
Zip = (string) FindParameter(item, "zip")
};
I suspect you'll find that's neater than any alternative using XPath, assuming I've understood what you're trying to do.
use XPATH - it is very fast ( except xmlreader - but a lot of if's)
using (var stream = new StringReader(xml))
{
XDocument xmlFile = XDocument.Load(stream);
var query = (IEnumerable)xmlFile.XPathEvaluate("/data/item/parameter[#type='id']");
foreach (var x in query.Cast<XElement>())
{
Console.WriteLine( x.Value );
}
}

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

Categories

Resources