JavaScript
HTML5
jQuery
Markdown

各種小説投稿サイトのルビ記法をJavaScriptで実現する

GitHubでWeb小説書いてる8amjpと申します。

原稿は、基本的にMarkdownで書いてるんですが、厄介なのがルビの扱い。
Markdownでルビを表現することは、今のところできません。かと言って、わざわざruby要素を記述するのも面倒です。
なので、執筆時は各種小説投稿サイトで採用されているルビ記法で記述し、表示するときにJavaScriptでruby要素に変換する、という手法を取ることにしました。
その際に調べたことを書いておきます。

はじめに

HTMLでは、ルビを下記のように表記します。

<ruby>瀬尾絵子<rt>せのおえこ</rt></ruby>

ブラウザでは瀬尾絵子せのおえこという風に表示されます。
で、「瀬尾絵子」の部分をベーステキスト、「せのおえこ」の部分をルビテキストと呼びます。

各種小説投稿サイトでは、この表記を簡略化するために、

  • <ruby>に代わるベーステキストの開始記号
  • <rt></rt>に代わるルビテキストの範囲指定記号

を定義しているわけですね。

各種小説投稿サイトのルビ記法

それでは、各サイトのルールを見ていきましょう。

青空文庫

青空文庫作業マニュアル【入力編】

「4. 青空文庫注記形式」では、ルビの入力方法について以下のように定義されています。

  • 開始記号は全角の縦棒「|」とする。
  • 範囲指定記号は《》(二重山括弧)を使用できる。
  • ただし、ベーステキストが漢字の連続である場合は、開始記号を省略できる。

例: 全方位弾《ブロードキャストパケット》 |PM《プロジェクトマネージャー》

自由度は少ないですが、その分ルールはシンプルです。
ちなみに、《》(二重山括弧)をそのまま表示させることは、青空文庫ではできないらしいです。

小説家になろう/ノベラボ

ルビの振り方

小説家になろうでは、以下のように定義されています。

  • 開始記号は全角の縦棒「|」または半角の縦棒「|」とする。
  • 範囲指定記号は《》(二重山括弧)、()(全角丸括弧)、()(半角丸括弧)を使用できる。
  • ただし、ベーステキストが漢字の連続である場合は、開始記号を省略できる。
    • その際、範囲指定記号に()(全角丸括弧)または()(半角丸括弧)を使用した場合は、ルビテキストはカタカナまたはひらがなである必要がある。
  • 括弧内の文字をルビ扱いにしたくない場合は、括弧の直前に開始記号を付ける。

例: 強制帰還(ループバック)

だそうです。自由な分、ややこしいですね。
なおノベラボも同じルールです。縦書きなのにエライ。

カクヨム/エブリスタ/NOVEL DAYS

ルビや傍点を付ける(カクヨム記法を使う)

カクヨム記法では以下のとおり。

  • 開始記号は全角の縦棒「|」または半角の縦棒「|」とする。
  • 範囲指定記号は《》(二重山括弧)を使用できる。
  • ただし、ベーステキストが漢字の連続である場合は、開始記号を省略できる。
  • 括弧内の文字をルビ扱いにしたくない場合は、括弧の直前に開始記号を付ける。

例: |《翠玉女帝》エクシエラ

ルビのルールは青空文庫とほぼ同じですが、例のように記述すると《》(二重山括弧)をそのまま表示させることもできます。
エブリスタNOVEL DAYSも同じルールです。

ハーメルン

ルビの振り方

  • 開始記号は半角の縦棒「|」とする。省略不可。
  • 範囲指定記号は《》(二重山括弧)を使用できる。

とってもシンプルです。潔い。

マグネット

  • 開始記号は全角の縦棒「|」または半角の縦棒「|」とする。省略不可。
  • 範囲指定記号は《》(二重山括弧)を使用できる。

マグネットのサイトのどこにも記法は明記されていませんが、エディタで試してみた限りではこうでした。

アルファポリス

ヘルプより引用:

※ルビの振り方
サンプル:#宇宙__そら__#
上記の例の場合、「宇宙」と書かれた部分が対象のテキストで、「そら」と書かれた部分がルビになります。

……なんでこんなルールにしたの?とりあえず無視。

魔法のiらんど/野いちご

