【问题标题】:XSLT: How to exclude empty elements from my result?XSLT:如何从我的结果中排除空元素?
【发布时间】:2010-04-24 21:21:40
【问题描述】:

我有一个相当复杂的 xslt 工作表,它使用模板将一种 xml 格式转换为另一种格式。但是,在生成的 xml 中,我需要排除所有空元素。这是怎么做的?

这是基本 xslt 的样子:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:far="http://www.itella.com/fargo/fargogate/" xmlns:a="http://tempuri.org/XMLSchema.xsd" xmlns:p="http://tempuri.org/XMLSchema.xsd">
    <xsl:import href="TransportCDMtoFDM_V0.6.xsl"/>
    <xsl:import href="ConsignmentCDMtoFDM_V0.6.xsl"/>
    <xsl:template match="/">
        <InboundFargoMessage>
            <EdiSender>
                <xsl:value-of select="TransportInformationMessage/SenderId"/>
            </EdiSender>
            <EdiReceiver>
                <xsl:value-of select="TransportInformationMessage/RecipientId"/>
            </EdiReceiver>
            <EdiSource>
                <xsl:value-of select="TransportInformationMessage/Waybill/Parties/Consignor/Id"/>
            </EdiSource>
            <EdiDestination>FARGO</EdiDestination>
            <Transportations>
                <xsl:for-each select="TransportInformationMessage/TransportUnits/TransportUnit">
                    <xsl:call-template name="transport"/>
                </xsl:for-each>
                <xsl:for-each select="TransportInformationMessage/Waybill/TransportUnits/TransportUnit">
                    <xsl:call-template name="transport"/>
                </xsl:for-each>
                <xsl:for-each select="TransportInformationMessage/Waybill">
                    <EdiImportTransportationDTO>
                        <Consignments>
                            <xsl:for-each select="Shipments/Shipment">
                                <xsl:call-template name="consignment"/>
                            </xsl:for-each>
                        </Consignments>
                        <EdiTerminalDepartureTime>
                            <xsl:value-of select="DatesAndTimes/EstimatedDepartureDateTime"/>
                            <xsl:value-of select="DatesAndTimes/DepartureDateTime"/>
                        </EdiTerminalDepartureTime>
                        <EdiAgentTerminalArrivalDate>
                            <xsl:value-of select="DatesAndTimes/EstimatedArrivalDateTime"/>
                            <xsl:value-of select="DatesAndTimes/ArrivalDateTime"/>
                        </EdiAgentTerminalArrivalDate>
                        <EdiActivevehicle>
                            <xsl:value-of select="Vehicle/TransportShiftNumber"/>
                        </EdiActivevehicle>
                        <EdiConveyerZipCodeTown><xsl:text> </xsl:text></EdiConveyerZipCodeTown>
                    </EdiImportTransportationDTO>
                </xsl:for-each>
            </Transportations>
        </InboundFargoMessage>
    </xsl:template>
</xsl:stylesheet>

需要添加什么,以便将空元素排除在外?

例如,来自生成的 xml 的 sn-p:

<?xml version="1.0" encoding="UTF-8"?>
<InboundFargoMessage xmlns:p="http://tempuri.org/XMLSchema.xsd"
        xmlns:far="http://www.itella.com/fargo/fargogate/"
        xmlns:a="http://tempuri.org/XMLSchema.xsd">
    <EdiSender>XXXX</EdiSender>
    <EdiReceiver>YYYY</EdiReceiver>
    <EdiSource>TR/BAL/IST</EdiSource>
    <EdiDestination>FARGO</EdiDestination>
    <Transportations>
        <EdiImportTransportationDTO>
            <Consignments>
                <EdiImportConsignmentDTO>
                    <ConsignmentLines>
                        <EdiImportConsignmentLineDTO>
                            <DangerousGoodsItems>
                                <EdiImportDangerGoodsItemDTO>
                                    <EdiKolliTypeOuter/>
                                    <EdiKolliTypeInner/>
                                    <EdiTechnicalDescription/>
                                    <EdiUNno/>
                                    <EdiClass/>
                                    <EdiDangerFactor/>
                                    <EdiEmergencyTemperature/>
                                </EdiImportDangerGoodsItemDTO>
                            </DangerousGoodsItems>
                            <BarCodes>
                                <EdiImportConsignmentLineBarcodeDTO/>
                            </BarCodes>
                            <EdiNumberOfPieces>00000002</EdiNumberOfPieces>
                            <EdiGrossWeight>0.000</EdiGrossWeight>
                            <EdiHeight/>
                            <EdiWidth/>
                            <EdiLength/>
                            <EdiGoodsDescription/>
                            <EdiMarkingAndNumber/>
                            <EdiKolliType>road</EdiKolliType>
                            <EdiCbm/>
                            <EdiLdm/>
                        </EdiImportConsignmentLineDTO>

这确实需要:

<?xml version="1.0" encoding="UTF-8"?>
<InboundFargoMessage xmlns:p="http://tempuri.org/XMLSchema.xsd"
        xmlns:far="http://www.itella.com/fargo/fargogate/"
        xmlns:a="http://tempuri.org/XMLSchema.xsd">
    <EdiSender>XXXX</EdiSender>
    <EdiReceiver>YYYY</EdiReceiver>
    <EdiSource>TR/BAL/IST</EdiSource>
    <EdiDestination>FARGO</EdiDestination>
    <Transportations>
        <EdiImportTransportationDTO>
            <Consignments>
                <EdiImportConsignmentDTO>
                    <ConsignmentLines>
                        <EdiImportConsignmentLineDTO>
                            <DangerousGoodsItems/>
                            <BarCodes/>
                            <EdiNumberOfPieces>00000002</EdiNumberOfPieces>
                            <EdiGrossWeight>0.000</EdiGrossWeight>
                            <EdiKolliType>road</EdiKolliType>
                        </EdiImportConsignmentLineDTO>

