I am trying to use streaming (XSLT 3.0) to process the report output because of huge volume. I have to use XSLT because of specific output format. While transforming the report output, I need to lookup "Supplier_Invoice_number" based on a combination of Customer_Invoice and Supplier stored in "Customer_Invoice_and_Supplier" at the line level. I need to show "Supplier_Invoice_number" at the line level.
I have created XSLT maps to store the value of "Customer_Invoice_and_Supplier" and "Supplier_Invoice_number" so that while transforming the lines i can fetch "Supplier_Invoice_number" to then show at the line level in the output. Below is the XSLT I have created, but I am getting blank for "Supplier_Invoice_number". I am new to XSLT maps and streaming, therefore would really appreciate if someone can guide me to the solution.
<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:wd="urn:com.workday/bsvc" xmlns:wd1="urn:com.workday.report/INT1109_CR_REV_Lookup_Supplier_Invoice_for_Customer_Invoice" xmlns:wd2="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect" exclude-result-prefixes="xs" version="3.0">
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="SupplierInvoiceLookup CurrentLookupValue"/>
<xsl:output method="text"/>
<xsl:accumulator name="CurrentLookupValue" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="wd1:Customer_Invoice_and_Supplier/text()" select="."/>
</xsl:accumulator>
<xsl:accumulator name="SupplierInvoiceLookup" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="wd1:Supplier_Invoice_Number/text()" select="map:put($value, string(.), accumulator-before('CurrentLookupValue'))"/>
</xsl:accumulator>
<xsl:strip-space elements="*"/>
<xsl:template match="AggregatedData">
<xsl:for-each select="wd2:Report_Data/wd2:Report_Entry/copy-of()">
<xsl:text>{ "Company": "</xsl:text>
<xsl:value-of select="wd2:Company"/>
<xsl:iterate select="wd2:lines">
<xsl:text> { </xsl:text>
<xsl:text> "sequence": "</xsl:text>
<xsl:value-of select="wd2:sequence"/>
<xsl:text>", </xsl:text>
<xsl:text> "sales_item_id": "</xsl:text>
<xsl:value-of select="wd2:sales_item_id"/>
<xsl:text>", </xsl:text>
<xsl:text> "supplier_invoice_no": "</xsl:text>
<xsl:variable name="supplier_invoice_no" select="accumulator-before('SupplierInvoiceLookup')( normalize-space( @wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:value-of select="accumulator-before('SupplierInvoiceLookup')( normalize-space( @wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:text>", </xsl:text>
</xsl:iterate>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:template>
Sample XML -
<?xml version="1.0" encoding="utf-8"?>
<AggregatedData>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Lookup_Supplier_Invoice_for_Customer_Invoice">
<wd:Report_Entry>
<wd:Supplier_Invoice_Lines_group>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Dell Receivables LP</wd:Customer_Invoice_and_Supplier>
<wd:Supplier_Invoice_Number>SI-00000047</wd:Supplier_Invoice_Number>
</wd:Supplier_Invoice_Lines_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Supplier_Invoice_Lines_group>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Charles Case [C]</wd:Customer_Invoice_and_Supplier>
<wd:Supplier_Invoice_Number>SI-00000050</wd:Supplier_Invoice_Number>
</wd:Supplier_Invoice_Lines_group>
</wd:Report_Entry>
</wd:Report_Data>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect">
<wd:Report_Entry>
<wd:Company>Financial/wd:Company>
<wd:lines>
<wd:sequence>a8</wd:sequence>
<wd:sales_item_id>Data - Enterprise License</wd:sales_item_id>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Dell Receivables LP</wd:Customer_Invoice_and_Supplier>
</wd:lines>
<wd:lines>
<wd:sequence>a9</wd:sequence>
<wd:sales_item_id>TBA Trade Reports ATS Cncl</wd:sales_item_id>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Charles Case [C]</wd:Customer_Invoice_and_Supplier>
</wd:lines>
</wd:Report_Entry>
</wd:Report_Data>
</AggregatedData>
For some reason you have used @wd2:Customer_Invoice_and_Supplier
to select an attribute although your input has element data, so I think you want
<xsl:template match="AggregatedData">
<xsl:for-each select="wd2:Report_Data/wd2:Report_Entry/copy-of()">
<xsl:text>{ "Company": "</xsl:text>
<xsl:value-of select="wd2:Company"/>
<xsl:iterate select="wd2:lines">
<xsl:text> { </xsl:text>
<xsl:text> "sequence": "</xsl:text>
<xsl:value-of select="wd2:sequence"/>
<xsl:text>", </xsl:text>
<xsl:text> "sales_item_id": "</xsl:text>
<xsl:value-of select="wd2:sales_item_id"/>
<xsl:text>", </xsl:text>
<xsl:text> "supplier_invoice_no": "</xsl:text>
<xsl:value-of select="accumulator-before('SupplierInvoiceLookup')( normalize-space( wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:text>", </xsl:text>
</xsl:iterate>
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:template>
And I think you want the map to be the other way around in terms of key and values so the accumulators would do
<xsl:accumulator name="CurrentLookupValue" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="wd1:Customer_Invoice_and_Supplier/text()" select="string()"/>
</xsl:accumulator>
<xsl:accumulator name="SupplierInvoiceLookup" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="wd1:Supplier_Invoice_Number/text()" select="map:put($value, accumulator-before('CurrentLookupValue'), string(.))"/>
</xsl:accumulator>
https://xsltfiddle.liberty-development.net/ncdD7mu
If you want to output JSON then note that XSLT 3 with XPath 3.1 maps and arrays and the output method json
does allow that in a comfortable way without struggling to output pairs of braces and element names. But that might be better solved in a separate question where you show us the JSON you want.