Click here to Skip to main content
Click here to Skip to main content

Generate Classes From Declarative Code

, 7 Oct 2004
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
GeneralNice idea PinmemberPaul Talbot17-Oct-07 0:14 
GeneralI Forgot Pinmemberjosenfin28-Nov-06 2:41 
GeneralRe: I Forgot Pinmemberjosenfin28-Nov-06 2:44 
GeneralVery Interesting but....From Paris Near Opéra Pinmemberjosenfin27-Nov-06 22:53 
GeneralGreat Minds PinmemberBoneSoft3-Jul-06 11:37 
GeneralXSD.exe Pinmemberx0n14-Oct-04 4:13 
GeneralRe: XSD.exe Pinmembernickvh28-Apr-08 14:42 
GeneralError running the example PinmemberTommaso Caldarola14-Oct-04 0:37 
GeneralRe: Error running the example PinmemberMichael P Butler14-Oct-04 1:06 
GeneralRe: Error running the example PinmemberTommaso Caldarola14-Oct-04 2:23 
GeneralRe: Error running the example PinmemberVäinölä Harri14-Oct-04 5:43 
GeneralRe: Error running the example PinmemberTommaso Caldarola14-Oct-04 21:01 
QuestionHow about Date PinsussHarri Väinölä7-Oct-04 8:50 
QuestionWhy not XSLT? PinmemberOrnus7-Oct-04 7:45 
AnswerRe: Why not XSLT? PinmemberAK7-Oct-04 9:47 
GeneralLooks great! PinmemberMikeBeard7-Oct-04 6:27 
GeneralInheritance PinmemberMichael P Butler7-Oct-04 6:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 7 Oct 2004
Article Copyright 2004 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid