|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article presents one way you could build an XML Template Engine that processes or parses XML templates and generates code. The engine can be used as an alternative to XML/XSLT template engines available out there. Compared to them, the template transformation or code generation logic is implemented in C# code. The general idea is to use the generated code with a compiler in runtime to make your application more configurable or flexible and to get the job done with fewer lines of code. I suggest that before reading the article, read first the great article from Shahed.Khan: Write your own Code Generator or Template Engine in .NET; it was a trigger for this one. BackgroundI was searching for a way to make an application "as configurable as possible" when I stumbled upon the aforementioned great article from Shahed.Khan. After the first "wow effect," I soon realized that the presented idea was not quite suitable for my needs:
All this encouraged me to build my own XML template engine and the result is here. And, maybe the most important, I've read so many great articles and learned so many things from the people that contribute to The Code Project that I felt I had an obligation to start to contribute to this great site. This is my first article for The Code Project. Using the CodeAfter downloading the source, extract it, run it and have fun. The solution consists of two projects: TemplateCompiler, which parses the template and generates code, and GUI. GUI should provide a base environment for working with XML templates (since there exists no add-in for Visual Studio). It has basic features like opening, closing and saving templates and colorizing XML syntax. The most important feature is the "Run" command, which will start the transformation of a template into code and show it in the "Output" tab. To get you started, a few template examples are located in the Examples folder of the GUI project. You can find SimpleExample.xml and SimpleExample2.xml described in the following section. XML Template StructureAn XML template engine generates code from XML files. The first condition that an XML file needs to satisfy to be a template is to have an XML declaration and
There are two ways that template variables can be defined. One is to use an already mentioned "define" keyword and the other is to specify it as an attribute in your custom node. For example, public class MyClass
{
// Default constructor
public MyClass() {}
// Some method
public void MyMethod(string arg1, string arg2)
{
Console.WriteLine("Hello from MyClass.MyMethod.");
}
}
The template from which that code is generated could look like this (SimpleExample.xml): <?xml encoding="utf-8" version="1.0" ?>
<template>
<function name="class">
<begin>
public class $name
{
$name() {}
</begin>
<end>
}
</end>
</function>
<class name="MyClass">
// Some method
public void MyMethod(string arg1, string arg2)
{
Console.WriteLine("Hello from $name.MyMethod.");
}
</class>
</template>
First, we have defined a function which tells the engine what to render to output when the The function starts to show benefits if we use it more than once. If we now want to generate the "skeleton" of another class, we only need to define another <?xml encoding="utf-8" version="1.0" ?>
<template>
<function name="class">
<begin>
public class $name
{
// Default constructor
public $name() {}
// Some method
public void $method(string arg1, string arg2)
{
Console.WriteLine("Hello from $name.$method.");
}
</begin>
<end>
}
</end>
</function>
<class name="MyClass" method="MyMethod" />
<class name="MyOtherClass" method="MyOtherMethod" />
</template>
The output produced: public class MyClass
{
// Default constructor
public MyClass() {}
// Some method
public void MyMethod(string arg1, string arg2)
{
Console.WriteLine("Hello from MyClass.MyMethod.");
}
}
public class MyOtherClass
{
// Default constructor
public MyOtherClass() {}
// Some method
public void MyOtherMethod(string arg1, string arg2)
{
Console.WriteLine("Hello from MyOtherClass.MyOtherMethod.");
}
}
Next, to make the template clearer, we could put it to another file and include it in this one. You can find that example as SimpleExample3.xml and SimpleExample3Include.xml in the Examples folder of the source download. ImplementationThe implementation is basically very simple. The entry point for XML template transformation is the internal void HandleNode(XmlNode node, RenderContext context)
{
ITemplateNodeHandler handler;
// get node handler, if not found use default if not null
string key = node.Name + node.NodeType;
if (!nodeHandlers.TryGetValue(key, out handler))
handler = defaulNodeHandler;
if (handler != null)
handler.Handle(node, context);
}
The method checks if the handler for that type and name of node is registered with <TemplateCompiler>
<handlers>
<handler nodeName="function" nodeType="Element"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.FunctionNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="include" nodeType="Element"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.IncludeNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="define" nodeType="Element"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.DefineNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="ref" nodeType="Element"
type=
"XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.ReferenceNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="#text" nodeType="Text"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.TextNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
</handlers>
<defaultHandler nodeName="" nodeType="None"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.DefaultNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
</TemplateCompiler>
This is a replacement for a switch statement that was here at first. ITemplateNodeHandler InterfaceEach node handler implements the
class RenderContext
{
internal readonly RenderStack Stack = new RenderStack();
internal readonly StringBuilder Output = new StringBuilder("");
internal readonly XmlTemplateCompiler Compiler;
internal readonly List<string> References = new List<string>();
private RenderContext() { }
internal RenderContext(XmlTemplateCompiler compiler)
{
Compiler = compiler;
}
}
Each node handler first validates if the node has a valid structure (expected attributes, attribute values and child nodes) and processes the node and all of its child nodes. Extending the EngineTo extend namespace MyNamespace
{
class CommentNodeHandler : ITemplateNodeHandler
{
void ITemplateNodeHandler.Handle(System.Xml.XmlNode node,
XmlTemplateEngine.TemplateCompiler.Context.RenderContext context)
{
context.Output.Append("//" + node.Value.Replace("\n", "\n//") + "\n");
}
}
}
Finally, define a new element in the <TemplateCompiler>
<handlers>
...
<handler nodeName="ref" nodeType="Element"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.ReferenceNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="#text" nodeType="Text"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.TextNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
<handler nodeName="#comment" nodeType="Comment"
type="MyNamespace.CommentNodeHandler, MyAssemblyName" />
</handlers>
<defaultHandler nodeName="" nodeType="None"
type="XmlTemplateEngine.TemplateCompiler.BuiltinNodeHandlers.DefaultNodeHandler,
XmlTemplateEngine.TemplateCompiler" />
</TemplateCompiler>
Currently, when rendering text nodes to output, the engine recognizes only words that begin with History
|
||||||||||||||||||||||