Home > Software engineering >  XSLT - Select Nodes (Based On Arrays) Between Two Nodes?
XSLT - Select Nodes (Based On Arrays) Between Two Nodes?

Time:04-22

I now have the following XML:

<Example>
        <SiteCode null="no">1ExampleSite</SiteCode>
        <SiteName null="yes"/>
        <CustomerPIN null="no">1234567</CustomerPIN>
        <CustomerLastName null="no">Test </CustomerLastName>
        <CustomerFirstName null="no">Example</CustomerFirstName>
        <CustomerMiddleName null="yes"/>
        <CustomerDOB null="no">2000-01-01 00:00:00.000</CustomerDOB>
        <CustomerGender null="no">F</CustomerGender>
        <CustomerAddressLine1 null="no">1234 Easy ST</CustomerAddressLine1>
        <CustomerAddressLine2 null="yes"/>
        <CustomerCity null="no">Hartford</CustomerCity>
        <CustomerState null="no">CT</CustomerState>
        <CustomerZipCode null="no">123456</CustomerZipCode>
        <CustomerRaceCode null="no">White</CustomerRaceCode>
        <CustomerRaceName null="yes"/>
        <CustomerReferral1Phone>8881234567</CustomerReferral1Phone>
        <CustomerReferral2Phone>8881234567</CustomerReferral2Phone>
        <CustomerReferral3Phone>8881234567</CustomerReferral3Phone>
        <CustomerOrderNumber>48290275</CustomerOrderNumber>
        <ShopDesignationNumber>4572</ShopDesignationNumber>
        <CustomerFirstOrderDate>2020-11-13</CustomerFirstOrderDate>
        
</Example>

I also have the following XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0">

  <xsl:template match="/Example">
    <xsl:variable name="addr" select="CustomerAddressLine1 | CustomerAddressLine2 | CustomerCity | CustomerState | CustomerZipCode" />
    <xsl:variable name = "referralContact1" select="*[starts-with(local-name(), 'CustomerReferral1')]"/>
    <xsl:variable name = "referralContact2" select="*[starts-with(local-name(), 'CustomerReferral2')]"/>
    <xsl:variable name = "referralContact3" select="*[starts-with(local-name(), 'CustomerReferral3')]"/>

    <Information>
        <xsl:apply-templates select="$addr[1]/preceding-sibling::*[starts-with(name(), 'Customer')]"/>
        <HomeAddress>
            <Address>
                <xsl:apply-templates select="$addr" />
            </Address>
        </HomeAddress>
        <xsl:apply-templates select="$addr[last()]/following-sibling::*[starts-with(name(), 'Customer')]"/>

        <CustomerReferrals>
          <xsl:apply-templates select="$referralContact1"/>
          <xsl:apply-templates select="$referralContact2"/>
          <xsl:apply-templates select="$referralContact3"/>
        </CustomerReferrals>
    </Information>
</xsl:template>

<xsl:template match="*">
    <xsl:element name="{substring-after(name(), 'Customer')}">
        <xsl:apply-templates/>
    </xsl:element>
</xsl:template> 

<xsl:template match="CustomerGender">
    <Gender>
        <Code>
            <xsl:apply-templates/>
        </Code>
  </Gender>
</xsl:template>

<xsl:template match="CustomerDOB">
    <DateOfBirth>
        <DateNode>
            <xsl:apply-templates/>
        </DateNode>
  </DateOfBirth>
</xsl:template>

<xsl:template match="*[starts-with(local-name(), 'CustomerReferral')]">
  <xsl:element name="{replace(local-name(), 'CustomerReferral[\d]', '')}">
    <xsl:apply-templates/>
  </xsl:element>
</xsl:template>

</xsl:stylesheet>

Here's the output that I would hope for:

<?xml version="1.0" encoding="UTF-8"?>
<Information>
<PIN>1234567</PIN>
<LastName>Test </LastName>
<FirstName>Example</FirstName>
<MiddleName/>
<DateOfBirth><DateNode>2000-01-01 00:00:00.000</DateNode></DateOfBirth>
<Gender><Code>F</Code></Gender>
<HomeAddress>
  <Address>
   <AddressLine1>1234 Easy ST</AddressLine1>
   <AddressLine2/>
   <City>Hartford</City>
   <State>CT</State>
   <ZipCode>123456</ZipCode>
  </Address>