魔法のiらんど野いちごなんかは、ルビ記法は実装されていない代わりに、一部のHTML要素(タグ)が使えるそうです。
ひょっとしたら、直接ruby要素を記述することで実現できるかもしれません。(未確認)
ケータイ小説、けっこう骨太。

taskey

taskeyのルビ機能。もはや記法じゃないです。これは無理。

ルビ非対応のサイト

note時空モノガタリcomicoノベルmonogatary.comなどはルビそのものが非対応みたいです。残念。

JavaScriptでルビ機能を実装する

さて、上記に挙げた記法のうち、青空文庫/小説家になろう/カクヨムの記法通りにルビを振るJavaScript(jQuery)を作成してみます。
なお、文書全体を対象にする必要もないので、対象はarticle要素内の文字列だけにしています。

$(function(){
  $('article').each(function() {
    $(this).html(
      $(this).html()
        /* 半角または全角の縦棒以降の文字をベーステキスト、括弧内の文字をルビテキストとします。 */
        .replace(/[\|](.+?)(.+?)》/g, '<ruby>$1<rt>$2</rt></ruby>')
        .replace(/[\|](.+?)(.+?))/g, '<ruby>$1<rt>$2</rt></ruby>')
        .replace(/[\|](.+?)\((.+?)\)/g, '<ruby>$1<rt>$2</rt></ruby>')
        /* 漢字の連続の後に括弧が存在した場合、一連の漢字をベーステキスト、括弧内の文字をルビテキストとします。 */
        .replace(/([一-龠]+)(.+?)》/g, '<ruby>$1<rt>$2</rt></ruby>')
        /* ただし丸括弧内の文字はひらがなかカタカナのみを指定できます。 */
        .replace(/([一-龠]+)([ぁ-んァ-ヶ]+?))/g, '<ruby>$1<rt>$2</rt></ruby>')
        .replace(/([一-龠]+)\(([ぁ-んァ-ヶ]+?)\)/g, '<ruby>$1<rt>$2</rt></ruby>')
        /* 括弧を括弧のまま表示したい場合は、括弧の直前に縦棒を入力します。 */
        .replace(/[\|](.+?)》/g, '《$1》')
        .replace(/[\|](.+?))/g, '($1)')
        .replace(/[\|]\((.+?)\)/g, '($1)')
    );
  });
});

こんな感じですかね?もっと短くなりそうなんですけど。どうでしょう。

ちなみに、正規表現におけるひらがな・カタカナ・漢字の指定方法ですが。
ひらがなのゾーンは、「ぁあ」から始まり「ん」で終わるので、[ぁ-ん]と指定します。
一方、カタカナのゾーンは、「ァアィイ」から始まり「ヮワヰヱヲンヴヵヶ」という順番で並んでいます。[ァ-ン]だと「ヴ」とかが漏れちゃうので[ァ-ヶ]と指定します。
漢字は……なんだか定義が難しいんですけど、[一-龠]としておけば支障ない、らしいです。ご指摘があればお願いします。

サンプルあるいは宣伝

このスクリプトの適用例を宣伝しておきます。「Redmineで始める異世界人心掌握術」の冒頭部分なんですけど。

こっちがルビ変換前。
https://github.com/8novels/redmine-fantasy/blob/master/episodes/001.md

こっちがルビ変換後です。
https://8novels.github.io/redmine-fantasy/episodes/001.html

おまけ

漢字として扱いたい文字

漢字ではないけれど、漢字として扱ってほしい文字ってありますよね。例えば、「度々」の「々」、「戦場ヶ原」の「ヶ」とかですね。
こういう場合は、[一-龠々ヶ]という風に個別に追加指定すれば漢字扱いにすることができます。

傍点(圏点)

カクヨムでは、《《それ》》というように、二重山括弧ふたつで囲んだ文字に傍点(圏点)を振ることができます。これをJavaScriptで表現するには、

.replace(/《《(.+?)》》/g, '<strong>$1</strong>')

という風にstrong要素にでも変換するのがいいでしょう。
(ただし、一番最初にreplaceしないと意図した結果にならないので注意です)

それから、スタイルシートで

strong {
  -webkit-text-emphasis: filled circle black;
          text-emphasis: filled circle black;
}

とでも指定するのがいいでしょう。
……ま、Markdownなら**それ**で表記できるので別にいいんですけど。