Recursion, parsing xml file with attributes into treeview c#
You need to move the loop through attributes out of the loop through child nodes:
private void AddNode(XmlNode inXmlNode, TreeNode inTreeNode)
{
// Loop through the XML nodes until the leaf is reached.
// Add the nodes to the TreeView during the looping process.
if (inXmlNode.HasChildNodes)
{
//Check if the XmlNode has attributes
foreach (XmlAttribute att in inXmlNode.Attributes)
{
inTreeNode.Text = inTreeNode.Text + " " + att.Name + ": " + att.Value;
}
var nodeList = inXmlNode.ChildNodes;
for (int i = 0; i < nodeList.Count; i++)
{
var xNode = inXmlNode.ChildNodes[i];
var tNode = inTreeNode.Nodes[inTreeNode.Nodes.Add(new TreeNode(xNode.Name))];
AddNode(xNode, tNode);
}
}
else
{
// Here you need to pull the data from the XmlNode based on the
// type of node, whether attribute values are required, and so forth.
inTreeNode.Text = (inXmlNode.OuterXml).Trim();
}
treeView1.ExpandAll();
}
Update
If you want to filter out namespace attributes, you can add extension methods:
public static class XmlNodeExtensions
{
public static bool IsDefaultNamespaceDeclaration(this XmlAttribute attr)
{
if (attr == null)
return false;
if (attr.NamespaceURI != "http://www.w3.org/2000/xmlns/")
return false;
return attr.Name == "xmlns";
}
public static bool IsNamespaceDeclaration(this XmlAttribute attr)
{
if (attr == null)
return false;
if (attr.NamespaceURI != "http://www.w3.org/2000/xmlns/")
return false;
return attr.Name == "xmlns" || attr.Name.StartsWith("xmlns:");
}
}
Then use it to skip unwanted XmlAttribute
instances. You can also explicitly set the text of all nodes of type XmlElement
to be name + attribute data, not just those elements with children, using OuterXml
only for text nodes:
private void AddNode(XmlNode inXmlNode, TreeNode inTreeNode)
{
if (inXmlNode is XmlElement)
{
// An element. Display element name + attribute names & values.
foreach (var att in inXmlNode.Attributes.Cast<XmlAttribute>().Where(a => !a.IsNamespaceDeclaration()))
{
inTreeNode.Text = inTreeNode.Text + " " + att.Name + ": " + att.Value;
}
// Add children
foreach (XmlNode xNode in inXmlNode.ChildNodes)
{
var tNode = inTreeNode.Nodes[inTreeNode.Nodes.Add(new TreeNode(xNode.Name))];
AddNode(xNode, tNode);
}
}
else
{
// Not an element. Character data, comment, etc. Display all text.
inTreeNode.Text = (inXmlNode.OuterXml).Trim();
}
treeView1.ExpandAll();
}
If you really want to filter out just the default namespace definitions but leave others, you could do:
// An element. Display element name + attribute names & values.
foreach (var att in inXmlNode.Attributes.Cast<XmlAttribute>().Where(a => !a.IsDefaultNamespaceDeclaration()))
{
inTreeNode.Text = inTreeNode.Text + " " + att.Name + ": " + att.Value;
}
Incidentally, I don't recommend doing this since the following actually mean the same thing, namely an element with local name DataConfiguration
in the namespace http://somenamespace
:
<ss:DataConfiguration xmlns:ss="http://somenamespace"/>
<DataConfiguration xmlns="http://somenamespace"/>
Your tree will display the namespace for the first element but not the second.
Update 2
To include the XmlDeclaration
in the tree, change the top level loop to be:
treeView1.Nodes.Clear();
foreach (XmlNode xNode in dom.ChildNodes)
{
var tNode = treeView1.Nodes[treeView1.Nodes.Add(new TreeNode(xNode.Name))];
AddNode(xNode, tNode);
}
Update 3
Put the loop to include the XmlDeclaration
loop in DisplayTreeView
:
private void DisplayTreeView(string pathname)
{
try
{
// SECTION 1. Create a DOM Document and load the XML data into it.
XmlDocument dom = new XmlDocument();
dom.Load(pathname);
// SECTION 2. Initialize the TreeView control.
treeView1.Nodes.Clear();
// SECTION 3. Populate the TreeView with the XML nodes.
foreach (XmlNode xNode in dom.ChildNodes)
{
var tNode = treeView1.Nodes[treeView1.Nodes.Add(new TreeNode(xNode.Name))];
AddNode(xNode, tNode);
}
}
catch (XmlException xmlEx)
{
MessageBox.Show(xmlEx.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Parse XML to Treeview List
The existing code displays the entire XML of a "leaf" XML node but just the element name of a non-leaf node. If you don't want that, you need to modify AddNode
to display the content you want:
static string GetAttributeText(XmlNode inXmlNode, string name)
{
XmlAttribute attr = (inXmlNode.Attributes == null ? null : inXmlNode.Attributes[name]);
return attr == null ? null : attr.Value;
}
private void AddNode(XmlNode inXmlNode, TreeNode inTreeNode)
{
// Loop through the XML nodes until the leaf is reached.
// Add the nodes to the TreeView during the looping process.
if (inXmlNode.HasChildNodes)
{
XmlNodeList nodeList = inXmlNode.ChildNodes;
for (int i = 0; i <= nodeList.Count - 1; i++)
{
XmlNode xNode = inXmlNode.ChildNodes[i];
string text = GetAttributeText(xNode, "name");
if (string.IsNullOrEmpty(text))
text = xNode.Name;
inTreeNode.Nodes.Add(new TreeNode(text));
TreeNode tNode = inTreeNode.Nodes[i];
AddNode(xNode, tNode);
}
}
else
{
// If the node has an attribute "name", use that. Otherwise display the entire text of the node.
string text = GetAttributeText(inXmlNode, "name");
if (string.IsNullOrEmpty(text))
text = (inXmlNode.OuterXml).Trim();
if (inTreeNode.Text != text)
inTreeNode.Text = text;
inTreeNode.Nodes.Clear();
}
}
And the result looks like
(The first "namespace" node in your XML has an empty name, which is why the full text is still showing.)
Update
Now that you have shown the UI you want to achieve, what you need to do is to:
- Skip the root XML node and loop over its children, adding top level tree nodes for each.
- Skip a top-level "namespace" node if it has no name and children.
Thus:
private void LoadTreeFromXmlDocument(XmlDocument dom)
{
try
{
// SECTION 2. Initialize the TreeView control.
treeView1.Nodes.Clear();
// SECTION 3. Populate the TreeView with the DOM nodes.
foreach (XmlNode node in dom.DocumentElement.ChildNodes)
{
if (node.Name == "namespace" && node.ChildNodes.Count == 0 && string.IsNullOrEmpty(GetAttributeText(node, "name")))
continue;
AddNode(treeView1.Nodes, node);
}
treeView1.ExpandAll();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
static string GetAttributeText(XmlNode inXmlNode, string name)
{
XmlAttribute attr = (inXmlNode.Attributes == null ? null : inXmlNode.Attributes[name]);
return attr == null ? null : attr.Value;
}
private void AddNode(TreeNodeCollection nodes, XmlNode inXmlNode)
{
if (inXmlNode.HasChildNodes)
{
string text = GetAttributeText(inXmlNode, "name");
if (string.IsNullOrEmpty(text))
text = inXmlNode.Name;
TreeNode newNode = nodes.Add(text);
XmlNodeList nodeList = inXmlNode.ChildNodes;
for (int i = 0; i <= nodeList.Count - 1; i++)
{
XmlNode xNode = inXmlNode.ChildNodes[i];
AddNode(newNode.Nodes, xNode);
}
}
else
{
// If the node has an attribute "name", use that. Otherwise display the entire text of the node.
string text = GetAttributeText(inXmlNode, "name");
if (string.IsNullOrEmpty(text))
text = (inXmlNode.OuterXml).Trim();
TreeNode newNode = nodes.Add(text);
}
}
which gives
Generate TreeView from XML file
use Attributes to get the value of siteMapNode like,
var attr = node.Attributes["title"];
Populating treeview from XML
You could try using this using the linq xml(System.Xml.Linq):
private TreeNode TNGroups(XElement xml)
{
TreeNode node = new TreeNode();
foreach (XElement group in xml.Descendants("Group"))
{
TreeNode tnGroup = new TreeNode(group.Element("GroupName").Value);
node.Nodes.Add(tnGroup);
foreach (XElement subgroup in group.Elements("SubGroup"))
{
TreeNode tnSubGroup = new TreeNode(subgroup.Element("SubGroupName").Value);
tnGroup.Nodes.Add(tnSubGroup);
}
}
return node;
}
You would call it like this myTreeView.Nodes.Add(TNGroups(groupsXML))
.
To load you XML into an element just use XElement.Load
.
Treeview to XML with proper indentation c#
public void exportToXml(TreeView tv, string filename)
{
sr = new StreamWriter(filename, false, System.Text.Encoding.UTF8);
//Write the header
sr.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
//Write our root node
sr.WriteLine("<" + treeView1.Nodes[0].Text + ">");
foreach (TreeNode node in tv.Nodes)
{
int depth = 1;
saveNode(node.Nodes, depth);
}
//Close the root node
sr.WriteLine("</" + treeView1.Nodes[0].Text + ">");
sr.Close();
}
private void saveNode(TreeNodeCollection tnc, int depth)
{
foreach (TreeNode node in tnc)
{
for(int i =0; i<depth;i++)
{
sr.Write("\t");
}
if (node.Nodes.Count > 0)
{
sr.WriteLine("<" + node.Text + ">");
saveNode(node.Nodes, depth + 1);
for(int i =0; i<depth;i++)
{
sr.Write("\t");
}
sr.WriteLine("</" + node.Text.Split()[0] + ">");
}
else //No child nodes, so we just write the text
sr.WriteLine("<" + node.Text + "/>");
}
}
if you are going to use your code you need a variable to count how deep you are in recursion and use that number of tabs
Save TreeView as xml with attributes and elements
How are you filling the TreeView? Are you actually giving the object keys or just titles? Try replacing the two occurences of "node.Name" with "node.Text" (and, for reasons of code sanity, refactoring the multiple checks for Count > 0):
EDIT: ok, try this:
private void SaveNodes(TreeNodeCollection nodesCollection, XmlWriter textWriter)
{
foreach (var node in nodesCollection.OfType<TreeNode>().Where(x => x.Nodes.Count == 0))
textWriter.WriteAttributeString(node.Name, "Attribute value");
foreach (var node in nodesCollection.OfType<TreeNode>().Where(x => x.Nodes.Count > 0))
{
textWriter.WriteStartElement(node.Name);
SaveNodes(node.Nodes, textWriter);
textWriter.WriteEndElement();
}
}
Related Topics
Cs0120: an Object Reference Is Required For the Nonstatic Field, Method, or Property 'Foo'
Encrypt and Decrypt a String in C#
Performance Differences Between Debug and Release Builds
Creating a Byte Array from a Stream
Understanding Garbage Collection in .Net
How to Enable Assembly Bind Failure Logging (Fusion) in .Net
Linq'S Distinct() on a Particular Property
How to Call Asynchronous Method from Synchronous Method in C#
What Is the Correct Way to Create a Single-Instance Wpf Application
What Are the Differences Between a Multidimensional Array and an Array of Arrays in C#
Fastest Way of Inserting in Entity Framework
Collection Was Modified; Enumeration Operation May Not Execute
Is There a Constraint That Restricts My Generic Method to Numeric Types
Convert a String to an Enum in C#
Do Event Handlers Stop Garbage Collection from Occurring
Getting Attributes of Enum'S Value