Consolidating data and excluding duplicates in XSLT

I have an incoming XML document that contains some nodes of the form:

<userOptions>
  <userOption>
      <type>A</type>
      <plan>
          <frequency>MONTHLY</frequency>
      </plan>
      <method>
          <methodType>X1</methodType>
      </method>
  </userOption>

  <userOption>
      <type>B</type>
      <plan>
          <frequency>QUARTERLY</frequency>
      </plan>
      <method>
          <methodType>X2</methodType>
      </method>
  </userOption>

  <userOption>
    <type>A</type>
      <plan>
        <frequency>3MONTH</frequency>
      </plan>
      <method>
        <methodType>X1-B</methodType>
      </method>
  </userOption>
</userOptions>

I need to consolidate and transform this into something that looks like this:

<userOptions>
    <userOption>
      <type>A</type>
      <plan>
         <frequency>3MONTH</frequency>
         <methodType>X1-B</methodType>
      </plan>
      <plan>
         <frequency>MONTHLY</frequency>
         <methodType>X1</methodType>
      </plan>
    <userOption>
    <userOption>
      <type>B</type>
      <plan>
         <frequency>QUARTERLY</frequency>
         <methodType>X2</methodType>
      </plan>
    <userOption>
</userOptions>

My current transformation looks like this:

<userOptions>
    <xsl:for-each select="//userOptions">
        <userOption>
            <type>
                <xsl:value-of select="type" />
            </type>

            <xsl:variable name="currentType" select="type"/>
            <xsl:message><xsl:value-of select="$currentType"/></xsl:message>

            <xsl:variable name="currentMethod" select="method/methodType"/>
            <xsl:message><xsl:value-of select="$currentMethod/></xsl:message>

            <plan>
                <frequency>
                    <xsl:value-of select="plan/frequency" />
                </frequency>
                <method>
                    <xsl:value-of select="method/methodType" />
                </method>
            </plan>

            <xsl:for-each select="../userOptions[type=$currentType and method/methodType!=$currentMethod]">

                <plan>
                    <frequency>
                        <xsl:value-of select="plan/frequency" />
                    </frequency>
                    <method>
                        <xsl:value-of select="method/methodType" />
                    </method>
                </plan>

            </xsl:for-each>
        </userOption>
    </xsl:for-each>
</userOptions>

The problem is I get duplicates, such as:

    <userOption>
      <type>A</type>
      <plan>
         <frequency>3MONTH</frequency>
         <methodType>X1-B</methodType>
      </plan>
      <plan>
         <frequency>MONTHLY</frequency>
         <methodType>X1</methodType>
      </plan>
    <userOption>
    <userOption>
      <type>A</type>
      <plan>
         <frequency>MONTHLY</frequency>
         <methodType>X1</methodType>
      </plan>
      <plan>
         <frequency>3MONTH</frequency>
         <methodType>X1-B</methodType>
      </plan>
    <userOption>

I'm not sure how to consolidate the records with matching type and different methodType but also to avoid duplicates.

Upvotes: 0

Views: 115

Answers (1)

Ian Roberts
Ian Roberts

Reputation: 122364

This is a good candidate for the Muenchian grouping method:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="user-type" match="userOption" use="type" />
  <xsl:template match="/">
    <userOptions>
      <!-- this for-each iterates over a node set consisting of just
      the *first* userOption element for each type -->
      <xsl:for-each select="userOptions/userOption[count( . | key('user-type', type)[1]) = 1]">
        <userOption>
          <xsl:copy-of select="type" />
          <!-- apply templates for all userOption elements of this type
          (including the one we are currently for-eaching over) -->
          <xsl:apply-templates select="key('user-type', type)" />
        </userOption>
      </xsl:for-each>
    </userOptions>
  </xsl:template>

  <xsl:template match="userOption">
    <plan>
      <frequency>
        <xsl:value-of select="plan/frequency" />
      </frequency>
      <method>
        <xsl:value-of select="method/methodType" />
      </method>
    </plan>
  </xsl:template>
</xsl:stylesheet>

This is a nifty way of defining groups of nodes, and then iterating once per group rather than once per node, by careful use of keys. If you can use XSLT 2 there's a much more straightforward method built in to the language (<xsl:for-each-group>) but that is not available in XSLT 1.

Upvotes: 1

Related Questions