Markdown でビジネス文書を作成する

まとめ

  • Pandoc’s Markdown という一方言には::: という汎用Divに対応する記法がある。
  • {markdown, latex} → html は pandoc のみでできる。別途 css を用意するといい。
  • {html, latex} → markdown は pandoc のみでできる。
  • {html, markdown} → latex はフィルタが必要。別途 sty を用意するといい。

ビジネス文書

しばらく前に「公文書を Markdown で」という話題があった。図入りの面倒くさいものや白書のような普通の書籍ほどの長大なものや法律の条文のような特殊なものは別として、圧倒的に多いであろう1枚ペラ程度の通達の類の文書には、 Markdown はとても向いていると思う。簡単なものほど Word のような高機能文書作成ソフトを使わずに Markdown で済ませたい。

この様式 ((英文レターの形式を調べると、アメリカ式「フル・ブロック・スタイル」とイギリス式「フル・インデント・スタイル」が見つかる。日本のいわゆるビジネス文書はこのイギリス式「フル・インデント・ス タイル」にとてもよく似ている。特に電磁的文書ならアメリカ式がとても楽だし合理的だと思うのだが、なんと言っても理屈ではない習慣だからそう簡単に廃れることはないだろう。))は、役所が作成する公文書 ((検索すると多くの自治体の規程が見つかる。たとえば墨田区佐伯市など。))から会社、さらには小学校や PTA、町内会までよく浸透している。

しかし Markdown では、この類の文書の様式に欠かせない右寄せ(右揃え)ができないのが最大のネックだ。意味見た目の分離という観点から言えば右寄せ(右揃え)は見た目なのだが、ここではもうそう呼ぶ ことにする。

Markdown

機能を絞り込んで簡潔であることが Markdown の特長なのだから、あれもこれも盛り込んでいけば最初から HTML を使えばいいとなって元も子もない。とは言え不満はなんとかしたい。Markdown で書きながらも最終的には HTML にすることを想定して直接 HTMLタグ (<div style="text-align:right">)を書いてしまうという手段もあるのだが、せっかくの Markdown なのだし他の形式への変換も考えるとなるべくそういうことはやりたくない。

ここは Markdown 記法に HTML の汎用タグ <div><span> を表せるものがあれば万能なのになあ……と思っていたら、Pandoc’s Markdown という一方言にはこれが存在していることに今さらながら気がついた。

Pandoc の Markdown 拡張

Pandoc’s Markdown という一方言には::: という汎用の Div に対応する記法がある。ここでは触れないが、汎用の Span に対応する []{} 記法もある。

この記法は Pandoc で考案されたようだ ((Syntax for divs))。他の方言ではあまりサポートされていないようだ。HackMD (CodiMD, HedgeDoc) では ::: はやや違った意味にされ、記法も違い限定された語だけしか使えないようだ。

汎用タグを使いすぎれば意味見た目の分離が台なしになることは解っているのだが、最終手段として便利であることは間違いない。

たとえばファイル名 input.md に、Pandoc’s Markdown で書く。

::: {.myaddress}
○△□町会\
会長 ▼▲ ■◆
:::

Markdown から HTML へ

pandoc の標準機能で、特に何もしなくてもいい。コマンドラインで

pandoc --from markdown input.md -to html -o output.html

これにより作成される output.html は次のようになる。

<div class="myaddress">
<p>○△□町会<br />
会長 ▼▲ ■◆</p>
</div>

つまり、Markdown で書いた {.myaddress} がクラス名になる。このクラス名に対応する CSS は別途用意しておく。たとえばファイル名 myaddress.css に

.myaddress {
        text-align: right;
        text-indent: 0pt;
}

と書いておき、コマンドラインで

pandoc --from markdown input.md --to html --output output.html --css myaddress.css --standalone

として利用する。スタイルシートへのリンクがドキュメントヘッダに含まれるため、–standalone オプションも同時に付ける必要がある。

Markdown から LaTeX へ

出力形式を LaTeX とすると、::: は除去されてしまう。つまりコマンドラインで

pandoc --from markdown input.md --to latex --output output.tex

とすると、作成されるファイル output.tex は次のようになる。

○△□町会\\
会長 ▼▲ ■◆

pandoc の作者によるフィルタの例 latexdivs.py を使う ((サンプルのフィルタ latexdivs.py をそのまま使うには、入力ファイルのクラス名を書くところに .latex を加えておく必要がある。つまり input.md は ::: {.latex .myaddress} のように書く。))と、出力を

\begin{myaddress}

○△□町会\\
会長 ▼▲ ■◆

\end{myaddress}

のようにできる。これを実際に LaTeX で処理するには、 myaddress 環境を別途定義しておく必要がある。たとえば myaddress.sty に

