在 XSLT 中动态包含其他 XSL 文件

Posted

技术标签:

【中文标题】在 XSLT 中动态包含其他 XSL 文件【英文标题】:Dynamically include other XSL files in XSLT 【发布时间】:2011-12-17 21:31:04 【问题描述】:

我有一个小问题,有没有办法动态包含另一个 xsl?例如:

<xsl:variable name="PathToWeb" select="'wewe'"/>
<xsl:include href="http://$PathToWeb/html/xsl/head.xsl" />
<xsl:include href="http://$PathToWeb/html/xsl/navigation.xsl" />
<xsl:include href="http://$PathToWeb/html/xsl/promo.xsl" />
<xsl:include href="http://$PathToWeb/html/xsl/3columns.xsl" />

<xsl:include href="http://$PathToWeb/html/xsl/footer.xsl" />

【问题讨论】:

使用 【参考方案1】:

你不能这样做。原因很简单:

XSL 将首先在编译期间扩展 xsl:include,然后再执行其他任何操作。那时您的“变量”不知道也无法知道,并且一旦编译,您就无法更改编译的转换。此外,href 是统一资源定位器而不是 XPath 表达式,因此您不能只在其中展开变量。

【讨论】:

好的,但那很愚蠢......在php中一个简单的include()解决了这个问题......我真的需要这个,为什么要这样开发? 我不知道为什么但是 xslt != php 恐怕:) @Row Minds 这是您以“好的,但这很愚蠢”开头的回复的精确翻译。就是这样:“我相信每次我想吃一根香蕉时,我的嘴里都没有一根香蕉,这很愚蠢。当我吃 PHP 时,它很好吃”。技术monkey-hoot(在这里替换任何名称,可能是PHP)以您想要的方式实现包含这一事实绝不会导致技术不同的猴子叫声(在这里替换任何名称,可能是xsl)以不同方式实现包含.【参考方案2】:

我有一个小问题,有没有办法动态包含另一个 xsl?例如:

<xsl:variable name="PathToWeb" select="'wewe'"/> 
<xsl:include href="http://$PathToWeb/html/xsl/head.xsl" /> 
<xsl:include href="http://$PathToWeb/html/xsl/navigation.xsl" /> 
<xsl:include href="http://$PathToWeb/html/xsl/promo.xsl" /> 
<xsl:include href="http://$PathToWeb/html/xsl/3columns.xsl" /> 

<xsl:include href="http://$PathToWeb/html/xsl/footer.xsl" />

&lt;xsl:include&gt;href 属性中有变量引用是非法的。根据 W3C XSLT 1.0 和 XSLT 2.0 规范,该属性的值必须是一个 URI 引用。

