トップ «前の日記(2019年12月14日) 最新 次の日記(2019年12月16日)» 編集

KeN's GNU/Linux Diary


2019年12月15日

_ [indesign] Markdownファイルをpandocを使わずに力ずくで組む方法

DTP Advent Calendar 2019、昨日14日目は@assauseさんでした。本日15日目は@kmutoが担当します。

まえおき

DTPを業務とされている皆さんの中には「原稿はMarkdownですがよろしく」という依頼を受けた経験があるのではないでしょうか。

Markdownとは、メールの文章と同じ感覚で読んだり書いたりでき、よりリッチな見た目のHTMLに変換できることを目的としたテキストマークアップ言語です。マークアップというのは「この範囲は強調」とか「この行は見出し」といった、本題の文章以外の何らかの情報を表すもので、テキストマークアップの場合は本題の文章と合わせて人間に視認できる形で含まれます(これに対してInDesignやWordの場合は、強調や見出しのスタイルが適用されているという形では表されますが、内部的にどのような情報が入っているかは視認できません)。

WikipediaのMarkdownの項の内容から抜粋してMarkdownで記述した例を、以下にサンプルとして示します。

# Markdown

__Markdown__(マークダウン)は、文書を記述するための[軽量マークアップ言語](https://ja.wikipedia.org/wiki/軽量マークアップ言語)のひとつである。
本来は _プレーンテキスト_ 形式で手軽に書いた文書から[HTML][]を生成するために開発されたものである。
しかし、現在ではHTMLのほかパワーポイント形式やLaTeX形式のファイルへ変換するソフトウェア(**コンバータ**)も開発されている。
各コンバータの開発者によって多様な*拡張*が施されるため、各種の方言が存在する。

[HTML]: https://ja.wikipedia.org/wiki/HyperText_Markup_Language

## 概要
「書きやすくて読みやすいプレーンテキストとして記述した文書を、妥当な
XHTML(もしくはHTML)文書へと変換できるフォーマット」として、ジョン・
グルーバー <https://en.wikipedia.org/wiki/John_Gruber> により作成
された。アーロン・スワーツも大きな貢献をしている。
Markdownの記法の多くは、電子メールにおいてプレーンテキストを
装飾する際の慣習から着想を得ている。

``Markdown.pl``は、その後第三者によってCPANのPerlモジュール (`Text::Markdown`) として再実装され、さらにPython等の他のプログラミング言語でも実装された。

## 利用例と方言
以下にMarkdownの利用例を挙げる。

- Stack Overflowや他のStack Exchange Networkサイトは、Markdownを改変した方言をデフォルトのフォーマットシステムとして利用している。
- PosterousはMarkdownをマークアップの選択肢として提供している。

1. “RFC 7763”. IETF. 2018年12月12日閲覧。
2. Aaron Swartz (2004年3月19日). “Markdown”. 2018年12月12日閲覧。

### コード
```javascript
(() => {
  'use strict';

  console.log('Hello world');
  /* _sample_ コード中リテラル \*、\_、\` */
})();
```

文中でリテラルとして使うには\*、\_、\`とする。インラインコード内は``*_``。
`` ` ``は1つのバッククォートとなる。

HTMLに変換してWebブラウザで表示するとこんな感じです。

sample.png

ちょっとした文章書き用に設計されたMarkdownですが、目的に応じていろいろな人がさまざまに改造・拡張しており、たとえばEPUB電子書籍作成のWebアプリケーション「でんでんコンバーター」は原稿ファイル形式にMarkdown拡張記法を採用しています。

単に読み書きする人にはわりと便利なこのMarkdown。しかし、これをInDesignを使うDTP担当者が紙面化するとなると、厄介なことになります。テキストファイルだと思ってMarkdown形式ファイルをフォーマットに割り付けた後、「これは確か強調……これは見出し……#がいくつだとどのスタイルにするんだっけ……」という果てのない単調な作業を繰り返し、なまじそのまま読むこともできてしまうためにMarkdownマークアップ(`〜`や_〜_など)が残ったまま印刷工程に進んでしまうなんて悲劇も起こります(※架空のお話です)。

MarkdownからInDesignのDTPを行う場面において常套手段として挙げられるのが、pandocという変換ツールです。pandocは多種多様な文書ファイル形式間を変換できる強力なツールで、その機能の1つとして、Markdown形式からInDesign/InCopyのICML形式に変換できます。

このICMLファイルはMarkdownのマークアップに対応してInDesignのスタイル付けもなされているので、後はInDesignで適当な空ファイルに貼り込み、スタイルを利用予定のフォーマットのものに置き換えていくだけです。すばらしい、完!

……と言いたいところですが、実際の社内のDTPの作業プロセスを観察していると、デザイン済みのフォーマットがまずあって、そこにプレインテキストのコンテンツを割り付け、目視で判断しながらスタイルを適用していく、というのが一般的な手法です。

独自のスタイルが適用済みのコンテンツ(ICMLやWord)を読み込んだ後、デザイン済みのフォーマットに当て直していくというやり方にはあまりなじみがありません。フォーマットがICMLのスタイルと一致しているなら合理化もできる余地はありそうですが、実際の制作でそううまくいくケースというのは滅多にないものです。

正規表現

そこで本題となるのが、「正規表現」(Regular Expression、regexpとも呼ばれる)を使う方法です。

正規表現の検索置換は古くはInDesign CS3から実装されているのですが、皆さんは使っていますか? おなじみの[検索と置換]ダイアログから[正規表現]タブをクリックすると、[検索文字列]、[置換文字列]という普通のテキスト検索置換同様の入力覧が示されます。

grepfind.png

普通のテキスト検索置換と違うのは、検索文字列に「正規表現」という記法を使って、魔法のようにいろいろな文字範囲に検索対象を合致させられることです。置換文字列側も、合致した任意の文字をうまく取り扱う記法が用意されています。

普通のテキスト検索置換と同様に、検索範囲や、検索対象あるいは置換対象とする段落スタイルや文字スタイルもこのタブ内で指定できます。

実際にどう正規表現を使っていくかについては、この後に(胃もたれするまで)紹介していきます(お気付きになったかもしれませんが、本記事の本質は「DTP作業にも正規表現はいいぞ」という普及活動です)。

対象とするMarkdownの範囲

さて、Markdownにはいろいろな記法や派生があるのですが、ここでは説明が長くなりすぎる、執筆検証の時間がとれない、正規表現だけでは限界といった諸々の都合で、これから操作するMarkdownファイルには次のような制約があるものとします。

  • John Gruberの定義した Markdown:Syntax の範囲で考えます。
  • 見出しは#を使ったもののみとし、下線型の記法は使いません。
  • 番号箇条書きを除き、自動的な採番は存在しません。
  • HTMLの生書きや、&による実体参照は使いません(実体参照は実装することはできますが利用を見かけないので……)。
  • 表も本来のMarkdownでもともと対象外なので使いません。
  • 引用(>)は使いません(実装できなくはないですが説明が長くなるので)。
  • ほか、いろいろと「仕様にはあるけど?」的なものについても考慮外にしているものがあるかもしれません(目を逸らしつつ)。

いずれにせよ、この範疇を超えるほど複雑なMarkdownファイルであれば、冒頭で紹介したpandocで、何か扱いやすい形式へ変換したほうが妥当でしょう。

最初の一歩、インラインの処理(1)

使いたいフォーマットテンプレートファイルを開いた状態で、段落スタイルを本文用スタイル(ここでは「本文」という段落スタイルがあるものとして、それを適用)、文字スタイルは「なし」になっている状態にして、Markdownファイルを普通にテキストとして割り付けます。

step1.png

記念すべき第一歩として、装飾のためのインラインマークアップ(本文段落中の一部の範囲への情報付与)を見て、InDesignの文字スタイルを適用してみます。

Markdownの装飾インラインマークアップとしてよく使われるものに、次の記法があります。

  • 「**〜**」「*〜*」:em強調(英文だとイタリック系表現)
  • 「__〜__」「_〜_」:strong強調(英文だとゴチ表現)

「*」「_」が1個か2個かの違いは基本的にありません。2個のときにはその中に1個の*、_を「リテラル」(=単なる文字)として使えるということらしいですが、後述のようにリテラル扱いにしたいときはエスケープを使うほうがわかりやすいので、ここではそのような対応はしないことにします。

emphasisとstrongは、和文のMarkdownファイルではあまり使い分けられていない印象ですが、「em強調」「strong強調」というような名前の文字スタイルをそれぞれ用意しておくことにします。当然ながらスタイル名は任意で、スタイルグループを使っても構いません。

ひとまず__〜__から変換してみましょう。

[検索と置換]ダイアログを開き、次のように指定します。

  • 検索文字列:__(.+?)__
  • 置換文字列:$1
  • 検索:ドキュメント
  • 検索形式:(指定なし)
  • 置換形式:文字スタイル:strong強調(Tに虫眼鏡が付いているアイコンをクリックし、文字スタイルを設定)

strongreplace.png

[すべてを置換]をクリックします。

strongreplaced.png

できました! __〜__の箇所から__が削除され、「strong強調」文字スタイルが適用されました。検索文字列の__はそのまま__を検索するという意味です。(.+?)は、「任意の1文字(.)が1個以上(+)で、かつ検索される中で一番短いもの(?)を、番号で呼べるようにする(())」という意味の正規表現です。ここで()で囲んだ内容で、置換文字列内の$1が置き換えられます。

置換文字列には「__」は含めていないので、前後の「__」は削除され、検索で(.+?)に適合する内容に文字スタイルが適用されます。なお、?を付けないと「最長一致」になって、その段落の最後まで探して一番最後に見つかった__までを範囲にしてしまいます(「__あ__、__い__」とあったときに「あ__、__い」が対象になってしまう)。目的があって最長一致にしたいのでない限り、正規表現で任意文字列を取得したいときには?を付けるのが安全です。

今度は_〜_の箇所にも適用してみます。[検索と置換]ダイアログを開き、検索文字列を次のように変更します。

  • 検索文字列:_(.+?)_

strong2replaced.png

同じ記号が並んでいるときには数の多いほうから実行していくことが大事です。さもないと、一致する範囲が意図に反してしまいます。Markdownでの__と_は同じ意味なので1つの正規表現内で記述することも実はできますが、わかりやすさの点では個別の手順にしたほうがよいでしょう。

また、ちょっと困ったことですが、この置換ではURL内の_も変換されてしまいました。これについては後で対策します。

次は**〜**ですが、先の正規表現で()、.、+、?がそれぞれリテラルではない特別な意味があったように、*も正規表現向けの特別な意味がある文字です(具体的には0文字以上を表すのに使います)。ここでは単なるリテラルとしたいのですが、そのようなときには「\*」と記します。\は0x5cの文字(バックスラッシュまたは円記号)です。macOSで¥キーを押すと円記号のほうが入りますが、optionキー+¥キーで0x5cのバックスラッシュ文字のほうを入れるようにしてください(円記号だとうまく動きません)。

  • 検索文字列:\*\*(.+?)\*\*
  • 置換形式:文字スタイル:em強調

emreplaced.png

うまくem強調になりました。同様に「\*(.+?)\*」で*〜*も適用できます。

この例だけでも、「正規表現、イイのでは……?」と思えてきたのではないでしょうか。

#見出しの処理(1)

文字スタイルの適用例を見たところで、次は見出しのマークアップに段落スタイルを適用してみましょう。

Markdownには行頭からの#の数で見出しレベルを表す記法と、見出しにしたい行の次の行に===や---などの下線を入れる記法とがありますが、後者は正規表現で解析しづらいのと、日本語のコンテンツでは和欧の文字幅の違いのせいかあまり使われていないように思うので、対応しないことにします。

「#の数による見出し」は、次のように#の数が多いほど小さな見出しという扱いです。「一番大きな見出し」を章として扱うのか節として扱うのかといった文書構造に関することは、Markdownでは規定していません。

# 大見出し
## 中見出し
### 小見出し

ここでは#見出しに対して、大見出しの段落スタイルを適用することにします。「行頭に#、1つ以上のスペースがあったら、スペースの後から行末までを大見出しとして扱う」と考えて、以下の正規表現になります。

  • 検索文字列:^#\s+(.+?)
  • 置換文字列:$1
  • 置換形式:段落スタイル:大見出し、文字スタイル:[文字スタイルあり]

^は段落の先頭を表します。\sは何らかの空白文字(スペースやタブなど)で、+が付いているので「1つ以上の連続する空白文字」という意味です。(.+?)でその後に続く見出しを$1とするようになりますが、ここでは最長一致でも支障はないので、(.+)と書いても構いません。

前述の文字スタイル設定の後で置換文字スタイルが設定されているでしょうから、[文字スタイルあり]にするか、最初にゴミ箱ボタンをクリックして解除しておいてください。さもないと、(当然ながら)見出しに文字スタイルも適用されてしまいます。

[すべてを置換]をクリックすると、#とスペースが除去された上で、大見出し段落スタイルが付きました。

title.png

スクリプトの導入

さて、検索置換パネルでいくつもの正規表現を入力して実行するのは現実的ではないので、ここらでスクリプトを使うことにしましょう。GitHubに2つのスクリプトファイルを用意したので、ダウンロードして[スクリプト]ウィンドウから見えるフォルダに配置してください(サブフォルダを作ってもよいでしょう)。

  • macOSであれば、ユーザーホームのLibrary/Preferences/Adobe InDesign/利用バージョン/ja_JP/Scripts/Scripts Panelフォルダ
  • Windowsであれば、ユーザーホームのApplication Data\Adobe\InDesign\利用バージョン\ja_JP\Scripts\Scripts Panelフォルダ

本記事でスクリプトの実装について細かく説明していると終わらないので、詳細を知りたい方はスクリプトの内容をご覧ください。本記事で説明していること以外にも、いろいろと面白そうな機能があることに気付かれるかもしれません。

2つのファイルはそれぞれ次の機能を提供します。

  • dialogChooseStyleDefs.jsx:ユーザーが作った定義ファイルをdialogApplyStyles.jsxの利用対象に設定します。
  • dialogApplyStyles.jsx:定義ファイルの正規表現ルールに従って、選択したテキストフレーム内に紐づけられたストーリーに適用を繰り返します。

要するに、定義ファイルを作っておき、dialogChooseStyleDefs.jsxでそれを使うよう設定したら、後はフォーマットにMarkdownファイルを割り付けてdialogApplyStyles.jsxを実行するだけで、正規表現での置換とスタイル付けがズババッと適用されるというからくりです(dialogApplyStyles.jsxに定義ファイルの設定機能を混ぜてもよいのですが、分けたほうが繰り返し作業に利便性が高いという判断です)。

また、検索置換は、これまでにターゲットとしていた「ドキュメント」ではなく、テキストフレームを人間が選択した上で、そのテキストフレームに紐づいている「ストーリー」に対して実行するようにしています。Markdownのコンテンツに無関係の何らかのテキストフレームがフォーマットに存在する場合、誤ってそこに置換が影響してしまわないようにという理由です。

定義ファイルの書式は以下のとおりです(UTF-8文字エンコーディングで記述します)。

TITLE <タブ> 定義ファイルタイトル
#から始まる行はコメント
P <タブ> 正規表現 <タブ> 置換文字列 <タブ> 段落スタイル名
P <タブ> 正規表現 <タブ> 置換文字列 <タブ> 段落スタイル名 <タブ> 元段落スタイル名
C <タブ> 正規表現 <タブ> 置換文字列 <タブ> 文字スタイル名
C <タブ> 正規表現 <タブ> 置換文字列 <タブ> 文字スタイル名 <タブ> 親段落スタイル名
C <タブ> 正規表現 <タブ> 置換文字列 <タブ> 文字スタイル名 <タブ> <タブ> 元文字スタイル名
C <タブ> 正規表現 <タブ> 置換文字列 <タブ> 文字スタイル名 <タブ> 親段落スタイル名 <タブ> 元文字スタイル名
R <タブ> 正規表現 <タブ> 置換文字列
R <タブ> 正規表現 <タブ> 置換文字列 <タブ> 親段落スタイル名

それぞれの列はタブ1つで区切ります。スペース文字は正規表現や置換文字列の中で使われることがあるので、区切りには使えません。もし正規表現や置換文字列でタブ文字を対象としたいときには、「\t」という特殊な正規表現文字を使います。

TITLEはその定義ファイルを区別しやすいようにするための任意の名前です。#は説明のとおりコメント行です。

Pは段落スタイルを適用するための、Cは文字スタイルを適用するための定義です。2列目の「正規表現」が検索ダイアログでいうところの[検索文字列]、3列目の「置換文字列」が[置換文字列]に相当します。4列目のスタイル名は置換後に適用するスタイルだとして、5列目や6列目は謎めいた印象がありますが、これは後で説明します。

前出の正規表現を定義ファイル化してみると、次のようになります。ファイル名は何でもよいですが、ここでは「markdown-indesign.txt」という名前にしておくことにします。

TITLE	Markdownを力ずくで組む
# 文字スタイル適用
C	__(.+?)__	$1	strong強調
C	_(.+?)_	$1	strong強調
C	\*\*(.+?)\*\*	$1	em強調
C	\*(.+?)\*	$1	em強調
# 段落スタイル適用
P	^#\s+(.+?)	$1	大見出し

スタイル名は、もしスタイルグループを使っているときには「親グループ名\子グループ名\スタイル名」のように\区切りで指定できます(副作用として、スタイル名・スタイルグループ名に「\」は使えません。スクリプト内の_delimiter変数で決めているので、これを変えても構いません)。

[スクリプト]ウィンドウからdialogChooseStyleDefs.jsxを実行して、開いたダイアログで[選択]をクリックし、markdown-indesign.txtを選びます。選んだら[閉じる]をクリックしてダイアログを閉じてください。

choosestyle.png

続いてレイアウトテンプレートを開いて、段落スタイルを本文用スタイル、文字スタイルなしの状態でMarkdownファイルを割り付け、コンテンツを含むいずれかのテキストフレームを選択した状態にします。そして、[スクリプト]ウィンドウからdialogApplyStyles.jsxを実行します。

applystyle.png

定義ファイルmarkdown-indesign.txtの上から順に、正規表現の検索置換が実行されました! 何か夢が広がってきましたね。

コードブロック

MarkdownはITエンジニアの利用を念頭に置いており、複数行からなるプログラムコードなどをコンテンツ内に記述する記法があります。紙面では等幅で表すようなコードブロックです。`が3つ連続するの行で始まり(その後にコードの言語名が付くこともある)、`が3つ連続する行で範囲が終わる、という記法が一般的です。あるいは、行頭がタブまたは4つのスペースであればコードブロックと見なすという記法もあります。前者のほうがよく見かけるのですが、Syntaxには後者しか記載されていないことに気付きました。

コードブロックはインラインマークアップを無視するなど、少々この後の処理に絡んで特別なMarkdownの規定があるので、最初に段落スタイルを設定してしまいます。ここでは「コード」段落スタイルを適用することにします。

TITLE	Markdownを力ずくで組む
# コードブロックへの段落スタイル適用
P	(?s)^```.*?\r(.+?)\r```	$1	コード
P	^\t(.+?)	$1	コード
P	^    (.+?)	$1	コード
# 文字スタイル適用
 …

「(?s)」は、任意の1文字(.)に改段落も含むことを意味し、これによって(.+?)は複数行をとります。カッコで囲んでいない最初の.*?(0文字以上の任意文字列)はコードの言語名がある場合への対処で、言語名があってもなくてもこの正規表現で適用させます(言語は単に無視して使いません)。

\rは改段落を表します。他の一般的なコンピューティング環境では1行の終わりを表すのに\nを使いますが、InDesignでは\nは段落中強制改行(Shift+Enterで入るもの)で、段落の末尾に置かれて改段落するのは\rです。ただし、Markdownファイルの割り付けなどの外部ファイル読み込み時には、\nが改段落として扱われます。

2つ目、3つ目は説明するまでもないでしょう。

再度Markdownファイルを割り付け直し、dialogApplyStyles.jsxを実行します。

codeblock.png

エスケープ処理

続けていろいろなMarkdownマークアップに対応していきたいところですが、その前の大切な作業にエスケープ(退避)があります。

段落中で_や*で囲んだものは強調になるとして、_や*という文字自体、つまりリテラルを使いたいときにはどうしたらよいのでしょうか。Markdownではこの場合、\_、\*とそれぞれバックスラッシュを文字の前に付けることでリテラルであることを表します。正規表現でもバックスラッシュは活躍していましたが、Markdownのリテラル表現にもバックスラッシュを使います。

Markdownのコンテンツでリテラル表現で書かれている箇所については、誤って文字スタイルを適用しないよう、いったん何か別の形にしておかなければなりません。これがエスケープです。

本記事での対象とするインラインマークアップの範疇で考えると、\_、\*、\`という3つについてエスケープが必要です。「何か別の形」は、他の検索置換で誤認されることもなく、また後の「アンエスケープ」の処理で戻しやすければよいのですが、ここでは次のようにしておきます。

TITLE	Markdownを力ずくで組む
# コードブロックへの段落スタイル適用
 …
# エスケープ
R	\\_	♪→5F←♪
R	\\\*	♪→2A←♪
R	\\\x{60}	♪→60←♪
R	♪→5F←♪	♪→コ5F←♪	コード
R	♪→2A←♪	♪→コ2A←♪	コード
R	♪→60←♪	♪→コ60←♪	コード
# 文字スタイル適用
 …

\が多いように見えますが、「\`」を置換するには、「\」自体をリテラルで扱うために「\\」とする必要があります。

Rは、スタイル適用はせず文字列置換のみを行うための定義です。60、5F、2Aはそれぞれの記号の文字コードです。♪→←♪は気分でこうしているだけですが、開始・終了がはっきりしているほうが後々楽です。

\`に対してのエスケープが「\\`」ではなく、「\\\x{60}」になっているのは奇妙ですが、これはInDesignのバグ対応で、正規表現上正しいはずなのに、いくつかの文字についてはなぜか最初の1つ、あるいはどれにも適合しないという現象があります。「\x{Unicodeの16進数文字コード}」の記法で実際の文字に代替できます。

続く3つの定義は、「コード」段落スタイルが適用されている箇所に対しては、さらに「コ」という文字をエスケープ内に追加しています。コードだけ別のエスケープにしているのは理由があるのですが、わかりますか? 後述のアンエスケープのところで答え合わせをします。

escape.png

インラインの処理(2)

エスケープ処理の第1段階を終えたので、インラインマークアップに着手します。すでに*、**、_、__は紹介しました。

このほかによく使われるものとして、``と`があります。これはその中をコードとして扱うもので、プログラミング技術系のコンテンツでは頻繁に登場します。ここでは「インラインコード」文字スタイルを適用することにします。

```のコードブロックと同様、囲んだ範囲内はリテラル、つまり`*`や`_`と書いたら、それは強調インラインマークアップではなく、そのままコード装飾の*や_として扱われます。これもいったんエスケープする必要がありそうですね。なお、`自体をリテラルに使いたいときはどうするのか、というと、

`` ` ``

と書くそうです。

 …
# 文字スタイル適用
C	`` ` ``	♪→イ60←♪	インラインコード	本文
C	``(.+?)``	$1	インラインコード	本文
C	`(.+?)`	$1	インラインコード	本文
# ↓ここの4列目は空でタブ2つ
R	_	♪→イ5F←♪		インラインコード
R	\*	♪→イ2A←♪		インラインコード
# ↑ここまで
C	__(.+?)__	$1	strong強調	本文
C	_(.+?)_	$1	strong強調	本文
C	\*\*(.+?)\*\*	$1	em強調	本文
C	\*(.+?)\*	$1	em強調	本文
# 段落スタイル適用
 …

魔術じみていますが、「本文」段落スタイルで ` だったときには`単体扱いでエスケープしておきます(「コード」段落スタイルの場合は何もしない)。そして、「本文」段落スタイル中にあるおよび`〜`に「インラインコード」文字スタイルを適用します。

その後の_および*の処理では、親段落スタイルを指定する任意の段落スタイル(4列目が空)でかつ「インラインコード」文字スタイルが適用されている(5列目)ときに_と*をエスケープします。

その後の_や*については先述のとおりですが、「コード」段落スタイルがかかっているところには適用したくないので、「本文」段落スタイルが適用されている箇所のみに範囲を制限しています。

inline2.png

なお、Markdownファイルで「ここは<スペース>**強調**<スペース>です」のように前後にInDesign DTPには不要な空白が入ってしまっており、ついでにこれらを消してしまいたいというときには、\s*〜\s*で囲みます(前後の空白0個以上も置換対象にする)。

# 前後の空白も削除
C	\s*__(.+?)__\s*	$1	strong強調	本文

副作用として、「This is **a** pen.」と空白が意味を持って書かれているときにも前後のスペースが消されて「Thisisapen.」となってしまうことには注意してください。正規表現でがんばって前後の文字種を判断することもできなくはないですが、そこまで考えるくらいなら別のスクリプトやpandocを使ったほうが妥当です。

リンク

Markdownのリンクはパターンが多く、ややこしいです。というか、印刷紙面でハイパーリンク化されても困るので(“ADPS”というタームが脳裏に浮かびましたが、そのまま過ぎ去っていきました)、表現を紙面用に変える必要があります。ここでは次のように紙面表現することにします。

  • [ラベル](URL)→ラベル(URL)
  • [id]: URL→インラインではなく1行の宣言。行頭に空白文字が入ることもある。以下2つの情報として利用する
  • [ラベル][id]、[ラベル] [id]→ラベル(id先のURL)
  • [id][]→id(id先のURL)

このほかにリンクのTitle情報を入れる記法もあるのですが、まともに使っているのを見たことがないので、省略し、対応もしないことにします。

さて、最初の1つはさして難しくありませんが、下3つは普通に処理するには「idのURLを記憶する」「記憶されているURLを展開する」という2段階の処理、また記憶という能力が必要です。これは正規表現だと困難です。

そこで、少し制限を設けましょう。

  • Markdownではidは本来大文字小文字を問わない(ケースインセンシティブ)が、ここでの範囲では大文字小文字は完全一致のみとする
  • 宣言文は参照より後ろの行で、間にコードブロックを挟まない
  • 同じid宣言を繰り返してもよい。その場合、参照より後ろの行で最初に合致した宣言が使われる

正規表現定義は以下のとおりです。リンクにリテラルな_などが含まれる可能性があるので、文字スタイル適用の前に実行します。

 …
# エスケープ
 …
# リンクの処理
R	\[(.+?)\]\((http.+?)\)	$1(♪→U♪$2←♪)	本文
R	^\s*\[(.+?)\]:\s+(http.+)	♪→リ♪$1♪$2←♪	本文
R	(?s)\[(.+?)\]\s*\[\](.+)(♪→リ♪\1♪(.+?)←♪)	$1(♪→U♪$4←♪)$2$3	本文
R	(?s)\[(.+?)\]\s*\[(.+?)\](.+)(♪→リ♪\2♪(.+?)←♪)	$1(♪→U♪$5←♪)$3$4	本文
C	♪→U♪(.+?)←♪	$1	URL
# 3列目は空でタブ2つ
R	♪→リ♪.+?←♪		本文
# 文字スタイル適用
 …

うわぁ……。書いている自分もドンビキのこの処理ですが、以下のような順序で処理しています。

最初は、シンプルな[ラベル](URL)→ラベル(URL)の置換をします。URLの箇所に文字スタイルを後で付けたいので、「♪→U♪」と「←♪」で囲みました。これまで()囲みは1つしか登場していませんでしたが、ここではラベル側の囲みと、URL側の囲みの2つが存在します。順に$1、$2となるので、これを置換文字列に含めています。

次に、[id]: URLの宣言を、「♪→リ♪」「id」「♪」「URL」「←♪」という形に置き換えます。

そして、[id][]の箇所をなんとかします。コードブロックと同様に(?s)指定で.+が複数行をとれるようにした上で、idの部分$1とし、先ほど置き換えていた宣言が見つかる直前までの範囲(複数行も可)を$2に入れます。

idと同じ宣言を探すにはどうするかというと、見つかったid=$1の内容を検索正規表現の中で「\1」という形で自己参照しています。相当キモいですね。自己参照にはいくつか制限があるのですが、ひとまずこの場所での利用はうまくいくようです。

宣言文全体の「(♪→リ♪\1♪(.+?)←♪)」は$3となりますが、そのうちのURL部を入れ子のカッコを使って取得します。ここは$4となります。

置換文字列のほうは、シンプルな形と同様にidと♪→U♪〜←♪囲みのURLに置き換え、さらに何食わぬ顔で検索範囲にしていた$2と$3を元に戻します。

[ラベル][id]、[ラベル] [id]のほうは、ラベルも正規表現置換の対象に加わる($1)ので上記の$1〜$4が$2〜$5になるだけです。

後は♪→U♪〜←♪囲みを「URL」文字スタイルに置換し、紙面には不要となるid宣言文を削除しておしまいです。いやはや。

このほかに<〜>の中にURLやメールアドレスを記載する自動リンクもあります。前後の空白や<>自体は紙面化時には取るようにするのが正しい、のだそうです。メールアドレスが記載されるケースは見かけないので、ひとまずURLっぽいものだけ対処し、「URL」文字スタイルを割り当てておくことにします。

 …
# リンクの処理
 …
# 自動リンクへ適用
C	\s*<(http.+?)>\s*	$1	URL	本文

link.png

まだリンクの_Markup_がem強調扱いになってしまっているのが気に入らないですね。強調の文字スタイルを適用する際に、「『本文』段落スタイルで、かつ文字スタイルが『[なし]』になっている」という条件に制約します。URLの箇所にはすでに「URL」文字スタイルが適用されているので、そこにstrongやemの正規表現が適用されるのを防げます。

# 文字スタイル適用
C	__(.+?)__	$1	strong強調	本文	[なし]
C	_(.+?)_	$1	strong強調	本文	[なし]
C	\*\*(.+?)\*\*	$1	em強調	本文	[なし]
C	\*(.+?)\*	$1	em強調	本文	[なし]
# 段落スタイル適用
 …

link2.png

良い感じですね。

見出しの処理(2)

文字スタイルに関わるインラインマークアップはだいたい片付けたので、段落スタイルに関わるものを片付けていきましょう。

「#見出し」の処理は簡単ですね。「コード」段落スタイルを避けるために「本文」段落スタイルの箇所のみに適用するだけです。#見出しは6レベルまで許容されているようですが、ここでは3レベルまでを定義しておくことにします。

 …
# 段落スタイル適用
P	^#\s+(.+?)	$1	大見出し	本文
P	^##\s+(.+?)	$1	中見出し	本文
P	^###\s+(.+?)	$1	小見出し	本文

title2.png

箇条書きの処理

Markdownでは番号あり箇条書きと番号なし(通常はいわゆるナカグロが代わりに付く)箇条書きが用意されています。行頭から「数字、ピリオド、空白」で始まっていれば番号あり、「*か+か-、ピリオド、空白」から始まっていれば番号なしの箇条書きです。

 …
# 段落スタイル適用
 …
P	^[*+-]\s+(.+?)	●\t$1	ナカグロ箇条書き	本文
P	^(\d+)\.\s+(.+?)	$1\t$2	番号箇条書き	本文

新しい正規表現が登場しました。[*+-]は、*か+か-かのいずれか1文字を表します。*や+、-はいずれも正規表現で特別な文字ですが、[]で囲んでいるときには*や+はリテラルとして扱われます。-だけは少し意味があり、たとえば[a-c]のようにした場合はa、b、cという3つのうちのいずれかという範囲指定になりますが、この例のように最後に置いておけば、範囲ではなくリテラルになります。

番号なし箇条書きのほうには、「ナカグロ箇条書き」段落スタイルを適用し、また「●」とタブを先頭に置くようにしています(段落スタイルのほうでは先頭文字スタイルを使って装飾してもよいでしょう)。もし段落スタイルの箇条書き機能でナカグロの配置を構成しているなら、ここは「$1」だけで済ますこともできます。

番号あり箇条書きでは、\d+という正規表現があります。\dは数字を表します([0-9]と同等です)。\d+なので、1桁以上の数字に合致します。ピリオド(.)は正規表現では任意の1文字を表すので、\.とリテラルにしています。

番号部分は$1、文は$2に入っていますが、こちらも段落スタイルの箇条書き機能で自動番号を有効にしているなら、「$2」だけで済ますこともできます。Markdownの番号箇条書きではその数字に意味はなく、たとえば「1.」と連続して書いておいて後は変換に任せる、ということも許容しているので(DTP担当者にとっては悪夢ですね)、段落スタイルで自動番号を使うほうが妥当かもしれません。

itemize.png

画像

Markdownでは「![キャプション](ファイルパス)」という記法でファイルパスの画像ファイルをそこに配置し、キャプションを近くに置きます。キャプションを省略して「![](ファイルパス)」という書き方もできます。

なんとここで、サンプルのMarkdownファイルに画像の例を入れるのを忘れていました! キャプチャし直しはつらいので、一例で示すことにします。

![Markdownのロゴ](markdown-logo.png)

Webブラウザで表示したときはこんな感じです。

sample2.png

しかし、実際のDTP作業において、経験上、Markdownファイルと一緒に支給された画像ファイルがそのまま使える状態にあることはまずないので、DTP作業の参考用にファイルパスをそのまま掲載するに留めることにします。画像ファイルがいらすとややネットの拾い物のコピペで使えないから作図というケースもままあるかもしれませんが(※再び架空のお話です)、そうでなくても一般にpsdファイル変換・トリミング・白黒化等々の作業が発生するものです。

ファイルパスは「画像」段落スタイル、キャプションは「図キャプション」段落スタイルで表すことにしましょう。リンクと同様、ファイルパスに_などがリテラルで含まれる可能性があるので、文字スタイル適用の前に実施します。

 …
# 3列目は空でタブ2つ
 …
# 画像の処理
P	^!\[\]\((.+?)\)	$1	画像	本文
P	^!\[(.+?)\]\((.+?)\)	♪→キ♪$1←♪\r$2	画像	本文
P	♪→キ♪(.+?)←♪	$1	図キャプション
# 文字スタイル適用
 …

2つ目のキャプション付きの場合は、2行に分割した上で、キャプション側は段落スタイルを付けるために♪→キ♪〜←♪で囲んでおいて、続くステップで段落スタイルを適用します。また、キャプション→画像という順序にしていますが、置換文字列の$1と$2を入れ替えれば、画像→キャプションの順序になります。実際のレイアウトに合わせるのがよいでしょう。

figures.png

これで捗りますね!

行結合と空行の削除

さて、ここまで説明していなかったのですが、Markdownでの段落は次のように構成されます。

  • 空行が入らず複数行が続いた場合、それは段落の継続と見なされる
  • 空行が入ったら、それまでの内容が1つの段落となる
  • 段落間の空行は変換後には入らない
「書きやすくて読みやすいプレーンテキストとして記述した文書を、妥当な
XHTML(もしくはHTML)文書へと変換できるフォーマット」として、ジョン・
グルーバー  <https://en.wikipedia.org/wiki/John_Gruber> により
作成された。アーロン・スワーツも大きな貢献をしている。
Markdownの記法の多くは、電子メールにおいてプレーンテキストを
装飾する際の慣習から着想を得ている。

``Markdown.pl``は、その後第三者によってCPANのPerlモジュール (`Text::Markdown`) として再実装され、さらにPython等の他のプログラミング言語でも実装された。

上記のようなMarkdownコンテンツは、以下のように紙面化されることが期待されるというわけですね。

sample3.png

正規表現でこれに対処してみましょう。pandocで試すと英語であろうと日本語であろうと結合時には間にスペース文字が入るようなのですが、日本語の文が行分割されていたという前提でここではスペース文字は入れないことにします(「$1$2」のところを「$1 スペース $2」とすればpandocと同じになります)。

 …
# 段落スタイル適用
 …
# 行の結合
R	(.+?)\r([^\r]+?)	$1$2	本文
# 空行の削除(3列目は空。タブが2つ連続)
R	^\r		本文

join.png

なお、Markdown仕様をつらつらと読んでいくと、「- 〜」のような箇条書きも、ただ次の行に文を連ねるだけ(インデントを入れてもよい)で行結合される、ということが後になってわかりました。できるとは思いますがだいぶ長くなりそうかな……という感じなので実装はやめておきます(必要と思われる行数ぶん箇条書きへの行結合を実行する、ということになりそうですが、別の整形スクリプト処理かpandocを使ったほうがよいですね)。

アンエスケープ処理

だいぶ良い感じになりましたが、もう1つだけ、大事な作業があります。最後の手続きとして、最初にエスケープしていたものを\を取った形で戻します。

ここでエスケープのところで出したクイズの答え合わせになりますが、コードブロック内はエスケープはせず、リテラルとして扱うというMarkdownのルールがあります(つまり、コード内で\*と書いたら、*ではなく文字どおり\*)。そこで、「コ」付きのエスケープは\付きで戻します。

 …
# 空行の削除(3列目は空。タブが2つ連続)
 …
# アンエスケープ
# コード内は\付きで戻す
R	♪→コ5F←♪	\_
R	♪→コ2A←♪	\*
R	♪→コ60←♪	\`
# ほかは\なしで戻す
R	♪→5F←♪	_
R	♪→2A←♪	*
R	♪→60←♪	`
# インラインコードのエスケープを戻し、文字スタイル適用
C	♪→イ5F←♪	_	インラインコード
C	♪→イ2A←♪	*	インラインコード
C	♪→イ60←♪	`	インラインコード

なお、置換文字列のほうで\が付いたら\tのように特殊文字扱いになるのでは……?と思うのですが、英字アルファベットなど特定の意味があるもの以外を指定した場合、\はリテラルに扱われるようです。たとえば上記で\\_のように書くと、結果も\\_になりました。

ついに、MarkdownファイルからInDesignレイアウトへの変換が完成です! やりました!

unescape.png

おわりに

長々とした記事になりましたが、最後まで読んでいただいてありがとうございます。

完成した定義ファイルは、GitHubのsamplesフォルダにIDMLとともに収録しています。

完璧ではないにせよ、正規表現による検索置換を繰り返し適用するだけで、生のMarkdownファイルをまずまずのInDesignドキュメントファイルへと作り変えることができました。

実際のところ、この正規表現処理スクリプトはMarkdown用に作ったわけではなく、社内マークアップ記法で記述された原稿からの仮組の支援を目的として、古くから開発・社内配備して利用しているものです。

Markdownよりも開始記号と終了記号が明確でエスケープ不要な記法であれば、定義ファイルの正規表現もずっとシンプルになり、かつ高度な変換処理も可能です(弊社の社内マークアップ記法に興味のある方は、Re:VIEWのテキスト形式出力がほぼ同等なので、お試しあれ)。また、単純な検索置換をまとめて実行するのにも利用できるでしょう。

InDesignで利用可能な正規表現記法については、市川せうぞーさんが過去にまとめられた「InDesign CS3 正規表現一覧」が詳しいので、これを見ながらさらに活用してください。

正規表現とスクリプトで皆さんの業務が捗り、良いクリスマスと年始年末を迎えられますことをお祈りいたします。Enjoy!

DTP Advent Calendar 2019、明日16日は@Uske_Sさんです。

_ [cooking] ガルバンゾのチリコンカルネ