Listing atributes from sub-elements - c#

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();

Related

Linq to XML delete parent node

I'm trying to delete a parent from a element in XML.
My XML:
<root>
<Element1 ManagementID="10" />
<Users>
<UserID ManagementID="10">
<Identification IDValue="1" />
<!-- More elements Here -->
</UserID>
</Users>
<!-- More Users elements Here -->
I find my user my its IDValue:
XElement user = (from el in document.Root.Elements("Users").Elements("UserID ").Elements("Identification")
where (string)el.Attribute("IDValue") == myID
select el).FirstOrDefault();
Now, I would like to remove all the user.Parent.Parent
I mean delete the element:
<Users>
<UserID ManagementID="10">
<Identification IDValue="1" />
<!-- More elements Here -->
</UserID>
</Users>
** I'll have many Users elements, that's why first I look for the identification IDValue
I found the solution for who needs it:
I already had the node from my linq so
user.Parent.Parent.Remove()
var user = document.Root
.XPathSelectElements("//UserId")
.FirstOrDefault(userId => userId.Element("Identification").Attribute(XName.Get("IDValue")).Value == myID);
Try this :
List<XElement> users = document.Descendants("Users")
.Where(user => user.Elements("Identification")
.Where(el => (string)el.Attribute("IDValue") != myID)
.Any()).ToList();
XElement element1 = document.Descendants("Element1").FirstOrDefault();
element1.ReplaceNodes(users);

linq to XML: unique attribute value count per group

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

Loading attribtues from an element within an element

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.

Converting xml tree to json tree

