[2025/01/12更新] MS WordのdocxからMarkdownファイルに変換する

最近は原稿やドキュメントの作成に、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

著書紹介

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Akira Hayashi (林 晃)のアバター Akira Hayashi (林 晃) Representative(代表), Software Engineer(ソフトウェアエンジニア)

アールケー開発代表。Appleプラットフォーム向けの開発を専門としているソフトウェアエンジニア。ソフトウェアの受託開発、技術書執筆、技術指導・セミナー講師。note, Medium, LinkedIn
-
Representative of RK Kaihatsu. Software Engineer Specializing in Development for the Apple Platform. Specializing in contract software development, technical writing, and serving as a tech workshop lecturer. note, Medium, LinkedIn

目次