Рекурсия, парсинг XML-файла с атрибутами в древовидную структуру C#
В настоящее время я работаю над проектом, в котором приложение будет использовать XML-файлы и отображать их в виде дерева в C#. Я использую Visual Studio 10 для написания этого кода.
Я не могу ограничить количество раз, когда атрибуты отображаются. Я использовал цикл foreach, чтобы пройтись по каждому из имеющихся у него атрибутов и отобразить его, но он отображает атрибуты один раз для каждого дочернего узла, который он имеет под узлом. Как я могу изменить этот код, чтобы атрибуты отображались только один раз?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
namespace xmlToTreeview
{
public partial class Form1 : Form
{
string samplePath = Application.StartupPath + @"\\sample.xml";
public Form1()
{
InitializeComponent();
DisplayTreeView(samplePath);
}
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();
treeView1.Nodes.Add(new TreeNode(dom.DocumentElement.Name));
TreeNode tNode = new TreeNode();
tNode = treeView1.Nodes[0];
// SECTION 3. Populate the TreeView with the DOM nodes.
AddNode(dom.DocumentElement, tNode);
}
catch (XmlException xmlEx)
{
MessageBox.Show(xmlEx.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void AddNode(XmlNode inXmlNode, TreeNode inTreeNode)
{
XmlNode xNode;
TreeNode tNode;
XmlNodeList nodeList;
int i;
// Loop through the XML nodes until the leaf is reached.
// Add the nodes to the TreeView during the looping process.
if (inXmlNode.HasChildNodes)
{
nodeList = inXmlNode.ChildNodes;
for (i = 0; i <= nodeList.Count - 1; i++)
{
xNode = inXmlNode.ChildNodes[i];
inTreeNode.Nodes.Add(new TreeNode(xNode.Name));
tNode = inTreeNode.Nodes[i];
//Check if the XmlNode has attributes
if (inXmlNode.Attributes.Count != 0)
{
foreach (XmlAttribute att in inXmlNode.Attributes)
{
inTreeNode.Text = inTreeNode.Text + " " + att.Name + ": " + att.Value;
}
}
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();
}
}
}
А вот пример моего xml
<?xml version="1.0" encoding="utf-8"?>
<DataConfiguration xmln="abcefg12345" xmlns:xsi="12345abcefg" xsi:schemaLocation="12345abcefg12345abcefg">
<Hosts>
<Sites>
<Site Name="ss">
<Host Id="aa">
<Address Host="www.www.com"> </Address>
</Host>
<Host Id="ee">
<Address Host="www.www.com"> </Address>
</Host>
<Host Id="dd">
<Address Host="www.www.com"> </Address>
</Host>
<Host Id="pp">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com/"/>
</Host>
<Host Id="ss">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="561">
<Address Host="www.www.com"> </Address>
</Host>
</Site>
<Site Name="hihi">
<Host Id="cc">
<Address Host="www.www.com"> </Address>
</Host>
<Host Id="sdD">
<Address Host="www.www.com"> </Address>
</Host>
<Host Id="8uj">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="222">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
</Site>
</Sites>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
<Host Id="hhh">
<Address Scheme="ppp" Host="www.www.com" Path="www.www.com"/>
</Host>
</Hosts>
<DataPools>
<DataPool Id="sss" default="sure">
<DataGroup Id="sss" Parent="aaa" UserCanSelectHost="sure" >
<HostId Parent="hhhh">I'm breaking here</HostId>
<DataSources>
<empty/>
</DataSources>
</DataGroup>
<DataGroup Id="ccc" UserCanSelectHost="whynot" >
<HostId>God I'm breaking here again, i hope you can fix me</HostId>
<DataSources>
<empty/>
</DataSources>
</DataGroup>
<DataGroup Id="sss" UserCanSelectHost="yessure" >
<HostId>cry face</HostId>
<webfg displaygroup="sss" provider="sss" id="ccc" principal="ccc" nioarc="ccc" nap="ccc" group="ccc">
</webfg>
<nhood port="1234"/>
<ServerNames>
<!-- insert comment -->
<!-- insert comment -->
<!-- insert comment -->
<ServerName>myname</ServerName>
<ServerName>yourname</ServerName>
</ServerNames>
<!-- insert comment -->
<Implementations>
<Implementation>
<Name>yourname</Name>
<Type>typeme</Type>
<Assembly>visionme</Assembly>
<Path>ohno</Path>
</Implementation>
</Implementations>-->
<cfgman port="ccc" />
<webservice provider="ccc" />
<webservice provider="ccc" />
<webservice provider="ccc" />
<parameters>
<useeventpush value="ccc"/>
</parameters>
<webservice provider="ccc" />
<pollingFrequency value="1000"/>
</DataGroup>
</DataPool>
<DataGroup Id="ccc " UserCanSelectHost="ccc" >
<DataGroup Id="ccc " UserCanSelectHost="ccc" >
<HostId>idk</HostId>
<DataSources>
<empty/>
</DataSources>
</DataGroup>
<DataGroup Id="ccc " UserCanSelectHost="ccc" >
<HostId>idk</HostId>
<DataSources>
<empty/>
</DataSources>
</DataGroup>
<DataGroup Id="default" UserCanSelectHost="true" >
<HostId>idk</HostId>
</DataGroup>
</DataGroup>
</DataPools>
</DataConfiguration>
2 ответа
Вам нужно переместить цикл через атрибуты из цикла через дочерние узлы:
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();
}
Обновить
Если вы хотите отфильтровать атрибуты пространства имен, вы можете добавить методы расширения:
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:");
}
}
Затем используйте его, чтобы пропустить нежелательные XmlAttribute
экземпляров. Вы также можете явно установить текст всех узлов типа XmlElement
быть данными name + attribute, а не только теми элементами с дочерними элементами, используя OuterXml
только для текстовых узлов:
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();
}
Если вы действительно хотите отфильтровать только определения пространства имен по умолчанию, но оставить другие, вы можете сделать следующее:
// 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;
}
Между прочим, я не рекомендую делать это, поскольку следующее фактически означает то же самое, а именно элемент с локальным именем DataConfiguration
в пространстве имен http://somenamespace
:
<ss:DataConfiguration xmlns:ss="http://somenamespace"/>
<DataConfiguration xmlns="http://somenamespace"/>
Ваше дерево будет отображать пространство имен для первого элемента, но не для второго.
Обновление 2
Чтобы включить XmlDeclaration
в дереве измените цикл верхнего уровня на:
treeView1.Nodes.Clear();
foreach (XmlNode xNode in dom.ChildNodes)
{
var tNode = treeView1.Nodes[treeView1.Nodes.Add(new TreeNode(xNode.Name))];
AddNode(xNode, tNode);
}
Обновление 3
Поместите цикл, чтобы включить XmlDeclaration
зациклиться 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);
}
}
Попробуйте простой рекурсивный алгоритм
static void Main(string[] args)
{
string input =
"<a>" +
"<b>" +
"<c>val1</c>" +
"<d>val2</d>" +
"</b>" +
"<e>val3</e>" +
"<f>val4</f>" +
"</a>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(input);
RecursiveRead(doc.DocumentElement);
}
static string RecursiveRead(XmlNode node)
{
List<string> children = new List<string>();
bool done = false;
if (node.HasChildNodes)
{
foreach (XmlNode child in node)
{
children.Add(RecursiveRead(child));
}
Console.WriteLine("table : {0}; children : {1}", node.Name, string.Join(",", children.ToArray()));
//string cmd = "INSERT INTO " + second + " (" + first + ") VALUES ('" + xml.Value + "')";
//SqlCommand command = new SqlCommand(cmd, conn);
//command.ExecuteNonQuery();
}
else
{
return node.Value;
}
return node.Name;
}