Limit query to ignore elements descendants - c#

I'm trying to query the following XML to build some objects which copy the folder hierarchy outlined in the XML.
<ShareList>
<Title>Documantis</Title>
<Url>/sites/dev/Documantis/Forms/AllItems.aspx</Url>
<Guid>fed8f456-efa9-4fe5-8b97-46734a3040b6</Guid>
<HasUniqueScopes>False</HasUniqueScopes>
<RootFolder>/sites/dev</RootFolder>
<Children>
<ShareListItem>
<Title>First</Title>
<Url>Documantis/First</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
<Children>
<ShareListItem>
<Title>Second</Title>
<Url>Documantis/First/Second</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<ParentGuid>22b2a7e9-a42e-497f-aad3-8caa85f6ac6d</ParentGuid>
</ShareListItem>
</Children>
</ShareListItem>
<ShareListItem>
<Title>Folda</Title>
<Url>Documantis/Folda</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
</ShareListItem>
</Children>
</ShareList>
I'm having trouble finding a way to return one level of the <ShareListItem> elements at a time, with my current code it returns all the ShareListItems in one List which doesn't represent the hierarchy accurately.
XmlDocument doc = new XmlDocument();
doc.LoadXml(sharepointXml);
XElement root;
using (XmlReader xr = new XmlNodeReader(doc)) { root = XElement.Load(xr); }
var result = from child in root.DescendantsAndSelf("ShareList") //.Elements("ShareList") // Descendants("ShareList")
select child;
foreach (XElement xml in result)
{
// Build ListItem from results
ShareList list = new ShareList()
{
Title = xml.Element("Title").Value,
Url = xml.Element("Url").Value,
Guid = xml.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(xml.Element("HasUniqueScopes").Value),
RootFolder = xml.Element("RootFolder").Value,
};
if (xml.Element("Children") != null)
{
var subResult = from child in xml.Element("Children").Descendants("ShareListItem")
select child;
foreach (XElement subXml in subResult)
{
// results here are flat and don't show depth of nodes
}
//list.Children =
}
I could recursively infer the hierarchy's structure from the URL Element, however I already have it represented in XML so I would rather learn how to return this through a query.
Edit:
Here's what I ended up using
public List<ShareList> HandleLists(XElement levelRoot)
{
List<ShareList> lists = new List<ShareList>();
var results = from list in levelRoot.DescendantsAndSelf("ShareList")
select list;
foreach (var list in results)
{
var children = list.Element("Children");
if (children == null)
return null;
ShareList shareList = new ShareList()
{
Title = list.Element("Title").Value,
Url = list.Element("Url").Value,
Guid = list.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(list.Element("HasUniqueScopes").Value),
RootFolder = list.Element("RootFolder").Value,
// Recursively find ListItem folders
Children = HandleSubfolders(list)
};
lists.Add(shareList);
}
return lists;
}
public List<ShareListItem> HandleSubfolders(XElement levelRoot)
{
List<ShareListItem> subfolders = new List<ShareListItem>();
// All nodes deeper than current
var children = levelRoot.Element("Children");
if (children == null)
return null;
// Subfolders
var items = children.Elements("ShareListItem");
foreach (var item in items)
{
ShareListItem listItem = new ShareListItem()
{
Title = item.Element("Title").Value,
Url = item.Element("Url").Value,
HasUniqueRole = Convert.ToBoolean(item.Element("HasUniqueRole").Value),
IsSubfolder = Convert.ToBoolean(item.Element("IsSubFolder").Value),
PermissionMask = item.Element("PermissionMask").Value,
PermissionMaskName = item.Element("PermissionMaskName").Value,
// Recursively find ListItem subfolders
Children = HandleSubfolders(item)
};
// Add subfolder to Children collection
subfolders.Add(listItem);
}
return subfolders;
}

You would want to use recursion here.
Create a method that handles one level of the hierarchy and calls itself with the next level.
public void HandleLevel(XElement levelRoot)
{
PerformAction(levelRoot);
var children = levelRoot.Element("Children");
if(children == null)
return;
var items = children.Elements("ShareListItem");
foreach(var item in item)
{
// Handle child's children:
HandleLevel(item);
}
}
PerformAction is the code that actually does, whatever you want to do for each document.
The way the code is currently structured, this action is also executed for the root document /sites/dev/Documantis/Forms/AllItems.aspx.
If you don't want this simply move PerformAction into the foreach loop and pass item instead of levelRoot.
BTW: Your initialization of the root element is very strange.
You can simply use this:
var root = XDocument.Parse(sharepointXml).Root;
The initial call to HandleLevel would simply look like this:
HandleLevel(root);

A good way of producing the results your after is by using XPath (here's a good primer if you need it).
Once you've got your XML into an XmlDocument you can return different bits of it by using an XPathNavigator, like this:
var xmlNavigator = xmlDocument.CreateNavigator();
var outerQuery = xmlNavigator.Select("ShareList/Children/ShareListItem");
while (outerQuery.MoveNext()) {
Console.WriteLine(outerQuery.Current.SelectSingleNode("Title").Value);
var innerQuery = outerQuery.Current.Select("Children/ShareListItem");
while (innerQuery.MoveNext()) {
Console.WriteLine(" - " + innerQuery.Current.SelectSingleNode("Title").Value);
}
}
In the code above, we query the XML for all ShareListItem nodes within Children nodes of the root ShareList node, and store the resultant XPathNodeIterator in the variable outerQuery. We then iterate over all the nodes found, and run an operation as well as another XPath query on each to retrieve child nodes to process. The code above produces the following output:
First
- Second
Folda
Which I think it what you're after. Obviously, you can use recursion if necessary if your XML can be nested more deeply than this.

One way of doing this is by creating Classes to represent the hierarchy like so:
public class ShareList {
...
public List<ShareList> Children { get; set; }
}
In your code, refactor the traversing part into a method that accepts a Sharelist node and traverse it, calling itself for each Children node:
private Sharelist RecurseHierarchy(XElement sharelistNode, ShareList parent)
{
// your traversing code goes here
// get your data and create a new Sharelist object
// if it has a children node, traverse it and call this same method on the child
// Sharelist nodes
parent.Title = sharelistNode.Element("Title").Value;
var children = sharelistNode.Element("Children");
if (children != null)
{
var items = children.Elements("ShareListItem");
foreach(var listItem in items)
{
ShareList childShareList = new ShareList();
parent.Children.Add(childShareList);
RecurseHierarchy(listItem, childShareList);
}
}
// Just in case we want to chain some method
return parent;
}
To call it initially, you will have to pass in the root node and a new ShareList object.

Related

Tree of objects

I have this list of 2000+ categories that need to be organized in a tree before being sent to the controller and the View so the javascript plugin can render them correctly.
I am already doing this but the performance is terrible. It is taking like 30 seconds to assemble the tree.
I can't see what is dropping performance here. Can you guys help me to improve this code?
var allCategories = dal.Listar();
List<Model.Entity.CategoriaCursoEADVO> nestedCategories = new List<Model.Entity.CategoriaCursoEADVO>();
foreach (Model.Entity.CategoriaCursoEAD item in allCategories)
{
if (item.IdCategoriaPai == null)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = item.Id;
child.text = item.Descricao;
nestedCategories.Add(child);
FillChild(allCategories, child, item.Id);
}
}
And here is the FillChild method:
public int FillChild(IEnumerable<CategoriaCursoEAD> categorias, CategoriaCursoEADVO parent, int IID)
{
var childCategories = categorias.Where(w => w.IdCategoriaPai.Equals(IID));
parent.children = new List<CategoriaCursoEADVO>();
if (childCategories.Count() > 0)
{
foreach (CategoriaCursoEAD cat in childCategories)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = cat.Id;
child.text = cat.Descricao;
parent.children.Add(child);
FillChild(categorias, child, cat.Id);
}
return 0;
}
else
{
return 0;
}
}
I think the problem is with the new instances and tried using Parallel loops with no satisfatory level of improvement.
This is a pretty good time to use a HashTable (Dictionary). Something like the below code should help.
// Convert the flat list into a hash table with the ID
// of the element as the key
var dict = allCategories.ToDictionary (i => i.Id);
// Group categories by the parent id
var parentGrouping = allCategories.Where(c => c.IdCategoriaPai != null).GroupBy(c => c.ParentId);
// Since we group the items by parent id, we can find
// the parent by id in the dictionary and add the children
// that have that particular id.
foreach(var groupItem in parentGrouping)
if(groupItem.Key != null)
dict[(int)groupItem.Key].children.AddRange(groupItem);
// Get the root elements.
var hierarchicalCategories = allCategories.Where(item => item.IdCategoriaPai == null);
// Do what you need to do here.
This code will create a tree of categories. hierarchicalCategories will contain direct references to the root elements (categories that do not have a parent), assuming that your data is structured that way.

