using for-each-group technique in xsl-fo - xslt

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>

Related

XSLT 1.0 - repeating table using child context doesn't work

I am using the XML below:
<root>
<Products name="product01">
<Instruments name="ins01"/>
<Instruments name="ins02"/>
<Instruments name="ins01"/>
</Products>
<Products name="product02">
<Instruments name="random text A">
<Bill name="A"/>
</Instruments>
<Instruments name="random text B and C">
<Bill name="B">
<Notes>some text</Notes>
</Bill>
<Bill name="C">
<Notes>some text</Notes>
</Bill>
</Instruments>
<Instruments name="random text A and B">
<Bill name="A">
<Notes>some text</Notes>
</Bill>
<Bill name="B">
<Notes>some text</Notes>
</Bill>
</Instruments>
<Instruments name="random text B">
<Bill name="B">
<Notes>some text</Notes>
</Bill>
</Instruments>
<Instruments name="random text C">
<Bill name="C">
<Notes>some text</Notes>
</Bill>
</Instruments>
<Instruments name="random text C and A">
<Bill name="C">
<Notes>some text</Notes>
</Bill>
<Bill name="A">
<Notes>some text</Notes>
</Bill>
</Instruments>
</Products>
</root>
First, I am grouping after Products, then grouping after Bills, on the left table-column, while the right table-column should only display the Instruments name, based on the Bill Type grouping from first column. As an example of above XML, I am trying to achieve this:
product02
Bill Type: A random text A
random text A and B
random text C and A
Bill Type: B random text B and C
random text A and B
random text B
Bill Type: C random text B and C
random text C
random text C and A
A sample of my template is below:
<xsl:key name="groupProducts" match="/root/Products/Instruments/Bill" use="../../#name" />
<xsl:key name="groupBilling" match="/root/Products/Instruments/Bill" use="concat(../../#name,#name)" />
<xsl:template name="myTemplate">
<fo:block>
<fo:table>
<fo:table-column column-width="proportional-column-width(1)" />
<fo:table-body>
<xsl:for-each select="/root/Products/Instruments/Bill[generate-id(.) = generate-id(key('groupProducts',../../#name)[1])]">
<fo:table-row>
<fo:table-cell>
<fo:block font-weight="bold" margin-top="4pt">
<xsl:value-of select="../../#name" />
</fo:block>
<fo:block>
<fo:table>
<fo:table-column column-width="50.000" column-number="1" />
<fo:table-column column-width="50.000" column-number="2" />
<fo:table-body>
<xsl:for-each select="/root/Products/Instruments/Bill[generate-id(.) = generate-id(key('groupBilling',concat(current()/../../#name,#name))[1])]">
<fo:table-row>
<fo:table-cell>
<fo:block color="blue">
Bill Type: <xsl:value-of select="#name" /></fo:block>
<fo:block />
</fo:table-cell>
<fo:table-cell>
<fo:block>
<fo:table>
<fo:table-column column-width="proportional-column-width(1)" />
<fo:table-body>
<xsl:for-each select="../.">
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="#name" />
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block>
</xsl:template>
For the for-each on the right table-column, I also tried the following:
<xsl:for-each select="../.">
../*[score/#naam = current()/score/#naam]
../node()[score/#naam = current()/score/#naam]
/root/Products/Instruments, but none works.
Any help is greatly appreciated.
While I cannot follow your attempted XSLT with fo namespace, consider this shortened XSLT running the Muenchian Method rendering an HTML version of desired output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="bill_key" match="Bill" use="#name"/>
<xsl:template match="/root">
<html>
<xsl:apply-templates select="Products[descendant::Bill]"/>
</html>
</xsl:template>
<xsl:template match="Products">
<table>
<tr>
<td><xsl:value-of select="#name"/></td>
<td></td>
</tr>
<xsl:apply-templates select="Instruments"/>
<tr></tr>
</table>
</xsl:template>
<xsl:template match="Instruments">
<xsl:apply-templates select="Bill[generate-id() =
generate-id(key('bill_key', #name))]"/>
</xsl:template>
<xsl:template match="Bill">
<xsl:for-each select="key('bill_key', #name)">
<tr>
<xsl:if test="position() = 1">
<td><xsl:value-of select="concat('Bill Type: ', #name)"/></td>
</xsl:if>
<xsl:if test="position() > 1">
<td></td>
</xsl:if>
<td><xsl:value-of select="../#name"/></td>
</tr>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Hopefully you can adjust the <table>, <tr>, <td> tags to your fo styling.
Online Demo (click HTML to view output)
HTML Table Output
product02
Bill Type: A random text A
random text A and B
random text C and A
Bill Type: B random text B and C
random text A and B
random text B
Bill Type: C random text B and C
random text C
random text C and A

How to do sort(as alphabetical odder) within a for loop as expected below in xslt or xsl-fo?

I want to create a table Which contain two columns as product type and number of quantity in XSL-Fo.
In the XML input file Type node contain 00 then in the table the product should be come like "Other"
if Type element value is
00 then "Other" like wise
51 = Business Loan – General
05 = Personal Loan
I want to see all product in alphabetical ascending odder.
This is my Input XML
<product>
<months>
<Type>00</Type>
<Number>2</Number>
</months>
<months>
<Type>51</Type>
<Number>2</Number>
</months>
<months>
<Type>05</Type>
<Number>1</Number>
</months>
</product>
I tried this Here
quantity
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block >Product Type</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Quantity</fo:block>
</fo:table-cell>
<xsl:for-each select=" Product/months">
<xsl:variable name="Variable" select="Type"></xsl:variable>
<fo:table-row>
<fo:table-cell>
<fo:block >
<xsl:choose>
<xsl:when test="Type[contains(text(),'05')]">
Personal Loan
</xsl:when>
<xsl:when test="ProductType[contains(text(),'55')]">
Business Loan – General
</xsl:when>
<xsl:when test="ProductType[contains(text(),'00')]">
Other
</xsl:when>
</xsl:choose>
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:value-of select="Number"/>
</fo:block>
</fo:table-cell>
</xsl:for-each>
</fo:table-body>
</fo:table>
I got the O/p like below
Product Type | Quantity
-----------------------------------------------------------
Other | 2
Business Loan – General | 2
Personal Loan | 1
but Actual Expected OUT/PUT is this
Product Type | Quantity
-----------------------------------------------------------
Business Loan – General | 2
Other | 2
Personal Loan | 1
If can use XSLT 2.0, you can create a variable to hold elements that represent your mappings
<xsl:variable name="products">
<map-entry key="05">Personal Loan</map-entry>
<map-entry key="51">Business Loan – General</map-entry>
<map-entry key="00">Other</map-entry>
</xsl:variable>
Then you can use a key to look these up by the key attribute
<xsl:key name="map" match="map-entry" use="#key" />
You would then use this key in a sort, like so:
<xsl:for-each select="product/months">
<xsl:sort select="key('map', Type, $products)" />
(As an aside, do make sure you use the correct case, as in your XSLT you were using Product, not product)
Try this XSLT, which you can see in action at http://xsltfiddle.liberty-development.net/pPzifoQ
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="products">
<map-entry key="05">Personal Loan</map-entry>
<map-entry key="51">Business Loan – General</map-entry>
<map-entry key="00">Other</map-entry>
</xsl:variable>
<xsl:key name="map" match="map-entry" use="#key" />
<xsl:template match="/">
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>Product Type</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Quantity</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:for-each select="product/months">
<xsl:sort select="key('map', Type, $products)" />
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="key('map', Type, $products)" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:value-of select="Number"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</xsl:template>
</xsl:stylesheet>
Note, if you can use XSLT 3.0, there is a dedicated "map" function you can use, which slightly simplifies things
Try this XSLT 3.0, which you can see in action at http://xsltfiddle.liberty-development.net/bnnZVx
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
exclude-result-prefixes="xs"
version="3.0">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="products" as="map(xs:string, xs:string)">
<xsl:map>
<xsl:map-entry key="'05'" select="'Personal Loan'"/>
<xsl:map-entry key="'51'" select="'Business Loan – General'"/>
<xsl:map-entry key="'00'" select="'Other'"/>
</xsl:map>
</xsl:variable>
<xsl:template match="/">
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>Product Type</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Quantity</fo:block>
</fo:table-cell>
</fo:table-row>
<xsl:for-each select="product/months">
<xsl:sort select="$products(Type)" />
<fo:table-row>
<fo:table-cell>
<fo:block>
<xsl:value-of select="$products(Type)" />
</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>
<xsl:value-of select="Number"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</xsl:template>
</xsl:stylesheet>
There are several ways you could look at this. For example, you could avoid sorting altogether - esp. if you only have 3 possible product types - by applying templates to each type in turn.
Here's a simplified example:
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:template match="/product">
<table>
<tr>
<th>Product Type</th>
<th>Quantity</th>
</tr>
<xsl:apply-templates select="months[Type='51']"/>
<xsl:apply-templates select="months[Type='00']"/>
<xsl:apply-templates select="months[Type='05']"/>
</table>
</xsl:template>
<xsl:template match="months">
<tr>
<td>
<xsl:choose>
<xsl:when test="Type='51'">Business Loan – General</xsl:when>
<xsl:when test="Type='05'">Personal Loan</xsl:when>
<xsl:when test="Type='00'">Other</xsl:when>
</xsl:choose>
</td>
<td>
<xsl:value-of select="Number"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
Another option is to sort by custom order - again, using the codes in their intended order:
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:template match="/product">
<table>
<tr>
<th>Product Type</th>
<th>Quantity</th>
</tr>
<xsl:variable name="sort-order">|51|00|05|</xsl:variable>
<xsl:for-each select="months">
<xsl:sort select="string-length(substring-before($sort-order, concat('|', Type, '|')))" data-type="number"/>
<tr>
<td>
<xsl:choose>
<xsl:when test="Type='51'">Business Loan – General</xsl:when>
<xsl:when test="Type='05'">Personal Loan</xsl:when>
<xsl:when test="Type='00'">Other</xsl:when>
</xsl:choose>
</td>
<td>
<xsl:value-of select="Number"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

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.

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()" />

XSLT - Adding Elements dynamically

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.