動機
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マークダウンパーサとその拡張機能を利用して一発変換します。
// 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"
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を作成する流れは
- マークダウンをパースする(AST変換)
- ASTからHTMLを作成する
- HTMLからXHTMLにする(
createFullXhtmlメソッド) - XHTMLからPDF出力する
です。Word(.docx)を作成する場合は、ASTから直接作成しています。
PDFでの文字の取り扱い
PDFを出力する際、何もしなければ日本語文字が出力できません。
IPAのフォントなどをダウンロードし、渡すCSSに以下のような形で記述しておきます。
@font-face {
font-family: 'IPAexGothic';
src: url('file:<フォントのパス>/ipaexg.ttf');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'IPAexGothic';
}
/* 中略 */


