Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

XmlNode

4.64/5 (8 votes)
15 May 2011CPOL9 min read 64K   758  
A technique to make working with XML easier

Introduction

This is a technique I devised recently because I got tired of writing similar code for working with similar XML files and having to retrofit new functionality into older applications as my needs evolve. It can also help to improve standardization of XML that you create -- for instance, always following a particular schema when writing a DataTable out to XML.

It assists with creating, parsing, and updating XML and can be used with sections of an XML document, whereas other techniques (e.g. serialization) may not be as flexible. I haven't tried to use it with a Microsoft-centric config file (I've never used one).

Background

I like XML, especially attributes, and I prefer to create my own configuration files -- for a variety of reasons, mostly because that's just the kind of guy I am. There are situations that are common to a number of applications and I feel that it is better to have a common configuration layout for such situations rather than to define a different one for each application. One such situation is the desire to persist the size and position of an application's Forms in the configuration when a Form closes and restore it when the Form is loaded again. Working with XML to do such things also requires checking to see whether or not the desired elements and values exist and often the parsing of string values to other datatypes.

Another facet of working with XML is that of creating the schema when it doesn't yet exist; constantly having to check for null and creating elements and attributes as required.

After writing the code to do this and then copying-and-pasting that code a few too many times, I decided that there had to be a better way.

Solution

The primary tasks I wanted to accomplish were to encapsulate ensuring that the desired elements and values exist in the XML before I try to access them and the parsing of values to the appropriate datatype so I can work with the XML in as type-safe a manner as I can.

Obviously, it doesn't take a rocket scientist to see that an XmlElement can be wrapped in a class with a property for each attribute. Consider the following elements:

XML
<Name />
<Name></Name>
<Name>InnerText</Name>

