如何使用XSL-FO样式表和Apache FOP将XML文件中的不同表转换为PDF

时间:2018-11-11 18:57:57

标签: xml pdf xsl-fo apache-fop

我使用xsl样式表 ReportXHTML.xsl 将具有2个表的XML文件 ReportXHTML.xml 转换为xhtml。下一步,我构建了可比较的XSL-FO文件 ReportFO.xsl ,将其与修改后的xml文件 ReportFO.xml 和Apache FOP一起使用以获取PDF文件,但我的Java应用程序 FopReport.java 因此 ValidationException

而失败
  

“ fo:table-row”缺少子元素。所需内容模型:   (table-cell +)

寻找解决方案,我阅读了XSL-Fo规范,并通过删除名为table-rows的xsl模板中的xsl声明 for-each 来修改XSL-FO文件。我得到了PDF ReportFO.pdf 文件。

这表明名为 table-head 的xsl模板中的每个xsl语句均有效。名为 table-rows 的xsl模板与此类似,但失败了。那是我的问题。在XSL-FO文件中要修改什么?感谢您的帮助。

ReportXHTML.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="ReportXHTML.xsl" ?>
<report>
 <table>
  <tableRow>
    <tableHead>Name</tableHead>
    <tableHead>Street</tableHead>
    <tableHead>City</tableHead>
  </tableRow>
  <tableRow>
   <entry>Torsten Horn</entry>
   <entry>Hauptsr. 44</entry>
   <entry>Aachen</entry>
  </tableRow>
  <tableRow>
   <entry>Heinz Hinz</entry>
   <entry>Bahnhofstr. 22</entry>
  <entry>Hamburg</entry>
 </tableRow>
 <tableRow>
  <entry>Karl Kunz</entry>
  <entry>Königstr. 1</entry>
  <entry>Köln</entry>
 </tableRow>
</table>
<table>
 <tableRow>
  <tableHead>Name</tableHead>
  <tableHead>City</tableHead>
 </tableRow>
 <tableRow>
  <entry>Torsten Horn</entry>
  <entry>Aachen</entry>
 </tableRow>
 <tableRow>
  <entry>Heinz Hinz</entry>
  <entry>Hamburg</entry>
 </tableRow>
 <tableRow>
  <entry>Karl Kunz</entry>
  <entry>Köln</entry>
 </tableRow>
 </table>
</report>

样式表ReportXHTML.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<html>
<head>
    <title>Report</title>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
<xsl:template match="table">
    <p>
    <table border="1" cellspacing="0" cellpadding="5">
    <tr>
<xsl:for-each select="tableRow/tableHead">
    <th><xsl:apply-templates/></th>
</xsl:for-each>
    </tr>
<xsl:for-each select="tableRow">
    <tr>
    <xsl:for-each select="entry">
        <td><xsl:apply-templates/></td>
    </xsl:for-each>
    </tr>
</xsl:for-each>
    </table>
    </p>
</xsl:template>
</xsl:stylesheet>

FireFox的结果: image of result

样式表ReportFO.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xs:stylesheet version="1.0"
           xmlns:xs="http://www.w3.org/1999/XSL/Transform"
           xmlns:fo="http://www.w3.org/1999/XSL/Format">
<!-- XLS/ FO Specification see: https://www.w3.org/TR/xsl11/ -->
<!-- Attribute-Sets -->
<xs:attribute-set name="cell-style">
<xs:attribute name="border-width">0.5pt</xs:attribute>
<xs:attribute name="border-style">solid</xs:attribute>
</xs:attribute-set>
<xs:attribute-set name="block-style">
<xs:attribute name="font-size">  10pt</xs:attribute>
<xs:attribute name="line-height">15pt</xs:attribute>
<xs:attribute name="start-indent">1mm</xs:attribute>
<xs:attribute name="end-indent">  1mm</xs:attribute>
</xs:attribute-set>
<!-- Page Layout -->
<xs:template match="/">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
 <fo:simple-page-master master-name="DIN-A4"
       page-height="29.7cm" page-width="21cm"
       margin-top="2cm"     margin-bottom="1.5cm"
       margin-left="2.5cm"  margin-right="1.0cm">
    <fo:region-body
       margin-top="1.5cm" margin-bottom="1.0cm"
       margin-left="0.0cm"  margin-right="0.0cm"/>
    <fo:region-before region-name="header" extent="1.3cm"/>
    <fo:region-after  region-name="footer" extent="1.0cm"/>
 </fo:simple-page-master>
 </fo:layout-master-set>
 <fo:page-sequence master-reference="DIN-A4">
 <fo:static-content flow-name="header">
    <fo:block font-size="14pt" font-family="Helvetica" font-weight="bold" text-align="left">
       Report
    </fo:block>
 <fo:block text-align-last="justify"><fo:leader leader-pattern="rule" rule-thickness="0.5" />
 </fo:block>
 </fo:static-content>
 <fo:static-content flow-name="footer">
 <fo:block text-align-last="justify"><fo:leader leader-pattern="rule" rule-thickness="0.5" />
 </fo:block>
    <fo:block font-size="10pt" text-align="center" >
       Page <fo:page-number/> of <fo:page-number-citation ref-id="LastPage"/>
    </fo:block>
 </fo:static-content>
 <fo:flow flow-name="xsl-region-body">
    <xs:apply-templates/>
    <fo:block id="LastPage"/>
 </fo:flow>
 </fo:page-sequence>
 </fo:root>
 </xs:template>

 <!-- Table-Head -->
 <xs:template name="table-head">
 <fo:table-row>
 <xs:for-each select="tableRow/tableHead">
 <fo:table-cell xs:use-attribute-sets="cell-style">
 <fo:block xs:use-attribute-sets="block-style" text-align="center">
    <xs:apply-templates/>
 </fo:block>
 </fo:table-cell>
 </xs:for-each>
 </fo:table-row>
 </xs:template>

 <!-- Table-Rows -->
 <xs:template name="table-rows">
 <fo:table-row>
   <xs:for-each select="tableRow/entry">

     <fo:table-cell xs:use-attribute-sets="cell-style">
      <fo:block xs:use-attribute-sets="block-style">
      <xs:apply-templates/>
      </fo:block>
     </fo:table-cell>

   </xs:for-each>

 </fo:table-row>
 </xs:template>
 <!-- Table -->
 <xs:template match="report/table">
 <fo:block><fo:leader /></fo:block>
 <fo:table border-style="solid" table-layout="fixed" width="100%">
     <fo:table-header>
     <xs:call-template name="table-head"/>
     </fo:table-header>       
     <fo:table-body>
     <xs:for-each select="tableRow">
         <xs:call-template name="table-rows"/>
     </xs:for-each>
     </fo:table-body>
 </fo:table>
 </xs:template>
 </xs:stylesheet>

修改后的xml文件ReportFO.xml

<?xml version="1.0" encoding="UTF-8"?>
<report>
 <table>
   <tableRow>
     <tableHead>Name</tableHead>
 etc .....

FopReport.java

import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.*;
import org.apache.*;

// compile by: javac -classpath ".:lib/fop.jar:lib/xmlgraphics-commons-2.3.jar" FopReport.java
// run by:
// java -classpath ".:lib/fop.jar:lib/xmlgraphics-commons-2.3.jar:lib/commons-logging-1.0.4.jar:lib/avalon-framework-api-4.3.1.jar:lib/avalon-framework-impl-4.3.1.jar:lib/commons-io-1.3.1.jar:lib/batik-all-1.10.jar" FopReport ReportFO.xsl ReportFO.xml ReportFO.pdf
// see : https://xmlgraphics.apache.org/fop/2.3/embedding.html

