RFK
RFK

Reputation: 3

XSLT, mapping id's to new values

I have searched through all the xslt mapping questions but haven't found a solution to my very specific case yet.

So, suppose this is my source XML:

<Data>
  <A val='1'>
    <CC>1234</CC>
  </A>
  <A val='2'>
    <CC>1234</CC>
    <CC>5678</CC>
  </A>
  <A val='3'>
    <CC>1234</CC>
  </A>
  <B val='1'>
    <CC>5678</CC>
  </B>
  <B val='2'>
    <CC>1234</CC>
    <CC>9012</CC>
  </B>
</Data>

What I want as result is the following:

<Data>
  <A val='1'>
    <CC>-1</CC>
  </A>
  <A val='2'>
    <CC>-1</CC>
    <CC>-2</CC>
  </A>
  <A val='3'>
    <CC>-1</CC>
  </A>
  <B val='1'>
    <CC>-2</CC>
  </B>
  <B val='2'>
    <CC>-1</CC>
    <CC>-3</CC>
  </B>
</Data>

If I would code this in some programming language I would first put all the CC-values into a set/collection and then when I know all the possible CC-values I would iterate through the set and replace the value in the XML with a counter that is decreased within the loop. But I have no clue how to do that in XSLT... So in the above example I have the CC-values 1234, 5678 and 9012. When iterating over the values, all CC-values having 1234 should become -1, 5678 ==> -2 and 9012 ==>-3 regardless if CC is under A or B. It would also be ok if 5678 is mapped to -1, 9012 to -2 and 1234 to -3, but all occurences of 1234 must be changed to -3 then and so on. Thanks for any help!

Upvotes: 0

Views: 745

Answers (2)

Tim C
Tim C

Reputation: 70598

Assuming you can use XSLT 2.0, you can use distinct-values to get the distinct values of CC, and then for each CC you can use index-of to find it's position in the distinct list and use that to output the number you want.

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

  <xsl:variable name="distinctCC" select="distinct-values(//CC)" />

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

  <xsl:template match="CC">
    <xsl:copy>
      <xsl:value-of select="0 - index-of($distinctCC, text())" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

And if you can only use XSLT 1.0, you would fall back to using Muenchian Grouping to get distinct values, although finding the index is a little more work too.

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

  <xsl:key name="cc" match="CC" use="." />

  <xsl:variable name="distinctCC" select="//CC[generate-id() = generate-id(key('cc', .)[1])]" />

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

  <xsl:template match="CC">
    <xsl:copy>
      <xsl:variable name="current" select="text()" />
      <xsl:variable name="pos">
          <xsl:for-each select="$distinctCC">
              <xsl:if test="$current = ."><xsl:value-of select="position()" /></xsl:if>
          </xsl:for-each>
      </xsl:variable>
      <xsl:value-of select="0 - $pos" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Joel M. Lamsen
Joel M. Lamsen

Reputation: 7173

you can create a "map" by putting all distinct-values in a variable, such as:

<xsl:variable name="CCcoll">
    <xsl:for-each select="distinct-values(//CC)">
        <CC><xsl:value-of select="."/></CC>
    </xsl:for-each>
</xsl:variable>

add an identity template and a template override for CC nodes:

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

<xsl:template match="CC">
    <xsl:variable name="curr_CC" select="."/>
    <xsl:copy>
        <!-- here, you are matching the
             current value to the "map"
             and getting it's position -->
        <xsl:value-of select="concat('-', $CCcoll/CC[.=$curr_CC]/count(preceding-sibling::*) + 1)"/>
    </xsl:copy>
</xsl:template>

The whole stylesheet is below:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">

    <xsl:variable name="CCcoll">
        <xsl:for-each select="distinct-values(//CC)">
            <CC><xsl:value-of select="."/></CC>
        </xsl:for-each>
    </xsl:variable>

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

    <xsl:template match="CC">
        <xsl:variable name="curr_CC" select="."/>
        <xsl:copy>
            <xsl:value-of select="concat('-', $CCcoll/CC[.=$curr_CC]/count(preceding-sibling::*) + 1)"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions