日本語と英語を混ぜられるようにbibtexスタイルファイルを改造しよう


全国の卒論や論文に追われる同志の皆様,こんにちは!N.Mです.今回自分も修論の関係で,参考文献をbibtexで管理,出力する必要があり,スタイルの微調整に追われることになりました(修論のルールや日本語文献に合わせる等).
Bibtexで参考文献を書くときに日本語と英語混ぜるのどうしてる?

自分も学部卒論を書いていたときは,この方法でbblファイルをいじって微調整していましたが,Cloud LaTeXなどのようにオンラインのTeXシステムを使用する場合,bblファイルが出力されないため,この方法が使えません.かといって,bibitemで一つ一つ書いていく方法に変更するのは面倒ですし,変更の際にミスが発生してしまう可能性も高いです.なので,bstファイル,bibtexのスタイルファイルを改造しようという話になりました.

前提

まずは,自分が希望するスタイル,並び順に近いスタイルを見つけてきましょう.特に,並び順のソートもbstファイルで実装しているスタイルも存在しています.このソーティングをbstファイルの文法で一から書くのは大変なので,特に並び順を優先してスタイルを探しましょう.今回の自分の修論の場合,第1著者のファミリーネームのアルファベット順にしているようだったので,そのように並び替えられるieeetranS.bstを用いました.この後の説明もこれを用いることにします.

最低限必要な基本文法

※この文法は,bstファイルをいじったり,少し調べてある程度理解した文法なので,間違っているところも多少あるかもしれませんが,この知識だけでも改造することはできました.より詳細な文法はここを読んでください.

・代入

値 '変数 :=

(例)
#1 'japanese.flag :=

japanese.flagは独自変数です.代入する変数の前に'を入れるようです.また値の前にも#を入れます.見ていただいてわかるように,Pascalの文法を逆ポーランド記法(最後に演算子を置く)にしています.

・比較

変数or値 変数or値 =

(例)
japanese.flag #1 =

これで,japanese.flagが1なら真を,違うなら偽を返すようになります.Pascalと同じように,代入には:=を,比較には=を使います.

・if文

条件文
  { 真のときの処理 }
  { 偽のときの処理 }
if$

