Fiona Chen
Fiona Chen

Reputation: 1368

XSLT 3.0 incremental merge

I have two XSLT workflows: Full (XSLT2) and Incremental (XSLT3).

Below full3.xml is the merge & split result of Full workflow:

<Account>
<metadata>
    <_uri>full3.xml</_uri>
    <created>2020-11-26T23:16:08.076-07:00</created>
    <lastModified>2020-11-26T23:16:08.076-07:00</lastModified>
    <merge-lineage>
        <merged-uri>lot960151-3.xml</merged-uri>
        <merged-uri>lot860150-3.xml</merged-uri>
    </merge-lineage>
</metadata>
<accountPersistentID>51b10faa</accountPersistentID>
<accountID>ACC300</accountID>
<accountName>bonafide-3</accountName>
<Item>
    <contract>
        <amount>
            <currency>USD</currency>
            <amount>5000000.00</amount>
        </amount>
    </contract>
    <contract>
        <amount>
            <currency>USD</currency>
            <amount>4000000.00</amount>
        </amount>
    </contract>
</Item></Account>

I have subsequent incremental input XMLs to be matched & (if matched) merged; if no match, then transform it as above similar structure.

lot660152-3.xml is the raw document structure. The match & merge criteria are the accountId and accountName

<ContractServicing>
<account id="ACC3">
    <accountId>ACC300</accountId>
    <accountName>bonafide-3</accountName>
    <accountBeneficiary href="party5"/>
    <servicingParty href="party6"/>
</account>  
<contract>
    <amount>
        <currency>USD</currency>
        <amount>5700000.00</amount>
    </amount>
</contract>
<contract>
    <amount>
        <currency>USD</currency>
        <amount>4000000.00</amount>
    </amount>
</contract></ContractServicing>

The desired results of the Incremental XSLT workflow should be:

    <Account>
    <metadata>
        <_uri>full3.xml</_uri>
        <created>2020-11-26T23:16:08.076-07:00</created>
        <lastModified>2020-11-29T00:00:00.000-00:00</lastModified>
        <merge-lineage>
            <merged-uri>lot960151-3.xml</merged-uri>
            <merged-uri>lot860150-3.xml</merged-uri>
            <merged-uri>lot660152-3.xml</merged-uri>
        </merge-lineage>
    </metadata>
    <accountPersistentID>51b10faa</accountPersistentID>
    <accountID>ACC300</accountID>
    <accountName>bonafide-3</accountName>
    <Item>
        <contract>
            <amount>
                <currency>USD</currency>
                <amount>5000000.00</amount>
            </amount>
        </contract>
        <contract>
            <amount>
                <currency>USD</currency>
                <amount>4000000.00</amount>
            </amount>
        </contract>
    <contract>
        <amount>
            <currency>USD</currency>
            <amount>5700000.00</amount>
        </amount>
    </contract>
    <contract>
        <amount>
            <currency>USD</currency>
            <amount>4000000.00</amount>
        </amount>
    </contract>
    </Item>
</Account>

As it currently stands, I am pleased with Full workflow but I can’t seem to get any line on XSLT3 merge instruction during the Incremental workflow.

My XSLT Incremental workflow

  <xsl:template match="/">
    <xsl:merge>
      <xsl:merge-source name="full" streamable="yes" for-each-source="$full-docs" select="Account">
        <xsl:merge-key select="accountID"/>
      </xsl:merge-source>
      <xsl:merge-source name="incremental" for-each-source="$incre-docs" select="ContractServicing">
        <xsl:merge-key select="account/accountId"/>
      </xsl:merge-source>
      <xsl:merge-action>
        <xsl:choose>
          <xsl:when test="current-merge-group('incremental')/account/accountId = current-merge-group('full')/accountID">
                <xsl:apply-templates select="current-merge-group('full')"/>
                <xsl:for-each select="current-merge-group('full')/Item">
                  <xsl:copy-of select="current-merge-group('incremental')/contract"/>
                </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:apply-templates select="current-merge-group('incremental')" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:merge-action>
    </xsl:merge>
  </xsl:template>

The result is none of the matched incremental contract merged into Item and the no-match document has not been transformed. (This has been resolved by Michael Kay)

Null Pointer

Upvotes: 0

Views: 194

Answers (1)

Michael Kay
Michael Kay

Reputation: 163595

I added some boilerplate:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   exclude-result-prefixes="xs"
   expand-text="yes"
   version="3.0">
   
   <xsl:mode on-no-match="shallow-copy"/>
   <xsl:mode name="merge-contracts" on-no-match="shallow-copy"/>
   <xsl:mode name="unmatched" on-no-match="shallow-copy"/>
   <xsl:output method="xml" indent="yes"/>
   <xsl:strip-space elements="*"/>

and then changed the xsl:merge-action to

           <xsl:merge-action>
               <xsl:choose>
                  <xsl:when test="current-merge-group('incremental')/account/accountId = current-merge-group('full')/accountID">
                     <xsl:apply-templates select="current-merge-group('full')" mode="merge-contracts">
                        <xsl:with-param name="extra" select="current-merge-group('incremental')" 
                           tunnel="yes" as="element(ContractServicing)"/>
                     </xsl:apply-templates>
                  </xsl:when>
                  <xsl:otherwise>
                     <xsl:apply-templates select="current-merge-group('incremental')" mode="unmatched"/>
                  </xsl:otherwise>
               </xsl:choose>
            </xsl:merge-action>

so if there's a match, it's processing the main document in mode merge-contracts with the incremental document passed in a tunnel parameter; and if there's no match, it's processing the incremental document in mode "unmatched".

The merged-contracts mode has three template rules:

   <xsl:template match="lastModified/text()" mode="merge-contracts">
      <xsl:value-of select="current-dateTime()"/>
   </xsl:template>

   <xsl:template match="merge-lineage" mode="merge-contracts">
      <xsl:param name="extra" tunnel="yes" as="element(ContractServicing)"/>
      <xsl:copy>
         <xsl:copy-of select="*"/>
         <merged-uri>{
           tokenize($extra/root()/document-uri(),'/')[last()]
         }</merged-uri>
      </xsl:copy>     
   </xsl:template>

   <xsl:template match="Item" mode="merge-contracts">
      <xsl:param name="extra" tunnel="yes" as="element(ContractServicing)"/>
      <xsl:copy>
         <xsl:copy-of select="*, $extra//contract"/>
      </xsl:copy>
   </xsl:template>

which might not do everything you want to do, but I think it captures the essence.

Where the incremental document isn't matched, it becomes a very routine transformation which I approximated with:

   <xsl:template match="ContractServicing" mode="unmatched">
      <xsl:result-document href="unmatched.xml">
         <Account>
            <metadata>...</metadata>
            <accountPersistentID>...</accountPersistentID>
            <xsl:copy-of select="//contract"/>
         </Account>
      </xsl:result-document>
   </xsl:template>

I'm not really sure where your difficulties arose. Your code refers to an "item" element that doesn't exist; and you didn't show us any code for combining the merge-lineage element or for merging the contracts. I don't know if that's because you had no problems with this code, or because you didn't know how to go about writing it.

Upvotes: 1

Related Questions