Reportlab 在创建包含 table 内容的多个文档时不会重置序列

Reportlab does not reset sequences when creating multiple documents with table of contents

我正在使用一个模板函数,该函数使用 reportlab 在一次程序执行中创建多个 PDF 文档。

这些文档在结构上是相同的,并且具有相同的标题。它们仅在标题下方的内容上有所不同。所有这些文档都包含 table of contents 元素。

我正在使用序列标签(<seq/> 等)来创建编号标题 e。 G。

1. Top1
  1.1 Sub1
2. Top2
  2.1 Sub1
  2.2 Sub2

这适用于单个文档,但只要我在第一个文档之后创建第二个文档,序列就不会重置,第二个文档的 TOC 看起来像

2. Top1
  2.1 Sub1
3. Top2
  3.1 Sub1
  3.2 Sub2

创建第三个文档 Top1 将从 3 开始。

但是因为我正在开始一个新文档,创建一个新的 BaseDocTemplate class,我希望序列被重置。我怎样才能做到这一点?

我尝试使用 reportlab 的其中一个教程创建一个尽可能小的示例。

from  reportlab.lib.styles import ParagraphStyle as PS
from  reportlab.platypus import PageBreak
from  reportlab.platypus.paragraph import Paragraph
from  reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from  reportlab.platypus.tableofcontents import TableOfContents
from  reportlab.platypus.frames import Frame
from  reportlab.lib.units import cm

class MyDocTemplate(BaseDocTemplate):
     def __init__(self, filename, **kw):
         self.allowSplitting = 0
         super().__init__(filename, **kw)
         template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')])
         self.addPageTemplates(template)

# Entries to the table of contents can be done either manually by
# calling the addEntry method on the TableOfContents object or automatically
# by sending a 'TOCEntry' notification in the afterFlowable method of
# the DocTemplate you are using. The data to be passed to notify is a list
# of three or four items countaining a level number, the entry text, the page
# number and an optional destination key which the entry should point to.
# This list will usually be created in a document template's method like
# afterFlowable(), making notification calls using the notify() method
# with appropriate data.

     def afterFlowable(self, flowable):
         "Registers TOC entries."
         if flowable.__class__.__name__ == 'Paragraph':
             text = flowable.getPlainText()
             style = flowable.style.name
             if style == 'Heading1':
                 self.notify('TOCEntry', (0, text, self.page))
             if style == 'Heading2':
                 self.notify('TOCEntry', (1, text, self.page))


centered = PS(name = 'centered',
    fontSize = 30,
    leading = 16,
    alignment = 1,
    spaceAfter = 20)

h1 = PS(
    name = 'Heading1',
    fontSize = 14,
    leading = 16)


h2 = PS(name = 'Heading2',
    fontSize = 12,
    leading = 14)

# Heading definition with sequence numbers
heading = {
    1 : "<seq id='h1'/>.<seqreset id='h2'/><seqreset id='h3'/> {}",
    2 : "<seq id='h1' inc='no'/>.<seq id='h2'/><seqreset id='h3'/> {}",
    3 : "<seq id='h1' inc='no'/>.<seq id='h2' inc='no'/>.<seq id='h3'/> {}",
}

def build_document(filename):
    # Build story.
    story = []

    # Create an instance of TableOfContents. Override the level styles (optional)
    # and add the object to the story

    toc = TableOfContents()
    toc.levelStyles = [
        PS(fontName='Times-Bold', fontSize=20, name='TOCHeading1', leftIndent=20, firstLineIndent=-20, spaceBefore=10, leading=16),
        PS(fontSize=18, name='TOCHeading2', leftIndent=40, firstLineIndent=-20, spaceBefore=5, leading=12),
    ]
    story.append(toc)

    story.append(Paragraph('<b>Table of contents</b>', centered))
    story.append(PageBreak())
    story.append(Paragraph(heading[1].format('First heading'), h1))
    story.append(Paragraph('Text in first heading', PS('body')))
    story.append(Paragraph(heading[2].format('First sub heading'), h2))
    story.append(Paragraph('Text in first sub heading', PS('body')))
    story.append(PageBreak())
    story.append(Paragraph(heading[2].format('Second sub heading'), h2))
    story.append(Paragraph('Text in second sub heading', PS('body')))
    story.append(PageBreak())
    story.append(Paragraph(heading[2].format('Last heading'), h1))
    doc = MyDocTemplate(filename)
    doc.multiBuild(story)

if __name__ == "__main__":
    build_document("1.pdf")
    build_document("2.pdf")

我找到了一个快速解决方案来解决我的问题,但我不喜欢它作为最终解决方案。

问题是我正在使用同名的全局序列。 h1h2h3 出现在每个文档中。并且 reportlab 似乎不会在启动新文档时重置它们。因此,我改为在填充故事列表之前手动重置。

def build_document(filename):
    # Build story. story = []

    # Reset the sequences
    story.append(Paragraph("<seqreset id='h1'/>", PS('body')))
    story.append(Paragraph("<seqreset id='h2'/>", PS('body')))
    story.append(Paragraph("<seqreset id='h3'/>", PS('body')))

    # ...  rest of the code from the question

看起来正在使用的 reportlab.lib.sequencer.Sequencer 对象是全局对象。

您可以通过提供新的 Sequencer 来重置所有计数器

    from reportlab.lib.sequencer import setSequencer, Sequencer

    
    setSequencer(Sequencer())

您可以通过执行以下操作来重置单个计数器:

    from reportlab.lib.sequencer import getSequencer

    s = getSequencer()
    s.reset('h1')

您也可以尝试直接使用 Sequencer 而不是注入 XML。

Class Sequencer: Something to make it easy to number paragraphs, sections, images and anything else. The features include registering new string formats for sequences, and 'chains' whereby some counters are reset when their parents. It keeps track of a number of 'counters', which are created on request.

Usage::
 >>> seq = Sequencer()
 >>> seq.next('Bullets')
 1
 >>> seq.next('Bullets')
 2
 >>> seq.next('Bullets')
 3
 >>> seq.reset('Bullets')
 >>> seq.next('Bullets')
 1
 >>> seq.next('Figures')
 1
 >>>