Search code examples
xsltgroup-bycountsequenceedi

XSLT 2.0 create counter through grouping


I am trying to create a counter to grouped elements.

Source:

<?xml version="1.0" encoding="UTF-8"?>
<root_com>
    <root_por-out>
        <is_globalprocessid>1370284</is_globalprocessid>
        <is_processid>1370284</is_processid>
        <partneridentcode>123456</partneridentcode>
        <por-out>
            <por_number>320060916</por_number>
            <order_pos>10</order_pos>
            <order_pos_partner>10</order_pos_partner>
        </por-out>
        <por-out>
            <por_number>320060916</por_number>
            <order_number_partner>875421</order_number_partner>
            <order_pos>20</order_pos>
            <order_pos_partner>20</order_pos_partner>
        </por-out>
        <por-out>
            <por_number>320060916</por_number>
            <order_pos>30</order_pos>
            <order_pos_partner>10</order_pos_partner>
        </por-out>
                <por-out>
            <por_number>320060916</por_number>
            <order_pos>40</order_pos>
            <order_pos_partner>30</order_pos_partner>
        </por-out>
        <por-out>
            <por_number>320060916</por_number>
            <order_pos>50</order_pos>
            <order_pos_partner>10</order_pos_partner>
        </por-out>
    </root_por-out>
</root_com>

Desired output:

<Confirmation>
    <Settings>
        <DecimalSymbol>.</DecimalSymbol>
    </Settings>
    <Orders>
        <Order>
            <OrderIdSupplier>320060916</OrderIdSupplier>
            <OrderItems>
                <OrderItem>
                    <LineNumber>10</LineNumber>
                    <ItemSubNo>1</ItemSubNo>
                </OrderItem>
                <OrderItem>
                    <LineNumber>20</LineNumber>
                    <ItemSubNo>1</ItemSubNo>
                </OrderItem>
                <OrderItem>
                    <LineNumber>10</LineNumber>
                    <ItemSubNo>2</ItemSubNo>
                </OrderItem>
                <OrderItem>
                    <LineNumber>30</LineNumber>
                    <ItemSubNo>1</ItemSubNo>
                </OrderItem>
                <OrderItem>
                    <LineNumber>10</LineNumber>
                    <ItemSubNo>3</ItemSubNo>
                </OrderItem>
            </OrderItems>
        </Order>
    </Orders>
</Confirmation>

The Code i currently have:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:formatter="com.inubit.ibis.xsltext.Formatter" version="2.0" exclude-result-prefixes="formatter">
  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:template match="/"><xsl:for-each select="root_com/root_por-out"><Confirmation>
    <Settings>
        <DecimalSymbol>.</DecimalSymbol>
    </Settings>
    <Orders>
        <Order>
            <PurchaseNo><xsl:value-of select="por-out[1]/order_number_partner"/></PurchaseNo>
            <SupplierId><xsl:value-of select="partneridentcode"/></SupplierId>
            <OrderIdSupplier><xsl:value-of select="por-out[1]/por_number"/></OrderIdSupplier>
            <OrderItems><xsl:for-each select="por-out/order_pos"><xsl:for-each-group select="../order_pos_partner" group-by="text()"><OrderItem>
                    <DeliveryDate><xsl:value-of select="delivery_date"/></DeliveryDate>
                                <LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber><ItemSubNo><xsl:value-of select="last()"/></ItemSubNo>
                    <ProductId><xsl:value-of select="article_partner"/></ProductId>
                    <Price><xsl:value-of select="price"/></Price>
                    <PriceFactor>1</PriceFactor>
                    <Quantity><xsl:value-of select="quantity"/></Quantity>
                </OrderItem></xsl:for-each-group></xsl:for-each>
                
            </OrderItems>
        </Order>
    </Orders>
</Confirmation></xsl:for-each></xsl:template>
</xsl:stylesheet>

Code reduced to the core issue:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:formatter="com.inubit.ibis.xsltext.Formatter" version="2.0" exclude-result-prefixes="formatter">
  <xsl:output method="xml" encoding="UTF-8"/>
  <xsl:template match="/"><xsl:for-each select="root_com/root_por-out"><Confirmation>
    <Settings>
        <DecimalSymbol>.</DecimalSymbol>
    </Settings>
    <Orders>
        <Order>
            <OrderItems><xsl:for-each select="por-out/order_pos"><xsl:for-each-group select="../order_pos_partner" group-by="text()"><OrderItem>
                        <LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber><ItemSubNo><xsl:value-of select="last()"/></ItemSubNo>
                </OrderItem></xsl:for-each-group></xsl:for-each>
                
            </OrderItems>
        </Order>
    </Orders>
