Elio Copas
Elio Copas

Reputation: 11

Changing line numbers in XSLT 2.0 transformation

I am facing some troubles to solve a client request for their XML transformation. They request that when they receive repeated invoice lines, they want to automatic change the value of the repeated for the next value in the sequence of invoice line numbers, see the input and the expected output below.

Input:

<?xml version="1.0" encoding="UTF-8"?>
    <DatiBeniServizi>
        <DettaglioLinee>
            <NumeroLinea>1</NumeroLinea>
        </DettaglioLinee>
        <DettaglioLinee>
            <NumeroLinea>1</NumeroLinea>
        </DettaglioLinee>
        <DettaglioLinee>
            <NumeroLinea>2</NumeroLinea>
        </DettaglioLinee>
        <DettaglioLinee>
            <NumeroLinea>3</NumeroLinea>
        </DettaglioLinee>
    </DatiBeniServizi>

Expected Output:

<Invoice>
        <InvoiceDetail>
            <InvoiceLineNumber>1</InvoiceLineNumber>
        </InvoiceDetail>
        <InvoiceDetail>
            <InvoiceLineNumber>4</InvoiceLineNumber>
        </InvoiceDetail>
        <InvoiceDetail>
            <InvoiceLineNumber>2</InvoiceLineNumber>
        </InvoiceDetail>
        <InvoiceDetail>
            <InvoiceLineNumber>3</InvoiceLineNumber>
        </InvoiceDetail>
</Invoice>

I tried grouping but I'm not getting the result as they request, it sounds easy but I spent all day trying to solve in vain.

Any help is welcome, thanks for your time guys!

Upvotes: 0

Views: 366

Answers (2)

Tim C
Tim C

Reputation: 70638

Assuming each duplicate gets incremented by 1 each time (so that the first duplicate is 4, then the next 5), one way to do it may be to use a recursive template that processes each item in turn, and increments a parameter when it finds a duplicate).

To find duplicates I have fallen back to using Muenchian Grouping, which is typically used in XSLT 1.0

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:key name="invoices" match="DettaglioLinee" use="NumeroLinea" />

  <xsl:variable name="max" select="max(/DatiBeniServizi/DettaglioLinee/NumeroLinea)" />

  <xsl:template match="DatiBeniServizi">
    <Invoice>
      <xsl:apply-templates select="DettaglioLinee[1]" />
    </Invoice>
  </xsl:template>

  <xsl:template match="DettaglioLinee">
    <xsl:param name="incr" select="1" />

    <xsl:variable name="isDistinct" select="generate-id() = generate-id(key('invoices', NumeroLinea)[1])" />
    <InvoiceDetail>
      <InvoiceLineNumber>
        <xsl:value-of select="if ($isDistinct) then NumeroLinea else $max + $incr" />
      </InvoiceLineNumber>
    </InvoiceDetail>
    <xsl:apply-templates select="following-sibling::*[1]">
      <xsl:with-param name="incr" select="if ($isDistinct) then $incr else $incr + 1" />
    </xsl:apply-templates>
  </xsl:template>
</xsl:stylesheet>

If the duplicates were always going to be consecutive (i.e. you wouldn't have 1, 2, 1 for example), you remove the use of the key, and define the isDistinct variable like so:

<xsl:variable name="isDistinct" select="not(NumeroLinea = preceding-sibling::*[1]/NumeroLinea)" />

EDIT: If you are not worried about the numbers being consecutive (i.e You can have 1, 5, 2, 3 for example), then you just add the duplicate items position onto the max value, which will avoid duplicates

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" indent="yes" />

  <xsl:key name="invoices" match="DettaglioLinee" use="NumeroLinea" />

  <xsl:variable name="max" select="max(/DatiBeniServizi/DettaglioLinee/NumeroLinea)" />

  <xsl:template match="DatiBeniServizi">
    <Invoice>
      <xsl:apply-templates select="DettaglioLinee" />
    </Invoice>
  </xsl:template>

  <xsl:template match="DettaglioLinee">
    <xsl:variable name="isDistinct" select="generate-id() = generate-id(key('invoices', NumeroLinea)[1])" />
    <InvoiceDetail>
      <InvoiceLineNumber>
        <xsl:value-of select="if ($isDistinct) then NumeroLinea else $max + position()" />
      </InvoiceLineNumber>
    </InvoiceDetail>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 2

Alejandro
Alejandro

Reputation: 1882

This is mixing a grouping problem with a numbering problem.

This XSLT 1.0 stylesheet

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kNumeroLineaByValue" match="NumeroLinea" use="." />
    <xsl:variable name="vNumeroLineaDistinct"
        select="//NumeroLinea[generate-id()=generate-id(key('kNumeroLineaByValue',.))]" />
    <xsl:variable name="vNumeroLineaDistinctCount"
        select="count($vNumeroLineaDistinct)" />
    <xsl:variable name="vNumeroLineaLastValue">
        <xsl:for-each select="$vNumeroLineaDistinct">
            <xsl:sort data-type="number" order="descending" />
            <xsl:if test="position()=1">
                <xsl:value-of select="." />
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="NumeroLinea/text()">
        <xsl:choose>
            <xsl:when
                test="count(..|$vNumeroLineaDistinct)!=$vNumeroLineaDistinctCount">
                <xsl:variable name="vPosition">
                    <xsl:number level="any"
                        count="NumeroLinea[.=preceding::NumeroLinea]" />
                </xsl:variable>
                <xsl:value-of
                    select="$vPosition + $vNumeroLineaLastValue" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

And this input

<DatiBeniServizi>
    <DettaglioLinee>
        <NumeroLinea>1</NumeroLinea>
    </DettaglioLinee>
    <DettaglioLinee>
        <NumeroLinea>1</NumeroLinea>
    </DettaglioLinee>
    <DettaglioLinee>
        <NumeroLinea>2</NumeroLinea>
    </DettaglioLinee>
    <DettaglioLinee>
        <NumeroLinea>3</NumeroLinea>
    </DettaglioLinee>
    <DettaglioLinee>
        <NumeroLinea>3</NumeroLinea>
    </DettaglioLinee>
    <DettaglioLinee>
        <NumeroLinea>5</NumeroLinea>
    </DettaglioLinee>
</DatiBeniServizi>

Result:

<DatiBeniServizi>
      <DettaglioLinee>
            <NumeroLinea>1</NumeroLinea>
      </DettaglioLinee>
      <DettaglioLinee>
            <NumeroLinea>6</NumeroLinea>
      </DettaglioLinee>
      <DettaglioLinee>
            <NumeroLinea>2</NumeroLinea>
      </DettaglioLinee>
      <DettaglioLinee>
            <NumeroLinea>3</NumeroLinea>
      </DettaglioLinee>
      <DettaglioLinee>
            <NumeroLinea>7</NumeroLinea>
      </DettaglioLinee>
      <DettaglioLinee>
            <NumeroLinea>5</NumeroLinea>
      </DettaglioLinee>
</DatiBeniServizi>

Note: Grouping for deduplication and then numbering from a starting value (the maximun from all the distinct values)

Upvotes: 1

Related Questions