Thursday, February 1, 2007

Into the Wild Blue XML

So in the previous entry, I posited a hypothetical template-based, metadata-driven, code generation system with one shortcoming - there was no concrete way to perform the application of the template onto the data.

In this article, I'm going to describe an approach we have developed to perform this operation.

The template we ended up with last time was:

template :=
[PATTERN_BEGIN(property : property_metadata)]
do
{
try
{
cmd.Parameters.Add("@p_[property.name]", [property.type]);
[IF property.is_mandatory]
cmd.Parameters["@p_[property.name]"].Value = this.[property.name];
[ELSE]
[IF property.is_reference]
if (this.[property.name] == null)
{
cmd.Parameters["@p_[property.name]"].Value = System.DBNull.Value;
}
else
{
cmd.Parameters["@p_[property.name]"].Value = this.[property.name];
}
[ELSE]
cmd.Parameters["@p_[property.name]"].Value = this.[property.name];
[ENDIF]
}
[ENDIF]
catch (Exception ex)
{
[IF property.is_mandatory]
System.Diagnostics.Debug.WriteLine(String.Format("Customer.Persist - Persisting Customer.[property.name] threw Exception [ {0} ]. Bailing.", ex.Message));
throw ex;
[ELSE]
System.Diagnostics.Debug.WriteLine(String.Format("Customer.Persist - Persisting Customer.[property.name] threw Exception [ {0} ]. Filling Null Value.", ex.Message));
cmd.Parameters["@p_[property.name]"].Value = System.DBNull.Value;

break;
[ENDIF]
}
}
while(false);
[PATTERN_END]

Also, the data used to parametrize the template was:


data := {
{"ID", "System.Data.SqlDbType.UniqueIdentifier", true, false},
{"IsActive", "System.Data.SqlDbType.Bit", false, false},
{"Name", "System.Data.SqlDbType.NVarChar", false, true}
}

One way of looking at this snippet is to realise is that the invariant portion of the code (the non-italicised bits) is actually the output we want. It follows that if the non-italicised bits are the output, then the italicised portion must be the program, in some hitherto undescribed language, which when processed by some hitherto unspecified compiler, will generate the output.


If we want to take this approach, and we don't want to be saddled with the task of creating a new language and writing a full-fledged compiler, then it behooves us to look at what languages and tools are already out there to be able to acheive what we want without going through all the motions.

Epiphany #1

As a side note, it is very interesting to note that the output of an XSLT transformation of an XML document can be formatted text! All you have to do to achieve this is to appropriately set up the <xsl:output/> tag within the stylesheet.

So, if somehow, we could represent our data in an XML document, and have the template as an XSLT transform, we could then apply the transform to the data and emit text.

The reality, of course, is that it's fairly straightforward to represent both the data and the metadata in XML. In fact, we already have a mechanism to describe the metadata using XSD, which is itself an XML document. We won't appreciate the ramifications of this observation just yet, but all you need to do for now is to keep in mind that both the data and its metadata can be stored in XML format.

Application

Consider the following XML snippet which would be a reasonable XML representation of the data:

<data>
<property name="ID" type="System.Data.SqlDbType.UniqueIdentifier" is_mandatory="true" is_reference="false"/>
<property name="IsActive" type="System.Data.SqlDbType.Bit" is_mandatory="false" is_reference="false"/>
<property name="Name" type="System.Data.SqlDbType.NVarChar" is_mandatory="false" is_reference="true"/>
</data>

And consider the following XSLT transform:

<xsl:transform match="data/property">
<xsl:value-of select="concat('if (this.', @Name, ' == null')"/>
<xsl:value-of select="'{'"/>
<xsl:value-of select="'}'"/>
<xsl:value-of select="'else'"/>
<xsl:value-of select="'{'"/>
<xsl:value-of select="'}'"/>
</xsl:transform>

If we apply this transform to the data xml, we would generate the following snippet:

if (this.ID == null)
{
}
else
{
}
if (this.IsActive == null)
{
}
else
{
}
if (this.Name == null)
{
}
else
{
}

Interesting.

Three observations can be immediately made now:

  1. We have something which seems to give us a first approximation of what we want! This positive observation is very important!
  2. XSLT has control structures like <xsl:for-each> and <xsl:if> which means we seem to have a control language and its processing tools. This is also a positive observation.
  3. It's a right pain-in-the-patootie to write the XSLT by hand. This negative observation is critically important.

In fact, if we wanted to write the whole XSLT for the whole class by hand, it would be a laborious and time-consuming procedure. We wrote all of the v1 patterns of Designer this way, and believe us - it was frustrating and difficult work. Emitting apostrophes, quotes, ampersands and getting the formatting right are all singularly difficult, as is debugging any one of a dozen kind of problems that may crop up. Besides, all those <'s and >'s were starting to look a lot like distorted Lisp parens!

(The similarity with Lisp is a very interesting coincidence. Lisp programmers think nothing of generating Lisp functions at runtime and executing them in-situ. In fact, a functional programming language like Lisp would make generation of heirarchical data structures like XML snippets very easy. More on this later.)

I'm lazy, remember? Catch me trying to write XSLT by hand longer than I need to!

Watch out for the next article where I'll discuss an approach to generate the XSLT itself!

In the meanwhile, recap what we discovered here. We can use XML and XSLT as the basis of our code generation approach, as long as we don't have to write the XSLT by hand.

Laziness Rocks!

No comments: