0
votes

I have got a source XML

<Records>
  <Data>
    <RecordType>New</RecordType>
    <Number>4734122946</Number>
    <DateOfBirth>20160506</DateOfBirth>
    <Title>Mr</Title>
    <ChangeTimeStamp>20160101010001</ChangeTimeStamp>
    <SerialChangeNumber>01</SerialChangeNumber>
  </Data>
  <Data>
    <RecordType>New</RecordType>
    <Number>4734122946</Number>
    <LastName>Potter</LastName>
    <DateOfBirth>20160506</DateOfBirth>
    <ChangeTimeStamp>20160101010002</ChangeTimeStamp>
    <SerialChangeNumber>01</SerialChangeNumber>
  </Data>
</Records>

I want to use XSLT 1.0 to produce the below output

<Contact>
  <Number>4734122946</Number>
  <Title>Mr</Title>
  <LastName>Potter</LastName>
  <BirthDate>20160506</BirthDate>
  <ChangeTimeStamp>20160101010002</ChangeTimeStamp>
</Contact>

The XSLT has to group and merge the child nodes of Data records into one based on the Number field. Also if there are same elements present, then it should use the ChangeTimeStamp element to figure out the latest change and use that element.

I tried the below XSLT. But I am nowhere close to the output.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes" />

  <xsl:key name="groups" match="Data" use="Number"/>
  <xsl:key name="sortGroup" match="Data" use ="ChangeTimeStamp"/>

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

  <xsl:template match="Records">
    <xsl:for-each select="Data[generate-id() = generate-id(key('groups',Number))]">
      <Contact>
        <Number>
          <xsl:value-of select="Number"/>
        </Number>
        <xsl:for-each select="key('groups',Number)">
          <xsl:for-each select="key('sortGroup',ChangeTimeStamp)">
            <xsl:sort select="sortGroup" order="ascending"/>
            <xsl:if test="Title">
              <Title>
                <xsl:value-of select ="Title"/>
              </Title>
            </xsl:if>
            <xsl:if test="LastName">
              <LastName>
                <xsl:value-of select="LastName"/>
              </LastName>
            </xsl:if>
            <BirthDate>
              <xsl:value-of select="DateOfBirth"/>
            </BirthDate>
          </xsl:for-each>
        </xsl:for-each>
      </Contact>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Appreciate your help.

1
The output you show will not be valid XML if there are multiple Contact nodes in the result. -- Note also that your attempt at Muenchian grouping is incorrect. - michael.hor257k

1 Answers

0
votes

The XSLT has to group and merge the child nodes of Data records into one based on the Number field. Also if there are same elements present, then it should use the ChangeTimeStamp element to figure out the latest change and use that element.

For that, I believe you would want to do something like:

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

<xsl:key name="group" match="Data" use="Number"/>
<xsl:key name="item" match="Data/*" use="concat(../Number, '|', name())"/>

<xsl:template match="/Records">
    <root>
        <xsl:for-each select="Data[generate-id() = generate-id(key('group', Number)[1])]">
            <Contact>
                <xsl:for-each select="key('group', Number)/*[generate-id() = generate-id(key('item', concat(../Number, '|', name()))[1])]">
                    <xsl:for-each select="key('item', concat(../Number, '|', name()))">
                        <xsl:sort select="../ChangeTimeStamp" data-type="number" order="descending"/>
                            <xsl:if test="position()=1">
                                <xsl:copy-of select="."/>
                            </xsl:if>
                    </xsl:for-each>
                </xsl:for-each>
            </Contact>
        </xsl:for-each>
    </root>
</xsl:template>

</xsl:stylesheet>

You will have to make some adjustments if you want to include only some data items and/or if you want them to appear in particular order. If you have a list of all possible data item names (e.g. Title, LastName, DateOfBirth, etc.) then this could be simpler.