I'm trying to import an XML file of nodes into the same node structure in a TreeView using C#. I have found a lot of example that use a single node structure, but have had a lot of issues traversing the XML file and populating the TreeView with it. This is a sample of the XML file:
<?xml version="1.0"?>
<xmlRoot>
<ProductGroup>
<Group>
<GroupName>Soda</GroupName>
<Classifications>
<Classification>
<ClassificationName>Regular</ClassificationName>
<Containers>
<Container>
<ContainerType>Can</ContainerType>
<ContainerName>SmallCan</ContainerName>
</Container>
<Container>
<ContainerType>bottle</ContainerType>
<ContainerName>SmallBottle</ContainerName>
</Container>
</Containers>
</Classification>
<Classification>
<ClassificationName>Diet</ClassificationName>
<Containers>
<Container>
<ContainerType>Can</ContainerType>
<ContainerName>SmallCan</ContainerName>
</Container>
</Containers>
</Classification>
</Classifications>
</Group>
<Group>
<GroupName>Water</GroupName>
<Classifications>
<Classification>
<ClassificationName>Regular</ClassificationName>
<Containers>
<Container>
<ContainerType>Bottle</ContainerType>
<ContainerName>EcoBottle</ContainerName>
</Container>
</Containers>
</Classification>
</Classifications>
</Group>
</ProductGroup>
</xmlRoot>
I've tried using something like this:
treProducts.Nodes.Clear();
XDocument xdoc = XDocument.Load("ProductDocument.xml");
foreach (XElement groupElement in xdoc.Descendants("Group"))
{
treProducts.Nodes.Add(groupElement.Element("GroupName").Value);
treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("GroupName").Value];
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("ClassificationName").Value];
foreach (XElement ContainerElement in groupElement.Descendants("Container"))
{
treProducts.SelectedNode.Nodes.Add(ContainerElement.Element("ContainerName").Value);
}
}
}
I'm trying to get the tree to show:
Soda
Regular
SmallCan
SmallBottle
Diet
SmallCan
Water
Regular
EcoBottle
...but the tree is only showing Soda and it seems to skip the rest, except if I comment out the nested foreach statements, it will show Soda and Water.
There's something wrong with the syntax I'm using and I'm wondering if someone who understands Linq better can help see where the code is wrong.
You are using the wrong variable in your loop over the Classification Elements. Replace groupElement with ClassificationElement inside the loop.
Change:
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
// groupElement.Element("ClassificationName") is null:
treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
...
}
to
foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
treProducts.SelectedNode.Nodes.Add(ClassificationElement.Element("ClassificationName").Value);
...
}
I suggest recursion
void AddNodes(XElement parentElement, TreeNode parent = null)
{
Queue<XElement> queue = new Queue<XElement>(parentElement.Elements());
while (queue.Count > 0)
{
TreeNode child = parent;
XElement element = queue.Dequeue();
if (!element.HasElements)
{
string value = element.Value;
element = (XElement)element.NextNode;
if (null != element && !element.HasElements)
value = element.Value;
if (null == parent)
treeView1.Nodes.Add(child = new TreeNode(value));
else
parent.Nodes.Add(child = new TreeNode(value));
child.Expand();
element = queue.Dequeue();
}
AddNodes(element, child);
}
}
AddNodes(XElement.Load("ProductDocument.xml"));
Note: dbc's answer is probably better if your XML structure is likely to change, but if you are given it as it currently stands, and it won't change - then this will slurp it right into the tree quickly, without a lot of overhead.
The reason this is complicated is that your tree node hierarchy does not correspond 1-1 to your XML hierarchy. In situations like this, I suggest introducing intermediate classes to provide a view into the base XML model data. In WPF these classes would be the View Model, but the windows forms TreeView doesn't support data binding. Despite this, the abstraction of a view model is useful here.
First, some basic view model interfaces and classes to tie together TreeNode and XElement hierarchies:
public interface ITreeNodeViewModel
{
string Name { get; }
string Text { get; }
object Tag { get; }
object Model { get; }
IEnumerable<ITreeNodeViewModel> Children { get; }
}
public abstract class TreeNodeViewModel<T> : ITreeNodeViewModel
{
readonly T model;
public TreeNodeViewModel(T model)
{
this.model = model;
}
public T Model { get { return model; } }
#region ITreeNodeProxy Members
public abstract string Name { get; }
public abstract string Text { get; }
public virtual object Tag { get { return this; } }
public abstract IEnumerable<ITreeNodeViewModel> Children { get; }
#endregion
#region ITreeNodeViewModel Members
object ITreeNodeViewModel.Model
{
get { return Model; }
}
#endregion
}
public abstract class XElementTreeNodeViewModel : TreeNodeViewModel<XElement>
{
public XElementTreeNodeViewModel(XElement node) : base(node) {
if (node == null)
throw new ArgumentNullException();
}
public XNamespace Namespace { get { return Model.Name.Namespace; } }
public override string Name
{
get { return Model.Name.ToString(); }
}
}
Next, a couple extension classes:
public static class TreeViewExtensions
{
public static void PopulateNodes(this TreeView treeView, IEnumerable<ITreeNodeViewModel> viewNodes)
{
treeView.BeginUpdate();
try
{
treeView.Nodes.PopulateNodes(viewNodes);
}
finally
{
treeView.EndUpdate();
}
}
public static void PopulateNodes(this TreeNodeCollection nodes, IEnumerable<ITreeNodeViewModel> viewNodes)
{
nodes.Clear();
if (viewNodes == null)
return;
foreach (var viewNode in viewNodes)
{
var name = viewNode.Name;
var text = viewNode.Text;
if (string.IsNullOrEmpty(text))
text = name;
var node = new TreeNode { Name = name, Text = text, Tag = viewNode.Tag };
nodes.Add(node);
PopulateNodes(node.Nodes, viewNode.Children);
node.Expand();
}
}
}
public static class XObjectExtensions
{
public static string TextValue(this XContainer node)
{
if (node == null)
return null;
//return string.Concat(node.Nodes().OfType<XText>().Select(tx => tx.Value)); c# 4.0
return node.Nodes().OfType<XText>().Select(tx => tx.Value).Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
}
public static IEnumerable<XElement> Elements(this IEnumerable<XElement> elements, XName name)
{
return elements.SelectMany(el => el.Elements(name));
}
}
Next, view models for the three levels of your tree:
class ContainerViewModel : XElementTreeNodeViewModel
{
public ContainerViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ContainerName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get { return Enumerable.Empty<ITreeNodeViewModel>(); }
}
}
class ClassificationViewModel : XElementTreeNodeViewModel
{
public ClassificationViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "ClassificationName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get
{
return Model.Elements(Namespace + "Containers").Elements<XElement>(Namespace + "Container").Select(xn => (ITreeNodeViewModel)new ContainerViewModel(xn));
}
}
}
class GroupViewModel : XElementTreeNodeViewModel
{
public GroupViewModel(XElement node) : base(node) { }
public override string Text
{
get
{
return Model.Element(Namespace + "GroupName").TextValue();
}
}
public override IEnumerable<ITreeNodeViewModel> Children
{
get
{
return Model.Elements(Namespace + "Classifications").Elements<XElement>(Namespace + "Classification").Select(xn => (ITreeNodeViewModel)new ClassificationViewModel(xn));
}
}
}
Now, building your tree becomes quite simple:
var xdoc = XDocument.Load("ProductDocument.xml");
var ns = xdoc.Root.Name.Namespace;
treeView1.PopulateNodes(xdoc.Root.Elements(ns + "ProductGroup").Elements(ns + "Group").Select(xn => (ITreeNodeViewModel)new GroupViewModel(xn)));
And the result:
Later, if you wish to add editing functionality to your tree, you can add the appropriate methods to ITreeNodeViewModel -- for instance, a setter method for Text. Since the ITreeNodeViewModel has saved itself in the TreeNode.Tag the appropriate methods will be available.
Related
So, this seems very tricky for some reason. I think I have tried every solution I can think of but none seem to be coming close to the result I require.
Say I have a custom XML. It is unstructured but has a common "schema" i.e:
<holder id="1">
<name datatype="int">First Record</name>
<cost datatype="double">100.24</cost>
<service datatype="string" serviceID="12345">A custom service</service>
</holder>
<holder id="2">
<name datatype="int">secondRecord</name>
<cost datatype="double">3000.24</cost>
<service datatype="string" serviceID="9876">A custom service 2</service>
</holder>
all I want to do is go through all of this and parse it into my custom class files.
I have a custom Xml Node (properties: Name (i.e "Cost", value i.e "100.24")
I have a collection of attributes (key/valuepair) in the custom xml Node class
Any ideas how to parse this xml?
Well, you could go for a simple Tree system to read your Xml, it doesn't have to know anything about the XML, can set attributes with an Item indexer, or by using some methods, and using the XmlReader you can go through the node quite fast yourself
However, from my comments you could also see that to me, your problem wasn't completely clear (what you have tried, what is failing, what should be the end result)
I have set up some classes that create a Tree, can register some properties (ITreeNode), have a parent and have a child (i am guessing, as this part of your post is also missing, that it is something a like your custom XmlNode)
Basically, this is the custom XmlParser
public class TreeNodeParser
{
public T ReadNode<T>(T parent, XmlReader reader)
where T: class, ITreeNode, new()
{
if (reader.NodeType == XmlNodeType.Whitespace)
{
return null;
}
T subNode = new T();
while (reader.NodeType != XmlNodeType.Element && reader.Read())
{
}
subNode.Tag = reader.Name;
subNode.Parent = parent;
if (reader.AttributeCount > 0)
{
reader.MoveToFirstAttribute();
do
{
subNode.SetAttribute(reader.Name, reader.Value);
} while (reader.MoveToNextAttribute());
}
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
{
if (string.Equals(subNode.Tag, reader.Name))
{
break;
}
continue;
}
if (reader.NodeType == XmlNodeType.Element)
{
T child = ReadNode(subNode, reader);
if (child != null)
{
subNode.Children.Add(child);
}
continue;
}
if (reader.NodeType == XmlNodeType.Text)
{
subNode.ValueAsString = reader.Value;
continue;
}
}
return subNode;
}
public T FromStream<T>(Stream stream)
where T : class, ITreeNode, new()
{
T result = default(T);
using (XmlReader reader = new XmlTextReader(stream))
{
while (reader.Read())
{
T child = ReadNode(result, reader);
if (child != null)
{
result = child;
}
}
}
return result;
}
}
You can hand it a stream, which it well read through, reading all elements and all attributes (and ignoring whitespace elements). As an xml is in essence a Tree, you get the rootNode back (if any)
As an example, this is the test main program (console)
static void Main(string[] args)
{
TreeNodeParser nodeParser = new TreeNodeParser();
using (Stream readStream = new FileStream(Path.Combine(Environment.CurrentDirectory, "TestFile.xml"), FileMode.Open, FileAccess.Read))
{
var node = nodeParser.FromStream<Node>(readStream);
Console.WriteLine("{0}", node.ToString());
}
Console.ReadLine();
}
The implement TreeNode had the following ToString() override implemented (so you can see that it's not just reading the value as a string ;)):
public override string ToString()
{
string content = "", attributes = "";
foreach (var child in Children)
{
content += child;
}
if (!string.IsNullOrWhiteSpace(ValueAsString))
{
content += ValueAsString;
}
foreach (var entry in properties)
{
attributes += string.Format(" {0}=\"{1}\"", entry.Key, entry.Value);
}
if (string.IsNullOrWhiteSpace(content))
{
return string.Format("<{0}{2} />", this.Tag, content, attributes);
}
return string.Format("<{0}{2}>{1}</{0}>", this.Tag, content, attributes);
}
}
which has as a result
for this to work, i had to change your basic Xml to the following structure (at least one root element)
<?xml version="1.0" encoding="utf-8"?>
<ValueList>
<holder id="1">
<name datatype="int">First Record</name>
<cost datatype="double">100.24</cost>
<service datatype="string" serviceID="12345">A custom service</service>
</holder>
<holder id="2">
<name datatype="int">secondRecord</name>
<cost datatype="double">3000.24</cost>
<service datatype="string" serviceID="9876">A custom service 2</service>
</holder>
</ValueList>
And a possible interface for the ITreeNode could be something like that (though that shouldn't be necessary with your custom XmlNode, so that part you could refactor in the first piece of code)
public interface IPropertyHolder
{
string this[string attributeName] { get; }
void SetAttribute(string attribute, string value);
string GetAttribute(string attribute);
}
public interface INodeParent<T>
{
IList<T> Children { get; }
}
public interface INodeChild<T>
{
T Parent { get; set; }
}
public interface INode
{
string Tag { get; set; }
string ValueAsString { get; set; }
object Value { get; }
}
public interface ITreeNode : INode, IPropertyHolder, INodeParent<ITreeNode>, INodeChild<ITreeNode>
{
}
I have a XML document provided by client applications to my C# application. This is how a client sends the XML file:
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<parentId>2380983</parentId>
<!-- more elements -->
</SomeAccount>
And a C# class that supports the XML deserialization:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
But there are some clients whose system send the XML in this way (note the upper case in LeParentId):
<?xml version="1.0" encoding="utf-8"?>
<SomeAccount>
<LeParentId>2380983</LeParentId>
<!-- similar for the other elements -->
</SomeAccount>
How can I make this field (and others) to support both XML names parentId and LeParentId?
This is the method I'm currently using for XML deserialization:
public sealed class XmlSerializationUtil
{
public static T Deserialize<T>(string xml)
{
if (xml == null)
return default(T);
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader stringReader = new StringReader(xml);
return (T)serializer.Deserialize(stringReader);
}
}
I tried to add [XmlElement] twice in the field, one per element name, but that didn't work.
Take 2 - let's implement this ourselves using the unknown element handling event (see the comments below for some limitations though):
public class XmlSynonymDeserializer : XmlSerializer
{
public class SynonymsAttribute : Attribute
{
public readonly ISet<string> Names;
public SynonymsAttribute(params string[] names)
{
this.Names = new HashSet<string>(names);
}
public static MemberInfo GetMember(object obj, string name)
{
Type type = obj.GetType();
var result = type.GetProperty(name);
if (result != null)
return result;
foreach (MemberInfo member in type.GetProperties().Cast<MemberInfo>().Union(type.GetFields()))
foreach (var attr in member.GetCustomAttributes(typeof(SynonymsAttribute), true))
if (attr is SynonymsAttribute && ((SynonymsAttribute)attr).Names.Contains(name))
return member;
return null;
}
}
public XmlSynonymDeserializer(Type type)
: base(type)
{
this.UnknownElement += this.SynonymHandler;
}
public XmlSynonymDeserializer(Type type, XmlRootAttribute root)
: base(type, root)
{
this.UnknownElement += this.SynonymHandler;
}
protected void SynonymHandler(object sender, XmlElementEventArgs e)
{
var member = SynonymsAttribute.GetMember(e.ObjectBeingDeserialized, e.Element.Name);
Type memberType;
if (member != null && member is FieldInfo)
memberType = ((FieldInfo)member).FieldType;
else if (member != null && member is PropertyInfo)
memberType = ((PropertyInfo)member).PropertyType;
else
return;
if (member != null)
{
object value;
XmlSynonymDeserializer serializer = new XmlSynonymDeserializer(memberType, new XmlRootAttribute(e.Element.Name));
using (System.IO.StringReader reader = new System.IO.StringReader(e.Element.OuterXml))
value = serializer.Deserialize(reader);
if (member is FieldInfo)
((FieldInfo)member).SetValue(e.ObjectBeingDeserialized, value);
else if (member is PropertyInfo)
((PropertyInfo)member).SetValue(e.ObjectBeingDeserialized, value);
}
}
}
And now the actual code of the class would be:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
[XmlSynonymDeserializer.Synonyms("LeParentId", "AnotherGreatName")]
public long ParentId { get; set; }
//rest of fields...
}
To deserialize, simply use XmlSynonymDeserializer instead of the regular XmlSerializer. This should work for most of the basic needs.
Known limitations:
This implementation supports only elements with multiple names; extending it for attributes should be trivial
Support for handling of properties/fields in cases where the entities inherit from one another is not tested
This implementation does not check for programming bugs (having the attribute on read-only/constant field/properties, multiple members with the same synonyms and so on)
I know this is an old post, but maybe this will help anyone else having the same problem.
What you could use for this problem is XmlChoiceIdentifier.
[XmlRoot]
public class SomeAccount
{
[XmlIgnore]
public ItemChoiceType EnumType;
[XmlChoiceIdentifier("EnumType")]
[XmlElement("LeParentId")]
[XmlElement("parentId")]
public long ParentId { get; set; }
//rest of fields...
}
[XmlType(IncludeInSchema = false)]
public enum ItemChoiceType
{
LeParentId,
parentId
}
Now if you have a new xml version and a new XmlElement name you just add that name to the ItemChoiceType enum and a new XmlElement to the property.
If you need only one more name, here is a quick (and rather ugly) solution that we deployed in several cases in my work when we had only to read XMLs (this will be problematic for serializing back to an XML), because it's the simplest and easiest to understand:
[XmlRoot]
public class SomeAccount
{
[XmlElement("parentId")]
public long ParentId { get; set; }
[XmlElement("LeParentId")]
public long LeParentId { get { return this.ParentId; } set { this.ParentId = value; } }
//rest of fields...
}
I have multiple XML templates and I would like to populate them using LINQ to XML, but I wasn't sure how to get started, meaning how to correctly read in the XML file (Best method) and populate attributes and Nodes (InnerText). Also, how do create new elements and attributes in the existing template as well remove/update existing ones? Here is an example of a template:
<Person age="age">
<FirstName></FirstName>
<LastName></LastName>
<Children>
<Name></Name>
</Children>
</Person>
In the abvoe, the age attribute is a placeholder, FirstName, LastName and Name under Children are placeholders. Let's also assume that I either want to remove Names under children or add names as well. Also lets assume I want to add a DOB element inside Person with day, month, year attributes?
Here's an example. You build a class for each template. I'm using this library extension class (XElementExtensions.cs) from: https://github.com/ChuckSavage/XmlLib/
public class Person
{
XElement self;
public Person(XElement person) { self = person; }
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); } // true set as attribute
}
public string FirstName
{
get { return self.Get("FirstName", string.Empty); }
set { self.Set("FirstName", value, false); } // false set as child node
}
public string LastName
{
get { return self.Get("LastName", string.Empty); }
set { self.Set("LastName", value, false); }
}
public Children Children
{
get { return new Children(self.GetElement("Children")); }
}
}
public class Children
{
XElement self;
public Children(XElement children) { self = children; }
public Child this[string name]
{
get
{
return self.Elements("Name")
.Select(x => new Child(x))
.FirstOrDefault(c => c.Name == name);
}
}
public Child this[int index]
{
get { return new Child(self.Elements("Name").ElementAt(index)); }
}
public void Add(string name)
{
Child child = this[name];
if(null == child)
{
child = new Child(new XElement("Name"));
child.Name = name;
self.Add(child.self);
}
else
throw new ArgumentException("Child with name: "+ name +" already exists!");
}
}
public class Child
{
internal XElement self;
public Child(XElement child) { self = child; }
// You can have an Age/DOB for the child as well, or remove these two properties.
// Age should be a value of Now minus DOB in years.
public int Age { get { return DateTime.Now.Year - DOB.Year; } }
// DOB is an attribute as self.Value in Name erases all child nodes when set.
public DateTime DOB
{
get { return self.Get("DOB", DateTime.MinValue ); } // choose a default date that works for you
set { self.Set("DOB", value, true); }
}
public string Name
{
get { return self.Value }
set { self.Value = value; }
}
}
If you want to specify null as the default for any Get() then you need to specify the type to Get with Get<Type>("node", null)
I would suggest to take a look at the LINQ to XML and its XDocument class. It makes navigation and editing of an xml document very easy.
Look at this site for samples of linq to xml
I am having problems serializing a cdata section using c#
I need to serialize XmlCDataSection object property as the innertext of the element.
The result I am looking for is this:
<Test value2="Another Test">
<![CDATA[<p>hello world</p>]]>
</Test>
To produce this, I am using this object:
public class Test
{
[System.Xml.Serialization.XmlText()]
public XmlCDataSection value { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string value2 { get; set; }
}
When using the xmltext annotation on the value property the following error is thrown.
System.InvalidOperationException:
There was an error reflecting property
'value'. --->
System.InvalidOperationException:
Cannot serialize member 'value' of
type System.Xml.XmlCDataSection.
XmlAttribute/XmlText cannot be used to
encode complex types
If I comment out the annotation, the serialization will work but the cdata section is placed into a value element which is no good for what I am trying to do:
<Test value2="Another Test">
<value><![CDATA[<p>hello world</p>]]></value>
</Test>
Can anybody point me in the right direction to getting this to work.
Thanks, Adam
Thanks Richard, only now had chance to get back to this. I think I have resolved the problem by using your suggestion. I have created a CDataField object using the following:
public class CDataField : IXmlSerializable
{
private string elementName;
private string elementValue;
public CDataField(string elementName, string elementValue)
{
this.elementName = elementName;
this.elementValue = elementValue;
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
w.WriteStartElement(this.elementName);
w.WriteCData(this.elementValue);
w.WriteEndElement();
}
public void ReadXml(XmlReader r)
{
throw new NotImplementedException("This method has not been implemented");
}
}
The way Test is defined, your data is a CData object. So the serialisation system is trying to preserve the CData object.
But you want to serialise some text data as a CData section.
So first, the type of Test.value should be String.
You then need to control how that field is serialised, but there does not appear to be any inbuilt method or attribute to control how strings are serialised (as string, maybe with entities for reserved characters, or as CDATA). (Since, from an XML infoset perspective all of these are the same, this is not surprising.)
You can of course implemented IXmlSerializable and just code the serialisation of the Test type yourself which gives you complete control.
This basically shorter version of Jack answer with better error messages:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] ContentAsCData
{
get => new[] { new XmlDocument().CreateCDataSection(Content) };
set => Content = value?.Cast<XmlCDataSection>()?.Single()?.Data;
}
Just found an alternative from here:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] CDataContent
{
get
{
var dummy = new XmlDocument();
return new XmlNode[] {dummy.CreateCDataSection(Content)};
}
set
{
if (value == null)
{
Content = null;
return;
}
if (value.Length != 1)
{
throw new InvalidOperationException(
String.Format(
"Invalid array length {0}", value.Length));
}
var node0 = value[0];
var cdata = node0 as XmlCDataSection;
if (cdata == null)
{
throw new InvalidOperationException(
String.Format(
"Invalid node type {0}", node0.NodeType));
}
Content = cdata.Data;
}
}
}
I had very same problem as Adam. However this Answer does not helped me at 100% :) but gives me a clue. So I'va created a code like below. It generates XML like this:
<Actions>
<Action Type="reset">
<![CDATA[
<dbname>longcall</dbname>
<ontimeout>
<url>http://[IPPS_ADDRESS]/</url>
<timeout>10</timeout>
</ontimeout>
]]>
</Action>
<Action Type="load">
<![CDATA[
<dbname>longcall</dbname>
]]>
</Action>
</Actions>
Code:
public class ActionsCDataField : IXmlSerializable
{
public List<Action> Actions { get; set; }
public ActionsCDataField()
{
Actions = new List<Action>();
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
foreach (var item in Actions)
{
w.WriteStartElement("Action");
w.WriteAttributeString("Type", item.Type);
w.WriteCData(item.InnerText);
w.WriteEndElement();
w.WriteString("\r\n");
}
}
public void ReadXml(XmlReader r)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(r);
XmlNodeList nodes = xDoc.GetElementsByTagName("Action");
if (nodes != null && nodes.Count > 0)
{
foreach (XmlElement node in nodes)
{
Action a = new Action();
a.Type = node.GetAttribute("Type");
a.InnerText = node.InnerXml;
if (a.InnerText != null && a.InnerText.StartsWith("<![CDATA[") && a.InnerText.EndsWith("]]>"))
a.InnerText = a.InnerText.Substring("<![CDATA[".Length, a.InnerText.Length - "<![CDATA[]]>".Length);
Actions.Add(a);
}
}
}
}
public class Action
{
public String Type { get; set; }
public String InnerText { get; set; }
}
I have a N-Ary non sorted in any way tree and each node can have 0-N children. Given the data structure below, how can I fill the tree view assuming you have an array of TermNodes and that array is the first level of the TreeView? I have not been able to come up with a recursive way to do this.
class TermNode
{
public string Name;
public string Definition;
public List<TermNode> Children
}
Here is a bit of code to get you started with the recursion. It's not tested (I can't right now), but you should get the idea:
public static void BuildTreeView(TreeNodeCollection Parent, List<TermNode> TermNodeList)
{
foreach (TermNode n in TermNodeList)
{
TreeNode CurrentNode = Parent.Add(n.Name);
// no need to recurse on empty list
if (n.List.Count > 0) BuildTreeView(CurrentNode.Nodes, n.List);
}
}
// initial call
List<TermNode> AllTermNodes = /* all your nodes at root level */;
BuildTreeView(treeView1.Nodes, AllTermNodes);
Just took out Generics for a spin.. Worked nicely. Worth a look at...
public interface INode<T>
{
List<T> Children { get; }
}
class TermNode:INode<TermNode>
{
public string Name;
public string Definition;
public List<TermNode> Children { get; set; }
public TermNode()
{
this.Children = new List<TermNode>();
}
}
public class TreeBuilder<T> where T : INode<T>
{
public Func<T, TreeNode> obCreateNodeFunc;
public void AddNode(TreeView obTreeView, T obNodeToAdd, TreeNode obParentNodeIfAny)
{
TreeNodeCollection obNodes;
if (obParentNodeIfAny == null)
{
obNodes = obTreeView.Nodes;
}
else
{
obNodes = obParentNodeIfAny.Nodes;
}
int iNewNodeIndex = obNodes.Add(obCreateNodeFunc(obNodeToAdd));
TreeNode obNewNode = obNodes[iNewNodeIndex];
foreach (T child in obNodeToAdd.Children)
{
AddNode(obTreeView, child, obNewNode);
}
}
}
// calling code - Some class
static TreeNode GetTreeNodeFor(TermNode t)
{
return new TreeNode(t.Name); // or any logic that returns corr TreeNode for T
}
void Main()...
{
TermNode[] arrNodesList;
// populate list with nodes
TreeBuilder<TermNode> tb = new TreeBuilder<TermNode>();
tb.obCreateNodeFunc = GetTreeNodeFor;
foreach (TermNode obNode in arrNodesList)
{
tb.AddNode(treeView, obNode, null);
}
}
Thanks All I was getting confused because I did not realize that for a given TreeNode tn, tn.Nodes.Add would return the added TreeNode
Once you know that the solution is straight forward like so
private void /*TreeNode*/ RecursiveAdd(OntologyNode on, TreeNode tn)
{
if (on.Children.Count == 0)
{
return;
}
foreach (OntologyNode child in on.Children)
{
TreeNode tCur = tn.Nodes.Add(child.Name);
tCur.Tag = child;//optional for some selected node events
RecursiveAdd(child, tCur);
}
}
and to start of the recursive call
foreach( OntologyNode on in Nodes )
{
if (on.IsTopLevelNode == true)// internal not pertinent to this code snippet
{
TreeNode tn = tvOntoBrowser.Nodes.Add(on.Name);
tn.Tag = on;
if (on.Children.Count > 0)
{
RecursiveAdd(on, tn);
}
}
}