Populating TreeListView from existing hierarchy list

I have a list of objects called Items, that I want to display in a Treelistview in a hierarchy. The objects look like this:
public class ListItem
{
public int ID;
public string LabelText;
public int ParentID;
public bool Checked;
}
The list order ensures that parents are always defined first. Now I want to iterate over this list and build my hierarchy in the Treelistview. When I dectect a parent in the object I can search the already created nodes on the ListView and search for the ID, but I was wondering if it is possible to create the TreeNodes dynamically with the ID as the reference, like so (bit of pseudo code);
foreach (ListItem Item in Items)
{
TreeNode {Item.ID} = new TreeNode()
{
Checked = Item.Checked,
Text = Item.LabelText
};
if (Item.ParentID == null)
DropDown.Nodes.Add({Item.ID});
else
{Item.ParentID}.SubItems.Add({Item.ID});
}
To avoid having to recursively search the existing tree every time, I'd simply store each node in a temporary Dictionary<int,TreeNode>. Something like this:
var temp = new Dictionary<int,TreeNode>();
foreach( var item in Items )
{
var node = new TreeNode()
{
Checked = item.Checked,
Text = Item.LabelText
};
temp.Add( item.ID, node );
if( item.ParentID == 0 )
DropDown.Nodes.Add( node );
else
// TODO: make sure node.ParentId exists in temp here
temp[node.ParentID].SubItems.Add( node );
}
Also, depending on what you need, it might also be useful to create a custom TreeNode class that holds a reference to the ListItem.