Here's my code which succesfully creates XML:
XDocument xdoc = new XDocument();
XElement root = new XElement("tree");
root.Add(new XAttribute("id", 0));
xdoc.Add(root);
new BuildFoldersTree(root, db);
var items = (from x in db.Items orderby x.name select new { x.name, x.id, x.parent }).ToList();
foreach (var p in items)
{
XElement e = new XElement("item",
new XAttribute("text", p.name),
new XAttribute("id", p.id),
new XAttribute("parentId", p.parent));
XElement parent = root.XPathSelectElement(String.Format("//*[#id=\"FOLDER_{0}\"] ", p.parent.ToString()));
if (parent != null) parent.Add(e);
}
and:
public void BuildFoldersTree(XElement root, MyEntities db)
{
List<Folder> folders = (from x in db.Folders orderby x.parent select x).ToList();
for (int i = 0; i < folders.Count; i++)
{
int f_id = folders[i].parent;
Folder folder = folders[i];
XElement e = new XElement("item",
new XAttribute("text", folder.name),
new XAttribute("id", "FOLDER_" + folder.id.ToString()),
new XAttribute("parentId", folder.parent));
if (folder.parent == 0)
{
root.Add(e);
}
else
{
XElement parent = root.XPathSelectElement(String.Format("//*[#id=\"FOLDER_{0}\"] ", folder.parent.ToString()));
parent.Add(e);
}
}
}
Here's what's going on there:
I have two tables in my database. One for Folders, and one for Items. Each item has a parent folder. The item has a column 'parent' which is integer and which represents the folder id.
But, each folder also has a parent. The folder has a column named 'parent' which is integer and which represents an id of another folder.
With that code, I'm creating an xml tree with folders, and then I add the items to the correct folder.
The previous code works.
Now, I need to make the same algorithm but for Json. So, it won't use Xml, it should create a Json tree.
I have no idea on how to begin. What should I use?
Here's an example of what the result xml looks like:
<tree id="0">
<item text="Folder_name" id="FOLDER_1" parentId="0">
<item text="Other folder name" id="FOLDER_96" parentId="1">
<item text="Third folder name" id="FOLDER_127" parentId="96">
<item text="New folder" id="FOLDER_147" parentId="127" />
<item text="item name" id="959" parentId="147" />
<item text="item name sdgdfh" id="1152" parentId="147" />
</item>
</item>
</item>
</tree>
There is functionality in the JSON.NET library for this. You can use the SerializeXmlNode method of the JsonConvert class contained in the JSON.NET library. Your code would look as follows:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
string json = JsonConvert.SerializeXmlNode(xmlDocument);
You can find more information here: http://james.newtonking.com/projects/json/help/index.html?topic=html/ConvertingJSONandXML.htm
If we apply this to your example, we have the following:
string xml = "<tree id=\"0\">" +
"<item text=\"Folder_name\" id=\"FOLDER_1\" parentId=\"0\">" +
"<item text=\"Other folder name\" id=\"FOLDER_96\" parentId=\"1\">" +
"<item text=\"Third folder name\" id=\"FOLDER_127\" parentId=\"96\">" +
"<item text=\"New folder\" id=\"FOLDER_147\" parentId=\"127\" />" +
"<item text=\"item name\" id=\"959\" parentId=\"147\" />" +
"<item text=\"item name sdgdfh\" id=\"1152\" parentId=\"147\" />" +
"</item>" +
"</item>" +
"</item>" +
"</tree>";
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
string json = JsonConvert.SerializeXmlNode(xmlDocument);
The json variable now contains the following data:
{
"tree":{
"#id":"0",
"item":{
"#text":"Folder_name",
"#id":"FOLDER_1",
"#parentId":"0",
"item":{
"#text":"Other folder name",
"#id":"FOLDER_96",
"#parentId":"1",
"item":{
"#text":"Third folder name",
"#id":"FOLDER_127",
"#parentId":"96",
"item":[
{
"#text":"New folder",
"#id":"FOLDER_147",
"#parentId":"127"
},
{
"#text":"item name",
"#id":"959",
"#parentId":"147"
},
{
"#text":"item name sdgdfh",
"#id":"1152",
"#parentId":"147"
}
]
}
}
}
}
This is a JSON representation of your XML document.
I think easiest way would be using Json.Net and serializing your XDocument to json.
var jsonstr = JsonConvert.SerializeXNode(xdoc);

Pulling data from an XML file into a TreeView item (C#)

I'm having a bit of an issue. I'm trying to parse an XML file and put it's contents into a TreeView. I got everything pretty much working, but I'm having issues with one thing.
Here is an example of the XML file:
<AnswerIt>
<category name="Category 1">
<question is="Question 1">
<answer couldbe="Answer 1" />
<answer couldbe="Answer 2" />
</question>
</category>
<category name="Category 2">
<question is="Question 1">
<answer couldbe="Answer 1" />
</question>
<question is="Question 2">
<answer couldbe="Answer 1" />
</question>
</category>
</AnswerIt>
The code I am using to prase the XML file pulls all the categories just fine. When it gets to the question part it pulls the first question, but none after that. All the answers get pulled just fine (as long as they belong to the first question). Here is my C# code:
public void LoadQuestionDatabase()
{
XmlTextReader reader = new XmlTextReader("http://localhost/AnswerIt.xml");
TreeNode node = new TreeNode();
TreeNode subnode = new TreeNode();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "category")
{
node = lstQuestions.Nodes.Add(reader.GetAttribute(0));
categories.Add(reader.GetAttribute(0));
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.Read();
if (reader.Name == "question")
{
subnode = node.Nodes.Add(reader.GetAttribute(0));
questions.Add(reader.GetAttribute(0));
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.Read();
if (reader.Name == "answer")
{
// add each answer
subnode.Nodes.Add(reader.GetAttribute(0).Replace("\t", ""));
}
}
}
}
}
}
reader.Close();
}
I'm not very good at C#, I'm guessing somewhere along the lines it's not looping through all the questions and adding them. Any ideas what I'm doing wrong? Anywhere I can read up on to help me out? Every example I read puts the root node (AnswerIt) in the treeview, and I do not want that.
var xDocument = XDocument.Load("http://localhost/AnswerIt.xml");
foreach (var element in xDocument.Descendants("category"))
{
var node = lstQuestions.Nodes.Add(element.Attribute("name").Value);
foreach (var subElement in element.Elements("question"))
{
var subnode = node.Nodes.Add(subElement.Attribute("is").Value);
foreach (var answer in subElement.Elements("answer"))
subnode.Nodes.Add(answer.Attribute("couldbe")
.Value.Replace("\t", ""));
}
}
Instead of using the XmlTextReader try using
XDocument doc = XDocument.Load("http://localhost/AnswerIt.xml");
then you don't have to manually construct all the elements.
XDocument is provided in the System.Xml.Linq assembly and namespace. Once it's loaded into the XDocument you can then use any of its built in methods to manipulate the data.

Categories

Resources