Here is the source XML:
<CellData>
<Cell CellOrdinal="0">
<Value>actual</Value>
<FmtValue>actual</FmtValue>
</Cell>
<Cell CellOrdinal="1">
<Value>630961942</Value>
<FmtValue>630961942</FmtValue>
</Cell>
<Cell CellOrdinal="2">
<Value>2.045711422E7</Value>
<FmtValue>20457114.2200</FmtValue>
</Cell>
<Cell CellOrdinal="3">
<Value>9.997105219639378E1</Value>
<FmtValue>99.9711</FmtValue>
</Cell>
<Cell CellOrdinal="4">
<Value>3.33E1</Value>
<FmtValue>33.0000</FmtValue>
</Cell>
<Cell CellOrdinal="5">
<Value>2.046303782E7</Value>
<FmtValue>20463037.8200</FmtValue>
</Cell>
<Cell CellOrdinal="6">
<Value>deposit</Value>
<FmtValue>deposit</FmtValue>
</Cell>
<Cell CellOrdinal="7">
<Value>144783359</Value>
<FmtValue>144783359</FmtValue>
</Cell>
<Cell CellOrdinal="8">
<Value>2.1388E2</Value>
<FmtValue>213.8800</FmtValue>
</Cell>
<Cell CellOrdinal="9">
<Value>1.0452016063370595E-3</Value>
<FmtValue>0.0010</FmtValue>
</Cell>
<Cell CellOrdinal="10">
<Value>6.67E1</Value>
<FmtValue>67.0000</FmtValue>
</Cell>
<Cell CellOrdinal="11">
<Value>2.046303782E7</Value>
<FmtValue>20463037.8200</FmtValue>
</Cell>
<Cell CellOrdinal="12">
<Value>deposit</Value>
<FmtValue>deposit</FmtValue>
</Cell>
<Cell CellOrdinal="13">
<Value>304011203</Value>
<FmtValue>304011203</FmtValue>
</Cell>
<Cell CellOrdinal="14">
<Value>5.70972E3</Value>
<FmtValue>5709.7200</FmtValue>
</Cell>
<Cell CellOrdinal="15">
<Value>2.7902601999882342E-2</Value>
<FmtValue>0.0279</FmtValue>
</Cell>
<Cell CellOrdinal="16">
<Value>6.67E1</Value>
<FmtValue>67.0000</FmtValue>
</Cell>
<Cell CellOrdinal="17">
<Value>2.046303782E7</Value>
<FmtValue>20463037.8200</FmtValue>
</Cell>
</CellData>
This list contains 6-column table data.
Data is ordered by 1-st column and contain 'type', which will come in order: actual, accumulation, deposit but some can be absent at all (accumulation in example).
i.e. it's actually contain this data:
contract_type contract_id sum percentage contract_type_percentage balance_total
actual 630961942 20457114.2200 99.9711 33.0000 20463037.8200
deposit 144783359 213.8800 0.0010 67.0000 20463037.8200
deposit 304011203 5709.7200 0.0279 67.0000 20463037.8200
Here is desired XML output (based on example):
<body>
<actual_accounts>
<actual_account>
<contract_id>630961942</contract_id>
<sum>20457114.2200</sum>
<percentage>99.9711</percentage>
</actual_account>
<actual_percentage>33.0000</actual_percentage>
</actual_accounts>
<accumulation_accounts>
<accumulation_percentage>0</accumulation_percentage>
</accumulation_accounts>
<deposits>
<deposit>
<contract_id>144783359</contract_id>
<sum>213.8800</sum>
<percentage>0.0010</percentage>
</deposit>
<deposit>
<contract_id>304011203</contract_id>
<sum>5709.7200</sum>
<percentage>0.0279</percentage>
</deposit>
<deposit_percentage>67.0000</deposit_percentage>
</deposits>
<balance_total>20463037.8200</balance_total>
</body>
Where *_percentage tags should contain value of 5th column from any row from associated * set.
Here is that I got so far:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" method="xml" version="1.0"/>
<xsl:template match="/">
<body>
<actual_accounts>
<xsl:apply-templates select="//Cell">
<xsl:with-param name="cellType" select="'actual_accounts'"/>
</xsl:apply-templates>
<actual_percentage>0</actual_percentage>
</actual_accounts>
<accumulation_accounts>
<xsl:apply-templates select="//Cell">
<xsl:with-param name="cellType" select="'accumulation_accounts'"/>
</xsl:apply-templates>
<accumulation_percentage>0</accumulation_percentage>
</accumulation_accounts>
<deposits>
<xsl:apply-templates select="//Cell">
<xsl:with-param name="cellType" select="'deposits'"/>
</xsl:apply-templates>
<deposit_percentage>0</deposit_percentage>
</deposits>
<xsl:choose>
<xsl:when test="count(CellData/Cell) >= 6">
<balance_total>
<xsl:value-of select="CellData/Cell[6]/FmtValue"/>
</balance_total>
</xsl:when>
<xsl:otherwise>
<balance_total>0</balance_total>
</xsl:otherwise>
</xsl:choose>
</body>
</xsl:template>
<xsl:template match="//Cell">
<xsl:param name="cellType"/>
<xsl:if test="#CellOrdinal mod 6 = 0">
<xsl:variable name="contract_type" select="FmtValue"/>
<xsl:variable name="contract_id" select="#CellOrdinal + 2"/>
<xsl:variable name="sum" select="#CellOrdinal + 3"/>
<xsl:variable name="percentage" select="#CellOrdinal + 4"/>
<xsl:variable name="type_percentage" select="#CellOrdinal + 5"/>
<xsl:variable name="balance_total" select="#CellOrdinal + 6"/>
<xsl:if test="$contract_type = 'actual' and $cellType = ('actual_accounts')">
<actual_account>
<contract_id>
<xsl:value-of select="parent::CellData/Cell[$contract_id]/FmtValue"/>
</contract_id>
<sum>
<xsl:value-of select="parent::CellData/Cell[$sum]/FmtValue"/>
</sum>
<percentage>
<xsl:value-of select="parent::CellData/Cell[$percentage]/FmtValue"/>
</percentage>
</actual_account>
</xsl:if>
<xsl:if test="$contract_type = 'accumulation' and $cellType = ('accumulation_accounts')">
<accumulation_account>
<contract_id>
<xsl:value-of select="parent::CellData/Cell[$contract_id]/FmtValue"/>
</contract_id>
<sum>
<xsl:value-of select="parent::CellData/Cell[$sum]/FmtValue"/>
</sum>
<percentage>
<xsl:value-of select="parent::CellData/Cell[$percentage]/FmtValue"/>
</percentage>
</accumulation_account>
</xsl:if>
<xsl:if test="$contract_type = 'deposit' and $cellType = 'deposits'">
<deposit>
<contract_id>
<xsl:value-of select="parent::CellData/Cell[$contract_id]/FmtValue"/>
</contract_id>
<sum>
<xsl:value-of select="parent::CellData/Cell[$sum]/FmtValue"/>
</sum>
<percentage>
<xsl:value-of select="parent::CellData/Cell[$percentage]/FmtValue"/>
</percentage>
</deposit>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
which do everything except whose *_percentage tags..
I'm limited to XSLT 1.0.
UPDATE WITH FINAL ANSWER: with small fixes to Maesto13 solution, and only works on MSXML4.0+, .NET1.0+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxml="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxml">
<xsl:output indent="yes" omit-xml-declaration="yes" method="xml" version="1.0"/>
<xsl:key name="CellGroup" use="#CellOrdinal - (#CellOrdinal mod 6)" match="CellData/Cell"/>
<xsl:template match="CellData">
<xsl:variable name="Cells">
<xsl:apply-templates select="Cell[generate-id() = generate-id(key('CellGroup', #CellOrdinal - (#CellOrdinal mod 6))[1])]" mode="group"/>
</xsl:variable>
<body>
<actual_accounts>
<xsl:for-each select="msxml:node-set($Cells)/Cell[contract-type='actual']">
<actual_account>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</actual_account>
</xsl:for-each>
<xsl:variable name="type_percentage" select="msxml:node-set($Cells)/Cell[contract-type='actual'][1]/contract-type-percentage[1]"></xsl:variable>
<xsl:choose>
<xsl:when test="boolean($type_percentage)">
<actual_percentage><xsl:value-of select="$type_percentage"/></actual_percentage>
</xsl:when>
<xsl:otherwise>
<actual_percentage>0</actual_percentage>
</xsl:otherwise>
</xsl:choose>
</actual_accounts>
<accumulation_accounts>
<xsl:for-each select="msxml:node-set($Cells)/Cell[contract-type='accumulation']">
<accumulation_account>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</accumulation_account>
</xsl:for-each>
<xsl:variable name="type_percentage" select="msxml:node-set($Cells)/Cell[contract-type='accumulation'][1]/contract-type-percentage[1]"></xsl:variable>
<xsl:choose>
<xsl:when test="boolean($type_percentage)">
<accumulation_percentage><xsl:value-of select="$type_percentage"/></accumulation_percentage>
</xsl:when>
<xsl:otherwise>
<accumulation_percentage>0</accumulation_percentage>
</xsl:otherwise>
</xsl:choose>
</accumulation_accounts>
<deposits>
<xsl:for-each select="msxml:node-set($Cells)/Cell[contract-type='deposit']">
<deposit>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</deposit>
</xsl:for-each>
<xsl:variable name="type_percentage" select="msxml:node-set($Cells)/Cell[contract-type='deposit'][1]/contract-type-percentage[1]"></xsl:variable>
<xsl:choose>
<xsl:when test="boolean($type_percentage)">
<deposit_percentage><xsl:value-of select="$type_percentage"/></deposit_percentage>
</xsl:when>
<xsl:otherwise>
<deposit_percentage>0</deposit_percentage>
</xsl:otherwise>
</xsl:choose>
</deposits>
<xsl:variable name="total" select="msxml:node-set($Cells)/Cell[1]/balance-total"></xsl:variable>
<xsl:choose>
<xsl:when test="boolean($total)">
<balance_total>
<xsl:value-of select="$total"/>
</balance_total>
</xsl:when>
<xsl:otherwise><balance_total>0</balance_total></xsl:otherwise>
</xsl:choose>
</body>
</xsl:template>
<xsl:template match="Cell" mode="group">
<Cell>
<xsl:variable name="Cells" select="key('CellGroup', #CellOrdinal - (#CellOrdinal mod 6))"/>
<contract-type><xsl:value-of select="$Cells[1]/FmtValue"/></contract-type>
<contract-id><xsl:value-of select="$Cells[2]/FmtValue"/></contract-id>
<sum><xsl:value-of select="$Cells[3]/FmtValue"/></sum>
<percentage><xsl:value-of select="$Cells[4]/FmtValue"/></percentage>
<contract-type-percentage><xsl:value-of select="$Cells[5]/FmtValue"/></contract-type-percentage>
<balance-total><xsl:value-of select="$Cells[6]/FmtValue"/></balance-total>
</Cell>
</xsl:template>
</xsl:stylesheet>
I would prefer a two step transformation, collecting the data into a variable first. When vast data size is involved, this may have to be reconsidered. Below is an xslt which does the trick.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" method="xml" version="1.0"/>
<xsl:key name="CellGroup" use="#CellOrdinal - (#CellOrdinal mod 6)" match="CellData/Cell"/>
<xsl:template match="CellData">
<xsl:variable name="Cells">
<xsl:apply-templates select="Cell[generate-id() = generate-id(key('CellGroup', #CellOrdinal - (#CellOrdinal mod 6))[1])]" mode="group"/>
</xsl:variable>
<body>
<actual_accounts>
<xsl:for-each select="$Cells/Cell[contract-type='actual']">
<actual_account>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</actual_account>
</xsl:for-each>
<actual_percentage><xsl:value-of select="$Cells/Cell[contract-type='actual'][1]/contract-type-percentage"/></actual_percentage>
</actual_accounts>
<accumulation_accounts>
<xsl:for-each select="$Cells/Cell[contract-type='accumulation']">
<accumulation_account>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</accumulation_account>
</xsl:for-each>
<accumulation_percentage><xsl:value-of select="$Cells/Cell[contract-type='accumulation'][1]/contract-type-percentage[1]"/></accumulation_percentage>
</accumulation_accounts>
<deposits>
<xsl:for-each select="$Cells/Cell[contract-type='deposit']">
<deposit>
<xsl:copy-of select="contract-type"/>
<xsl:copy-of select="sum"/>
<xsl:copy-of select="percentage"/>
</deposit>
</xsl:for-each>
<deposit_percentage><xsl:value-of select="$Cells/Cell[contract-type='deposit'][1]/contract-type-percentage[1]"/></deposit_percentage>
</deposits>
<balance_total><xsl:value-of select="$Cells/Cell[1]/balance-total"/></balance_total>
</body>
</xsl:template>
<xsl:template match="Cell" mode="group">
<Cell>
<xsl:variable name="Cells" select="key('CellGroup', #CellOrdinal - (#CellOrdinal mod 6))"/>
<contract-type><xsl:value-of select="$Cells[1]/FmtValue"/></contract-type>
<contract-id><xsl:value-of select="$Cells[2]/FmtValue"/></contract-id>
<sum><xsl:value-of select="$Cells[3]/FmtValue"/></sum>
<percentage><xsl:value-of select="$Cells[4]/FmtValue"/></percentage>
<contract-type-percentage><xsl:value-of select="$Cells[5]/FmtValue"/></contract-type-percentage>
<balance-total><xsl:value-of select="$Cells[6]/FmtValue"/></balance-total>
</Cell>
</xsl:template>
</xsl:stylesheet>
The output I get is as follows:
<body>
<actual_accounts>
<actual_account>
<contract-type>actual</contract-type>
<sum>20457114.2200</sum>
<percentage>99.9711</percentage>
</actual_account>
<actual_percentage>33.0000</actual_percentage>
</actual_accounts>
<accumulation_accounts>
<accumulation_percentage></accumulation_percentage>
</accumulation_accounts>
<deposits>
<deposit>
<contract-type>deposit</contract-type>
<sum>213.8800</sum>
<percentage>0.0010</percentage>
</deposit>
<deposit>
<contract-type>deposit</contract-type>
<sum>5709.7200</sum>
<percentage>0.0279</percentage>
</deposit>
<deposit_percentage>67.0000</deposit_percentage>
</deposits>
<balance_total>20463037.8200</balance_total>
</body>
Related
I am very new in xsl. I was trying to add the <quote> tag in between the <para>.tag. but the output printing twice.
Here is my xsl code
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" mode="pretrans">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[count(child::node())=0]" mode="pretrans"/>
<xsl:template match="doc">
<poc>
<xsl:apply-templates/>
</poc>
</xsl:template>
<xsl:template match="text">
<chapter>
<xsl:variable name="pos" select="count(child::node()[#style='H5']/preceding-sibling::p)+1"/>
<xsl:apply-templates select="child::node()[position()<$pos]" mode="presec"/>
<section>
<xsl:variable name="nodesets" >
<xsl:apply-templates select="child::node()[position()>=$pos]" mode="pretrans"/>
</xsl:variable>
<xsl:apply-templates select="$nodesets" mode="postsec"/> <!---->
</section>
</chapter>
</xsl:template>
<xsl:template match="p" mode="presec">
<xsl:choose>
<xsl:when test="#style='H2'">
<title><xsl:apply-templates/></title>
</xsl:when>
<xsl:when test="#style='H4'">
<subdivision>
<title><xsl:apply-templates/></title>
</subdivision>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="p" mode="postsec">
<xsl:variable name="pos" select="count(preceding-sibling::p[#style='H5'][1]/preceding-sibling::p)+1"/>
<xsl:variable name="pos" select="count(preceding-sibling::p)+1"/>
<xsl:variable name="styleblock" select="count(preceding-sibling::p[#style='BlockStyle'][1]/preceding-sibling::p)+1"/>
<xsl:choose>
<xsl:when test="#style='H5'">
<title><xsl:apply-templates/></title>
</xsl:when>
<xsl:when test="count(child::node())=0"/>
<xsl:otherwise>
<paragraph>
<xsl:if test="#style='BlockStyle'">
<quotes>
<xsl:apply-templates/>
</quotes>
</xsl:if>
<xsl:apply-templates/>
</paragraph>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
expected output:
<poc>
<chapter>
<section>
<paragraph>
<quote>
Hi welcome to new year 2022
</quote>
Hi welcome to new year 2022
</paragraph>
</section>
</chapter>
</poc>
The message is printing twice.
can anyone help me in this.
Depending on your needs delete the first or the second
<paragraph>
<xsl:if test="#style='BlockStyle'">
<quotes>
<xsl:apply-templates/><!-- First -->
</quotes>
</xsl:if>
<xsl:apply-templates/><!-- Second -->
</paragraph>
I have an issue while concatenating all the the child elements under parent element.
Here is the source data
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PurchaseOrder id="abc">
<attr attr-name="A">
<new-value>123</new-value>
</attr>
<attr attr-name="B">
<new-value>99</new-value>
</attr>
<attr attr-name="B">
<new-value>77</new-value>
</attr>
<attr attr-name="C">
<new-value>1</new-value>
<new-value>2</new-value>
<new-value>3</new-value>
<new-value>4</new-value>
<new-value>5</new-value>
<new-value>6</new-value>
<new-value>7</new-value>
<new-value>8</new-value>
</attr>
<attr attr-name="D">
<new-value>
<child1>567</child1>
<child2>2</child2>
</new-value>
</attr>
<attr attr-name="E">
<new-value>
<child3>890</child3>
<child4>3</child4>
</new-value>
</attr>
</PurchaseOrder>
XSLT Transformation used
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:if test="position()!=1">
<ns0:text>|</ns0:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
The expected Result should be
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
The output that is coming with the XSLT I have used is as below. Separator logic is not working.
<?xml version="1.0" encoding="UTF-8"?>
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order>
<A>123</A>
<B>99</B>
<B>77</B>
<C>12345678</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
I have tried logic mentioned in some blogs but no luck
XSLT merging/concatenating values of siblings nodes of same name into single node
Concatenate several child items into one child item using XSLT
You need to modify the <xsl:otherwise> condition to handle the values in <new-value> node as below. If the count of <new-value> child nodes is > 1, then perform the concatenation using the separator else just output the value as is.
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
The complete XSLT and the output is as below.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="urn:demo:PurchaseOrder">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="keyAttrName" match="attr" use="#attr-name" />
<xsl:template match="PurchaseOrder">
<ns0:PurchaseOrderMSG>
<Orders>
<Order id="{#id}">
<xsl:for-each select="attr[generate-id() = generate-id(key('keyAttrName', #attr-name)[1])]">
<xsl:variable name="nodeName" select="#attr-name" />
<xsl:choose>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/*/node()">
<xsl:for-each select="new-value">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="*" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:when test="key('keyAttrName', #attr-name)/new-value/materials/material">
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:copy-of select="attr" />
</xsl:element>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="key('keyAttrName', #attr-name)">
<xsl:element name="{$nodeName}">
<xsl:choose>
<xsl:when test="count(new-value) > 1">
<xsl:for-each select="new-value">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="'|'" />
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
</xsl:template>
</xsl:stylesheet>
Output
<ns0:PurchaseOrderMSG xmlns:ns0="urn:demo:PurchaseOrder">
<Orders>
<Order id="abc">
<A>123</A>
<B>99</B>
<B>77</B>
<C>1|2|3|4|5|6|7|8</C>
<D>
<child1>567</child1>
<child2>2</child2>
</D>
<E>
<child3>890</child3>
<child4>3</child4>
</E>
</Order>
</Orders>
</ns0:PurchaseOrderMSG>
The transform I'm working on mergers two templates that has attributes that are space-separated.
An example would be:
<document template_id="1">
<header class="class1 class2" />
</document>
<document template_id="2">
<header class="class3 class4" />
</document>
And after the transform I want it to be like this:
<document>
<header class="class1 class2 class3 class4" />
</document>
How to achieve this?
I have tried (writing from memory):
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
But that appends them all together, but I need them separated... and would be awesome if uniqued as well.
Thank you
Try this template, which simply adds a space before all the headers, apart from the first
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:if test="position() > 1">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
If you want your classes without repetitions, then use the following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<document>
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls" select="exsl:node-set($cls1)"/>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls/*[not(preceding-sibling::* = .)]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>
In XSLT 1.0 it is much more difficult than in XSLT 2.0, but as you see,
it is possible.
Edit
A revised version using Muenchian grouping (inspired by comment from Michael):
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls2" select="exsl:node-set($cls1)"/>
<xsl:key name="classKey" match="cls" use="."/>
<xsl:template match="/">
<document>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls2/*[generate-id() = generate-id(key('classKey', .)[1])]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>
Input XML:
<root>
<number>4</number>
<format>start1</format>
<!--this could be start0/start1/alpha -->
</root>
My output should be:
If format=start1 Print 1,2,3,4
If format=start0 Print 0,1,2,3
If format=alpha Print A,B,C,D
number of sequential items is equal to value of "number" node
XSLT stub:
<xsl:template match="/">
<xsl:variable name="mynumber" select="number"></xsl:variable>
<xsl:variable name="mysequence">
<xsl:choose>
<xsl:when test="format='start0'">
<xsl:for-each select="$mynumber">
<!--0,1,2,3-->
</xsl:for-each>
</xsl:when>
<xsl:when test="format='start1'">
<xsl:for-each select="$mynumber">
<!--1,2,3,4-->
</xsl:for-each>
</xsl:when>
<xsl:when test="format='alpha'">
<xsl:for-each select="$mynumber">
<!--A, B, C, D-->
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$mysequence"></xsl:value-of>
</xsl:template>
Consider the following example:
XML
<root>
<item>
<number>4</number>
<format>start0</format>
</item>
<item>
<number>4</number>
<format>start1</format>
</item>
<item>
<number>4</number>
<format>alpha</format>
</item>
</root>
XSLT 2.0
<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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<sequence>
<xsl:choose>
<xsl:when test="format='start0'">
<xsl:value-of select="for $i in 1 to number return $i - 1" separator=", "/>
</xsl:when>
<xsl:when test="format='start1'">
<xsl:value-of select="for $i in 1 to number return $i" separator=", "/>
</xsl:when>
<xsl:when test="format='alpha'">
<xsl:value-of select="for $i in 1 to number return codepoints-to-string($i + 64)" separator=", "/>
</xsl:when>
</xsl:choose>
</sequence>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<sequence>0, 1, 2, 3</sequence>
<sequence>1, 2, 3, 4</sequence>
<sequence>A, B, C, D</sequence>
</root>
Note that this assumes number will not exceed 26 (at least not when the format is "alpha"); otherwise you will need to use xsl:number to format it as alpha, as shown in the answer by #potame - except it could be more concise:
<xsl:template match="item">
<xsl:variable name="fmt" select="format" />
<sequence>
<xsl:for-each select="1 to number">
<xsl:number value="if ($fmt='start0') then . - 1 else ." format="{if ($fmt='alpha') then 'A' else '0'}"/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</sequence>
</xsl:template>
Here's a possible solution, thanks to the use of an XPath sequence, e.g. select="1 to 10":
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="html" doctype-public="XSLT-compat"
omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<xsl:variable name="mynumber" select="number" as="xs:integer" />
<xsl:variable name="mysequence">
<xsl:choose>
<xsl:when test="format='start0'">
<xsl:for-each select="0 to ($mynumber - 1)">
<!--0,1,2,3-->
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:when>
<xsl:when test="format='start1'">
<xsl:for-each select="1 to $mynumber">
<!--0,1,2,3-->
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:when>
<xsl:when test="format='alpha'">
<xsl:for-each select="1 to $mynumber">
<xsl:number value="." format="A"/>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$mysequence"/>
</xsl:template>
</xsl:transform>
I have a stylesheet designed to add some structure which currently looks like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://docbook.org/ns/docbook" xpath-default-namespace="http://docbook.org/ns/docbook">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="book">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:copy-of select="info"/>
<xsl:for-each-group select="*" group-starting-with="chapnumber">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'chapnumber'">
<xsl:variable name="chapter_number" select="replace(., '.*([0-9]).*', '$1')"/>
<chapter label="{$chapter_number}">
<xsl:call-template name="nest_head1">
<xsl:with-param name="working_group" select="current-group() except ."/>
</xsl:call-template>
</chapter>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_head1">
<xsl:with-param name="working_group" select="current-group() except ."/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template name="nest_head1">
<xsl:param name="working_group"/>
<xsl:for-each-group select="$working_group" group-starting-with="head1">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'head1'">
<section>
<xsl:call-template name="nest_head2">
<xsl:with-param name="working_group" select="current-group()"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_head2">
<xsl:with-param name="working_group" select="current-group()"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="nest_head2">
<xsl:param name="working_group"/>
<xsl:for-each-group select="$working_group" group-starting-with="head2">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'head2'">
<section>
<xsl:apply-templates select="current-group()"/>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="head1|head2|head3|head4|head5">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="chaptitle">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="italic">
<emphasis role="italic">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="bold">
<emphasis role="bold">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I dislike how each heading level has it's own template (especially as there may in theory be many more levels of headings) and thought it could be condensed to a single, more usable template with a variable for the heading level, like so:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://docbook.org/ns/docbook" xpath-default-namespace="http://docbook.org/ns/docbook">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="book">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:copy-of select="info"/>
<xsl:for-each-group select="*" group-starting-with="chapnumber">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'chapnumber'">
<xsl:variable name="chapter_number" select="replace(., '.*([0-9]).*', '$1')"/>
<chapter label="{$chapter_number}">
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group() except ."/>
<xsl:with-param name="heading_level" select="1"/>
</xsl:call-template>
</chapter>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group() except ."/>
<xsl:with-param name="heading_level" select="1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template name="nest_headings">
<xsl:param name="working_group"/>
<xsl:param name="heading_level"/>
<xsl:variable name="heading_name">
<xsl:value-of select="concat('head', string($heading_level))"/>
</xsl:variable>
<xsl:if test="$heading_level < 6">
<xsl:for-each-group select="$working_group" group-starting-with="$heading_name">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = $heading_name">
<section>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
<xsl:template match="head1|head2|head3|head4|head5">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="chaptitle">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="italic">
<emphasis role="italic">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="bold">
<emphasis role="bold">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But this stylesheet fails to compile with A variable reference is not allowed in an XSLT pattern (except in a predicate)
A mailing list I found suggests group-starting-with="*[name()=$heading_name]. Using this will create the section nodes, but they will be empty.
Is there a way to achieve what I'm looking for?
Sample input:
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" role="fullText" xml:lang="en">
<chapnumber>Chapter 1</chapnumber>
<chaptitle>Chapter Title</chaptitle>
<head1>Heading 1</head1>
<para>1st paragraph</para>
<head2>Heading 2</head2>
<para>2nd paragraph</para>
<head2>Heading 2 2</head2>
<para>Final paragraph</para>
</book>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" role="fullText" xml:lang="en">
<chapter label="1">
<info>
<title>Chapter Title</title>
</info>
<section>
<info>
<title>Heading 1</title>
</info>
<para>1st paragraph</para>
<section>
<info>
<title>Heading 2</title>
</info>
<para>2nd paragraph</para>
</section>
<section>
<info>
<title>Heading 2 2</title>
</info>
<para>Final paragraph</para>
</section>
</section>
</chapter>
</book>
You need to make sure you process the items once the grouping is done so use <xsl:apply-templates select="current-group()"/> in the xsl:otherwise:
<xsl:template name="nest_headings">
<xsl:param name="working_group"/>
<xsl:param name="heading_level"/>
<xsl:variable name="heading_name" select="concat('head', $heading_level)"/>
<xsl:if test="$heading_level < 6">
<xsl:for-each-group select="$working_group" group-starting-with="*[local-name() eq $heading_name]">
<xsl:choose>
<xsl:when test="local-name() eq $heading_name">
<section>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
The test="current-group()[1]/name() = $heading_name" could be shortened to test="local-name() eq $heading_name".
See http://xsltransform.net/pPqsHTm/1 for a full sample.