public class FopReport
{
public static void main( String[] args ) throws Exception
{
  if( args.length != 3 ) {
 System.out.println( "Enter XSL- and XML-Inputfile, PDF-Outputfile." );
 return;
  }
  FopReport.xmlToPdfPerXsl( args[0], args[1], args[2] );
  System.out.println( args[0] + " + " + args[1] + " --> " + args[2] );
 }

 public static void xmlToPdfPerXsl( String inputXSL, String inputXML, String outputPDF ) throws Exception
 {

// Step 1: Construct a FopFactory by specifying a reference to the configuration file
// (reuse if you plan to render multiple documents!)
  FopFactory fopFactory = FopFactory.newInstance(new File("fop.xconf"));

// Step 2: Set up output stream
  OutputStream pdf = new FileOutputStream( outputPDF );

// Step 3: Construct fop with desired output format
//   Fop    fop = FopFactory.newFop( MimeConstants.MIME_PDF, pdf );
  Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, pdf);

// Step 5: Setup input and output for XSLT transformation
// Setup input stream
  Source xml = new StreamSource( inputXML );
  Source xsl = new StreamSource( inputXSL );
// Resulting SAX events (the generated FO) must be piped through to FOP
  Result sax = new SAXResult( fop.getDefaultHandler() );

// Step 4: Setup JAXP using identity transformer
//      Transformer transformer = TransformerFactory.newInstance().newTransformer( xsl );
  TransformerFactory factory = TransformerFactory.newInstance();
  Transformer transformer = factory.newTransformer( xsl ); // identity transformer

// Step 6: Start XSLT transformation and FOP processing
  transformer.transform( xml, sax );
}
}

验证异常

Nov. 11, 2018 7:34:05 NACHM. org.apache.fop.apps.FopConfParser configure
INFORMATION: Default page-height set to: 11in
Nov. 11, 2018 7:34:05 NACHM. org.apache.fop.apps.FopConfParser configure
INFORMATION: Default page-width set to: 8.26in
ERROR:  '"fo:table-row" is missing child elements. Required content model: (table-cell+) (No context info available)'
Exception in thread "main" javax.xml.transform.TransformerException: org.apache.fop.fo.ValidationException: "fo:table-row" is missing child elements. Required content model: (table- cell+) (Keine Kontextinformationen verfügbar)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:786)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:370)
at FopReport.xmlToPdfPerXsl(FopReport.java:52)
at FopReport.main(FopReport.java:22)
Caused by: org.apache.fop.fo.ValidationException: "fo:table-row" is missing child elements. Required content model: (table-cell+) (Keine Kontextinformationen verfügbar)
at org.apache.fop.events.ValidationExceptionFactory.createException(ValidationExceptionFactory.java:38)
at org.apache.fop.events.EventExceptionManager.throwException(EventExceptionManager.java:58)
at org.apache.fop.events.DefaultEventBroadcaster$1.invoke(DefaultEventBroadcaster.java:173)
at com.sun.proxy.$Proxy2.missingChildElement(Unknown Source)
at org.apache.fop.fo.FONode.missingChildElementError(FONode.java:588)
at org.apache.fop.fo.flow.table.TableRow.finalizeNode(TableRow.java:115)
at org.apache.fop.fo.FONode.endOfNode(FONode.java:330)
at org.apache.fop.fo.flow.table.TableRow.endOfNode(TableRow.java:108)
at org.apache.fop.fo.FOTreeBuilder$MainFOHandler.endElement(FOTreeBuilder.java:360)
at org.apache.fop.fo.FOTreeBuilder.endElement(FOTreeBuilder.java:190)
at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:263)
at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:557)
at jdk.translet/die.verwandlung.ReportFO.table$dash$rows()
at jdk.translet/die.verwandlung.ReportFO.template$dot$3()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.template$dot$0()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.transform()
at java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(AbstractTranslet.java:624)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:776)
... 3 more
---------
org.apache.fop.fo.ValidationException: "fo:table-row" is missing child elements. Required content model: (table-cell+) (Keine Kontextinformationen verfügbar)
at org.apache.fop.events.ValidationExceptionFactory.createException(ValidationExceptionFactory.java:38)
at org.apache.fop.events.EventExceptionManager.throwException(EventExceptionManager.java:58)
at org.apache.fop.events.DefaultEventBroadcaster$1.invoke(DefaultEventBroadcaster.java:173)
at com.sun.proxy.$Proxy2.missingChildElement(Unknown Source)
at org.apache.fop.fo.FONode.missingChildElementError(FONode.java:588)
at org.apache.fop.fo.flow.table.TableRow.finalizeNode(TableRow.java:115)
at org.apache.fop.fo.FONode.endOfNode(FONode.java:330)
at org.apache.fop.fo.flow.table.TableRow.endOfNode(TableRow.java:108)
at org.apache.fop.fo.FOTreeBuilder$MainFOHandler.endElement(FOTreeBuilder.java:360)
at org.apache.fop.fo.FOTreeBuilder.endElement(FOTreeBuilder.java:190)
at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:263)
at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.endElement(ToXMLSAXHandler.java:557)
at jdk.translet/die.verwandlung.ReportFO.table$dash$rows()
at jdk.translet/die.verwandlung.ReportFO.template$dot$3()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.template$dot$0()
at jdk.translet/die.verwandlung.ReportFO.applyTemplates()
at jdk.translet/die.verwandlung.ReportFO.transform()
at java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.transform(AbstractTranslet.java:624)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:776)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:370)
at FopReport.xmlToPdfPerXsl(FopReport.java:52)
at FopReport.main(FopReport.java:22)

