如何避免 XSL 转换中的重复?
How to avoid duplication in XSL transformations?
在我们的应用程序中,XML 个具有以下结构(大大简化)的文档需要处理:
<FooBar>
<Foo ID="attr.1">value1</Foo>
<Foo ID="attr.2">another value</Foo>
<Bar ID="attr.3">
<Foo>1</Foo>
<Foo>2</Foo>
</Bar>
</FooBar>
这些文档使用 2 种不同的转换进行处理。这两个转换主要针对 Foo
元素和上面示例中未显示的一些支持信息。第一个转换输出 csv,其中 Foo
元素被转换成它自己的行。另一个转换只是将 Foo
元素输出为逗号分隔列表。
第一个转换的示例输出是:
attr.1-0;attr.1;value1
attr.2-0;attr.2;another value
attr.3-0;attr.3;1
attr.3-1;attr.3;2
第二次转换的示例输出是:
attr.1-0,attr.2-0,attr.3-0,attr.3-1
比较这两种转换时,它们共享大约 75% 的代码。事实上,唯一的区别是实际用于输出文本的命名模板。
转换如下所示:
<xsl:template match="/">
<!-- output some header information -->
<xsl:apply-templates select="Foo" />
<xsl:apply-templates select="Bar" />
</xsl:template>
<xsl:template match="Bar">
<!-- here is also some stuff -->
<xsl:apply-templates select="Foo" />
</xsl:template>
<xsl:template match="Foo" >
<!-- output the text that is special to transformation 1 or 2 -->
</xsl:template>
我怎样才能防止两个转换中的代码重复?
解决方案只是将整个代码重新组织到一些新文件中。
我遗漏的一点是,如果链接到的样式表定义了一个与链接样式表中调用的名称相同的模板,则样式表可以通过包含链接到不同的其他样式表。
每个转换现在都有一个根样式表。这将包括一个包含共享模板的样式表和一个包含转换专用模板的样式表。
所以这看起来像:
transformation1-root.xsl
:
<xsl:include href="transformation-shared.xsl" />
<xsl:include href="transformation1-specific.xsl" />
<xsl:template match="/">
<xsl:call-template name="templateFooBarRoot" />
</xsl:template>
transformation2-root.xsl
:
<xsl:include href="transformation-shared.xsl" />
<xsl:include href="transformation2-specific.xsl" />
<xsl:template match="/">
<xsl:call-template name="templateFooBarRoot" />
</xsl:template>
transformation-shared.xsl
:
<xsl:template name="templateFooBarRoot">
<!-- output some header information -->
<xsl:apply-templates select="Foo" />
<xsl:apply-templates select="Bar" />
</xsl:template>
<xsl:template match="Bar">
<!-- here is also some stuff -->
<xsl:apply-templates select="Foo" />
</xsl:template>
<xsl:template match="Foo" >
<xsl:call-template name="transformationSpecificTemplate" />
</xsl:template>
transformation1-specific.xsl
:
<xsl:template name="transformationSpecificTemplate">
<!-- stuff specific to transformation 1 -->
</xsl:template>
transformation2-specific.xsl
:
<xsl:template name="transformationSpecificTemplate">
<!-- stuff specific to transformation 2 -->
</xsl:template>
这是一种非常通用的策略传递方式(类似于函数式编程语言和 XSLT 3.0 中的高阶函数):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t1="my:t1" xmlns:t2="my:t2" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pStrategy" select="'t1:'"/>
<t1:strategy/>
<t2:strategy/>
<xsl:variable name="vStrategy" select="document('')/*/*[starts-with(name(), $pStrategy)]"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*">
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="t1:strategy">
<xsl:param name="pNum"/>
<xsl:value-of select="2*$pNum"/>
</xsl:template>
<xsl:template match="t2:strategy">
<xsl:param name="pNum"/>
<xsl:value-of select="$pNum*$pNum"/>
</xsl:template>
<xsl:template match="num/text()">
<xsl:apply-templates select="$vStrategy">
<xsl:with-param name="pNum" select="."/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
策略"t1"被执行,结果包含每个原始数字的两倍:
<nums>
<num>2</num>
<num>4</num>
<num>6</num>
<num>8</num>
<num>10</num>
<num>12</num>
<num>14</num>
<num>16</num>
<num>18</num>
<num>20</num>
</nums>
当转换的调用者为全局参数 $pStrategy 提供值 't2:' 时,将执行策略 "t2",结果包含每个原始数字的平方:
<nums>
<num>1</num>
<num>4</num>
<num>9</num>
<num>16</num>
<num>25</num>
<num>36</num>
<num>49</num>
<num>64</num>
<num>81</num>
<num>100</num>
</nums>
可以阅读更多关于这项强大技术的信息,它是 the FXSL library for functional programming in XSLT 1.0 and 2.0 here and here 的基础。
在我们的应用程序中,XML 个具有以下结构(大大简化)的文档需要处理:
<FooBar>
<Foo ID="attr.1">value1</Foo>
<Foo ID="attr.2">another value</Foo>
<Bar ID="attr.3">
<Foo>1</Foo>
<Foo>2</Foo>
</Bar>
</FooBar>
这些文档使用 2 种不同的转换进行处理。这两个转换主要针对 Foo
元素和上面示例中未显示的一些支持信息。第一个转换输出 csv,其中 Foo
元素被转换成它自己的行。另一个转换只是将 Foo
元素输出为逗号分隔列表。
第一个转换的示例输出是:
attr.1-0;attr.1;value1
attr.2-0;attr.2;another value
attr.3-0;attr.3;1
attr.3-1;attr.3;2
第二次转换的示例输出是:
attr.1-0,attr.2-0,attr.3-0,attr.3-1
比较这两种转换时,它们共享大约 75% 的代码。事实上,唯一的区别是实际用于输出文本的命名模板。
转换如下所示:
<xsl:template match="/">
<!-- output some header information -->
<xsl:apply-templates select="Foo" />
<xsl:apply-templates select="Bar" />
</xsl:template>
<xsl:template match="Bar">
<!-- here is also some stuff -->
<xsl:apply-templates select="Foo" />
</xsl:template>
<xsl:template match="Foo" >
<!-- output the text that is special to transformation 1 or 2 -->
</xsl:template>
我怎样才能防止两个转换中的代码重复?
解决方案只是将整个代码重新组织到一些新文件中。
我遗漏的一点是,如果链接到的样式表定义了一个与链接样式表中调用的名称相同的模板,则样式表可以通过包含链接到不同的其他样式表。
每个转换现在都有一个根样式表。这将包括一个包含共享模板的样式表和一个包含转换专用模板的样式表。
所以这看起来像:
transformation1-root.xsl
:
<xsl:include href="transformation-shared.xsl" />
<xsl:include href="transformation1-specific.xsl" />
<xsl:template match="/">
<xsl:call-template name="templateFooBarRoot" />
</xsl:template>
transformation2-root.xsl
:
<xsl:include href="transformation-shared.xsl" />
<xsl:include href="transformation2-specific.xsl" />
<xsl:template match="/">
<xsl:call-template name="templateFooBarRoot" />
</xsl:template>
transformation-shared.xsl
:
<xsl:template name="templateFooBarRoot">
<!-- output some header information -->
<xsl:apply-templates select="Foo" />
<xsl:apply-templates select="Bar" />
</xsl:template>
<xsl:template match="Bar">
<!-- here is also some stuff -->
<xsl:apply-templates select="Foo" />
</xsl:template>
<xsl:template match="Foo" >
<xsl:call-template name="transformationSpecificTemplate" />
</xsl:template>
transformation1-specific.xsl
:
<xsl:template name="transformationSpecificTemplate">
<!-- stuff specific to transformation 1 -->
</xsl:template>
transformation2-specific.xsl
:
<xsl:template name="transformationSpecificTemplate">
<!-- stuff specific to transformation 2 -->
</xsl:template>
这是一种非常通用的策略传递方式(类似于函数式编程语言和 XSLT 3.0 中的高阶函数):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t1="my:t1" xmlns:t2="my:t2" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pStrategy" select="'t1:'"/>
<t1:strategy/>
<t2:strategy/>
<xsl:variable name="vStrategy" select="document('')/*/*[starts-with(name(), $pStrategy)]"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*">
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="t1:strategy">
<xsl:param name="pNum"/>
<xsl:value-of select="2*$pNum"/>
</xsl:template>
<xsl:template match="t2:strategy">
<xsl:param name="pNum"/>
<xsl:value-of select="$pNum*$pNum"/>
</xsl:template>
<xsl:template match="num/text()">
<xsl:apply-templates select="$vStrategy">
<xsl:with-param name="pNum" select="."/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
策略"t1"被执行,结果包含每个原始数字的两倍:
<nums>
<num>2</num>
<num>4</num>
<num>6</num>
<num>8</num>
<num>10</num>
<num>12</num>
<num>14</num>
<num>16</num>
<num>18</num>
<num>20</num>
</nums>
当转换的调用者为全局参数 $pStrategy 提供值 't2:' 时,将执行策略 "t2",结果包含每个原始数字的平方:
<nums>
<num>1</num>
<num>4</num>
<num>9</num>
<num>16</num>
<num>25</num>
<num>36</num>
<num>49</num>
<num>64</num>
<num>81</num>
<num>100</num>
</nums>
可以阅读更多关于这项强大技术的信息,它是 the FXSL library for functional programming in XSLT 1.0 and 2.0 here and here 的基础。