๐ท Use Case
In Oracle EBS payment integrations, incoming data files often contain repeating header-detail structures. A typical scenario includes File Headers (MH), Batch Headers (RH), and Detail Records (D1).
The requirement is to extract the POS ID value from each batch header (RH) and include it in each of the detail records (D1) for the target file.
Source file:
MHHEADER Record
RHHEADER Record posid1
D1DETAIL Record 1.1
D1DETAIL Record 1.2
D1DETAIL Record 1.3
RHHEADER Record posid2
D1DETAIL Record 2.1
D1DETAIL Record 2.2
...
The target file should follow the format:
Header
Detail posid1
Detail posid1
Detail posid1
Header
Detail posid2
Detail posid2
...
๐ท Solution Overview
To achieve this, we utilize XSLT to:
✅ Identify and group records by batch header (RH).
✅ Extract the POS ID from each RH record.
✅ Write one Header record to the target for each RH.
✅ Write corresponding Detail records, including the extracted POS ID, for each D1 under the RH.
✅ Repeat for all batches in the file.
๐ท Solution Steps
1️⃣ Identify the Grouping
Use <xsl:for-each-group>
with group-starting-with="RH"
to create logical batches.
2️⃣ Extract POS ID from Batch Header
Within each group, identify the RH record and extract the POS ID using XSLT’s substring
and normalize-space
functions.
3️⃣ Write Header Record
For each group, create a target Header record.
4️⃣ Iterate Over Detail Records
Loop through the Detail (D1) records within the group and create target Detail records. Insert the extracted POS ID value into each of these records.
5️⃣ Output to Target XML
The XSLT’s template writes the Header and Detail records in the specified order for the target file.
๐ท Source XSLT Snippet
<xsl:for-each-group select="..." group-starting-with="...">
<!-- Extract RH's POS ID -->
<xsl:variable name="POSID" select="substring(RH, ... , ... )"/>
<!-- Write Header record -->
<Header>
<POSID><xsl:value-of select="$POSID"/></POSID>
</Header>
<!-- Write all Detail records under RH -->
<xsl:for-each select="current-group()[...D1...]">
<Detail>
<POSID><xsl:value-of select="$POSID"/></POSID>
...
</Detail>
</xsl:for-each>
</xsl:for-each-group>
Actual code:
<xsl:template match="/">
<ns0:empmpr:Write xmlns:ns0="http://www.oracle.com/XSL/Transform/java/oracle.tip.adapter.bpel.services.XSLTProcessor.BPEL2XSLT">
<ns24:Payments xmlns:ns24="http://xmlns.oracle.com/apps/financials/payables/payments/shared/paymentsmodel/paymentsservice/">
<ns24:Payment>
<ns24:HDR_RECORD-RECORD>
<ns24:VALUE-OF xml:id="id_248" select="'HDR'"/>
<ns24:value-of xml:id="id_249" select="'HDR'"/>
</ns24:HDR_RECORD-RECORD>
<ns24:CUST_VENDOR_ID xml:id="id_277">
<ns24:value-of xml:id="id_278" select="'PSB'"/>
</ns24:CUST_VENDOR_ID>
<ns24:CURR_CD_PAY_MODE xml:id="id_252">
<ns24:value-of xml:id="id_253" select="'HKD'"/>
</ns24:CURR_CD_PAY_MODE>
<ns24:PIT_EFF_PAY_SOURCE xml:id="id_254">
<ns24:value-of xml:id="id_255" select="'PPSB'"/>
</ns24:PIT_EFF_PAY_SOURCE>
<ns24:PAY_SOURCE_ACCT_NO xml:id="id_256">
<ns24:value-of xml:id="id_257" select="'PPSB'"/>
</ns24:PAY_SOURCE_ACCT_NO>
</ns24:Payment>
</ns24:Payments>
<xsl:for-each-group select="$ReadRawFile/nsmp0:ReadResponse/ns23:FileRowSet/ns23:FileRow"
group-starting-with="ns23:FileRow[starts-with(normalize-space(ns23:MyData), 'RH')]">
<xsl:if test="starts-with(normalize-space(current-group()[1]/ns23:MyData), 'RH')">
<xsl:variable name="hvalue" select="normalize-space(current-group()[1]/ns23:MyData)"/>
<xsl:for-each select="current-group()[position() > 1][starts-with(normalize-space(ns23:MyData), 'D1')]">
<ns24:Payment xml:id="id_42">
<ns24:HDR_RECORD-RECORD>
<ns24:VALUE-OF xml:id="id_248" select="'DTR'"/>
<ns24:value-of xml:id="id_249" select="'DTR'"/>
</ns24:HDR_RECORD-RECORD>
<ns24:CUST_VENDOR_ID xml:id="id_277">
<ns24:value-of xml:id="id_278" select="normalize-space(substring(ns23:MyData, 28, 25))"/>
</ns24:CUST_VENDOR_ID>
<ns24:CURR_CD_PAY_MODE xml:id="id_252">
<ns24:value-of xml:id="id_253" select="'CASH'"/>
</ns24:CURR_CD_PAY_MODE>
<ns24:PIT_EFF_PAY_SOURCE xml:id="id_254">
<ns24:value-of xml:id="id_255" select="'PPSB'"/>
</ns24:PIT_EFF_PAY_SOURCE>
<ns24:PAY_SOURCE_ACCT_NO xml:id="id_256">
<ns24:value-of xml:id="id_257" select="'PPSB'"/>
</ns24:PAY_SOURCE_ACCT_NO>
<ns24:PAY_AMT xml:id="id_258">
<ns24:value-of xml:id="id_259" select="normalize-space(substring(ns23:MyData, 28, 25))"/>
</ns24:PAY_AMT>
<ns24:PAY_DATE xml:id="id_260">
<ns24:value-of xml:id="id_261" select="normalize-space(substring(ns23:MyData, 20, 8))"/>
</ns24:PAY_DATE>
<ns24:POSID xml:id="id_262">
<ns24:value-of xml:id="id_263" select="normalize-space(substring(ns23:MyData, 55, 8))"/>
</ns24:POSID>
<ns24:INPUT_TIME xml:id="id_264">
<ns24:value-of xml:id="id_265" select="normalize-space(substring(ns23:MyData, 11, 15))"/>
</ns24:INPUT_TIME>
</ns24:Payment>
</xsl:for-each>
</xsl:if>
</xsl:for-each-group>
</ns0:empmpr:Write>
</xsl:template>
Screenshot:
๐ท Step-by-Step Breakdown
✅ Step 1: Start the Template
<xsl:template match="/">
This is the root template—processing begins here.
✅ Step 2: Write Static Header Payment Record
<ns24:Payments>
<ns24:Payment>...</ns24:Payment>
</ns24:Payments>
This block writes a static Payment record containing hard-coded values. It seems to serve as a placeholder or “file-level header.”
✅ Step 3: Group Records by Batch Header (RH)
<xsl:for-each-group select="..." group-starting-with="...RH...">
This groups all input file rows into batches starting with a RH record (Batch Header).
✅ Step 4: Validate the Group
<xsl:if test="starts-with(normalize-space(current-group()[1]/ns23:MyData), 'RH')">
Ensures the first row in the group is indeed an RH record.
✅ Step 5: Extract Batch Header Data
<xsl:variable name="hvalue" select="normalize-space(current-group()[1]/ns23:MyData)"/>
This stores the RH record’s data for use in Detail record mapping.
✅ Step 6: Process Each Detail Record (D1) in the Group
<xsl:for-each select="current-group()[position() > 1][starts-with(normalize-space(ns23:MyData), 'D1')]">
Loops through all D1 rows after the first row in the group (which is the RH record). Each D1 record triggers the creation of a new Payment record.
✅ Step 7: Write Detail Payment Record
Inside the for-each, it writes a Payment XML element for each D1 record, extracting and transforming parts of the data:
Extracts substrings from MyData to populate fields like:
CUST_VENDOR_ID
PAY_AMT
PAY_DATE
POSID
INPUT_TIME
Adds static values to fields like:
CURR_CD_PAY_MODE (CASH)
PIT_EFF_PAY_SOURCE (PPSB)
๐ท Conclusion
This XSLT approach automates the transformation from a mixed file structure to the target structured file by grouping, extracting POS IDs, and mapping the data efficiently. It ensures data consistency and proper file formatting for downstream processing.