Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generate Classes From Declarative Code

0.00/5 (No votes)
7 Oct 2004 1  
Creates C# classes from declarative XML code samples.

Sample Image - DeclarativeClassGenerator.png

Introduction

I find that I do a lot of object graph modeling directly in XML rather than implementing the classes first or drawing UML diagrams of the classes. The XML modeling approach lets me see the object graph in a more compact tree structure, and I can play with what properties each class should have, etc., until the declarative markup meets the design requirements. Once I have the markup completed, I end up having to code the underlying classes by hand. Over and over again. It finally dawned on me that a quick and dirty generator that would take my sample XML and generate the underlying classes would meet 90% of my needs and save me a lot of typing. That's what this utility does.

Requirements

The code utilizes the MycroXaml declarative parser to generate the GUI which you'll need to download separately if you want to compile the project.

Example

Let's say you have a very simple declarative element in your markup:

<ComplexNumber Real="12.34" Imaginary="45.67"/>

The generator creates the underlying class:

public class ComplexNumber
{
  protected double imaginary;
  protected double real;
  public double Imaginary
  {
    get {return imaginary;}
    set {imaginary=value;}
  }
  public double Real
  {
    get {return real;}
    set {real=value;}
  }
}

If the markup contains a more complicated object graph, such as:

<Database Name="MyDB">
  <Tables>
    <Table Name="Person">
      <Fields>
        <Field Name="ID" PrimaryKey="true"/>
        <Field Name="FirstName" Unique="false"/>
        <Field Name="LastName" Unique="false" Indexed="true"/>
      </Fields>
    </Table>
  </Tables>
</Database>

The generator creates all the classes and their properties:

public class Field
{
  protected bool unique;
  protected string name;
  protected bool primaryKey;
  protected bool indexed;
  public bool Unique
  {
    get {return unique;}
    set {unique=value;}
  }
  public string Name
  {
    get {return name;}
    set {name=value;}
  }
  public bool PrimaryKey
  {
    get {return primaryKey;}
    set {primaryKey=value;}
  }
  public bool Indexed
  {
    get {return indexed;}
    set {indexed=value;}
  }
}

public class Database
{
  protected string name;
  protected ArrayList tables;
  public string Name
  {
    get {return name;}
    set {name=value;}
  }
  public ArrayList Tables
  {
    get {return tables;}
  }
  public Database()
  {
    tables=new ArrayList();
  }
}

public class Table
{
  protected string name;
  protected ArrayList fields;
  public string Name
  {
    get {return name;}
    set {name=value;}
  }
  public ArrayList Fields
  {
    get {return fields;}
  }
  public Table()
  {
    fields=new ArrayList();
  }
}

These classes are now ready to be pasted into your application, and the declarative code can now be used to instantiate the object graph and assign property values.

Things Of Note

The above example highlights some of the features of the generator:

  • It follows the "class-property-class" schema for the declarative code.
  • It assumes that the "property" in the "class-property-class" schema is a collection and implements the property type as an ArrayList. It could also be an abstract class, for example, which is not supported by this generator.
  • If a property in the markup is assigned "true" or "false", the property type is a "bool".
  • If a property value contains only digits, the property type is an "int".
  • If a property value contains digits and a decimal point, the property type is a "double".
  • All other property types are assumed to be "string". Enumerations are not supported.
  • Collection properties only have a "get" method.
  • Collection properties are instantiated in the class constructor.

The Code

The code is quite trivial. Since some people like to see the code in the article, here it is:

using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;

using MycroXaml.Parser;

namespace MyXaml.ClassGenerator
{
  public sealed class ClassInfo
  {
    public string name;
    public Hashtable properties;
    public ArrayList collections;

    public ClassInfo(string name)
    {
      this.name=name;
      properties=new Hashtable();
      collections=new ArrayList();
    }
  }

  public class ClassGenerator
  {
    protected Parser p;
    protected Hashtable classInfoList;

    public static void Main()
    {
      new ClassGenerator();
    }

    public ClassGenerator()
    {
      classInfoList=new Hashtable();
      StreamReader sr=new StreamReader("ClassGen.mycroxaml");
      string text=sr.ReadToEnd();
      sr.Close();
      XmlDocument doc=new XmlDocument();
      doc.LoadXml(text);
      p=new Parser();
      Form form=(Form)p.Load(doc, "ClassGenerator", this);
      Application.Run(form);
    }

