Writing the .xslt file

XSL Transformations

ConfigZilla uses a technology called XSL Transformations to create the config files. For each part of the template that you want to transform you write a small file that inserts, deletes or changes nodes and attributes. ConfigZilla will merge all the .xslt files you write into one large file called $(MSBuildProjectName).ConfigZilla.xslt (which can be found in your "obj" folder) and then apply it to each *.template.config file. This merging allows you to keep each individual .xslt file small and manageable.

The Transforms folder is recursively scanned for files matching the pattern *.czDefault.xslt. These files are always included in the merged ConfigZilla.xslt. Additionally, any files that match your current configuration, such as ProjectX.Debug.xslt (if you are in Debug mode) will also be included. This allows you to take special actions for particular configurations. However, the power of the ConfigZilla variable subsitution feature means that you will rarely need to do this.

Learning by example

XSLT is a complicated and powerful technology which can perform any transformation you want. Luckily the set of transformations we typically need for config files is quite small and simple to understand. So let's take a look at a typical XSLT file. This is from the ConfigZilla samples and is used for setting the <appSettings> block.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">

  <xsl:template match="/configuration/appSettings/add[@key='Setting1']|/appSettings/add[@key='Setting1']">
    <add key="Setting1" value="$(appSetting1)" />
  </xsl:template>
  
  <xsl:template match="/configuration/appSettings/add[@key='Setting2']|/appSettings/add[@key='Setting2']">
    <add key="Setting2" value="$(appSetting2)" />
  </xsl:template>
  
  <!-- Replace <AppSettingsBlock /> with the whole set -->
  <xsl:template match="AppSettingsBlock" xml:space="preserve">
    <appSettings>
      <add key="Setting1" value="$(appSetting1)" />
      <add key="Setting2" value="$(appSetting2)" />
    </appSettings>
  </xsl:template>
  
</xsl:stylesheet>

The first two lines (down to and including the <stylesheet> tag) are just XML boilerplate and are in fact stripped by ConfigZilla when it merges the files. I tend to include them however, because otherwise the Visual Studio XML editor will complain about the syntax. The body of the file then consists of a series of <xsl:template> declarations. Each template consists of 2 parts. The first part, the match="..." statement, is written in XPath syntax, and specifies which elements in the source *.template.config document to apply your template to. The second part is the body of the template, this says what to do when the template matches. These examples are very simple, the template bodies just consist of XML to be output. The "$(appSetting1)" variable will be replaced with the appropriate value as determined by your .targets file.

Let's deconstruct the XPath in the first template. Firstly, note that there is a '|' in the middle. This is an "or" and separates two separates XPath expressions, meaning that the template will match either of them. So the two XPaths are

/configuration/appSettings/add[@key='Setting1']

or

/appSettings/add[@key='Setting1']

"/" means "root of the document, so "/configuration" means look for a node called "configuration" directly under the root. Then we ask for an "appSettings" node under that, and then an "add" node under that. Not surprisingly, this matches the structure of the typical app.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="reportingSettings" type="CZ.ReportingSettings, ConfigZilla" requirePermission="false" />
    <section name="PaymentSettings" type="CZ.PaymentSettings, ConfigZilla"/>
  </configSections>
  
  <appSettings>
    <add key="Setting1" value="" />
    <add key="Setting2" value="" />
  </appSettings>
  
...

The [@key='Setting'1] syntax is a further filter: it means "only match elements with an attribute called 'key' which has the value of 'Setting1'". In other words, this matches a particular appSetting/add element rather than them all.

The second XPath expression after the '|' matches exactly the same thing but without the leading "configuration" node. It becomes effective if you split your template files into separate files, because the sub-files don't have the <configuration> node, they just look like this:

<appSettings>
  <add key="Setting1" value=""/>
  <add key="Setting2" value=""/>
</appSettings>

You don't have to use the '|', but it's handy to have in case you decide to split or not-split at a later date. It would also be possible to use the XPath expression "//appSettings" which will find the appSettings node wherever it is in the document structure. You would only need one XPath expression if you used this, but I tend to avoid it because of the (rare) chance that I might add some XML in the future which matches the expression when I didn't want it to. It's also slow.

Both of the templates we have discussed so far are "node rewriting templates". They completely replace the XML they match with new XML. An alternative would be to just set the "value" attribute, but in fact this is a bit more verbose and the node-rewriting technique is often easier to grok.

The final template in the example takes this node-rewriting technique a step further. It replaces the tag <AppSettingsBlock /> with an entire <appSettings>...</appSettings> section containing all settings. This is a great way of cutting your template files down to size. Plus, if you use this technique and want to add a new app setting or connection string to all your projects you only need to do it in the .xslt file.

Setting attributes

The ReportingSettings in the samples uses this XML syntax in the config file:

<reportingSettings PageSize="" Server="" RecipientEmail="" />

and this is the XSLT used to set the attribute values:

<xsl:template match="/configuration/reportingSettings/@PageSize|/reportingSettings/@PageSize">
  <xsl:attribute name="PageSize">
    <xsl:value-of select="'$(rsPageSize)'"/>
  </xsl:attribute>
</xsl:template>

<xsl:template match="/configuration/reportingSettings/@Server|/reportingSettings/@Server">
  <xsl:attribute name="Server">
    <xsl:value-of select="'$(rsServer)'"/>
  </xsl:attribute>