(例)
#1 japanese.flag =
  { #0 'japanese.flag := }
  { skip$ }
if$

例の場合,japanese.flagが1なら,そのjapanese.flagを0にし,そうでないなら,何も処理しないという分岐処理になります.処理のところにskip$と書くことで,何も処理せず通過します.

さっそく改造してみよう

今回は表題のように,日本語文献の場合にスタイルを切り替えるということをやっていきますが,具体的には

  • フルネーム表記に変更(デフォルトはファーストネームは頭文字で省略される)
  • カンマやピリオドを日本語に合わせて全角にする.
  • 英語タイトルだと"title,"となるところを,日本語に合わせて"タイトル",にする.
  • 著者名で,英語だと最後の一部分で著者 and 著者というようにandがついてしまう部分を,日本語に合わせて全部カンマでつなぐようにする.

ということをやっていきます.ではieeetranS.bstの上から順に改造していきましょう.

フルネーム表記に変更

FUNCTION {default.name.format.string}の部分を以下のように変更してください.ここの部分の詳細は
.bstファイルをいじって著者名表記を変える
に詳しい解説があるので,詳細は省きますが,f.だとファミリーネームが頭文字だけになり,ffだとファミリーネームもフルに表記されるようになります.

FUNCTION {default.name.format.string}{ "{f.~}{vv~}{ll}{, jj}" }
↓
FUNCTION {default.name.format.string}{ "{ff~}{vv~}{ll}{, jj}" }

(19/12/20追記)
英語文献の場合はこれで良いのですが,日本語文献の場合,"{ff~}{vv~}{ll}{, jj}"を"{ff~ }{vv~}{ll}{, jj}" (ff~のあとに半角スペースを入れる)にしないと,著者が6人以上出てきたときに一部苗字と名前の間に半角スペースが現れないようです(詳細の原因は不明です).この切替えについては,「日本語文献の複数著者の場合に出てきてしまうandの抑制」のところで後述します.

準備:エントリの追加

エントリというのは,bibtexでauthor(著者)とかtitleとか指定するところです.さすがに,その文献が日本語かどうかを自動で判別するのは厳しいので,新たにisjapaneseというエントリを用意しておき,もしなにかisjapaneseにデータが入っていれば日本語の文献としてスタイルを整えるというようにします.ieeetranS.bst内にENTRYから始まる

ENTRY
{ address
  assignee
  articleno
  author
  booktitle
  chapter
  day
  ...

みたいに続く部分があるので,ここにisjapaneseを追加します.

ENTRY
{ 
  isjapanese
  address
  assignee
  articleno
  author
  booktitle
  chapter
  day
  ...

これで使用しているbibファイルで

@misc{Label,
  author = {著者},
  year = {2019},
  title = {タイトル},
  isjapanese = {true}
}

のようにisjapaneseのエントリを指定できるようになりました.

(19/02/07追記)
日本語文献が1つや2つなら大丈夫ですが,多くなってくるとisjapaneseのエントリを追加するのも面倒になってしまいます.この記事を書くきっかけとなった記事の投稿者の方が,この記事を見ていただいたようで,さらに2バイト文字が含まれているかどうかで,このisjapaneseを自動で追加するPythonプログラムを書いてくださいましたので,紹介させていただきます.
bibファイルの日本語文献(2バイト文字)を判定してフラグを建てるPythonプログラム

準備:独自変数の定義

日本語の文献かどうかを管理するフラグ変数をあらかじめ定義しておきます.ieeetranS.bstにINTEGERSから始まる

INTEGERS { prev.status.punct this.status.punct punct.std
           punct.no punct.comma punct.period 
           prev.status.space this.status.space space.std
           space.no space.normal space.large
           prev.status.quote this.status.quote quote.std
           quote.no quote.close
           prev.status.nline this.status.nline nline.std
           nline.no nline.newblock 
           status.cap cap.std
           cap.no cap.yes
         }

というような部分があるので,ここに独自変数japanese.flagを追加します.

INTEGERS { prev.status.punct this.status.punct punct.std
           punct.no punct.comma punct.period 
           prev.status.space this.status.space space.std
           space.no space.normal space.large
           prev.status.quote this.status.quote quote.std
           quote.no quote.close
           prev.status.nline this.status.nline nline.std
           nline.no nline.newblock 
           status.cap cap.std
           cap.no cap.yes
           japanese.flag
         }

これで,ieeetranS.bst内で独自変数japanese.flagが使えるようになりました.

準備:独自変数の初期化

上でjapanese.flagを定義しましたが,値を0に初期化する必要があります.ieeetranS.bstに初期化の関数,FUNCTION {initialize.status.constants}があると思いますので,以下のようにjapanese.flagの初期化も追加します.ちなみに,bstファイルの関数は以下のように,FUNCTION {関数名}というように表記します.

FUNCTION {initialize.status.constants}
{ #0 'punct.no :=
  #1 'punct.comma :=
  #2 'punct.period :=
  #0 'space.no := 
  #1 'space.normal :=
  #2 'space.large :=
  #0 'quote.no :=
  #1 'quote.close :=
  #0 'cap.no :=
  #1 'cap.yes :=
  #0 'nline.no :=
  #1 'nline.newblock :=
  #0 'japanese.flag :=
}

日本語文献のカンマやピリオドの全角化,"の後にカンマが来るように変更

さてここからが本題です.まず,どこでカンマやピリオドの処理をしているかについては,","や", "(ダブルクォーテーション含む)で検索をかけると良いでしょう.","や", "が出てくるところが一番怪しいです.今回のieeetranS.bstではFUNCTION {output.nonnull}, FUNCTION {fin.entry}のところで,処理をしていました.", " *と出てくるところをjapanese.flagが0かそうでないかで分岐して,0でない(日本語文献)の場合に"," *というように全角カンマに書き換えれば良いです.以下に自分が修正したieeetranS.bstを示します.("文字列" *がその文字列をくっつけるという処理をしているようです.)

FUNCTION {output.nonnull}

FUNCTION {output.nonnull}
{ swap$
  %%追加箇所1:日本語文献なら,"のあとにカンマ
  japanese.flag #1 =
    {
      prev.status.quote quote.close =
        { "''" * }
        { skip$ }
      if$
    }
    { skip$ }
  if$
  %%追加箇所1ここまで
  %%変更箇所2:日本語文献なら全角カンマに
  japanese.flag #0 =
    { 
      prev.status.punct punct.comma =
        { "," * }
        {skip$}
      if$
    }
    {
      prev.status.punct punct.comma =
        { "," * }
        {skip$}
      if$
    }
  if$ 
  %%変更箇所2ここまで
  %%変更箇所3:日本語の場合は全角ピリオド
  prev.status.punct punct.period =
    { 
      japanese.flag #0 =
        { add.period$ }
        { "." * }
      if$
    }
    { skip$ }
  if$ 
  %%変更箇所3ここまで
  %%変更箇所4:英語の場合は"の前にカンマ
  japanese.flag #0 =
    {
      prev.status.quote quote.close =
        { "''" * }
        { skip$ }
      if$
    }
    { skip$ }
  if$
  %%変更箇所4ここまで
  %%変更箇所5:日本語の場合の謎スペースの抑制(全角カンマの後ろの空白で十分)
  prev.status.space space.normal =
    { 
      japanese.flag #0 =
        { " " * }
        { skip$ }
      if$
    }
    { skip$ }
  if$
  %%変更箇所5ここまで
  prev.status.space space.large =
    { large.space * }
    { skip$ }
  if$
  write$
  prev.status.nline nline.newblock =
    { newline$ "\newblock " write$ }
    { skip$ }
  if$
}

FUNCTION {fin.entry}

FUNCTION {fin.entry}
{ this.status.punct punct.no =
    { skip$ }
    { 
      %%変更箇所:日本語文献の場合,全角ピリオドに
      japanese.flag #0 =
        { add.period$ }
        { "." * }
      if$
    }
  if$
  this.status.quote quote.close =
    { "''" * }
    { skip$ }
  if$
  write$
  newline$
}

日本語文献の複数著者の場合に出てきてしまうandの抑制

英語の場合,複数人著者がいる場合には最後の著者の前にandをつけますが,日本語の場合はandをつけません.この著者名等を扱う場所の見つけ方ですが,まず,authorsの形式を変更するので,FUNCTION{format.authors}のところを見ます.そして,その関数の中をみるとFUNCTION{format.names}という関数を内部で使っていることがわかるので,その関数を見に行くと,該当する場所を見つけられます.このFUNCTION{format.names}の上にFUNCTION{change.name.format.string}を追加し,FUNCTION{format.names}を以下のように変更します.

(19/12/20追記)
change.name.format.string関数を追加したのは,前述のように名前のフォーマット用文字列を英語文献の場合と日本語文献の場合とで切り替えられるようにするためです.

FUNCTION{change.name.format.string}
{ 
  japanese.flag #0 =
  {
    "{ff~}{vv~}{ll}{, jj}" 'name.format.string :=
  }
  {
    "{ff~ }{vv~}{ll}{, jj}" 'name.format.string :=
  }
  if$ 
}

FUNCTION {format.names}
{ 'bibinfo :=
  duplicate$ empty$ 'skip$ {
  this.to.prev.status
  this.status.std
  's :=
  "" 't :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
  change.name.format.string
    { namesleft #0 > }
    { s nameptr
      name.format.string
      format.name$
      bibinfo bibinfo.check
      't :=
      nameptr #1 >
        { nameptr num.names.shown.with.forced.et.al #1 + =
          numnames max.num.names.before.forced.et.al >
          is.forced.et.al and and
            { 
              "others" 't :=
              #1 'namesleft :=
            }
            { skip$ }
          if$
          namesleft #1 >
            { 
            %%変更箇所1:日本語の場合にカンマを全角に変更.
              japanese.flag #0 =
                {
                  ", " * t do.name.latex.cmd * 
                }
                {
                  "," * t do.name.latex.cmd * 
                }
              if$
            %%変更箇所1ここまで
            }
            { s nameptr "{ll}" format.name$ duplicate$ "others" =
                { 't := }
                { pop$ }
              if$
              t "others" =
                { " " * bbl.etal emphasize * }
                { numnames #2 >
                    { 
                      %%変更箇所2:日本語の場合にカンマを全角に変更.
                      japanese.flag #0 =
                        {
                          "," * 
                        }
                        {
                          "," *
                        }
                      if$
                      %%変更箇所2ここまで
                    }
                    { skip$ }
                  if$
                  %%変更箇所3:日本語の場合で著者が2人の場合はandの代わりに全角カンマを入れる.
                  %%bbl.andはandをくっつける処理と推測
                  japanese.flag #0 =
                    {
                      bbl.and
                      space.word * t do.name.latex.cmd *
                    }
                    {
                      numnames #2 =
                        { "," * }
                        { skip$ }
                      if$
                      t do.name.latex.cmd *
                    }
                  if$
                  %%変更箇所3ここまで
                }
              if$
            }
          if$
        }
        { t do.name.latex.cmd }
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
  cap.status.std
  } if$
}

日本語を使う可能性のある文献形式にフラグ管理する処理を追加

あとは,日本語文献の場合に上で書いた処理が動くようにフラグを立てる処理を追加すれば完了です.自分の場合はmisc形式で日本語文献が出てきたので,FUNCTION{misc}に書いています.論文であるarticle形式に適用したい場合はFUNCTION{article}に同じように書きましょう.

FUNCTION {misc}
{ 
  std.status.using.comma
  start.entry
  %%変更箇所1:日本語エントリに何かあれば日本語化するフラグを立てる
  isjapanese empty$
    {skip$}
    {#1 'japanese.flag :=}
  if$
  %%変更箇所1ここまで
  if.url.alt.interword.spacing
  format.authors output
  name.or.dash
  format.article.title output
  format.howpublished "howpublished" bibinfo.check output 
  format.organization "organization" bibinfo.check output
  format.address "address" bibinfo.check output
  format.pages output
  format.date output
  format.note output
  format.url output
  fin.entry
  empty.entry.warn
  if.url.std.interword.spacing
  %%変更箇所2:次の文献のために日本語化フラグの解除(0に戻しておく)
  #0 'japanese.flag :=
  %%変更箇所2ここまで
}

他の形式の文献もそうですが,ここの関数の羅列を見れば,どの要素を記述しているかを知ることができます.今回のようにauthorの形式を変える場合は,FUNCTION{format.authors}を見ました.例えば,ページ数の形式を変えたい場合はFUNCTION{format.pages}を見るというように,FUNCTION{format.要素}のところにその要素の形式が書かれている可能性が高いです.また,表記の順序を変えたい場合は,この関数呼び出しをしているformat.から始まる文を入れ替えればよいでしょう.

結果

以下のようなテストbibファイルを用意しました.

@misc{japaneseTest1,
 author = {山田 一郎 and 山田 次郎 and 山田 三郎 and 山田 四郎},
 year = {2019},
 title = {文献1},
 isjapanese = {true}
}
@misc{japaneseTest2,
 author = {山田 五郎 and 山田 六郎},
 year = {2019},
 title = {文献2},
 isjapanese = {true}
}
@misc{englishTest1,
 author = {Ichiro Yamada and Jiro Yamada and Saburo Yamada and Shiro Yamada},
 year = {2019},
 title = {Title1}
}
@misc{englishTest2,
 author = {Goro Yamada and Rokuro Yamada},
 year = {2019},
 title = {Title2}
}

そして,無事日本語か英語かで形式を切り替えることができました!