% myaddress
\newenvironment{myaddress}
{\begin{flushright}}
  {\end{flushright}}

と書いておき、コマンドラインで --include-in-header で読み込ませる。

pandoc --from markdown input.md --to latex --output output.tex --include-in-header myaddress.sty --standalone

これにより作成される出力ファイルの内容は

\documentclass (略)
  (略)
% myaddress
\newenvironment{myaddress}
{\begin{flushright}}
  {\end{flushright}}
  (略)
\begin{document}

\begin{myaddress}

○△□町会\\
会長 ▼▲ ■◆

\end{myaddress}

\end{document}

のようになる。

形式の相互変換

以上、markdown → {html, latex} の例を示した。

自分のよく使う markdown, html, latex の相互変換についてまとめると、

  • {markdown, latex} → html は pandoc のみでできる。別途 css を用意するといい。
  • {html, latex} → markdown は pandoc のみでできる。
  • {html, markdown} → latex はフィルタが必要。別途 sty を用意するといい。

となる。

具体的な例

変換された html で利用する CSS ファイル
business.css
latex 出力のための pandoc フィルタ
business_md.py
latex 出力が利用する sty ファイル
business.sty
入力ファイル (markdown)
business-sample.md
pandoc --from markdown --to html --css business.css --standalone --output business-sample.html business-sample.md での出力ファイル (html)
business-sample.html
pandoc --from markdown --to latex --filter=./business_md.py --include-in-header business.sty --standalone --output business-sample.tex business-sample.md ((PDF に変換させるには、オプションはこれだけでは実は足りず、ここの例では --pdf-engine=lualatex -V documentclass=bxjsarticle -V classoption=pandoc -V classoption=jafont=auto -V indent=1zw -V pagestyle=empty を加えている。))での出力ファイル (latex)
business-sample.tex
pandoc --from markdown --to pdf --filter=./business_md.py --include-in-header=./business.sty --output business-sample.pdf business-sample.md ((前註に同じ。))での出力ファイル (pdf)
business-sample.pdf

2001–2010年ころの日本語システムフォント

これまでにも何度か書いているように、私自身は個人的には「日本語のある程度の長さのまとまった文章には、ゴシック体より明朝体のほうが向いている」と考えています。Web ページにおいても、です。

一方で、「Web ページはゴシック体」という意見が数多く見られます。おそらく、単純に「明朝派」か「ゴシック派」かで言えば、「ゴシック派」のほうがかなり多数のような印象があります。

システムフォントの歴史

いろいろ思い出すために、2000年頃以降のシステムフォント—大きなシェアを占めていた Windows と Mac にデフォルトで装備されているフォント—について、ざっと調べてみました。

Windows

2001XPMS明朝ゴシック 2.30 MS明朝 2.31
2006Vistaメイリオ 5.00 MS明朝ゴシック 5.00 MS明朝 5.00 (JIS X 0213:2004)
20138.1游ゴシック 游明朝

Mac

MacOS 9.2.2までOsaka 平成明朝 リュウミンライト-KL
2001OS X 10.0ヒラギノ角ゴ Pro W4・ヒラギノ明朝 Pro W3
200710.5ヒラギノ角ゴ ProN W4・ヒラギノ明朝 ProN W3
201310.9游ゴシック体 游明朝体 M

フリーフォント

私自身はこの時代より前から今日に至るまでずっと Linux (Debian) を常用していて、それにはデフォルトとか標準という考えがなく好みのフォントを使います。もう記憶が確かでない部分もあるのですが、主流だったと思えるものを拾い出してみました。

1998-1999渡邊フォント
2000-2003Kochi
2003-2004Sazanami
2007IPAゴシック・IPA明朝 (単体配布)
2010IPAexゴシック・IPAex明朝
2010Takaoフォント
2014源ノ角ゴシック / Noto Sans CJK JP
2017源ノ明朝 / Noto Serif CJK JP

参考:ブラウザーの歴史

1992mozaic
1994Netscape Navigator
1996NN3, IE3
1997NN4
2001IE6
2003Safari (10.3から。それ以前(10.2)の標準ブラウザはIEforMac)
2004Firefox 0.8
2006IE7
2008Chrome

漠然とですが、

  • 2001–2010年ころ、Web ページにとって明朝体は、技術的に「使い物にならな」かった
  • そのため、その頃とそれ以降、日本語の Web ページは圧倒的にゴシック体を主体としたものが多い
  • その環境で育った人たちは、もう「Web ページはゴシック体」が当たり前であり、技術的な問題が既に解決されても、むしろ明朝体だと違和感がある

