タイトルの通りです。htmlタグ直打ちではなく,スムーズに任意の位置に任意の数値もしくはRでの出力を差し込めるようにしました。
R Markdownが読み込んでいるもの
R Markdownの基本outputであるhtml_documentには、Bootstrapというデザインワークフレームが組み込まれています。その関係で、jQueryも合わせて読み込まれています。したがって、意図的に除去しない限りはこれら2つは標準で利用可能となっています。
これら2つが使えるということは、Webにおけるいろいろな要素を簡単に組み込むことができます。そこで今回は、BootstrapのBadges機能を,jQueryを利用して実現させてみます。
BootstrapのBadges機能とは
よく「未読メッセージが42件ありますよ」っていうときに,数値の部分を他特別できるようにわかりやすくする機能です。本家ドキュメントのキャプチャは以下の通りです:
上の画像を見てわかるとおり,<span class="badge">42</span>
というようにbadge
クラスを付与した<span>
タグが使えれば適用できます。
なおRmdに実装するなら,上記のようなhtmlタグを使えば達成できます。しかしそれは手間がかかります。そこでjQueryのコードによってうまくできるようにしました。
目標
- htmlタグなし
- 任意の位置に設置可能
- 設置も簡単で,なるべく他と競合しない
- 部品化して,簡単に他のRmdファイルでも使えるようにする
なんとなく簡単そうにみえますが……
実装
できました。以下の手順となります:
- スクリプトのみを記述したhtmlファイルを準備
- Rmdファイルを準備
- Rmdのyamlフロントマターに1のhtmlファイルをincludeさせる
- バッジを挿入したい場所へ
**bdg 3**
(数値は任意の文字列)を付与
基本的な流れは,R MarkdownにBootstrapのPanels機能を実装とほぼ同一です。上記の目標は全てクリアしています。以下手順を少し細かく。
スクリプトのhtmlファイルを準備
2017/08/29追記: コードに冗長なところがあったので修正しました。以前のコードでも動きますが、こちらのほうが少ないです。
これが実装のコアです。以下のようなhtmlファイル(ここでbs_badge.html
)を準備します:
<script>
$(function(){
// strong要素(mdで**で挟んだ要素)をターゲットに
$("strong").each(function(){
// strong要素のテキストを取得
var txt = $(this).text();
// バッジのターゲットならば,Bootstrapのbadgeになるよう書換
if(txt.match("^bdg")){
var strBadge = txt.replace(/^.* /g, '');
$(this).replaceWith("<span class='badge'>"+strBadge+"</span>")
}
});
});
</script>
jsのコーディング規約とか全く知らないので,汚い書き方になってしまいすみません。何をしているかはコード内のコメントを参照してください。このコードをコピペしてファイルを準備してもらえればOKです。
Rmdファイル側の設定
当然ですが,出力はhtml形式で,BootstrapとjQueryが読み込まている必要があります。大抵の場合デフォルトで組み込まれています。含まれていないフォーマットの場合は適宜読み込ませるようにしてください。
yamlフロントマターの設定
以下のように,先ほどのhtmlファイルを組み込んでください:
2017/08/28追記: ファイル名をミスしてましたので修正しました
---
output:
html_document:
include:
after_body:
- bs_badge.html
---
バッジの挿入
Rmdファイル内に,以下のように指定します:
ほげほげ**bdg 3** ほむほむ**bdg `r 3*4`**
markdownの強調をあらわず**
を利用し,その中の文字列先頭にbdg
(半角スペース必須)を付けます。これがバッジの識別子となり,これを含むRmdファイルをKnit(あるいはrender)すると,以下のようになります:
バッジの識別子であるbdg
は削除されて無事バッジとなります。また,このようにインラインRコードで数値を算出したり差し込むことも可能です。
解説(気になる人だけ読んでください)
今回実装に使うコード自体はそれほどでもないのですが,実はかなりやっかいでした。以下解説します。
Pandoc Markdownによるインライン上の任意の対象を指定
実装上,これが最大の課題になりました。
(現行のRStudio安定版に組み込まれた)Pandoc Markdownにはそんな機能はない
2017/08/29追記: 新しいPandocにはこの機能があることをこちらのコメントより教えていただきました。それに合わせ下記の記事を修正しました。
R MarkdownはKnitしてRコードを評価し,markdownファイルを作成します。これをPandocに通してドキュメントを生成します。そのため,R MarkdownはPandoc Markdownにほとんど依存します1。ただ問題は,**2017/08/29時点でのRStudio安定版に組み込まれている)**Pandocでは「文中のある文字列」といった感じで識別できるような機能は組み込まれておらず2,そのような場合は<span class="hoge">
というようにspan
タグ直打ちによるセレクタ指定しかありません。
今回のバッジ機能を使うためには,インライン要素に対して特定のセレクタを付与させる必要があり,このままでは太刀打ちできません…。
Pandocではなくjsで識別させる
このままでは解決できないので,すべきことを細分化しました:
- 指定したいインライン要素を(とにかく)マークアップ
- マークアップした対象をピックアップ
- ピックアップした対象から,今回バッジにしたい対象を抽出
- 抽出したものをバッジ向けに(タグごと)書き換える
要するに,ターゲットとなるものをマークアップできれば,あとは内容(テキスト)もタグもセレクタもなんとかできるので,1と3をどうにかすれば解決できそうです。
markdownでインライン要素を指定するといえば,強調(**
で挟む)です。R Markdownでこれを用いると<strong>
タグで挟まれるようになります(こちら参照)。そこで,今回インライン要素を指定するのにこれを用いました。
これで確かに1のマークアップおよび2のピックアップはクリアできますが,バッジ以外の通常の強調も拾ってくるので,3に対応します。色々悩みましたが,先頭に自作で識別子をつけるのがスムーズだと判断し,**bdg 3**
のような実装としました。
つまり,「<strong>
要素で,先頭にbdg
がついたもの」を抽出する,です。一度方向性さえ決まれば,この辺りはjsスクリプトでわりとあっさり実現できるので楽でした。
留意事項
あまりないとは思うのですが,バッジを想定していない場所で,**bdg (文字列)**
があると,自動的にバッジにします。注意してください。3
雑感
今回のこのバッジ機能,使いドコロとしてはShinyアプリケーション内とかでしょうか。自分自身でもあまり浮かんできませ。
ですが,解説以降で説明した内容を実現できたのは大きく,この考え方を他に流用することが可能となってさらに拡張することができます。それはまたUpしていきます。
Enjoy!
-
なおPandoc Markdownを経由させないバイパス的な処理をすることもできなくもないですが,Rによる処理が必要かつ大掛かりになるため,今回は不適と判断しました。 ↩
-
私の観測範囲ですが…。もしあるのでしたら教えてください…。ありました。ただ、これを2017/08/29時点で利用するなら、RStudioのPreview版を利用する、あるいはPandocをマシンにインストールして、そこへのパスを設定・指定することとなります。なので今回は記事の修正のみとし、実装方法はこのままとします。 ↩ -
回避する方法として,
**nobdg bdg (文字列)**
とするアイデアもあるのですが,面倒だったので組み込んでません。多分誰も踏まないでしょうし…。 ↩