Every XmlNode has a name and some content (which may be an empty string). They are accessed by the Name and InnerText properties (at least when using the classes in .NET's System.Xml namespace, other frameworks may have other terms). Therefore, I defined the IXmlNode interface:

C#
public interface IXmlNode
{
    string NodeName { get ; }
    string InnerText { get ; set ; }
}

Note that the property here is NodeName to indicate that it is the name of the node; this could be thought of as the type of the XmlElement. In most cases, I also have a Name attribute to differentiate between nodes of the same type, so I extended that interface to:

C#
public interface INamedXmlNode : IXmlNode
{
    string Name { get ; set ; }
}

An example of an XmlElement that I use is:

XML
<Window Name="dlgEdit" Top="100" Left="100" Width="1000" 
Height="800" WindowState="Normal" />

What I want to do is wrap this XmlElement in a class so that I can easily access the attributes and content in a type-safe manner.

XmlNodeAttribute

I decided that I wanted to have an Attribute to mark which properties of a class should be backed by the XmlNode. Having the attribute provide a default value for missing XmlAttributes is also very handy so I derived it from DefaultValueAttribute.

C#
[System.AttributeUsageAttribute(System.AttributeTargets.Property , 
AllowMultiple=false , Inherited=false)]
public sealed partial class XmlNodeAttribute : System.ComponentModel.DefaultValueAttribute
{
    public XmlNodeAttribute
    (
    )
    : base
    (
        null
    )
    {
        return ;
    }

    public XmlNodeAttribute
    (
        object DefaultValue
    )
    : base
    (
        DefaultValue
    )
    {
        return ;
    }
}

Foreshadowing

Here is a snippet from WindowNode.cs (included in the zip file) that shows how properties can be written so that they are backed by the wrapped XmlNode:

C#
public sealed partial class WindowNode : PIEBALD.Types.NamedXmlNode
{
    [PIEBALD.Types.XmlNodeAttribute("")]
    public int
    Top
    {
        get
        {
            return ( this.GetValue<int> ( "Top" ) ) ;
        }

        set
        {
            this.SetValue<int> ( "Top" , value ) ;

            return ;
        }
    }

    [PIEBALD.Types.XmlNodeAttribute(System.Windows.Forms.FormWindowState.Normal)]
    public System.Windows.Forms.FormWindowState
    WindowState
    {
        get
        {
            return ( this.GetValue<System.Windows.Forms.FormWindowState> 
		( "WindowState" ) ) ;
        }

        set
        {
            this.SetValue<System.Windows.Forms.FormWindowState> 
		( "WindowState" , value ) ;

            return ;
        }
    }
}

The property is written in terms of the desired datatype and the underlying code handles the conversion to and from the strings in the wrapped XmlNode. The name of the property must match the name of the Attribute of the wrapped XmlNode.

XmlNode

XmlNode is the class that provides the basic functionality for derived classes. It holds references to the wrapped XmlNode, the XmlDocument, and any parsers it will need.

C#
public partial class XmlNode : IXmlNode
{
    protected readonly System.Xml.XmlDocument document ;
    protected readonly System.Xml.XmlNode     node     ;

    private readonly System.Collections.Generic.Dictionary
    <string,PIEBALD.Types.Parsomatic.ParseDelegate> dic =
        new System.Collections.Generic.Dictionary
        <string,PIEBALD.Types.Parsomatic.ParseDelegate>() ;
}

Constructor

There are a number of overloaded constructors, but I'll only discuss the primary one here, the others all call this one. The only required parameter is the XmlDocument. If the Node is null, the Document itself will be used. If the Path has a value, then it will be used to select the desired descendent element to wrap. Any elements specified by Path that do not exist in the document will be created.

C#
public XmlNode
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlNode     Node
,
    string                 Path
)
{
    if ( Document == null )
    {
        throw ( new System.ArgumentNullException 
	( "Document" , "You must supply an XmlDocument" ) ) ;
    }

    this.document = Document ;

    if ( Node == null )
    {
        Node = this.document ;
    }

    if ( !System.String.IsNullOrEmpty ( Path ) )
    {
        foreach
        (
            string name
        in
            Path.Split ( '/' )
        )
        {
            System.Xml.XmlNode temp = Node.SelectSingleNode ( name ) ;

            if ( temp == null )
            {
                temp = this.document.CreateElement ( name ) ;

                Node.AppendChild ( temp ) ;
            }

            Node = temp ;
        }
    }

    this.node = Node ;

    this.Initialize() ;

    return ;
}

XmlNodeDemo1.cs demonstrates this constructor:

C#
System.Xml.XmlDocument doc = new System.Xml.XmlDocument() ;

PIEBALD.Types.IXmlNode nod = new PIEBALD.Types.XmlNode
(
    doc
,
    null
,
    "XmlNodeDemo1/DemoNodes/Node"
) ;

nod.InnerText = "XmlNodeDemo1" ;

System.Console.WriteLine ( doc.OuterXml ) ;

The output is:

XML
<XmlNodeDemo1><DemoNodes><Node>XmlNodeDemo1</Node></DemoNodes></XmlNodeDemo1> 

XmlNodeDemo2.cs demonstrates a simple derived class with two properties; the output is:

XML
<XmlNodeDemo2><DemoNodes>
<Node DemoIntValue="" DemoBoolValue="False">
XmlNodeDemo2</Node></DemoNodes></XmlNodeDemo2>

The important thing to notice is that the XML and default values were created automatically, based on the given Path.

Initialize

The Initialize method iterates the properties of the class, looking for any with an XmlNodeAttribute. If a matching attribute doesn't yet exist in the XmlNode, then an attempt to create one with the default value is made. If there is no default value, then an Exception is thrown. A parser for each property will also be added to the dictionary of parsers.