</Confirmation></xsl:for-each></xsl:template>
</xsl:stylesheet>

What the point is: The customer sends an order. We provide a response. Since high amounts are ordered, we need to delivere the ordered parts on different dates. We split original positions. In our reply customer gets a reference to his original order position(order_pos_partner) and customer also needs a counter for amount of split positions(destination: "ItemSubNo")

how can I do that?

If output is sorted by positions, that is fine, but not needed.

Thank you


Solution

  • If you use

      <xsl:template match="root_por-out">
        <Confirmation>
            <Orders>
              <Order>
                <OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
                <xsl:for-each-group select="por-out" group-by="order_pos_partner">
                  <xsl:apply-templates select="current-group()"/>
                </xsl:for-each-group>
              </Order>
            </Orders>
        </Confirmation>
      </xsl:template>
    
      <xsl:template match="por-out">
        <OrderItem>
          <LineNumber>{current-grouping-key()}</LineNumber>
          <ItemSubNumber>{position()}</ItemSubNumber>
        </OrderItem>    
      </xsl:template>
    

    you will get

            <OrderItem>
               <LineNumber>10</LineNumber>
               <ItemSubNumber>1</ItemSubNumber>
            </OrderItem>
            <OrderItem>
               <LineNumber>10</LineNumber>
               <ItemSubNumber>2</ItemSubNumber>
            </OrderItem>
            <OrderItem>
               <LineNumber>10</LineNumber>
               <ItemSubNumber>3</ItemSubNumber>
            </OrderItem>
            <OrderItem>
               <LineNumber>20</LineNumber>
               <ItemSubNumber>1</ItemSubNumber>
            </OrderItem>
            <OrderItem>
               <LineNumber>30</LineNumber>
               <ItemSubNumber>1</ItemSubNumber>
            </OrderItem> 
    

    So I think that has the right numbers, although not the ordering you showed.

    To preserve the original input order, you could use the grouping only to store the right sequences and them map then later on:

      <xsl:template match="root_por-out">
        <Confirmation>
            <Orders>
              <Order>
                <OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
                <xsl:variable name="groups" as="map(*)">
                  <xsl:map>
                    <xsl:for-each-group select="por-out" group-by="order_pos_partner">
                      <xsl:map-entry key="current-grouping-key()" select="current-group() ! generate-id()"/>
                    </xsl:for-each-group>                
                  </xsl:map>
                </xsl:variable>
                <xsl:apply-templates select="*">
                  <xsl:with-param name="groups" select="$groups"/>
                </xsl:apply-templates>
              </Order>
            </Orders>
        </Confirmation>
      </xsl:template>
      
      <xsl:template match="por-out">
        <xsl:param name="groups"/>
        <OrderItem>
          <LineNumber>{order_pos_partner}</LineNumber>
          <ItemSubNumber>{index-of($groups(order_pos_partner), generate-id())}</ItemSubNumber>
        </OrderItem>    
      </xsl:template>
    

    Both examples use XSLT 3 although with some more verbosity (<LineNumber>{order_pos_partner}</LineNumber> instead of <LineNumber><xsl:value-of select="current-grouping-key()"/></LineNumber>or XML data structures instead of light-weight maps

      <xsl:key name="group" match="group" use="@key"/>
      
      <xsl:template match="root_por-out">
        <Confirmation>
            <Orders>
              <Order>
                <OrderIdSupplier>{por-out[1]/por_number}</OrderIdSupplier>
                <xsl:variable name="groups">
                    <xsl:for-each-group select="por-out" group-by="order_pos_partner">
                      <group key="{current-grouping-key()}">
                        <xsl:for-each select="current-group()">
                          <item>
                            <xsl:value-of select="generate-id()"/>
                          </item>
                        </xsl:for-each>
                      </group>
                    </xsl:for-each-group>                
                </xsl:variable>
                <xsl:apply-templates select="*">
                  <xsl:with-param name="groups" select="$groups"/>
                </xsl:apply-templates>
              </Order>
            </Orders>
        </Confirmation>
      </xsl:template>
      
      <xsl:template match="por-out">
        <xsl:param name="groups"/>
        <OrderItem>
          <LineNumber>
            <xsl:value-of select="order_pos_partner"/>
          </LineNumber>
          <ItemSubNumber>
            <xsl:value-of select="index-of(key('group', order_pos_partner, $groups)/item, generate-id())"/>
          </ItemSubNumber>
        </OrderItem>    
      </xsl:template>
    

    it could be done in XSLT 2.