Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / XML
Article

Generate Classes From Declarative Code

Rate me:
Please Sign up or sign in to vote.
4.54/5 (12 votes)
7 Oct 2004CPOL2 min read 82.1K   901   42   17
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:

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

The generator creates the underlying class:

C#
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:

XML
<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:

C#
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:

C#
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)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralError running the example Pin
Tommaso Caldarola14-Oct-04 0:37
Tommaso Caldarola14-Oct-04 0:37 
GeneralRe: Error running the example Pin
Michael P Butler14-Oct-04 1:06
Michael P Butler14-Oct-04 1:06 
GeneralRe: Error running the example Pin
Tommaso Caldarola14-Oct-04 2:23
Tommaso Caldarola14-Oct-04 2:23 
GeneralRe: Error running the example Pin
Väinölä Harri14-Oct-04 5:43
Väinölä Harri14-Oct-04 5:43 
GeneralRe: Error running the example Pin
Tommaso Caldarola14-Oct-04 21:01
Tommaso Caldarola14-Oct-04 21:01 

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

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