サイボウズLiveでエクスポートしたデータをPDFに変換する [アップデート]

## 背景
[前回](https://qiita.com/OttyLab/items/e1b559fd463539deb19d) サイボウズLiveからエクスポートしたCSVファイルをPDF化しました。

さて、いよいよサイボウズLiveサービス終了間近ということでいくつかアップデートしました。

## 目次機能
先頭に目次をつけるようにしました。また、目次をリンクにすることで記事にスムーズにジャンプできるようになりました。

変更点としては以下のとおりです。

### `BaseDocTemplate` を継承したクラスの作成

`Paragraph`クラスである要素(=flowable)である場合、そのヘッダから目次のエントリを作成します。ちなみに、`TOCEntry` を `notify` することで自動的に目次のエントリに追加されるそうです。

```python
 34 class DocTemplate(BaseDocTemplate):
 35     def __init__(self, filename, **kw):
 36         self.allowSplitting = 0
 37         BaseDocTemplate.__init__(self, filename, **kw)
 38         template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')])
 39         self.addPageTemplates(template)
 40 
 41     def afterFlowable(self, flowable):
 42         if flowable.__class__.__name__ == 'Paragraph':
 43             text = flowable.getPlainText()
 44             style = flowable.style.name
 45             if style == 'header':
 46                 self.notify('TOCEntry', (0, text, self.page, flowable._bookmark))
```

[マニュアル](https://www.reportlab.com/docs/reportlab-userguide.pdf)に以下の記載があります。

>
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. 


### 目次の作成

最初に目次（=TOC: Table Of Contents）の作成コードを追加しただけです。

```python
338 def gen_pdf(generator, output, toc=True):
339     addMapping(DEFAULT_FONT, 1, 1, DEFAULT_FONT)
340 
341     story = []
342 
343     if toc:
344         title_style = ParagraphStyle(
345             fontName=DEFAULT_FONT,
346             fontSize=15,
347             name='TOC',
348             spaceAfter=10
349         )
350         story.append(Paragraph('目次', title_style))
351 
352         toc = TableOfContents()
353         toc.levelStyles = [
354             ParagraphStyle(
355                 fontName=DEFAULT_FONT,
356                 fontSize=8,
357                 name='body',
358                 spaceAfter=4,
359                 justifyBreaks=1
360             )
361         ]
362 
363         story.append(toc)
364         story.append(PageBreak())
365 
366     story.extend(generator.convert())
367 
368     doc = DocTemplate(output)
369     pdfmetrics.registerFont(TTFont(DEFAULT_FONT, DEFAULT_FONT_FILE))
370     doc.multiBuild(story)
```

## リンクの作成

まず、記事を一意に識別できる名前を作ります。後で考えたら記事のIDで良かったのですが、今回はタイトルの文字列をxxHashしたものを識別名としました。

`DocTemplate`クラス内の`self.notify`ですが、第二引数のタプル`(0, text, self.page, flowable._bookmark)`の４要素目に先程の識別名を代入することでリンクとして動作します。ただし、この識別名を渡す手段が特に用意されていないので`_bookmark`のような適当なメンバを作成して代入してます（111行目）。

また、実際のジャンプ先に`<a>`タグを設定する必要があるようです。そこで、この識別名を`name`属性に持つ`<a>`タグを`header`に入れます(108行目)。

```python
101     def convert(self):
102         import xxhash
103         story = []
104         for board in self.boards:
105             title = '{} [{}]'.format(board.title, board.id)
106             creator = '{} ({})'.format(board.creator, board.create_time)
107             digest = xxhash.xxh32_hexdigest(title)
108             header = '{} / {} <a name={} />'.format(title, creator, digest)
109 
110             hp = Paragraph(header, self.header_style)
111             hp._bookmark = digest
112             story.append(hp)
113             story.append(Paragraph(board.body, self.body_style))
114             story.extend(CommentGenerator(board.comments).convert())
115             story.append(PageBreak())
116 
117         return story
```


目次の作り方は[こちらのコード](https://www.reportlab.com/snippets/13/)が参考になります。

## 見た目の変更
以下の変更を行いました。

+ 作成日順にソート(CSVは最終更新日順に並んでいるようです)
+ トピックのタイトルを矩形、本文を網掛けで装飾
+ コメントの改行が正しく表示されない問題の修正

## 使い方

シンプル

```shell
$ python generate.py live_board.csv board.pdf
```

作成日を指定（２０１８年の記事だけをPDF化したい等の用途）

```shell
python generate.py -f 2018/01/01 -t 2019/01/01 live_board.csv board.pdf
```