XAML Preprocessor — Syntax

XAML is a great language for interface description but it have a caveat: it doesn't support C# like conditional compilation directives. Thus it is tricky to create and maintain multiple versions of an app where there are only small changes that would better lies in XAML files.

That's why I create a small program that take an XML file in input and output another one. In between it applies some transformations.

I choose to implements the directives as XML comments. That way it is neutral for other tools such as the XAML designer.

The IF directive

The semantic of this directive is: "if its expression is evaluated to true, the directive itself is transformed and no other change are made. Otherwise the directive is transformed and the next sibling non comment XML node is deleted."

This semantic means you can put comments between the node to be conditionally deleted and a directive.

Actually each directive is always transformed in non-directive comments by the preprocessor. That way to application of the preprocessor to a file is idempotent.

Sample

We will apply the preprocessor to the following file in two ways: either while defining the DEBUG symbols or not. The symbols handling is case sensitive.

This is the reference file:

<root>
	<node1>hello</node1>
	<!-- IF[DEBUG] -->
	<!-- This node will be removed in release mode. -->
	<node2>debug only</node2>
</root>

After using this command line:

XamlPreprocessor.exe DEBUG if.xml if.out.xml

The file is transformed this way:

<root>
 <node1>hello</node1>
 <!--expression 'IF[DEBUG]' evaluated at 'True'-->
 <!-- This node will be removed in release mode. -->
 <node2>debug only</node2>
</root>

Because the expression was evaluated to true, node2 was kept in place. Also the conditional directive now shows a trace information.

Let's run the preprocessor again against the reference file but without the DEBUG symbol. The preprocessor expect at least one symbol so we pass it a dummy symbol.

XamlPreprocessor.exe DUMMY if.xml if.out.xml
<root>
 <node1>hello</node1>
 <!--expression 'IF[DEBUG]' evaluated at 'False'-->
 <!-- This node will be removed in release mode. -->
 <!--Deleted node was here.-->
</root>

Attribute related directives

While deleting a node is great it isn't always applicable in the XAML designer: for example if you have two nodes with the same x:Key attribute, the designer will complains. This is why it is possible to add a (namespaced) attribute with a special directive.

Note: the attribute is added or removed unconditionally. You must use it with another directive (~IF) to conditionally add or remove an attribute.

ATTR-ADD

<root>
<!-- IF[WP7] -->
<item x:Key="MyKey" Text="v7" />
<!-- IF[WP8] -->
<!-- ATTR-ADD[x:Key="MyKey"] -->
<item Text="v8" />
</root>
XamlPreprocessor WP8 add.xml add.out.xml
<root>
 <!--expression 'IF[WP7]' evaluated at 'False'-->
 <!--expression 'IF[WP8]' evaluated at 'True'-->
 <!--attribute 'Key' added with value 'MyKey'-->
 <item Text="v8" Key="MyKey" />
</root>

Note : this sample seems to not works on Mono. It runs fine on MS .net.

ATTR-DEL

You can also delete an attribute.

The LIF directive

The ~IF directive works almost like the IF directive, hence its name. The difference lies in which node it will delete. While the IF directive delete the next non comment XML node, ~IF delete the next XML node even if it is a comment.

Because the preprocessor sequentially evaluates and runs directives from the beginning of the file to the bottom, it means you can deleted preprocessor directives before they are evaluated.

This is the canonical way to implement conditional attribute directive.

<root>
  <!-- Conditionally colors the text in debug. -->
  <!-- ~IF[DEBUG] -->
  <!-- ATTR-ADD[Foreground="Yellow"] -->
  <TextBlock Text="Debug mode"/>
</root>

Of course you can use this pattern more than once at a time.

<root>
  <!-- Set the text color to yellow in debug and to red in release. -->
  <!-- ~IF[DEBUG] -->
  <!-- ATTR-ADD[Foreground="Yellow"] -->
  <!-- ~IF[(not DEBUG)] -->
  <!-- ATTR-ADD[Foreground="Red"] -->
  <TextBlock Text="Debug mode"/>
</root>

This last sample use a more complex expression to express the condition. Let's dive into that.

Boolean expression

The processor evaluates expression. The more basic expression is just a symbol name. If the symbol is defined (that is given to the preprocessor as it first argument) it will be evaluated to true while processing the file, and false otherwise.

More complex expressions can be expressed using a LISP-like syntax. Three booleans operators are supported: and, or and not.

(or DEBUG WIN_8)
(not DEBUG)

Note: in the case of the not operator, parenthesis are mandatory.

Arbitrary complex expression can we used.

(and (not WIN_RT) (or PHONE HOLOLENS))

More formally, expressions must follows this pseudo-BNF grammar:

EXPRESSION :=  SYMBOL
| (OPERATOR EXPRESSION EXPRESSION)
| (not EXPRESSION)

OPERATOR := or | and

SYMBOL := character string

That's all

Have fun building app more efficiently with this preprocessor. Let me know if you use it to build your product ;)