Extreme XSLT XML Flattening - xslt

I have the following input XML file:
<root>
<a>
<b>1</b>
</a>
<c>
<d>
<e>2</e>
<f>3</f> or <e>3</e>
</d>
<g h="4"/>
<i>
<j>
<k>
<l m="5" n="6" o="7" />
<l m="8" n="9" o="0" />
</k>
</j>
</i>
</c>
</root>
I would like to use XSLT to transform it into the follow outputs:
OUTPUT 1
<root>
<row b="1" e="2" f="3" h="4" m="5" n="6" o="7" />
<row b="1" e="2" f="3" h="4" m="8" n="9" o="0" />
<root>
OUTPUT 2
<root>
<row b="1" e="2" h="4" m="5" n="6" o="7" />
<row b="1" e="2" h="4" m="8" n="9" o="0" />
<row b="1" e="3" h="4" m="5" n="6" o="7" />
<row b="1" e="3" h="4" m="8" n="9" o="0" />
<root>
Can anyone help my XSLT isn't very strong. Thanks.

It will be easier if you let the occurrence of <e> determine the outer loop constructing your <row>s and have a inner loop iterating over all <l>s.
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="e">
<!-- store values of 'e' and 'f' in params -->
<xsl:param name="value_of_e" select="." />
<xsl:param name="value_of_f" select="ancestor::d[1]/f" />
<!-- iterate over all 'l's -->
<xsl:for-each select="//l">
<xsl:element name="row">
<xsl:attribute name="b">
<xsl:value-of select="//b" />
</xsl:attribute>
<xsl:attribute name="e">
<xsl:value-of select="$value_of_e" />
</xsl:attribute>
<!-- only include 'f' if it has a value -->
<xsl:if test="$value_of_f != ''">
<xsl:attribute name="f">
<xsl:value-of select="$value_of_f" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="h">
<xsl:value-of select="ancestor::c[1]/g/#h" />
</xsl:attribute>
<xsl:attribute name="m">
<xsl:value-of select="./#m" />
</xsl:attribute>
<xsl:attribute name="n">
<xsl:value-of select="./#n" />
</xsl:attribute>
<xsl:attribute name="o">
<xsl:value-of select="./#o" />
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="b" />
<xsl:template match="f" />
<xsl:template match="g" />
</xsl:stylesheet>

Related

Select repeating node with xpath

