XSLT - Adding Elements dynamically - xslt

I've a need to display certain XML content in tabular form (XSL-FO for pdf reports),
and not all the columns to displayed are present in source XML. So, I was wondering
if there is a way to transform source XML by embedding additional columns based on
certain element values, and then process the resulting XML to display content?
As an example, for source data:
<projectteam>
<member>
<name>John Doe</name>
<role>dev</role>
<hrs>100</hrs>
</member>
<member>
<name>Paul Coder</name>
<role>dev</role>
<hrs>40</hrs>
</member>
<member>
<name>Henry Tester</name>
<role>qa</role>
<hrs>80</hrs>
</member>
<member>
<name>Peter Tester</name>
<role>qa</role>
<hrs>40</hrs>
</member>
</projectteam>
I'd like the data to be displayed as :
Name Role Dev QA
---------------------------
John Doe dev 100
Paul Coder dev 40
Henry Tester qa 80
Peter Tester qa 40
---------------------------
Role Totals: 140 120
---------------------------
I would like to know if I can use something like:
<xsl:element name="{role}">
<xsl:value-of select="member/hrs"/>
</xsl:element>
So that I can embed elements <dev>100</dev> and so on at run time during
first pass, and then use the resulting XML to display data for new columsn 'dev'
and 'qa', that way, calculating totals for each role type will be much simpler
(for eg. "sum(preceding-sibling::member/dev)" for dev column), and the data for each
cell in 'dev' and 'qa' colums could simply be the value-of these tags respectively.
It got the desired results the hard way using following stylesheet (page formatting
details omitted to keep it brief), but am not convinced that this is the apt solution.
...
<fo:table-body>
<!-- fills table rows -->
<xsl:apply-templates select="member"/>
<!-- dislpay totals for each role -->
<fo:table-row height="12pt" border-bottom="1pt solid black">
<fo:table-cell number-columns-spanned="2">
<fo:block>Role Totals:</fo:block>
</fo:table-cell>
<fo:table-cell text-align="right">
<xsl:call-template name="RoleTotals">
<xsl:with-param name="node" select="//member[1]"/>
<xsl:with-param name="roleName" select="'dev'"/>
</xsl:call-template>
</fo:table-cell>
<fo:table-cell text-align="right">
<xsl:call-template name="RoleTotals">
<xsl:with-param name="node" select="//member[1]"/>
<xsl:with-param name="roleName" select="'qa'"/>
</xsl:call-template>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
...
</fo:root>
</xsl:template>
<xsl:template match="member">
<fo:table-row border-bottom="1pt solid black">
<fo:table-cell> <fo:block> <xsl:value-of select="name"/></fo:block></fo:table-cell>
<fo:table-cell> <fo:block> <xsl:value-of select="role"/></fo:block></fo:table-cell>
<fo:table-cell text-align="right">
<fo:block>
<xsl:if test="role = 'dev'"><xsl:value-of select="hrs"/></xsl:if>
</fo:block>
</fo:table-cell>
<fo:table-cell text-align="right">
<fo:block>
<xsl:if test="role = 'qa'"><xsl:value-of select="hrs"/></xsl:if>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
<xsl:template name="RoleTotals">
<xsl:param name="node"/>
<xsl:param name="roleName"/>
<xsl:param name="RT" select="0"/>
<xsl:variable name="newRT">
<xsl:choose>
<xsl:when test="$node/role = $roleName">
<xsl:value-of select="$RT + $node/hrs"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$RT"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$node/following-sibling::member">
<xsl:call-template name="RoleTotals">
<xsl:with-param name="node" select="$node/following-sibling::member[1]"/>
<xsl:with-param name="roleName" select="$roleName"/>
<xsl:with-param name="RT" select="$newRT"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<fo:block><xsl:value-of select="$newRT"/></fo:block>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