Is there a better way to organize this data rather than using string arrays?

I have two loops that pull data from two XML sources:
Loop1:
foreach (XmlNode nodes in node.ChildNodes)
{
if (nodes.Name == "DEFAULT")
defaults[count] = nodes.InnerText;
if (nodes.Name == "TYPE"
types[count] = nodes.InnerText;
if (nodes.Name == "COL_NAM"
names[count] = nodes.InnerText;
}
count++;
Loop2:
foreach (XmlNode nodes in root.ChildNodes)
{
vals[i] = nodes.InnerText;
cols[i] = nodes.Name;
i++;
}
Somehow, I want to organize this data into one ultimate object. The object needs to have 4 parts: Names, Types, Values, and Defaults. Essentially, I want to group together everything from Loop1, and then everything from Loop2, and then add together the two objects into one object matching names from Loop1 with cols from Loop2. Ideally, the amount of nodes in Loop2 could be less than that of Loop1. But, if that's not possible I can work around it.
For a better picture of the final object:
object everything = {{names}, {types}, {values}, {defaults}};
Names will come from BOTH loops, and will be the 'Key' to the object. Types and defaults will come from Loop1, and values will come from Loop2. The concatenation will match using Name/Col.
PS: I tried to do this using 2D String arrays, but ran into trouble when trying to combine the two matching the cols and names fields.
You could use the Dictionary class and use the Name as the key for your object.
For example:
public class MyObj{
public string Name{get;set;};
public string Type{get;set;};
public string Value{get;set;};
public string Default{get;set;};
}
And modify your loop to use it:
var allObjs = new Dictionary<string, MyObj>();
foreach (XmlNode nodes in node.ChildNodes)
{
var obj = new MyObj();
if (nodes.Name == "DEFAULT")
obj.Default = nodes.InnerText;
...
allObjs.Add(obj.Name, obj);
}
Then on your second loop retrieve your existing object using the key in order to update the value. Something like this:
foreach (XmlNode nodes in root.ChildNodes)
{
var myObj = allObs[nodes.Name];
myObj.Value = nodes.InnerText;
}
Well, you can use XPath to select your values.
public class Ultimate
{
public Ultimate(XmlNode node)
{
XmlNode child = node.SelectSingleObject("./COL_NAM");
if (child == null) throw new InvalidArgument("COL_NAM not found");
Name = child.InnerText;
XmlNode child = node.SelectSingleObject("./TYPE");
if (child == null) throw new InvalidArgument("TYPE not found");
Type = child.InnerText;
XmlNode child = node.SelectSingleObject("./DEFAULT");
if (child == null) throw new InvalidArgument("DEFAULT not found");
Default = child.InnerText;
XmlNode child = node.SelectSingleObject("./COL_NAM");
if (child == null) throw new InvalidArgument("COL_NAM not found");
Name = child.InnerText;
}
public string Name;
public string Type;
public string Value;
public string Default;
}
// Reading objects would be like this:
Dictionary<string, Ultimate> ultimateCollection = new Dictionary<string, Ultimate>();
foreach(XmlNode node in xmlDocument1.SelectNodes("/DATA1"))
{
Ultimate ultimate = new Ultimate(node);
ultimateCollection.Add(ultimate.Name, ultimate);
}
foreach(XmlNode node in root.ChildNodes)
{
Ultimate ultimate;
if (ultimateCollection.TryGetValue(node.Name, out ultimate))
ultimate.Values = node.InnerText;
}
I hope this will help you in your quest.
From what is sounds like your describing I would use XElement \ LINQ.
http://codingsense.wordpress.com/2008/09/23/join-xml-using-linq/
..and/or..
Serialize/deserialze the xml to C# objects
http://www.java2s.com/Code/CSharp/XML/XmlSerializationHelper.htm

How can I get all expanded nodes in treeview?

I have a program that contains a TreeView. All of my nodes except for the root and two nodes under the root are loaded from a database.
When the user adds data to the database it must be automatically added to the TreeView. I can successfully do this by clearing all the nodes, add the default nodes and add all the data including the new one to my TreeView, but all nodes of the new TreeView are collapsed.
Our client wants to retain all expanded nodes but still add the new data he just added. Is there any way to know all expanded nodes and expand it again once collapsed or refreshed? Thank you for any response.
Hi As i understand you want to save you tree view map after you refresh tree view (by adding new data or even remove some) you want to expand all expanded nodes the other are collapsed by default.
Solution is:
1) save expanded tree view nodes before refresh
2) refresh tree view data (notice that if you are removing a node , remove it from saved list as well)
3) set tree view map which saved before
saving tree view map (only expanded nodes)->
This Code Look through a tree view node collection and saves expanded nodes Names in a string list
List<string> collectExpandedNodes(TreeNodeCollection Nodes)
{
List<string> _lst = new List<string>();
foreach (TreeNode checknode in Nodes)
{
if (checknode.IsExpanded)
_lst.Add(checknode.Name);
if (checknode.Nodes.Count > 0)
_lst.AddRange(collectExpandedNodes(checknode.Nodes));
}
return _lst;
}
Now you have collected expanded nodes name in a list and you want to gain back your tree view appearance you need 2 functions a function which retrieves node by a name and a function which expand selected node and it's parents the following codes does :
This function retrieves a pointer to selected node Name if node is exist in Tree Node Collection
TreeNode FindNodeByName(TreeNodeCollection NodesCollection , string Name)
{
TreeNode returnNode = null; // Default value to return
foreach (TreeNode checkNode in NodesCollection)
{
if (checkNode.Name == Name) //checks if this node name is correct
returnNode = checkNode;
else if (checkNode.Nodes.Count > 0 ) //node has child
{
returnNode = FindNodeByName(checkNode.Nodes , Name);
}
if (returnNode != null) //check if founded do not continue and break
{
return returnNode;
}
}
//not found
return returnNode;
}
and This Function Expand node and it's parents
void expandNodePath(TreeNode node)
{
if (node == null)
return;
if (node.Level != 0) //check if it is not root
{
node.Expand();
expandNodePath(node.Parent);
}
else
{
node.Expand(); // this is root
}
}
and the following show you the usage of the functions
private void button4_Click(object sender, EventArgs e)
{
//saving expanded nodes
List<string> ExpandedNodes = new List<string>();
ExpandedNodes = collectExpandedNodes(treeView1.Nodes);
//resetting tree view nodes status to colapsed
treeView1.CollapseAll();
//Restore it back
if (ExpandedNodes.Count > 0)
{
TreeNode IamExpandedNode;
for (int i = 0; i < ExpandedNodes.Count;i++ )
{
IamExpandedNode = FindNodeByName(treeView1.Nodes, ExpandedNodes[i]);
expandNodePath(IamExpandedNode);
}
}
}
For expanding all the nodes use below code
treeView1.ExpandAll();
For expanding selected node use below code
treeView1.SelectedNode.ExpandAll();
For expanding particular node use below code
treeView1.Nodes[Index].Expand();
Assume the Nodename is unique.
Using an Database the nodename can be the unique rowid of an table
The state of the tree (List) can simply saved with an Formatter (e.g. BinaryFormatter)
if User want to save status
Only save the expandedstate
private List<string> SaveTreeState(TreeNodeCollection nodes)
{
List<string> nodeStates = new List<string>();
foreach (TreeNode node in nodes)
{
if (node.IsExpanded) nodeStates.Add(node.Name);
nodeStates.AddRange(SaveTreeState(node.Nodes));
}
return (nodeStates);
}
Let the treeview do the work finding the nodes for restore
private void RestoreTreeState(TreeView tree, List<string> treeState)
{
foreach (string NodeName in treeState)
{
TreeNode[] NodeList = treeView1.Nodes.Find(NodeName, true);
if (NodeList.Length > 0) // only if node after reload is avail
NodeList[0].Expand();
}
}
Using:
List<string> StateList = SaveTreeState(treeView1.Nodes);
... // reload
RestoreTreeState(treeView1, StateList);
To simply expand nodes you can try this following code
private void button1_Click(object sender, EventArgs e)
{
treeView1.Nodes.Add(new TreeNode("New Node",
new TreeNode[2] { new TreeNode("Node1"), new TreeNode("Node2") }));
treeView1.Nodes[1].Expand();
}
Hope that helps
It's very simple. Below, you can see my recursive version:
//List of storage ids of expanded nodes
List<int> expandedNodeIds = new List<int>();
//call recursive fun for our tree
CollectExpandedNodes(tree.Nodes);
//recursive fun for collect expanded node ids
private void CollectExpandedNodes(TreeListNodes nodes)
{
foreach (TreeListNode node in nodes)
{
if (node.Expanded) expandedNodeIds.Add(node.Id);
if (node.HasChildren) CollectExpandedNodes(node.Nodes);
}
}
I know this post is old but if the tree is deep it might not be a good idea to use a recursive traversal of the tree. Since I have not seen any anwser using a non-recursive way here is a solution for doing getting expanded nodes without impacting performance.
public static IEnumerable<TreeNodeAdv> CollectExpandedNodes(this TreeNodeAdv root)
{
Stack<TreeNodeAdv> s = new Stack<TreeNodeAdv>();
s.Push(root);
while (s.Count > 0)
{
TreeNodeAdv n = s.Pop();
if (n.IsExpanded)
yield return n;
foreach (var child in n.Children.ToArray().Reverse())
{
s.Push(child);
}
}
}
To use this method, you can do the following:
foreach (TreeNodeAdv expandedNode in yourTreeView.Root.CollectExpandedNodes())
{
//Do processing on the expanded node or add in list.
}
This extension method uses Deep-First traversal in post-order along with the yield keyword to generate an IEnumerable collection.