I am using XSLT 1.0
I have the following xml document
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<InputMessagePart_0>
<ns1:ArrayOfArticleMasterDTO xmlns:ns1="http://BTS.GO.FactFeeds/DC_ArticleMaster">
<ArticleMasterDTO>
<AltBarcodes>
<string>5020436473709</string>
</AltBarcodes>
<ClientId>GO01</ClientId>
<ArticleID>100000005503</ArticleID>
<ArticleReference>CORE_OWBA_WW873R</ArticleReference>
<ArticleSize>14</ArticleSize>
<ArticleStyle>26600</ArticleStyle>
<ArticleFit>-</ArticleFit>
<SupplierID>50102</SupplierID>
<Description>AMELIE OTRS [REG]</Description>
<Sku>COREOWBAWW873RBLAC-14</Sku>
<UnitsPerCase>1</UnitsPerCase>
<UnitsPerLayer xsi:nil="true" />
<UnitsPerPallet xsi:nil="true" />
<LayersPerPallet xsi:nil="true" />
<FullPalletWeightKG xsi:nil="true" />
<CaseLengthMM xsi:nil="true" />
<CaseHeightMM xsi:nil="true" />
<CaseDepthMM xsi:nil="true" />
<CaseWeightG xsi:nil="true" />
<Colour>BLAC</Colour>
<Level1Description>CLOTHING</Level1Description>
<Level2Description>WOMENS</Level2Description>
<Level3Description>WP LEGWEAR</Level3Description>
<Level4Description>LEG COATED</Level4Description>
<PrimaryBarcode>5051513724896</PrimaryBarcode>
</ArticleMasterDTO>
<ArticleMasterDTO>
<AltBarcodes>
<string>5027793433728</string>
</AltBarcodes>
<ClientId>GO01</ClientId>
<ArticleID>100000032177</ArticleID>
<ArticleReference>CORE_OMOE_47354</ArticleReference>
<ArticleSize>L-XL</ArticleSize>
<ArticleStyle>24608</ArticleStyle>
<ArticleFit>-</ArticleFit>
<SupplierID>50013</SupplierID>
<Description>POWER STRETCH GLOVE</Description>
<Sku>COREOMOE47354BLAC-L-XL</Sku>
<UnitsPerCase>6</UnitsPerCase>
<UnitsPerLayer xsi:nil="true" />
<UnitsPerPallet xsi:nil="true" />
<LayersPerPallet xsi:nil="true" />
<FullPalletWeightKG xsi:nil="true" />
<CaseLengthMM xsi:nil="true" />
<CaseHeightMM xsi:nil="true" />
<CaseDepthMM xsi:nil="true" />
<CaseWeightG xsi:nil="true" />
<Colour>BLAC</Colour>
<Level1Description>CLOTHING</Level1Description>
<Level2Description>MENS</Level2Description>
<Level3Description>GLOVES</Level3Description>
<Level4Description>FLEECE GLOVE</Level4Description>
<PrimaryBarcode>5052071278609</PrimaryBarcode>
</ArticleMasterDTO>
</ns1:ArrayOfArticleMasterDTO>
</InputMessagePart_0>
<InputMessagePart_1>
<ns2:SelectResponse xmlns:ns2="http://Microsoft.LobServices.OracleDB/2007/03/USER_DWDEV/Table/DW_PACK_BARCODES">
<ns2:SelectResult>
<ns2:DW_PACK_BARCODESRECORDSELECT>
<ns2:PRODUCT_ID>100000005503</ns2:PRODUCT_ID>
<ns2:SYS_BARCODE>SYS_BARCODES</ns2:SYS_BARCODE>
<ns2:PACK_TYPE>PACK_T</ns2:PACK_TYPE>
<ns2:CHECK_DIGIT_PACK_BARCODE>PackBarcode5503</ns2:CHECK_DIGIT_PACK_BARCODE>
<ns2:PACK_BARCODE>PACK_BARCODEP</ns2:PACK_BARCODE>
<ns2:CHECK_DIGIT_CARTON_BARCODE>CHECK_DIGIT_CA</ns2:CHECK_DIGIT_CARTON_BARCODE>
<ns2:CARTON_BARCODE>CARTON_BARCOD</ns2:CARTON_BARCODE>
<ns2:IN_DATE>1999-05-31T13:20:00.000-05:00</ns2:IN_DATE>
<ns2:UP_DATE>1999-05-31T13:20:00.000-05:00</ns2:UP_DATE>
<ns2:CREATEDTIME>1999-05-31T13:20:00.000-05:00</ns2:CREATEDTIME>
</ns2:DW_PACK_BARCODESRECORDSELECT>
<ns2:DW_PACK_BARCODESRECORDSELECT>
<ns2:PRODUCT_ID>100000032177</ns2:PRODUCT_ID>
<ns2:SYS_BARCODE>SYS_BARCODES</ns2:SYS_BARCODE>
<ns2:PACK_TYPE>PACK_T</ns2:PACK_TYPE>
<ns2:CHECK_DIGIT_PACK_BARCODE>PackBarcode32177</ns2:CHECK_DIGIT_PACK_BARCODE>
<ns2:PACK_BARCODE>PACK_BARCODEP</ns2:PACK_BARCODE>
<ns2:CHECK_DIGIT_CARTON_BARCODE>CHECK_DIGIT_CA</ns2:CHECK_DIGIT_CARTON_BARCODE>
<ns2:CARTON_BARCODE>CARTON_BARCOD</ns2:CARTON_BARCODE>
<ns2:IN_DATE>1999-05-31T13:20:00.000-05:00</ns2:IN_DATE>
<ns2:UP_DATE>1999-05-31T13:20:00.000-05:00</ns2:UP_DATE>
<ns2:CREATEDTIME>1999-05-31T13:20:00.000-05:00</ns2:CREATEDTIME>
</ns2:DW_PACK_BARCODESRECORDSELECT>
</ns2:SelectResult>
</ns2:SelectResponse>
</InputMessagePart_1>
</ns0:Root>
I'd like to select the value of the CHECK_DIGIT_PACK_BARCODE element that is a sibling of a given PRODUCT_ID
Following Tim's suggestion, here's my XSLT which attempts to provide a value for OuterCartonBarcode by keying from the CHECK_DIGIT_PACK_BARCODE of the ns2:SelectResponse
<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var ns2 s1" version="1.0" xmlns:ns1="http://BTS.GO.FactFeeds/DC_ArticleMaster" xmlns:s1="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:ns2="http://Microsoft.LobServices.OracleDB/2007/03/USER_DWDEV/Table/DW_PACK_BARCODES" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:key name="barcodes" match="ns2:DW_PACK_BARCODESRECORDSELECT" use="ns2:PRODUCT_ID" />
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s1:Root" />
</xsl:template>
<xsl:template match="/s1:Root">
<ns1:ArrayOfArticleMasterDTO>
<xsl:for-each select="InputMessagePart_0/ns1:ArrayOfArticleMasterDTO/ArticleMasterDTO">
<ArticleMasterDTO>
<AltBarcodes>
<string>
<xsl:value-of select="AltBarcodes/string/text()" />
</string>
<xsl:value-of select="AltBarcodes/text()" />
</AltBarcodes>
<ClientId>
<xsl:value-of select="ClientId/text()" />
</ClientId>
<ArticleID>
<xsl:value-of select="ArticleID/text()" />
</ArticleID>
<ArticleReference>
<xsl:value-of select="ArticleReference/text()" />
</ArticleReference>
<ArticleSize>
<xsl:value-of select="ArticleSize/text()" />
</ArticleSize>
<ArticleStyle>
<xsl:value-of select="ArticleStyle/text()" />
</ArticleStyle>
<ArticleFit>
<xsl:value-of select="ArticleFit/text()" />
</ArticleFit>
<SupplierID>
<xsl:value-of select="SupplierID/text()" />
</SupplierID>
<Description>
<xsl:value-of select="Description/text()" />
</Description>
<Sku>
<xsl:value-of select="Sku/text()" />
</Sku>
<UnitsPerCase>
<xsl:value-of select="UnitsPerCase/text()" />
</UnitsPerCase>
<UnitsPerLayer>
<xsl:value-of select="UnitsPerLayer/text()" />
</UnitsPerLayer>
<xsl:variable name="var:v1" select="string(UnitsPerPallet/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v1)='true'">
<UnitsPerPallet>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</UnitsPerPallet>
</xsl:if>
<xsl:if test="string($var:v1)='false'">
<UnitsPerPallet>
<xsl:value-of select="UnitsPerPallet/text()" />
</UnitsPerPallet>
</xsl:if>
<xsl:variable name="var:v2" select="string(LayersPerPallet/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v2)='true'">
<LayersPerPallet>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</LayersPerPallet>
</xsl:if>
<xsl:if test="string($var:v2)='false'">
<LayersPerPallet>
<xsl:value-of select="LayersPerPallet/text()" />
</LayersPerPallet>
</xsl:if>
<xsl:variable name="var:v3" select="string(FullPalletWeightKG/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v3)='true'">
<FullPalletWeightKG>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</FullPalletWeightKG>
</xsl:if>
<xsl:if test="string($var:v3)='false'">
<FullPalletWeightKG>
<xsl:value-of select="FullPalletWeightKG/text()" />
</FullPalletWeightKG>
</xsl:if>
<xsl:variable name="var:v4" select="string(CaseLengthMM/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v4)='true'">
<CaseLengthMM>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</CaseLengthMM>
</xsl:if>
<xsl:if test="string($var:v4)='false'">
<CaseLengthMM>
<xsl:value-of select="CaseLengthMM/text()" />
</CaseLengthMM>
</xsl:if>
<xsl:variable name="var:v5" select="string(CaseHeightMM/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v5)='true'">
<CaseHeightMM>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</CaseHeightMM>
</xsl:if>
<xsl:if test="string($var:v5)='false'">
<CaseHeightMM>
<xsl:value-of select="CaseHeightMM/text()" />
</CaseHeightMM>
</xsl:if>
<CaseDepthMM>
<xsl:value-of select="CaseDepthMM/text()" />
</CaseDepthMM>
<CaseWeightG>
<xsl:value-of select="CaseWeightG/text()" />
</CaseWeightG>
<Colour>
<xsl:value-of select="Colour/text()" />
</Colour>
<Level1Description>
<xsl:value-of select="Level1Description/text()" />
</Level1Description>
<Level2Description>
<xsl:value-of select="Level2Description/text()" />
</Level2Description>
<Level3Description>
<xsl:value-of select="Level3Description/text()" />
</Level3Description>
<Level4Description>
<xsl:value-of select="Level4Description/text()" />
</Level4Description>
<xsl:call-template name="PBTemplate">
<xsl:with-param name="id" select="string(ArticleID/text())" />
</xsl:call-template>
<xsl:variable name="var:v6" select="string(PrimaryBarcode/#xsi:nil) = 'true'" />
<xsl:if test="string($var:v6)='true'">
<PrimaryBarcode>
<xsl:attribute name="xsi:nil">
<xsl:value-of select="'true'" />
</xsl:attribute>
</PrimaryBarcode>
</xsl:if>
<xsl:if test="string($var:v6)='false'">
<PrimaryBarcode>
<xsl:value-of select="PrimaryBarcode/text()" />
</PrimaryBarcode>
</xsl:if>
</ArticleMasterDTO>
</xsl:for-each>
</ns1:ArrayOfArticleMasterDTO>
</xsl:template>
<xsl:template name="PBTemplate">
<xsl:param name="id" select="ArticleID" />
<xsl:for-each select="key('barcodes',$id)">
<OuterCartonBarcode>
<xsl:value-of select="/ns2:CHECK_DIGIT_PACK_BARCODE"/>
</OuterCartonBarcode>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Your current xpath ends with this...
/*[local-name() = 'CHECK_DIGIT_PACK_BARCODE'[PRODUCT_ID/text()="100000005503"]]
... which does not look correct.
The expression you are looking for is this....
/ns0:Root/InputMessagePart_1/*[local-name() = 'SelectResponse']/*[local-name() = 'SelectResult']/*[*[local-name()= 'PRODUCT_ID']/text()='100000005503']/*[local-name() = 'CHECK_DIGIT_PACK_BARCODE']
Or, to make it a bit more readable, this....
/ns0:Root
/InputMessagePart_1
/*[local-name() = 'SelectResponse']
/*[local-name() = 'SelectResult']
/*[*[local-name()= 'PRODUCT_ID']/text()='100000005503']
/*[local-name() = 'CHECK_DIGIT_PACK_BARCODE']
It would be much better if the expression did not try to ignore the namespaces, but used the relevant prefixes (as happens already with ns0:Root). Then the expression is much simplified
/ns0:Root/InputMessagePart_1/ns2:SelectResponse/ns2:SelectResult/*[ns2:PRODUCT_ID='100000005503']/ns2:CHECK_DIGIT_PACK_BARCODE
Or better still, define a key like so:
<xsl:key name="barcodes" match="ns2:DW_PACK_BARCODESRECORDSELECT" use="ns2:PRODUCT_ID" />
Then you can write just this...
key('barcodes', '100000005503')/ns2:CHECK_DIGIT_PACK_BARCODE

How to define attribute name dynamically in XSLT?

I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:param name="navigation-xml">
<item id="home" title-en="Services" title-de="Leistungen" />
<item id="company" title-en="Company" title-de="Unternehmen" />
<item id="references" title-en="References" title-de="Referenzen" />
</xsl:param>
<xsl:param name="navigation" select="exsl:node-set($navigation-xml)/*" />
<xsl:param name="navigation-id" />
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
How can I refactor the last 12 lines, so that the attribute name (either #title-de or #title-en) gets determined dynamically rather than in the (silly) way I did it in?
Thanks for any help.
You could write
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
as
<xsl:template name="title">
<xsl:apply-templates select="$navigation[$navigation-id = #id]" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:value-of select="#*[local-name() = concat('title-', $current-language)]" />
</xsl:template>
IMHO, your problem starts much earlier. If you define your navigation-xml parameter as:
<xsl:param name="navigation-xml">
<item id="home">
<title lang="en">Services</title>
<title lang="de">Leistungen</title>
</item>
<item id="company">
<title lang="en">Company</title>
<title lang="de">Unternehmen</title>
</item>
<item id="references">
<title lang="en">References</title>
<title lang="de">Referenzen</title>
</item>
</xsl:param>
you will be able to address its individual nodes much more conveniently and elegantly.

Group contiguous siblings of a type

Given siblings, some of which are <row> elements and some not, like this,
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
using xslt 1.0, I need to process them in order but to group the non-row ones together as I go, like this,
<notRow>
<h />
</notRow>
<row id='v' />
<notRow>
<a />
<b />
</notRow>
<row id='w' />
<notRow>
<d />
<row id='x' />
<row id='y' />
<notRow>
<f />
<r />
</notRow>
<row id='z' />
The first and last may or may not be <row> elements.
How?
It can be as short and simple as this (no need of calling templates several times, xsl:for-each, xsl:if). Here is the complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:copy-of select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML (wrapped into a single top element to make it well-formed):
<t>
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
</t>
the wanted, correct result is produced:
<t>
<notRow>
<h/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
</t>
Update:
The OP has expressed an additional requirement that nodes need be processed by matching templates -- not just copied.
This requires only minimal change:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:apply-templates mode="group" select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<!-- This template can be replaced with whatever processing needed -->
<xsl:template match="*" mode="group">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
The template that operates in mode "group" should be substituted with template(s) that implement the exact wanted processing. In this case it copies the matched element -- but in the real application any wanted processing would go here.
You might be able to do a trick with a key to group each non-row element by its preceding row (if there is one), or its parent element if not:
<xsl:key name="elementsFollowingRow"
match="*[not(self::row)]"
use="generate-id( (.. | preceding-sibling::row )[last()])" />
and define a named template to put in a notRow if the current element has any associated elements according to the key
<xsl:template name="addNotRow">
<xsl:if test="key('elementsFollowingRow', generate-id())">
<notRow>
<xsl:copy-of select="key('elementsFollowingRow', generate-id())" />
</notRow>
</xsl:if>
</xsl:template>
Then in the template where you're matching the parent element (the one that contains all these row and non-row elements you can do
<xsl:call-template name="addNotRow" />
<xsl:for-each select="row">
<xsl:copy-of select="." />
<xsl:call-template name="addNotRow" />
</xsl:for-each>
The first call-template outside the for-each will deal with any notRow that is required before the first row, and the call inside the for-each will put in any notRow required after the row in question.
This isn't pretty, but it works.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="t">
<xsl:if test="row[1]/preceding-sibling::*">
<notRow>
<xsl:for-each select="row[1]/preceding-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
<xsl:for-each select="row">
<xsl:copy-of select="."/>
<xsl:if test="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<notRow>
<xsl:for-each select="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:for-each>
<xsl:if test="row[last()]/following-sibling::*">
<notRow>
<xsl:for-each select="row[last()]/following-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
On this XML source
<t>
<h />
<i />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
<i />
</t>
it returns the correct result:
<notRow>
<h/>
<i/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
<notRow>
<i/>
</notRow>
but it does seem there should be something simpler.

XSL check param length and make a choice

I need to check if a param has got a value in it and if it has then do this line otherwise do this line.
I've got it working whereas I don't get errors but it's not taking the right branch
The branch that is wrong is in the volunteer_role template
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="volunteers-by-region" match="volunteer" use="region" />
<xsl:template name="hoo" match="/">
<html>
<head>
<title>Registered Volunteers</title>
<link rel="stylesheet" type="text/css" href="volunteer.css" />
</head>
<body>
<h1>Registered Volunteers</h1>
<h3>Ordered by the username ascending</h3>
<xsl:for-each select="folktask/member[user/account/userlevel='2']">
<xsl:for-each select="volunteer[count(. | key('volunteers-by-region', region)[1]) = 1]">
<xsl:sort select="region" />
<xsl:for-each select="key('volunteers-by-region', region)">
<xsl:sort select="folktask/member/user/personal/name" />
<div class="userdiv">
<xsl:call-template name="volunteer_volid">
<xsl:with-param name="volid" select="../volunteer/#id" />
</xsl:call-template>
<xsl:call-template name="volunteer_role">
<xsl:with-param name="volrole" select="../volunteer/roles" />
</xsl:call-template>
<xsl:call-template name="volunteer_region">
<xsl:with-param name="volloc" select="../volunteer/region" />
</xsl:call-template>
</div>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
<xsl:if test="position()=last()">
<div class="count">
<h2>Total number of volunteers: <xsl:value-of select="count(/folktask/member/user/account/userlevel[text()=2])" />
</h2>
</div>
</xsl:if>
</body>
</html>
</xsl:template>
<xsl:template name="volunteer_volid">
<xsl:param name="volid" select="'Not Available'" />
<div class="heading2 bold"><h2>VOLUNTEER ID: <xsl:value-of select="$volid" /></h2></div>
</xsl:template>
<xsl:template name="volunteer_role">
<xsl:param name="volrole" select="'Not Available'" />
<div class="small bold">ROLES:</div>
<div class="large">
<xsl:choose>
<xsl:when test="string-length($volrole)!=0">
<xsl:value-of select="$volrole" />
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
<xsl:template name="volunteer_region">
<xsl:param name="volloc" select="'Not Available'" />
<div class="small bold">REGION:</div>
<div class="large"><xsl:value-of select="$volloc" /></div>
</xsl:template>
</xsl:stylesheet>
And the XML:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="volunteers.xsl"?>
<folktask xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:noNamespaceSchemaLocation="folktask.xsd">
<member>
<user id="1">
<personal>
<name>Abbie Hunt</name>
<sex>Female</sex>
<address1>108 Access Road</address1>
<address2></address2>
<city>Wells</city>
<county>Somerset</county>
<postcode>BA5 8GH</postcode>
<telephone>01528927616</telephone>
<mobile>07085252492</mobile>
<email>adrock#gmail.com</email>
</personal>
<account>
<username>AdRock</username>
<password>269eb625e2f0cf6fae9a29434c12a89f</password>
<userlevel>4</userlevel>
<signupdate>2010-03-26T09:23:50</signupdate>
</account>
</user>
<volunteer id="1">
<roles></roles>
<region>South West</region>
</volunteer>
</member>
<member>
<user id="2">
<personal>
<name>Aidan Harris</name>
<sex>Male</sex>
<address1>103 Aiken Street</address1>
<address2></address2>
<city>Chichester</city>
<county>Sussex</county>
<postcode>PO19 4DS</postcode>
<telephone>01905149894</telephone>
<mobile>07784467941</mobile>
<email>ambientexpert#yahoo.co.uk</email>
</personal>
<account>
<username>AmbientExpert</username>
<password>8e64214160e9dd14ae2a6d9f700004a6</password>
<userlevel>2</userlevel>
<signupdate>2010-03-26T09:23:50</signupdate>
</account>
</user>
<volunteer id="2">
<roles>Van Driver,gas Fitter</roles>
<region>South Central</region>
</volunteer>
</member>
</folktask>
The following should do the trick:
<xsl:template name="volunteer_volid">
<xsl:param name="volid" />
<xsl:choose>
<xsl:when test="string-length($volid) > 0">
<div class="heading2 bold"><h2>VOLUNTEER ID: <xsl:value-of select="$volid" /></h2></div>
</xsl:when>
<xsl:otherwise>
<!-- No volid -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I've replaced the default value with an empty string so that not providing a parameter value is the same as providing the parameter value as "". If this isn't the desired behaviour then change the parameters select and modify the test expression accordingly, for example:
$volid != 'Not Available'

xsl grouping sort problem

I have the following xsl template that I'm using to group my xsl. The problem I have is that I need to uppercase the #Title as currently my grouping is seeing upper and lowercase as seperate groups.
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:sort select="substring(#Title,1,1)" />
<p></p><xsl:value-of select="substring(#Title,1,1)" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
I tried to use call-template and set a variable but xsl does not seem to like this:
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:variable name="myTitle">
<xsl:call-template name="to-upper">
<xsl:with-param name="text">
<xsl:value-of select="#Title"/>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<p></p><xsl:value-of select="$myTitle" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
What I am trying to achieve is meunchian grouping but without case sensitivity - hope this makes Sense!
Kieran
The way to convert lower-case letters to upper is to use the XPath translate() function.
Using it, one way to express the desired transformation is the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:variable name="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'"
/>
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"
/>
<xsl:key name="rows-by-title" match="Row" use=
"translate(substring(#Title,1,1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)" />
<xsl:template match="/">
<html>
<xsl:apply-templates select="*/*"/>
</html>
</xsl:template>
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select=
"Row[generate-id()
=
generate-id(key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)
)[1]
)
]">
<xsl:sort select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<p></p>
<xsl:value-of select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<br />
<xsl:for-each select=
"key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)">
<xsl:value-of select="#Title" />
<br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<dsQueryResponse>
<Rows>
<Row Title="Agenda" />
<Row Title="Policy" />
<Row Title="policy" />
<Row Title="Report" />
<Row Title="report" />
<Row Title="Test2" />
<Row Title="test1" />
<Row Title="Boo" />
<Row Title="foo" />
</Rows>
</dsQueryResponse>
it produces the desired result:
<html>
<p/>A
<br/>Agenda
<br/>
<p/>B
<br/>Boo
<br/>
<p/>F
<br/>foo
<br/>
<p/>P
<br/>Policy
<br/>policy
<br/>
<p/>R
<br/>Report
<br/>report
<br/>
<p/>T
<br/>Test2
<br/>test1
<br/>
</html>
In XPath 2.0 one will use the upper-case() function to convert lower case to upper case.
Also, grouping in XSLT 2.0 can be better expressed using the <xsl:for-each-group>
instruction.