group adjacent with constraints? - grouping

I'm having an issue while using group-adjacent. Below a simplified XML snippet :
<Paras>
<Para tag="Bind">
<Content>some standalone Bind data</Content>
</Para>
<Para tag="L3">
<Content>some header data</Content>
</Para>
<Para tag="BStep.n=1">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="BStep.n+">
<Content>some data</Content>
</Para>
<Para tag="BStep.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="L1">
<Content>some header</Content>
</Para>
<Para tag="BBox.n=1">
<Content>some data</Content>
</Para>
<Para tag="BBox.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="BBox.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="L2">
<Content>some header</Content>
</Para>
</Paras>
What I like to get after final transformation is something as below :
<Paras>
<Para tag="Bind">
<Content>some standalone Bind data</Content>
</Para>
<Para tag="L3">
<Content>some header data</Content>
</Para>
<StepGroup>
<Steps>
<Para tag="BStep.n=1">some data</Para>
<Para tag="Bind">some data</Para>
</Steps>
<Steps>
<Para tag="BStep.n+">some data</Para>
</Steps>
<Steps>
<Para tag="BStep.n+">some data</Para>
<Para tag="Bind">some data</Para>
<Para tag="Bind">some data</Para>
</Steps>
</StepGroup>
<Para tag="L1">
<Content>some header</Content>
</Para>
<BoxGroup>
<Steps>
<Para tag="BBox.n=1">some data</Para>
<Para tag="BBox.n+">some data</Para>
<Para tag="Bind">some data</Para>
</Steps>
<Steps>
<Para tag="BBox.n+">some data</Para>
<Para tag="Bind">some data</Para>
</Steps>
</BoxGroup>
<Para tag="L2">
<Content>some header</Content>
</Para>
</Paras>
Or, to make it a bit textual : All 'bstep' type of tags and 'bind' tags that are adjacent to each other should be grouped in a StepGroup Element, and also all 'bblock' type of tags that are adjacent, including Bind tags, should be grouped in a 'BoxGroup' element.
I used following xslt (only partly shown) :
<!-- Some data above this left out ... -->
<xsl:for-each-group select="current-group()" group-adjacent="#tag='BStep.boxnmb.n=1' or #tag='BStep.boxnmb.n+' or #tag='Bind' or #tag='BStep.nobox' ">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<StepGroup>
<!-- do some stuff with group / not included now -->
<xsl:apply-templates select="current-group()"/>
</StepGroup>
</xsl:when>
<xsl:otherwise>
<xsl:for-each-group select="current-group()" group-adjacent="#tag='BBox.n=1' or #tag='BBox.n+' or #tag='Bind'">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<BoxGroup>
<xsl:apply-templates select="current-group()"/>
</BoxGroup>
</xsl:when>
This works partly, but as I have 'Bind' tags in both types of adjacent groups I need to be able to modify the group-adjacent keys so that for the 'StepGroup' only 'Binds' are included where the element has a 'Step type' tag, and for the 'BoxGroup' only 'Binds' where the previous element has a 'Box type' tag. I've tried some things but all resulting in nice error messages, so I hope someone can point me in the right direction here.

