Overview:
Write a meta XSLT transformation that takes the paths
file as input and produces a new XSLT transformation as output. This new XSLT will transform from your root
input XML to the annotated copy output XML.
Notes:
- Works with XSLT 1.0, 2.0, or 3.0.
- Should be very efficient, especially if the generated
transformation has to be run over a large input or has to be run
repeatedly, because it effectively compiles into native XSLT rather
than reimplementing matching with an XSLT-based interpreter.
- Is more robust than approaches that have to rebuild
element ancestry manually in code. Since it maps the paths to
template/@match
attributes, the full sophistication of @match
ing
is available efficiently. I've included an attribute value test as
an example.
- Be sure to consider elegant XSLT 2.0 and 3.0 solutions by @DanielHaley
and @MartinHonnen, especially if an intermediate meta XSLT file
won't work for you. By leveraging XSLT 3.0's XPath evaluation
facilities, @MartinHonnen's answer appears to be able to provide
even more robust matching than
template/@match
does here.
This input XML that specifies XPaths and annotations:
<paths>
<xpath location="/root/a" annotate="1"/>
<xpath location="/root/a/b" annotate="2"/>
<xpath location="/root/c[@x='123']" annotate="3"/>
</paths>
When input to this meta XSLT transformation:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/paths">
<xsl:element name="xsl:stylesheet">
<xsl:attribute name="version">1.0</xsl:attribute>
<xsl:element name="xsl:output">
<xsl:attribute name="method">xml</xsl:attribute>
<xsl:attribute name="indent">yes</xsl:attribute>
</xsl:element>
<xsl:call-template name="gen_identity_template"/>
<xsl:apply-templates select="xpath"/>
</xsl:element>
</xsl:template>
<xsl:template name="gen_identity_template">
<xsl:element name="xsl:template">
<xsl:attribute name="match">node()|@*</xsl:attribute>
<xsl:element name="xsl:copy">
<xsl:element name="xsl:apply-templates">
<xsl:attribute name="select">node()|@*</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="xpath">
<xsl:element name="xsl:template">
<xsl:attribute name="match">
<xsl:value-of select="@location"/>
</xsl:attribute>
<xsl:element name="xsl:comment">
<xsl:value-of select="@annotate"/>
</xsl:element>
<xsl:element name="xsl:text">
<xsl:text disable-output-escaping="yes">&#xa;</xsl:text>
</xsl:element>
<xsl:element name="xsl:copy">
<xsl:element name="xsl:apply-templates">
<xsl:attribute name="select">node()|@*</xsl:attribute>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Will produce this XSLT transformation:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/a">
<xsl:comment>1</xsl:comment>
<xsl:text>
</xsl:text>
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/a/b">
<xsl:comment>2</xsl:comment>
<xsl:text>
</xsl:text>
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/c[@x='123']">
<xsl:comment>3</xsl:comment>
<xsl:text>
</xsl:text>
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which, when provided this input XML file:
<root>
<a>
<b>B</b>
</a>
<c x="123">C</c>
</root>
Will produce the desired output XML file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--1-->
<a>
<!--2-->
<b>B</b>
</a>
<!--3-->
<c x="123">C</c>
</root>