Count ChildElements of the same name, inside an XML Element, with XDocument - c#

I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?

You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();

You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.

How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;

If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});

Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();

Related

c# extract value from nextnode

I have the following xml part and am trying to extract the value where key is known. The example below is a snippet, from a larger xml that contains 1000's of nodes.
<?xml version="1.0" encoding="utf-8"?>
<DictionarySerializer>
<item>
<key>key1</key>
<value>CONTENT1</value>
</item>
<item>
<key>key2</key>
<value>CONTENT2</value>
</item>
</DictionarySerializer>
i assume the above is a string called xml,
then with
XDocument.Parse(xml)
.Descendants("key")
.Where(x => (string)x.Value == "key1")
.FirstOrDefault().NextNode.ToString()
I can get the string <value>CONTENT1</value> But i simply cannot get my head around how to get the value of the value node to to say.
I am afrad it is super simple, and i just are stuck in a coffein loop :-)
XDocument.Parse(xml)
.Descendants("key")
.Where(x => (string)x.Value == "key1")
.FirstOrDefault().Value.ToString()
you should use .Value property instead of .NextNode
If you want to get all keys and values from the XML from all 1000 elemnts. You can use:
Dictionary<string, string> elements = new Dictionary<string, string>();
xml.Root.Elements().ToList().ForEach(xmlElement =>
{
elements.Add(xmlElement.Descendants("key").First().Value,
xmlElement.Descendants("value").First().Value);
});
So, the elements dictionary will contain all of your 1000 nodes.
Try to cast NextNode to XElement and get Value from it.
Considering you can use XPath expressions.
expression = #"//Item[Key='1']/Value"
XmlNodeList nodeList = xmlDocument.SelectNodes(expression);
This would give you the value node(s) of items with Key=1. Just find the value of the desired node.
I believe using XDocument you can also try,
string output = xDocument.XPathEvaluate(expression);

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

XPath and attributes

I'm trying to traverse an XML document and select certain node attributes. The XML is dynamically generated.
<?xml version="1.0" encoding="ISO-8859-1"?>
<streams>
<stream>
<title>+23 (Panama)</title>
<info resolution="768x420" bitrate="1000kbps"/> ----- Need These
<swfUrl>http://www.freeetv.com/script/mediaplayer/player.swf</swfUrl>
<link>rtmp://200.75.216.156/live/</link>
<pageUrl>http://www.freeetv.com/</pageUrl>
<playpath>livestream</playpath>
<language>Music</language>
<advanced></advanced>
</stream>
</streams>
The code that I'm trying to use with zero luck and Visual Studio saying "No you're wrong. Try 600 more times" is
xDoc.Load("http://127.0.0.1/www/xml.php");
XmlNodeList nodes = xDoc.SelectNodes("/streams/stream");
foreach (XmlNode xn in nodes)
{
ListViewItem lvi = listView1.Items.Add(xn["title"].InnerText);
lvi.SubItems.Add(xn["swfUrl"].InnerText);
lvi.SubItems.Add(xn["link"].InnerText);
lvi.SubItems.Add(xn["pageUrl"].InnerText);
lvi.SubItems.Add(xn["playpath"].InnerText);
lvi.SubItems.Add(xn["language"].InnerText);
lvi.SubItems.Add(xn["advanced"].InnerText);
lvi.SubItems.Add(xn["//info/#resolution"].Value);
}
Please tell me oh wise ones what am I doing wrong?
If you want to select node's attribute using XPath you should use SelectSingleNode method, e.g.:
xn.SelectSingleNode("info/#resolution").Value
To select resolution attribute of your last node you need to use:
xn["info"].Attributes["resolution"].Value
Alternatively, you can try LINQ to XML for the same results (I find its API easier to use):
var doc = XDocument.Parse("http://127.0.0.1/www/xml.php");
foreach (var d in doc.Descendants("stream"))
{
ListViewItem lvi = listView1.Items.Add(d.Element("title").Value);
lvi.SubItems.Add(d.Element("swfUrl").Value);
// ...
vi.SubItems.Add(d.Element("info").Attribute("resolution").Value);
}
Here is an example of LINQ to XML to extract attributes from the entire document of a particular attribute name OR list of attribute names.
var xml = XElement.Parse("http://127.0.0.1/www/xml.php");
// find all attributes of a given name
var attributes = xml
.Descendants()
.Attributes("AttributeName")
// find all attributes of multiple names
var attributes = xml
.Descendants()
.Attributes()
.Where(a => ListOfAttribNames.Contains(a.Name.LocalName))
Replace:
lvi.SubItems.Add(xn["//info/#resolution"].Value);
with:
lvi.SubItems.Add(xn.SelectSingleNode("info/#resolution").Value);

XML How to select Child Elements using XPath

I've got the following XML, shown in the following image:
But I can't for the life of me, get any code to select the house element between <ArrayOfHouse>.
There will be more than one House element once I've managed to get it to select one, here's my code so far:
// Parse the data as an XML document
XDocument xmlHouseResults = XDocument.Parse(houseSearchResult);
// Select the House elements
XPathNavigator houseNavigator = xmlHouseResults.CreateNavigator();
XPathNodeIterator nodeIter = houseNavigator.Select("/ArrayOfHouse/House");
// Loop through the selected nodes
while (nodeIter.MoveNext())
{
// Show the House id, as taken from the XML document
MessageBox.Show(nodeIter.Current.SelectSingleNode("house_id").ToString());
}
I'm getting the stream of XML, because I have managed to show the data in the MessageBox shown above, but I can't get to the individual houses.
You can select the House nodes like this:
var houses = XDocument.Parse(houseSearchResult).Descendants("House");
foreach(var house in houses)
{
var id = house.Element("house_id");
var location = house.Element("location");
}
Or you can use Select to directly get a strongly typed object:
var houses = XDocument.Parse(houseSearchResult)
.Descendants("House")
.Select(x => new House
{
Id = x.Element("house_id"),
Location = x.Element("location")
});
This assumes that there exists a class House with the properties Id and Location.
Also, please be sure to think about the suggestion by Thomas Levesque to use XML serialization.
With XPath you would need to use an XmlNamespaceManager, however as you have an XDocument you could simply use the LINQ to XML axis methods e.g.
XNamespace df = XmlHouseResults.Root.Name.Namespace;
foreach (XElement house in XmlHouseResults.Descendants("df" + "House"))
{
MessageBox.Show((string)house.Element("df" + "house_id"));
}

Noob LINQ - reading, filtering XML with XDocument

I'm just learning XDocument and LINQ queries. Here's some simple XML (which doesn't look formatted exactly right in this forum in my browser, but you get the idea . . .)
<?xml version="1.0" encoding="utf-8"?>
<quiz
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.com/name XMLFile2.xsd"
title="MyQuiz1">
<q_a>
<q_a_num>1</q_a_num>
<q_>Here is question 1</q_>
<_a>Here is the answer to 1</_a>
</q_a>
<q_a>
<q_a_num>2</q_a_num>
<q_>Here is question 2</q_>
<_a>Here is the answer to 2</_a>
</q_a>
</quiz>
I can iterate across all elements in my XML file and display their Name, Value, and NodeType in a ListBox like this, no problem:
XDocument doc = XDocument.Load(sPath);
IEnumerable<XElement> elems = doc.Descendants();
IEnumerable<XElement> elem_list = from elem in elems
select elem;
foreach (XElement element in elem_list)
{
String str0 = "Name = " + element.Name.ToString() +
", Value = " + element.Value.ToString() +
", Nodetype = " + element.NodeType.ToString();
System.Windows.Controls.Label strLabel = new System.Windows.Controls.Label();
strLabel.Content = str0;
listBox1.Items.Add(strLabel);
}
...but now I want to add a "where" clause to my query so that I only select elements with a certain name (e.g., "qa") but my element list comes up empty. I tried . . .
IEnumerable<XElement> elem_list = from elem in elems
where elem.Name.ToString() == "qa"
select elem;
Could someone please explain what I'm doing wrong? (and in general are there some good tips for debugging Queries?) Thanks in advance!
The problem is that the Name property is not a string, it's an XName. When you ToString it, you get a lot more than you think.
While it's possible to write the query in the way you're attempting to, also consider these possibilites:
//from nodes immediately below this one
IEnumerable<XElement> elem_list = doc.Elements("qa");
//from nodes of all levels below this node.
IEnumerable<XElement> elem_list = doc.Descendants("qa");
I would perhaps change your query to something that looks more like this
var query = from q_a in document.Descendants("q_a")
select new
{
Number = (int)q_a.Element("q_a_num"),
Question = (string)q_a.Element("q_"),
Answer = (string)q_a.Element("_a")
};
With this, you'll pull from each of your q_a descendants the inner elements into an IEnumerable<[Anonymous Type]>, each object containing the number, question, and answer.
However, if you just want to extract the XElements where the name is q_a, you could do this using a where clause.
IEnumerable<XElement> elem_list = elems.Where(elem => elem.Name.LocalName == "q_a");
Of course, as David B showed, the where clause is not necessary here.
IEnumerable<XElement> elem_list = elems.Elements("q_a");

Categories