C#
protected virtual void
Initialize
(
)
{
    foreach
    (
        System.Reflection.PropertyInfo pi
    in
        this.GetType().GetProperties
        (
            System.Reflection.BindingFlags.Instance
            |
            System.Reflection.BindingFlags.Public
        )
    )
    {
        object[] atts = pi.GetCustomAttributes 
	( typeof(PIEBALD.Types.XmlNodeAttribute) , false ) ;

        if ( atts.Length == 1 )
        {
            if ( this.node.Attributes [ pi.Name ] == null )
            {
                object value = ((PIEBALD.Types.XmlNodeAttribute) atts [ 0 ]).Value ;

                if ( value == null )
                {
                    throw ( new System.ArgumentNullException 
			( pi.Name , "This attribute requires a value" ) ) ;
                }

                System.Xml.XmlAttribute att = this.document.CreateAttribute ( pi.Name ) ;

                att.Value = value.ToString() ;

                this.node.Attributes.Append ( att ) ;
            }

            this.dic [ pi.Name ] = PIEBALD.Types.Parsomatic.Parser ( pi.PropertyType ) ;
        }
    }

    return ;
}

If a class requires a parser that is not already in the Parsomatic, then one needs to be added. This can be done in a static constructor, such as the one for WindowNode:

C#
static WindowNode
(
)
{
    PIEBALD.Types.Parsomatic.AddType
        ( System.Windows.Forms.FormWindowState.Normal ) ;

    return ;
}

See my Parsomatic[^] article if you have any questions.

GetValue and SetValue

XmlNode provides these two methods to ease the burden of converting between string and the desired type of the property. GetValue accesses the parser associated with the attribute and SetValue merely performs a ToString. (SetValue needn't be generic, but having it match GetValue adds regularity to the interface.)

C#
protected virtual T
GetValue<T>
(
    string AttributeName
)
{
    return ( (T) this.dic [ AttributeName ]
    (
        this.node.Attributes [ AttributeName ].Value
    ) ) ;
}

protected virtual void
SetValue<T>
(
    string AttributeName
,
    T      Value
)
{
    this.node.Attributes [ AttributeName ].Value = Value.ToString() ;

    return ;
}

NodeName and InnerText

These methods simply access the properties of the wrapped XmlNode. Note that NodeName is readonly.

C#
public virtual string
NodeName
{
    get
    {
        return ( this.node.Name ) ;
    }
}

public virtual string
InnerText
{
    get
    {
        return ( this.node.InnerText ) ;
    }

    set
    {
        this.node.InnerText = value ;

        return ;
    }
}

NamedXmlNode

NamedXmlNode extends XmlNode by adding a Name property. This is an example of what I was saying about standardizing how I layout XML -- if an XmlElement needs a name, it gets a Name attribute, not an ID attribute or a Name element or any other way of doing it.

C#
[PIEBALD.Types.XmlNodeAttribute("")]
public virtual string
Name
{
    get
    {
        return ( this.GetValue<string> ( "Name" ) ) ;
    }

    set
    {
        this.SetValue<string> ( "Name" , value ) ;

        return ;
    }
}

XmlNodeDemo3.cs demonstrates a NamedXmlNode, the output is:

XML
<XmlNodeDemo3><DemoNodes><Node Name="XmlNodeDemo3">XmlNodeDemo3</Node>
</DemoNodes></XmlNodeDemo3>

XmlNodeCollection<T>

Often I have a number of Elements of the same type, e.g. DataRows in a DataTable, and they should be represented by a collection; this class accomplishes that.

Obviously, the class must hold the members of the collection -- I'm using a List<T> for that. And to wrap each member before I add it to the collection, I need a constructor for class T.

The following static constructor attempts to get the required constructor:

C#
public partial class XmlNodeCollection<T> : PIEBALD.Types.XmlNode
where T : PIEBALD.Types.IXmlNode
{
    private static readonly System.Reflection.ConstructorInfo constructor ;

    static XmlNodeCollection
    (
    )
    {
        constructor = typeof(T).GetConstructor
        (
            System.Reflection.BindingFlags.Public
            |
            System.Reflection.BindingFlags.Instance
        ,
            null
        ,
            new System.Type[] { typeof(System.Xml.XmlDocument) , 
		typeof(System.Xml.XmlNode) }
        ,
            null
        ) ;

        if ( constructor == null )
        {
            throw ( new System.InvalidOperationException 
		( "No suitable constructor found" ) ) ;
        }

        return ;
    }

    private readonly System.Collections.Generic.List<T> nodes =
        new System.Collections.Generic.List<T>() ;
}

Constructor, Initialize, and Add

Because XmlNodeCollection IS_AN XmlNode, the constructor(s) will call the base constructor(s). Then any existing member elements with the specified NodeName will be wrapped and added to the collection.

C#
public XmlNodeCollection
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlNode     Node
,
    string                 Path
,
    string                 Element
)
: base
(
    Document
,
    Node
,
    Path
)
{
    this.Initialize ( Element ) ;

    return ;
}