</HomeAddress>
<RaceCode>White</RaceCode>
<RaceName/>
<CustomerReferrals>
  <Phone>8881234567</Phone>
  <Phone>8881234567</Phone>
  <Phone>8881234567</Phone>
</CustomerReferrals>
<OrderNumber>48290275</OrderNumber>
<ShopDesignationNumber>4572</ShopDesignationNumber>
<FirstOrderDate>2020-11-13</FirstOrderDate>
</Information>

Here's my current output:

<?xml version="1.0" encoding="UTF-8"?>
<Information>
<PIN>1234567</PIN>
<LastName>Test </LastName>
<FirstName>Example</FirstName>
<MiddleName/>
<DateOfBirth><DateNode>2000-01-01 00:00:00.000</DateNode></DateOfBirth>
<Gender><Code>F</Code></Gender>
<HomeAddress>
  <Address>
   <AddressLine1>1234 Easy ST</AddressLine1>
   <AddressLine2/>
   <City>Hartford</City>
   <State>CT</State>
   <ZipCode>123456</ZipCode>
  </Address>
</HomeAddress>
<RaceCode>White</RaceCode>
<RaceName/>
<Phone>8881234567</Phone>
<Phone>8881234567</Phone>
<Phone>8881234567</Phone>
<OrderNumber>48290275</OrderNumber>
<FirstOrderDate>2020-11-13</FirstOrderDate>
<CustomerReferrals>
  <Phone>8881234567</Phone>
  <Phone>8881234567</Phone>
  <Phone>8881234567</Phone>
</CustomerReferrals>
</Information>

Is there any way that I can be sure I'm selecting ONLY the nodes between the address nodes and the "ReferralPhone" nodes BEFORE the "ReferralPhone" transformation call so that I don't create these extra phone number lines? I also want to preserve the order of the elements that are getting put into the new XML, if possible.

CodePudding user response:

Not sure this is the right approach in general, but it does what you want. The idea is to figure out contextual indexes of key elements and then apply templates for nodes between them.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0">
    
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="/Example">
        <xsl:variable name="addr" select="CustomerAddressLine1 | CustomerAddressLine2 | CustomerCity | CustomerState | CustomerZipCode" />        
        
        <Information>
            <xsl:apply-templates select="$addr[1]/preceding-sibling::*[starts-with(name(), 'Customer')]"/>
            <HomeAddress>                
                <Address>
                    <xsl:apply-templates select="$addr" />
                </Address>
            </HomeAddress>

            <xsl:variable name="lastAddrIndex" select="count(*[contains(local-name(),'ZipCode')][1]/preceding-sibling::*) 1"/>
            <xsl:variable name="firstRefIndex" select="count(*[contains(local-name(),'Phone')][1]/preceding-sibling::*)"/>
            <xsl:variable name="lastRefIndex" select="count(*[contains(local-name(),'Phone')][last()]/preceding-sibling::*)"/>
            
            <!-- process everything before phones -->
            <xsl:apply-templates select="*[(position() &lt;= $firstRefIndex) 
                and (position() &gt; $lastAddrIndex)]"/>
            
            <CustomerReferrals>
                <xsl:apply-templates select="node()[contains(local-name(),'Phone')]"></xsl:apply-templates>                
            </CustomerReferrals>
            
            <!-- process everything after phones -->            
            <xsl:apply-templates select="*[position() &gt; $lastRefIndex]"/>
            
        </Information>
    </xsl:template>
    
    <xsl:template match="*">
        <xsl:choose>
            <xsl:when test="contains(name(), 'Customer')">
                <xsl:element name="{substring-after(name(), 'Customer')}">
                    <xsl:apply-templates/>
                </xsl:element>        
            </xsl:when>
            <xsl:otherwise>
                <xsl:element name="{name()}">
                    <xsl:apply-templates/>
                </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
        
    </xsl:template> 
    
    <xsl:template match="CustomerGender">
        <Gender>
            <Code>
                <xsl:apply-templates/>
            </Code>
        </Gender>
    </xsl:template>
    
    <xsl:template match="CustomerDOB">
        <DateOfBirth>
            <DateNode>
                <xsl:apply-templates/>
            </DateNode>
        </DateOfBirth>
    </xsl:template>
    
    <xsl:template match="*[starts-with(local-name(), 'CustomerReferral')]">
        <xsl:element name="{replace(local-name(), 'CustomerReferral[\d]', '')}">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    
</xsl:stylesheet>
  • Related