Search code examples
xslt-2.0xslt-3.0

How to match template based on condition?


Here is my source XML which I'm trying to transform based on values of <City>

<?xml version="1.0" encoding="UTF-8"?>
    <Workers>
        <Worker>
            <EmpID>12345</EmpID>
            <City>NYC</City>
            <Allowance>
                <Type>Meal</Type>
                <Amount>150</Amount>
            </Allowance>
            <Allowance>
                <Type>Gym</Type>
                <Amount>200</Amount>
            </Allowance>
        </Worker>
        <Worker>
            <EmpID>56789</EmpID>
            <City>SFO</City>
            <Base>
                <BaseType>General</BaseType>
                <BaseAmount>1000</BaseAmount>
            </Base>
        </Worker>
        
        <Worker>
            <EmpID>18978</EmpID>
            <City>LAX</City>
            <Base>
                <BaseType>General</BaseType>
                <BaseAmount>3000</BaseAmount>
            </Base>
        </Worker>    
    </Workers>

I'm attempting to transform as below. Transformation doesn't need be to applied if the value of <City> is either NYC or SFO

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
        <m:GetQuotationResponse xmlns:m = "http://www.example.com">
            <m:Worker>12345</m:Worker>
            <m:Location>NYC</m:Location>
            <m:Expense>
                <m:ExpenseType>Meal</m:ExpenseType>
                <m:Amount>150</m:Amount>
            </m:Expense>
            <m:Expense>
                <m:ExpenseType>Gym</m:ExpenseType>
                <m:Amount>200</m:Amount>
            </m:Expense>
        </m:GetQuotationResponse>
    </SOAP-ENV:Body>


    <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
        <m:GetQuotationResponse xmlns:m = "http://www.example.com">
            <m:Worker>56789</m:Worker>
            <m:Location>SFO</m:Location>
            <m:Expense>
                <m:ExpenseType>General</m:ExpenseType>
                <m:Amount>1000</m:Amount>
            </m:Expense>
        </m:GetQuotationResponse>
    </SOAP-ENV:Body>
</Root>

This is my attempt

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
    xmlns:m = "http://www.example.com"
    exclude-result-prefixes="xs m SOAP-ENV"
    version="2.0">
    <xsl:output method="xml" indent="yes"/>
     
    
    <xsl:template match="Worker[City='NYC']">
      <Root>
        <SOAP-ENV:Body xmlns:m = "http://www.example.com">
            <m:GetQuotationResponse>
                <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                <m:Location><xsl:value-of select="City"/></m:Location>
                <xsl:for-each select="Allowance">
                <m:Expense>
                    <m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>            
                    <m:Amount><xsl:value-of select="Amount"/></m:Amount>
                </m:Expense>                
                </xsl:for-each>
            </m:GetQuotationResponse>
        </SOAP-ENV:Body>           
      </Root>
    </xsl:template>
    
    <xsl:template match="Worker[City='SFO']">
        <Root>        
            <SOAP-ENV:Body xmlns:m = "http://www.example.com">
            <m:GetQuotationResponse>
                <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                <m:Location><xsl:value-of select="City"/></m:Location>
                <xsl:for-each select="Base">
                    <m:Expense>
                        <m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>            
                        <m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
                    </m:Expense>                
                </xsl:for-each>
            </m:GetQuotationResponse>
        </SOAP-ENV:Body>   
        </Root>        
    </xsl:template>     
                 
</xsl:stylesheet>

I have two issues(or more)

  • Couldn't get <Root> as the Parent node
  • Templates which are not matching values of <City> also returned. I wanted to match only <xsl:template match="Worker[City='NYC']"> or <xsl:template match="Worker[City='SFO']">

Current Output

<?xml version="1.0" encoding="UTF-8"?>
<Root>
    <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
        <m:GetQuotationResponse xmlns:m="http://www.example.com">
            <m:Worker>12345</m:Worker>
            <m:Location>NYC</m:Location>
            <m:Expense>
                <m:ExpenseType>Meal</m:ExpenseType>
                <m:Amount>150</m:Amount>
            </m:Expense>
            <m:Expense>
                <m:ExpenseType>Gym</m:ExpenseType>
                <m:Amount>200</m:Amount>
            </m:Expense>
        </m:GetQuotationResponse>
    </SOAP-ENV:Body>
</Root>
<Root>
    <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
        <m:GetQuotationResponse xmlns:m="http://www.example.com">
            <m:Worker>56789</m:Worker>
            <m:Location>SFO</m:Location>
            <m:Expense>
                <m:ExpenseType>General</m:ExpenseType>
                <m:Amount>1000</m:Amount>
            </m:Expense>
        </m:GetQuotationResponse>
    </SOAP-ENV:Body>
</Root>
18978
LAX
General
3000

Any help is appreciated to get this working using xslt 2.0 or xslt 3.0. Thank you