protected virtual void
Initialize
(
    string Element
)
{
    if ( !System.String.IsNullOrEmpty ( Element ) )
    {
        foreach
        (
            System.Xml.XmlNode node
        in
            this.node.SelectNodes ( Element )
        )
        {
            this.Add ( node ) ;
        }
    }

    return ;
}

protected virtual T
Add
(
    System.Xml.XmlNode Node
)
{
    object[] parms = new object [ 2 ] ;

    parms [ 0 ] = this.document ;
    parms [ 1 ] = Node ;

    return ( this.Add ( (T) constructor.Invoke ( parms ) ) ) ;
}

protected virtual T
Add
(
    T Node
)
{
    this.nodes.Add ( Node ) ;

    this.Count = this.nodes.Count ;

    return ( Node ) ;
}

Count, indexer, and Nodes

Count is a property which is backed by a Count attribute of the wrapped XmlNode. The indexer allows accessing collection members by index. Nodes returns a readonly copy of the List<T>.

C#
[PIEBALD.Types.XmlNodeAttribute(0)]
public int
Count
{
    get
    {
        return ( this.GetValue<int> ( "Count" ) ) ;
    }

    private set
    {
        this.SetValue<int> ( "Count" , value ) ;

        return ;
    }
}

public virtual T
this
[
    int Index
]
{
    get
    {
        return ( this.nodes [ Index ] ) ;
    }
}

public System.Collections.Generic.IList<T>
Nodes
{
    get
    {
        return ( this.nodes.AsReadOnly() ) ;
    }
}

XmlNodeDemo4.cs demonstrates simple use of XmlNodeCollection:

C#
System.Xml.XmlDocument doc = new System.Xml.XmlDocument() ;

PIEBALD.Types.XmlNodeCollection<PIEBALD.Types.XmlNode> nod =
    new PIEBALD.Types.XmlNodeCollection<PIEBALD.Types.XmlNode>
    (
        doc
    ,
        null
    ,
        "XmlNodeDemo4/DemoNodes"
    ,
        "Node"
    ) ;

System.Console.WriteLine ( doc.OuterXml ) ;

nod.Add ( "Member" ) ;
nod.Add ( "Member" ) ;
nod.Add ( "Member" ) ;

System.Console.WriteLine ( doc.OuterXml ) ;

Its output is:

XML
<XmlNodeDemo4><DemoNodes Count="0" /></XmlNodeDemo4>
<XmlNodeDemo4><DemoNodes Count="3"><Member /><Member />
<Member /></DemoNodes></XmlNodeDemo4>

NamedXmlNodeCollection<T>

NamedXmlNodeCollection<T> is very similar to XmlNodeCollection<T> except for that it collects INamedXmlNodes in a Dictionary<string,T>.