但是,如果$PathToWeb 变量的值在转换开始之前已知,则可以以多种方式使用它来动态生成样式表表示,其中上述&lt;xsl:include&gt; 语句包含所需的 URI (将$PathToWeb 的引用替换为所需的值后:

    使用 XSLT 从当前样式表生成新样式表

    将样式表加载为 XmlDocument 对象。然后找到相应的&lt;xsl:include&gt; 元素并将它们的href 属性设置为所需的值。最后,使用经过如此修改的表示样式表的 XmlDocument 调用转换。

方法 2. 已在 XPath Visualizer 中使用了 11 年,用于动态设置 select 属性的准确值,该属性用于选择用户输入的 XPath 表达式选择的所有节点并生成一个代表 XML 文档的 HTML 文档,其中突出显示了所有选定和可见的节点。

【讨论】:

我用 XMLSpy 凭经验确定 &lt;xsl:include&gt; 不允许 XPath 表达式作为其 href 属性的值,而 &lt;xsl:result-document&gt; 允许。不过,我在规范中找不到任何支持这一点的东西。两者之间真的有区别,还是错误/专有扩展? (实际上我什至没有使用变量,只是像 'test' 这样的任何表达式。) 这不是错误。 &lt;xsl:result-document&gt;href 属性允许 AVT,目的是允许生成多个结果文档。 &lt;xsl:include&gt;&lt;xsl:result-document&gt; 之间有很大的不同。前者只能在编译时处理,后者在运行时处理。 我不知道有多少编程语言具有在执行期间修改程序的结构,所以如果允许动态 xsl:includes 会相当令人惊讶。至于规范,xsl:include 的规则是 &lt;xsl:include href = uri-reference /&gt; 而 xsl:result-document 的规则是 &lt;xsl:result-document href? = uri-reference :这些花括号很重要,如 2.2 Notation 中所述。 当它的语法不正确时,这个答案如何得到 4 次赞成? 什么是“不正确的语法”?如果您的意思是问题中的引用-是的,这是非法的。但这是问题,不是答案,只是说明这是非法的。【参考方案3】:

我以不同的方式解决了这个问题,可能对使用 Java 和 XSLT 的人有用(此解决方案特定于使用 javax.xml.transform 包的人)。

XSLT 转换器工厂允许设置自定义 URI 解析器。说如果你的 XSLT 看起来像

<?xml version="1.0" encoding="utf-8"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" version="4.0" encoding="UTF-8"/>
    <xsl:include href="import://***.com/xsl"/>
    ...

URI 解析器的resolve 方法将获得import://***.com/xsl 作为href 参数。 import:// 可以作为自定义包含的“特殊”标识符方案,因此您可以检测它并创建/返回指向必要文件的javax.xml.transform.Source。例如:

TransformerFactory tf = TransformerFactory.newInstance();
URIResolver delegate = tf.getURIResolver();
tf.setURIResolver( new CustomURIResolver( delegate ) );

那么,在CustomURIResolver里面:

  public Source resolve( String href, String base )
    throws TransformerException 
    Source result = null;
    URI uri = null;

    try 
      uri = new URI( href );
    
    catch( Exception e ) 
      throw new TransformerException( e );
    

    // The XSLT file has a URI path that allows for a file to be included
    // dynamically.
    if( "import".equalsIgnoreCase( uri.getScheme() ) &&
        "***.com".equalsIgnoreCase( uri.getAuthority() ) ) 
      result = openTemplate();
    
    else 
      result = getDelegate().resolve( href, base );
    

    return result;
  

添加一个 openTemplate() 方法,其中包含动态确定要打开的 XSL 文件的逻辑。

【讨论】:

【参考方案4】:

在 PHP 中,与在其他制度下一样,使用 XSL 样式表是一个多步骤的过程:

1) 从 XSL 文件创建 SimpleXML 或 DOMDocument 对象。

2) 创建一个 XSLTProcessor 对象。

3) 将 XSL 文档对象导入到处理器对象中。

4) 对 XML 数据文件运行转换。

在 1) 之后,可以在作为步骤 3) 的一部分编译之前对 XSL 进行操作。正是在这里,xsl:include 元素可以根据需要从根元素中动态插入。

因此,要动态插入 xsl:includes:

1.1) 使用Xpath|getElementById|getElementsByTagname 检查数据 XML 中是否存在您可能需要额外样式表的元素。

1.2) 从 XSL 的 XML 对象的根元素中动态创建 xsl:include 元素。

就是这样。在第 3 步),修改后的 XSL XML 对象将被编译,就好像它从一开始就是这样构建的。

当然,在 1.2) 中,可以将来自其他 XSL 文档对象的任何节点(不仅仅是 xsl:includexsl:import)添加到基本 XSL 文档对象中的任何节点,从而提供更精细的控制。但是,所有 XSL 样式表的正确 xsl:template 构造应该可以更直接地插入 xsl:include 元素。

【讨论】:

【参考方案5】:

我在一个简单(但有效)的替代方案上价值 2 便士(仅提供伪代码用于说明。谨慎操作:)

方法概要: 另一种解决方案可以包括一个简单的包装脚本(例如 shell、bash 脚本或其他)来调用您的主 xsl、使用名称 xslt 模式、主 xslt 文件、一个简单(空白)静态指定的 xslt 文件。

