XSLT - select consecutive elements with the same attribute but ignore later same element/same attribute - xslt-1.0

I have this XML:
<doc>
<paragraph>AAA</paragraph>
<paragraph stylename="numbered" num="1">BBB</paragraph>
<paragraph stylename="numbered" num="2">CCC</paragraph>
<paragraph>DDD</paragraph>
<paragraph>EEE</paragraph>
<paragraph stylename="numbered" num="1">FFF</paragraph>
<paragraph stylename="numbered" num="2">GGG</paragraph>
<paragraph stylename="numbered" num="3">HHHh</paragraph>
<paragraph>III</paragraph>
</doc>
I need to count the total number of consecutive paragraph[#stylename='numbered'] and output:
<paragraph>AAA</paragraph>
<paragraph count="1" totalnums="2">BBBa</paragraph>
<paragraph count="2" totalnums="2">CCC</paragraph>
<paragraph>DDD</paragraph>
<paragraph>EEE</paragraph>
<paragraph count="1" totalnums="3">FFF</paragraph>
<paragraph count="2" totalnums="3">GGG</paragraph>
<paragraph count="3" totalnums="3">HHH</paragraph>
<paragraph>III</paragraph>
I can't figure out how to output the #count or the total number (#totalnums) of consecutive #stylename="numbered".
Currently, I get the total number of paragraph/#stylename="numbered" in the whole file.
Any pointers and/or code would be greatly appreciated. (XSLT-1.0 solution preferred, although 2.0 might be possible to implement)

This is easy to do in XSLT 2.0 using xsl:for-each-group with group-adjacent. To achieve the same in XSLT 1.0 is more difficult:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="paragraph[#stylename='numbered']" use="generate-id(preceding-sibling::paragraph[not(#stylename='numbered')][1])" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="paragraph[#stylename='numbered']">
<xsl:variable name="grp" select="key('k1', generate-id(preceding-sibling::paragraph[not(#stylename='numbered')][1]))" />
<!-- if this is the first one in the group -->
<xsl:if test="generate-id()=generate-id($grp[1])">
<xsl:for-each select="$grp">
<paragraph count="{position()}" totalnums="{last()}">
<xsl:value-of select="."/>
</paragraph>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to your example input, this will return:
Result
<doc>
<paragraph>AAA</paragraph>
<paragraph count="1" totalnums="2">BBB</paragraph>
<paragraph count="2" totalnums="2">CCC</paragraph>
<paragraph>DDD</paragraph>
<paragraph>EEE</paragraph>
<paragraph count="1" totalnums="3">FFF</paragraph>
<paragraph count="2" totalnums="3">GGG</paragraph>
<paragraph count="3" totalnums="3">HHHh</paragraph>
<paragraph>III</paragraph>
</doc>
This is different from the result shown in your question, but I believe it is correct.

Related

Advanced muenchian grouping: group by items in child collection

I'm familiar with simple muenchian grouping in XSL, but I've encountered a problem, which I honestly don't even know, how to approach it.
So I've got an XML:
<whiskies>
<whisky name="ABC" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="0" date=""/>
<bottle price="" size="" level="70" date=""/>
<bottle price="" size="" level="100" date=""/>
</bottles>
<comments/>
</whisky>
<whisky name="DEF" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="0" date=""/>
<bottle price="" size="" level="100" date=""/>
</bottles>
<comments/>
</whisky>
<whisky name="GHI" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="30" date=""/>
</bottles>
<comments/>
</whisky>
<whiskies>
And the goal is to group the whiskies by levels of a bottle:
So level="0" is considered empty.
Anything from level="1" to level="99" is considered open.
And level="100" is considered unopened.
So the transformed result (will be done in HTML) should look like this:
<h1>Empty</h1>
<ul>
<li>ABC</li>
<li>DEF</li>
</ul>
<h1>Open</h1>
<ul>
<li>ABC</li>
<li>GHI</li>
</ul>
<h1>Unopened</h1>
<ul>
<li>ABC</li>
<li>DEF</li>
</ul>
As you can see, the same whisky can show up in multiple groups, depending on how much bottles there are and how full those bottles are.
The second problem is, that the "Open" group doesn't have an exact value and can be aynthing from 1 to 99.
So yeah, don't really know if this can be solved at all or how to even start on this. Any tips appreciated.
I don't think you want to use Muenchian grouping for this. You would need to define a key that enumerates all values in the range from 1 to 99 - and I believe that would take away any advantage that using a key would otherwise bring.
Since your result has either exactly or at most 3 groups (your question is ambiguous in this respect), you could do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="whisky[bottles/bottle/#level=0]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="whisky[bottles/bottle[#level>0 and #level < 100]]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="whisky[bottles/bottle/#level=100]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
to get (after fixing the input to be a well-formed XML!):
Result
<?xml version="1.0" encoding="utf-8"?>
<output>
<group status="empty">
<name>ABC</name>
<name>DEF</name>
</group>
<group status="open">
<name>ABC</name>
<name>GHI</name>
</group>
<group status="unopened">
<name>ABC</name>
<name>DEF</name>
</group>
</output>
Make your own adjustment for HTML output.
Added:
Here is an alternative approach using keys (though still not Muenchian grouping) which might be more performant:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="w" match="whisky" use="bottles/bottle/#level" />
<xsl:key name="w1" match="whisky" use="boolean(bottles/bottle[#level!=0 and #level!=100])" />
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="key('w', 0)">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="key('w1', true())">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="key('w', 100)">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
Or even:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="bottle" match="bottle" use="ceiling(#level div 99)" />
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="key('bottle', 0)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="key('bottle', 1)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="key('bottle', 2)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
If you did want to use xsl:key, you could create 3 of them with the filter criteria:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:key name="Empty" match="whisky[bottles/bottle/#level=0]" use="#name"/>
<xsl:key name="Open" match="whisky[bottles/bottle/#level[. > 1 and . < 99]]" use="#name"/>
<xsl:key name="Unopened" match="whisky[bottles/bottle/#level=100]" use="#name"/>
<xsl:template match="/">
<xsl:variable name="whiskies" select="/whiskies/whisky/#name"/>
<xsl:for-each select="document('')/xsl:stylesheet/xsl:key/#name">
<xsl:variable name="status" select="."/>
<h1><xsl:value-of select="$status"/></h1>
<ul>
<xsl:for-each select="$whiskies[key($status, .)]">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Splitting up elements into groups of n child elements (follow-up from 69940580)

Follow-up from insert page-break after all elements except the last, when position() is not reliable.
How would I achieve the following result (more info below):
<pages>
<page title="Manchuria">
<edit version="2"/>
<edit version="3"/>
</page>
<page-break/>
<page title="Manchuria">
<edit version="4"/>
<edit version="5"/>
</page>
<page-break/>
<page title="Manchuria">
<edit version="6"/>
<edit version="7"/>
</page>
<page-break/>
<page title="Perfect the first time"/>
<page-break/>
<page title="Zombie Librarian">
<edit version="2"/>
</page>
</pages>
From the following input:
<articles>
<article id="er113" title="Manchuria" publishable="true" version="7">
<original>
<article id="xc141" version="1"/>
</original>
<edits>
<article id="er111" version="2"/>
<article id="yu475" version="4"/>
<article id="iu931" version="3"/>
<article id="er111" version="5"/>
<article id="er112" version="6"/>
<article id="er113" version="7"/>
</edits>
</article>
<article id="ww555" title="Perfect the first time" publishable="true" version="1">
<original>
<article id="ww555" version="1"/>
</original>
<edits/>
</article>
<article id="nb741" title="Zombie Librarian" publishable="true" version="2">
<original>
<article id="nc441" version="1"/>
</original>
<edits>
<article id="nb741" version="2"/>
</edits>
</article>
</articles>
starting from the following xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<pages>
<xsl:variable name="pages">
<xsl:apply-templates select="/articles/article[#publishable='true']" mode="subfilter"/>
</xsl:variable>
<xsl:apply-templates select="msxsl:node-set($pages)/article" mode="layout"/>
</pages>
</xsl:template>
<xsl:template match="article" mode="subfilter">
<xsl:variable name="id_of_latest_edit">
<xsl:for-each select="edits/article">
<xsl:sort data-type="number" select="#version"/>
<xsl:if test="position()=last()">
<xsl:value-of select="#id"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="#id = $id_of_latest_edit or not(edits/article)">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
<xsl:template match="article" mode="layout">
<page title="{#title}">
<xsl:apply-templates select="edits/article" mode="editdetails"/>
</page>
<xsl:if test="position()!=last()">
<page-break/>
</xsl:if>
</xsl:template>
<xsl:template match="article" mode="editdetails">
<xsl:param name="editPos"/>
<edit version="{#version}"/>
</xsl:template>
</xsl:stylesheet>
The problem
A page can have maximum n edit elements (n=2 in this example), and the edits need to be sorted by version.
What I tried
Before the linked question was solved, I tried something along the lines of:
<xsl:template match="article" mode="subfilter">
<xsl:variable name="currentnode" select="."/>
<!-- always print -->
<xsl:apply-templates select="." mode="layout"/>
<!-- print extra pages when number of articles exceeds limit -->
<xsl:if test="count(edits/article) > 2">
<xsl:for-each select="edits/article[(position() mod 2) = 0]">
<xsl:apply-templates select="$currentnode" mode="layout"/>
</xsl:for-each>
</xsl:if>
</xsl:template>
which gets me the multiple pages and I would still need to filter and sort the edit elements. However, the new code (to get the page-breaks) uses the node-set() function and I can't seem to figure out how to merge this approach (if it is even a good idea to try). Is maybe muenchian grouping needed?
I suppose this is one way you could look at it:
XSLT 1.0
<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:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/articles">
<pages>
<xsl:variable name="pages">
<xsl:apply-templates select="article[#publishable='true']" mode="preprocess"/>
</xsl:variable>
<xsl:apply-templates select="exsl:node-set($pages)/page"/>
</pages>
</xsl:template>
<xsl:template match="article[not(edits/article)]" mode="preprocess">
<page title="{#title}"/>
</xsl:template>
<xsl:template match="article" mode="preprocess">
<xsl:variable name="title" select="#title" />
<xsl:for-each select="edits/article">
<xsl:sort select="#version" data-type="number" order="ascending"/>
<xsl:if test="position() mod 2 = 1">
<page title="{$title}">
<edit version="{#version}"/>
<xsl:if test="position()!=last()">
<edit version="{#version + 1}"/>
</xsl:if>
</page>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="page">
<xsl:copy-of select="."/>
<xsl:if test="position()!=last()">
<page-break/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Note that this assumes versions are numbered consecutively.

XSLT sort data in variable and save them to another variable

I have a problem. I have the following XML
<countryList>
<country>
<name>Afghanistan</name>
<population>29117000</population>
<area>654329</area>
</country>
<country>
<name>Albania</name>
<population>3195000</population>
<area>28748</area>
</country>
<country>
<name>Algeria</name>
<population>35423000</population>
<area>2381741</area>
</country>
<country>
<name>Andorra</name>
<population>84082</population>
<area>468</area>
</country>
</countryList>
I have one question. All I need to do is divide population/area and sort these divisions for each country. However, I tried this
<xsl:variable name="Podiel">
<xsl:value-of select="../population div ../area"/>
</xsl:variable>
<xsl:variable name="PodielPodiel">
<xsl:for-each select="$Podiel">
<xsl:sort select="." data-type="number" order="descending"/>
</xsl:for-each>
</xsl:variable>
but I still get error
The 'select' expression does not evaluate to a node set.
no result for data1.xml
Any help? I just want to know the maximum of all divisions.
Thanks.
Not sure whether the issue has been resolved, however for using node-set in XSLT 1.0, extension functions have to be used. Please refer to node-set extension for additional details on it's usage in XSLT 1.0.
If at all the country list in the input XML needs to be sorted on the value of population div area, the number() function can be used within <xsl:sort>. Declaring variables and calculating the additional values and then accessing the variable is not required.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" />
<xsl:strip-space elements="*" />
<!-- identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="countryList">
<xsl:copy>
<xsl:apply-templates select="country">
<xsl:sort select="number(population div area)" data-type="number" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
<countryList>
<country>
<name>Andorra</name>
<population>84082</population>
<area>468</area>
</country>
<country>
<name>Albania</name>
<population>3195000</population>
<area>28748</area>
</country>
<country>
<name>Afghanistan</name>
<population>29117000</population>
<area>654329</area>
</country>
<country>
<name>Algeria</name>
<population>35423000</population>
<area>2381741</area>
</country>
</countryList>

Unable to Successfully Pass Parameters to xsl:call-template

I have a generic template I've designed with 2 params title and category.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="category" />
<xsl:param name="title" />
<xsl:template name="test-group">
<fo:block>
<xsl:value=of select="$title" />
</fo:block>
<fo:block>
<xsl:value-of select="$category" />
</fo:block>
</xsl:template>
</xsl:stylesheet>
In the parent template I have the following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:include href="templates/generic_template.xsl" />
...
<xsl:call-template name="test-group">
<xsl:with-param name="category" select="'animals'" />
<xsl:with-param name="title" select="'DOGS'" />
</xsl:call-template>
...
</xsl:stylesheet>
However, when the transform completes title and category are blank. I'm using FOP 2.0 so I'm not sure if this is a known shortcoming.
When defining a xsl:template that takes parameters, the parameter names used within the xsl:template should be declared using xls:param elements nested within.
<xsl:template name="test-group">
<xsl:param name="category" />
<xsl:param name="title" />
...
</xsl:template>
The parameters being attached to the xsl:template and not the xsl:stylesheet.
This is similar to when calling the template with xsl:call-template, except you are specifying the values instead using xsl:with-param.

Grouping of the elements based on the field value using muenchian in XSLT1.0

Request xml..
<tem:responseDA xmlns:tem="http://tempuri.org">
<tem:outputData>
<tem:dictionary id="AutoOutputs">
<tem:list numOfItems="2">
<tem:item>
<tem:field dataType="double" name="DOWNPAYMENT">2000.00</tem:field>
</tem:item>
<tem:item>
<tem:field dataType="double" name="DOWNPAYMENT">3000.00</tem:field>
</tem:item>
</tem:list>
<tem:list numOfItems="2">
<tem:item>
<tem:field dataType="string" name="CAMPAIGNCODE">A</tem:field>\
</tem:item>
<tem:item>
<tem:field dataType="string" name="CAMPAIGNCODE">B</tem:field>
</tem:item>
</tem:list>
<tem:list numOfItems="2">
<tem:item>
<tem:field dataType="double" name="BALLOONPAYMENT">4000.00</tem:field>
</tem:item>
<tem:item>
<tem:field dataType="double" name="BALLOONPAYMENT">5000.00</tem:field>
</tem:item>
</tem:list>
</tem:dictionary>
</tem:outputData>
</tem:responseDA>
Now I need to group the campaign based on the "numofitems" in the field.
Output should be..
<ns:FinalPricingQuoteResponse xmlns:ns="http://corp.alahli.com/middlewareservices/CreditVerificationService/1.1/" xmlns:tem="http://tempuri.org">
<ns:PricingQuoteOutput>
<ns:Campaigns>
<ns:campaignNumber>A</ns:campaignNumber>
<ns:downPayment>2000.00</ns:downPayment>
<ns:ballonPayment>4000.00</ns:ballonPayment>
</ns:Campaigns>
<ns:Campaigns>
<ns:campaignNumber>B</ns:campaignNumber>
<ns:downPayment>3000.00</ns:downPayment>
<ns:ballonPayment>5000.00</ns:ballonPayment>
</ns:Campaigns>
</ns:PricingQuoteOutput>
</ns:FinalPricingQuoteResponse>
Could you please help me for the muenchian grouping for XSLT1.0..
Thanks in advance..
regards,
Inian
Not sure what you mean with Münchian grouping. Presumably you have 3 lists coming up each time (with a variable number of entries, you could do this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:ns="http://corp.alahli.com/middlewareservices/CreditVerificationService/1.1/"
xmlns:tem="http://tempuri.org"
exclude-result-prefixes="xd"
version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<ns:FinalPricingQuoteResponse>
<xsl:apply-templates select="tem:responseDA/tem:outputData/tem:dictionary" />
</ns:FinalPricingQuoteResponse>
</xsl:template>
<xsl:template match="tem:dictionary">
<ns:PricingQuoteOutput>
<xsl:apply-templates select="tem:list[position()=1]" />
</ns:PricingQuoteOutput>
</xsl:template>
<xsl:template match="tem:list[position()=1]">
<xsl:apply-templates select="tem:item/tem:field[#name='DOWNPAYMENT']" />
</xsl:template>
<xsl:template match="tem:field[#name='DOWNPAYMENT']">
<xsl:variable name="pos" select="position()" />
<ns:Campaigns>
<ns:campaignNumber><xsl:value-of select="/tem:responseDA/tem:outputData/tem:dictionary/tem:list[position()=2]/tem:item[position()=$pos]/tem:field"/></ns:campaignNumber>
<ns:downPayment><xsl:value-of select="."/></ns:downPayment>
<ns:ballonPayment><xsl:value-of select="../../../tem:list[position()=3]/tem:item[position()=$pos]/tem:field"/></ns:ballonPayment>
</ns:Campaigns>
</xsl:template>
</xsl:stylesheet>
Note the two different XPath (pick one and stick to it) to select the matching fields. Of course that would depend on the sequence how the lists appear, which could be fragile. So a slightly more robust version would be:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:ns="http://corp.alahli.com/middlewareservices/CreditVerificationService/1.1/"
xmlns:tem="http://tempuri.org"
exclude-result-prefixes="xd"
version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<ns:FinalPricingQuoteResponse>
<xsl:apply-templates select="tem:responseDA/tem:outputData/tem:dictionary" />
</ns:FinalPricingQuoteResponse>
</xsl:template>
<xsl:template match="tem:dictionary">
<ns:PricingQuoteOutput>
<xsl:apply-templates select="tem:list[tem:item/tem:field[#name='DOWNPAYMENT']]" />
</ns:PricingQuoteOutput>
</xsl:template>
<xsl:template match="tem:list[tem:item/tem:field[#name='DOWNPAYMENT']]">
<xsl:apply-templates select="tem:item/tem:field[#name='DOWNPAYMENT']" />
</xsl:template>
<xsl:template match="tem:field[#name='DOWNPAYMENT']">
<xsl:variable name="pos" select="position()" />
<ns:Campaigns>
<ns:campaignNumber><xsl:value-of select="/tem:responseDA/tem:outputData/tem:dictionary/tem:list[tem:item/tem:field[#name='CAMPAIGNCODE']]/tem:item[position()=$pos]/tem:field"/></ns:campaignNumber>
<ns:downPayment><xsl:value-of select="."/></ns:downPayment>
<ns:ballonPayment><xsl:value-of select="../../../tem:list[tem:item/tem:field[#name='BALLOONPAYMENT']]/tem:item[position()=$pos]/tem:field"/></ns:ballonPayment>
</ns:Campaigns>
</xsl:template>
</xsl:stylesheet>
Hope that helps and feel free to accept the answer