C#
public partial class NamedXmlNodeCollection<T> : PIEBALD.Types.XmlNode
where T : PIEBALD.Types.INamedXmlNode
{
    private static readonly System.Reflection.ConstructorInfo constructor ;

    static NamedXmlNodeCollection
    (
    )
    {
        constructor = typeof(T).GetConstructor
        (
            System.Reflection.BindingFlags.Public
            |
            System.Reflection.BindingFlags.Instance
        ,
            null
        ,
            new System.Type[] { typeof(System.Xml.XmlDocument) , 
			typeof(System.Xml.XmlNode) }
        ,
            null
        ) ;

        if ( constructor == null )
        {
            throw ( new System.InvalidOperationException 
		( "No suitable constructor found" ) ) ;
        }

        return ;
    }

    private readonly System.Collections.Generic.Dictionary<string,T> nodes =
        new System.Collections.Generic.Dictionary<string,T>
        (
            System.StringComparer.CurrentCultureIgnoreCase
        ) ;
}

Constructor, CheckItems, Initialize, and Add

These are very similar to the ones for XmlNodeCollection except for the addition of CheckItems, which ensures that members with the provided names (Items) exist before proceeding.

C#
public NamedXmlNodeCollection
(
    System.Xml.XmlDocument Document
,
    System.Xml.XmlNode     Node
,
    string                 Path
,
    string                 Element
,
    params string[]        Items
)
: base
(
    Document
,
    Node
,
    Path
)
{
    this.CheckItems ( Element , Items ) ;
    this.Initialize ( Element ) ;

    return ;
}

protected virtual void
CheckItems
(
    string   Element
,
    string[] Items
)
{
    if
    (
        !System.String.IsNullOrEmpty ( Element )
    &&
        ( Items != null )
    &&
        ( Items.Length > 0 )
    )
    {
        System.Collections.Generic.HashSet<string> names =
            new System.Collections.Generic.HashSet<string>() ;

        foreach
        (
            System.Xml.XmlNode nod
        in
            this.node.SelectNodes ( Element )
        )
        {
            if ( nod.Attributes [ "Name" ] != null )
            {
                names.Add ( nod.Attributes [ "Name" ].Value ) ;
            }
        }

        foreach
        (
            string item
        in
            Items
        )
        {
            if
            (
                !System.String.IsNullOrEmpty ( item )
            &&
                !names.Contains ( item )
            )
            {
                System.Xml.XmlNode nod = this.document.CreateElement ( Element ) ;
                System.Xml.XmlAttribute att = this.document.CreateAttribute ( "Name" ) ;

                att.Value = item ;

                nod.Attributes.Append ( att ) ;

                this.node.AppendChild ( nod ) ;
            }
        }
    }

    return ;
}

protected virtual void
Initialize
(
    string Element
)
{
    if ( !System.String.IsNullOrEmpty ( Element ) )
    {
        foreach
        (
            System.Xml.XmlNode node
        in
            this.node.SelectNodes ( Element )
        )
        {
            this.Add ( node ) ;
        }
    }

    return ;
}

protected virtual T
Add
(
    System.Xml.XmlNode Node
)
{
    object[] parms = new object [ 2 ] ;

    parms [ 0 ] = this.document ;
    parms [ 1 ] = Node ;

    return ( this.Add ( (T) constructor.Invoke ( parms ) ) ) ;
}

protected virtual T
Add
(
    T Node
)
{
    this.nodes [ Node.Name ] = Node ;

    this.Count = this.nodes.Count ;

    return ( Node ) ;
}

Count, indexer, Names, and Nodes

Count is the same. This indexer uses the Name of the member rather than the index. Names returns the collection of member Names. Nodes returns the members in the collection.

C#
[PIEBALD.Types.XmlNodeAttribute(0)]
public int
Count
{
    get
    {
        return ( this.GetValue<int> ( "Count" ) ) ;
    }

    private set
    {
        this.SetValue<int> ( "Count" , value ) ;

        return ;
    }
}

public virtual T
this
[
    string Name
]
{
    get
    {
        return ( this.nodes [ Name ] ) ;
    }
}

public System.Collections.Generic.IEnumerable<string />
Names
{
    get
    {
        return ( this.nodes.Keys ) ;
    }
}

