/// Thursday, November 10
How to Inject Code from XSLT Transformations
This tutorial describes how to implement an ICodeBuilder class to inject code from the result of an XSLT transformation.
Application frameworks and GUI builders create and store metadata in XML format. We can take advantage of this metadata to autogenerate code. We can not only generate code, we can attach metadata to a class by utilizing the MGutz.CodeInjection library. When the metadata changes the booc compiler will update the class during the compilation phase.
The compiler cannot perform this magic on its own. We must create a class derived from AbstractAstAttribute to manipulate the AST nodes the compiler works with. The MGutz.CodeInjection.InjectCodeAttribute does exactly that, it uses 'lower-level' operations to inject code. InjectCodeAttribute is flexible in the sense that it can use any ICodeBuilder object to build a string containing Boo code.
The rest of the tutorial describes the steps to implement XslCodeBuilder, an ICodeBuilder derived object that uses the result of an XSLT transformation to inject code.
We have two options to process XML metadata: 1) create a dedicated ICodeBuilder to process specific XML metadata with XmlTextReader and Boo code, or 2) use XSL transformations. The first option is the most flexible as you have the entire framework class library at your disposal. The second option is an all-purpose solution using XSL stylesheets. I'll take the second approach for this article. The end goal is to decorate a class with an attribute that says take this xml file, transform it to Boo code then inject the code into the class during compile time. In other words,
[InjectCode(XslCodeBuilder, "metadata.xml;test.xsl")] class XslTest: pass
To create a code builder implement the MGutz.CodeInjection.ICodeBuilder interface. The source for XslCodeBuilder is fairly simple.
01 public class XslCodeBuilder(ICodeBuilder): 02 TargetParser as BooParserType: 03 get: 04 return BooParserType.WSABooParser 05 06 def BuildCode([required] arg as string, [required] klass as ClassDefinition) as string: 07 // parse xml and xsl file name 08 args = arg.Split(char(';')) 09 if args.Length < 2: 10 raise ArgumentOutOfRangeException("arg", "Expected '<xml-file>;<xsl-file>', received: " + arg) 11 xml = args.Trim() 12 xsl = args.Trim() 13 14 // files are located in same folder as class 15 xml = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(klass.LexicalInfo.FileName)), xml) 16 xsl = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(klass.LexicalInfo.FileName)), xsl) 17 18 // transform to WSABoo code 19 using reader = StreamReader(xml): 20 xmlDoc = XPathDocument(reader) 21 stylesheet = XslTransform() 22 stylesheet.Load(xsl) 23 writer = StringWriter() 24 stylesheet.Transform(xmlDoc, null, writer, null) 25 return writer.ToString()
At line 02, TargetParser informs InjectCode that XslCodeBuilder is building WSABoo (white space agnostic) code. It's very difficult to control indentation in an XSL file. Don't waste any time making it pretty. This is a shortcoming of languages like Boo that rely on indentation. Fortunately, the Boo masters realized this and provide a WSABooParser class to handle the situation.
At line 16, InjectCode calls BuildCode() to build code. It has two arguments. arg is the argument from the attribute declaration, e.g. "metadata.xml;test.xsl". klass is the AST node of the decorated class.
The rest of the code converts the arguments into absolute paths then performs the XSLT transformation. The transformation result is returned to InjectCode as a string.
This example is found in the example/xsl directory.
First, decorate the class with InjectCode attribute. The example specifies test.xml as the metadata and test.xsl as the stylesheet.
# XslView.boo import MGutz.CodeInjection from MGutz.CodeInjection [InjectCode(XslCodeBuilder, "test.xml;test.xsl")] class XslTest: pass
# test.xml - metadata <def name="KR" comment="white book"> print "Hello world!" </def>
# test.xsl - stylesheet to transform metadata into WSABoo code <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:apply-templates /> </xsl:template> <xsl:template match="def"> # <xsl:value-of select="@comment"/> def <xsl:value-of select="@name"/>(): <xsl:value-of select="."/> end </xsl:template> </xsl:stylesheet>
Create a simple controller to use the XslTest.
o = XslTest() o.KR()
Compile it all together, then run it. The build file for the example creates an executable xsl.exe:
> xsl Helo world!
This article described the implementation of XslCodeBuilder, an ICodeBuilder derived class to perform XSLT transformations on XML metadata generating Boo code. The Boo code is injected into a decorated class each time the class is compiled syncing the class with the metadata.
A future article applies XslCodeBuilder to Glade#/Gtk# application development.