【问题标题】:Avoid empty elements in XSLT output避免 XSLT 输出中的空元素
【发布时间】:2014-08-11 08:44:39
【问题描述】:

我想将一个输入 XML 映射到另一个 XML,只需将输入中的值写入输出中的不同标签。

举个简单的例子,如下:

<root1>
  <a1>valA<a1>
  <b1>valB<b2>
</root1>

需要成为:

<root2>
  <a2>valA</a2>
  <b2>valB</b2>
</root2>

目前我的 XSLT 中有以下内容:

<xsl:apply-templates match="root1" />

<xsl:template match="root1">
  <a2>
    <xsl:value-of select="a1" />
  </a2>
  <b2>
    <xsl:value-of select="b1" />
  </b2>
</xsl:template>

问题是我不想在我的输出中出现空标签。如果valAvalB 为空,我会得到:

<root2>
  <a2></a2>
  <b2></b2>
<root2>

但我想省略空标签。我原以为xsl:output 可能有一个属性,但没有......我在 SO 上遇到了这个问题:XSLT: How to exclude empty elements from my result? - 但答案是间接的,它指定了一个 second样式表在第一次转换后去除空的输出元素。

我需要使用一个样式表来完成此操作。肯定有比做更简洁的事情:

<xsl:if test="string-length(a1) != 0">
  <a2>
    <xsl:value-of select="a1" />
  </a2>
</xsl:if>

甚至:

<xsl:template match="a1[string-length(.) != 0]">
  <a2>
    <xsl:value-of select="." />
  </a2>
</xsl:template>

对每个元素重复??

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    在我看来,您的尝试很好,尽管许多人使用而不是测试字符串长度,例如&lt;xsl:template match="a1[normalize-space()]"&gt;&lt;a2&gt;&lt;xsl:value-of select="."/&gt;&lt;/a2&gt;&lt;/xsl:template&gt; 代替。但是如果你需要检查一个元素是否为空,那么你需要一些谓词或测试表达式,没有可以全局打开的设置。

    【讨论】:

    • 感谢您的回答。我发现这是 XSLT 的一个缺点。我不会完成将所有输出包装在此表达式中的艰巨任务,而是将更改输入以便保留标签名称,之后我可以使用身份转换。
    【解决方案2】:

    你可以做的是有一个通用模板匹配任何没有文本的“叶子”元素,然后忽略这样的元素

    <xsl:template match="*[not(*)][not(normalize-space())]" />
    

    您可以将其与仅匹配 a1b1 元素且不带任何条件的模板组合,这样您就可以像当前一样转换为 a2b2

    试试这个 XSLT

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes" />
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="root1">
        <root2>
          <xsl:apply-templates select="@*|node()"/>
        </root2>
      </xsl:template>
    
      <xsl:template match="a1">
        <a2>
          <xsl:apply-templates select="@*|node()"/>
        </a2>
      </xsl:template>
    
      <xsl:template match="b1">
        <b2>
          <xsl:apply-templates select="@*|node()"/>
        </b2>
      </xsl:template>
    
      <xsl:template match="*[not(*)][not(normalize-space())]" />
    </xsl:stylesheet>
    

    请注意 XSLT 标识模板的使用,在您的特定示例中不需要该模板,因为您正在重命名所有元素,但如果您确实希望保持某些元素相同,则会使用该模板。

    【讨论】:

    • 嗯,这与我说的我宁愿避免的第二件事几乎相同......我很欣赏你的努力,你的方法确实有效,但每个元素需要 4 行实在太多了。就像我对 Martin Honnen 说的那样,我将在不同的层面上解决这个问题。我会让输入的标签名称与输出中我需要的匹配,这样我就可以充分利用身份模板。
    • 您的问题是关于避免空元素,还是实际上是关于如何以通用方式重命名大量元素?如果您真的想在每个元素名称的末尾将“1”更改为“2”,那么可能有一个通用模板来执行此操作,而不是为 a1b1 指定单独的模板等等。或者,您可以指定一个可以映射名称的单独 XML 文件,例如 &lt;map&gt;&lt;a1 newname='a2' /&gt;&lt;b1 newname='b2' /&gt;&lt;/map&gt; 并使用它来重命名元素。
    • 实际上根本问题确实是关于重命名许多元素。我也读过你在其他地方建议的方法,我同意这是一个不错的解决方案。不幸的是,我正在制作的地图用于 Microsoft BizTalk Server,尽管数据转换是一项核心功能,但它对 XSLT 的支持却很糟糕。它仅限于 1.0 版,并且在 XSLT 中使用其他 XML 来映射元素名称之类的魔法似乎是不可能的。
    【解决方案3】:

    另一种方法是“延迟”输出输出。 让我解释。通过将输出放入变量中,可以防止输出开始输出,然后使用 copy-of 最终可以输出输出,但要小心使用过滤空标签的 XPath 表达式,如下所示:

    假设您的源 XML 是:

    <root1>
      <a1>valA</a1>
      <b1>valB</b1>
      <c1>valC</c1>
      <d1></d1>
      <e1>valE</e1>
      <f1></f1>
    </root1>
    

    然后您可以将输出放入变量中,而无需关心 xsl:if 检查或模板匹配:

    <xsl:template match="/root1">
        <xsl:variable name="output" as="node()*">
            <a1><xsl:value-of select="a1"/></a1>
            <b1><xsl:value-of select="b1"/></b1>
            <c1><xsl:value-of select="c1"/></c1>
            <d1><xsl:value-of select="d1"/></d1>
            <e1><xsl:value-of select="e1"/></e1>
            <f1><xsl:value-of select="f1"/></f1>
        </xsl:variable>
    
        <root1>
            <xsl:copy-of select="$output[text()]"/>
        </root1>
    </xsl:template>
    

    使用 $output[text()] 允许您只保留具有文本内容的标签。

    此外,如果您想应用于所有标签,您可以使用 xsl:for-each 循环来自动化一点:

    <xsl:template match="/">
        <xsl:variable name="output" as="node()*">
            <xsl:for-each select="./*">
                <xsl:element name="{name(.)}">
                    <xsl:value-of select="."/>
                </xsl:element>
            </xsl:for-each>
        </xsl:variable>
    
        <root1>
            <xsl:copy-of select="$output[text()]"/>
        </root1>
    </xsl:template>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-29
      • 1970-01-01
      • 2018-11-11
      • 1970-01-01
      • 2020-10-09
      • 2016-02-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多