【问题标题】:XSLT for-each loopXSLT for-each 循环
【发布时间】:2012-08-30 18:43:37
【问题描述】:

我有一个非常棘手的 XSLT 问题。 想象一下我有以下输入:

<OtherCharges>        
<LineId>
    <Id>P</Id>
</LineId>
<Items>
    <Item1>1</Item1>
    <Item2>2</Item2>
    <Item3>3</Item3>  
</Items>    
<LineId>
    <Id>P</Id>
</LineId>
<Items>
    <Item1>4</Item1>
</Items>
<LineId>
    <Id>P</Id>
</LineId>
<Items>
    <Item1>5</Item1>
    <Item2>6</Item2>    
</Items>
</OtherCharges>

作为一个输出,我想要这个:

<OtherCharges>
  <LineId>P</LineId>
  <OtherChargesValues>
    <value>1</value>
    <value>2</value>
    <value>3</value>
  </OtherChargesValues>
</OtherCharges>
<OtherCharges> 
  <LineId>P</LineId>
  <OtherChargesValues>
    <value>4</value>
  </OtherChargesValues>
</OtherCharges>
<OtherCharges> 
  <LineId>P</LineId>
  <OtherChargesValues>
    <value>5</value>
    <value>6</value>
  </OtherChargesValues>
</OtherCharges>

我可以有无限数量的行,但每行最多有 3 个项目。 我试过以下代码:

<xsl:for-each select="/OtherCharges/LineId">
<Id>
    <xsl:value-of select="Id"/>
<Id>
<xsl:variable name="ChargeLine" select="."/>
<xsl:for-each select="following-sibling::Items[preceding-sibling::LineId[1] = $ChargeLine]">    
    <xsl:if test="name(.)='Items'">
        <xsl:if test="Item1">
            <value>
                <xsl:value-of select="Item1"/>
            </value>
        </xsl:if>  
        <xsl:if test="Item2">
            <value>
                <xsl:value-of select="Item2"/>
            </value>
        </xsl:if> 
        <xsl:if test="Item3">
            <value>
                <xsl:value-of select="Item3"/>
            </value>
        </xsl:if>
    </xsl:if>
</xsl:for-each>

如果 ID 不同,它工作得很好,但问题是当我有相同的 ID 值时(如示例中所示)。 任何人都可以帮助我吗?

谢谢。

【问题讨论】:

  • 注意:感谢您的回答,但输出错误。我对此感到非常抱歉。确实,它似乎有一个简单的解决方案,但我对此不是很有经验。谢谢。

标签: xml xslt


【解决方案1】:

这种转变

<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:key name="kFollowing" match="*[not(self::LineId)]"
  use="generate-id(preceding-sibling::LineId[1])"/>

 <xsl:template match="LineId">
  <OtherCharges>
     <LineId><xsl:value-of select="."/></LineId>
     <OtherChargesValues>
       <xsl:apply-templates mode="inGroup"
            select="key('kFollowing', generate-id())"/>
     </OtherChargesValues>
  </OtherCharges>
 </xsl:template>

 <xsl:template match="Items/*" mode="inGroup">
  <value><xsl:value-of select="."/></value>
 </xsl:template>
 <xsl:template match="text()"/>
</xsl:stylesheet>

应用于提供的 XML 文档时

<OtherCharges>
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>1</Item1>
        <Item2>2</Item2>
        <Item3>3</Item3>
    </Items>
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>4</Item1>
    </Items>
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>5</Item1>
        <Item2>6</Item2>
    </Items>
</OtherCharges>

产生想要的正确结果:

<OtherCharges>
   <LineId>P</LineId>
   <OtherChargesValues>
      <value>1</value>
      <value>2</value>
      <value>3</value>
   </OtherChargesValues>
</OtherCharges>
<OtherCharges>
   <LineId>P</LineId>
   <OtherChargesValues>
      <value>4</value>
   </OtherChargesValues>
</OtherCharges>
<OtherCharges>
   <LineId>P</LineId>
   <OtherChargesValues>
      <value>5</value>
      <value>6</value>
   </OtherChargesValues>
</OtherCharges>

【讨论】:

  • 效果很好!我实际上不知道 generate-id()。谢谢。
  • '...是在 XSLT 1.0 中测试节点身份的唯一方法'
  • @user295744,不客气。 generate-id() 是在 XSLT 1.0 中测试节点身份的唯一方法。在 XPath 2.0 (XSLT 2.0) 中有 is 运算符,其唯一语义是确认两个节点之间的身份。 ——
  • @MiMo,感谢您注意到这一点。评论中的错字现已修正。
  • @user295744,实际上在 XPath 1.0 中检查节点身份至少还有另一种方法:count($n1|$n2) = 1
【解决方案2】:

一般来说,避免使用 value-of 并了解 apply-templates,这将成为一个非常简单的 XSLT 问题。顺便说一下,您的输出不是格式良好的 XML,因为它没有单个根包装元素。

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

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

<xsl:template match="OtherCharges/LineId">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="OtherCharges/Items">
  <xsl:apply-templates/>
</xsl:template>

<!--* now the real work *-->
<xsl:template match="OtherCharges/LineId/Id">
  <xsl:copy-of select="." />
</xsl:template>

<xsl:template match="OtherCharges/Items/*">
  <value><xsl:apply-templates/></value>
</xsl:template>

【讨论】:

  • 感谢您的回答,但输出错误。我对此感到非常抱歉。我已经编辑了输出。
【解决方案3】:

starts-with() 可能会有所帮助...

XML 输入

<OtherCharges>        
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>1</Item1>
        <Item2>2</Item2>
        <Item3>3</Item3>  
    </Items>    
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>4</Item1>
    </Items>
    <LineId>
        <Id>P</Id>
    </LineId>
    <Items>
        <Item1>5</Item1>
        <Item2>6</Item2>    
    </Items>
</OtherCharges>

XSLT 1.0

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

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

    <xsl:template match="Items/*[starts-with(name(),'Item')]">
        <value>
            <xsl:apply-templates select="@*|node()"/>
        </value>
    </xsl:template>

</xsl:stylesheet>

XML 输出(格式不正确)

<Id>P</Id>
<value>1</value>
<value>2</value>
<value>3</value>
<Id>P</Id>
<value>4</value>
<Id>P</Id>
<value>5</value>
<value>6</value>

【讨论】:

  • 它不会产生问题中要求的输出:元素应该被称为value,而不是Item1Item2等。
  • @MiMo - 我没有注意到这一点。不错的收获。我会在有时间的时候尝试更新。谢谢!
  • 感谢您的回答,但输出错误。我对此感到非常抱歉。我已经编辑了输出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多