public System.Collections.Generic.IEnumerable<T>
Nodes
{
    get
    {
        return ( this.nodes.Values ) ;
    }
}

XmlNodeDemo5.cs demonstrates simple use of NamedXmlNodeCollection:

C#
System.Xml.XmlDocument doc = new System.Xml.XmlDocument() ;

PIEBALD.Types.NamedXmlNodeCollection<PIEBALD.Types.NamedXmlNode> nod =
    new PIEBALD.Types.NamedXmlNodeCollection<PIEBALD.Types.NamedXmlNode>
    (
        doc
    ,
        null
    ,
        "XmlNodeDemo5/DemoNodes"
    ,
        "Node"
    ,
        "Member1"
    ,
        "Member2"
    ,
        "Member3"
    ) ;

System.Console.WriteLine ( doc.OuterXml ) ;

Its output is:

XML
<XmlNodeDemo5><DemoNodes Count="3"><Node Name="Member1" />
<Node Name="Member2" /><Node Name="Member3" /></DemoNodes></XmlNodeDemo5>

Putting It All Together: XmlNodeDemo6

XmlNodeDemo6 demonstrates how this technique can be used to simplify persisting and restoring a Form's window size and position.

This is a very simple WinForms application that persists its window size and position in an XML file when it closes and, if the file exists at startup, restores the persisted values. If the file doesn't exist at startup, then the proper nodes are created with default values.

C#
System.Xml.XmlDocument doc = new System.Xml.XmlDocument() ;

System.IO.FileInfo fi = new System.IO.FileInfo ( "XmlNodeDemo6.xml" ) ;

if ( fi.Exists )
{
    doc.Load ( fi.FullName ) ;
}

PIEBALD.Types.NamedXmlNodeCollection<PIEBALD.Types.WindowNode> nod =
    new PIEBALD.Types.NamedXmlNodeCollection<PIEBALD.Types.WindowNode>
    (
        doc
    ,
        null
    ,
        "XmlNodeDemo6/Windows"
    ,
        "Window"
    ,
        "frmXmlNodeDemo6"
    ) ;

System.Windows.Forms.Application.Run
(
    new frmXmlNodeDemo6.frmXmlNodeDemo6()
    {
        WindowNode = nod [ "frmXmlNodeDemo6" ]
    }
) ;

System.IO.File.WriteAllText ( fi.FullName , doc.OuterXml ) ;

