so I have this code that should allow me to list all the attributes from an element within an element within another element (elementception?) in an xml file. What I'm trying to do is have it where the first list (lst_adventure in my code) loads the first level of elements attributes (the name attributes). Upon selecting one of said elements, list 2 (lst_adventures with an s) loads the next level of elements in the previously selected element. After selecting a second level element, list 3 (lst_senario) loads up the scenario elements from within the previous element that's within the previous element. It's kinda confusing trying to explain. But hwat's happinging right now is it'll load both the first and second elements perfectly but the 3rd list remains empty. Any help would be great.
string selectedItem = lst_Adventure.SelectedItem.ToString();
string selectedAdventure = lst_Adventures.SelectedItem.ToString();
XDocument doc = new XDocument();
doc = XDocument.Load("D:\\WpfApplication1\\WpfApplication1\\Adventures.xml");
XElement selectedElement = doc.Descendants().Where(x => (string)x.Attribute("Name") == selectedItem).FirstOrDefault();
XElement selectedAdventures = selectedElement.Descendants().Where(x => (string)x.Attribute("Name") == selectedItem).FirstOrDefault();
if (selectedAdventures != null)
{
foreach (var docs in selectedAdventures.Elements("senario"))
{
string AdventuresPathName = docs.Attribute("Name").Value;
lst_Adventures.Items.Add(AdventuresPathName);
}
}
The xml file I'm using is this-
<?xml version="1.0" encoding="utf-8" ?>
<adventures>
<adventure_path Name ="Adventure Path 1">
<adventure Name ="Adventure 1">
<senario Name ="Senario 1"/>
<senario Name ="Senario 2"/>
</adventure>
<adventure Name="Addventure 2">
<senario Name ="Senario 3"/>
</adventure>
</adventure_path>
<adventure_path Name ="Adventure Path 2">
<adventure Name ="Adventure 3">
<senario Name ="Senario 4"/>
<senario Name ="Senario 5"/>
</adventure>
</adventure_path>
</adventures>
So the code should add each scenario name attribute to lst_scenario within the selected item in lst_adventure and lst_adventures. So far it doesn't.
Following works just fine:
string selectedItem = "Adventure Path 1";
string selectedAdventure = "Adventure 1";
var doc = XDocument.Load("Input.txt");
var selectedElement = doc.Root
.Elements("adventure_path")
.Where(x => (string)x.Attribute("Name") == selectedItem)
.FirstOrDefault();
var selectedAdventures = selectedElement.Elements("adventure")
.Where(x => (string)x.Attribute("Name") == selectedAdventure)
.FirstOrDefault();
var items = new List<string>();
if (selectedAdventures != null)
{
foreach (var docs in selectedAdventures.Elements("senario"))
{
string AdventuresPathName = docs.Attribute("Name").Value;
items.Add(AdventuresPathName);
}
}
You're comparing Name attribute value to the same selectedItem variable value twice. I've also changed Descendants calls to proper Root/Elements() calls, because you shouldn't use Descendants unless you're data is tree-like. And as far as I can see, yours is not.
You can do the same in single query:
var items = doc.Root
.Elements(selectedItem)
.FirstOrDefault(x => (string)x.Attribute("Name") == selectedItem)
.Elements(selectedAdventure)
.FirstOrDefault(x => (string)x.Attribute("Name") == selectedAdventure)
.Elements("senario")
.Select(x => (string)x.Attribute("Name"))
.ToList();
items will be List<string> with all scenarios under proper XML document part.
Related
I have got XML nodes as below.
...
<ParentNode>
<Node id="2343" name="some name" mode="Some Mode">
//Some child nodes here
</Node>
<Node id="2344" name="some other name" mode="Some Mode">
//Some child nodes here
</Node>
...
</ParentNode>
<ParentNode>
<Node id="2343" name="some name" mode="Some Other Mode">
//Some child nodes here
</Node>
<Node id="2344" name="some other name" mode="Some Mode">
//Some child nodes here
</Node>
</ParentNode>
....
What I need is
id name distinct-mode-count
--------------------------------------------
2343 some name 2
2344 some other name 1
I have tried below to get this.
XElement myXML = XElement.Load(filePath);
IEnumberable<XElement> parentNodes = myXML.Descendants("ParentNode");
var nodeAttributes = parentNodes.Select(le => le.Descendants("Node")
.GroupBy(x => new {
id = x.Attribute("id").Value,
name = x.Attribute("name").Value
}).Select(g => new {
id = g.Key.id,
name = g.Key.name,
distinct_mode_count = // This is where I am stuck
}));
I am not sure how to get distinct_mode_count in the above query.
Edit
I need distinct attribute value count for attribute "mode", regardless of which ParentNode they are in.
Assuming you want the count of the distinct "mode" attribute values within the nodes with the same ID/name, you just need to project from each element in the group to the mode, then take the distinct sequence of those modes, then count it:
You just need to take the count of the group, and also use SelectMany to "flatten" your parent nodes. (Or just use myXml.Descendants("Node") to start with.)
Short but complete example which gives your desired results:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Test
{
static void Main()
{
XElement myXML = XElement.Load("test.xml");
IEnumerable<XElement> parentNodes = myXML.Descendants("ParentNode");
var nodeAttributes = parentNodes
.SelectMany(le => le.Descendants("Node"))
.GroupBy(x => new {
Id = x.Attribute("id").Value,
Name = x.Attribute("name").Value
})
.Select(g => new {
g.Key.Id,
g.Key.Name,
DistinctModeCount = g.Select(x => x.Attribute("mode").Value)
.Distinct()
.Count()
});
foreach (var item in nodeAttributes)
{
Console.WriteLine(item);
}
}
}
Alternatively:
XElement myXML = XElement.Load("test.xml");
var nodeAttributes = myXML
.Descendants("Node")
.GroupBy(...)
// Remaining code as before
I'm new to C# but I'm attempting to load data from a xml file that's like this...
<?xml version="1.0" encoding="utf-8" ?>
<adventures>
<adventure_path Name ="Adventure Path 1">
<adventure Name ="Adventure 1">
<senario Name ="Senario 1">
<location Name="Location 1" Players="1"/>
</senario>
<adventure Name ="Adventure 2">
<senario Name ="Senario 2">
<location Name="Location 2" Players="1"/>
</senario>
</adventure>
</adventure_path>
<adventure_path Name ="Adventure Path 2">
<adventure Name ="Adventure 3">
<senario Name ="Senario 3">
<location Name="Location 3" Players="1"/>
</senario>
<adventure Name ="Adventure 4">
<senario Name ="Senario 4">
<location Name="Location 4" Players="1"/>
</senario>
</adventure>
</adventure_path>
</adventures>
What I'm trying to do with this is load the name attributes to an itemlistbox of the adventure element (the "adventure 1", "adventure 2", etc.) within the selected adventure_path element in an itemlistbox. I got the selection part working and the loading to the list working. What's not working is loading all the adventures...
Basically what happens is ListBox1 loads the adventure paths all fine and dandy, I select one of the said adventure paths, and ListBox2 load the first adventure... and that's it. It wont load adventure 2, 3, or 4. So here's the code that should load all the adventures-
private void lst_Adventure_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string selectedItem = lst_Adventure.SelectedItem.ToString();
lst_Adventures.Items.Clear();
XDocument doc = new XDocument();
bool gotStatsXml = false;
try
{
doc = XDocument.Load("D:\\WpfApplication1\\WpfApplication1\\Adventures.xml");
gotStatsXml = true;
}
catch
{
gotStatsXml = false;
}
XElement selectedElement = doc.Descendants().Where(x => (string)x.Attribute("Name") == selectedItem).FirstOrDefault();
foreach (var docs in selectedElement.Descendants("adventure")) ;
{
XElement elements = selectedElement.Element("adventure");
string AdventuresPathName = elements.Attribute("Name").Value;
lst_Adventures.Items.Add(AdventuresPathName);
}
}
You have two problems:
You don't check if selectedElement is null before accessing its descendants
You are adding first adventure element of selectedElement on each loop (take a look - instead of using docs element (which is adventure) you are loading Element("adventure") from selectedElement)
Instead you should use
if (selectedElement != null)
{
foreach (var docs in selectedElement.Elements("adventure"))
{
string AdventuresPathName = (string)docs.Attribute("Name");
lst_Adventures.Items.Add(AdventuresPathName);
}
}
Or, simply get all adventure names:
List<string> adventureNames =
doc.Root.Elements("adventure_path")
.Where(ap => (string)ap.Attribute("Name") == selectedItem)
.Elements("adventure")
.Select(a => (string)a.Attribute("Name"))
.ToList();
Or with XPath
var xpath = String.Format("//adventure_path[#Name='{0}']/adventure", selectedItem);
List<string> adventureNames = doc.XPathSelectElements(xpath)
.Select(a => (string)a.Attribute("Name"))
.ToList();
you are missing the value property x.Attribute("Name").Value
XElement selectedElement = doc.Descendants().Where(x => x.Attribute("Name").Value == selectedItem).FirstOrDefault();
My sample XML is something like this:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<RoleSecurity Name="A" Workflowstatus ="B">
<Accountgroup Name = "Group1">
<Attribute ID="12345" Name="Sample1"/>
<Attribute ID="12445" Name="Sample2"/>
</Accountgroup>
<Accountgroup Name = "Group2">
<Attribute ID="12345" Name="Sample1"/>
<Attribute ID="12445" Name="Sample2"/>
</Accountgroup>
</RoleSecurity>
</Root>
I am trying to enumerate and extract all the ID corresponding to a particular Role name, workflow status and account group.
My LINQ query is working to select a node based on role name. But I am unable to proceed further.
Please help!
This is my LINQ code till now.
XElement xcd = XElement.Load(strFileName);
IEnumerable<XElement> enumCust = from cust in xcd.Elements("RoleSecurity")
where (string)cust.Attribute("Name") == strRole
select cust;
Try this:
string roleName = "A";
string workflowStatus = "B";
string accountGroup = "Group1";
string xml = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Root>
<RoleSecurity Name=""A"" Workflowstatus =""B"">
<Accountgroup Name = ""Group1"">
<Attribute ID=""12345"" Name=""Sample1""/>
<Attribute ID=""12445"" Name=""Sample2""/>
</Accountgroup>
<Accountgroup Name = ""Group2"">
<Attribute ID=""12345"" Name=""Sample1""/>
<Attribute ID=""12445"" Name=""Sample2""/>
</Accountgroup>
</RoleSecurity>
</Root>";
XElement element = XElement.Parse(xml);
var ids = element.Elements("RoleSecurity")
.Where(
e =>
(string) e.Attribute("Name") == roleName &&
(string) e.Attribute("Workflowstatus") == workflowStatus)
.Elements("Accountgroup").Where(e => (string) e.Attribute("Name") == accountGroup)
.Elements("Attribute")
.Select(e => new {ID = (string) e.Attribute("ID"), Name = (string) e.Attribute("Name")});
Try with this approach, seems different from your (and in some aspects it really changes), but in my opinion its a good way to use fluently a LINQ query to parse an XML file, it follows XML node sequence and it's easy to understand:
XElement element = XElement.Load(strFileName);
var linqList = element.Elements("RoleSecurity")
.Where(entry => entry.Attribute("Name").Value == "A" &&
entry.Attribute("Workflowstatus").Value == "B")
.Descendants("Accountgroup")
.Where(x => x.Attribute("Name").Value == "Group1")
.Descendants("Attribute")
.SelectMany(id => id.Attribute("ID").Value);
XElement xcd = XElement.Load(strFileName);
IEnumerable<XElement> enumCust = from cust in xcd.Root.Elements("RoleSecurity")
where cust.Attribute("Name").Value == strRole
select cust;
This should work now
you were missing .Root which is required to enumerate below the root node
and .Value to retrive the string value of the specified attribute
refer this artical :- http://www.dotnetcurry.com/ShowArticle.aspx?ID=564
foreach (XElement xcd xelement.Descendants("Id"))
{
Console.WriteLine((string)xcd);
}
I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)
I'm having trouble trying to update my xml file with a new value. I have a class Person, which only contains 2 strings, name and description. I populate this list and write it as an XML file. Then I populate a new list, which contains many of the same names, but some of them contains descriptions that the other list did not contain. How can I check if the name in the current XML file contains a value other than "no description", which is the default for "nothing"?
Part of the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Names>
<Person ID="2">
<Name>Aaron</Name>
<Description>No description</Description>
</Person>
<Person ID="2">
<Name>Abdi</Name>
<Description>No description</Description>
</Person>
</Names>
And this is the method for writing the list to the xml file:
public static void SaveAllNames(List<Person> names)
{
XDocument data = XDocument.Load(#"xml\boys\Names.xml");
foreach (Person person in names)
{
XElement newPerson = new XElement("Person",
new XElement("Name", person.Name),
new XElement("Description", person.Description)
);
newPerson.SetAttributeValue("ID", GetNextAvailableID());
data.Element("Names").Add(newPerson);
}
data.Save(#"xml\boys\Names.xml");
}
In the foreach loop how do I check if the person's name is already there, and then check if the description is something other than "no description", and if it is, update it with the new information?
I'm not sure I understand properly what you want, but I'm assuming you want to update the description only when the name is already there and the description is currently No description (which you should probably change to an empty string, BTW).
You could put all the Persons into a Dictionary based by name:
var doc = …;
var persons = doc.Root.Elements()
.ToDictionary(x => (string)x.Element("Name"), x => x);
and then query it:
if (persons.ContainsKey(name))
{
var description = persons[name].Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
That is, if you care about performance. If you don't, you don't need the dictionary:
var person = doc.Root.Elements("Person")
.SingleOrDefault(x => (string)x.Element("Name") == name);
if (person != null)
{
var description = person.Element("Description");
if (description.Value == "No description")
description.Value = newDescription;
}
You can use the Nodes-Method on XElement and check manually.
But i will advise you to use the XPathEvaluate-Extension Method
For XPath expression take a look at:
How to check if an element exists in the xml using xpath?
I think you could create a peoplelist which only contains people not in the xml.
like ↓
var containlist = (from p in data.Descendants("Name") select p.Value).ToList();
var result = (from p in peoplelist where !containlist.Contains(p.Name) select p).ToList();
so that , you would no need to change anything with your exist method ...
just call it after..
SaveAllNames(result);