I am fairly new to xslt 3.0 and have a problem that requires it’s use. I am very close but my output is printing the text-only copy of the data that I do not want.
I have to basically merge the T3 Block with the T4 Block only for workers that have Employee IDs and have to get the Header data also.
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:bsvc="urn:com.workday/bsvc"
xmlns:wd="urn:com.workday/bsvc"
xmlns:functx = "http://www.functx.com"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tdf="urn:com.workday/tdf"
xmlns:saxon="http://saxon.sf.net/"
xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue"
xmlns:this="urn:this"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="p.file"/>
<xsl:variable name="Linefeed" select="'
'"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="#all" />
<xsl:output method="xml"/>
<xsl:function name="this:ApplyMap">
<xsl:param name="externalValue"/>
<xsl:param name="mapName"/>
<xsl:variable name="internalValue">
<xsl:value-of select="is:getIntegrationMapValue(string($mapName),string($externalValue))"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$internalValue">
<xsl:value-of select="$internalValue"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$externalValue"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:key name="EmpLookup" match ="T3Record" use="string(CardholderId)"/>
<xsl:template match = "/">
<Root>
<xsl:apply-templates select="*"></xsl:apply-templates>
</Root>
</xsl:template>
<xsl:template match = "Blocks">
<xsl:iterate select="*">
<xsl:param name="EmpLookupData"/>
<xsl:choose>
<xsl:when test="self::T3Block">
<xsl:next-iteration>
<xsl:with-param name="EmpLookupData" select="snapshot()"/>
</xsl:next-iteration>
</xsl:when>
<xsl:when test="self::T4Block">
<xsl:apply-templates select=".">
<xsl:with-param name="EmpLookupData" select="$EmpLookupData" tunnel="yes"/>
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:iterate>
</xsl:template>
<xsl:template match="Blocks/T4Block">
<bsvc:Credit_Card_Header__HV__Request xmlns:bsvc="urn:com.workday/bsvc">
<wd:Card_Holder_Listing_File_Data>
<xsl:apply-templates select="copy-of()" mode="in-memory"/>
</wd:Card_Holder_Listing_File_Data>
</bsvc:Credit_Card_Header__HV__Request>
</xsl:template>
<xsl:template match="Header" mode="in-memory">
<wd:Card_Holder_Listing_File_Name><xsl:value-of select="concat(ProcessorPlatformRef,'-',CompanyId,'-',SequenceNum,'-',RecordTypeCode,'-',IssuerIdNum,'-',ProcessorIdNum,'-',VisaRegionId)"/></wd:Card_Holder_Listing_File_Name>
<wd:Card_Holder_Listing_File_Date><xsl:value-of select="concat(substring(ProcessingDate,5,4),'-',substring(ProcessingDate,1,2),'-',substring(ProcessingDate,3,2))"/></wd:Card_Holder_Listing_File_Date>
</xsl:template>
<xsl:template match="T4Record[EmployeeId]" mode = "in-memory">
<xsl:param name="EmpLookupData" tunnel="yes"/>
<xsl:variable name="org" select="key('EmpLookup', CardholderId, $EmpLookupData)"/>
<wd:Credit_Card_Data>
<wd:Expense_Credit_Card_ID><xsl:value-of select="$org/AcctNum"/></wd:Expense_Credit_Card_ID>
<wd:Corporate_Credit_Card_Account_Reference>
<wd:ID wd:type="Corporate_Credit_Card_Account_ID"><xsl:value-of select="this:ApplyMap(CompanyId,'Billing_Account')"/></wd:ID>
</wd:Corporate_Credit_Card_Account_Reference>
<wd:Last_4_Digits_of_Credit_Card_Number><xsl:value-of select="substring($org/AcctNum,string-length($org/AcctNum) - 3)"/></wd:Last_4_Digits_of_Credit_Card_Number>
<wd:Credit_Card_Expiration_Date><xsl:value-of select="concat(substring($org/CardExpDate,5,4),'-',substring($org/CardExpDate,1,2),'-',substring($org/CardExpDate,3,2))"/></wd:Credit_Card_Expiration_Date>
<wd:Cardmember_Embossed_Name><xsl:value-of select="$org/EmbossLine1"/></wd:Cardmember_Embossed_Name>
<wd:Document_Status_Reference>
<wd:ID wd:type="Document_Status_ID">ACTIVE</wd:ID>
</wd:Document_Status_Reference>
<wd:Cardholder_ID>
<xsl:value-of select="EmployeeId"/>
</wd:Cardholder_ID>
</wd:Credit_Card_Data>
<xsl:value-of select="$Linefeed"/>
</xsl:template>
</xsl:stylesheet>
Desired Output: Note here that since Matt Carp doesn’t have an Employee ID, I do not want him on the Output. However my code above seems to print out all his T4 Block in one string like so: 4xyz333MATTHEWCARP
<?xml version='1.0' encoding='utf-8'?>
<Root>
<bsvc:Credit_Card_Header__HV__Request xmlns:bsvc="urn:com.workday/bsvc">
<wd:Card_Holder_Listing_File_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Card_Holder_Listing_File_Name>987-xyz-0-04-1423-200-1</wd:Card_Holder_Listing_File_Name>
<wd:Card_Holder_Listing_File_Date>2024-08-02</wd:Card_Holder_Listing_File_Date>
<wd:Credit_Card_Data>
<wd:Expense_Credit_Card_ID>222</wd:Expense_Credit_Card_ID>
<wd:Corporate_Credit_Card_Account_Reference>
<wd:ID wd:type="Corporate_Credit_Card_Account_ID">111</wd:ID>
</wd:Corporate_Credit_Card_Account_Reference>
<wd:Last_4_Digits_of_Credit_Card_Number>6756</wd:Last_4_Digits_of_Credit_Card_Number>
<wd:Credit_Card_Expiration_Date>2027-06-30</wd:Credit_Card_Expiration_Date>
<wd:Cardmember_Embossed_Name>PAUL MITCHELL</wd:Cardmember_Embossed_Name>
<wd:Document_Status_Reference>
<wd:ID wd:type="Document_Status_ID">ACTIVE</wd:ID>
</wd:Document_Status_Reference>
<wd:Cardholder_ID>12345</wd:Cardholder_ID>
</wd:Credit_Card_Data>
</wd:Card_Holder_Listing_File_Data>
</bsvc:Credit_Card_Header__HV__Request>
</Root>
Sample XML:
<Root>
<Set>
<Blocks>
<T3Block>
<Header>
<CompanyId>xyz</CompanyId>
<SequenceNum>0</SequenceNum>
<ProcessingDate>08022024</ProcessingDate>
<RecordTypeCode>03</RecordTypeCode>
<RecordCount>0</RecordCount>
<TotalAmount>0</TotalAmount>
<LoadFileFormat>4.0</LoadFileFormat>
<IssuerIdNum>1423</IssuerIdNum>
<ProcessorIdNum>200</ProcessorIdNum>
<VisaRegionId>1</VisaRegionId>
<ProcessorPlatformRef>987</ProcessorPlatformRef>
</Header>
<T3Record>
<LoadTransCode>4</LoadTransCode>
<CardholderId>222</CardholderId>
<AcctNum>111</AcctNum>
<EffectiveDate>06052003</EffectiveDate>
<CardExpDate>06302027</CardExpDate>
<CorpPaymentIndicator>0</CorpPaymentIndicator>
<StatusCode>2</StatusCode>
<AcctTypeFlag>2</AcctTypeFlag>
<EmbossLine1>PAUL MITCHELL </EmbossLine1>
</T3Record>
<T3Record>
<LoadTransCode>4</LoadTransCode>
<CardholderId>333</CardholderId>
<AcctNum>111</AcctNum>
<EffectiveDate>01212004</EffectiveDate>
<CardExpDate>03312027</CardExpDate>
<CorpPaymentIndicator>0</CorpPaymentIndicator>
<StatusCode>2</StatusCode>
<AcctTypeFlag>2</AcctTypeFlag>
<EmbossLine1>MATTHEW A CARP</EmbossLine1>
</T3Record>
</T3Block>
<T4Block>
<Header>
<CompanyId>xyz</CompanyId>
<SequenceNum>0</SequenceNum>
<ProcessingDate>08022024</ProcessingDate>
<RecordTypeCode>04</RecordTypeCode>
<RecordCount>0</RecordCount>
<TotalAmount>0</TotalAmount>
<LoadFileFormat>4.0</LoadFileFormat>
<IssuerIdNum>1423</IssuerIdNum>
<ProcessorIdNum>200</ProcessorIdNum>
<VisaRegionId>1</VisaRegionId>
<ProcessorPlatformRef>987</ProcessorPlatformRef>
</Header>
<T4Record>
<LoadTransCode>4</LoadTransCode>
<CompanyId>xyz</CompanyId>
<CardholderId>222</CardholderId>
<FirstName>PAUL</FirstName>
<LastName>MITCHELL</LastName>
<EmployeeId>12345</EmployeeId>
</T4Record>
<T4Record>
<LoadTransCode>4</LoadTransCode>
<CompanyId>xyz</CompanyId>
<CardholderId>333</CardholderId>
<FirstName>MATTHEW</FirstName>
<LastName>CARP</LastName>
</T4Record>
</T4Block>
</Blocks>
</Set>
</Root>
Jack Creed is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1
It might suffice to declare <xsl:mode name="in-memory" on-no-match="shallow-skip"/>
.
2
This isn’t an answer to your question but a comment on your this:ApplyMap
function, which appears to have a bug.
<xsl:function name="this:ApplyMap">
<xsl:param name="externalValue"/>
<xsl:param name="mapName"/>
<xsl:variable name="internalValue">
<xsl:value-of select="is:getIntegrationMapValue(string($mapName),string($externalValue))"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$internalValue">
<xsl:value-of select="$internalValue"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$externalValue"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
Note that the variable internalValue
, because it has no select
attribute, and no as
attribute, acquires its value from its content (a “sequence constructor”), and because the xsl:value-of
statement produces a single string you might think the variable would end up as a string value, but it doesn’t. In fact, when a variable has no declared type (as
attribute) and its value is defined using a sequence constructor, the resulting sequence is wrapped in a document node. So in your case, if the is:getIntegrationMapValue()
function returned a string "foo"
, your $internalValue
variable would be a plain text document containing the text foo
. If is:getIntegrationMapValue()
returned a zero-length string, the $internalValue
variable’s value would be an empty document node.
The reason this is important is that you then treat this variable as a boolean, and whereas the boolean value of a string is false
if the string is zero-length, the boolean value of an empty document (i.e. a document node containing no text) is true
(in fact the boolean value of any node is always true
). Of course this means that the following xsl:choose
will always select the xsl:when
and never the xsl:otherwise
.
One possible fix would be to add an as="xs:string"
to the variable. Or you could replace the xsl:variable
‘s sequence constructor with a select
attribute (or you could do both):
<xsl:variable name="internalValue" select="is:getIntegrationMapValue(
string($mapName),
string($externalValue)
)"/>
Personally, I would probably drop the xsl:variable
, xsl:choose
, xsl:when
, and replace it with a single XPath expression; something more like this:
<xsl:function name="this:ApplyMap">
<xsl:param name="externalValue"/>
<xsl:param name="mapName"/>
<xsl:value-of select="
let $internal-value:= is:getIntegrationMapValue(
string($mapName),
string($externalValue)
)[normalize-space()]
return ($internalValue, $externalValue)[1]
"/>
</xsl:function>
… but that’s because I have a general policy of using XPath expressions with variables, loops, etc, in place of xsl:*
elements, for brevity and clarity. Not everyone feels the same way.