C# Treeview checking if node exists

I'm trying to populate a treeview from an XmlDocument.
The Root of the tree is set as 'Scripts' and from the root the next level should be 'Departments' which is within the XML script. I can get data from the XML document no problem. My question is when looping through the XmlDocument and adding nodes to the root node, I want to ensure that if a department is already within the treeview then it is not added again. I should also add that each Department also has a list of scripts that need to be child nodes of the department.
My code so far is:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(scriptInformation);
TreeNode t1;
TreeNode rootNode = new TreeNode("Script View");
treeView1.Nodes.Add(rootNode);
foreach (XmlNode node in xDoc.SelectNodes("//row"))
{
t1 = new TreeNode(node["DEPARTMENT"].InnerXml);
//How to check if node already exists in treeview?
}
Thanks.
if(treeView1.Nodes.ContainsKey("DEPARTMENT")){
//...
}
EDIT: Recursive method:
bool exists = false;
foreach (TreeNode node in treeView1.Nodes) {
if (NodeExists(node, "DEPARTMENT"))
exists = true;
}
private bool NodeExists(TreeNode node, string key) {
foreach (TreeNode subNode in node.Nodes) {
if (subNode.Text == key) {
return true;
}
if (node.Nodes.Count > 0) {
NodeExists(node, key);
}
}
return false;
}
Depending upon the size of your XML file, you could consider using an associated List for fast lookup. As you add each node to the TreeView also add it to the List.
If your XML document has a set structure where 'Departments' will always be indexed at 1;
ie:
index:[0] Scripts
index:[1] Department
index:[2] Script
index:[1] Department2
index:[2] Script
Then you could encapsulate the following code into a method where 'name' is a string parameter and the return type is boolean.
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
The idea is you would call that function each time you encounter a 'Department' node in your Xml, before creating the TreeNode.
Full example:
private bool DepartmentNodeExists(string name) {
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
}
Lastly, the easy way:
private bool DepartmentNodeExists(string name) {
if (uxTreeView.Nodes[0].ContainsKey(name)) {
return true;
}
else {
return false;
}
}
These are all just refactored and encapsulated into their own named methods, you of course could just call:
if (uxTreeView.Nodes[0].ContainsKey(name)) {
// do not create TreeNode
}
...during your parsing of your XML. PS. These examples all assume that you have the first root node in the TreeView already created and added to the TreeView.
http://www.vbdotnetforums.com/listviews-treeviews/13278-treeview-search.html#post39625
http://forums.asp.net/t/1645725.aspx/1?Check+if+child+Node+exists+on+treeview
You can do something like this:
TreeNode parentNode = t1.Parent;
if (parentNode != null}
{
if(parentNode.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
//Add node
}
}
else
{
bool isFound = true;
if (treeView1.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
isFound = false;
}
if(!isFound)
{
//Add node
}
}
Not sure about the document structure...
Couldn't you use Linq to Xml, load the document and get the distinct row ( row = department?) and consider only those elements to create a TreeNode? It is more efficient than trying to find if a node with such a text has already been added.
ex:
var rows = ( from row in XDocument.Load(document).Root.Elements("row")
select row
).Distinct(new SampleElementComparerOnNameAttribute());
Here the EqualityComparer is made on the "name" attribute value assuming the doc structure to be
<rows><row name='dep1'><script>script1</script><script>script2</script></row><row name='dep1'><script>script3</script><script>script4</script></row></rows>
I use,
string department = node["DEPARTMENT"].InnerXml;
TreeNode node = parentNode.Nodes[department] ?? parentNode.Nodes.Add(department, department);
That line guarantees that a lookup of the value department will be done first, if not found it creates it. You have to do the double entry in Add() so it will have a key value you can do the lookup with the .Nodes[department].
It depends on the structure of your input. Since you don't show how exactly you add your subnodes I can only point you towards either the Contains or the ContainsKey method of the Nodes property, either of the treeView1 itself, or of any subnodes you add. You should use an overload of the Add method to specify a key name to simplify lookup.

Categories

Resources