Redmineの複数のWikiページを一つのMSWordファイルにまとめるまで自動化した話

[Redmine Advent Calendar 2024](https://adventar.org/calendars/10311) の 15日用の記事です。

# 初めに

※ 本記事は私の主観的な思いが多く含まれています。
読みにくいですがご容赦いただけますと幸いです。

社内Wikiは便利ですよね！
最新情報を共有する際はURLを1つ送るだけで済みますし、編集履歴や差分も簡単に確認できます。また、オフラインで参照したいときにはPDFエクスポート機能も用意されています。私はオンプレのRedmineに搭載してるwikiを使って、タスクと紐付けながら書きます。

ところで、私事ではありますが、誰かにドキュメントを提出した時、皆さんもこんな依頼を受けたことはありませんか？

> PJリーダー：「いや、PDFじゃなくてWordで欲しいんだけど。」

…え？いや…提出物にPDFで十分じゃないですか…ていうか、社内の人ならwikiページ見たらそれで良いじゃな…

しかし、現実は甘くありません。
私は権力には逆らえず、RedmineのWikiに書いた情報をWordやExcel形式に変換する作業を頻繁に行う羽目になりました。

情報の中身は変わらず、ただ体裁を整えるだけの作業。これに時間を取られるのは非常に苦痛でした。
この経験がきっかけで、RedmineのWikiからWordファイルを自動的に変換するツールを開発するに至りました。
本記事では、そのツールの動作プロセス紹介や、作ってみて得られた知見について紹介します。

# WikiからWordへの手動変換の苦悩と自動変換への動機

RedmineのWikiには、「HTML」や「PDF」でエクスポートできる機能があります。この機能を使えば、プロジェクト内にある複数のWikiページをワンボタンで出力することが可能です。
参考URL: [RedmineでWikiページをすべてエクスポートする方法](https://blog.redmine.jp/articles/new-feature-1_4/export-all-wiki-pages/)

「PDFからWordに変換するだけなら、WebアプリやPandocを使えば簡単にできそうだな」と、思ってました。
しかし、Word提出とは単にPDFをWordに変換するだけで済む話では無かったのです。

私の想定と違い「WORDで欲しい」には隠れた要求があります。
それは「再編集性」や社内の人が定義する「整った体裁」が求められています。これらの要求は、私にとっては客先提出資料じゃないんだから、できれば欲しい程度の要求と思っていました。ですが、後にPJリーダーから叱られて当たり前品質であることがわかりました。
これらを踏まえると、現行Redmineの標準Wikiエクスポート機能には以下の問題点があることになります。

- 標準Wikiエクスポート機能の問題点
    - 出力順序の問題
    WikiページがUnicode順に出力されるため、Wordに変換した後でページ順を手動調整する必要がある。
    - ページ選択の制限
    エクスポート対象のページを選べないため、後で不要なページを削除する手間が発生する。
    ※Wikiの設計段階で不要なページを作らない運用も可能だが、それではWiki本来の自由度が損なわれる。
    - 体裁の問題
    Word提出時に必要な表紙や目次などを、変換後に追加しなければならない。
- PDFエクスポートからWord変換の問題点
    - 体裁の崩れ
    PDFをWordに変換した際、PDFの体裁と異なる箇所が多数発生する。
    変換ツールによる差異もある。
    - 不要情報の出力
    PDFにWikiの添付ファイルリストなど余分な情報が含まれるが後で加工が必要。
- HTMLエクスポートからWord変換の問題点
    - 目次の不要化
    ページ内目次はWordには不要なため、削除作業が必要になる。
    - 表の枠線表示の不具合
    表の枠が見えないスタイルの問題が発生する。
    - 画像表示の問題
    サーバの画像を表示しようとしてオフラインで表示できない
    - ページ区切りの欠如
    Wikiページ単位でWordのページ改行が自動設定されないため、適宜手動で編集が必要。

このように、現行の機能を使った場合、Word変換後の問題があり、手直しがあまりにも多過ぎました。
急ぎの依頼時には、必要な情報を手動でWordにコピペして対応しましたが、20ページを超えるころには操作ミスが増え、限界を感じます。1回限りの作業なら我慢はできても「これが今後も繰り返されるのか」と絶望していました。

絶望が顔に現れていた頃に周りからは「初めからWordで資料を作れば良いのに」と、度々アドバイスを貰いました。
ですが私の属したPJでは、ファイルの内容や更新履歴を何故か書き手が管理することになります。
いつのどの書類のどこに例の情報があるのか？と、関係者に問われ続けて一生書き手から手離れしません。それも仕事だと飲み込もうとはしたのですが、私は私の仕事で残業時間が増えてゆくので無理になりました。
※ イメージ図として適切な参考URLを下記に貼っておきます
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">1匹いたら20匹いると思え <a href="https://twitter.com/hashtag/%E7%8F%BE%E5%A0%B4%E7%8C%AB?src=hash&amp;ref_src=twsrc%5Etfw">#現場猫</a> <a href="https://t.co/yqilDQgBbE">pic.twitter.com/yqilDQgBbE</a></p>&mdash; からあげのるつぼ (@karaage_rutsubo) <a href="https://twitter.com/karaage_rutsubo/status/1570149111147696128?ref_src=twsrc%5Etfw">September 14, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

持論ですが、蓄積してゆく情報とは読み手も十分に関わり、読み手も情報を育てるべき。せめて、情報を育てることに協力するべきだと思ってます。
なので、共有を妨げる1ファイルによるナレッジの蓄積は避けたいのです。そして、複数人でナレッジが蓄積できる状況を作りたいのです。

こうした苦悩から、私はWikiで情報を蓄積したい。という思いと、PJリーダはWordで提出しないと読んでやらない。を両立するために、自動でWordに変換する仕組みを作る必要性を強く感じるようになりました。

# 自動化の目標と環境
- 自動化の目標
RedmineのWikiに記載した情報を、関係者間で使用されるフォーマットに従った1つのWordファイルにまとめ、そのまま提出できる形にすることを目指します。達成しなくとも、手動での変換作業の負担を減らし、再現性のある形で効率化することが主な目的です。

- 動作環境
※ 全体的に古い環境です…。

    - Redmineバージョン: 4.2.1 stable
    （今回関係するプラグイン: Redmine draw.io plugin）
    - Wordバージョン: 2016
    - 開発環境: Windows 10 64bit、Python 3.11(各種ライブラリ)

# 自動化ツールの動作解説

ここからは、作ったツールの中身に触れますが、ソフト要件を中心に一部設計の説明します。

TODO: ここに動作している動画を載せたい…

# 自動化ツールの全体概要

本ツールは、以下のような機能ブロックで構成されています。
簡単に言えば「WikiからHTMLをエクスポートして、必要な情報を編集・加工して、最終的にWord形式で保存しなおして微調整する」
という手動の流れを自動化しただけです。

```mermaid
flowchart TB

    A --> 最新Wikiのダウンロード --> F
    最新Wikiのダウンロード --> ローカルファイル向け加工 --> E
    G --> テンプレートHTMLの作成

    テンプレートHTMLの作成 --> テンプレートHTMLとマージ
    E --> エクスポート対象の抽出と加工 --> テンプレートHTMLとマージ --> WordのVBA実行 --> docmファイルに保存 --> G
    F --> エクスポート対象の抽出と加工
    F --> 編集されたファイルのアップロード

    subgraph サーバ
        A[("最新Redmine<br>Wiki")]
    end

    subgraph クライアントpythonアプリ
    subgraph ダウンローダ
        最新Wikiのダウンロード
        ローカルファイル向け加工
        E[("Wikエクスポートデータ.html<br>Wiki更新情報.json")]
    end
    subgraph アップローダ
        編集されたファイルのアップロード
    end

    subgraph Wordコンバータ
        テンプレートHTMLの作成
        エクスポート対象の抽出と加工
        テンプレートHTMLとマージ
        WordのVBA実行
        docmファイルに保存
    end
    end
        F[("WikページRawデータ<br>(文章、添付ファイル)<br>タイトル情報リスト.json")]
        G[("Wordファイル")]
        F <--> タイトル情報の調整
        F <--> Wiki文章修正
        F <--> 添付ファイル修正
        編集されたファイルのアップロード --> A

    subgraph 手作業
        タイトル情報の調整
        Wiki文章修正
        添付ファイル修正
    end

```    
# 各機能の説明

## ダウンローダ


最新情報はRedmineのWikiにあるとして、一先ずAPIを使ってクライアントPCに最新情報をダウンロードします。

PythonでRedmineにアクセスする際には、python-redmine というライブラリが便利です。このライブラリを使用することで、簡単にRedmineにアクセスし、データを取得できます。
なお、Redmineサーバー側の設定が必要となります。以下の記事を参考に設定しました。
参考記事：[Redmine REST APIを有効化する方法](https://qiita.com/mima_ita/items/1a939db423d8ee295c85)

ダウンローダは、python-redmine を使用して、対象プロジェクトのWikiページ単位の情報をとしてダウンロードします。
(これらはrawデータとして後で再編集に使います)
![2024-12-23_20h30_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140206/4208d5c7-c0c3-0f5b-0a11-2e711a5cdb38.png)また、Redmine標準のエクスポート機能を使い、Wiki全体のデータをHTML形式でダウンロードします。

しかし、このエクスポート機能のHTML形式はオフラインで画像表示できない問題がありました。そのため、ダウンロード後に必要な加工処理を幾つか行います。
なお、HTMLの加工はBeautiful Soup 4というライブラリを使うとHTMLのタグ単位で加工ができるので楽です。
- ダウンロードしたローカルPC上の添付ファイルと新たにリンクを結び直す
- 一部の見えにくいスタイルを別のスタイルに変更する
- 次工程で利用するエクスポート機能に向けて、Wikiページのタイトルリンクにクラス分けする

これらの加工により、RedmineのHTMLエクスポートデータはオフライン利用が可能となります。
この時点で、Redmineの標準エクスポート機能の不満は解消されたと言えます…！
以降は、Word提出に向けた処理です。

## Wordコンバータに向けた手作業

Wordにエクスポートする際には、Wikiページの要不要、順番を検討します。
固定したページを自動的に定義しても良いですが、基本的には手動です。
Wordだけで文章を作ったとしても、何をどのような順番で見せたいかを決めるのは手動ですので、同じことです。

ただ、一度定義したエクスポート対象の定義は使いまわしたく、タイトルの要不要と順番、階層情報をjsonファイル保存して、同じプロジェクトのWordコンバート時は流用してゆきます。
このデスクトップアプリは、規格物のjsonファイル加工ですのでChatGPT先生にお任せしました。
雑な指示でも作ってくれて、大変御世話になっております…!
![2024-12-23_20h21_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140206/3e6367f0-c616-a5f0-667f-60792c34264c.png)


## Wordコンバータ～前加工～

エクスポート対象情報を元に、Wikiページ単位の情報の取り出して順番を変えながら、一つの必要な情報だけを集めたhtmlにします。
この他にも
 - ページ単位で要らない目次を消す
 - ページの最後にWord改ページ構文(HTML)を挿入する
 - ページ内の見出しレベルを3以上に調整して、Word目次への表示がされないようにする
 - Word2016向けにsvg → emf変換
 - etc.....

色々加工しますが、特徴的な加工は、エクスポート対象のページにサーバのWikiと連携できるリンクを追加したことです。
Word上では以下のような表示になります。
![2024-12-23_07h32_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140206/d47161e7-1da3-43a3-8060-97cfc4e218e6.png)

このリンクは、Wikiを原本にしてWordはそのコピーであるという従属関係を読み手に示します。
読み手は、ファイル単位の更新日時ではなく、Wikiページ単位でいつの情報なのかを知ることができます。
さらに、最新との差分表示もリンクで貼り付けることで、読み手自身が最新情報とのギャップを把握できます。
参考URL：[RedmineのWikiの活用方法と便利な機能紹介](https://lychee.techmatrix.jp/wiki_2023-04/#i-5)

もちろん最新情報は秘匿にしたい場合もあるので、Wikiが存在するプロジェクトメンバじゃないと閲覧できないようにすれば、万が一資料が客先に渡っても大きな問題が無くなります。

## Wordコンバータ～変換～

必要な情報だけになったhtmlは、後は変換するだけです。
でも、Word固有のフレームや目次を後で追加する問題を解決するために、Word側でdocmファイルを用意します。その後、Pythonからpywin32ライブラリを使って、docm → html変換したテンプレを作成します。
参考URL：[Python(pywin32)でWordを操作する[2] - Wordを起動／終了する](https://qiita.com/int_main_void/items/8776cc4b9b05478273b3)

なお、テンプレの元になるWordはオリジナルの文字列をフィールドコードでを使う仕様にしています。
これはのstart~endを再編集の目印にして、他の箇所はWordで編集した情報のまま使うためです。

![2024-12-23_20h05_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140206/8c267501-08ed-1ac5-0195-9890fa9d6b64.png)


後は目印の範囲にhtmlを挿入するだけで終わればよいのですが、Wordはhtmlと違って印刷用紙サイズに縛られがちなアプリです。
全てにおいて用紙サイズに配慮する必要であり、これはWordにとってネイティブ言語(?)であるVBAをpywin32経由で実行しました。
でも、VBAの遅さを舐めてました…なるべくhtmlの前加工で行ったほうが良いと反省しています。

VBAでは
- png/svgは画像サイズがA4を越えているかチェックして、サイズを小さくします
- 画像がリンク先に存在しているのでdocファイルに埋め込みます
- スタイルの再適用をします
- 目次の更新を行います

最後、不要一時ファイルの削除や、ファイルの移動を行ってファイルを開きます。
以上で、自動変換の行程は終了です。

# 作ってみて思ったこと

いざ、中身を見ると、若干ズレが体裁上の問題はまだ残っています。
でも、私のプロジェクトをWord化したところ100ページくらいの内容になったので、転写しなくて済むだけでも自動化ツールはありがたいです。後は手動で直し、修正内容を汎化してツールに機能追加してゆきます。

なお、Wordの1ファイルにまとめたことで、文章校正ツールが文字のゆらぎを見つけてくれます。Redmineで単ページの文章を作ってたら気が付かない点でして、まとめて提出したことでの嬉しい誤算でした。

## 再編集はしやすい？

主な編集手段は3択くらいでしょうか。

1. Wordで直接編集する
WordはWYSIWYG（What You See Is What You Get）を体現しているため、体裁に関する編集は非常に直感的で簡単です。
提出物の外見やフォーマットを調整したい場合、この方法が最適です。
2. RedmineでWikiを編集し、再度ダウンローダで変換する
中身の更新が必要な場合、Redmine上のWikiを直接編集して同期を取ります。これを基本の編集とします。
3. Wikiページのrawデータを編集し、アップロード後に変換する
中身の編集量が多い場合には、Wikiのrawデータをローカル環境で編集し、アップローダでサーバに反映させます。
ダウンローダはWikiのテキスト情報や添付ファイルを取得しているため、エクスプローラーから目的のファイルを直接好きなエディタで編集することが可能です。

この中で、3番が特徴的で気に入ってます。
ダウンロード処理によって、ローカルに保存されたWikiのテキスト情報やSVGファイルはテキスト形式で保存されおり、Grep置換が可能です。
ちょっとした文字の揺らぎはGrep置換により、文字列を一括で検索・置換できます。
更に、SVG内のテキストも置換できるので、画像内の文字列も修正可能です。
参考URL [VSCodeで複数ファイル内のテキストの一括置換する方法、等の忘備録](https://qiita.com/cp3/items/acc99399348331a325fd)

RedmineのWikiでもそうですが、Web媒体の情報を一括で編集する方法は需要が少なすぎてフロント側には提供されないと思います。
情報が増やせば増やす程、修正する時には一つずつ力ずくで修正するしかなく、地味にストレスの溜まる行為でした。
この点は、編集機能が拡張されたと言ってよいのかと思います。
※ アップローダ機能は説明を省略しますが、変更ファイルだけを抽出して、API越しにテキストと添付ファイルをアップする仕様で作りました。
APIのアップロード機能であるため、一括で修正した情報もWiki側の編集履歴として残ります。

:::note info
ダウンロードした各ファイルをバージョン管理すると、再編集の際に変化点をローカルで管理できます。
Redmine側と2重管理になるので、アップロード前の一時的な変化点管理に使うと良いです。
:::


## 画像の再編集について

画像ファイルのSVGの場合、diagrams.net（旧名 draw.io） を利用してGUIで編集すると楽な再編集ができるのでお勧めです。
これは、ローカルもRedmine上も同じ操作で編集出来るので、同じ操作感で気が付いた時にサッと編集できるからです。

- ローカル環境
diagrams.netのデスクトップ版（draw.io Desktop）を使用
VScode + Draw.io Integration を使用
- Redmine環境
Redmineではdraw.ioプラグインを使用して表示や編集をサポート

ちなみに、Mermaidによる作図も良いのですが、図形の位置関係を狙うと作図が難しかったり、Word作図がメインの人が馴染めませんのでdraw.ioが多くのシーンで使い易いと思っています。


:::note info
余談ですが、RedmineにおけるSVG表示は標準ではできませんでした。
参考URL: [Feature #2047: Add SVG support, like images](https://www.redmine.org/issues/2047)

ですが、draw.ioプラグインを使用することで、Webでのsvg編集と同時に、svgの表示がマクロ構文で可能になります。
参考URL: [Redmineでdraw.ioプラグインを使用する](https://qiita.com/juno-nishizaki/items/f3e5f192935de01ce49b)

有料のWikiはMermaidかPluntUMLのダイアグラムツールはサポートしていることが多いですか、GUIの作図ツールの組み合わせは無いのでは？(詳しく知りませんが)。
:::


# 今後は

環境を新しくして、Office360とRedmineの最新バージョンの組み合わせで動くように作り、なるべく一般化したいです。
そこまでしたら、GitHubにソースコードを公開しようかと。

でも、クライアント側のpythonで何とかしようとしてますが、サーバー側で解消する方法がより正攻法で需要があるとは思ってます。
(どうしたら良いか方向性は見えてません)

# 最後に

PJリーダや、Wordの事が嫌いになりそうだったのですが、怒りや苦しみの力を開発にぶつけることができ、技術で課題を解決しました(？解決しつつあります)。

怒りから先に手が出ずに、きちんと要求を受け取って技術で対応できるとは…
私にも健全なエンジニア魂があったことに自分でも驚いてます。
なんだがこの先もエンジニアやってゆける気がしてきましたよ!!

Qiita初投稿も含めて全て良い経験でした。
また、アウトプット出来るネタ(次はPJリーダとのぶつかりあいで得られた知見とか？)があればやってみます。
ありがとうございました...！