</xsl:template>

<xsl:template match="/configuration/reportingSettings/@RecipientEmail|/reportingSettings/@RecipientEmail">
  <xsl:attribute name="RecipientEmail">
    <xsl:value-of select="'$(rsRecipientEmail)'"/>
  </xsl:attribute>
</xsl:template>

"@PageSize" means match an attribute called PageSize. Then the body of the template is applied. This replaces the matched element of 'PageSize=""' with a new attribute called PageSize and the appropriate value.

Attribute-setting templates like this are most useful when you must change a specific attribute and leave the rest unchanged. Setting the "Debug" flag on the compilation node in a web app is a good example. We definitely don't want to change any other attributes that may be set:

<xsl:template match="/configuration/system.web/compilation/@debug|/system.web/compilation/@debug">
    <xsl:attribute name="debug">
        <xsl:value-of select="'$(wsDebugFlag)'"/>
    </xsl:attribute>
</xsl:template>

Setting text values (inside nodes)

The PaymentSettings uses this style of XML:

<PaymentSettings>
    <PaymentSystem>Tom</PaymentSystem>
    <URL>Dick</URL>
    <Timeout>Harry</Timeout>
</PaymentSettings>

To replace Tom, Dick and Harry with the correct values you need to use the text() selector:

<xsl:template match="/configuration/PaymentSettings/PaymentSystem/text()|/PaymentSettings/PaymentSystem/text()">
    <xsl:text>$(payPaymentSystem)</xsl:text>
</xsl:template>
etc.

This will match "Tom" and replace it with whatever the value of $(payPaymentSystem) is.

Dealing with special characters (quoting)

In any type of text-replacement work you will eventually come across the need to escape, or quote, your strings. ConfigZilla is no different, but there is a gotcha - there are two technologies in use, MSBuild and Xslt, and they use different quoting techniques. To quote in MSBuild you need to use hexadecimal ASCII character codes, while to quote in Xslt the easiest way is to use CDATA blocks. Here is an example using Entity Framework. Let's say your .xslt file has an EF connection string with the form:

<add name="BlogContext"
    connectionString="metadata=res://*/BloggingModel.csdl|
                     res://*/BloggingModel.ssdl|
                     res://*/BloggingModel.msl;
                     provider=System.Data.SqlClient
                     provider connection string=
                     &‌quot;data source=(localdb)\v11.0;
                     initial catalog=Blogging;
                     integrated security=True;
                     multipleactiveresultsets=True;&‌quot;"
    providerName="System.Data.EntityClient" />
    

Note the two embedded &quot;s. They will get translated into the " character by MSBuild, breaking your template. The solution is to replace the 6 characters in &quot; with their ASCII codes, which are "%26%71%75%6F%74%3B", hence this will work:

<add name="BlogContext"
    connectionString="metadata=res://*/BloggingModel.csdl|
                     res://*/BloggingModel.ssdl|
                     res://*/BloggingModel.msl;
                     provider=System.Data.SqlClient
                     provider connection string=
                     %26%71%75%6F%74%3B;data source=(localdb)\v11.0;
                     initial catalog=Blogging;
                     integrated security=True;
                     multipleactiveresultsets=True;%26%71%75%6F%74%3B"
    providerName="System.Data.EntityClient" />
    

In this particular case, you may also be able to use a single quote rather than a double quote.

Here is an example of quoting on the Xsl-side. If you try and include the following log4net Xml in a template:

<layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />

you will get error messages around the yyyy part, because Xsl uses "{" as a delimiter for attribute value templates. You can prevent this error by simply doubling up the braces

<layout type="log4net.Layout.PatternLayout" value="%date{{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}}" />

An alternative and more powerful technique is to use CDATA blocks which can escape anything. You may need to wrap them with an xsl:text element:

<xsl:template match="...">
    <xsl:text disable-output-escaping="yes">
    <![CDATA[
    <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
    ]]>
    </xsl:text>
</xsl:template>

Using per-project targets

ConfigZilla will include templates from a per-project Transforms folder in the same way as it does for targets. As for targets, it is likely to be rare that you need this (I find it much more preferable to keep all my targets and templates gathered in the central ConfigZilla folder). Because all templates are written into the merged .xslt file, you may need to look into the conflict resolution rules if you want to use this technique.

Using Xsl Parameters

It is possible to pass Xsl Parameters to the ConfigZilla.xslt file. (This technique was used in ConfigZilla v1 to get access to properties like $(MSBuildProjectName) but it isn't needed for this purpose any longer because the Xslt file is now generated for each project that you are compiling). To set a parameter, create an MSBuild file called ConfigZilla.user in the root of your project and define your Xsl parameters like this (you must use the property name "czXslParameterString"):

<PropertyGroup>
    <czXslParameterString>
    <![CDATA[
    <Parameter Name='czpMyParam1' Value='some value'/>
    <Parameter Name='czpMyParam2' Value='You can use $(Substitution) here as well'/>
    ]]>
    </czXslParameterString>
</PropertyGroup>

To use it in your template you need to declare the parameter and then use value-of:

// outside a template:
<xsl:param name="czpMyParam1" />

// then:
<xsl:template>
    ...
    <xsl:value-of select="$czpMyParam1" />
    ...
</xsl:template>

Further reading


Table Of Contents