Search code examples
streamingaccumulatorxslt-3.0

I am trying to fetch one field looking at another field using XSLT Maps


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>{&#10;        "Company": "</xsl:text>
        <xsl:value-of select="wd2:Company"/>            
        <xsl:iterate select="wd2:lines">
            <xsl:text>      {&#10;</xsl:text>
            <xsl:text>        "sequence": "</xsl:text>
            <xsl:value-of select="wd2:sequence"/>
            <xsl:text>",&#10;</xsl:text>
            <xsl:text>        "sales_item_id": "</xsl:text>
            <xsl:value-of select="wd2:sales_item_id"/>
            <xsl:text>",&#10;</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>",&#10;</xsl:text>
        </xsl:iterate>            
    </xsl:for-each>
    <xsl:text>  &#10;</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>

Solution

  • 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>{&#10;        "Company": "</xsl:text>
            <xsl:value-of select="wd2:Company"/>            
            <xsl:iterate select="wd2:lines">
                <xsl:text>      {&#10;</xsl:text>
                <xsl:text>        "sequence": "</xsl:text>
                <xsl:value-of select="wd2:sequence"/>
                <xsl:text>",&#10;</xsl:text>
                <xsl:text>        "sales_item_id": "</xsl:text>
                <xsl:value-of select="wd2:sales_item_id"/>
                <xsl:text>",&#10;</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>",&#10;</xsl:text>
            </xsl:iterate>            
        </xsl:for-each>
        <xsl:text>  &#10;</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.