关于xml:XSLT条件包含外部文件

XSLT conditional include of external file

我想在XSLT中执行条件包含,但是xsl:include是顶级元素。您只能在模板内部使用xsl:if或xsl:choose。是否存在允许有条件地包含外部文件的任何形式的hack或变通方法?我尝试使用document()函数,但是无法加载我的外部文件(可能是因为它不符合使它"有效"的某些规则集)。

我的外部xml文件是一堆xslt代码片段。根据外部XSLT文件中变量的值,外部文件中的相应代码应"复制/粘贴"到位(例如C或PHP中的条件包含)。

我的主要XSLT文件的流应按以下方式进行:

1
2
$configurationMode
if ( $configurationMode = Standard ) { xsl:include="standard.xml" } else { xsl:include="alt.xml" }

很显然,我不能像上面那样简单地做到这一点,因此为什么我要问是否有黑客或变通办法。


这不能用XSLT 1.0完成,可以在XSLT 2.0中使用use-when属性完成(在非常有限的范围内)。

存在非xslt方式来实现xsl:includexsl:import指令的所需动态更改。

一种这样的方法是将XSLT样式表加载为XmlDocument,并使用可用的DOM方法访问和修改属性,以将href属性设置为所需的值。然后从此内存中修改的,包含XMLDocument的XSLT样式表启动转换。


尝试反转结构:如果您有两个专用模块pink.xsl和blue.xsl,以及一个通用模块baby.xsl,则不要尝试导入/包括pink.xsl或blue.xsl中的一个进入baby.xsl,而是使用pink.xsl或blue.xsl作为顶层输入样式表,并将这两个导入baby.xsl中的每一个导入。这就是它设计使用的方式,它不是破解或解决方法。

或者,根据您的情况"我的外部xml文件是一堆xslt代码片段"的描述,您的情况下更好的方法可能是使用XSLT转换而不是将这些片段的样式表作为一个单独的步骤来组装比使用xsl:include / xsl:import。


据我所知,当xml解析器正在解析和编译样式表时,就会发生"包含"。这意味着在处理包含之前不会进行逻辑或表达式求值,因此无法将其作为条件条件。您需要使条件行为发生在样式表之外。

看看这个http://www.dpawson.co.uk/xsl/sect2/N4760.html#d6065e100

Mike Kay的这一评论特别有帮助:

This has been raised a number of times. On a previous thread we came
to the conclusion that the user was trying to write a general-purpose
stylesheet G and then specialize it by conditionally including a
special-purpose stylesheet A or B. The way to meet this requirement is
to have A and B include G, not the other way around, and then you
conditionally select A or B as the principal stylesheet when starting
the transformation.


不确定这是否适用于您的情况,但是无论如何我都会把它扔在那里。

过去我做过类似的事情,但是我没有做条件包含,而是根据xsl:when的计算结果在两个文件之一中调用一个模板。如果您不想使用模板,请忽略此答案。

例如,假设"父" xslt文件称为root.xslt,两个有条件使用的文件是child1.xslt和child2.xslt。假设我想对称为status的节点的值运行条件逻辑。如果值为" CURRENT",我想在child1.xslt中调用名为isCurrent的模板,否则在child2.xslt中调用名为isNotCurrent的模板。为了简洁起见,在每种情况下,我只是将模板传递给根节点和所有子节点。

它看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:import href="child1.xslt"/>
    <xsl:import href="child2.xslt"/>

    <xsl:template match="/">
      <xsl:choose>
        <xsl:when test="rootNode/status = 'CURRENT'">
          <!-- template isCurrent defined in child1.xslt -->
          <xsl:apply-templates select="rootNode" mode="isCurrent" />
        </xsl:when>
        <xsl:otherwise>
          <!-- template isNotCurrent defined in child2.xslt -->
          <xsl:apply-templates select="rootNode" mode="isNotCurrent" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

希望这至少会有所帮助。


我发现有一种方法可以使用XSLT 2.0中的use-when功能有条件地包含文件。