のようなことではなかろうかと考えています ((翻って考えると、私の「日本語のある程度の長さのまとまった文章には、ゴシック体より明朝体のほうが向いている」という考えも、WWW 以前の、印刷物に接する時間が長かった(印刷物は言うまでもなく、本文は明朝系であることが圧倒的に多い)影響が強いのかもしれません。))。

2001年のMacOS Xにヒラギノというのは本当に画期的だったとは思うのですが、何しろシェアが違いすぎ、それにあぐらをかいた Windows のために「暗黒の10年」だったと言っても過言ではありません。ブラウザーの IE6 天下と軌を一にしています。

話はややずれますが、ハードウェアとしてのディスプレイが CRT から LCD になっていったのものこの頃でした。私が切り換えたのはだいぶ遅めの2005年ころでしたが、CRT ではいい具合にボケていた文字の輪郭が LCD だとくっきりしすぎて、文字として美しくなくなったのを覚えています。それからアンチエイリアスとかヒンティングなどを意識することになりました。

HTML の <ruby> に思うこと

ニュースサイトから記事本文を抜き出して利用しようという作業をやってみて、<ruby> 要素がほかの要素に比べてかなり異質なものあることに気がつきました。

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

実際に行った作業は「HTML からタグを除去して本文を取り出す」なのですが、単純に行って得られるものは上記のとおりで、それを「本文」とは呼べないと思うのです。

マークアップ言語

はじめに、

気象庁は「もっと二酸化炭素を出さないようにしなければなりません」と話しています。

という“内容”そのものがあります。これに“意味”をつけるために、たとえば「気象庁」は固有名詞という意味をつけるためにこれを <span> で囲み、それからこの文ひとつでひとつの段落という意味をつけるためにこれを <p> で囲みます。これがマークアップであり、そのためのタグであるはずです。

次の段階で、これをどう表示するか、たとえば固有名詞は文字を赤色にし、段落はそのはじめに改行する、というものはさらに分離してスタイルシートで記述することになっています。

逆向きに考えてみます。ブラウザで表示されるもののソースから表現のスタイルを取り除き、“意味”づけのタグを取り除いたら、残るのは“内容”そのものでなければならないはずです。

ところが今回の例では上述のように、余計なものが残ったままになります。

ルビは“内容”か

私は「ふりがなは内容そのものではなく、それに付け加えているもの」と考えますので、「余計なもの」と書きました。たとえば、ここの言葉は重要と思って下線を付ける、固有名詞ははっきり分かるように文字の色を他と違える、というのと同じで、内容に対して何らか助けのために付け加えているものだと思います。ですから、そういったものを剥ぎ取ったときには残っていてはならないと考えます。

そう考えたときに、現在の仕様の <rt> はかなり異質です。下線や色付けがタグ内の属性 (attribute) とそれに紐付けられるスタイルで表現されるように、ふりがなもタグではなく属性で示されるのが適当だと思います。

仮に <span>ruby という属性が定義されているとして

...<p><span class="colorC" ruby="きしょうちょう">気象庁</span>は「...」と<span ruby="はな">話</span>しています。</p>...

と書くのがもっとも自然のように感じます。

既存の仕様である <a> を逆に考えてみましょう。<a> にとって属性 href は、それがなければほとんど意味がないくらい重要なものです。

もっと<a href="example.html>二酸化炭素</a>を出さないようにしなければなりません

だからと言ってもしそれがタグになっていて

もっと<a>二酸化炭素<href>example.html</href></a>を出さないようにしなければなりません

と書かれるものだとしたら、その違和感がわかろうというものです。

<rt> のあり方

前節に書いたように、ルビは属性で指定されるのが自然だと思います。ではどうすればいいのか。

<ruby>関連要素って駄目駄目」に、この問題の歴史 (なんと不幸な!) から対案まで解説されていました。この記事をぜひじっくり読んでいただきたい。

この記事の「脱法ルビ」が、当面の対応策です。仕様に反せず、仕様内でできる策です。現状ここまでできるのですから、ここに示されているスタイルをレンダリングエンジン側で装備し、属性名を data-ruby ではなく ruby とできるよう仕様のほうが改訂されたら、当分は現在の <ruby>, <rt> との共存期間があったとしても、もうほとんど解決ではないですか。

……と思ったら、このスタイル、すなわち display: ruby-text を実装しているのは Gecko (Firefox) だけで、Webkit (Safari) も Blink (Chrome) も未対応でした (2018年6月現在)。ひどい状況です。

ルビを実際に使う人やデザイナのほとんどは、ほんの少し首をかしげるくらいはあっても、仕様に従って使うだけで、その仕様がどうあるべきかまで問い返したりはしません。そもそも世界のうちでごく限られた言語だけでしか知られておらず、その中でも使う人はごく少数ですから、問題にする人も滅多にいなければ対応もぜんぜん進まないのでしょう。ほんとうに不幸です。