文件ReportFO.pdf image of ReportFO.pdf

1 个答案:

答案 0 :(得分:0)

在这里,xs:for-each依次更改每个tableRow/entry的上下文,并调用带有当前tableRow作为上下文项的'table-rows'模板:

     <xs:for-each select="tableRow">
         <xs:call-template name="table-rows"/>
     </xs:for-each>

在这里,xs:for-each正在将上下文更改为上下文项的每个entry子级的每个tableRow子级:

 <!-- Table-Rows -->
 <xs:template name="table-rows">
 <fo:table-row>
   <xs:for-each select="tableRow/entry">

     <fo:table-cell xs:use-attribute-sets="cell-style">
      <fo:block xs:use-attribute-sets="block-style">
      <xs:apply-templates/>
      </fo:block>
     </fo:table-cell>

   </xs:for-each>

当上下文为tableRow时,没有tableRow/entry可供选择。

快速的解决方案是删除tableRow中的tableRow/entry

我建议,较长期的解决方案是对xs:apply-templates做更多的事,而对xs:for-each做更少的事。如果您使用过xs:apply-templates,那么XSLT处理器将每次都选择子元素,然后找到最匹配的xs:template用于每个元素。诸如此类(未经测试):

<xs:template match="table">
  <fo:table>
    <fo:table-header>
      <xs:apply-templates select="tableRow[tableHead]" />
    </fo:table-header>
    <fo:table-body>
      <xs:apply-templates select="tableRow[entry]" />
    </fo:table-body>
  </fo:table>
</xs:template>

<xs:template match="tableRow">
   <fo:table-row>
     <xs:apply-templates />
   </fo:table-row>
</xs:template>

<xs:template match="tableHead">
  <fo:table-cell xs:use-attribute-sets="cell-style">
    <fo:block xs:use-attribute-sets="block-style" text-align="center">
      <xs:apply-templates/>
    </fo:block>
   </fo:table-cell>
</xs:template>

<xs:template match="entry">
  <fo:table-cell xs:use-attribute-sets="cell-style">
    <fo:block xs:use-attribute-sets="block-style">
      <xs:apply-templates/>
    </fo:block>
  </fo:table-cell>
</xs:template>