What would happen if more job roles, other than Dev and QA got added? Would your stylesheet be able to cope? Maybe you can make use of Muenchian Grouping to get all possible roles in the stylesheet, and then generate columns for each possible role dynamically?
<xsl:key name="roles" match="role" use="."/>
<xsl:template match="/projectteam">
<table border="1">
<tr>
<td>Name</td>
<td>Role</td>
<xsl:for-each select="member[generate-id(role) = generate-id(key('roles', role)[1])]">
<td>
<xsl:value-of select="role"/>
</td>
</xsl:for-each>
</tr>
<xsl:apply-templates select="member"/>
</table>
</xsl:template>
<xsl:template match="member">
<xsl:variable name="currentrole" select="role"/>
<xsl:variable name="currenthrs" select="hrs"/>
<tr>
<td>
<xsl:value-of select="name"/>
</td>
<td>
<xsl:value-of select="role"/>
</td>
<xsl:for-each select="/projectteam/member[generate-id(role) = generate-id(key('roles', role)[1])]">
<td>
<xsl:choose>
<xsl:when test="$currentrole = role">
<xsl:value-of select="$currenthrs"/>
</xsl:when>
</xsl:choose>
</td>
</xsl:for-each>
</tr>
</xsl:template>
I've outputted as HTML, not XSL-FO, but maybe this gives you the general idea.

Yes, you can.
To answer your question. I haven't read the huge stylesheet, to be true.

Related

Need to dynamically change the simple-page-master's master-name

I have an xml document as shown below. Each repeating doc is a page in PDF file
<AFPXMLFile>
<docs>
<regList>
<region>1</region>
<secList>
<col>1</col>
<lines></lines>
</secList>
</regList>
<regList>
<region>2</region>
<secList>
<col>2</col>
<lines>
<line>IBM BELGIUM xxx</line>
<line>xxxxxx</line>
<line>xxxx</line>
</lines>
</secList>
</regList>
<regList></regList>
<regList></regList>
</docs>
<docs></docs>
</AFPXMLFile>
My XSL is as follows:
<?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"
exclude-result-prefixes="fo">
<xsl:template match="AFPXMLFile">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master page-width="21cm" page-height="29.7cm" margin-top="1.27cm" margin-bottom="1.27cm" margin-left="1.75cm" master-name="A4">
<fo:region-body margin-top="1mm" margin-bottom="1mm"/>
<fo:region-before extent="0mm"/>
<fo:region-after extent="0mm"/>
<fo:region-start writing-mode="tb-rl" precedence="true" extent="10mm" />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4" font-family="sans-serif">
<xsl:for-each select="docs">
<xsl:for-each select="./regList">
<xsl:choose>
<xsl:when test="region = 1">
<fo:static-content flow-name="xsl-region-before">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="purple">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
<xsl:when test="region = 2">
<fo:static-content flow-name="xsl-region-start">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="4pt" padding-before="4pt" text-align="left" color="green">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
<xsl:when test="region = 3">
<fo:static-content flow-name="xsl-region-body">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="blue">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
<xsl:when test="region = 4">
<fo:flow flow-name="xsl-region-after">
<xsl:for-each select="./secList">
<xsl:for-each select="./lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="orange">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</fo:flow>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
When I run the transformation I get the following error:
org.apache.fop.fo.ValidationException: For "fo:page-sequence", "fo:static-content" must be declared before "fo:flow"! (No context info available)
This I suspect because it is repeating the static content region for each page. So I believe I need to change the simple-page-master:master-name for every page
I need help with that.
According to the XSL-FO recommendation, the contents of 'fo:page-sequence' is:
(title?,folio-prefix?,folio-suffix?,static-content*,flow+)
which means that all the fo:static-content elements must be before the fo:flow elements.
Your stylesheet dynamically creates either fo:static-content (if region is 1, 2 or 3) or fo:flow (if region is 4). From the reduced input file in your question it is not possible to see whether the regList elements are alway correctly sorted, so that they produce a valid output when sequentially processed.
Moreover, as each docs element represents a different document with different headers/footers, you have to create distinct fo:page-sequences instead of a single one (otherwise you get too many static contents and flows for a single page sequence):
<xsl:for-each select="docs">
<fo:page-sequence master-reference="A4" font-family="sans-serif">
<xsl:for-each select="./regList">
....
</xsl:for-each>
</fo:page-sequence>
</xsl:for-each>
Moreover, there is a strange thing in the stylesheet: you map an fo:static-content to the xsl-region-body region, and the fo:flow to the xsl-region-after region, which is quite unusual. If the "real" content for the body region is the one with region = 3, you must process region 1, 2 and 4 first, and then region 3:
<xsl:for-each select="docs">
<fo:page-sequence master-reference="A4" font-family="sans-serif">
<!-- create static contents -->
<xsl:apply-templates select="./regList[region != '3']"/>
<!-- create flow -->
<xsl:apply-templates select="./regList[region = '3']"/>
</fo:page-sequence>
</xsl:for-each>
with an additional template to match reglist:
<xsl:template match="regList">
<xsl:choose>
<xsl:when test="region = '1'">
<fo:static-content flow-name="xsl-region-before">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="purple">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
<xsl:when test="region = '2'">
<fo:static-content flow-name="xsl-region-start">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="4pt" padding-before="4pt" text-align="left" color="green">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
<xsl:when test="region = '3'">
<fo:flow flow-name="xsl-region-body">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="blue">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:flow>
</xsl:when>
<xsl:when test="region = '4'">
<fo:static-content flow-name="xsl-region-after">
<xsl:for-each select="./secList">
<xsl:for-each select="./lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="orange">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:when>
</xsl:choose>
</xsl:template>
or several, smaller templates:
<xsl:template match="reglist[region = '1']">
<fo:static-content flow-name="xsl-region-before">
<xsl:for-each select="./secList/lines">
<xsl:for-each select="node()">
<fo:block font-size="8pt" color="purple">
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</xsl:for-each>
</fo:static-content>
</xsl:template>
<xsl:template match="reglist[region = '2']">
...
</xsl:template>
...

