Document preprocessor for Apache FOP
Project description
Generate PDF files [1] based on Apache FOP.
Installation
Install pypfop:
pip install pypfop
Install Apache FOP:
Quick start
Create a file named helloworld.fo.mako:
<%inherit file="letter-portrait.fo.mako" />
<block>Hello ${name}!</block>
And then in you python code:
import pypfop
pdf_path = pypfop.generate_document("helloworld.fo.mako", {"name": "Foo"})
print("The document has been generated at {}".format(pdf_path))
Alternatively there is decorator based syntax:
import pypfop
document = pypfop.make_document_decorator()
@document(template="helloworld.fo.mako")
def hello_world():
return {"name": "Foo"}
pdf_path = hello_world()
print("The document has been generated at {}".format(pdf_path))
How does it work?
It does what the huge title is implying, preprocess a higher level template to generate dynamically an specific XSL-FO document, which then gets fed to Apache FOP and generate the expected output. So that means that this packages requires Java >_<', but fear not! It is almost transparent to the python application.
In general the internal workflow is:
template -> mako -> apply css -> xsl-fo -> fop -> *Document*
Usage
The Markup
The markup used to generate the documents is almost the same as the xsl-fo, the only difference is that is not necessary to set the xml namespace to all the elements, for example:
<fo:table>
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<fo:block>Project</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<fo:table-row>
<fo:table-cell>
<fo:block>pypfop</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
can be written like this:
<table>
<table-header>
<table-row>
<table-cell>
<block>Project</block>
</table-cell>
</table-row>
</table-header>
<table-body>
<table-row>
<table-cell>
<block>pypfopp</block>
</table-cell>
</table-row>
</table-body>
</table>
The higher level template language
Currently the only supported template language is mako. If for some reason you don’t like that templating language, it shouldn’t be hard to extend to your favorite template language based in the implementation of mako (which is pretty straight forward) and hopefully contribute back to the project :).
For example, the previous table can be generated with this mako template assuming the header and rows variables are passed as parameters:
<table>
<table-header>
<table-row>
% for name in header:
<table-cell>
<block>${name}</block>
</table-cell>
% endfor
</table-row>
</table-header>
<table-body>
% for row in rows:
<table-row>
% for cell in row:
<table-cell>
<block>${cell}</block>
</table-cell>
% endfor
</table-row>
% endfor
</table-body>
</table>
Skeletons
The previous examples are just fragments of a document. To be able to fully generate a document with metadata, paper size, margins, etc and to avoid the repetitive work to write this kind of base document pypfop have the notion of skeleton documents, the purpose if this documents are to be inherited at each template, at the time the implemented skeleton are:
pypfop/skeletons/mako/A4-landscape.fo.mako
pypfop/skeletons/mako/A4-portrait.fo.mako
pypfop/skeletons/mako/letter-landscape.fo.mako
pypfop/skeletons/mako/letter-portrait.fo.mako
Those include place-holders for:
Metadata:
title
author
subject
GENERATOR (by default “PyPFOP”)
There is also a mako block called rfooter and the body of your template will be the body of the document.
You don’t have to define anything else than the body of your own document but you still have the option to override any of the metadata and your own footer region.
To be a fully functional template for pypfop the previous table need to be like this.
simple-table.fo.mako:
<%inherit file="simple-letter-base.fo.mako" />
<table id="main-table">
<table-header>
<table-row>
% for name in header:
<table-cell>
<block>${name}</block>
</table-cell>
% endfor
</table-row>
</table-header>
<table-body>
% for row in rows:
<table-row>
% for cell in row:
<table-cell>
<block>${cell}</block>
</table-cell>
% endfor
</table-row>
% endfor
</table-body>
</table>
The skeletons directory is set in the template directory path by default.
Format and style with CSS
Beside the higher level language that define the content and layout of the document, the style and formatting uses CSS, to be more specific it can parse the rules that cssutils support, which are a very good subset of CSS2 and CSS3, for example it support things like :nth-child(X) and @import url(XX).
The properties that can be set are the same as in the specification of xsl-fo, check out the section of About XSL-FO syntax, with the only exception that you can use classes as selectors, xsl-fo does not support the class attribute, the pypfop parser is going to look for the class attribute then substitute with the specific style and then remove the class attribute.
For example I could define the style for the previous table in three files.
simple_table.css:
@import url("general.css");
@import url("colors.css");
#main-table > table-header > table-row{
text-align: center;
font-weight: bold;
}
#main-table > table-header table-cell{
padding: 2mm 0 0mm;
}
general.css:
flow[flow-name="xsl-region-body"] {
font-size: 10pt;
font-family: Helvetica;
}
colors.css:
#main-table> table-body > table-row > table-cell:first-child{
color: red;
}
#main-table> table-body > table-row > table-cell:nth-child(2){
color: blue;
}
#main-table> table-body > table-row > table-cell:nth-child(3){
color: cyan;
}
#main-table> table-body > table-row > table-cell:last-child{
color: green;
}
Generate the document
There are a few different ways to generate a document.
Single function call
import pypfop
params = {
'header': ['Project', 'Website', 'Language', 'Notes'],
'rows': [
('pypfop', 'https://github.com/cyraxjoe/pypfop',
'Python', 'Abstraction on top of Apache FOP'),
('Apache FOP', 'https://xmlgraphics.apache.org/fop/',
'Java', '')
]
}
doc_path = pypfop.generate_document(
"sample-table.fo.mako",
params,
"simple_table.css"
) # returns the path of the generated file.
print(doc_path)
Decorator based
import pypfop
document = pypfop.make_document_decorator()
@document("simple-table.fo.mako", "simple_table.css")
def simple_table():
return {
'header': ['Project', 'Website', 'Language', 'Notes'],
'rows': [
('pypfop', 'https://github.com/cyraxjoe/pypfop',
'Python', 'Abstraction on top of Apache FOP'),
('Apache FOP', 'https://xmlgraphics.apache.org/fop/',
'Java', '')
]
}
doc_path = simple_table() # returns the path of the generated file.
print(doc_path)
Explicit construction
import pypfop
import pypfop.templates.mako
tfactory = pypfop.templates.mako.Factory()
params = {
'header': ['Project', 'Website', 'Language', 'Notes'],
'rows': [
('pypfop', 'https://github.com/cyraxjoe/pypfop',
'Python', 'Abstraction on top of Apache FOP'),
('Apache FOP', 'https://xmlgraphics.apache.org/fop/', 'Java', '')
]
}
doc_gen = pypfop.DocumentGenerator(tfactory('simple-table.fo.mako'), 'simple_table.css')
doc_path = doc_gen.generate(params) # returns the path of the generated file.
print(doc_path)
Supported document formats
In the previous example we didn’t define the output of the Document in that case the default output of pdf is used, but the supported outputs are the almost the same as in Apache FOP output formats.
rtf
tiff
png
pcl
ps
txt
The output format can be set in any of the supported methods:
# simple function call
pypfop.generate_document(
"sample-table.fo.mako", "simple_table.css", out_format='rtf'
)
# decorator based
@document("simple-table.fo.mako", "simple_table.css", out_format='rtf')
def simple_table(): ...
# explicit method
doc_gen.generate(params, out_format='rtf')
About XSL-FO syntax
As you may have already noticed, it is required to know how to format xsl-fo documents which in most part are very similar to the HTML counterparts (except that anything needs to be in block tags), two of the best reference that I could find online is in the XML Bible and the Data 2 Type tutorial.
How about a CSS pre-processor and base generic styles?
I’m looking to add support for less or something similar and try to generalize the styling of fonts, colors, tables, etc. Very much in the bootstrap sense of the things. If you are interested in something similar we can join forces and build something nice.
Why!
The project used to be part of a larger project of one of my clients, on which I decide early on that I will only use python 3, terrible decision if you want to generate pdf files easily or at least at the time (2012 I believe?) when the Report Lab PDF Toolkit was not yet available for Python 3 and I was looking to have some kind of template to the very rigid format of the average invoice and billing order, so pypfop came to relieve that pain.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.