Reputation: 3
I have a file with a header record and one to many detail records. I need to create the same header record, along with the last detail record for a given policy number. How do I write the XSLT to both sort and select the first record?
I have tried placing a hard coded value in between the "for-each" statements within the XSLT to see where the code breaks. Anything placed after the second "for-each" doesn't display. I have searched high and low there to see if anyone can handle a header with multiple details, but I couldn't find an answer to this question.
This is what I'm receiving (simplified - there are a lot more fields):
<ns0:Root xmlns:ns0="http://CORE.BizTalk.Applications.Schemas.PolicyFF">
<Header>
<RecordType>RecordType</RecordType>
<RecordID>RecordID</RecordID>
<PolicyNumber>PolicyNumber</PolicyNumber>
</Header>
<Detail>
<RecordType>POL</RecordType>
<RecordID>1</RecordID>
<PolicyNumber>ABC 0000018 00</PolicyNumber>
</Detail>
<Detail>
<RecordType>POL</RecordType>
<RecordID>2</RecordID>
<PolicyNumber>DEF0000019</PolicyNumber>
</Detail>
<Detail>
<RecordType>POL</RecordType>
<RecordID>3</RecordID>
<PolicyNumber>DEF0000019</PolicyNumber>
</Detail>
<Detail>
<RecordType>POL</RecordType>
<RecordID>3</RecordID>
<PolicyNumber>ABC 0000018 00</PolicyNumber>
</Detail>
</ns0:Root>
The output has the identical layout, but I only want to see this, since the RecordID is the last RecordID for both policies (sorted descending by RecordID for each policy):
<ns0:Root xmlns:ns0="http://CORE.BizTalk.Applications.Schemas.PolicyFF">
<Header>
<RecordType>RecordType</RecordType>
<RecordID>RecordID</RecordID>
<PolicyNumber>PolicyNumber</PolicyNumber>
</Header>
<Detail>
<RecordType>POL</RecordType>
<RecordID>3</RecordID>
<PolicyNumber>DEF0000019</PolicyNumber>
</Detail>
<Detail>
<RecordType>POL</RecordType>
<RecordID>3</RecordID>
<PolicyNumber>ABC 0000018 00</PolicyNumber>
</Detail>
</ns0:Root>
This is the XSLT I'm using:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var" version="1.0" xmlns:ns0="http://CORE.BizTalk.Applications.Schemas.PolicyFF">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="policy" match="Root/Detail" use="PolicyNumber" />
<xsl:template match="/">
<xsl:apply-templates select="/ns0:Root" />
</xsl:template>
<xsl:template match="/ns0:Root">
<ns0:Root>
<Header>
<RecordType>
<xsl:value-of select="Header/RecordType/text()" />
</RecordType>
<RecordID>
<xsl:value-of select="Header/RecordID/text()" />
</RecordID>
<PolicyNumber>
<xsl:value-of select="Header/PolicyNumber/text()" />
</PolicyNumber>
</Header>
<xsl:for-each select="Detail">
<xsl:for-each select="Detail[generate-id() = generate-id(key('policy', PolicyNumber)[1])]">
<xsl:for-each select="key('policy', PolicyNumber)">
<xsl:sort order="descending" select="RecordID" data-type="text"/>
<xsl:if test="position() = 1">
<Detail>
<xsl:copy-of select="./*" />
</Detail>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
I am currently only getting the header record with the XSLT above.
@Martin-Honnen: I need to sort by RecordID descending. I took your first and second XSLTs and turned it into one, but I'm still only getting the header record. Suggestions?
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var" version="1.0" xmlns:ns0="http://CORE.BizTalk.Applications.PPPL.TrisuraReports.Schemas.PolicyFF">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="policy" match="Root/Detail" use="PolicyNumber" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail[not(generate-id() = generate-id(key('policy', PolicyNumber)[1]))]"/>
<xsl:template match="Detail[generate-id() = generate-id(key('policy', PolicyNumber)[1])]">
<xsl:for-each select="key('policy', PolicyNumber)">
<xsl:sort select="RecordID" data-type="text" order="descending"/>
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Views: 245
Reputation: 167716
In XSLT 1, if "with the last detail record" means last in the input, I would simply use the identity transformation template plus a template preventing the copying of any Detail
not being the last of its group defined by the key:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="policy" match="Detail" use="PolicyNumber" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail[not(generate-id() = generate-id(key('policy', PolicyNumber)[last()]))]"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/3NSSEuP
If you need to sort by the RecordID
then in one template for the first or last item of the group you can sort all items in the group and output the last in descending:
<xsl:template match="Detail[not(generate-id() = generate-id(key('policy', PolicyNumber)[1]))]"/>
<xsl:template match="Detail[generate-id() = generate-id(key('policy', PolicyNumber)[1])]">
<xsl:for-each select="key('policy', PolicyNumber)">
<xsl:sort select="RecordID" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
https://xsltfiddle.liberty-development.net/3NSSEuP/1 is the full online sample and has
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="policy" match="Detail" use="PolicyNumber" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail[not(generate-id() = generate-id(key('policy', PolicyNumber)[1]))]"/>
<xsl:template match="Detail[generate-id() = generate-id(key('policy', PolicyNumber)[1])]">
<xsl:for-each select="key('policy', PolicyNumber)">
<xsl:sort select="RecordID" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 117140
I would do it this way:
XSLT 1.0
<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="policy" match="Detail" use="PolicyNumber" />
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="Header"/>
<!-- for each distinct policy -->
<xsl:for-each select="Detail[count(. | key('policy', PolicyNumber)[1]) = 1]">
<!-- sort current group -->
<xsl:for-each select="key('policy', PolicyNumber)">
<xsl:sort select="RecordID" data-type="number" order="descending"/>
<!-- return the record with highest RecordID -->
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0