XSLT - Select 来自基于地图的模板的节点

XSLT - Select node from templates based on map

这是我为解释目的而编写的示例xml内容,

<doc>
    <array>
        <?Table_type Small?>
        <table>
            <data>text</data>
        </table>
    </array>

    <array>
        <?Table_type Medium?>
        <table>
            <data>text</data>
        </table>
    </array>

    <array>
        <?Table_type Large?>
        <table>
            <data>text</data>
        </table>
    </array>
</doc>

这里可以识别3种类型的table,table包含处理指令值Small、Medium和Large。 (在实际文档中有更多 tables 类型)

我想做的是,通过检查table类型(使用处理指令识别)将size属性添加到<data>节点。

由于原始文档中存在大量 table 类型,我使用以下 XSL 方法来完成此任务,

<xsl:variable name="table.type.mapping">
        <map parent='Small' mapto='10'/>
        <map parent='Medium' mapto='20'/>
        <map parent='Large' mapto='30'/>
    </xsl:variable>

    <xsl:template match="data[ancestor::array/processing-instruction('Table_type')=$table.type.mapping/map[@parent=ancestor::array/processing-instruction('Table_type')]/@parent]">
        <data size="{$table.type.mapping/map[@parent=ancestor::array/processing-instruction('Table_type')]/@mapto}">
            <xsl:apply-templates/>
        </data>
    </xsl:template>

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

但它并没有从地图中读取相应的属性值并放置在相应的<data>节点中。

我的预期输出是,

<doc>
    <array>
        <?Table_type Small?>
        <table>
            <data size="10">text</data>
        </table>
    </array>

    <array>
        <?Table_type Medium?>
        <table>
            <data size="20">text</data>
        </table>
    </array>

    <array>
        <?Table_type Large?>
        <table>
            <data size="30">text</data>
        </table>
    </array>
</doc>

有什么建议可以更改我的模板以从地图中读取正确的值并将正确的属性添加到 <data> 节点吗?

您掉进了 Resulting tree fragment 的陷阱,这是 XSLT-1.0 的一个严重问题。

避免这种情况的一种方法是将 <xsl:variable> 转换为 XSLT 文件中的数据岛。这个数据岛需要一个我称为 var 的自己的命名空间,必须使用 exclude-result-prefixes 指令从结果中明确排除它。

此外,我确实使用了两个变量来避免 XML 和 XSLT 命名空间之间的命名空间冲突。

仅供参考:document('')/xsl:stylesheet 确实引用了当前 XSLT 文档的 XML 根节点。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:var="some.var" exclude-result-prefixes="var">
  <xsl:output method="xml" indent="yes" />

  <var:tabletypemapping>
        <map parent='Small' mapto='10'/>
        <map parent='Medium' mapto='20'/>
        <map parent='Large' mapto='30'/>
  </var:tabletypemapping>

  <xsl:template match="data">
    <xsl:variable name="procIns" select="ancestor::array/processing-instruction('Table_type')" />
    <xsl:variable name="mapIns"  select="document('')/xsl:stylesheet/var:tabletypemapping/map[@parent = $procIns]/@mapto" />
    <data size="{$mapIns}">
      <xsl:apply-templates/>
    </data>
  </xsl:template>

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

</xsl:stylesheet>

输出为:

<?xml version="1.0"?>
<doc>
    <array>
        <?Table_type Small?>
        <table>
            <data size="10">text</data>
        </table>
    </array>

    <array>
        <?Table_type Medium?>
        <table>
            <data size="20">text</data>
        </table>
    </array>

    <array>
        <?Table_type Large?>
        <table>
            <data size="30">text</data>
        </table>
    </array>
</doc>

您可能不需要在匹配本身中使用复杂的 xpath 表达式,尤其是当所有 data 元素之前都有一个有效的处理指令时。

主要问题在于用于计算 size 属性的 AVT。它正在寻找 map 元素的祖先,而不是当前 data 元素。应该是这个...

<data size="{$table.type.mapping/map[@parent=current()/ancestor::array/processing-instruction('Table_type')]/@mapto}">

试试这个 XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="table.type.mapping">
        <map parent='Small' mapto='10'/>
        <map parent='Medium' mapto='20'/>
        <map parent='Large' mapto='30'/>
    </xsl:variable>

    <xsl:template match="data">
        <data size="{$table.type.mapping/map[@parent=current()/ancestor::array/processing-instruction('Table_type')]/@mapto}">
            <xsl:apply-templates/>
        </data>
    </xsl:template>

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

如果某些 data 元素没有相应的处理指令,请改用此 XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="table.type.mapping">
        <map parent='Small' mapto='10'/>
        <map parent='Medium' mapto='20'/>
        <map parent='Large' mapto='30'/>
    </xsl:variable>

    <xsl:template match="data">
        <data>
            <xsl:variable name="size" select="$table.type.mapping/map[@parent=current()/ancestor::array/processing-instruction('Table_type')]/@mapto" />
            <xsl:if test="$size">
                <xsl:attribute name="size" select="$size" />
            </xsl:if>
            <xsl:apply-templates/>
        </data>
    </xsl:template>

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

通过使用 key 进行查找,可以非常方便(高效)地完成此操作:

XSLT 2.0

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

<xsl:variable name="table.type.mapping">
    <map parent='Small' mapto='10'/>
    <map parent='Medium' mapto='20'/>
    <map parent='Large' mapto='30'/>
</xsl:variable>

<xsl:key name="size" match="map" use="@parent" />

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

<xsl:template match="data">
    <data size="{key('size', ancestor::array/processing-instruction('Table_type'), $table.type.mapping)/@mapto}">
        <xsl:apply-templates/>
    </data>
</xsl:template>

</xsl:stylesheet>