2

假设我有一个使用 XIncludes 的源 XML 文档,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
   <xi:include href="child01.xml"/>
   <xi:include href="child02.xml"/>
   <xi:include href="child03.xml"/>
</parent>

它在 XIncludes 中调用的另外三个 XML 文档如下所示:

child01.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child01">
      <p>This is child 1.</p>
   </child>
</children>

child02.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child02">
      <p>This is child 2.</p>
   </child>
</children>

child03.xml:

<?xml version="1.0" encoding="UTF-8"?>
<children>
   <child xml:id="child03">
      <p>This is child 3.</p>
   </child>
</children>

我有一个这样的 XSLT 2.0 转换:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:output method="xml" encoding="UTF-8" indent="yes"/>

   <xsl:strip-space elements="*"/>

   <xsl:template match="/">
      <xsl:apply-templates select="parent"/>
   </xsl:template>

   <xsl:template match="parent">
      <volume>
         <xsl:apply-templates select="@*|.//child"/>
      </volume>
   </xsl:template>

   <xsl:template match="child">
      <chapter>
         <xsl:apply-templates select="@*|*|text()"/>
      </chapter>
   </xsl:template>

   <xsl:template match="@*|*|text()">
      <xsl:copy copy-namespaces="no">
         <xsl:apply-templates select="@*|*|text()"/>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

当 XIncludes 引用的所有文件都存在于与 parent01.xml 相同的文件夹中时,我的转换工作正常,并产生以下输出:

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter xml:id="child01">
      <p>This is child 1.</p>
   </chapter>
   <chapter xml:id="child02">
      <p>This is child 2.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>

但是,如果缺少一个文件(例如 child02.xml),则转换将失败。

如果 parent01.xml 包含 xi:fallback 元素,则可以防止此失败,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
   <xi:include href="child01.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
   <xi:include href="child02.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
   <xi:include href="child03.xml">
      <xi:fallback>
         <child>
            <p>The file is missing.</p>
         </child>
      </xi:fallback>
   </xi:include>
</parent>

然后,输出将如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter xml:id="child01">
      <p>This is child 1.</p>
   </chapter>
   <chapter>
      <p>The file is missing.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>

我的问题是:是否可以编写我的 XSLT 转换以在处理 XInclude之前将 xi:fallback 的实例插入到每个 xi:include中——也就是说,在不存在的情况下添加默认的 xi:fallback 实例,然后处理XInclude 好像那个 xi:fallback 实例已经存在?

感谢您提供任何建议。

4

1 回答 1

1

将我的评论扩展到完整答案,因为这是一个有趣的问题!

XSLT 转换不直接对 XML 文档的文本内容进行操作,而是对内容的树状表示(DOM、XDM)进行操作。输入的这种表示或模型由 XML 解析器提供,理论上,它可以完全独立于 XSLT 处理器。

现在重要的一点是:XML 解析器负责执行 XInclusion,而不是 XSLT 处理器。一旦 XSLT 处理器看到文档模型,就无法知道是否发生了 XInclusions。不,据我所知,在单个 XSLT 转换步骤中 XInclude 之前和之后都无法访问文档树。您可以在不同的模式下两次处理相同的输入节点,但您还需要能够从 XSLT 转换中控制 XML 解析器的 XInclude 功能,这是不可能的。

我建议您绕道而行,分两步解决您的问题:编写一个您在不使用 XInclude 的情况下应用的 XSLT 转换(故意在您的 XML IDE 的 XML解析器首选项中或在命令行中将其关闭)以修复缺失后备:

XSLT 修复回退

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    version="2.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="xi:include[not(xi:fallback)]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xi:fallback>
                <child>
                    <p>The file is missing.</p>
                </child>
            </xi:fallback>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

之后,临时输出文件将如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
    <xi:include href="child01.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
    <xi:include href="child02.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
    <xi:include href="child03.xml">
        <xi:fallback>
            <child>
                <p>The file is missing.</p>
            </child>
        </xi:fallback>
    </xi:include>
</parent>

然后应用您已经拥有的第二个转换,但在此之前再次打开 XInclude。这样,在第二次转换发生之前,丢失的文件将被其后备内容替换。


如果这对您来说是不可接受的,您可以查看XIPr,一个由 Erik Wilde 完全用 XSLT 2.0 编写的 XInclude 处理器。通过将 XIPr 样式表导入到您的原始 XSLT 样式表中,您可以首先提供缺少的回退,正如我在上面向您展示的那样,然后使用mode="xipr". 在这种情况下,您应该通过 IDE 或命令行工具禁用任何其他 XInclude 处理。

这是你可以做到的(是的,它变得有点复杂):

首先,href指向您的文件的属性必须是绝对的,因为 XIPr 处理器的特殊性:

XML 输入

<?xml version="1.0" encoding="UTF-8"?>
<parent xmlns:xi="http://www.w3.org/2001/XInclude" xml:id="parent01">
    <xi:include href="file:/Users/User/Desktop/child01.xml"/>
    <xi:include href="file:/Users/User/Desktop/child02.xml"/>
    <xi:include href="file:/Users/User/Desktop/child03.xml"/>
</parent>

XSLT 样式表

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    exclude-result-prefixes="xi">

    <xsl:import href="xipr.xsl"/>

    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <xsl:variable name="fixedfallbacks">
            <xsl:apply-templates select="." mode="fixfallbacks"/>
        </xsl:variable>
        <xsl:variable name="xincluded">
            <xsl:apply-templates select="$fixedfallbacks" mode="xipr"/>
        </xsl:variable>
        <xsl:apply-templates select="$xincluded/*" mode="#default"/>
    </xsl:template>

    <xsl:template match="xi:include[not(xi:fallback)]" mode="fixfallbacks">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="fixfallbacks"/>
            <xi:fallback>
                <child>
                    <p>The file is missing.</p>
                </child>
            </xi:fallback>
        </xsl:copy>
    </xsl:template>

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

    <xsl:template match="parent">
        <volume>
            <xsl:apply-templates select="@*|.//child"/>
        </volume>
    </xsl:template>

    <xsl:template match="child">
        <chapter>
            <xsl:apply-templates select="@*|*|text()"/>
        </chapter>
    </xsl:template>

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

</xsl:stylesheet>

在执行此之前,您必须关闭 XML 解析器的 XInclude 选项,在此处下载 XIPr 样式表,打开它并替换第 52 行

<xsl:variable name="include-uri" select="resolve-uri(@href, document-uri(/))"/>

<xsl:variable name="include-uri" select="resolve-uri(@href)"/>

您必须这样做,因为您要求 XIPr 包含一个临时树的中间结果。如果document-uri(/)在这样的树上使用,它将返回一个空序列,不允许将其作为 的第二个参数resolve-uri()

现在,最后,如果其中一个文件不存在,结果将是

最终 XML 输出

<?xml version="1.0" encoding="UTF-8"?>
<volume xml:id="parent01">
   <chapter>
      <p>The file is missing.</p>
   </chapter>
   <chapter xml:id="child02">
      <p>This is child 2.</p>
   </chapter>
   <chapter xml:id="child03">
      <p>This is child 3.</p>
   </chapter>
</volume>
于 2016-01-04T19:27:25.747 回答