Recursion, Parsing Xml File With Attributes into Treeview C#

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

Sample Image
(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:

  1. Skip the root XML node and loop over its children, adding top level tree nodes for each.
  2. 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

Sample Image

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



Leave a reply



Submit