我可以在处理 XInclude 之前插入默认 xi:fallback 实例吗?

Can I insert a default xi:fallback instance before processing an XInclude?

假设我有一个使用 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 转换以将 xi:fallback 的实例插入每个 xi:include before 处理 XInclude –也就是说,添加默认的 xi:fallback 实例,其中存在 none,然后处理 XInclude,就好像 xi:fallback 实例已经存在一样?

感谢您提供任何建议。

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

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

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

我建议您稍微绕点弯路,分两步解决您的问题:编写一个 XSLT 转换,您应用 没有 XInclude(故意在 XML XML IDE 的解析器首选项,例如 Oxygen 或在命令行上)以修复缺失的回退:

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, an XInclude processor written purely in XSLT 2.0 by Erik Wilde. By importing the XIPr stylesheets into your original XSLT stylesheets you could first provide the missing fallbacks as I've shown you above and then process the result with mode="xipr"。在这种情况下,您应该通过 IDE 或命令行工具禁用任何其他 XInclude 处理。

以下是您的操作方法(是的,有点复杂):

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

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 样式表 here,打开它并替换第 52

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

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

您必须这样做,因为您要求 XIPr XInclude 一个临时树的中间结果。如果在这样的树上使用 document-uri(/),它将 return 一个空序列,这是不允许作为 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>