XSL for-each(-group) nested in for-each loop - xslt

We're aiming to print invoice lines grouped first by type of service being invocied and then by job description and constultant names, in this manner:
CONSULTANCY
Doing Actual Work
Gummo - 2014-03-03
Zeppo - 2014-02-24
Harpo - 2014-03-07
Snide Remarks
Groucho - 2014-02-24
Harp Playing
Harpo - 2014-02-28
EXPENSES INCURRED
Cigars
We're extracting this data from an xml file with a somewhat complex structure, namely the national Danish OIOUBL standard:
<Invoice>
(...)
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:Note>Bla bla</cbc:Note>
<cbc:InvoicedQuantity unitCode="EA">1.2500</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="DKK">1395.0000</cbc:LineExtensionAmount>
<cac:Delivery>
<cbc:ActualDeliveryDate>2014-02-24</cbc:ActualDeliveryDate>
</cac:Delivery>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="DKK">348.75</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="DKK">1395.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="DKK">348.75</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxcategoryid-1.1">StandardRated</cbc:ID>
<cbc:Percent>25.00</cbc:Percent>
<cac:TaxScheme>
<cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxschemeid-1.1">63</cbc:ID>
<cbc:Name>Moms</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:Item>
<cbc:Description>Snide Remarks</cbc:Description>
<cbc:Name>CONSULTANCY</cbc:Name>
<cac:SellersItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo">Groucho</cbc:ID>
<cbc:ExtendedID>Timer</cbc:ExtendedID>
</cac:SellersItemIdentification>
<cac:StandardItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo">2014-02-24</cbc:ID>
</cac:StandardItemIdentification>
<cac:AdditionalItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo">10000</cbc:ID>
</cac:AdditionalItemIdentification>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="DKK">1116.0000</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="EA">1</cbc:BaseQuantity>
</cac:Price>
</cac:InvoiceLine>
(...)
<cac:InvoiceLine>
<cbc:ID>6</cbc:ID>
<cbc:InvoicedQuantity unitCode="EA">1.0000</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="DKK">1116.0000</cbc:LineExtensionAmount>
<cac:Delivery>
<cbc:ActualDeliveryDate>2014-03-06</cbc:ActualDeliveryDate>
</cac:Delivery>
<cac:TaxTotal>
<cbc:TaxAmount currencyID="DKK">279.00</cbc:TaxAmount>
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="DKK">1116.00</cbc:TaxableAmount>
<cbc:TaxAmount currencyID="DKK">279.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxcategoryid-1.1">StandardRated</cbc:ID>
<cbc:Percent>25.00</cbc:Percent>
<cac:TaxScheme>
<cbc:ID schemeAgencyID="320" schemeID="urn:oioubl:id:taxschemeid-1.1">63</cbc:ID>
<cbc:Name>Moms</cbc:Name>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:Item>
<cbc:Description>Cigars</cbc:Description>
<cbc:Name>EXPENSES INCURRED</cbc:Name>
<cac:SellersItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo"></cbc:ID>
<cbc:ExtendedID>Timer</cbc:ExtendedID>
</cac:SellersItemIdentification>
<cac:StandardItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo">2014-03-06</cbc:ID>
</cac:StandardItemIdentification>
<cac:AdditionalItemIdentification>
<cbc:ID schemeAgencyID="9" schemeID="foo">20000</cbc:ID>
</cac:AdditionalItemIdentification>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="DKK">1116.0000</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="EA">1</cbc:BaseQuantity>
</cac:Price>
</cac:InvoiceLine>
</Invoice>
We start our XSL style sheet off by doing a simple for-each loop which groups invoice lines by cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification (the consultancy lines are labelled 10000 and the expenses lines are labelled 20000); we then need to ensure that each consultancy line is grouped first by job description (cac:InvoiceLine/cac:Item/cbc:Description). We've tried several iterations of further for-each loops, for-each-group'ing and solutions which used key. However, they all seem to fall back on XPath issues
<xsl:choose>
<xsl:when test="$layouttype = '1'">
<xsl:for-each select="cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification[cbc:ID='10000']">
<!-- Invoice headline - Printed once-->
<xsl:if test="number(position()) = 1">
<fo:table-row margin-left="0mm" margin-right="0mm" white-space="normal">
<!-- see if period is empty so colspan should be = 2 -->
<xsl:choose>
<xsl:when test="../cac:StandardItemIdentification/cbc:ID != 'n/a'">
<fo:table-cell display-align="after">
<fo:block margin-bottom="1mm" font-weight="bold">
<xsl:value-of select="../cbc:Name"/>
</fo:block>
</fo:table-cell>
</xsl:when>
<xsl:otherwise>
<fo:table-cell number-columns-spanned="2" display-align="after">
<fo:block margin-bottom="1mm" font-weight="bold">
<xsl:value-of select="../cbc:Name"/>
</fo:block>
</fo:table-cell>
</xsl:otherwise>
</xsl:choose>
</fo:table-row>
</xsl:if>
<!-- the actual invoice line -->
<!-- and this is where it gets tricky... -->
<xsl:for-each-group select="../cbc:Description" group-by="../cbc:Description">
<fo:table-row>
<fo:table-cell>
<fo:block><xsl:value-of select="../cbc:Description"/></fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:for-each select="current-group()">
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="../cac:SellersItemIdentification/cbc:ID"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</xsl:for-each-group>
</xsl:for-each>
<xsl:for-each select="cac:InvoiceLine/cac:Item/cac:AdditionalItemIdentification[cbc:ID='20000']">
<!-- then the same pattern is repeated -->

