Home > database >  XSLT 3.0 - Removing and Replacing nodes (with Saxon 9.7)
XSLT 3.0 - Removing and Replacing nodes (with Saxon 9.7)

Time:03-17

I have an input XML like following (requirements are highlighted as comments):

<?xml version="1.0" encoding="utf-8"?>
<Aggregated_Data>

    <References>
        <Reference>
            <Type>Company_Reference_ID</Type>
            <Value>Dest Company</Value>
        </Reference>
        <Reference>
            <Type>ABC_Reference_ID</Type>
            <Value>XYZ</Value>
        </Reference>
    </References>


    <wd:root xmlns:wd="urn:com.workday/bsvc">
        <wd:Calculated_Field_Data xmlns:wd="urn:com.workday/bsvc"
            xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
            <wd:Calculated_Field_Reference_ID>Ref</wd:Calculated_Field_Reference_ID>
            <wd:Name>NameABC</wd:Name>
           
            <wd:Conditional_Expression_Calculated_Field_Data>
                <wd:Conditional_Expression_Calculated_Field_Condition_Data>
                    <wd:Condition_Item_Data>
                        <wd:Order>dd</wd:Order>
                        <wd:And_Or_Reference>
                            <wd:ID wd:type="WID">da4e1c34446c11de98360015c5e6daf6</wd:ID>
                            <wd:ID wd:type="And_Or_Operator_Name">And</wd:ID>
                        </wd:And_Or_Reference> 

                        <!-- Need to remove every wd:ID node except the last one (in this case last one is "<wd:ID wd:type="Company_Reference_ID">ABC</wd:ID>") and replace last one's value from the value from References node with the same type (In this case type = Company_Reference_ID) -->
                        <wd:Filter_Instances_Reference>
                            <wd:ID wd:type="WID">e50d309a6d8540e3b533219cfa2c330b</wd:ID>
                            <wd:ID wd:type="Organization_Reference_ID">ABC</wd:ID>
                            <wd:ID wd:type="Company_Reference_ID">ABC</wd:ID>
                        </wd:Filter_Instances_Reference>                        
                       
                    </wd:Condition_Item_Data>
                </wd:Conditional_Expression_Calculated_Field_Condition_Data>
            </wd:Conditional_Expression_Calculated_Field_Data>
        </wd:Calculated_Field_Data>
        
        <!-- Has multiple <wd:Calculated_Field_Data> nodes -->
    </wd:root>

</Aggregated_Data>

Expected output:

<?xml version="1.0" encoding="utf-8"?>
<Aggregated_Data>

    <References>
        <Reference>
            <Type>Company_Reference_ID</Type>
            <Value>Dest Company</Value>
        </Reference>
        <Reference>
            <Type>ABC_Reference_ID</Type>
            <Value>XYZ</Value>
        </Reference>
    </References>


    <wd:root xmlns:wd="urn:com.workday/bsvc">
        <wd:Calculated_Field_Data xmlns:wd="urn:com.workday/bsvc"
            xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
            <wd:Calculated_Field_Reference_ID>Ref</wd:Calculated_Field_Reference_ID>
            <wd:Name>NameABC</wd:Name>
           
            <wd:Conditional_Expression_Calculated_Field_Data>
                <wd:Conditional_Expression_Calculated_Field_Condition_Data>
                    <wd:Condition_Item_Data>
                        <wd:Order>dd</wd:Order>
                        <wd:And_Or_Reference>
                            <wd:ID wd:type="WID">da4e1c34446c11de98360015c5e6daf6</wd:ID>
                            <wd:ID wd:type="And_Or_Operator_Name">And</wd:ID>
                        </wd:And_Or_Reference> 

                        <!-- Notice that after removing all wd:ID nodes except last, the value of last node was replaced from the value from References node of its type -->
                        <wd:Filter_Instances_Reference>                                
                            <wd:ID wd:type="Company_Reference_ID">Dest Company</wd:ID>
                        </wd:Filter_Instances_Reference>                        
                       
                    </wd:Condition_Item_Data>
                </wd:Conditional_Expression_Calculated_Field_Condition_Data>
            </wd:Conditional_Expression_Calculated_Field_Data>
        </wd:Calculated_Field_Data>
        
        <!-- Has multiple <wd:Calculated_Field_Data> nodes -->
    </wd:root>

</Aggregated_Data>

To achieve this I have created the following XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:wd="urn:com.workday/bsvc"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    exclude-result-prefixes="#all">
    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />
    
    <xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
    
     <xsl:accumulator name="RefType" as="xs:string?" initial-value="()" streamable="yes">
        <xsl:accumulator-rule match="References/Reference/Type/text()"
            select="normalize-space()"/>
    </xsl:accumulator>


    <xsl:accumulator name="RefMap" as="map(xs:string,xs:string)" streamable="yes"
        initial-value="map{}">
        <xsl:accumulator-rule match="References/Reference/Value/text()"
            select="map:put($value, accumulator-before('RefType'), normalize-space())"/>
    </xsl:accumulator>
    
    
    
   
    
    <!-- removes all nodes except last one -->
    <xsl:template match="wd:Filter_Instances_Reference/wd:ID[position() != last()]"/>
    
    <!-- replaces last node with value from accumulator from References node -->
    <xsl:template match="wd:Filter_Instances_Reference/wd:ID[position() = last()]">
        <xsl:variable name="type" select="./@wd:type"/>
        <wd:ID>
            <xsl:attribute name="wd:type"><xsl:value-of select="$type"/></xsl:attribute>
            
            <!-- Accumulator map value goes here -->
            <xsl:value-of select="accumulator-before('RefMap')($type)"/>
        </wd:ID>
    </xsl:template>
    
   
</xsl:stylesheet>

THE ISSUE: Since my system uses lower version of Saxon (9.7 I believe), the above XSLT is throwing a streaming error: Template rule is declared streamable but it does not satisfy the streamability rules: The match pattern is not motionless

I suspect the reason for this is I have used predicates in the match expression to identify position of wd:ID nodes in both the templates. However, when I execute the same XSLT in a Saxon processor with higher version (10.6), I get the expected output, linked here.

I just wanted to know if there is any other way to achieve my requirement without using predicates to compare positions of the nodes or any other better way to achieve my requirement in lower XSLT 3.0 saxon environments. Any help is appreciated!

CodePudding user response:

You could set up an accumulator (assumes xpath-default-namespace="urn:com.workday/bsvc", otherwise prefix the element names with wd)

  <xsl:accumulator name="id" as="map(xs:string, xs:string)" initial-value="map{}" stremable="yes">
    <xsl:accumulator-rule match="Filter_Instances_Reference" select="map{}"/>
    <xsl:accumulator-rule match="Filter_Instances_Reference/ID/text()" select="map { ../@wd:type/string() : string() }"/>
  </xsl:accumulator>

then use templates

  <xsl:template match="Filter_Instances_Reference">
    <xsl:copy>
      <xsl:apply-templates/>
      <wd:ID wd:type="{accumulator-after('id') => map:keys()}">{accumulator-before('RefMap')(accumulator-after('id') => map:keys())}</wd:ID>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="Filter_Instances_Reference/ID"/>
  • Related