I'm posting the first solution that Martin Honnen has suggested.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
    xmlns:m = "http://www.example.com"
    exclude-result-prefixes="xs m SOAP-ENV"
    version="2.0">
    <xsl:output method="xml" indent="yes"/>
     
    
            
    <xsl:template match="Workers">
        <Root>
            <xsl:apply-templates/>
        </Root>
    </xsl:template>
    
    <xsl:template match="Worker[City='NYC']">
        
            <SOAP-ENV:Body xmlns:m = "http://www.example.com">
                <m:GetQuotationResponse>
                    <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                    <m:Location><xsl:value-of select="City"/></m:Location>
                    <xsl:for-each select="Allowance">
                        <m:Expense>
                            <m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>            
                            <m:Amount><xsl:value-of select="Amount"/></m:Amount>
                        </m:Expense>                
                    </xsl:for-each>
                </m:GetQuotationResponse>
            </SOAP-ENV:Body>           
        
    </xsl:template>
    
    <xsl:template match="Worker[City='SFO']">
               
            <SOAP-ENV:Body xmlns:m = "http://www.example.com">
                <m:GetQuotationResponse>
                    <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                    <m:Location><xsl:value-of select="City"/></m:Location>
                    <xsl:for-each select="Base">
                        <m:Expense>
                            <m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>            
                            <m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
                        </m:Expense>                
                    </xsl:for-each>
                </m:GetQuotationResponse>
            </SOAP-ENV:Body>   
           
    </xsl:template> 
    
    <xsl:template match="Worker[not(City = ('SFO', 'NYC'))]"/>
                 
</xsl:stylesheet>

Here is what alternate solution returns. I'm not sure how this solution can be modified to get expected output since City='SFO' has different sibling node <Base> than City='NYC'

<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
      <m:GetQuotationResponse xmlns:m="http://www.example.com">
         <m:Worker>12345</m:Worker>
         <m:Location>NYC</m:Location>
         <m:Expense>
            <m:ExpenseType>Meal</m:ExpenseType>
            <m:Amount>150</m:Amount>
         </m:Expense>
         <m:Expense>
            <m:ExpenseType>Gym</m:ExpenseType>
            <m:Amount>200</m:Amount>
         </m:Expense>
      </m:GetQuotationResponse>
   </SOAP-ENV:Body>
   <SOAP-ENV:Body xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
      <m:GetQuotationResponse xmlns:m="http://www.example.com">
         <m:Worker>56789</m:Worker>
         <m:Location>SFO</m:Location>
      </m:GetQuotationResponse>
   </SOAP-ENV:Body>
</Root>

Following part is missing from the output

    <m:Expense>
        <m:ExpenseType>General</m:ExpenseType>
        <m:Amount>1000</m:Amount>
    </m:Expense>

Alternate solution suggested by @Martin Honnen with some minor tweaks also provide desired output.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:SOAP-ENV = "http://www.w3.org/2001/12/soap-envelope"
    xmlns:m = "http://www.example.com"
    exclude-result-prefixes="xs m SOAP-ENV"
    version="2.0">
    <xsl:output method="xml" indent="yes"/>   
    
    <xsl:template match="Workers">
        <Root>
            <xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>
        </Root>
    </xsl:template>
    
    <xsl:template match="Worker">
        
        <SOAP-ENV:Body xmlns:m = "http://www.example.com">
            <m:GetQuotationResponse>
                <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                <m:Location><xsl:value-of select="City"/></m:Location>
                <xsl:for-each select="Allowance">
                    <m:Expense>
                        <m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>            
                        <m:Amount><xsl:value-of select="Amount"/></m:Amount>
                    </m:Expense>                
                </xsl:for-each>
                
                <xsl:for-each select="Base"> <!-- This part was included to get desired output without having to use more than one templates -->
                    <m:Expense>
                        <m:ExpenseType><xsl:value-of select="BaseType"/></m:ExpenseType>            
                        <m:Amount><xsl:value-of select="BaseAmount"/></m:Amount>
                    </m:Expense>                
                </xsl:for-each>
                
            </m:GetQuotationResponse>
        </SOAP-ENV:Body>           
    </xsl:template> 

</xsl:stylesheet>

Solution

  • Start with a template

    <xsl:template match="Workers">
      <Root>
        <xsl:apply-templates/>
      </Root>
    </xsl:template>
    

    then map your selected Workers to a SOAP body with a single template using e.g. <xsl:template match="Worker[City = ('SFO', 'NYC')]"> or <xsl:template match="Worker[City = 'SFO'] | Worker[City = 'NYC']">, if you prefer.

    For other Workers, set up an empty template e.g. <xsl:template match="Worker[not(City = ('SFO', 'NYC'))]"/>.

    As an alternative, you can of course just use a template matching Worker to map to a SOAP body and make your desired selection in the apply-templates of the first template I have show, i.e. change that to <xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>, that way you also ensure that only the wanted Workers are processed e.g.

    <xsl:template match="Workers">
      <Root>
        <xsl:apply-templates select="Worker[City = ('SFO', 'NYC')]"/>
      </Root>
    </xsl:template>
    
    
    <xsl:template match="Worker">
        
            <SOAP-ENV:Body xmlns:m = "http://www.example.com">
                <m:GetQuotationResponse>
                    <m:Worker><xsl:value-of select="EmpID"/></m:Worker>
                    <m:Location><xsl:value-of select="City"/></m:Location>
                    <xsl:for-each select="Allowance">
                        <m:Expense>
                            <m:ExpenseType><xsl:value-of select="Type"/></m:ExpenseType>            
                            <m:Amount><xsl:value-of select="Amount"/></m:Amount>
                        </m:Expense>                
                    </xsl:for-each>
                </m:GetQuotationResponse>
            </SOAP-ENV:Body>           
        
    </xsl:template>