简体   繁体   中英

XSLT for-each loop

I have a very tricky XSLT question. Imagine I have the following input:

<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>

and as an output I would like to have this:

<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>

Where I can have an unlimited number of lines but each line has maximum of 3 items. I've tried the following code:

<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>

If the ID's are different it works well but the problem is when I have the same ID values (as is in the example). Anyone can help me with this?

Thanks.

In general, avoid usng value-of and learn about apply-templates, and this will become a really easy XSLT question. Your output is not well-formed XML by the way, as it doesn't have a single root wrapper element.

<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>

starts-with() may help...

XML Input

<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 Output (not well-formed)

<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>

This transformation :

<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>

when applied on the provided XML document :

<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>

produces the wanted, correct result:

<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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM