I have the following example 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>
<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>
<CustomerGender null="no">F</CustomerGender>
<CustomerRaceCode null="no">White</CustomerRaceCode>
<CustomerRaceName null="yes"/>
</Example>
What I WANT to do, is to output XML so that the Address nodes are shared under HomeAddress/Address, like this:
<?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>
<HomeAddress>
<Address>
<AddressLine1>1234 Easy ST</AddressLine1>
<AddressLine2/>
<City>Hartford</City>
<State>CT</State>
<ZipCode>123456</ZipCode>
</Address>
</HomeAddress>
<Gender><Code>F</Code></Gender>
<RaceCode>White</RaceCode>
<RaceName/>
</Information>
This is my current XSLT code:
<?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">
<Information>
<xsl:for-each select="*">
<xsl:apply-templates select=".[starts-with(local-name(), 'CustomerGender')]"/>
<xsl:apply-templates select=".[starts-with(local-name(), 'CustomerDOB')]"/>
<xsl:apply-templates select=".[starts-with(local-name(), 'CustomerAddress')
or starts-with(local-name(), 'CustomerCity')
or starts-with(local-name(), 'CustomerState')
or starts-with(local-name(), 'CustomerZipCode')][1]"/>
<xsl:apply-templates select=".[starts-with(local-name(), 'Customer')
and not(contains(local-name(), 'Gender'))
and not(contains(local-name(), 'Address'))
and not(contains(local-name(), 'City'))
and not(contains(local-name(), 'State'))
and not(contains(local-name(), 'ZipCode'))
and not(contains(local-name(), 'DOB'))]"/>
</xsl:for-each>
</Information>
</xsl:template>
<xsl:template match=".[starts-with(local-name(), 'Customer')]">
<xsl:element name="{replace(local-name(), 'Customer', '')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match=".[starts-with(local-name(), 'CustomerGender')]">
<xsl:element name="{replace(local-name(), 'Customer', '')}">
<Code>
<xsl:apply-templates/>
</Code>
</xsl:element>
</xsl:template>
<xsl:template match=".[starts-with(local-name(), 'CustomerDOB')]">
<DateOfBirth>
<DateNode>
<xsl:apply-templates/>
</DateNode>
</DateOfBirth>
</xsl:template>
<xsl:template match=".[starts-with(local-name(), 'CustomerAddress')
or starts-with(local-name(), 'CustomerCity')
or starts-with(local-name(), 'CustomerState')
or starts-with(local-name(), 'CustomerZipCode')]">
<HomeAddress>
<Address>
<xsl:for-each select=".">
<xsl:element name="{replace(local-name(), 'Customer', '')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:for-each>
</Address>
</HomeAddress>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Yet - here is 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>
<RaceCode>White</RaceCode>
<RaceName/>
<HomeAddress><Address><AddressLine1>1234 Easy ST</AddressLine1></Address></HomeAddress><HomeAddress><Address><AddressLine2/></Address></HomeAddress>
<HomeAddress><Address><City>Hartford</City></Address></HomeAddress>
<HomeAddress><Address><State>CT</State></Address></HomeAddress>
<HomeAddress><Address><ZipCode>123456</ZipCode></Address></HomeAddress>
</Information>
How can I change this XSL logic so that I get the desired output (the shared "Address" node) instead of the current (multiple "Address" nodes)?
I'll appreciate any help on this, it's possible I overlooked a previous example, but I tried searching and couldn't find anything specific to this scenario.
EDIT: I understand that one can extract the "Address" information outside of the "for-each" I have, like this:
<xsl:for-each select="*">
<xsl:apply-templates select=".[starts-with(local-name(), 'CustomerGender')]"/>
<xsl:apply-templates select=".[starts-with(local-name(), 'CustomerDOB')]"/>
<xsl:apply-templates select=".[starts-with(local-name(), 'Customer')
and not(contains(local-name(), 'Gender'))
and not(contains(local-name(), 'Address'))
and not(contains(local-name(), 'City'))
and not(contains(local-name(), 'State'))
and not(contains(local-name(), 'ZipCode'))
and not(contains(local-name(), 'DOB'))]"/>
</xsl:for-each>
<HomeAddress>
<Address>
<xsl:apply-templates select="*[starts-with(local-name(), 'CustomerAddress')
or starts-with(local-name(), 'CustomerCity')
or starts-with(local-name(), 'CustomerState')
or starts-with(local-name(), 'CustomerZipCode')]"/>
</Address>
</HomeAddress>
However, I want to also preserve the order of the data. If the customer address information is in the middle of the data, I want it to appear as such in the output.
CodePudding user response:
Is there a reason why you cannot do simply:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Example">
<xsl:variable name="addr" select="CustomerAddressLine1 | CustomerAddressLine2 | CustomerCity | CustomerState | CustomerZipCode" />
<Information>
<xsl:copy-of select="* except $addr"/>
<HomeAddress>
<Address>
<xsl:copy-of select="$addr"/>
</Address>
</HomeAddress>
</Information>
</xsl:template>
</xsl:stylesheet>
Added:
Assuming that the address fields are always in a contiguous block, the expected output could be produced using:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<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:apply-templates select="$addr[last()]/following-sibling::*[starts-with(name(), 'Customer')]"/>
</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:stylesheet>