换句话说:空元素应该被排除在外。

【问题讨论】:

  • 请更具体。您想跳过 for-each 循环中的空节点吗?是否要跳过 value-of 为空白的元素?
  • 我想省略值为空白的元素。我将编辑问题以添加示例...
  • 好问题 (+1)。有关可能是最简单和最基本的 XSLT 解决方案的解决方案,请参阅我的答案。 :)

标签: xml xslt


【解决方案1】:

提供的(部分)XSLT 代码很好地说明了 XSLT 反模式。几乎总是尽量避免使用&lt;xsl:for-each&gt;

下面是一个示例 XML 文档和一个转换,它复制除“空”元素之外的所有节点。这里的“空”是指没有子节点,或者有一个子节点,只有一个空白子节点。

XML 文档

<a>
 <b>
   <c>  </c>
   <d/>
   <e>1</e>
 </b>
</a>

转换

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

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

 <xsl:template match=
  "*[not(node())]
  |
   *[not(node()[2])
   and
     node()/self::text()
   and
     not(normalize-space())
     ]
  "/>
</xsl:stylesheet>

结果:

<a>
   <b>
      <e>1</e>
   </b>
</a>

请注意

  1. 身份规则的使用

  2. 我们如何使用仅匹配“空”元素的模板覆盖身份规则。由于此模板什么都不做(根本没有正文),因此它不会复制(“删除”)“空”元素。

使用和覆盖身份规则是最重要的 XSLT 设计模式。

【讨论】:

  • 我想我更喜欢*[not(*) and not(normalize-space())]。是的,仅包含 cmets 和/或处理指令的元素将被过滤掉,但我猜这可能不是不可取的。也就是说,您对原始 XSLT 反模式的评论是正确的。
  • 一位同事向我传递了一个类似的解决方案,现在看来可行,所以我将此答案标记为 the 解决方案。
  • @Robert-Rossney:我不希望过分在猜测模式下工作——这就是 SO 的目的:用户可以提出新的、更具体的问题,而不是在数十次时间里调整他们原来的问题.感谢您的赞赏。
【解决方案2】:

这可能是最简单的方法:

<xsl:for-each select="Nodes/Node[text() != '']">

</xsl:for-each>

如果您可以控制 XML 生成,那么如果没有子节点,则不要添加根节点。无论您选择哪种方式,XSL 都是相当冗长的。

【讨论】:

  • 但我不是必须将它添加到每个节点吗?这一点都不简单,因为总共有几百个节点!
  • @ChaosPandion。您可能希望将其更正为 for-each。话虽如此,几乎任何使用 xsl:for-each 的解决方案都可以像 Dimitre 的解决方案一样使用 xsl:apply-templates 更好地表述。
  • 如果您的元素包含混合内容,这可能会产生不需要的结果 - &lt;Node&gt;&lt;b&gt;foo&lt;/b&gt;&lt;/Node&gt; 将被过滤掉,因为它没有子文本。
【解决方案3】:

在一些棘手的情况下,Dimitre 的答案(这当然是正确的方法)可能会出现意外行为。例如,如果您重构了 XSLT 以使用身份模式(您应该这样做),并且您创建了这样的模板:

<xsl:template match="Vehicle/TransportShiftNumber[. != '123']">
   <EdiActivevehicle>
      <xsl:value-of select="."/>
   </EdiActivevehicle> 
</xsl:template>

如果TransportShiftNumber 为空,则转换可能仍会创建空的EdiActivevehicle 元素。

通常,如果多个模板与一个节点匹配,则会选择更具体的一个。 “更具体”通常意味着具有谓词的模式将击败没有谓词的模式。 (实际的冲突解决规则更多;参见 XSLT 建议的第 5.5 节。)在这种情况下,上述模板和空元素模板都使用谓词,因此两者具有相同的优先级。

所以 XSLT 处理器将执行以下两种操作之一:它会报告错误(这是允许的,尽管我从未见过不友好的 XSLT 处理器),或者它将选择出现的模板最新 em> 在样式表中。

有两种方法可以解决此问题。要么将空元素过滤模板放在样式表的底部,要么显式为其分配高于 0.5 的优先级(这是大多数具有谓词的模式的默认值):

我可能会做后者,因为我通常在构建样式表时期望模板的顺序并不重要,并且如果我开始移动内容,我不希望出现任何令人讨厌的意外。但我肯定会在其中发表评论解释我自己:我从未见过有人真正在模板上使用明确的优先级。

【讨论】:

    【解决方案4】:

    我从上面的 Dimitre 解决方案开始(谢谢!),但我仍然有带有 null 子级的输出或 null 元素,如下所示:

                     <a>
                        <b>
                           <c/>
                           <d/>           
                        </b>             
                     </a>
    

    这似乎有效...仍在测试中。

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:date="http://exslt.org/dates-and-times" 
      xmlns:exsl="http://exslt.org/common" 
      xmlns:func="http://exslt.org/common"  
      xmlns:random="http://exslt.org/random"
      xmlns:regexp="http://exslt.org/regular-expressions" 
      xmlns:set="http://exslt.org/sets" 
      xmlns:str="http://exslt.org/strings" 
      version="1.0" 
      extension-element-prefixes="date exsl func random regexp set str">
    
      <xsl:output 
        method="xml" 
        encoding="utf-8" 
        omit-xml-declaration="no" 
        indent="yes"/>
    
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="node()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match=
        "*[not(node())]
        |
        *[not(string())]
        "/>
    </xsl:stylesheet>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      • 2021-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-17
      相关资源
      最近更新 更多