我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?
Posted
技术标签:
【中文标题】我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?【英文标题】:What XSLT:FO layout should I use for my multi-page iterative PDF converter?我的多页迭代 PDF 转换器应该使用什么 XSLT:FO 布局? 【发布时间】:2021-12-18 15:13:11 【问题描述】:我正在使用 Apache 的 XML Graphics FOP 2.6 创建一个至少包含 2 页的 PDF 文档。没有最大页数。
每个页面都使用完全相同的标题。
第 1 页(见附件)
需要包含带有总框数、总框数中包含的项目总数以及 1、2 或 3 行的表格的文本。在每一行,都有一张盒子的照片,它的名字和它包含的物品数量。至少会有一个盒子包含至少一件物品。
第 2 页
仅当有 4 个或更多框且包含第 1 页表格的第 4、5、6、7、8 和 9 行(共 6 行)时才存在。如果超过 9 个(= 3 + 6 X 1) 框,需要有一个新页面,其中包含第 2 页的布局,但包含框 10、...、15 的行。将遵循此模式,直到最后一个框。
第 3 页
将包含第一个盒子的照片、名称和物品数量。下面会有一个表格,其中一行包含列名,最多 4 行,对应于第一个框中的前 4 个项目。
第 4 页
如果第一个框的项目超过 4 个,则会出现新页面,称为第 4 页,仅包含表格的延续,包括具有列名的行。它将总共有 7 个项目,如果第一个框有超过 11 个(= 4 + 7 X 1)项目,则后面是另一页。以此类推,直到所有项目结束。
第5页(不附上以免重复)
将遵循与第 3 页相同的逻辑,但在这种情况下,对于第二个框,如果有第二个框。以此类推,直到所有盒子结束。
任何人都可以帮助我了解如何根据我的要求在 XSL 文件中构建 layout-master-set 吗?我不是在寻找完整的解决方案。仅适用于一般布局结构。答案不必太详细或太定制以满足我的需求。如果需要,我可以调整它。
<xsl:template match="/doc">
<xsl:variable name="Logo"><xsl:value-of select="Logo"/></xsl:variable>
...
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xmlgraphics.apache.org/fop/extensions"
font-family="Nexus Sans Pro" font-weight="normal">
<fo:layout-master-set>
<fo:simple-page-master master-name="Boxes-A4" page- page-
margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
<fo:region-body region-name="xsl-region-body"/>
<fo:region-before region-name="xsl-region-before" extent="70mm"/>
</fo:simple-page-master>
<fo:simple-page-master master-name="Items-A4" page- page-
margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
<fo:region-body region-name="xsl-region-body"/>
<fo:region-before region-name="xsl-region-before" extent="70mm"/>
</fo:simple-page-master>
</fo:layout-master-set>
我已经尝试了上面的代码(使用 xsl:stylesheet version="1.0"),但未能正确布局。即使我用 break-before="page" aka page-break-before="always" 标记了适当的行,表格中不适合页面的行也不会显示在下一页上。 作为辅助,我可以将后端数据结构中的索引插入到库使用的参数中,以便知道每个元素的索引。
非常感谢。
Page 1
Page 2
Page 3
Page 4
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/doc">
<fo:root font-family="Nexus Sans Pro" font-weight="normal">
<fo:layout-master-set>
<fo:simple-page-master master-name="Boxes-A4" page- page-
margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
<fo:region-body region-name="xsl-region-body"/>
<fo:region-before region-name="xsl-region-before" extent="70mm"/>
</fo:simple-page-master>
<fo:simple-page-master master-name="Items-A4" page- page-
margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
<fo:region-body region-name="xsl-region-body"/>
<fo:region-before region-name="xsl-region-before" extent="70mm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<xsl:call-template name="boxes-template"/>
<xsl:call-template name="items-template"/>
</fo:root>
</xsl:template>
<xsl:template name="boxes-template">
<fo:page-sequence master-reference="Boxes-A4" font-family="Nexus Sans Pro" font-weight="normal">
<fo:static-content flow-name="xsl-region-before">
<fo:block-container border-bottom-
border-bottom-style="solid"
border-bottom-color="rgb(220,220,220)"
position="absolute" top="1mm" left="5mm" right="5mm" >
<xsl:variable name="Logo" select="Logo"/>
<fo:block-container
background-image="url($Logo)"
top="5mm" left="5mm"
background-repeat="no-repeat"
fox:background-image- fox:background-image-
absolute-position="absolute">
<fo:block/>
</fo:block-container>
<xsl:variable name="Stamp" select="Stamp"/>
<fo:block-container
background-image="url($Stamp)"
top="5mm" right="5mm"
background-repeat="no-repeat"
fox:background-image- fox:background-image-
absolute-position="absolute">
<fo:block/>
</fo:block-container>
<fo:block-container position="absolute" top="8mm" left="80mm" >
<fo:table table-layout="fixed" >
<fo:table-column column-/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro Bold"
text-align="center" font-size="32pt"> Header Text
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell display-align="after" >
<fo:block font-family="Nexus Sans Pro"
display-align="after" text-align="center" font-size="32pt"
color="rgb(233,113,28)">
<xsl:value-of select="user"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
<fo:block-container position="absolute" top="48mm" left="77mm" >
<fo:table table-layout="fixed" >
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro" font-weight="normal" font-size="15pt"
color="rgb(128,128,128)">
From:
<xsl:value-of select="fromDate"/>
</fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro" font-weight="normal" font-size="15pt"
color="rgb(128,128,128)">
To:
<xsl:value-of select="toDate"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
</fo:block-container>
</fo:static-content>
<fo:flow flow-name="xsl-region-body">
<fo:block-container position="absolute" top="75mm" left="110mm" >
<fo:table table-layout="fixed" >
<fo:table-column column-number="1" column-/>
<fo:table-column column-number="2" column-/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block
font-family="Nexus Sans Pro Bold"
font-size="15pt" color="rgb(35,31,32)">
Total number of boxes:
</fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro Bold"
font-size="15pt" color="rgb(35,31,32)">
<xsl:value-of select="totalNumberOfBoxes"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro Bold"
font-size="15pt" color="rgb(35,31,32)">
Total number of items:
</fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block font-family="Nexus Sans Pro Bold"
font-size="15pt" color="rgb(35,31,32)">
<xsl:value-of select="totalNumberOfItems"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
<xsl:for-each select="/doc/box">
<xsl:variable name="boxImageURL" select="boxImageURL"/>
<fo:block-container position="absolute" top="105mm" left="30mm" >
<fo:table table-layout="fixed" >
<fo:table-column column-number="1" column-/>
<fo:table-column column-number="2" column-/>
<fo:table-column column-number="3" column-/>
<fo:table-body>
<fo:table-row page-break-inside="auto"
border-top-
border-top-style="solid"
border-top-color="rgb(220,220,220)"
margin-bottom="2mm"
>
<fo:table-cell>
<xsl:if test="$boxImageURL != 'null'">
<fo:block-container
background-image="url($boxImageURL)"
top="110mm" right="15mm"
background-repeat="no-repeat" margin-top="2mm"
fox:background-image- fox:background-image->
<fo:block/>
</fo:block-container>
</xsl:if>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="boxTitle"/>
</fo:block>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="numberOfItems"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</xsl:template>
<xsl:template name="items-template">
<xsl:variable name="Logo"><xsl:value-of select="Logo"/></xsl:variable>
<xsl:variable name="Stamp"><xsl:value-of select="Stamp"/></xsl:variable>
<xsl:variable name="user"><xsl:value-of select="user"/></xsl:variable>
<xsl:variable name="fromDate"><xsl:value-of select="fromDate"/></xsl:variable>
<xsl:variable name="toDate"><xsl:value-of select="toDate"/></xsl:variable>
<xsl:for-each select="/doc/box">
<xsl:variable name="boxImageURL" select="boxImageURL"/>
<fo:page-sequence master-reference="Items-A4" font-family="Nexus Sans Pro" font-weight="normal">
<fo:static-content flow-name="xsl-region-before">
... Same header as on the Box-A4
</fo:static-content>
<fo:flow flow-name="xsl-region-body">
<fo:block-container page-break-before="always"
position="absolute" top="70mm" left="30mm" >
<fo:table page-break-before="always" table-layout="fixed" >
<fo:table-column column-number="1" column-/>
<fo:table-column column-number="2" column-/>
<fo:table-body>
<fo:table-row margin-bottom="2mm"
>
<fo:table-cell>
<xsl:if test="$boxImageURL != 'null'">
<fo:block-container
background-image="url($boxImageURL)"
top="110mm" right="15mm"
background-repeat="no-repeat" margin-top="2mm"
fox:background-image- fox:background-image->
<fo:block/>
</fo:block-container>
</xsl:if>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block margin-top="4mm" text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="boxTitle"/>
<fo:block margin-top="4mm" text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="numberOfThings"/> things
</fo:block>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
<fo:block-container position="absolute" top="100mm" left="30mm" >
<fo:table page-break-before="always" table-layout="fixed" >
<fo:table-column column-number="1" column-/>
<fo:table-column column-number="2" column-/>
<fo:table-column column-number="3" column-/>
<fo:table-body>
<fo:table-row border-top-
border-top-style="solid"
border-top-color="rgb(220,220,220)"
margin-bottom="2mm"
>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
Item title
</fo:block>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
Revision
</fo:block>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
Date completed
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block-container>
<fo:block-container position="absolute" top="125mm" left="30mm" >
<fo:table page-break-before="always" table-layout="fixed" >
<fo:table-column column-number="1" column-/>
<fo:table-column column-number="2" column-/>
<fo:table-column column-number="3" column-/>
<fo:table-body>
<xsl:for-each select="items/item">
<fo:table-row keep-together.within-page="always"
break-after="page"
border-top-
border-top-style="solid"
border-top-color="rgb(220,220,220)"
margin-bottom="2mm"
>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="itemTitle"/>
</fo:block>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="revision"/>
</fo:block>
</fo:table-cell>
<fo:table-cell display-align="center" >
<fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
<xsl:value-of select="dateCompleted"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
</fo:block-container>
</fo:flow>
</fo:page-sequence>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
我的简化parameters.xml(少了几行):
<doc>
<Logo>ELogo.jpg</Logo>
<Stamp>Stamp.jpg</Stamp>
<backgroundImageURL>bkg.jpg</backgroundImageURL>
<user>John Richard Edgar Bowens-Robins III</user>
<fromDate>1st March 2021</fromDate>
<toDate>31 November 2021</toDate>
<totalNumberOfBoxes>5</totalNumberOfBoxes>
<totalNumberOfItems>19</totalNumberOfItems>
<box>
<boxImageURL>box1.jpg</boxImageURL>
<boxTitle>JREBR's Box 1</boxTitle>
<items>
<item>
<itemTitle>The Nice Item 11</itemTitle>
</item>
<item>
<itemTitle>The Nice Item 12</itemTitle>
</item>
<item>
<itemTitle>The Nice Item 13</itemTitle>
</item>
</items>
</box>
<box>
<boxImageURL>box2.jpg</boxImageURL>
<boxTitle>JREBR's Box 2</boxTitle>
<items>
<item>
<itemTitle>The Nice Item 21</itemTitle>
</item>
<item>
<itemTitle>The Nice Item 22</itemTitle>
</item>
<item>
<itemTitle>The Nice Item 23</itemTitle>
</item>
</items>
</box>
</doc>
【问题讨论】:
【参考方案1】:你真正所说的“第 1 页”、“第 2 页”……你的真正意思是:
第 1 部分是所有框。这可能是 1 到 x 页,具体取决于盒子的数量。所有的页面模板都是一样的,所以这是一个页面序列和一个页面母版。
第 2 节到第 n 节是列出所有项目的每个框的页面序列。所有这些页面序列都是相同的(一个简单的页面母版)。
您没有解释这些标题中的照片 1 和照片 2 是什么,但我认为它们对于每个页面都是相同的。如果这不是真的并且“盒子”和“项目”不同,那么您可能有不同的标题。
所以你只有两个简单的页面主控,一个叫做“盒子”,一个叫做“项目”。您只需创建引用框的简单页面主控的“框”页面序列。您没有提供示例 XML,所以为了简单起见,我们只是说它看起来像这样:
<order>
<box>
<name>Box 1</name>
<items>
<item>Item 1:1</item>
<item>Item 1:2</item>
<item>Item 1:3</item>
</items>
</box>
<box>
<name>Box 2</name>
<items>
<item>Item 2:1</item>
<item>Item 2:2</item>
<item>Item 2:3</item>
</items>
</box>
<box>
<name>Box 3</name>
<items>
<item>Item 3:1</item>
<item>Item 3:2</item>
<item>Item 3:3</item>
</items>
</box>
</order>
然后这个简单的 XSL 会为盒子和盒子中的项目抛出页面:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="boxes" page- page->
<fo:region-body region-name="body" margin-top="0.5in" margin-bottom="0.5in" margin-left="0.5in" margin-right="0.5in"/>
</fo:simple-page-master>
<fo:simple-page-master master-name="items" page- page->
<fo:region-body region-name="body" margin-top="0.5in" margin-bottom="0.5in" margin-left="0.5in" margin-right="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<xsl:call-template name="boxes"/>
<xsl:call-template name="items"/>
</fo:root>
</xsl:template>
<xsl:template name="boxes">
<fo:page-sequence master-reference="boxes">
<fo:flow flow-name="body">
<!-- Output the table of boxes -->
<xsl:for-each select="/order/box">
<fo:block>
<xsl:value-of select="name"/>
</fo:block>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</xsl:template>
<xsl:template name="items">
<xsl:for-each select="/order/box">
<fo:page-sequence master-reference="items">
<fo:flow flow-name="body">
<xsl:for-each select="items/item">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
</fo:flow>
</fo:page-sequence>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
这会导致:
将此与@Tony Graham 关于制作表格的建议结合起来,您就拥有了整个布局。如果您想知道自动页面流是如何工作的,这里是上面的确切示例,其中有几个框和一个框(框 4)有更多项目......我只更改了 fo:block 字体大小以导致分页。
【讨论】:
非常感谢布朗先生您非常全面的回复。我在问题中添加了一些代码。每个页面的标题都是相同的。不幸的是,我在 2 个模板中的每一个中都复制了它的代码。我认为没有办法重用它。即使我使用了break-before =“page”,我的第一页不适合的行也不会移动到下一页,但会在第一页上丢失。可能是因为经常使用这样的定位标记:使用fo:table
,并为每个fo:table-row
指定一个合适的值height
(https://www.w3.org/TR/xsl11/#height),以获得所需的每页行数。
您可能还需要指定keep-together.within-page="always"
以避免破坏表格行。 FOP 在fo:table-row
(或fo:table-cell
)上可能支持也可能不支持。
【讨论】:
非常感谢您的建议。我将这些添加到表格行中。以上是关于我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?的主要内容,如果未能解决你的问题,请参考以下文章
ImageMagick或GhostScript:将多页TIFF转换为多页PDF