3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MarkdwonをDocxへ変換する(without pandoc)

Last updated at Posted at 2020-08-30

はじめに

本記事は表題を実現する一例です。
より良い方法や実現できないパターンがあることはご承知おきください。
typoなど凡ミスもご容赦いただけるとありがたいです。

背景

筆者も世のサラリーマンしがらみによってWord形式の納品/提供が求められました。
しかしどーーーーーーしてもWord管理はしたくない。
しかもソースコード管理上すでに良いMarkdownファイルがあるにも関わらず変換しなければならない。
こんな作業は**ブルシットジョブだ!クソだ!**と叫びたくなったので何とかする方法を調べました。

TL;DR

  • pandocは使いません(注1)
  • 簡単なMarkdownであればそれなりにできた
  • 工程が多くて手間
  • 自動化できそうにない
  • mermaidPlantUMLなど特殊な表現には別途工夫が必要そう

注1: pandocの変換は読み込めるファイルを作れるものの表が崩れるなど手直しの良いファイルしか生成できませんでした

本方法でできること/できないこと

できること

  • 比較的シンプルなMarkdownの変換
  • 比較的元の情報を維持した変換

できないこと

  • jsなどのプログラム処理によるレンダリング
    例えばmermaidなど。画像ファイル化して貼り付けてあればOK
  • 可逆変換。本方法では非可逆に変換します。そのため、Wordに直接手直しされると反映が面倒です。
  • cssの維持。htmlをWordで読み込むまでは良いが、最後docxとして保存するとその情報が破棄されます。markdownの書き方によっては体裁が崩れるかもしれません。

全体の流れ

  1. VisualStudioCodeでMarkdownを作成する
  2. VisualStudioCodeの拡張Markdown Preview Enhancedで確認する
  3. Markdown Preview Enhancedの機能でHTML出力する
  4. 出力したHTMLをGoogle Chromeで開く
  5. ページを"完全な保存"する
  6. [Optional]保存した_filesフォルダ内の画像を適当な幅にリサイズする
  7. 保存したhtmlファイルをWordで開く

開発環境

Note: 特にバージョン依存しそうな処理はないので、バージョン情報は省略します

1. VisualStudioCodeでMarkdownを作成する

今回は、どんなMarkdown表現がどうなるのか、を確認するためにmarkdown-it demoへ記載されている内容を採用します。
またもう一点、いわゆるシステム仕様書、っぽいものも用意しました。

2. VisualStudioCodeの拡張Markdown Preview Enhancedで確認する

インストール方法
他にもわかりやすいQiita記事などいっぱいあるのでそちらを参照してください

プレビュー結果 markdown-it demo

image.png

プレビュー結果 システム仕様書っぽいもの

image.png

3. Markdown Preview Enhancedの機能でHTML出力する

プレビュー内で右クリックしてHTML/HTML(offline)を選択
image.png

Note: ここで作成したHTMLをWordで開くことも当然可能ですが、手元で試していると画像が読み込めないといった現象もあったので、次の工程に進んだほうがより確実です。

4. 出力したHTMLをGoogle Chromeで開く

URLなどは伏せています。
image.png

5. 出力したHTMLをGoogle Chromeで開く

ページを開いている状態でCtrl+Sや右クリックコンテキストメニューでページを保存します。
image.png
ウェブページ、完全であることを確認してください。

[Optional]保存した_filesフォルダ内の画像を適当な幅にリサイズする

Wordで開くと画像は元サイズのまま開かれます。
そのため、大きい画像を読み込むとファイルをはみ出してしまい、さすがにそのまま渡すにはちょっと問題があります。
image.png

このような問題は他にもありそうですが、代表的な画像ファイルの取り扱いだけこの記事では触れておきます。
Wordでhtmlが参照している画像を読み込む際のサイズは、imgタグ内に書かれたwidth/height値に依存します。
つまり大きさを調整したいimgファイルのタグオプションにwidth値とheight値を直接指定すれば良いです。
上記の例では出力したhtml内に次のようなimgタグが記載されています。

<img src="./images/InternalArchtecture.png" alt="&#x5185;&#x90E8;&#x30A2;&#x30FC;&#x30AD;&#x30C6;&#x30AF;&#x30C1;&#x30E3;"></p>

InternalArchtecture.pngの画像サイズを調べてみると、width=1071 height=651でした。
image.png

Wordの横幅に入るよう半分のサイズにタグ上設定します。

<img src="./images/InternalArchtecture.png" width=535 height=325 alt="&#x5185;&#x90E8;&#x30A2;&#x30FC;&#x30AD;&#x30C6;&#x30AF;&#x30C1;&#x30E3;"></p>

これを適用してWordで開き直すと次のように縮小されています。
image.png

本作業を手作業でやるには面倒すぎるので_files内にある画像ファイルが指定サイズ以上だったら、比率を維持して指定サイズに縮小するPythonプログラムを作成しました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import codecs
import glob
from PIL import Image

def usage():
  print("\nUsage: python Resize.py $Filepath $ResizeValue\n")
  print('  Biref: This program resize images in "_files" folder. Set target file and resize value with follow arguments.\n')
  print("  - Filepath: Set the absolute or relative path of the html file.")
  print('  - ResizeValue: Set any Int value. The images resize to this value if it larger than this value.')

def resize(filepath, resize_value):
  print("\nTarget path: " + filepath)
  print("ResizeValue: " + str(resize_value))
  print("Start to resize images")
  filename = os.path.basename(filepath)
  filename_title, filename_ext = os.path.splitext(filename)
  resource_files = glob.glob(os.path.join(filepath.replace(filename_ext, '_files'), '*'))

  for rf in resource_files:
    try:
      img = Image.open(rf)
      img_name = os.path.basename(rf)
      print("Detect image file: " + img_name)
      if img.height > resize_value or img.width > resize_value:
        print(img_name + " is need to resize.")
        print(img_name + "'s height is " + str(img.height) " and new width is " + str(img.width))
        if img.height > img.width:
          print(img_name + "'s height is larger than " + str(resize_value))
          ratio = img.height / resize_value
        else:
          print(img_name + "'s width is larger than " + str(resize_value))
          ratio = img.width / resize_value
        print(img_name + " is resized by a factor of " + str(ratio))
        img_new_height = int(img.height / ratio)
        img_new_width = int(img.width / ratio)
        print(img_name + "'s new height is " + str(img_new_height) " and new width is " + str(img_new_width))
        print("Update " + filename + " by new size")
        with codecs.open(filepath, 'r', 'utf-8') as html:
          text = html.read()
          after = text.replace(img_name + '"', img_name + '" width=' + str(img_new_width) + ' height=' + str(img_new_height))
        with codecs.open(filepath, 'w', 'utf-8') as html:
          html.write(after)
      else:
        print(img_name + " is not need to resize.")
    except OSError as e:
      pass
  print("All done. Success!")


def main():
  if len(sys.argv) != 3 or not os.path.exists(sys.argv[1]) :
    usage()
  else:
    resize(sys.argv[1], int(sys.argv[2]))

if __name__ == "__main__":
  main()

実行コマンド例:

python Resize.py ./SavedHtml/markdown-it_demo.html 550

7. 保存したhtmlファイルをWordで開く

docxファイルにするにはhtmlで開いたあと、"名前をつけて保存"でdocx保存する必要があります。
markdown-it demo word変換後
システム仕様書っぽいもの

最後に

今回の変換例は本当に一部のパターンでしか確認できておりませんし、十分な検証ができているわけではありません。
pandoc以外にもこんなやり方があるんだな程度にご参考ください。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?