MIRA
XML Macros Reference


XML macros provide a way to organize applications consisting of large XML configuration files.

In order to use macros, the macro namespace must be declared:

<root xmlns:macro="http://www.mira-project.org/xmlns:macro">
</root>

Invoking macros

When a macro is encountered in an XML file, it is evaluated and replaced with the resulting XML at the invocation site.

<macro:MyMacro MyParameter="MyValue" />
<!-- or -->
<macro:MyMacro>
<MyParameter>MyValue</MyParameter>
</macro:MyMacro>

Macros that do not require parameters can be coerced into strings:

<MyXMLNode MyAttribute="${macro MyMacroWithNoParameters}" />

What exactly the macro expands to depends on the definition of MyMacro.

Defining macros

In order to define a macro, the desired name of the macro is prefixed with macro:Define_. Then follows the definition of required and optional parameters. Parameters are evaluated in the same scope as the body of the definition. This scope has access to all macros that were available where the macro was defined, as well as all parameters. Note that parameters themselves are also macros and can be evaluated via invocation.

Example

<macro:Define_MyMacro>
<Parameters>
<MyParameter />
<!-- String default value: -->
<MyOptionalParameter1>MyDefaultValue</MyOptionalParameter1>
<!-- Nested XML as default value: -->
<MyOptionalParameter2>
<Foo>Bar</Foo>
</MyOptionalParameter2>
<!-- Use the value of another parameter as a default. -->
<MyOptionalParameter3>
<macro:MyParameter />
</MyOptionalParameter3>
</Parameters>
<Body>
<!-- Use the parameters to construct some XML. -->
<Foo><macro:MyParameter /></Foo>
</Body>
</macro:Define_MyMacro>

Importing macros

Macros from other XML files can be imported by pointing an XML namespace to the file containing macro definitions. Imported macros are then available under that namespace.

Example

<root
xmlns:macro="http://www.mira-project.org/xmlns:macro"
xmlns:mymodule="Package:path/to/file.xml"
>
<mymodule:MyMacro MyParameter="MyValue" />
<!-- Coerce namespaced macros into strings. -->
${macro mymodule:MyMacroWithNoParameters}
</root>

Single macros can also be imported in the same line as their invocation. This is useful when the macro is used only once.

Example

<macro:MyImportedMacro From="file/that/defines/MyImportedMacro.xml">
...

By default an <include> tag does not leak any macro definitions into the active file. Macros can be explicitly loaded into a namespace by providing the macroNamespace attribute. However, using XML namespace delcarations or "From" imports is recommended.

Example

<include file="..." macroNamespace="mymodule" />

Scoping rules

In contrast to XML variables, macros are not global. Each macro operates in a scope which dictates how other macros are resolved. In addition to this, definitions are immutable and cannot be changed. As a consequence, you do not have to worry about coming up with globally unique names. The following rules apply for resolving definitions:

Example

<macro:Define_Foo>
<Body>123</Body>
</macro:Define_Foo>
<macro:Define_Bar>
<Parameters>
<Foo />
</Parameters>
<Body>
<!-- This refers to the parameter, not the global Foo -->
<macro:Foo />
</Body>
</macro:Define_Bar>
<!-- Result: ABC -->
<macro:Bar><Foo>ABC</Foo></macro:Bar>
<!-- Result: 123 -->
<macro:Foo />
<!-- Result: 123 -->
<macro:Bar><macro:Foo /></macro:Bar>

Nested definitions allow you to define small helper macros that can capture parameters but aren't available outside of the parent definition.

Example

<macro:Define_MyMacro>
<Parameters>
<MyParameter />
<Parameters>
<Body>
<!-- Create a helper macro that only exists within this definition. -->
<macro:Define_MyHelperMacro>
<Parameters>
<Sentence />
</Parameters>
<Body>
<!-- Note that the helper has access to its parent scope and therefore to MyParameter. -->
<macro:Sentence /> <macro:MyParameter />
</Body>
</macro:Define_MyHelperMacro>
<macro:MyHelperMacro Sentence="The parameter is " />
<macro:MyHelperMacro Sentence="The parameter still is " />
<Body>
</macro:Define_MyMacro>
<!-- Result:
The parameter is Foo
The parameter still is Foo
-->
<macro:MyMacro MyParameter="Foo" />
<!-- Error: No definition for <macro:MyHelperMacro> found. -->
<macro:MyHelperMacro Sentence="This will not work!" />

Lazy evaluation

Macros are intentionally evaluated lazily. This means that an invocation is not turned into XML when being passed as a parameter to another macro. This is a crucial detail when working with global variables. While mixing global variables and macros should be avoided, it may be necessary for backward compatibility.

Example

<macro:Define_MyMacro>
<Parameters>
<MyParameter />
</Parameters>
<Body>
<var myVariable="1" overwrite="true" />
<macro:MyParameter />
<var myVariable="2" overwrite="true" />
<macro:MyParameter />
</Body>
</macro:Define_MyMacro>
<macro:MyMacro>
<MyParameter>
<!-- We have not defined myVariable yet, but this will not throw an exception. -->
<ValueOfVariable>${myVariable}</ValueOfVariable>
</MyParameter>
</macro:MyMacro>

The above code will expand to:

<ValueOfVariable>1</ValueOfVariable>
<ValueOfVariable>2</ValueOfVariable>

Built-in macros

The following macros are built-in and can be used within any scope:

<macro:Zip>
<MyNumber>
<item>1</item>
<item>2</item>
<item>3</item>
</MyNumber>
<MyLetter>
<item>A</item>
<item>B</item>
<item>C</item>
</MyLetter>
</macro:Zip>
<!-- Results in: -->
<item>
<MyNumber>1</MyNumber>
<MyLetter>A</MyLetter>
</item>
<item>
<MyNumber>2</MyNumber>
<MyLetter>B</MyLetter>
</item>
<item>
<MyNumber>3</MyNumber>
<MyLetter>C</MyLetter>
</item>
<macro:For>
<!-- Name this whatever you like; it will be available in the <Do> tag. -->
<MyIterator>
<item>1</item>
<item>2</item>
<item>3</item>
</MyIterator>
<Do>
<TheValueIs><macro:MyIterator /></TheValueIs>
</Do>
</macro:For>
<!-- Results in: -->
<TheValueIs>1</TheValueIs>
<TheValueIs>2</TheValueIs>
<TheValueIs>3</TheValueIs>

Example

This integrated example shows how to combine Split, Zip and For to define a list of complex items:

<var ids = "2 4" />
<var names = "foo bar" />
<macro:For>
<macro:Zip>
<Name>
<macro:Split Words="${names}" />
</Name>
<ID>
<macro:Split Words="${ids}" />
</ID>
</macro:Zip>
</Object>
<Do>
<ID><macro:Object.ID /></ID>
<Name><macro:Object.Name /></Name>
</Object>
</Do>
</macro:For>
<!-- Results in: -->
<ID>2</ID>
<Name>foo</Name>
<ID>4</ID>
<Name>bar</Name>

Definition of more Object items requires nothing more than extending the variables for ids and names - they just need to be symmetric.