    public void OnGenerate(object sender, EventArgs e)
    {
      classInfoList.Clear();
      TextBox src=(TextBox)p.GetInstance("xml");
      TextBox dest=(TextBox)p.GetInstance("code");
    
      XmlDocument doc=new XmlDocument();
      try
      {
        doc.LoadXml(src.Text);
      }
      catch(Exception ex)
      {
        MessageBox.Show(ex.Message);
        return;
      }
      XmlNode root=doc.DocumentElement;
      ProcessClass(root);
      dest.Text=EmitCode();
    }

    protected void ProcessClass(XmlNode node)
    {
      string className=node.Name;
      ClassInfo ci;

      if (classInfoList.Contains(className))
      {
        ci=(ClassInfo)classInfoList[className];
      }
      else
      {
        ci=new ClassInfo(className);
        classInfoList[className]=ci;
      }

      foreach(XmlAttribute attr in node.Attributes)
      {
        string name=attr.Name;
        string val=attr.Value.ToLower();

        // assume a string type

        string ptype="string";

        // if this is a new field, figure out the type

        if (!ci.properties.Contains(name))
        {
          // may be boolean

          if ( (val=="true") || (val=="false") )
          {
            ptype="bool";
          }

          // if it starts with a digit, may be int or double

          if (Char.IsDigit(val, 0))
          {
            ptype="int";
            if (val.IndexOf(".") != -1)
            {
              ptype="double";
            }
          }
          ci.properties[name]=ptype;
        }

        foreach(XmlNode childNode in node.ChildNodes)
        {
          if (!ci.collections.Contains(childNode.Name))
          {
            ci.collections.Add(childNode.Name);
            foreach(XmlNode grandchildNode in childNode.ChildNodes)
            {
              ProcessClass(grandchildNode);
            }
          }
        }
      }
    }

    protected string EmitCode()
    {
      StringBuilder sb=new StringBuilder();

      foreach(DictionaryEntry entry in classInfoList)
      {
        ClassInfo ci=(ClassInfo)entry.Value;
        string cname=ci.name.ToUpper()[0] + ci.name.Substring(1);
        sb.Append("public class "+cname+"\r\n");
        sb.Append("{\r\n");

        // emit properties

        foreach(DictionaryEntry prop in ci.properties)
        {
          string pname=(string)prop.Key;
          string ptype=(string)prop.Value;
          pname=pname.ToLower()[0]+pname.Substring(1);
          sb.Append("\tprotected "+ptype+" "+pname+";\r\n");
        }
        // emit collections

        foreach(string collName in ci.collections)
        {
          string name=collName.ToLower()[0]+collName.Substring(1);
          sb.Append("\tprotected ArrayList "+name+";\r\n");
        }

        // emit get/set for properties

        foreach(DictionaryEntry prop in ci.properties)
        {
          string pname=(string)prop.Key;
          string ptype=(string)prop.Value;
          pname=pname.ToLower()[0]+pname.Substring(1);
          string sname=pname.ToUpper()[0]+pname.Substring(1);
          sb.Append("\tpublic "+ptype+" "+sname+"\r\n");
          sb.Append("\t{\r\n");
          sb.Append("\t\tget {return "+pname+";}\r\n");
          sb.Append("\t\tset {"+pname+"=value;}\r\n");
          sb.Append("\t}\r\n");
        }

        // emit get for collections

        foreach(string collName in ci.collections)
        {
          string name=collName.ToUpper()[0]+collName.Substring(1);
          string collNameSmall=collName.ToLower()[0]+collName.Substring(1);
          sb.Append("\tpublic ArrayList "+name+"\r\n");
          sb.Append("\t{\r\n");
          sb.Append("\t\tget {return "+collNameSmall+";}\r\n");
          sb.Append("\t}\r\n");
        }

        // If class has collections, create a constructor that

        // initializes the ArrayLists.

        if (ci.collections.Count > 0)
        {
          // emit constructor

          sb.Append("\tpublic "+cname+"()\r\n");
          sb.Append("\t{\r\n");
          // for each collection, initialize it in the constructor

          foreach(string collName in ci.collections)
          {
            string name=collName.ToLower()[0]+collName.Substring(1);
            sb.Append("\t\t"+name+"=new ArrayList();\r\n");
          }
          sb.Append("\t}\r\n");
        }
        sb.Append("}\r\n\r\n");
      }
      return sb.ToString();
    }
  }
}

Conclusion

I hope this little utility is helpful for all you declarative programmers out there!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here