Using node-set with Antenna House XSL-FO XSLT

I would like to use node-set() in Antenna House so I can access preceding-siblings in a sorted list. (I'm trying to follow this example: [using preceding-sibling with with xsl:sort)
I'm not sure what the syntax is for declaring the namespace to access node-set(). AH is not giving any errors, but my call to node-set() fails. I've tried:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="msxsl" version="1.0">
Here is the XML:
<illustratedPartsCatalog>
<figure id="fig1">...</figure>
<catalogSeqNumber assyCode="00" figureNumber="01" indenture="0" item="000" itemVariant="A" subSubSystemCode="0" subSystemCode="0" systemCode="00">
<itemSeqNumber itemSeqNumberValue="000">
<quantityPerNextHigherAssy>XX</quantityPerNextHigherAssy>
<partRef manufacturerCodeValue="00000" partNumberValue="11111-111">
</partRef>
<partSegment>
<itemIdentData>
<descrForPart>VALVE ASSEMBLY</descrForPart></itemIdentData>
</partSegment><applicabilitySegment><usableOnCodeAssy>X</usableOnCodeAssy>
</applicabilitySegment></itemSeqNumber></catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<figure id="fig2">...</figure>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
<catalogSeqNumber>...</catalogSeqNumber>
</illustratedPartsCatalog>
XSLT:
<xsl:variable name="sortedCSN">
<xsl:for-each select="illustratedPartsCatalog/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:template name="SortParts2" >
<xsl:for-each select="msxsl:node-set($sortedCSN)/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:call-template name="catalogSeqNumber-NI">
<xsl:with-param name="figNo" select="concat(#figureNumber,#figureNumberVariant)"/>
<xsl:with-param name="prfigNo" select="concat(preceding-sibling::catalogSeqNumber/#figureNumber,preceding-sibling::catalogSeqNumber/#figureNumberVariant)" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="catalogSeqNumber-NI">
<xsl:param name="figNo"/>
<xsl:param name="prfigNo" />
<fo:table-row keep-together.within-page="always" wrap-option="wrap">
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-transform="uppercase" wrap-option="wrap">
<fo:block wrap-option="wrap">
<xsl:value-of select=" ./itemSeqNumber/partRef/#partNumberValue"/>
</fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-align="start">
<xsl:choose>
<xsl:when test="$figNo">
<fo:block>
<xsl:text> </xsl:text><xsl:value-of select="$figNo"/><xsl:text> </xsl:text> <xsl:value-of select="$prfigNo"/>
</fo:block>
</xsl:when>
<xsl:otherwise>
<fo:block />
</xsl:otherwise>
</xsl:choose>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding" text-align="start">
<fo:block>
<xsl:if test="./itemSeqNumber/partLocationSegment/notIllustrated">
<xsl:text>-</xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="#item">
<xsl:variable name="itemNo">
<xsl:call-template name="suppressZero" >
<xsl:with-param name="pText" select="#item"/>
</xsl:call-template>
</xsl:variable>
<xsl:text> </xsl:text>
<xsl:value-of select="concat($itemNo,#itemVariant)"/>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding">
<fo:block>
<xsl:value-of select="./itemSeqNumber/quantityPerNextHigherAssy"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:template>
I think the default for Antenna House on Windows is to use MSXML so the attempt with xmlns:msxsl="urn:schemas-microsoft-com:xslt" is fine as far as using the node-set extension function.
I think you simply need to change the XSLT to
<xsl:variable name="sortedCSN">
<xsl:for-each select="illustratedPartsCatalog/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
<xsl:template name="SortParts2" >
<xsl:for-each select="msxsl:node-set($sortedCSN)/catalogSeqNumber">
<xsl:sort select="concat(itemSeqNumber/partRef/#partNumberValue, #figureNumber,#item)"/>
<xsl:call-template name="catalogSeqNumber-NI">
<xsl:with-param name="figNo" select="concat(#figureNumber,#figureNumberVariant)"/>
<xsl:with-param name="prfigNo" select="concat(preceding-sibling::catalogSeqNumber/#figureNumber,preceding-sibling::catalogSeqNumber/#figureNumberVariant)" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
to make sense with the input you have shown (as far as you have shown it, can't judge all those xsl:sort attempts without seeing the data to be sorted).
On the other hand, if you get errors about the stylesheet not being correct XSLT or XML, you would better show us a minimal but complete stylesheet allowing us to reproduce the problem.

Table of Contents XSL-FO XSLT 1.0

XML:
<levelledPara><title>Tools List and Tool Illustrations</title>
<levelledPara><title>General</title>
<levelledPara><para>The special tools, fixtures, and equipment needed.</para></levelledPara></levelledPara>
XSLT:
<xsl:template match="levelledPara" name="levelledPara" mode="tocdm">
<xsl:if test="*[self::title] and not(parent::*[self::levelledPara])">
<xsl:variable name="id">
<xsl:call-template name="para.id"/>
</xsl:variable>
<fo:table-row>
<fo:table-cell xsl:use-attribute-sets="table.cell.padding1" number-columns-spanned="2">
<fo:block text-transform="capitalize" text-align-last="justify" text-indent="21mm">
<xsl:number count="levelledPara" from="content" level="multiple" format="1.1.1.1.1"/>
<xsl:text>   </xsl:text>
<xsl:value-of select="title" /><fo:leader leader-pattern="dots"/><fo:basic-link internal-destination="{$id}"><fo:page-number-citation ref-id="{$id}"/></fo:basic-link>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:if>
</xsl:template>
<xsl:template match="levelledPara">
<fo:list-block
provisional-distance-between-starts="21mm"
provisional-label-separation="4pt">
<fo:list-item space-after="8pt" space-before="13pt" start-indent="0pt">
<xsl:variable name="id">
<xsl:if test="*[self::title] and not(parent::*[self::levelledPara])">
<xsl:call-template name="para.id"/>
</xsl:if>
</xsl:variable>
<fo:list-item-label
end-indent="label-end()"
text-align="start">
<fo:block font-weight="bold" id="{$id}">
<xsl:if test="not(./table)">
<xsl:number count="levelledPara" from="content" level="multiple" format="1.1.1.1.1"/>
</xsl:if>
</fo:block>
</fo:list-item-label>
<fo:list-item-body
start-indent="body-start()">
<xsl:apply-templates/>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</xsl:template>
<xsl:template name="para.id">
<xsl:param name="object" select="."/>
<xsl:apply-templates select="ancestor::dmodule/identAndStatusSection/dmAddress/dmIdent/dmCode"/>
<xsl:choose>
<xsl:when test="$object/#id">
<xsl:value-of select="$object/#id"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(count(ancestor::node()),'00000000',count(preceding::node()))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The first titled levelledPara should be included in the Table of Contents. In my sample markup none have IDs. The page number wasn't resolving because I forgot to assign an id to the fo:block for levelledPara.
You've shown the XSLT for the table of contents. The ID in the TOC should match an ID in the main text of your document.
So the template match="levelledPara" should contain a block that sets the ID:
<xsl:variable name="lpcode"><xsl:value-of select="$dmcode" /><xsl:value-of select="generate-id(.)" /></xsl:variable>
<fo:block id="{$lpcode}">

fo:Table to html table - xslt

I'm trying to write an xslt to generate another xslt that purpose is replacing only all xsl:fo with html tags..XSLT 1
I use CDATA around "xsl" namespace for avoid processing this kind of tag by the xslt. My scope is processing only xsl:fo directive and replace for example :
<fo:table table-layout="fixed" width="100%" font-size="10pt">
<fo:table-column column-width="proportional-column-width(0.65)"/>
<fo:table-column column-width="proportional-column-width(0.35)"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell padding-before="0.5cm"></fo:table-cell>
<fo:table-cell padding-before="0.5cm">
<fo:block>
y
<![CDATA[ --> this is treated as text so i can copy it with <xsl-valueof select="."/>??
<xsl:choose>
<xsl:when test="...xpath'">
<xsl:value-of select="..." />,
</xsl:when>
<xsl:otherwise>
at <xsl:value-of select=..." />,
</xsl:otherwise>
</xsl:choose>]]>
</fo:block>
<fo:block space-before="0.5cm" text-align="center">
x
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
I want traslate fo:table+fo:table-body with table tag, and fo:table-column with td width="..%", fo:table-row with tr.. Td width is not so easy to retrieve because the width property belong to fo:table-column and fo:table-cell handling the tag.
I try to loop fo:table-column when i read a table-cell i'm writing td and calculate the width using the property column-width obtained by precedent tag fo:table-column: i use the position() of tag table-column (first loop) in the fo:table-cell selection
for example here is my xslt tralslator for xsl:fo (above-mentioned):
<xsl:template name="fo-table">
<xsl:param name="font-size" />
<xsl:param name="width" />
<xsl:variable name="cols" select="count(fo:table-column)"/>
<xsl:if test="fo:table-column">
<xsl:variable name="effective-cols" select="count(fo:table-body/fo:table-row/fo:table-cell)"/>
<xsl:if test="$cols = $effective-cols">
<table>
<xsl:for-each select="fo:table-body/fo:table-row">
<tr>
<xsl:for-each select="parent::*/parent::*/fo:table-column">
<xsl:variable name="width-proportional">
<xsl:value-of select="#column-width"/>
</xsl:variable>
<td>
<xsl:attribute name="width">
<xsl:call-template name="getPercentWidth">
<xsl:with-param name="proportional-value-width"><xsl:value-of select="$width-proportional"/></xsl:with-param>
</xsl:call-template>
</xsl:attribute>
abc <xsl:variable name="vPosition"><xsl:value-of select="position()"/></xsl:variable>
<xsl:for-each select="parent::*/fo:table-body/fo:table-row/*[$vPosition]">
<xsl:value-of select="local-name()"/><xsl:text> #10;</xsl:text> <!-- debug-->
<xsl:choose>
<xsl:when test="fo:block">
<xsl:for-each select="fo:block">
<xsl:call-template name="fo-block-table">
<xsl:with-param name="text-align"><xsl:value-of select="#text-align"/></xsl:with-param>
<xsl:with-param name="space-before"><xsl:value-of select="#space-before"/></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
empty cell
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template name="fo-block-table">
<xsl:param name="text-align" />
<xsl:param name="space-before" />
<xsl:choose>
<xsl:when test="$text-align">
<div>
<xsl:attribute name="text-align">
<xsl:value-of select="normalize-space($text-align)"/>
</xsl:attribute>
<xsl:apply-templates select="."/>
</div>
</xsl:when>
<xsl:otherwise>
<div>
<xsl:apply-templates select="."/>
</div>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="getPercentWidth">
<xsl:param name="proportional-value-width"/>
<xsl:variable name="width" select="normalize-space($proportional-value-width)"/>
<xsl:variable name="begin"> <xsl:value-of select="string-length(substring-before($width, '('))" /></xsl:variable>
<xsl:variable name="last"> <xsl:value-of select="string-length(substring-before($width,')'))" /></xsl:variable>
<xsl:variable name="val" select="fn:substring($width, $begin, $last)" />
<xsl:variable name="val1" select="substring-after($val,'(')"/>
<xsl:variable name="cent" select="100"/>
<xsl:value-of select="concat(($val1 * $cent),'%')"/>
</xsl:template>
But i cant realize why all td's contains 'y',x and empty when it will belong only to the empty table-cell, seems it reads all fo:block..
<table>
<tr>
<td width="65%">
abc
table-cell #10;
empty cell
table-cell #10;<div>
y
</div>
<div text-align="center">
x
</div>
</td>
<td width="35%">
abc
table-cell #10;
empty cell
table-cell #10;<div>
y
</div>
<div text-align="center">
x
</div>
</td>
</tr>
</table>
I need to obtain:
<table>
<tr>
<td width="65%">
abc
table-cell #10;
empty cell
</td>
<td width="35%">
abc
table-cell #10;
<div>
y
</div>
<div text-align="center">
x
</div>
</td>
</tr>
</table>
if i replace the second loop
xsl : for-each
with
xsl : template
don't match anything!
Maybe *[$vPosition] doesn't work but it works if i replace number like 1 or 2..
What's wrong?
Thanks in advice!
Roby
The problem is with this line...
<xsl:for-each select="parent::*/fo:table-body/fo:table-row/*[$vPosition]">
Or rather, it is due to how the vPosition variable is defined:
<xsl:variable name="vPosition"><xsl:value-of select="position()"/></xsl:variable>
By using xsl:value-of you are actually causing vPosition to be set to a string value, not a numeric value. When you use a string value in a condition, like [*$vPosition] it will always return true if that string is not empty.
Try changing the variable declaration to this, which will set vPosition to be a number
<xsl:variable name="vPosition" select="position()" />

using for-each-group technique in xsl-fo

can I use for-each-group if yes then can someone show me an example. I am trying to generate a pdf using xsl-fo
I am trying to output it using a table. Please show me an example which makes use of grouping technique and adding values. However, the output should be displayed in a table.
xml file:
<?xml version="1.0"?>
<Library>
<Book code="123">
<BookName>XML</BookName>
<Category>Programming</Category>
<Quantity>10</Quantity>
<Price>100</Price>
</Book>
<Book code="345">
<BookName>Photoshop</BookName>
<Category>Design</Category>
<Quantity>50</Quantity>
<Price>200</Price>
</Book>
<Book code="123">
<BookName>XML</BookName>
<Category>Programming</Category>
<Quantity>5</Quantity>
<Price>100</Price>
</Book>
<Book code="345">
<BookName>Photoshop</BookName>
<Category>Design</Category>
<Quantity>10</Quantity>
<Price>200</Price>
</Book>
<Book code="456">
<BookName>Illustrator</BookName>
<Category>Design</Category>
<Quantity>100</Quantity>
<Price>300</Price>
</Book>
</Library>
myxsl-fo
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" standalone="no" omit-xml-declaration="no"/>
<xsl:template match="Library">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="A4-landscape" page-height="300mm" page-width="150mm" margin="1in">
<fo:region-body margin="1in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4-landscape">
<fo:flow flow-name="xsl-region-body">
<fo:table border-top-style="solid" border-top-width="thick">
<fo:table-body font-size="12pt" font-family="times new roman">
<fo:table-row border-bottom-style="solid" border-bottom-color="#000" border-bottom-width="thick">
<fo:table-cell padding-top="1mm" padding-bottom="1mm">
<fo:block font-weight="bold">Category</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="1mm" padding-bottom="1mm">
<fo:block font-weight="bold">Book Code</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="1mm" padding-bottom="1mm">
<fo:block font-weight="bold">Quantity</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="1mm" padding-bottom="1mm">
<fo:block font-weight="bold">Price</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="1mm" padding-bottom="1mm">
<fo:block font-weight="bold">Total</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:for-each select="Book">
<xsl:sort select="Category"/>
<xsl:sort select="#code"/>
<fo:table-row>
<fo:table-cell padding-top="3mm" padding-bottom="3mm">
<fo:block font-weight="bold">
<xsl:value-of select="Category"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="3mm" padding-bottom="3mm">
<fo:block font-weight="bold">
<xsl:value-of select="#code"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="3mm" padding-bottom="3mm">
<fo:block font-weight="bold">
<xsl:value-of select="Quantity"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="3mm" padding-bottom="3mm">
<fo:block font-weight="bold">
<xsl:value-of select="Price"/>
</fo:block>
</fo:table-cell>
<fo:table-cell padding-top="3mm" padding-bottom="3mm">
<fo:block font-weight="bold">
<xsl:value-of select="number(Quantity)*number(Price)"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
Instead of xsl:for-each if I try to use xsl:for-each-group then it throws an error saying that the xsl:for-each-group cannot be in that location.
my current output:
however the output I want is shown in the image below:
thanks
As mentioned in the comments, if you are using XSLT 1.0 then the xsl:for-each-group command is not available. In XSLT 1.0, grouping is usually done using a technique called Muenchian Grouping. It is worth reading it, and understanding it, as it is a very useful technique in XSLT 1.0 once you understand it.
In Muenchian Grouping, you start off by defining a key which will be used to look up the elements in your group. In your case, you are grouping by Category and Code together, and so they key would look like this:
<xsl:key name="GroupByCategoryCode" match="Book" use="concat(Category, '|', #code)"/>
Now, in XSLT 2.0, you would probably write this...
<xsl:for-each-group select="Library/Book" group-by="concat(Category, '|', #code)">
However, in XSLT 1.0 you have to write this (I've added lots of indentation to improve readability)
<xsl:for-each
select="Library/Book
[
generate-id() =
generate-id
(
key('GroupByCategoryCode', concat(Category, '|', #code))[1]
)
]">
(Using xsl:apply-templates here would also work).
What this is going is looking at all Book elements, and checking its combination of "Category" and "Code" to see whether it is the first occurrence of that element in the key. Effectively it will pick the distinct occurences of "Category" and "Code".
In XSLT 2.0, you would use "current-group" to then iterate over all elements in the group. In XSLT 1.0, you would just use the key
<xsl:for-each select="key('GroupByCategoryCode', concat(Category, '|', #code))">
Although, in this particular case, you don't need to do a for-each, as you are just summing up Quantity in each group.
<xsl:value-of
select="sum(key('GroupByCategoryCode', concat(Category, '|', #code))/Quantity)"/>
Or, to improve readability abit...
<xsl:variable name="currentGroup" select="key('GroupByCategoryCode', concat(Category, '|', #code))"/>
<xsl:value-of select="sum($currentGroup/Quantity)"/>
Try this XSLT. To keep things simple (and because I don't know xsl-fo) I am outputting HTML to demonstrate the principle. All you need to do is to replace the HTML tags with their xsl-fo equivalents
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="GroupByCategoryCode" match="Book" use="concat(Category, '|', #code)"/>
<xsl:template match="/">
<html>
<body>
<h1>Books Information</h1>
<table border="1">
<tr>
<th>Category</th>
<th>Book Code</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Price</th>
</tr>
<xsl:apply-templates select="Library/Book[generate-id() = generate-id(key('GroupByCategoryCode', concat(Category, '|', #code))[1])]">
<xsl:sort select="Category"/>
<xsl:sort select="#code"/>
</xsl:apply-templates>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Book">
<xsl:variable name="currentGroup" select="key('GroupByCategoryCode', concat(Category, '|', #code))"/>
<tr>
<td>
<xsl:value-of select="Category"/>
</td>
<td>
<xsl:value-of select="#code"/>
</td>
<td>
<xsl:value-of select="sum($currentGroup/Quantity)"/>
</td>
<td>
<xsl:value-of select="Price"/>
</td>
<td>
<xsl:value-of select="sum($currentGroup/Quantity) * Price"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
EDIT: To show the word 'Repeated' instead of the Category for repeated categories, you could view this as grouping by category, and only show the category name for the first one in the group.
So, add the following key to the XSLT, to allow you to look up books by category
<xsl:key name="GroupByCategory" match="Book" use="Category"/>
Then, you need to pick the distinct categories (this should surround the existing xsl:apply-templates)
<xsl:for-each select="Library/Book[generate-id() = generate-id(key('GroupByCategory', Category)[1])]">
Then within the template matching Book you could then determine whether to show the category name or "defined" like so
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:value-of select="Category" />
</xsl:when>
<xsl:otherwise>
<xsl:text>Repeated</xsl:text>
</xsl:otherwise>
</xsl:choose>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="GroupByCategoryCode" match="Book" use="concat(Category, '|', #code)"/>
<xsl:key name="GroupByCategory" match="Book" use="Category"/>
<xsl:template match="/">
<html>
<body>
<h1>Books Information</h1>
<table border="1">
<tr>
<th>Category</th>
<th>Book Code</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Price</th>
</tr>
<xsl:for-each select="Library/Book[generate-id() = generate-id(key('GroupByCategory', Category)[1])]">
<xsl:sort select="Category"/>
<xsl:apply-templates select="key('GroupByCategory', Category)[generate-id() = generate-id(key('GroupByCategoryCode', concat(Category, '|', #code))[1])]">
<xsl:sort select="#code"/>
</xsl:apply-templates>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Book">
<xsl:variable name="Category">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:value-of select="Category" />
</xsl:when>
<xsl:otherwise>
<xsl:text>Repeated</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="currentGroup" select="key('GroupByCategoryCode', concat(Category, '|', #code))"/>
<tr>
<td>
<xsl:value-of select="$Category"/>
</td>
<td>
<xsl:value-of select="#code"/>
</td>
<td>
<xsl:value-of select="sum($currentGroup/Quantity)"/>
</td>
<td>
<xsl:value-of select="Price"/>
</td>
<td>
<xsl:value-of select="sum($currentGroup/Quantity) * Price"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>