Abstract
This specification defines an extension of XSL Transformations (XSLT) . It describes a set of XSLT extension elements and functions, designed to implement functionality that is usually provided by traditional report generation tools. With report generation extensions, XSLT may be used to implement some important classes of document transformations.
Status of this document
This is the first working draft of the specification. It was developed as part of Unicorn XML Toolkit project.
Comments on this specification may be sent to [email protected]
Table of contents
1 Introduction
This specification defines an extension of XSL Transformations (XSLT). It describes a set of XSLT extension elements and functions, designed to implement in XSLT features usually provided by traditional report generation tools. These elements and functions belong to a specific namespace and are integrated into XSLT transformation environment using the extension mechanism specified in W3C XSLT recommendation.
Extension elements defined by this specification may be embedded in XSLT stylesheets and intermixed with the standard XSLT instructions. The special version of XSLT processor is required to process such stylesheets. When an instruction corresponding to the report generation extension element is encountered, it is instantiated as described in this specification.
This specification also defines the set of extension functions designed to access results of report generation procedures. These functions may be used in XSLT expressions and attribute value templates in all contexts where functions are allowed. Values returned by data access extension functions may be inserted into generated content or used as intermediate parameters during the transformation.
2 Extension Namespace
The extension namespace assigned to all XSLT report generation extension elements and functions is:
https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0
In this specification, the prefix rpt: is used for referring to report extension elements and functions. Authors of XSLT stylesheets are free to use any other prefix, provided that the appropriate namespace declarations are supplied.
3 Counters
In many cases it is needed to calculate various cumulative values based on the content of several document elements. For instance, given the source document fragment
<order> <item> ... <price>Price-1</price> </item> ... <item> ... <price>Price-N</price> </item> </order> ... <order> <item> ... <price>Price-1</price> </item> ... <item> ... <price>Price-N</price> </item> </order>
it might be required to calculate total amount for each order and build the result document that looks like
<order> ... <total>Total-1</total> </order> ... <order> ... <total>Total-N</total> </order>
XSLT 1.0 specification provides no means for the adequate implementation of this functionality. To solve the problem, the extension element rpt:counter was introduced.
There are three forms of the rpt:counter element:
The first form is used to create a new counter. The required attribute name is used to specify the counter name that may be an arbitrary string. The required attribute initialize contains an expression that is used to specify the initial counter value. When the rpt:counter element of this form is instantiated, the corresponding expression is evaluated, the result is converted to number using the XPath number() function, this number is assigned to the counter as the initial value.
The counter thus created is available while instantiation of the parent element of the corresponding rpt:counter is in progress. If needed, this counter may be accessed from the content of other template rules activated in course of instantiation of the parent element of rpt:counter. Therefore, visibility rules for counters are not the same as visibility rules for standard XSLT variables.
Counter names need not to be unique. The newly defined counter shadows counters with the same name that could be created before the instatiation of the parent element of rpt:counter was started. It is an error, however, if two sibling rpt:counter elements define counters with the same name.
The second form is used to increment a new counter. The required attribute name is used to specify the counter name. It is an error if no counter with this name is available. The required attribute increment contains an expression that is used to specify the counter value increment. When the rpt:counter element of this form is instantiated, the corresponding expression is evaluated, the result is converted to number using the XPath number() function, this number is added to the previous counter value.
The third form is used to output the current value of the counter to the result document. The required attribute name is used to specify the counter name. It is an error if no counter with this name is available. When the rpt:counter element of this form is instantiated, the corresponding counter value is converted to string using the XPath string() function, the result is written to the result document as piece of character data.
The alternative method to access current counter value is implemented using the extension function rpt:counter(). This function has the following syntax:
The required function argument is interpreted as the counter name. It is an error if no counter with this name is available. The function returns the current counter value as the result.
4 Groups
Many applications require elements from a source document being grouped according to certain criteria. For instance, given the source document fragment
<record> <category>Category-1</category> <product>...</product> <record> ... <record> <category>Category-N</category> <product>...</product> <record>
it might be required to group all products by category, producing the result document that looks like
<category name="Category-1"> <product>...</product> ... <product>...</product> </category> ... <category name="Category-N"> <product>...</product> ... <product>...</product> </category>
Unfortunately, XSLT 1.0 specification provides no adequate means to express this important class of transformations. To solve the problem, the extension element rpt:group was introduced. In many aspects rpt:group is similar to the standard xsl:for-each. Both elements implement iterative processing of nodes that belong to some node list. The difference is that, while xsl:for-each is simply processing all nodes from a list, rpt:group first divides nodes of the list into groups based on the certain criteria, and processes the entire group at each iteration. The individual processing of nodes from each group is normally performed by embedded rpt:group elements. Therefore, each top-level rpt:group element may contain embedded rpt:group elements, with the arbitrary number of levels of embedding. Top-level rpt:group elements must have the select attribute to specify the source node list, while embedded rpt:group elements form their node lists from groups produced by their rpt:group parents.
For example, the transformation described above may be expressed using the following stylesheet fragment with two embedded rpt:group elements:
<rpt:group select="record" key="category"> <category name="{category}"> <rpt:group> <xsl:copy-of select="product"/> </rpt:group> </category> </rpt:group>
The select attribute of the outermost rpt:group is used to specify the source node list - all record elements in this example, while the key attribute defines the grouping criteria - in this example, the string value of the category child element. When the outermost rpt:group element is instantiated, the appropriate set of groups is formed and the element content is instantiated, once for each group. In the example above, for each group a distinct category element is created in the result document; the name attribute of this element is assigned the corresponding category value.
The innermost rpt:group element is responsible for generation of the content of each result category element. When this innermost rpt:group is instantiated, the corresponding group is used as the source node list. If the key attribute is omitted (as in the example above), no further grouping is provided, and the content of rpt:group is repeatedly instantiated, once for each node in the group.
If the key attribute is specified for the innermost rpt:group element, the further grouping is applied to nodes that belong to the source group, and the content is repeatedly instantiated once for each result group. Examples of such multi-level group processing can be found in the appendix D Examples of this document.
There are two forms of the rpt:group element:
The first form corresponds to the top-level group element. The second form corresponds to the embedded group element.
Following is the formal definition of rpt:group processing semantics.
Definition. Given the XPath expression context C, node list L, node N that is member of L and expression E, we will say that the string value V is value of E on N if V is equal to the result of evaluation of E, converted to string using XPath string() function, where E was evaluated with N as the context node, position of N in L as the context position, size of L as the context size, and other context components taken from C.
Definition. Furthermore, we will say that N is a break node in L according to condition E if either N is the first element of L or the value of E on N is different from the value of E on the node that immediately precedes N in L. We will also say that the node list G is the group in L started by the break node N if G contains N and all nodes from L located between N and the next break node (or end of L if there is no next break node), and the ordering of elements in G is the same as in L.
When instantiated, each rpt:group element establishes a new group context. The extended XSLT processor keeps the stack of active group contexts. When instantiation of rpt:group element is started, the newly created group context is placed on the top of the stack and becomes the current group context. When instantiation of rpt:group is finished, the corresponding group context is removed from the top of the stack, and the new topmost group context on the stack (if any) becomes the current group context.
Each group context contains the following components:
- Source node list
- Result node list
- Current node
- Current group
When the new group context is created, these components are initialized as follows.
If the select attribute on the corresponding rpt:group element is specified, the corresponding expression is evaluated according to the common XSLT rules. The result of the evaluation should be of node-set type; otherwise the error must be signaled. The source node list of the group context contains all nodes from this node-set in document order if rpt:group contains no rpt:sort children, and in the sort order specified by rpt:sort children otherwise. If the select expression evaluates to an empty node set, then instantiation of rpt:group is successfully finished at this point, the new group context is not created and content of rpt:group is not instantiated.
If the select attribute on the corresponding rpt:group element is not specified, there must be the current group context defined (i.e., the group context stack must be not empty), otherwise the error must be signaled. The source node list of the newly created group context contains all nodes from the current group of the current group context in the same order if the corresponding rpt:group contains no rpt:sort children, and in the sort order specified by rpt:sort children otherwise.
If the key attribute on the corresponding rpt:group element is specified, the attribute value must be an expression. The result node list of the newly created group context is initialized to the list of all break nodes in the source node list of this group context, in the same order, according to the condition specified by this expression.
If the key attribute on the corresponding rpt:group element is not specified, the result node list of the newly created group context is equal to the source node list of this group context.
The current node of the newly created group context is initialized to the first node of the result node list.
If the key attribute on the corresponding rpt:group element is specified, the current group of the group context is defined as group in the source node list started with the current node of the group context.
If the key attribute on the corresponding rpt:group element is not specified, the current group of the group context is defined as a node list containing just the single current node of the group context.
When the new group context is created and becomes the current group context, the instantiation of rpt:group continued by iterative instantiation of rpt:group content. During the content instantiation, the result node list of the current group context is used as the current node list, and the current node of the current group context is used as the current node. When the iteration is finished, the new current node of the current group context is set to the node that immediately follows the previous current node of the current group context in the group context result node list. The current group of the current group context is assigned as specified above.
When the end of the result node list is reached, instantiation of rpt:group is finished. The corresponding group context is removed from the top of the stack.
The rpt:group may contain optional rpt:sort children. When rpt:sort elements are specified, they must be the first children of rpt:group. When the new group context is created, and the source node list of the group context is constructed, rpt:sort children of the corresponding rpt:group element are used to determine the sort order of nodes in the source node list.
The rpt:sort element has the following syntax:
The semantics is identical to the semantics of the standard XSLT xsl:sort element.
The extension function rpt:group() returns a node-list corresponding to the current group. This function has the following syntax:
It is an error if this function is called outside any group context.
5 Notation
The specification of each report generation extension element contains a summary of its syntax. The notation is similar to that used in XSL Transformations (XSLT) Version 1.0 recommendation.
The names of required attributes are given in bold. Strings that occur in place of attribute values specify the value type of those attributes. Strings surrounded by curly braces indicate that the corresponding attribute values are interpreted as attribute value templates. Elements allowed not to be empty contain comments specifying the allowed content.
If an extension element allows several forms that have different semantics, these forms are described separately.
A References
B Element Syntax Summary
C Reference Implementation
The software product developed by Unicorn Enterprises SA, Unicorn XSLT Processor, serves as the reference implementation for this specification.
This product is available at https://www.unicorn-enterprises.com .
D Examples
This section contains XSLT stylesheets that demonstrate usage of extension elements defined in this specification. These examples can be also found in the distribution of Unicorn XSLT Processor available at https://www.unicorn-enterprises.com .
All examples in this section are designed to process source documents having the following structure:
<root> <rec><a>key-1</a><b>key-2</b><x>data-1</x><y>data-2</y><z>data-3</z></rec> <rec><a>.....</a><b>.....</b><x>......</x><y>......</y><z>......</z></rec> <rec><a>.....</a><b>.....</b><x>......</x><y>......</y><z>......</z></rec> .......... </root>
This document provides the simulation for data that could be obtained, for instance, as the result of a single database query. Each <rec> element represents a single record from the query result set, elements <a> and <b> contain values of columns that will be used as group keys, and elements <x>, <y> and <z> contain values of data columns. Sample stylesheets from this section change the structure of the source document based on different grouping criteria.
Following is a full example of a source XML document:
<?xml version='1.0'?> <root> <rec><a>A100</a><b>B100</b><x>X100</x><y>Y100</y><z>Z100</z></rec> <rec><a>A100</a><b>B100</b><x>X100</x><y>Y100</y><z>Z200</z></rec> <rec><a>A100</a><b>B100</b><x>X100</x><y>Y100</y><z>Z300</z></rec> <rec><a>A100</a><b>B200</b><x>X100</x><y>Y200</y><z>Z100</z></rec> <rec><a>A100</a><b>B200</b><x>X100</x><y>Y200</y><z>Z200</z></rec> <rec><a>A100</a><b>B200</b><x>X100</x><y>Y200</y><z>Z300</z></rec> <rec><a>A100</a><b>B300</b><x>X100</x><y>Y300</y><z>Z100</z></rec> <rec><a>A100</a><b>B300</b><x>X100</x><y>Y300</y><z>Z200</z></rec> <rec><a>A100</a><b>B300</b><x>X100</x><y>Y300</y><z>Z300</z></rec> <rec><a>A200</a><b>B100</b><x>X200</x><y>Y100</y><z>Z100</z></rec> <rec><a>A200</a><b>B100</b><x>X200</x><y>Y100</y><z>Z200</z></rec> <rec><a>A200</a><b>B100</b><x>X200</x><y>Y100</y><z>Z300</z></rec> <rec><a>A200</a><b>B200</b><x>X200</x><y>Y200</y><z>Z100</z></rec> <rec><a>A200</a><b>B200</b><x>X200</x><y>Y200</y><z>Z200</z></rec> <rec><a>A200</a><b>B200</b><x>X200</x><y>Y200</y><z>Z300</z></rec> <rec><a>A200</a><b>B300</b><x>X200</x><y>Y300</y><z>Z100</z></rec> <rec><a>A200</a><b>B300</b><x>X200</x><y>Y300</y><z>Z200</z></rec> <rec><a>A200</a><b>B300</b><x>X200</x><y>Y300</y><z>Z300</z></rec> <rec><a>A300</a><b>B100</b><x>X300</x><y>Y100</y><z>Z100</z></rec> <rec><a>A300</a><b>B100</b><x>X300</x><y>Y100</y><z>Z200</z></rec> <rec><a>A300</a><b>B100</b><x>X300</x><y>Y100</y><z>Z300</z></rec> <rec><a>A300</a><b>B200</b><x>X300</x><y>Y200</y><z>Z100</z></rec> <rec><a>A300</a><b>B200</b><x>X300</x><y>Y200</y><z>Z200</z></rec> <rec><a>A300</a><b>B200</b><x>X300</x><y>Y200</y><z>Z300</z></rec> <rec><a>A300</a><b>B300</b><x>X300</x><y>Y300</y><z>Z100</z></rec> <rec><a>A300</a><b>B300</b><x>X300</x><y>Y300</y><z>Z200</z></rec> <rec><a>A300</a><b>B300</b><x>X300</x><y>Y300</y><z>Z300</z></rec> </root>
Example 1.
This example demonstrates the stylesheet that groups data from the source document using two levels of grouping.
At the first level, records with the same value of <a> element are grouped. In the result document, each group will be contained within <a> element having the key attribute value equal to the value of the group key.
At the second level, records with the same value of <b> element are grouped. In the result document, each group will be contained within <b> element having the key attribute value equal to the value of the group key.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rpt="https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0" extension-element-prefixes="rpt"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="root"> <root> <rpt:group select="rec" key="a"> <a key="{a}"> <rpt:group key="b"> <b key="{b}"> <rpt:group> <rec> <xsl:copy-of select="x"/> <xsl:copy-of select="y"/> <xsl:copy-of select="z"/> </rec> </rpt:group> </b> </rpt:group> </a> </rpt:group> </root> </xsl:template> </xsl:stylesheet>
The result document produced by this stylesheet has the following structure:
<root> <a key="key-1"> <b key="key-2"> <rec> <x>data-1</x> <y>data-2</y> <z>data-3</z> </rec> <rec> <x>...</x> <y>...</y> <z>...</z> </rec> ..... </b> <b key="..."> ..... </b> ..... </a> <a key="..."> <b key="..."> ..... </b> ..... </a> ..... </root>
Example 2.
This example demonstrates the stylesheet that groups data from the source document using two levels of grouping. This example is similar to previous one, but rpt:sort extension element is used here to apply grouping criteria in the reverse order.
At the first level, records with the same value of <b> element are grouped. In the result document, each group will be contained within <b> element having the key attribute value equal to the value of the group key.
At the second level, records with the same value of <a> element are grouped. In the result document, each group will be contained within <a> element having the key attribute value equal to the value of the group key.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rpt="https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0" extension-element-prefixes="rpt"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="root"> <root> <rpt:group select="rec" key="b"> <rpt:sort select="b"/> <rpt:sort select="a"/> <b key="{b}"> <rpt:group key="a"> <a key="{a}"> <rpt:group> <rec> <xsl:copy-of select="x"/> <xsl:copy-of select="y"/> <xsl:copy-of select="z"/> </rec> </rpt:group> </a> </rpt:group> </b> </rpt:group> </root> </xsl:template> </xsl:stylesheet>
The result document produced by this stylesheet has the following structure:
<root> <b key="key-1"> <a key="key-2"> <rec> <x>data-1</x> <y>data-2</y> <z>data-3</z> </rec> <rec> <x>...</x> <y>...</y> <z>...</z> </rec> ..... </a> <a key="..."> ..... </a> ..... </b> <b key="..."> <a key="..."> ..... </a> ..... </b> ..... </root>
Example 3.
This example demonstrates the stylesheet that provides exactly the same processing as the stylesheet from the previous example. Different arrangement for rpt:sort extension elements is used here.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rpt="https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0" extension-element-prefixes="rpt"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="root"> <root> <rpt:group select="rec" key="b"> <rpt:sort select="b"/> <b key="{b}"> <rpt:group key="a"> <a key="{a}"> <rpt:group> <rpt:sort select="a"/> <rec> <xsl:copy-of select="x"/> <xsl:copy-of select="y"/> <xsl:copy-of select="z"/> </rec> </rpt:group> </a> </rpt:group> </b> </rpt:group> </root> </xsl:template> </xsl:stylesheet>
Example 4.
The following stylesheet generates embedded groups based on the number of elements per group rather than the content of each element. It tries to place 2 elements in each internal group and 6 elements (i.e., 3 internal groups) in each external group.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rpt="https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0" extension-element-prefixes="rpt"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="root"> <root> <rpt:group select="rec" key="floor((position()-1) div 6)"> <level1> <rpt:group key="floor((position()-1) div 2)"> <level2> <rpt:group> <rec> <xsl:copy-of select="x"/> <xsl:copy-of select="y"/> <xsl:copy-of select="z"/> </rec> </rpt:group> </level2> </rpt:group> </level1> </rpt:group> </root> </xsl:template> </xsl:stylesheet>
Example 5.
Following is the stylesheet that performs the same processing as the stylesheet from the first example of this section, adding various counters at each group level.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rpt="https://www.unicorn-enterprises.com/XSLT/Extensions/Reports/1.0" extension-element-prefixes="rpt"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="root"> <root> <rpt:counter name="x" initialize="0"/> <rpt:group select="rec" key="a"> <rpt:counter name="x" increment="100"/> <a key="{a}" counter="{rpt:counter('x')}"> <rpt:counter name="x" initialize="0"/> <rpt:group key="b"> <rpt:counter name="x" increment="10"/> <b key="{b}" counter="{rpt:counter('x')}"> <rpt:counter name="x" initialize="0"/> <rpt:group> <rpt:counter name="x" increment="1"/> <rec> <counter><rpt:counter name="x"/></counter> <xsl:copy-of select="x"/> <xsl:copy-of select="y"/> <xsl:copy-of select="z"/> </rec> </rpt:group> </b> </rpt:group> </a> </rpt:group> </root> </xsl:template> </xsl:stylesheet>