I don't fully understand your requirements, here is some partial solution
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Paras">
<xsl:copy>
<xsl:for-each-group select="Para" group-adjacent="matches(#tag, 'BStep|Bind')">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<StepGroup>
<xsl:for-each-group select="current-group()" group-starting-with="Para[matches(#tag, 'BStep')]">
<Step>
<xsl:apply-templates select="current-group()"/>
</Step>
</xsl:for-each-group>
</StepGroup>
</xsl:when>
<xsl:otherwise>
<xsl:for-each-group select="current-group()" group-adjacent="matches(#tag, 'BBox|Bind')">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<BoxGroup>
<xsl:apply-templates select="current-group()"/>
</BoxGroup>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
[edit]
Here is an adaption of the first stylesheet that should do the first level of grouping as you asked for:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Paras">
<xsl:copy>
<xsl:for-each-group select="Para"
group-adjacent="matches(#tag, 'BStep|Bind')
and (self::Para[matches(#tag, 'BStep')]
or preceding-sibling::*[not(matches(#tag, 'Bind'))][1][self::Para[matches(#tag, 'BStep')]])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<StepGroup>
<xsl:for-each-group select="current-group()" group-starting-with="Para[matches(#tag, 'BStep')]">
<Step>
<xsl:apply-templates select="current-group()"/>
</Step>
</xsl:for-each-group>
</StepGroup>
</xsl:when>
<xsl:otherwise>
<xsl:for-each-group select="current-group()" group-adjacent="matches(#tag, 'BBox|Bind')">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<BoxGroup>
<xsl:apply-templates select="current-group()"/>
</BoxGroup>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When I apply that with Saxon 9 to your input document I get
<Paras>
<StepGroup>
<Step>
<Para tag="BStep.n=1">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
</Step>
<Step>
<Para tag="BStep.n+">
<Content>some data</Content>
</Para>
</Step>
<Step>
<Para tag="BStep.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
</Step>
</StepGroup>
<Para tag="L1">
<Content>some header</Content>
</Para>
<BoxGroup>
<Para tag="BBox.n=1">
<Content>some data</Content>
</Para>
<Para tag="BBox.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
<Para tag="BBox.n+">
<Content>some data</Content>
</Para>
<Para tag="Bind">
<Content>some data</Content>
</Para>
</BoxGroup>
<Para tag="L2">
<Content>some header</Content>
</Para>
</Paras>
I realize this is not yet a final solution but I have so far not understood what defines the grouping inside of a BoxGroup. Maybe you can explain that in more detail or you can fix that part yourself.

Related

Need to split the parent and child element into two seperate element

Hi I'm having the below input xml file:
<Description>Same Date <Text>True</Text></Description>
XSL I have tried for
<xsl:template match="Description">
<def>
<para>
<title>
<xsl:value-of select="Description"/>
</title>
<para>
<xsl:value-of select="Text"/>
</para>
</para>
</def>
</xsl:template>
Expected Output:
<def>
<para>
<title>Same Date</title>
<para>True</para>
</para>
</def>
I need to split the child element and change into seperate element.
You can try This:
<?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:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="Description">
<def>
<para>
<title>
<xsl:value-of select="normalize-space(node()[1])"/>
</title>
<xsl:if test="Text">
<para>
<xsl:value-of select="Text"/>
</para>
</xsl:if>
</para>
</def>
</xsl:template>
</xsl:stylesheet>
Change Following Code:-
<title><xsl:value-of select="Description"/></title>
to
<title><xsl:value-of select="normalize-space(substring-before(., Text))"/></title>

Basic xslt transformation

I have one XML request which I need to modify (to XML) and then send it further. I have no prior knowledge of XSLT.
Say I have
<Combined>
<Profile>
<Fullname>John Doe</Fullname>
<OtherData>
<Birthdate>1996</Birthdate>
<FavoriteBooks>
<Book>
<id>1</id>
<description>Libre1</description>
</Book>
<Book>
<id>2</id>
<description>Libre2</description>
</Book>
<Book>
<id>3</id>
<description></description>
</Book>
<Book>
<id>4</id>
<description>Libre4</description>
</Book>
</FavoriteBooks>
</OtherData>
</Profile>
<LoadedData>
<NewBirthdate>1998</NewBirthdate>
<BooksUpdate>
<Book id="1">
<BookText>Book1</BookText>
</Book>
<Book id="2">
<BookText>Book2</BookText>
</Book>
<Book id="3">
<BookText>Book3</BookText>
</Book>
<Book id="4">
<BookText>Book4</BookText>
</Book>
<Book id="5">
<BookText>Book5</BookText>
</Book>
</BooksUpdate>
</LoadedData>
And want to get
<Profile>
<Fullname>John Doe</Fullname>
<OtherData>
<Birthdate>1998</Birthdate>
<FavoriteBooks>
<Book>
<id>1</id>
<description>Libre1Book1</description>
</Book>
<Book>
<id>2</id>
<description>Libre2Book2</description>
</Book>
<Book>
<id>3</id>
<description>empty</description>
</Book>
<Book>
<id>4</id>
<description>Libre4Book4</description>
</Book>
<Book>
<id>5</id>
<description>new Book5</description>
</Book>
</FavoriteBooks>
</OtherData>
I did a pretty pathetic attempt, which obviously does not work.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Profile>
<xsl:apply-templates select="Combined/Profile/Fullname" />
</Profile>
<Otherdata>
<Birthdate>
<xsl:apply-templates select="Combined/LoadedData/NewBirthdate"/>
</Birthdate>
<FavoriteBooks>
<xsl:for-each select="/Combined/Profile/OtherData/FavoriteBooks/Book">
<Book>
<id>
<xsl:value-of select="id"/>
</id>
<description>
<xsl:value-of select="description"/>
<xsl:apply-templates select="/Combined/LoadedData/BooksUpdate/Book[#id='']" />
</description>
</Book>
</xsl:for-each>
</FavoriteBooks>
</Otherdata>
</xsl:template>
</xsl:stylesheet>
How can I get closer to what I want to get? Could you also advise me some book to jump start, because w3schools tutorials are useless :(
How's this?
According to the logic you described, the description would not be "empty" but rather "Book3" (empty string merged with "Book3").
<!-- root and static content -->
<xsl:template match="/">
<xsl:apply-templates select='Combined/Profile' />
</xsl:template>
<!-- identity/copy, with some tweaks -->
<xsl:template match='node()|#*'>
<!-- copy node -->
<xsl:copy>
<!-- add in its attributes -->
<xsl:apply-templates select='#*' />
<!-- now either apply same treatment to child nodes, or something special -->
<xsl:choose>
<!-- use updated birthdate -->
<xsl:when test='name() = "Birthdate"'>
<xsl:value-of select='/Combined/LoadedData/NewBirthdate' />
</xsl:when>
<!-- merge book descriptions -->
<xsl:when test='name() = "description"'>
<xsl:value-of select='concat(., /Combined/LoadedData/BooksUpdate/Book[#id = current()/../id]/BookText)' />
</xsl:when>
<!-- or just keep recursing -->
<xsl:otherwise>
<xsl:apply-templates select='node()' />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
<!-- if we've done all books, add in any in the loaded data but not the original data -->
<xsl:if test='name() = "Book" and not(count(following-sibling::Book))'>
<xsl:variable name='orig_book_ids'>
<xsl:for-each select='../Book'>
<xsl:value-of select='concat("-",id,"-")' />
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select='/Combined/LoadedData/BooksUpdate/Book[not(contains($orig_book_ids, concat("-",#id,"-")))]' mode='new_books' />
</xsl:if>
</xsl:template>
<!-- new books -->
<xsl:template match='Book' mode='new_books'>
<Book>
<id><xsl:value-of select='#id' /></id>
<description>new <xsl:value-of select='BookText' /></description>
</Book>
</xsl:template>
You can run it at this XMLPlayground session (see output source).

Output values in a certain way using XSLT/XPath 2.0

I have an XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
I like the have a message (later on convert them tags) output like this:
A
AAA
Honda
A
BBB
Honda
C
CCC
Toyota
This is my XSLT:
<?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:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="Section//Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:for-each select="Cell[#colname='2']//MyValue">
<xsl:message>
<xsl:value-of select="Cell[#colname='1']/Value"/>
<xsl:value-of select="."/>
<xsl:value-of select="Cell[#colname='3']/MyCar"/>
</xsl:message>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Unfortunately it doesn't output what I'd like it to do :(.
I realize that for-each will change the context so the remaining value-ofs won't do anything.
What would be a solution for this ?.
TIA,
John
This XSLT 2.0 transformation (the equivalent XSLT 1.0 transformation can be mechanically written from this one).
Do note: This is a generic solution that works with any number of children and doesn't rely on hardcoded names.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell[1]"/>
</xsl:template>
<xsl:template match="Cell">
<xsl:param name="pText" as="xs:string*"/>
<xsl:apply-templates select="*[1]">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Cell/*">
<xsl:param name="pText" as="xs:string*"/>
<xsl:variable name="vText" as="xs:string*"
select="$pText, string(.)"/>
<xsl:sequence select=
"$vText
[not(current()/../following-sibling::Cell)]"/>
<xsl:apply-templates select="../following-sibling::Cell[1]">
<xsl:with-param name="pText" select="$vText"/>
</xsl:apply-templates>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
produces exactly the wanted, correct result:
A AAA Honda A BBB Honda C CCC Toyota
Explanation:
This is essentially a task for producing all combinations of values that belong to N groups (the children of a Cell make a group), taking one item from each group.
At any item of group K, we add this item to the current combination of items of the groups 1, 2, ..., K-1. Then we pass this newly formed combination to the group K+1. If we are an item in the last group (N), we print out (xsl:sequence) the whole accumulated combination.
When the control returns, all combinations of elements of the remaining groups (K+1, K+2, ..., N) have been appended to our current combination of the group items of groups 1, 2, ..., K. All these combinations have already been printed.
We pass the same group elements combination that was passed to us -- now we pass it to the following item in the current group (the following-sibling).
I have modified your approach a little, because I'm guessing that you really don't want to use <xsl:message> diagnostic operation. Besides that, I'm not generating XML on the output and I'm not using for-each:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/Section">
<xsl:apply-templates select="Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell/MyValue" />
</xsl:template>
<xsl:template match="MyValue">
<xsl:value-of select="../../Cell[#colname='1']/Value/text()"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../Cell[#colname='3']/MyCar"/>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>

Flat to hierarchical XML transformation

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>

Comparing 2 node sets based on attribute sequence

I'm trying to build up a kind of library XML, comparing various nodes and combining them for later reuse. The logic should be fairly straightforward, if the tag_XX attribute value sequence of a given language is equal to the tag_YY attribute value sequence of another language, the nodes can be combined. See below XML example
<Book>
<Section>
<GB>
<Para tag_GB="L1">
<Content_GB>string_1</Content_GB>
</Para>
<Para tag_GB="Illanc">
<Content_GB>string_2</Content_GB>
</Para>
<Para tag_GB="|PLB">
<Content_GB>string_3</Content_GB>
</Para>
<Para tag_GB="L1">
<Content_GB>string_4</Content_GB>
</Para>
<Para tag_GB="Sub">
<Content_GB>string_5</Content_GB>
</Para>
<Para tag_GB="L3">
<Content_GB>string_6</Content_GB>
</Para>
<Para tag_GB="Subbull">
<Content_GB>string_7</Content_GB>
</Para>
</GB>
<!-- German translations - OK because same attribute sequence -->
<DE>
<Para tag_DE="L1">
<Content_DE>German_translation of_string_1</Content_DE>
</Para>
<Para tag_DE="Illanc">
<Content_DE>German_translation of_string_2</Content_DE>
</Para>
<Para tag_DE="|PLB">
<Content_DE>German_translation of_string_3</Content_DE>
</Para>
<Para tag_DE="L1">
<Content_DE>German_translation of_string_4</Content_DE>
</Para>
<Para tag_DE="Sub">
<Content_DE>German_translation of_string_5</Content_DE>
</Para>
<Para tag_DE="L3">
<Content_DE>German_translation of_string_6</Content_DE>
</Para>
<Para tag_DE="Subbull">
<Content_DE>German_translation of_string_7</Content_DE>
</Para>
</DE>
<!-- Danish translations - NG because not same attribute sequence -->
<DK>
<Para tag_DK="L1">
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag_DK="L1_sub">
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag_DK="Illanc">
<Content_DK>Danish_translation_of_string_2</Content_DK>
</Para>
<Para tag_DK="L1">
<Content_DK>Danish_translation_of_string_4</Content_DK>
</Para>
<Para tag_DK="|PLB">
<Content_DK>Danish_translation_of_string_3</Content_DK>
</Para>
<Para tag_DK="L3">
<Content_DK>Danish_translation_of_string_6</Content_DK>
</Para>
<Para tag_DK="Sub">
<Content_DK>Danish_translation_of_string_5</Content_DK>
</Para>
<Para tag_DK="Subbull">
<Content_DK>Danish_translation_of_string_7</Content_DK>
</Para>
</DK>
</Section>
</Book>
So
GB tag_GB value sequence = L1 -> Illanc -> ... -> SubBul
DE tag_DE value sequence = L1 -> Illanc -> ... -> SubBul (same as GB so ok)
DK tag_DK value sequence = L1 -> L1.sub -> Oops, expected Illanc meaning this sequence is not the same as GB and locale can be ignored
Since German and English node sets have the same attribute sequence I like to combine them as follows :
<Book>
<Dictionary>
<Para tag="L1">
<Content_GB>string_1</Content_GB>
<Content_DE>German_translation of_string_1</Content_DE>
</Para>
<Para tag="Illanc">
<Content_GB>string_2</Content_GB>
<Content_DE>German_translation of_string_2</Content_DE>
</Para>
<Para tag="|PLB">
<Content_GB>string_3</Content_GB>
<Content_DE>German_translation of_string_3</Content_DE>
</Para>
<Para tag="L1">
<Content_GB>string_4</Content_GB>
<Content_DE>German_translation of_string_4</Content_DE>
</Para>
<Para tag="Sub">
<Content_GB>string_5</Content_GB>
<Content_DE>German_translation of_string_5</Content_DE>
</Para>
<Para tag="L3">
<Content_GB>string_6</Content_GB>
<Content_DE>German_translation of_string_6</Content_DE>
</Para>
<Para tag="Subbull">
<Content_GB>string_7</Content_GB>
<Content_DE>German_translation of_string_7</Content_DE>
</Para>
</Dictionary>
</Book>
The stylesheet I use is the following :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" xmlns="http://www.w3.org/1999/xhtml" encoding="UTF-8" indent="yes"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
<xsl:template match="Section">
<!-- store reference tag list -->
<xsl:variable name="Ref_tagList" select="GB/Para/attribute()[1]"/>
<Dictionary>
<xsl:for-each select="GB/Para">
<xsl:variable name="pos" select="position()"/>
<Para tag="{#tag_GB}">
<!-- Copy English Master -->
<xsl:apply-templates select="element()[1]"/>
<xsl:for-each select="//Book/Section/element()[not(self::GB)]">
<!-- store current locale tag list -->
<xsl:variable name="Curr_tagList" select="Para/attribute()[1]"/>
<xsl:if test="$Ref_tagList = $Curr_tagList">
<!-- Copy current locale is current tag list equals reference tag list -->
<xsl:apply-templates select="Para[position()=$pos]/element()[1]"/>
</xsl:if>
</xsl:for-each>
</Para>
</xsl:for-each>
</Dictionary>
</xsl:template>
</xsl:stylesheet>
Apart from probably not the most efficient way to do this (I'm fairly new to the xslt game...) it's not working either. The logic I had in mind is to take the attribute set of the English master, and if the attribute set of any other locale is equal I copy, if not I ignore. But for some reason also nodesets that have a different attribute sequence are happily copied (as seen in below). Can some one tell me where my logic conflicts with reality ? Thanks in advance !
Current output Including Danish that should have been ignored ...
<Book>
<Dictionary>
<Para tag="L1">
<Content_GB>string_1</Content_GB>
<Content_DE>German_translation of_string_1</Content_DE>
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag="Illanc">
<Content_GB>string_2</Content_GB>
<Content_DE>German_translation of_string_2</Content_DE>
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag="|PLB">
<Content_GB>string_3</Content_GB>
<Content_DE>German_translation of_string_3</Content_DE>
<Content_DK>Danish_translation_of_string_2</Content_DK>
</Para>
<Para tag="L1">
<Content_GB>string_4</Content_GB>
<Content_DE>German_translation of_string_4</Content_DE>
<Content_DK>Danish_translation_of_string_4</Content_DK>
</Para>
<Para tag="Sub">
<Content_GB>string_5</Content_GB>
<Content_DE>German_translation of_string_5</Content_DE>
<Content_DK>Danish_translation_of_string_3</Content_DK>
</Para>
<Para tag="L3">
<Content_GB>string_6</Content_GB>
<Content_DE>German_translation of_string_6</Content_DE>
<Content_DK>Danish_translation_of_string_6</Content_DK>
</Para>
<Para tag="Subbull">
<Content_GB>string_7</Content_GB>
<Content_DE>German_translation of_string_7</Content_DE>
<Content_DK>Danish_translation_of_string_5</Content_DK>
</Para>
</Dictionary>
</Book>
This is might not be the best solution. I've used the following XSLT 2.0 features:
I compared the sequence of attributes using string-join().
I've exploited the possibility of using RTF variables
There are probably more XSLT 2.0 facilities which can resolve your problem. but I think the BIG problem here is your input document.
I'm sorry did not have a look to your current transform. Just implemented one from scratch. Hope it helps:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="GB">
<Book>
<Dictionary>
<xsl:variable name="matches">
<xsl:for-each select="following-sibling::*
[string-join(Para/#*,'-')
= string-join(current()/Para/#*,'-')]">
<match><xsl:copy-of select="Para/*"/></match>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="Para">
<xsl:with-param name="matches" select="$matches"/>
</xsl:apply-templates>
</Dictionary>
</Book>
</xsl:template>
<xsl:template match="Para[parent::GB]">
<xsl:param name="matches"/>
<xsl:variable name="pos" select="position()"/>
<Para tag="{#tag_GB}">
<xsl:copy-of select="Content_GB"/>
<xsl:copy-of select="$matches/match/*[position()=$pos]"/>
</Para>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When applied to the input document provided in the question, the following output is produced:
<Book>
<Dictionary>
<Para tag="L1">
<Content_GB>string_1</Content_GB>
<Content_DE>German_translation of_string_1</Content_DE>
</Para>
<Para tag="Illanc">
<Content_GB>string_2</Content_GB>
<Content_DE>German_translation of_string_2</Content_DE>
</Para>
<Para tag="|PLB">
<Content_GB>string_3</Content_GB>
<Content_DE>German_translation of_string_3</Content_DE>
</Para>
<Para tag="L1">
<Content_GB>string_4</Content_GB>
<Content_DE>German_translation of_string_4</Content_DE>
</Para>
<Para tag="Sub">
<Content_GB>string_5</Content_GB>
<Content_DE>German_translation of_string_5</Content_DE>
</Para>
<Para tag="L3">
<Content_GB>string_6</Content_GB>
<Content_DE>German_translation of_string_6</Content_DE>
</Para>
<Para tag="Subbull">
<Content_GB>string_7</Content_GB>
<Content_DE>German_translation of_string_7</Content_DE>
</Para>
</Dictionary>
</Book>
This stylesheet makes use of <xsl:for-each-group>
First, groups the elements by their sequence of Para/#* values
Then, for each of those sequences, groups the Para using the number of following sibling elements that have attributes that start with "tag".
I have predicate filters on the matches for #*, to ensure that it is comparing the ones that start with "tag_". That may not be necessary, but would help ensure that it still worked if other attributes were added to the instance XML.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" xmlns="http://www.w3.org/1999/xhtml" encoding="UTF-8"
indent="yes"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" priority="1">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
<xsl:template match="Section">
<xsl:for-each-group select="*"
group-adjacent="string-join(
Para/#*[starts-with(local-name(),'tag_')],'|')">
<Dictionary>
<xsl:for-each-group select="current-group()/Para"
group-by="count(
following-sibling::*[#*[starts-with(local-name(),'tag_')]])">
<Para tag="{(current-group()/#*[starts-with(local-name(),'tag_')])[1]}">
<xsl:copy-of select="current-group()/*"/>
</Para>
</xsl:for-each-group>
</Dictionary>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When applied to the sample input XML, produces the following output:
<Book>
<Dictionary>
<Para tag="L1">
<Content_GB>string_1</Content_GB>
<Content_DE>German_translation of_string_1</Content_DE>
</Para>
<Para tag="Illanc">
<Content_GB>string_2</Content_GB>
<Content_DE>German_translation of_string_2</Content_DE>
</Para>
<Para tag="|PLB">
<Content_GB>string_3</Content_GB>
<Content_DE>German_translation of_string_3</Content_DE>
</Para>
<Para tag="L1">
<Content_GB>string_4</Content_GB>
<Content_DE>German_translation of_string_4</Content_DE>
</Para>
<Para tag="Sub">
<Content_GB>string_5</Content_GB>
<Content_DE>German_translation of_string_5</Content_DE>
</Para>
<Para tag="L3">
<Content_GB>string_6</Content_GB>
<Content_DE>German_translation of_string_6</Content_DE>
</Para>
<Para tag="Subbull">
<Content_GB>string_7</Content_GB>
<Content_DE>German_translation of_string_7</Content_DE>
</Para>
</Dictionary>
<Dictionary>
<Para tag="L1">
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag="L1_sub">
<Content_DK>Partial_Danish_translation_of_string_1</Content_DK>
</Para>
<Para tag="Illanc">
<Content_DK>Danish_translation_of_string_2</Content_DK>
</Para>
<Para tag="L1">
<Content_DK>Danish_translation_of_string_4</Content_DK>
</Para>
<Para tag="|PLB">
<Content_DK>Danish_translation_of_string_3</Content_DK>
</Para>
<Para tag="L3">
<Content_DK>Danish_translation_of_string_6</Content_DK>
</Para>
<Para tag="Sub">
<Content_DK>Danish_translation_of_string_5</Content_DK>
</Para>
<Para tag="Subbull">
<Content_DK>Danish_translation_of_string_7</Content_DK>
</Para>
</Dictionary>
</Book>