(Just in case you're wondering, this is not the way I usually read and write XML files.)

The important thing to notice is that the actual WinForm code doesn't need to be altered to add this functionality; I use an implant[^].

WindowNode.Implant

This code adds the ability to persist and restore a Form's window size and position to XML without altering the rest of the WinForms code. Adding this ability is as simple as implanting this code and then setting the resulting WindowNode property of the target Form (as shown above).

The implant contains the following field and property:

C#
protected PIEBALD.Types.WindowNode windownode = null ;

public virtual PIEBALD.Types.WindowNode
WindowNode
{
    set
    {
        this.FormClosing -= this.PersistWindow ;
        this.Load -= this.RestoreWindow ;

        if ( ( this.windownode = value ) != null )
        {
            this.FormClosing += this.PersistWindow ;
            this.Load += this.RestoreWindow ;
        }

        return ;
    }
}

RestoreWindow

This method attempts to retrieve the values from the XmlNode when the form loads, but if an Exception is encountered (usually due to a parsing failure), then the current value will be retained and persisted.

C#
protected virtual void
RestoreWindow
(
    object           sender
,
    System.EventArgs e
)
{
    if ( this.windownode != null )
    {
        try
        {
            this.Top = this.windownode.Top ;
        }
        catch
        {
            this.windownode.Top = this.Top ;
        }

        // Left, Width, and Height snipped for brevity

        try
        {
            this.WindowState = this.windownode.WindowState ;
        }
        catch
        {
            this.windownode.WindowState = this.WindowState ;
        }

        if ( this.WindowState == System.Windows.Forms.FormWindowState.Minimized )
        {
            this.WindowState = System.Windows.Forms.FormWindowState.Normal ;
        }
    }

    return ;
}

PersistWindow

This method persists the form's current WindowState to the XmlNode and, if the state is normal, also persists the size and position:

C#
protected virtual void
PersistWindow
(
    object           sender
,
    System.EventArgs e
)
{
    if ( this.windownode != null )
    {
        if ( ( this.windownode.WindowState = this.WindowState ) == 
			System.Windows.Forms.FormWindowState.Normal )
        {
            this.windownode.Top    = this.Top    ;
            this.windownode.Left   = this.Left   ;
            this.windownode.Width  = this.Width  ;
            this.windownode.Height = this.Height ;
        }
    }

    return ;
}

The resultant XML is:

XML
<XmlNodeDemo6><Windows Count="1"><Window Name="frmXmlNodeDemo6" 
Top="348" Left="490" Width="300" Height="300" WindowState="Normal" />
</Windows></XmlNodeDemo6>

(Actual values may differ.)

You can move and resize the form and see the results in XmlNodeDemo6.xml after exiting the application.

Compare That to TheOldWay.txt

In the zip file, I have included the _Load and _Closing methods from my RegexTester as an example of one way I used to enable persisting a Form's size and position.

Notice that for that application, I use a boolean Maximized attribute, this is an example of non-standard behaviour that I want to eliminate. There are also two enumerations in use that I store as ints -- using my new technique I can persist them properly.

More Advanced Technique

I demonstrate the use of a family of classes that define a more-complex structure -- a DataTableNode, which contains a collection of ExtendedPropertyNodes, a collection of DataColumnNodes, and a collection of DataRowNodes. A DataRowNode contains a collection of DataFieldNodes. Both programs produce the following (whitespace added for clarity):

XML
<DataTables>
  <DataTable Database="" Name="TestTable">
    <ExtendedProperties Count="1">
      <ExtendedProperty Name="Timestamp" IsNull="False" DataType="System.DateTime" 
	Align="Unknown">2011-05-14 11:04:56</ExtendedProperty>
    </ExtendedProperties>
    <DataColumns Count="2">
      <DataColumn Name="Col1" IsKey="False" MaxWidth="8" IsNull="False" 
	DataType="System.String" Align="Left">Column 1</DataColumn>
      <DataColumn Name="Col2" IsKey="False" MaxWidth="8" IsNull="False" 
	DataType="System.Int32" Align="Right">Column 2</DataColumn>
    </DataColumns>
    <DataRows Count="1">
      <DataRow>
        <DataFields Count="2">
          <DataField Name="Col1" IsNull="False" DataType="System.String" 
		Align="Left">Answer</DataField>
          <DataField Name="Col2" IsNull="False" DataType="System.Int32" 
		Align="Right">42</DataField>
        </DataFields>
      </DataRow>
    </DataRows>
  </DataTable>
</DataTables>

This is not how I usually create a DataTable in XML, but I do have an application that reads the definitions (but not data) of several DataTables with these classes.

You can run XmlNodeDemo7 or XmlNodeDemo8 and redirect the output to XmlNodeDemo9.xml, then when you run XmlNodeDemo9, it will attempt to read and display the above XML-encoded DataTable.

TestTable

Timestamp = 2011-05-14 11:04:56

Column 1 Column 2
-------- --------
Answer         42

Transsubstantiate

These classes are why I wrote my Transsubstantiate [^] utility. The constructors for NamedXmlNode and several of the other classes don't add anything of substance, so I don't want to have to write or copy-and-paste them.

I included the files created by Transsubstantiate and Implant in case you don't want to create them yourself. If you supply the "full" parameter to build.bat, it will remove the supplied files and attempt to recreate them.

In Closing

I haven't worked with this extensively yet, so there are likely still some efficiency and robustness issues. Suggestions for improvements are welcome.

History

  • 2011-05-14 First submitted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)