在xsl-fo中使用for-each-group技术

时间:2014-02-03 04:40:44

标签: xslt xsl-fo

如果是的话,我可以使用for-each-group然后可以有人给我看一个例子。我正在尝试使用xsl-fo

生成pdf

我正在尝试使用表格输出它。请给我一个使用分组技术和添加值的示例。但是,输出应显示在表格中。

xml文件:

<?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>

而不是xsl:for-each如果我尝试使用xsl:for-each-group,则会抛出错误,指出xsl:for-each-group不能位于该位置。

我目前的输出:

enter image description here

但是我想要的输出如下图所示: enter image description here

感谢

1 个答案:

答案 0 :(得分:2)

如评论中所述,如果您使用的是XSLT 1.0,则 xsl:for-each-group 命令不可用。在XSLT 1.0中,分组通常使用名为Muenchian Grouping的技术完成。值得一读并理解它,因为一旦你理解它,它在XSLT 1.0中是一个非常有用的技术。

在Muenchian Grouping中,您首先要定义一个用于查找组中元素的键。在您的情况下,您按类别和代码进行分组,因此它们的键将如下所示:

<xsl:key name="GroupByCategoryCode" match="Book" use="concat(Category, '|', @code)"/>

现在,在XSLT 2.0中,你可能会写这个......

<xsl:for-each-group select="Library/Book" group-by="concat(Category, '|', @code)">

然而,在XSLT 1.0中你必须写这个(我添加了很多缩进以提高可读性)

<xsl:for-each 
     select="Library/Book
             [
               generate-id() = 
               generate-id
               (
                    key('GroupByCategoryCode', concat(Category, '|', @code))[1]
               )
             ]">

(在这里使用xsl:apply-templates也可以。)

这样做的目的是查看所有 Book 元素,并检查其“Category”和“Code”的组合,以查看它是否是键中该元素的第一次出现。实际上,它将选择“类别”和“代码”的明显出现。

在XSLT 2.0中,您将使用“current-group”然后迭代组中的所有元素。在XSLT 1.0中,您只需使用密钥

<xsl:for-each select="key('GroupByCategoryCode', concat(Category, '|', @code))">

虽然在这种特殊情况下,你不需要为每个人做一个,因为你只是在每个组中总结数量。

<xsl:value-of 
     select="sum(key('GroupByCategoryCode', concat(Category, '|', @code))/Quantity)"/>

或者,为了提高可读性... ...

  <xsl:variable name="currentGroup" select="key('GroupByCategoryCode', concat(Category, '|', @code))"/>
  <xsl:value-of select="sum($currentGroup/Quantity)"/>

试试这个XSLT。为了简单起见(因为我不知道xsl-fo),我正在输出HTML来演示原理。您需要做的就是用他们的xsl-fo等价物替换HTML标签

<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>

编辑:要显示“重复”一词而不是重复类别的类别,您可以将其视为按类别分组,并仅显示组中第一个的类别名称。

因此,将以下密钥添加到XSLT,以允许您按类别查找图书

<xsl:key name="GroupByCategory" match="Book" use="Category"/>

然后,您需要选择不同的类别(这应该围绕现有的 xsl:apply-templates

<xsl:for-each select="Library/Book[generate-id() = generate-id(key('GroupByCategory', Category)[1])]">

然后在匹配图书的模板中,您可以确定是否显示类别名称或“已定义”,如此

   <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>

试试这个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>