在主 xsl 中,包含一个静态 xsl 文件,它将调用/加载所有动态包含的 xslt。然后主 xsl 将在 2 种模式下运行:正常模式(未指定模式),它将加载包含在自身中的扩展 xsl 文件,以及在静态 xls 中,并处理任何输入文件,或者做它打算做的任何好事做。第二种模式,预处理器模式,将用于加载动态指定的 xsl 实例/文件。此模式将作为主处理运行的预处理器阶段调用。主 xslt 的处理流程是使用指定的预处理器模式调用它,然后使用指定的正常处理模式再次调用它。

实施提示: 为每个 xlator 定义一个 n 扩展名 xslt 文件 ext_xsl_container ,其目的是包含任何扩展名 xslt。 例如

    <xsl:stylesheet  >
     <!-- main xslt --> 
        <xsl:import href="../xsl/ext_xsl_container.xsl/>
         <!--param: list  of  dynamically specified  extension  xsl --> 
         <xsl:param name="extXslUrlList"/>
        <!--param:preprocessor  mode  flag, with default set to false --> 
        <xsl:param name="preProcModeLoadXslF" select="false()" type="xs:boolean"
<!-- param: path to the staticall included ext_xsl_container: with default  value set --> 
    <xsl:param name="extXslContainerUrl" select="'../xsl/ext_xsl_container.xsl'"/>

        <xsl:if test=" ($preProcModeLoadXslF=true())" >
            <xsl:call-template name="loadDynamicXsl" mode="preprocess_load_xsl"
        </xsl:if>
        ....
    </xsl:stylesheet>

ext_xslt_container 样式表将包含任何扩展 xslt。它可以在运行时通过编辑它(作为 xml 文档)动态更新,为扩展 xsl 样式表添加包含语句。 例如

 <!-- ext xsl container : ext_xsl_container.xsl--> 
<xsl:stylesheet
    <xsl:include href="ext_xsl_container.xsl"/>

    ....
</xsl:stylesheet 

创建一个小模板,比如 template_load_ext_xsl,指定模式,比如 mode="preprocess_load_xsl" 例如

<xsl:template name="loadDynamicXsl" mode="preprocess_load_xsl">
    <!-- param: path to the staticall included ext_xsl_container--> 
    <xsl:param name="extXslContainerUrl"/> 
    <!--param: list  of  dynamically specified  extension  xsl --> 
    <xsl:param name="extXslUrlList"/>

   <!-- step 1, [optional ]  open  the  ext Xsl container  file  --> 
   <!-- step 2  [optional]  clear  contexts of the ext X  -- > 
   <!-- step3  compile a  list of include elements, one  per each ext Xsl file -->
   <!-- step 4 [optional] create a union of the  include  elements  created  with the  content of the xsl container file : ie append  content > 
<!-- step 5 :  write the  union  list of  incudes to the ext XSL  container file -->
<!-- DONE ---> 

</xsl:template>

模板将接受 ex_xsl_container 的名称和扩展 xsl 文件列表(包括它们的路径)作为参数 然后它将作为 xml 文档打开 ext_xsl_container 文件,为每个扩展添加(附加选项或清除文件并添加新代码)语句:xsl,保存文件并退出

接下来,当您以正常执行模式运行主 xsl 时,它将包含模板 loadDynamicXsl,该模板又将包含在运行时指定的扩展 xslt 文件

创建一个简单的包装脚本(例如 bash 或 shell 脚本),它将接受主 xslt 的参数,以及运行预处理器模式的选项。如果启用了预处理器模式选项,脚本将简单地调用主 xslt 两次,并在第一次运行时启用预处理器模式,然后在正常模式下进行第二次调用

【讨论】:

以上是关于在 XSLT 中动态包含其他 XSL 文件的主要内容,如果未能解决你的问题,请参考以下文章

比较 XSL 中的动态驱动值

xslt 不会选择在 XSLT 转换中动态更改名称空间以进行进一步转换

XSLT - 动态添加元素

在 XSLT 中有 URL 时使用 fop 和 XSL 生成 PDF

XSLT

C# 中的 XSLT 转换 - 如何获取包含文档的有效 URL?