最近は原稿やドキュメントの作成に、Microsoft Wordを使用しています。以前はテキストエディタで直接Markdownファイルを作成していました。それを、Microsoft Wordを使用し、docxファイルを作成するように変えた理由は以下の通りです。
- 校正ツールを使用するようになった。
- Microsoft Copilotを利用するようになった。
- 作成したドキュメントをMicrosoft CopilotのRAGとして利用するために、OneDrive for BusinessやSharePointに置くようになった。
そこで課題となったのが、最終成果物としてMarkdownファイルが必要な場合に、どのようにしてdocxファイルからMarkdownファイルを作成するかです。
解決する課題
本記事で解決する課題は以下の通りです。
- 複数のdocxから複数Markdownファイルを変換する必要がある。
- 変換後のMarkdownファイルを結合する必要がある。
- 結合するMarkdownファイルの組み合わせと、出力するファイルの組み合わせを指定できる必要がある。
- 変換後のMarkdownファイルを加工する必要がある。(加工内容は変換プログラムの仕様に依存する)
- 元のdocxは改訂するため、繰り返し、何度も変換できる必要がある。
上記の課題を解決するには、手動で変換や加工をするのではなく、スクリプトで自動化する必要があります。というよりも、手作業ではやっていられません。
docxからMarkdownへの変換処理
一番重要なdocxからMarkdownへの変換処理はPandocを使用しました。Pandocはマークアップ形式のファイルを変換するコンバーターです。docxとMarkdownの両方ともサポートしています。
次のように実行すると、docxファイルからGFM(GitHub Flavored Markdown)形式のファイルを出力します。
pandoc -f docx -t gfm --wrap=preserve -o output-file input-file
docx内に含まれる画像も書き出す場合は、–extract-mediaオプションを使用します。以下のように実行するとmediaディレクトリを作成し、docx内に埋め込まれている画像も出力します。
pandoc -f docx -t gfm --wrap=preserve --extract-media=Media -o output-file input-file
pandocはWordで設定したテキストスタイルに合わせて出力します。「見出し」はマークダウンでも「見出し」として出力し、表はテーブル構文で出力します。セル内の行そろえも反映されます。箇条書きはマークダウンでもリストとして出力されます。
pandocによる変換処理は、ほとんど問題ない状態のファイルを出力するのですが、私の原稿特有の問題やテクニカルドキュメント特有の問題で、少し課題が残っています。
Makefileの作成
繰り返し実行可能で、自動化するためにはスクリプト化が必要です。今回はMakefileを作成し、makeコマンドで実行可能にしました。
複数のファイルを変換する
入力ファイルはMakefile内で定義します。INPUT_FILESという変数に代入します。
INPUT_FILES := \
CHAPTER01-01.docx \
CHAPTER01-02.docx \
CHAPTER02-01.docx \
CHAPTER02-02.docx
入力ファイルが入っているディレクトリをSRC_DIRという変数に代入し、生成したファイルの出力先ディレクトリをDST_DIRという変数に代入します。
SRCDIR := docx
DSTDIR := markdown
この例ではdocxディレクトリにある入力ファイルを変換し、markdownディレクトリに出力します。
変換処理は以下のようになります。
all: $(MERGE_TARGETS) cleanup
$(DSTDIR)/%.md: $(SRCDIR)/%.docx
mkdir -p $(DSTDIR)
pandoc -f docx -t gfm --wrap=preserve -o $@ $<
このスクリプトにより、INPUT_FILESで指定したdocxがMarkdownに変換されます。MERGE_TARGETS変数は後ほど定義します。
画像挿入のエスケープを削除する
Wordにはドキュメントの中に画像を挿入する機能があります。しかし、私の原稿では画像挿入機能は使用せず、原稿内ではMarkdownの画像挿入の構文で画像を指定しています。
![Image Title](File Path)
理由は画像挿入機能で挿入された画像は、元ファイルからリサイズされてしまうからです。Wordには最終的には画像ファイルへのリンク機能もあります。この機能を使えば、元画像が使われるのですが、pandocではリンクした画像を出力できません。docx内に埋め込まれた画像のみに対応しています。このような理由でMarkdownの画像挿入の構文で原稿も書いています。
pandocはMarkdown化した後もテキストとして画像挿入構文を表示するために、以下のようにエスケープ文字を追加してしまいます。
\![Image Title\](File Path)
pandocのオプションで回避できるか試してみましたが、gfmを使っているときはできませんでした。
そこで、Makefileの中でsedを使ってエスケープ文字を削除するようにしました。
all: $(MERGE_TARGETS) cleanup
$(DSTDIR)/%.md: $(SRCDIR)/%.docx
mkdir -p $(DSTDIR)
pandoc -f docx -t gfm --wrap=preserve -o $@ $<
sed -i '' 's/\\\!\[/\!\[/g' $@
sed -i '' 's/\\\]/\]/g' $@```
コードブロックのインデントを削除する
私の原稿ではプログラミングコードやシェルスクリプトなどがあります。Markdownでは、コード部分に対するマークアップとして、インラインコード構文とコードブロック構文があります。
インラインコード構文
インライン構文は行内の特定の範囲だけをコードとして指定します。通常の文の中に関数名などがあるときに、関数名だけをインラインコードとしてマークアップするという使い方です。pandocは「Verbatim Char」という名前のスタイルが割り当てられた文字列をインラインコードとして出力します。
たとえば、「文字列を出力するにはprint()関数を使用します。」という文章があり、「print()」という部分にだけ「Verbatim Char」スタイルが割り当てられていると、pandocは次のようなMarkdownを出力します。
文字列を出力するには`print()`関数を使用します。
コードブロック構文
ブロックコードはパラグラフ全体をコードとして出力する構文です。「“`」と「“`」で囲まれた範囲がコードブロックとなります。pandocでは「Source Code」というスタイルを割り当てた範囲はエスケープされる文字を使っても、エスケープされません。これを利用して、原稿内でも「“`」を使って記述し、「“`」から「“`」までの範囲に「Source Code」スタイルを割り当てます。
インデントの削除
pandocは「Source Code」スタイルを割り当てた範囲をエスケープせずに出力してくれるのですが、同時に、各行の先頭に4文字の半角スペースを挿入し、インデントさせてしまいます。変換後のMarkdownからこのインデントを削除する必要があります。この処理もMakefileに記述します。ここではawkを使いました。
all: $(MERGE_TARGETS) cleanup
$(DSTDIR)/%.md: $(SRCDIR)/%.docx
mkdir -p $(DSTDIR)
pandoc -f docx -t gfm --wrap=preserve -o $@ $<
# 画像挿入記法のエスケープ削除
sed -i '' 's/\\\!\[/\!\[/g' $@
sed -i '' 's/\\\]/\]/g' $@
# コードブロック内および開始行(```)に付いた先頭4文字のスペースを削除
awk ' \
/^ ```/ { sub(/^ /,""); in_code = !in_code; print; next } \
in_code && /^ / { sub(/^ /,""); print; next } \
{ print } \
' $@ > [email protected] && mv [email protected] $@
空白行の追加(2025年1月12日追加)
pandocが生成したMarkdownファイルは、変換元のファイルの末尾に入っていた空白行が削除されます。そのため、マージしたときに、マージ前後のファイルの行間に空白行が入りません。これに対応するため、Markdownファイル生成後に空白行を追加する処理を追加します。
$(DSTDIR)/%.md: $(SRCDIR)/%.docx
mkdir -p $(DSTDIR)
pandoc -f docx -t gfm --wrap=preserve -o $@ $<
# 画像挿入記法のエスケープ削除
sed -i '' 's/\\\!\[/\!\[/g' $@
sed -i '' 's/\\\]/\]/g' $@
# コードブロック内および開始行(```)に付いた先頭4文字のスペースを削除
awk ' \
/^ ```/ { sub(/^ /,""); in_code = !in_code; print; next } \
in_code && /^ / { sub(/^ /,""); print; next } \
{ print } \
' $@ > [email protected] && mv [email protected] $@
# --- ここで末尾に空白行を1行追記する ---
echo "" >> $@
Markdownファイルを結合する
原稿のdocxは複数のファイルに分割して作成しています。私はTypolessという校正ツールを使用しています。Typolessは朝日新聞社がSaaSで提供しているAI校正ツールです。
Word用のアドインがあり、Word内から直接利用できます。しかし、文字数に制限があります。書籍の原稿はCHAPTER単位でファイルを分割するのですが、CHAPTERごとだとTypolessの文字数制限をオーバーしてしまいます。そこで、原稿の段階ではセクション単位でファイルを分割し、Typolessを利用できるようにしています。Markdownに変換後、CHAPTER単位になるように、Markdownファイルを結合します。
結合するファイルの定義
結合するファイルはCHAPTERごとに定義します。CHAPTER 01とCHAPTER 02のみある場合は次のように定義します。
# CHAPTER01の結合ファイルリスト
MERGE_FILES_CHAPTER01 := \
\(DSTDIR)/CHAPTER01-01.md \
\(DSTDIR)/CHAPTER01-02.md
# CHAPTER02の結合ファイルリスト
MERGE_FILES_CHAPTER02 := \
\(DSTDIR)/CHAPTER02-01.md \
\(DSTDIR)/CHAPTER02-02.md
# 全結合ファイルリスト
MERGE_FILES := \
$(MERGE_FILES_CHAPTER01) \
$(MERGE_FILES_CHAPTER02)
ファイルの結合
ファイルを結合させる処理はcatを使用し、CHAPTERごとに定義しました。
$(DSTDIR)/CHAPTER01.md: $(MERGE_FILES_CHAPTER01)
cat $^ > $@
$(DSTDIR)/CHAPTER02.md: $(MERGE_FILES_CHAPTER02)
cat $^ > $@
完成したMakefile(2025年1月12日更新)
このような感じで処理を実装していたMakefileですが、完成形は次のようになりました。CHAPTERごとに結合したあと、結合元のファイルは削除する処理も追加しています。make cleanで生成したファイルを削除する処理も入っています。
# 入力ファイルリスト(スペース区切りで指定)
INPUT_FILES := \
CHAPTER01-01.docx \
CHAPTER01-02.docx \
CHAPTER02-01.docx \
CHAPTER02-02.docx
# 出力ディレクトリ
SRCDIR := docx
DSTDIR := markdown
# 結合ターゲット
MERGE_TARGETS := \
$(DSTDIR)/CHAPTER01.md \
$(DSTDIR)/CHAPTER02.md
# CHAPTER01 の結合ファイルリスト
MERGE_FILES_CHAPTER01 := \
$(DSTDIR)/CHAPTER01-01.md \
$(DSTDIR)/CHAPTER01-02.md
# CHAPTER02 の結合ファイルリスト
MERGE_FILES_CHAPTER02 := \
$(DSTDIR)/CHAPTER02-01.md \
$(DSTDIR)/CHAPTER02-02.md
# 全結合ファイルリスト
MERGE_FILES := \
$(MERGE_FILES_CHAPTER01) \
$(MERGE_FILES_CHAPTER02)
# デフォルトターゲット: 全ての処理と cleanup を実行
all: $(MERGE_TARGETS) cleanup
# .docx -> .md の変換ルール(pandoc 使用)
$(DSTDIR)/%.md: $(SRCDIR)/%.docx
mkdir -p $(DSTDIR)
pandoc -f docx -t gfm --wrap=preserve -o $@ $<
# 画像挿入記法のエスケープ削除
sed -i '' 's/\\\!\[/\!\[/g' $@
sed -i '' 's/\\\]/\]/g' $@
# コードブロック内および開始行(```)に付いた先頭4文字のスペースを削除
awk ' \
/^ ```/ { sub(/^ /,""); in_code = !in_code; print; next } \
in_code && /^ / { sub(/^ /,""); print; next } \
{ print } \
' $@ > [email protected] && mv [email protected] $@
# --- ここで末尾に空白行を1行追記する ---
echo "" >> $@
# 結合ルール
$(DSTDIR)/CHAPTER01.md: $(MERGE_FILES_CHAPTER01)
cat $^ > $@
$(DSTDIR)/CHAPTER02.md: $(MERGE_FILES_CHAPTER02)
cat $^ > $@
# 結合後に元のMarkdownファイルを削除
cleanup:
rm -f $(MERGE_FILES)
# クリーンアップターゲット(Textディレクトリは削除せず、MERGE_TARGETS のみ削除)
clean:
rm -f $(MERGE_TARGETS)
# 強制的に結合ファイルを再生成
.PHONY: all cleanup clean