Reportlab生成PDF文件

准备reportlab表格视频的材料时发现了一个挺实用的程序(http://bit.ly/2mOO3lQ),改吧改吧可用作应用实例。这个程序把指定文件夹为根的目录树上的超过1M的文件都找出来,然后把跟这些文件相关的信息制成表格输出到PDF文件中,生成的PDF文件大概长这样:

生成表格最关键的一步就是取数据,在第三方模块pathlib的帮助下遍历目录树取超过1M的文件,获取文件信息。如果q指向给定的文件夹,遍历以这个文件夹为根的目录树用

for p in q.glob('**/*.*'):

‘**/*.*’不可以替换成‘*/*.*’,不然只遍历q文件夹下面一级的文件和文件夹,不会进入到子目录、子目录的子目录……。

数据取出来之后还要整理,整理进列表,列表的元素还是列表,对应表格的行数据。数据整理完毕,生成表格对象,定义表格格式。添加进PDF文件页面时还需要判断一下页面放不放得下表格,如果放不下页面要放倒(横放)。除了放表格,生成的PDF文件里还会放一个标题。定义一个类DataToPdf来做这些工作。

from operator import itemgetter
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, \
    Table, TableStyle

# 注册中文字体
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics
pdfmetrics.registerFont(TTFont('lively', '/Library/Fonts/Chinese/ChaoZiSheZengYuBoShouShuJian-2.ttf'))
pdfmetrics.registerFont(TTFont('apple', '/Library/Fonts/Chinese/XiaoHuYao-2.ttf'))

class DataToPdf():
    """
    输出文件信息到到PDF文件的表格中去
    """

    def __init__(self, fields, data, sort_by=None, title=None):
        """
        参数解释:
            fields - 元组,元组元素也是元组,存放表头和读取数据的键值
            data - 列表,列表元素是存放文件信息的字典,字典键值也在fields里
            sort_by - 元组(排列的关键字,顺序),指定依据什么排列和排列顺序:ACS升序或DESC降序
            title - 文件的标题
        """
        self.fields = fields
        self.data = data
        self.title = title
        self.sort_by = sort_by

    def export(self, filename, data_align='LEFT', table_halign='CENTRE'):
        """
        数据输出到PDF文件中
        参数解释:
            filename - PDF的文件名
            data_align - 表格里数据的对齐方式(比如'LEFT', 'CENTER', 'RIGHT')
            table_halign - 整个表格在页面里的对齐方式(比如'LEFT', 'CENTER', 'RIGHT')
        """

        # 标题格式
        styles = getSampleStyleSheet()
        styleH = styles['Heading1']     # 标题模版上略作修改
        styleH.fontName = 'lively'      # 字体改成中文字体
        styleH.alignment = 1            # 居中对齐,默认是靠左(左0中1右2)

        story = []                      # 列表存放flowable

        if self.title:                  # 生成对象时如果给了标题参数……
            story.append(Paragraph(self.title, styleH))     # 标题是标题样式的段落,添加进story
            story.append(Spacer(1, 20)) # 标题和表格间放段空白,也是flowable的一种,填进story

        if self.sort_by:
        # 生成对象时如果没给排序参数,按遍历时找到文件的先后填入表格;如果给了排序参数……
            reverse_order = False       # 默认是正序排列
            if (str(self.sort_by[1]).upper() == 'DESC'):   # 除非特别指定是逆序(DESC)
                reverse_order = True
            self.data = sorted(self.data,
                    key=itemgetter(self.sort_by[0]),
                    # self.sort_by的值('size', 'DESC')
                    # self.sort_by[0]取'size'
                    reverse=reverse_order)
            # self.data是一个列表,每个元素是字典类型,排序的关键字有参数key=决定
            # key=itemgetter('size')决定按字典键值'size'排列,reverse=True逆序排

        converted_data = self.__convert_data()
        # 将表格要用到的数据整理到一个列表,列表元素还是列表,存放表格每一行的数据

        t = Table(converted_data, hAlign=table_halign)
        # 生成表格对象,hAlign='CENTRE'使得表格的页面对齐方式是居中
        t.setStyle(TableStyle([     # 自定义表格样式
            ('INNERGRID', (0, 0), (-1, -1), 0.50, colors.black),    # 内线
            ('BOX', (0, 0), (-1, -1), 0.25, colors.black),          # 外框

            ('FONT', (0, 0), (-1, 0), 'lively'),     # 表头字体
            ('ALIGN', (0, 0), (-1, 0), 'CENTER'),    # 表头行单元格内的对齐方式
            ('BACKGROUND', (0, 0), (-1, 0), colors.peachpuff),  # 表头行背景

            ('FACE', (0, 1), (-1, -1), 'apple'),     # 数据区字体
            ('ALIGN',(0, 1),(0,-1), data_align),     # 数据区单元格内对齐方式
            ('ROWBACKGROUNDS', (0, 1), (-1, -1), (colors.aliceblue, colors.lightgoldenrodyellow, colors.lavenderblush)). 
            # 数据区背景
        ]))

        story.append(t)     # 表格也是一个flowable,添加进story

        doc = SimpleDocTemplate(filename)   # 生成pdf文档对象
        self.pageSetup(doc, t)              # 根据表格宽度决定页面要不要横放
        doc.build(story)                    # 生成pdf文件

    def __convert_data(self):
        """
        为了生成PDF上的表格,把表头元组和存放文件信息的字典列表整合成元素是列表的列表
        """
        # 表头列表
        keys, names = zip(*self.fields)     
        # 生成数据的字典键值元组和表头元组
        names = list(names)     # 表头元组->表头列表

        new_data = [names]      # new_data接收整合好的表格数据

        # 整合数据
        for d in self.data:
            new_data.append([d[k] for k in keys])
        # for k in keys循环变量k依次取字典键值,d[k]取键值对应的值。外面的[]把取出来的值放进列表,对应表格里的一行

        return new_data

    def pageSetup(self, doc, t):
        """
        对比页面可用宽度和表格需要的宽度,表格太宽时把页面横过来
        参数解释:
            doc - 接收PDF文档对象
            t - 接收表格对象
        """
        from reportlab.lib.pagesizes import A3, A4, landscape
        pageSize = A3               # 页面尺寸
        doc.pagesize = pageSize     # 指定页面尺寸,doc没有这个属性,然而这种做法目前行之有效
        margin = 72                 # 缺省的上下左右留白是72points(1 inch)
        pW, pH = pageSize           # 页面宽高
        tW, tH = t.wrap(pH, pH)     # 表格宽高
        aW = pW - margin * 2        # 除去页面留白,表格真正可用的宽度
        if tW > aW:                 # 表格太宽页面只好横放
            doc.pagesize = landscape(pageSize)  # 页面横放
            print("横过来后的页面宽度:", pH - margin * 2)
        print("纵向页面可用空间:", aW, ",表格占用空间:", tW)

import time, datetime
from pathlib import Path

if __name__ == '__main__':
    data = []                       # 存放文件信息的列表,每个元素是字典类型
    q = Path('/Users/PythonABC/reportlab')
    for p in q.glob('**/*.*'):      # 遍历目录树
        if p.is_file():             # 判断是不是文件
            filename = p.name       # 文件名
            filepath = p.absolute() # 绝对路径
            filesize = p.stat().st_size / 1024  # 获得文件尺寸,/1024是为了单位换算成KB
            if filesize > 1024:     # 大于1024K即大于1M
                mtime = p.stat().st_mtime   # 文件修改时间,得到的是时间戳
                mtime = datetime.datetime.strptime(time.ctime(mtime),
                                                   '%a %b %d %H:%M:%S %Y')
                # 转化成指定格式的日期时间字符串
                data.append({'filename': filename,
                             'filepath': filepath,
                             'size': filesize,
                             'mtime': mtime})
                # 用字典类型保存文件信息,而后添加进列表

    fields = (
        ('filename', '文件名'),
        ('filepath', '文件路径'),
        ('size', '文件大小(KB)'),
        ('mtime', '修改时间'),
    )
    # 表头内容和个数固定用元组,表头的每个条目跟保存文件信息的字典的关键字组成元组,放进一个大元组里

    doc = DataToPdf(fields, data, sort_by=('size', 'DESC'),
                    title='超过1MB的文件')       # DataToPdf是自己定义的类,生成类的对象doc
    doc.export('listFile>1M.pdf')              # 调用对象的方法函数制作表格生成PDF文件