It's probably best to avoid repeated code where you can. It is not clear if you are only ever going to have "Consultancy" and "Expenses Incurred", but it may be safe to assume not. I am also going to assume you can have multiple Item elements per InvoiceLine (although the solution given still work if this is not the case).
Anyway, you would start off by grouping Item elements by their AdditionalItemIdentification. This would create your groups for "Consultancy" and "Expenses Incurred"
<xsl:for-each-group select="cac:InvoiceLine/cac:Item"
group-by="cac:AdditionalItemIdentification/cbc:ID">
Then, the title of each group would done by simply outputting the name
<xsl:value-of select="cbc:Name" />
Now, within the current group (of Item) elements, you then need to group by description
<xsl:for-each-group select="current-group()"
group-by="cbc:Description">
Finally, in this group (still of Item) you want to group by SellersItemIdentification, although you probably need an extra check because not all Item elements have them (in the case of "Expenses Incurred"
<xsl:for-each-group select="current-group()[cac:SellersItemIdentification/cbc:ID != '']"
group-by="cac:SellersItemIdentification/cbc:ID">
Outputing the current "seller" would them be a case of using the grouping-key, and other fields:
<xsl:value-of select="current-grouping-key()" />
<xsl:value-of select="cac:StandardItemIdentification/cbc:ID" />
As I don't know XSL-FO very well, the XSLT provided as an example will just output vague "table" and "row" elements, but it should give you the general idea.
Try this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:cac="cac" xmlns:cbc="cbc" exclude-result-prefixes="cac cbc">
<xsl:output indent="yes"/>
<xsl:template match="Invoice">
<xsl:for-each-group select="cac:InvoiceLine/cac:Item" group-by="cac:AdditionalItemIdentification/cbc:ID">
<table>
<row><xsl:value-of select="cbc:Name" /></row>
<xsl:for-each-group select="current-group()" group-by="cbc:Description">
<row><xsl:value-of select="cbc:Description" /></row>
<xsl:for-each-group select="current-group()[cac:SellersItemIdentification/cbc:ID != '']" group-by="cac:SellersItemIdentification/cbc:ID">
<row>
<xsl:value-of select="current-grouping-key()" /> - <xsl:value-of select="cac:StandardItemIdentification/cbc:ID" />
</row>
</xsl:for-each-group>
</xsl:for-each-group>
</table>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
This should output the following (based on your provided sample, which only includes Groucho, and not poor old Zeppo or Harpo)
<table>
<row>CONSULTANCY</row>
<row>Snide Remarks</row>
<row>Groucho - 2014-02-24</row>
</table>
<table>
<row>EXPENSES INCURRED</row>
<row>Cigars</row>
</table>

Related

How to interrupt a page-sequence to insert another page-sequence

Via xsl:for-each-group I group my data with a property pagemaster. For this group I assign the page-sequence in the attribute pagemaster. Then the template is called for each element in this group.
Now to my question: is it somehow possible that I leave the current page-sequence in the for-each loop to insert another page-sequence and then continue with the previous one?
As an example, let's assume that all elements in the group should be rendered on blue pages, but if an element has a certain attribute, a red page will be inserted at this place.
My code section looks like this:
.
<!-- master-set defined here -->
.
</fo:layout-master-set>
<xsl:for-each-group select=".//reportelements/*[pagemaster != '']" group-adjacent="pagemaster">
<fo:page-sequence master-reference="{current-grouping-key()}">
<!-- Static-Content per pagemaster Group -->
<xsl:call-template name="STATIC-CONTENT">
<xsl:with-param name="pageMaster" select="current-grouping-key()" />
</xsl:call-template>
<fo:flow flow-name="xsl-region-body">
<xsl:for-each select="current-group()">
<!-- Here, for example, i would like to check if the current element (.)
is from type "section" and the attribute "hastoc" is true.
If yes the current page sequence should be interrupted and a new page-sequence
should be inserted on which the table of contents is rendered. -->
<xsl:apply-templates select="."/>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</xsl:for-each-group>
</fo:root>
</xsl:template>
<xsl:template name="STATIC-CONTENT">
<xsl:param name="pageMaster" />
<xsl:if test="$pageMaster = 'TITLEPAGE'">
<fo:static-content flow-name="xsl-region-after">
<fo:block text-align="right">
<fo:external-graphic src="url(file:C\Logo.pdf)" content-width="6cm" />
</fo:block>
</fo:static-content>
</xsl:if>
</xsl:template>
Xml Code looks like
<?xml version="1.0" encoding="utf-8"?>
<root>
<report>
<layout>report.xsl</layout>
<namecontent>Report</namecontent>
<reportelements>
<section>
<layout>section_normal</layout>
<pagemaster></pagemaster>
<titlecontent>Vorspann</titlecontent>
<hastoc>false</hastoc>
<reportelements>
<picture>
<layout>picture_title</layout>
<pagemaster>TITLEPAGE</pagemaster>
<titlecontent>Titelpicture</titlecontent>
<reportelements />
<path>files\Titelpicture213124.Jpeg</path>
</picture>
<section>
<layout>section_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>Profil</titlecontent>
<hastoc>true</hastoc>
<reportelements>
<paragraph>
<layout>paragraph_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>p1</titlecontent>
<reportelements />
<textcontent>paragraph_leader 1 text</textcontent>
</paragraph>
<paragraph>
<layout>paragraph_leader.xsl</layout>
<pagemaster>DIN-A4-HEADER-ODD-EVEN</pagemaster>
<titlecontent>p2</titlecontent>
<reportelements />
<textcontent>paragraph_leader 2 text</textcontent>
</paragraph>
.
.
.
.
I am using the Apache Formatting Object Processor 2.3

Index mapping in XSLT-2.0

I have created index page using XSLT code. The contents name are shown to index page correctly but i want to map contents according to page number for specific title.
I am getting title with their description So I have named that block as id="TOC" and given as ref-id=”TOC” but the page number is not reflecting in my title.
Title:- Title names present in Index page.
Summary:- Title content associated with page.
The below is my XSLT-2.0 sample code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="content_name">
<fo:block space-after="7pt" space-after.conditionality="retain" line-height="1.147" font-family="Calibri" font-size="15pt" font-weight="bold" language="FR">
<fo:inline>
<fo:leader leader-length="0pt" />
<xsl:value-of select="title"/>
<fo:page-number-citation ref-id="TOC"/>
</fo:inline>
</fo:block>
</xsl:template>
<xsl:template match="pro_list">
<fo:block space-after="15pt" space-after.conditionality="retain" line-height="1.147" font-family="Calibri" font-size="15pt" font-weight="bold" text-decoration="underline" language="FR">
<fo:inline>
<fo:leader leader-length="0pt" />
<xsl:value-of select="title" id="TOC"/>
</fo:inline>
</fo:block>
<fo:block space-after="8pt" space-after.conditionality="retain" line-height="1.147" font-family="Calibri" font-size="15pt" language="FR">
<fo:inline>
<fo:leader leader-length="0pt"/>
<xsl:value-of select="summary" disable-output-escaping="yes" />
</fo:inline>
</fo:block>
</xsl:template>
</xsl:stylesheet>
This is the output:
Contents
Title1 page no.
Title2 page no.
Title3 page no.
Suppose title1 content is associated with page2 & similar for title2-3 & title3-4, then output looks like as follows
Contents
Title1 2
Tilte2 3
Title3 4
Is there any syntax or predefined function available to do index mapping?

How can Antennahouse Formatter handle referenced footnotes?

I am developing a pdf print publication with xsl-fo (Saxon XSL 2.0, AHF V6.2).
My goal is to have auto-numbered footnotes (excluding duplicates on a single page) with inserted text from referenced static text elements.
So basically inline footnotes (fn) do reference a static footnote text element, create an inline number and print the according footnote text at the bottom of the page.
<?xml version="1.0" encoding="UTF-8"?>
<document>
<chapter>
<paragraph>some description...</paragraph>
<paragraph>some description with a footnote <fn id="fn2"/></paragraph>
<paragraph>some description with a footnote <fn id="fn2"/></paragraph>
<paragraph>some description...</paragraph>
<paragraph>some description with a footnote <fn id="fn1"/></paragraph>
</chapter>
<!-- this is a wrapper element that will not be displayed in the rendered pdf but only contains the needed information for different footnote texts -->
<chapter class="footnoteWrapper">
<footnote id="fn1">
This is the text body of footnote #1.
</footnote>
<footnote id="fn2">
This is the text body of footnote #2.
</footnote>
<footnote id="fn3">
This is the text body of footnote #3.
</footnote>
</chapter>
</document>
Duplicate inline footnotes in a chapter have to show the same number according to the footnote they are pointing to.
This is what the result should look like...
Is it possible to achieve these goals with the AHF footnote extensions and the fo:footnote elements?
The AntennaHouse Formatter extentions do deliver strange behaviour if I´m using them for fn counting. They do continue counting (1, 2, 3) instead of refereing to the correct and current number of the referenced footnote.
This is the XSL so far (just the relevant snippet):
<?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:template match="fn[#id = //footnote/#nodeid]"
mode="content"
priority="7">
<!--+ fn link
|
| basic fn (inline) link template.
|
+-->
<xsl:apply-templates select="//footnote[#id = current()/#id]"
mode="content"/>
</xsl:template>
<xsl:template match="footnote"
mode="content"
priority="5">
<!--+ footnote
|
| basic footnote template.
|
+-->
<fo:footnote xsl:use-attribute-sets="fnt.footnote">
<fo:inline baseline-shift="super">
<axf:footnote-number id="fn_{#id}"/>
</fo:inline>
<fo:footnote-body space-after="1mm">
<fo:list-block provisional-distance-between-starts="5mm"
provisional-label-separation="2mm">
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>
<fo:inline baseline-shift="super">
<axf:footnote-number-citation ref-id="fn_{#id}"/>
</fo:inline>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>
<xsl:apply-templates mode="content"/>
</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</fo:footnote-body>
</fo:footnote>
</xsl:template>
</xsl:stylesheet>
These changes generate the footnote the first time that the footnote is used and just generate the number for subsequent times:
<xsl:key name="fn" match="fn[exists(key('footnote', #id))]" use="#id" />
<xsl:key name="fn-first" match="fn[. is key('fn', #id)[1]]" use="#id" />
<xsl:key name="footnote" match="footnote" use="#id" />
<xsl:template match="fn[exists(key('footnote', #id))][. is key('fn-first', #id)]"
mode="content"
priority="7">
<xsl:apply-templates select="key('footnote', #id)"
mode="content">
<xsl:with-param name="number" select="count(preceding::fn[. is key('fn-first', #id)]) + 1"></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="fn[exists(key('footnote', #id))][not(. is key('fn-first', #id))]"
mode="content"
priority="7">
<fo:inline baseline-shift="super">
<xsl:value-of select="count(key('fn-first', #id)/preceding::fn[. is key('fn-first', #id)]) + 1"/>
</fo:inline>
</xsl:template>
<xsl:template match="footnote" mode="content" priority="5">
<xsl:param name="number" select="count(preceding-sibling::footnote) + 1" as="xs:integer" />
<fo:footnote xsl:use-attribute-sets="fnt.footnote">
<fo:inline baseline-shift="super">
<xsl:value-of select="$number" />
</fo:inline>
<fo:footnote-body space-after="1mm">
<fo:list-block provisional-distance-between-starts="5mm"
provisional-label-separation="2mm">
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>
<fo:inline baseline-shift="super">
<xsl:value-of select="$number" />
</fo:inline>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>
<xsl:apply-templates mode="content" />
</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</fo:footnote-body>
</fo:footnote>
</xsl:template>
You could tidy it up a bit more by, e.g., making a function that returns the count() value for a fn, but this should get you going.
See my other answer for how you can use both axf:suppress-duplicate-footnote and axf:footnote-number so duplicates are suppressed only when the duplicates are on the same page.
The duplicate footnotes also had duplicate IDs. The error from the non-unique IDs was getting in the way of the axf:suppress-duplicate-footnote processing.
If you're not making links to the footnotes, generate a unique ID for each footnote based on the fn that refers to it:
<xsl:template match="fn[exists(key('footnote', #id))]" mode="content" priority="7">
<!--+ fn link
|
| basic fn (inline) link template.
|
+-->
<xsl:apply-templates select="key('footnote', #id)" mode="content">
<xsl:with-param name="id" select="generate-id()" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="footnote" mode="content" priority="5">
<xsl:param name="id" />
<!--+ footnote
|
| basic footnote template.
|
+-->
<fo:footnote xsl:use-attribute-sets="fnt.footnote" axf:suppress-duplicate-footnote="true">
<fo:inline baseline-shift="super">
<axf:footnote-number id="{$id}" />
</fo:inline>
<fo:footnote-body space-after="1mm">
<fo:list-block provisional-distance-between-starts="5mm"
provisional-label-separation="2mm">
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>
<fo:inline baseline-shift="super">
<axf:footnote-number-citation ref-id="{$id}" />
</fo:inline>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>
<xsl:apply-templates mode="content" />
</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</fo:footnote-body>
</fo:footnote>
</xsl:template>

Converting an xsl:apply-templates into a string value to use as an if parameter

I have this piece of code, modified from the DITA-OT original distribution:
<xsl:template match="*[contains(#class, ' topic/topic ')]" mode="in-this-section-chapter-list">
<fo:block margin-left="6em">
<fo:block>
<xsl:call-template name="insertVariable">
<xsl:with-param name="theVariableID" select="'Chapter with number'"/>
<xsl:with-param name="theParameters">
<number>
<fo:inline>
<xsl:apply-templates select="key('map-id', #id)[1]"
mode="topicTitleNumber"/>
</fo:inline>
</number>
</xsl:with-param>
</xsl:call-template>
</fo:block>
</fo:block>
</xsl:template>
I am trying to only execute/print this mini-toc, when this is a Part that has Chapters as child nodes (see below), but not when it is only a Part without any Chapters, in a book like this:
<?xml version="1.0" encoding="utf-8"?>
<bookmap>
<part>
<chapter/>
<chapter/>
<chapter/>
</part>
<part/>
<part/>
<part/>
<part/>
<appendix/>
</bookmap>
So in this case, only the first <part> would execute/print this.
I thought that passing the value from <xsl:apply-templates select="key('map-id', #id)[1]" mode="topicTitleNumber"/> as text, would allow me to add an if that basically will test for a value that is not empty, thus executing this. But it has not worked.
I came up with something like this, which is not valid:
<xsl:template match="*[contains(#class, ' topic/topic ')]" mode="in-this-section-chapter-list">
<xsl:with-param name="value-number">
<xsl:apply-templates select="key('map-id', #id)[1]"
mode="topicTitleNumber"/>
</xsl:with-param>
<xsl:if test="$value-number!=''">
<fo:block margin-left="6em">
<fo:block>
<xsl:call-template name="insertVariable">
<xsl:with-param name="theVariableID" select="'Chapter with number'"/>
<xsl:with-param name="theParameters">
<number>
<fo:inline>
<xsl:apply-templates select="key('map-id', #id)[1]"
mode="topicTitleNumber"/>
</fo:inline>
</number>
</xsl:with-param>
</xsl:call-template>
</fo:block>
</fo:block>
</xsl:if>
</xsl:template>
For the template to match only part that contain chapter, change match="*[contains(#class, ' topic/topic ')]" to match="part[chapter][contains(#class, ' topic/topic ')]".
You might no longer need the [contains(#class, ' topic/topic ')] if the predicate is going to be true for every part that you're interested in. And you really don't want it if it's going to be false for any of the part that you're interested in.
You may also need to add an OR so that you pick up chapter if you want the minitoc at the chapter level.

Displaying xml nodes dynamically and applying style to specific nodes using recursion

Below is my xml file.
<xml>
<top>
<main>
<firstname>John</firstname>
<lastname>John</lastname>
<table></table>
<chapter>
<firstname>Alex</firstname>
<lastname>Robert</lastname>
<p>Sample text chap</p>
<figure name="f1.svg"></figure>
<chapter>
<firstname>Rebec</firstname>
<lastname></lastname>
<p>Sample text</p>
<figure name="f2.svg"></figure>
</chapter>
</chapter>
</main>
</top>
</xml>
Desired output:
<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg
Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime.
I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations.
This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown.
And how do I match only 'lastname' and make it bold?
I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes.
Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.
XSLT:
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>
<xsl:template name="FetchNodes">
<xsl:param name="endIndex" />
<xsl:param name="startIndex" />
<xsl:param name="context" />
<xsl:if test="$startIndex <= $endIndex">
<xsl:if test="$context[$startIndex][name() = 'table']"">
<xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
</xsl:if>
<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$endIndex" />
<xsl:with-param name="startIndex" select="$startIndex + 1"/>
<xsl:with-param name="context" select="$context" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="table">
<xsl:value-of select="node()" />
</xsl:template>
With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.
Can anybody suggest something?
The problem it displaying "f1.svg" instead of "f2.svg" is because of this line
<xsl:variable name="ImageName">
<xsl:value-of select="$root/*/chapter/figure/#name" />
</xsl:variable>
You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first #name attribute regardless of your context. It should look this this
<xsl:variable name="ImageName">
<xsl:value-of select="#name" />
</xsl:variable>
Or better still, like this
<xsl:variable name="ImageName" select="#name" />
Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example
<xsl:template match="figure" mode="figure">
As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:
<xsl:choose>
<xsl:when test="self::lastname">
<fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()" />
</xsl:otherwise>
</xsl:choose>
EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.
For example, to turn a chapter into an fo:block do this
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
To output the firstname in bold, do this
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
Try this XSLT as a starting point, and build on it
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="main">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="chapter">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</xsl:template>
<xsl:template match="firstname">
<fo:inline font-weight="bold">
<xsl:value-of select="text()"/>
</fo:inline>
</xsl:template>
<xsl:template match="lastname"/>
<xsl:template match="figure">
<fo:block>
<fo:external-graphic src="../resources/{#name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
</fo:block>
</xsl:template>
</xsl:stylesheet>