10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

動機

GPTやClaudeなどのLLM(大規模言語モデル)が日常的に使われるようになり、Markdownは単なる技術ドキュメントの形式を超え、AIとの「共通言語」としての重要性を増しています。

しかし、実務では依然として「PDF」や「Word (DOCX)」形式での提出が求められます。

今回は、Javaのエコシステムで最も強力なMarkdownパーサの一つ flexmark-java を使い、Markdownから「デザインされたPDF」と「Wordファイル」を一発生成する方法を紹介します。

Javaのサーバ上など特殊な環境でのニッチなニーズなので、素直にPythonの変換ツールを使えるなら使えばいいのですが・・・

コード

以下は、MarkdownファイルとCSSファイルを読み込み、HTML、PDF、DOCXを一括出力するコードです。(Scalaで書いていますがJavaでもほぼ同様の記述が可能です)

Flexmarkマークダウンパーサとその拡張機能を利用して一発変換します。

build.sbt
// https://mvnrepository.com/artifact/com.vladsch.flexmark/flexmark-all
libraryDependencies += "com.vladsch.flexmark" % "flexmark-all" % "0.64.8"
// https://mvnrepository.com/artifact/com.vladsch.flexmark/flexmark-docx-converter
libraryDependencies += "com.vladsch.flexmark" % "flexmark-docx-converter" % "0.64.8"
Main.scala
object Main {

  def main(args: Array[String]): Unit = {
    val mdFile = args(0)
    val cssFile = args(1)
    val pdfFile = mdFile + ".pdf"
    val xhtmlFile = mdFile + ".html"
    val docxFile = mdFile + ".docx"

    Using(Source.fromFile(mdFile)) { mdSource =>
      Using(Source.fromFile(cssFile)) { cssSource =>
        
        //拡張機能のオプションを設定する
        val options = new MutableDataSet()
          .set(Parser.EXTENSIONS, java.util.List.of(
            TablesExtension.create(), //「表」の拡張
            GitLabExtension.create(), //GitLab拡張(いくつかのGitLab拡張マークダウンが利用できるようになる)
            EmojiExtension.create() //絵文字拡張、PDFでは表示されなかった。
          ))

        //マークダウンのパースを行う
        val parser = Parser.builder(options).build()
        val document = parser.parseReader(mdSource.bufferedReader())

        //XHTML作成と出力
        val htmlRenderer = HtmlRenderer.builder(options).build()
        val rawHtmlFragment = htmlRenderer.render(document)
        val xhtmlContent = createFullXhtml(rawHtmlFragment, cssSource.mkString)
        writeFile(xhtmlFile, xhtmlContent)

        //PDF出力
        PdfConverterExtension.exportToPdf(pdfFile, xhtmlContent, "", options)

        //DOCX出力
        val template = DocxRenderer.getDefaultTemplate()
        val docxRenderer = DocxRenderer.builder(options).build()
        docxRenderer.render(document, template)
        template.save(new File(docxFile))
      }
    }
  }


  /**
   * Bodyフラグメントを受け取り、完全なXHTML文字列を返す
   */
  def createFullXhtml(bodyHtml: String, css: String): String = {
    //KaTexをHTMLで表示するためにkatex.min.cssとkatex.min.jsを挿入
    //また最後にKaTexの描画のScriptを埋め込んでいる。当然PDFやDocxでは表現できない。
    s"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
       |<html xmlns="http://www.w3.org/1999/xhtml">
       |<head>
       |  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
       |  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.css">
       |  <script src="https://cdn.jsdelivr.net/npm/katex@0.16.25/dist/katex.min.js"></script>
       |  <style type="text/css">
       |    $css
       |  </style>
       |</head>
       |<body>
       |$bodyHtml
       |<script>
       |    (function () {
       |      document.addEventListener("DOMContentLoaded", function () {
       |        var mathElems = document.getElementsByClassName("katex");
       |        var elems = [];
       |        for (const i in mathElems) {
       |            if (mathElems.hasOwnProperty(i)) elems.push(mathElems[i]);
       |        }
       |
       |        elems.forEach(elem => {
       |            katex.render(elem.textContent, elem, { throwOnError: false, displayMode: elem.nodeName !== 'SPAN', });
       |        });
       |    });
       |})();
       |</script>
       |</body>
       |</html>""".stripMargin
  }

説明

PDFを作成する流れは

  1. マークダウンをパースする(AST変換)
  2. ASTからHTMLを作成する
  3. HTMLからXHTMLにする(createFullXhtmlメソッド)
  4. XHTMLからPDF出力する

です。Word(.docx)を作成する場合は、ASTから直接作成しています。

PDFでの文字の取り扱い

PDFを出力する際、何もしなければ日本語文字が出力できません。
IPAのフォントなどをダウンロードし、渡すCSSに以下のような形で記述しておきます。

sample.css
@font-face {
  font-family: 'IPAexGothic';
  src: url('file:<フォントのパス>/ipaexg.ttf');
  font-weight: normal;
  font-style: normal;
}
body {
    font-family: 'IPAexGothic';
}
/* 中略 */

出力例

HTML

タイトルなし.png

PDF

タイトルなし2.png

DOCX

MS OfficeではなくLibre Officeで開いてます
タイトルなし3.png

10
0
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
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?