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

[GitHubでWeb小説の原稿を管理している](https://github.com/8novels)8amjpと申します。

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

## はじめに

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

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

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

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

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

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


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

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

### 青空文庫

[青空文庫作業マニュアル【入力編】](http://www.aozora.gr.jp/aozora-manual/index-input.html)

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

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

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

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

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

[ルビの振り方](https://syosetu.com/man/ruby/)

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

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

例: 強制帰還（ループバック）

だそうです。自由な分、ややこしいですね。
なお[ノベラボ](http://www.novelabo.com/help#ruby)も同じルールです。縦書きなのにエライ。


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

[ルビや傍点を付ける（カクヨム記法を使う）](https://kakuyomu.jp/help/entry/notation)

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

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

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

ルビのルールは青空文庫とほぼ同じですが、例のように記述すると《》（二重山括弧）をそのまま表示させることもできます。
[エブリスタ](https://estar.jp/lp/guide/ruby/)、[NOVEL DAYS](https://novel.daysneo.com/manual.html)も同じルールです。

### ハーメルン

[ルビの振り方](https://syosetu.org/?mode=readme_view&fid=25)

* 開始記号は|（半角縦棒）とする。省略不可。
* 範囲指定記号は《》（二重山括弧）とする。

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

### マグネット

* 開始記号は｜（全角縦棒）または|（半角縦棒）とする。省略不可。
* 範囲指定記号は《》（二重山括弧）とする。

[マグネット](https://www.magnet-novels.com/)のサイトのどこにも記法は明記されていませんが、エディタで試してみた限りではこうでした。

### アルファポリス

[ヘルプ](https://www.alphapolis.co.jp/faq/)より引用:

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

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

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

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

### taskey

[taskeyのルビ機能](http://taskeyu.me/?p=6363)。もはや記法じゃないです。これは無理。

### ルビ非対応のサイト

[note](https://note.mu/)、[時空モノガタリ](http://www.jiku-monogatari.jp/)、[comicoノベル](http://novel.comico.jp/)、[monogatary.com](https://monogatary.com/)などはルビそのものが非対応みたいです。残念。


## JavaScriptでルビ機能を実装する

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

```js
$(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しないと意図した結果にならないので注意です)

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

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

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