ニュースのスクレイピングでタイピング練習

ひらがな数文字を打ち返すだけのタイピング練習は案の定すぐに飽きてしまったので、何か別のネタを考えなくてはならなくなりました。飽きないためには膨大かまたは頻繁に更新される元データがあればいい、青空文庫かな、でも小学生に向いているものがどれほどあるかしらん、頻繁に更新されるといえばニュース、でもこれまた小学生向きではなさそう……と思ったら実にぴったりのものがありました。NHK NENS WEB EASY です。ひとつの記事で50字ほどの文が10ほど。意味もわかりやすくて量もちょうどいい。かなり手間をかけて作られているようです。

さて、これをなんとか持ってきてタイピング練習の材料にしようと思ったのですが、何しろ本業でも何でもないので情報を集めるところからスタートでした。今回やっていることは実は「スクレイピング」という程のこともないのですが、そのとっかかりということで、せっかくなのでここに記録しておきます。

環境の準備

NHK NENS WEB EASY のページの肝心な部分は JavaScript で生成されているようで、Python で単純に requests.get(url) とやっても、ブラウザで見ている HTML ソースとは別のものしか得ることができません。そこでまず、ブラウザが実際に表示するページを取得できるようにします。

Debian パッケージ chromium-driver をインストールします。

sudo apt-get install chromium-driver

これを Python から使うためにライブラリ Selenium をインストールします。

pip install selenium

取得した HTML から必要な箇所を切り出すのには BeautifulSoup4 を使います ((Python にはじめからある html.parser でもある程度できます。また Selenium にも同様の機能があるようです。))。

pip install beautifulsoup4

でインストールします。

ニュースサイトの構造

ニュースサイトによくあることですが、各記事の URL は数字の羅列のような名前で、先頭ページではそれが日々更新されます。

ブラウザのデベロッパーツールで NHK NENS WEB EASY の先頭ページ https://www3.nhk.or.jp/news/easy/ の構造を見てみます。

<div class="top-news-list__pickup news-list-item" id="js-news-pickup">
  ...
  <h1 class="news-list-item__title is-pickup">
  <a href="./k10011463631000/k10011463631000.html"><em class="title"><ruby>日本<rt>にっぽん</rt></ruby>の<ruby>二酸化炭素<rt>にさんかたんそ</rt></ruby>の<ruby>濃度<rt>のうど</rt></ruby>が<ruby>今<rt>いま</rt></ruby>まででいちばん<ruby>高<rt>たか</rt></ruby>くなる</em><time class="time">6月5日 11時30分</time></a>
  </h1>
</div>

最初に大きく取り上げられている記事は <div id="js-news-pickup"> で、その中の <h1> の中の <a> から記事個別ページの URL が得られます。

その記事個別ページを同様にブラウザのデベロッパーツールで見てみると、記事本文は <div id="#js-article-body"> にあることがわかります。これを切り出してくればいい訳です。

スクレイピング

参考にした (というか、ほぼそのままコピーさせてもらった) コードは「Python Webスクレイピング テクニック集」の「JavaScriptによる描画に対応する」です。

ソース中のコメント「ブラウザを起動する」の箇所で、ブラウザのパスを指定する必要がありました。Debian のパッケージを使っている場合

driver = webdriver.Chrome(executable_path='/usr/bin/chromedriver', chrome_options=options)

です。

切り出し

1回めの

        # ブラウザでアクセスする
        siteurl = "https://www3.nhk.or.jp/news/easy/"
        driver.get(siteurl)

        ...

        # BeautifulSoupで扱えるようにパースします
        soup = BeautifulSoup(html, "html.parser")

        # id で特定の要素を切り出す
        href = soup.select_one("#js-news-pickup h1 a").get('href')

で記事個別ニュースの URL が得られるので、2回めは

        driver.get(newsurl)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        # ルビを削除
        for s in soup(['rt']):
            s.decompose()

        ...

        # ニュースの本文
        text = soup.select_one("#js-article-body").text

で、記事本文を切り出します。

ルビを削除

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

この作業をやってみて、rt がほかとは異質なタグ(要素)であることを実感しました。これについてはまた別の記事に書こうと思います。

1文ずつに分解

記事を「。」で区切り、リストにします。「。」自身も含めたいので split が使えません。NHK のニュース記事で全体の最後に「。」がないことはまさかないだろうと仮定して、

        lines = re.findall(".*?。", text)

とします。あとは Errnot がこれを1文ずつ表示するようにするだけです。これでこの bot を相手に XMPP のチャットでオウム返しにタイピングの練習をすることができるようになりました。

それにしても、いろいろ寄せ集めるだけでこれだけできるのですから、便利な世の中になったものだとつくづく思いました。