One possible solution would be to change your template file to be an XML configuration, like this:
<?xml version="1.0" encoding="UTF-8"?>
<template>
Dear <name/>,
some text with other variables like <age/> or <name/> again
greatings <me/>
</template>
Assuming that the above XML template is named template.xml
and in the same directory as the XSLT below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="xs xd"
version="1.0">
<xsl:output method="text"/>
<!--Load the template document as a variable -->
<xsl:variable name="templateFile" select="document('template.xml')" />
<!--Load the current document in a variable, so that we can reference this file from within template matches on the template.xml content-->
<xsl:variable name="letter" select="/*" />
<!--When this stylesheet is invoked, apply templates for the templateFile content (everything inside the template element) -->
<xsl:template match="/">
<xsl:apply-templates select="$templateFile/*/node()" />
</xsl:template>
<!--Match on any of the template placeholder elements and replace with the value from it's corresponding letter document-->
<xsl:template match="template/*">
<!--set the local-name of the current element as a variable, so that we can use it in the expression below -->
<xsl:variable name="templateElementName" select="local-name(.)" />
<!--Find the corresponding letter element that matches this template element placeholder-->
<xsl:value-of select="$letter/*[local-name()=$templateElementName]" />
</xsl:template>
<!--standard identity template that copies content forward-->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When the XSLT is run against the sample XML file, it produces the following output:
Dear Test,
some text with other variables like 20 or Test again
greatings Me
As @Tomalak pointed out, unmatched placeholder elements would be removed from the output. If you wanted to preserve them, to make it apparent that the XML file did not have a match for placeholder items in the template, you could change the template that matches on the template placeholder elements like this:
<!--Match on any of the template placeholder elements and replace with the value from it's corresponding letter document-->
<xsl:template match="template/*">
<!--set the local-name of the current element as a variable, so that we can use it in the expression below -->
<xsl:variable name="templateElementName" select="local-name(.)" />
<!--Find the corresponding letter element that matches this template element placeholder-->
<xsl:variable name="replacementValue" select="$letter/*[local-name()=$templateElementName]" />
<xsl:choose>
<xsl:when test="$replacementValue">
<xsl:value-of select="$replacementValue" />
</xsl:when>
<xsl:otherwise>
<xsl:text>$</xsl:text>
<xsl:value-of select="local-name()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
If there was an unmatched placeholder element, <foo/>
for example, then it would appear in the text output as $foo
.