您必须创建一个在运行时通过应用程序传递的系统属性,就像我使用Java一样,因此我已经使用命令创建了" Debug"属性来运行我的应用程序:

1
java -DDebug=yes myAppPath

然后在XSLT中使用此属性来包含条件文件。例如

1
2
<xsl:include href="file1.xslt" use-when="system-property('DEBUG') = 'yes'"/>
<xsl:include href="file2.xslt" use-when="system-property('DEBUG') != 'yes'"/>

那样。

不知道它是否适合所要求的方案,但是是的,在XSLT 2.0中可以做到这一点。
在XSLT 3.0中,还添加了对局部变量的支持。因此,可以使用局部变量轻松地做到这一点。

谢谢,
快乐编码


通过添加静态参数,现在可以在XSLT 3.0中实现。可以在xsl:includeuse-when属性中使用静态参数。

现在我们可以使用默认值false()声明参数,然后在运行时覆盖我们需要的参数...

1
2
3
<xsl:param name="someparam" as="xs:boolean" select="false()"
  static="yes" required="no"/>  
<xsl:include href="include_me.xsl" use-when="$someparam"/>

这里是使用Saxon-HE v9.7测试的完整示例(也使用Saxon-PE 9.5测试)。

XML输入(test.xml)

1
2
3
<doc>
    <foo/>
</doc>

主XSLT 3.0(test_main.xsl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:param name="inc1" as="xs:boolean" select="false()"
    static="yes" required="no"/>
  <xsl:param name="inc2" as="xs:boolean" select="false()"
    static="yes" required="no"/>

  <xsl:include href="test_inc1.xsl" use-when="$inc1"/>
  <xsl:include href="test_inc2.xsl" use-when="$inc2"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

第一个可能包含的XSLT 3.0(test_inc1.xsl)

1
2
3
4
5
6
7
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="foo">
    <xsl:copy>INCLUDE FILE 1!!!</xsl:copy>
  </xsl:template>

</xsl:stylesheet>

第二个可能包含的XSLT 3.0(test_inc2.xsl)

1
2
3
4
5
6
7
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="foo">
    <xsl:copy>INCLUDE FILE 2!!!</xsl:copy>
  </xsl:template>

</xsl:stylesheet>

命令行(将inc2设置为true)

1
java -cp"saxon9he.jar" net.sf.saxon.Transform -s:"test.xml" -xsl:"test_main.xsl" inc2="true"

输出

1
2
3
<doc>
   <foo>INCLUDE FILE 2!!!</foo>
</doc>

1
2
<xsl:variable name="CONDITIONAL" select="ELEMENT/CONDITION"/>
<xsl:variable name="DOCUMENTNAME" select="document(concat($CONDITIONAL,'-DOCUMENT.xml'))/ELEMENT"/>

此示例是我有条件使用外部文件的方式。我在XSL用作变量的基础XML中有一个标志,以帮助指定要拉入模板的其他文件的名称。在我定义了该变量之后,我将其用于将其他XML数据提取到生成的输出中。 concat命令将数据强制在一起以给出文件名。

因此,当我想要外部XML文件中的信息时,我使用'$ DOCUMENTNAME'来引用外部数据。


除了已经说过的以外,一种可能的解决方案是使补充文件成为纯的,提供内容的XML文件(而不是XSLT文件)。这样,您可以将它们包含在XPath的document()函数中(该函数将在运行时而不是编译时进行评估)。

然后,您可以根据已加载的XML文档的内容来更改转换的行为;但是,您不能在随附的文档中提供可执行的XSLT片段。

这是否取决于解决方案取决于您的用例-如果其他文档对转换的控制流程有很大影响,则您不想用纯XML定义它们,因为您基本上会重新定义XML。实现类似XSLT的功能。但是,当您的文档用作某种配置文件时,您可能需要考虑为它们提供纯XML。

document()遇到问题时,请在文件上使用XML验证器。错误表明您的文件不是有效的XML。