% GB/T 7714 BibTeX Style
% https://github.com/zepinglee/gbt7714-bibtex-style
%
% Copyright (C) 2016-2026 by Zeping Lee <zepinglee AT gmail DOT com>
%
% This file may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3c
% of this license or (at your option) any later version.
% The latest version of this license is in
%    https://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2008 or later.
%
%    \begin{macrocode}
INTEGERS {
  control.convert.punct
  control.max.bib.names
  control.min.bib.names
  control.max.cite.names
  control.min.cite.names
  control.uppercase.family
  control.initialize.with.hyphen
  control.check.pinyin
  control.initialize.pinyin
  control.cite.final.and
  control.bib.final.and
  control.space.before.et.al
  control.year.before.title
  control.sentence.case.title
  control.sentence.case.journal
  control.sentence.case.booktitle
  control.link.title
  control.article.title
  control.patent.country
  control.entry.type.id
  control.space.before.type.id
  control.entry.medium.id
  control.slash
  control.in
  control.emph.booktitle
  control.emph.journal
  control.short.journal
  control.journal.dots
  control.link.journal
  control.bold.volume
  control.unknown.publisher
  control.space.before.pages
  control.page.ranges
  control.urldate
  control.url
  control.doi
  control.cstr
  control.eprint
  control.note
  control.end.dot
  control.capitalize.subtitle
  control.warn.empty.field
%<*authoryear>
  lang.zh.order
  lang.ja.order
  lang.en.order
  lang.ru.order
  lang.other.order
%</authoryear>
}

STRINGS {
  control.bib.punct
  control.cite.lang
  control.name.year.delim
  control.page.range.delim
}

%    \end{macrocode}
%
% 下面每个变量若被设为 |#1| 则启用该项，若被设为 |#0| 则不启用。
% 默认的值是严格遵循国标的配置。
%    \begin{macrocode}
FUNCTION {load.config}
{
%    \end{macrocode}
%
% TODO: v3.0.0 将 2015、2005 版默认值改为 GB。
%    \begin{macrocode}
%<*!(2015|2005|halfwidth|thu|ucas|ustc)>
  "GB" 'control.bib.punct :=  % GB / half / bylanguage
%</!(2015|2005|halfwidth|thu|ucas|ustc)>
%<*2015|2005|halfwidth|thu|ucas|ustc>
  "half" 'control.bib.punct :=  % GB / half / bylanguage
%</2015|2005|halfwidth|thu|ucas|ustc>
%    \end{macrocode}
%
% 控制是否转换标题内部的标点。
%    \begin{macrocode}
  #1 'control.convert.punct :=
%    \end{macrocode}
%
% 姓名的数量超过 |control.max.bib.names| 时，只著录前 |control.min.bib.names| 个，
%  其后加“，等”或与之相应的词。
%    \begin{macrocode}
  #3 'control.max.bib.names :=
  #3 'control.min.bib.names :=
%<*!ucas>
  #1 'control.max.cite.names :=
  #1 'control.min.cite.names :=
%</!ucas>
%<*ucas>
  #2 'control.max.cite.names :=
  #1 'control.min.cite.names :=
%</ucas>
%    \end{macrocode}
%
%    \begin{macrocode}
  #0 'control.bib.final.and :=
  #1 'control.cite.final.and :=
%    \end{macrocode}
%
%     \begin{macrocode}
  #1 'control.space.before.et.al :=
%     \end{macrocode}
%
% 英文姓名转为全大写：
%    \begin{macrocode}
%<*!(2005|2015)|thu|ustc>
  #0 'control.uppercase.family :=
%</!(2005|2015)|thu|ustc>
%<*(2005|2015)&!(thu|ustc)>
  #1 'control.uppercase.family :=
%</(2005|2015)&!(thu|ustc)>
%    \end{macrocode}
%
% 名字缩写时是否保留连字符：
%    \begin{macrocode}
%<*!(2005|2015)>
  #1 'control.initialize.with.hyphen :=
%</!(2005|2015)>
%<*(2005|2015)>
  #0 'control.initialize.with.hyphen :=
%</(2005|2015)>
%    \end{macrocode}
%
% 检查姓名是否为拼音：
%    \begin{macrocode}
  #1 'control.check.pinyin :=
%    \end{macrocode}
%
% 控制用汉语拼音书写的人名是否缩写。
%    \begin{macrocode}
%<*!(2005|2015)>
  #0 'control.initialize.pinyin :=
%</!(2005|2015)>
%<*2005|2015>
  #1 'control.initialize.pinyin :=
%</2005|2015>
%    \end{macrocode}
%
% 使用 TeX 宏输出“和”、“等”
%    \begin{macrocode}
%<*!ucas>
  "byentry" 'control.cite.lang :=  % byentry / chinese / english / macro
%</!ucas>
%<*ucas>
  "macro" 'control.cite.lang :=  % byentry / chinese / english / macro
%</ucas>
%    \end{macrocode}
%
% 将年份置于著者后面（著者-出版年制默认）
%    \begin{macrocode}
%<*numeric|ucas>
  #0 'control.year.before.title :=
%</numeric|ucas>
%<*authoryear&!ucas>
  #1 'control.year.before.title :=
%</authoryear&!ucas>
%    \end{macrocode}
%
% 采用著者-出版年制时，作者姓名与年份之间使用句点连接：
%    \begin{macrocode}
%<*numeric>
  "period" 'control.name.year.delim :=  % comma / period
%</numeric>
%<*authoryear>
%<*!2005>
  "comma" 'control.name.year.delim :=  % comma / period
%</!2005>
%<*2005>
  "period" 'control.name.year.delim :=  % comma / period
%</2005>
%</authoryear>
%    \end{macrocode}
%
% 英文标题转为 sentence case （句首字母大写，其余小写）：
%    \begin{macrocode}
  #1 'control.sentence.case.title :=
  #1 'control.sentence.case.booktitle :=
%<*!(2005|2015)|thu|ustc>
  #0 'control.sentence.case.journal :=
%</!(2005|2015)|thu|ustc>
% TODO: v3.0.0 set this to #1 after implementation of GB/T 7714-2025
%<*(2005|2015)&!(thu|ustc)>
  #0 'control.sentence.case.journal :=
%</(2005|2015)&!(thu|ustc)>
%    \end{macrocode}
%
% Sentence case 的标题在冒号后是否大写：
%    \begin{macrocode}
  #0 'control.capitalize.subtitle :=
%    \end{macrocode}
%
% 在标题添加超链接：
%    \begin{macrocode}
  #0 'control.link.title :=
%    \end{macrocode}
%
% 期刊是否含标题（参考 \pkg{bilatex-phys} 的选项），此选项也适用于 @incollection,
% @inproceedings, @patent 的标题，它们的处理方式相同。
%    \begin{macrocode}
  #1 'control.article.title :=
%    \end{macrocode}
%
% 专利题名是否含专利国别
%    \begin{macrocode}
%<*!(2005|thu)>
  #0 'control.patent.country :=
%</!(2005|thu)>
%<*(2005|thu)>
  #1 'control.patent.country :=
%</(2005|thu)>
%    \end{macrocode}
%
% 控制是否著录类型标识（比如“[M/OL]，含文献载体标识）：
%    \begin{macrocode}
  #1 'control.entry.type.id :=
%    \end{macrocode}
%
% 题名与文献类型标识的分隔符。
%    \begin{macrocode}
  #0 'control.space.before.type.id :=
%    \end{macrocode}
%
% 是否显示文献载体标识（比如“/OL“）：
%    \begin{macrocode}
  #1 'control.entry.medium.id :=
%    \end{macrocode}
%
% 使用“//”表示析出文献
%    \begin{macrocode}
  #1 'control.slash :=
%    \end{macrocode}
%
% 控制是否使用”In:”和“见”。
%    \begin{macrocode}
  #0 'control.in :=
%    \end{macrocode}
%
% 书名使用斜体：
%    \begin{macrocode}
%<*!italic-book-title>
  #0 'control.emph.booktitle :=
%</!italic-book-title>
%<*italic-book-title>
  #1 'control.emph.booktitle :=
%</italic-book-title>
%    \end{macrocode}
%
% 期刊名使用斜体：
%    \begin{macrocode}
  #0 'control.emph.journal :=
%    \end{macrocode}
%
% 期刊名使用缩写：
%    \begin{macrocode}
  #0 'control.short.journal :=
%    \end{macrocode}
%
% 刊名缩写是否去除缩写点：
%    \begin{macrocode}
  #0 'control.journal.dots :=
%    \end{macrocode}
%
% 在期刊题名添加超链接：
%    \begin{macrocode}
  #0 'control.link.journal :=
%    \end{macrocode}
%
% 期刊的卷使用粗体：
%    \begin{macrocode}
  #0 'control.bold.volume :=
%    \end{macrocode}
%
% 无出版地或出版者时，著录“出版地不详”，“出版者不详”，“S.l.” 或 “s.n.”：
%    \begin{macrocode}
  #0 'control.unknown.publisher :=
%    \end{macrocode}
%
% 页码与前面的冒号之间是否有空格：
%    \begin{macrocode}
  #1 'control.space.before.pages :=
%    \end{macrocode}
%
% 页码是否只含起始页：
%    \begin{macrocode}
  #1 'control.page.ranges :=
%    \end{macrocode}
%
% 起止页码中的连接号：
%    \begin{macrocode}
  "-" 'control.page.range.delim :=
%    \end{macrocode}
%
% 是否著录非电子文献的引用日期：
%    \begin{macrocode}
  #1 'control.urldate :=
%    \end{macrocode}
%
% 是否著录 URL：
%    \begin{macrocode}
%<*!(ustc)>
  #1 'control.url :=
%</!(ustc)>
%<*ustc>
  #0 'control.url :=
%</ustc>
%    \end{macrocode}
%
% 是否著录 DOI：
%    \begin{macrocode}
%<*!(2005|ustc)>
  #1 'control.doi :=
%</!(2005|ustc)>
%<*2005|ustc>
  #0 'control.doi :=
%</2005|ustc>
%    \end{macrocode}
%
% 是否著录 CSTR：
%    \begin{macrocode}
%<*!(2005|2015)>
  #1 'control.cstr :=
%</!(2005|2015)>
%<*2005|2015>
  #0 'control.cstr :=
%</2005|2015>
%    \end{macrocode}
%
% 是否著录预印本的标识符 eprint（“arXiv:1112.6136”的编号部分。
%    \begin{macrocode}
  #0 'control.eprint :=
%    \end{macrocode}
%
% 在每一条文献最后输出注释（note）的内容：
%    \begin{macrocode}
  #0 'control.note :=
%    \end{macrocode}
%
% 结尾加句点
%    \begin{macrocode}
  #1 'control.end.dot :=
%    \end{macrocode}
%
% 是否警告缺失的字段。
%    \begin{macrocode}
  #1 'control.warn.empty.field :=
%    \end{macrocode}
%
% 参考文献表按照“著者-出版年”组织时，各个文种的顺序：
%    \begin{macrocode}
%<*authoryear>
  #1 'lang.zh.order :=
  #2 'lang.ja.order :=
  #3 'lang.en.order :=
  #4 'lang.ru.order :=
  #5 'lang.other.order :=
%</authoryear>
}

%    \end{macrocode}
%
%
% \subsection{The ENTRY declaration}
%
%   Like Scribe's (according to pages 231-2 of the April '84 edition),
%   but no fullauthor or editors fields because BibTeX does name handling.
%   The annote field is commented out here because this family doesn't
%   include an annotated bibliography style.  And in addition to the fields
%   listed here, BibTeX has a built-in crossref field, explained later.
%    \begin{macrocode}
ENTRY
  { address
    archiveprefix
    author
    bookauthor
    booksubtitle
    booktitle
    booktitleaddon
    country  % 专利国别
    cstr
    date
    dimensions
    doi
    edition
    editor
    eid
    entrymediumid
    entrysubtype
    entrytypeid
    eventtitle
    eprint
    eprinttype
    holder
    howpublished
    institution
    journal
    journalsubtitle
    journaltitle
    journaltitleaddon
    key
    langid
    language
    location
    mainsubtitle
    maintitle
    maintitleaddon
    mark
    medium
    nationality  % 专利国别，兼容 IEEEtran.bst
    note
    number
    organization
    pages
    publisher
    scale
    school
    series
    shortjournal
    subtitle
    title
    titleaddon
    translator
    url
    urldate
    version
    volume
    year
    CTL_bib_punct
    CTL_convert_punct
    CTL_max_bib_names
    CTL_min_bib_names
    CTL_max_cite_names
    CTL_min_cite_names
    CTL_uppercase_family
    CTL_initialize_with_hyphen
    CTL_check_pinyin
    CTL_initialize_pinyin
    CTL_bib_final_and
    CTL_cite_final_and
    CTL_cite_lang
    CTL_space_before_et_al
    CTL_year_before_title
    CTL_name_year_delim
    CTL_sentence_case
    CTL_sentence_case_title
    CTL_sentence_case_booktitle
    CTL_sentence_case_journal
    CTL_capitalize_subtitle
    CTL_link_title
    CTL_article_title
    CTL_patent_country
    CTL_entry_type_id
    CTL_space_before_type_id
    CTL_entry_medium_id
    CTL_slash
    CTL_in
    CTL_emph_booktitle
    CTL_emph_journal
    CTL_short_journal
    CTL_journal_dots
    CTL_link_journal
    CTL_bold_volume
    CTL_unknown_publisher
    CTL_space_before_pages
    CTL_page_ranges
    CTL_page_range_delim
    CTL_urldate
    CTL_url
    CTL_doi
    CTL_eprint
    CTL_note
    CTL_end_dot
    CTL_warn_empty_field
  }
  { entry.lang is.lang.cjk entry.numbered require.url }
%    \end{macrocode}
%
% These string entry variables are used to form the citation label.
% In a storage pinch, sort.label can be easily computed on the fly.
%    \begin{macrocode}
  { label extra.label sort.label short.list entry.type.id entry.eprint entry.url }

%    \end{macrocode}
%
%
% \subsection{Entry functions}
%
% Each entry function starts by calling output.bibitem, to write the
% |\bibitem| and its arguments to the .BBL file.  Then the various fields
% are formatted and printed by output or output.check.  Those functions
% handle the writing of separators (commas, periods, |\newblock|'s),
% taking care not to do so when they are passed a null string.
% Finally, fin.entry is called to add the final period and finish the
% entry.
%
% A bibliographic reference is formatted into a number of `blocks':
% in the open format, a block begins on a new line and subsequent
% lines of the block are indented.  A block may contain more than
% one sentence (well, not a grammatical sentence, but something to
% be ended with a sentence ending period).  The entry functions should
% call new.block whenever a block other than the first is about to be
% started.  They should call new.sentence whenever a new sentence is
% to be started.  The output functions will ensure that if two
% new.sentence's occur without any non-null string being output between
% them then there won't be two periods output.  Similarly for two
% successive new.block's.
%
% The output routines don't write their argument immediately.
% Instead, by convention, that argument is saved on the stack to be
% output next time (when we'll know what separator needs to come
% after it).  Meanwhile, the output routine has to pop the pending
% output off the stack, append any needed separator, and write it.
%
% To tell which separator is needed, we maintain an output.state.
% It will be one of these values:
%       before.all              just after the |\bibitem|
%       mid.sentence            in the middle of a sentence: comma needed
%                                       if more sentence is output
%       after.sentence          just after a sentence: period needed
%       after.block             just after a block (and sentence):
%                                       period and |\newblock| needed.
% Note: These styles don't use after.sentence
%
% VAR: output.state : INTEGER           -- state variable for output
%
% The output.nonnull function saves its argument (assumed to be nonnull)
% on the stack, and writes the old saved value followed by any needed
% separator.  The ordering of the tests is decreasing frequency of
% occurrence.
%
% \begin{pseudocode}
% output.nonnull(s) ==
%  BEGIN
%       s := argument on stack
%       if output.state = mid.sentence then
%           write$(pop() * ", ")
%                 -- "pop" isn't a function: just use stack top
%       else
%           if output.state = after.block then
%               write$(add.period$(pop()))
%               newline$
%               write$("\newblock ")
%           else
%               if output.state = before.all then
%                   write$(pop())
%               else        -- output.state should be after.sentence
%                   write$(add.period$(pop()) * " ")
%               fi
%           fi
%           output.state := mid.sentence
%       fi
%       push s on stack
%  END
% \end{pseudocode}
%
% The output function calls output.nonnull if its argument is non-empty;
% its argument may be a missing field (thus, not necessarily a string)
%
% \begin{pseudocode}
% output(s) ==
%  BEGIN
%       if not empty$(s) then output.nonnull(s)
%       fi
%  END
% \end{pseudocode}
%
% The output.check function is the same as the output function except that, if
% necessary, output.check warns the user that the t field shouldn't be empty
% (this is because it probably won't be a good reference without the field;
% the entry functions try to make the formatting look reasonable even when
% such fields are empty).
%
% \begin{pseudocode}
% output.check(s,t) ==
%  BEGIN
%       if empty$(s) then
%           warning$("empty " * t * " in " * cite$)
%       else output.nonnull(s)
%       fi
%  END
% \end{pseudocode}
%
% The output.bibitem function writes the |\bibitem| for the current entry
% (the label should already have been set up), and sets up the separator
% state for the output functions.  And, it leaves a string on the stack
% as per the output convention.
%
% \begin{pseudocode}
% output.bibitem ==
%  BEGIN
%       newline$
%       write$("\bibitem[")     % for alphabetic labels,
%       write$(label)           % these three lines
%       write$("]{")            % are used
%       write$("\bibitem{")             % this line for numeric labels
%       write$(cite$)
%       write$("}")
%       push "" on stack
%       output.state := before.all
%  END
% \end{pseudocode}
%
% The fin.entry function finishes off an entry by adding a period to the
% string remaining on the stack.  If the state is still before.all
% then nothing was produced for this entry, so the result will look bad,
% but the user deserves it. (We don't omit the whole entry because the
% entry was cited, and a bibitem is needed to define the citation label.)
%
% \begin{pseudocode}
% fin.entry ==
%  BEGIN
%       write$(add.period$(pop()))
%       newline$
%  END
% \end{pseudocode}
%
% The new.block function prepares for a new block to be output, and
% new.sentence prepares for a new sentence.
%
% \begin{pseudocode}
% new.block ==
%  BEGIN
%       if output.state <> before.all then
%           output.state := after.block
%       fi
%  END
% \end{pseudocode}
%
% \begin{pseudocode}
% new.sentence ==
%  BEGIN
%       if output.state <> after.block then
%           if output.state <> before.all then
%               output.state :=  after.sentence
%           fi
%       fi
%  END
% \end{pseudocode}
%    \begin{macrocode}
INTEGERS { output.state before.all mid.sentence after.sentence after.block after.punct }

INTEGERS { lang.zh lang.ja lang.ko lang.en lang.ru lang.other }

INTEGERS { charptr len }

STRINGS { pinyin.table }

FUNCTION {init.state.consts}
{ #0 'before.all :=
  #1 'mid.sentence :=
  #2 'after.sentence :=
  #3 'after.block :=
  #4 'after.punct :=
  #3 'lang.zh :=
  #4 'lang.ja :=
  #5 'lang.ko :=
  #1 'lang.en :=
  #2 'lang.ru :=
  #0 'lang.other :=
  " a ai an ang ao "
  "ba bai ban bang bao bei ben beng bi bian biao bie bin bing bo bu " *
  "ca cai can cang cao ce cen ceng cha chai chan chang chao che chen cheng chi chong chou chu chuai chuan chuang chui chun chuo ci cong cou cu cuan cui cun cuo " *
  "da dai dan dang dao de dei deng di dia dian diao die ding diu dong dou du duan dui dun duo " *
  "e ei en eng er " *
  "fa fan fang fei fen feng fo fou fu " *
  "ga gai gan gang gao ge gei gen geng gong gou gu gua guai guan guang gui gun guo " *
  "ha hai han hang hao he hei hen heng hong hou hu hua huai huan huang hui hun huo " *
  "ji jia jian jiang jiao jie jin jing jiong jiu ju juan jue jun " *
  "ka kai kan kang kao ke ken keng kong kou ku kua kuai kuan kuang kui kun kuo " *
  "la lai lan lang lao le lei leng li lia lian liang liao lie lin ling liu long lou lu luan lun luo lü lüe lyu lyue " *
  "ma mai man mang mao me mei men meng mi mian miao mie min ming miu mo mou mu " *
  "na nai nan nang nao ne nei nen neng ni nian niang niao nie nin ning niu nong nu nuan nuo nü nüe nyu nyue " *
  "o ou " *
  "pa pai pan pang pao pei pen peng pi pian piao pie pin ping po pou pu " *
  "qi qia qian qiang qiao qie qin qing qiong qiu qu quan que qun " *
  "ran rang rao re ren reng ri rong rou ru ruan rui run ruo " *
  "sa sai san sang sao se sen seng sha shai shan shang shao she shei shen sheng shi shou shu shua shuai shuan shuang shui shun shuo si song sou su suan sui sun suo " *
  "ta tai tan tang tao te teng ti tian tiao tie ting tong tou tu tuan tui tun tuo " *
  "wa wai wan wang wei wen weng wo wu " *
  "xi xia xian xiang xiao xie xin xing xiong xiu xu xuan xue xun " *
  "ya yan yang yao ye yi yin ying yong you yu yuan yue yun " *
  "za zai zan zang zao ze zei zen zeng zha zhai zhan zhang zhao zhe zhei zhen zheng zhi zhong zhou zhu zhua zhuai zhuan zhuang zhui zhun zhuo zi zong zou zu zuan zui zun zuo " *
  'pinyin.table :=
%<*deprecated>
%<*numeric>
  "The style name 'gbt7714-numerical' is deprecated. Use 'gbt7714-numeric' instead." warning$
%</numeric>
%<*authoryear>
  "The style name 'gbt7714-author-year' is deprecated. Use 'gbt7714-authoryear' instead." warning$
%</authoryear>
%</deprecated>
}

%    \end{macrocode}
%
% 下面是一些常量的定义
%    \begin{macrocode}
FUNCTION {bbl.anonymous}
{ entry.lang lang.zh =
    { "佚名" }
    { "Anon" }
  if$
}

FUNCTION {bbl.no.date}
{ entry.lang lang.zh =
    { "无日期" }
    { "n.d." }
  if$
}

FUNCTION {space.precedes.et.al}
{ is.lang.cjk
    { control.space.before.et.al
        { "\ " }
        { "" }
      if$
    }
    { " " }
  if$
}

FUNCTION {bbl.and}
{ entry.lang lang.zh =
    { "和" }
    { entry.lang lang.ja =
        { "と" }
        { entry.lang lang.ru =
            { "и" }
            { "and" }
          if$
        }
      if$
    }
  if$
}

FUNCTION {bbl.et.al}
{ entry.lang lang.zh =
    { "等" }
    { entry.lang lang.ja =
        { "ほか" }
        { entry.lang lang.ru =
            { "и~др." }
            { "et~al." }
          if$
        }
      if$
    }
  if$
}

FUNCTION {cite.and}
{ control.cite.lang "byentry" =
    'bbl.and
    { control.cite.lang "macro" =
        { "{\biband}" }
        { control.cite.lang "chinese" =
            { "和" }
            { "and" }
          if$
        }
      if$
    }
  if$
}

FUNCTION {cite.et.al}
{ control.cite.lang "byentry" =
    { bbl.et.al }
    { control.cite.lang "macro" =
        { "{\bibetal}" }
        { control.cite.lang "chinese" =
            { "等" }
            { "et~al." }
          if$
        }
      if$
    }
  if$
}

FUNCTION {bbl.in}
{ entry.lang lang.zh =
    { "见" }
    { "In" }
  if$
}

FUNCTION {bbl.translator}
{ entry.lang lang.zh =
    { "译" }
    { "trans." }
  if$
}

%<*!2005>
FUNCTION {bbl.wide.space} { "\quad " }
%</!2005>
%<*2005>
FUNCTION {bbl.wide.space} { "\ " }
%</2005>

FUNCTION {bbl.edition}
{ entry.lang lang.zh =
    { "版" }
    { entry.lang lang.ja =
        { "版" }
        { entry.lang lang.ru =
            { "изд." }
            { "ed." }
          if$
        }
      if$
    }
  if$
}

FUNCTION {bbl.double.slash} { "//\allowbreak" }

FUNCTION {bbl.sine.loco}
{ entry.lang lang.zh =
    { "出版地不详" }
    { "S.l." }
  if$
}

FUNCTION {bbl.sine.nomine}
{ entry.lang lang.zh =
    { "出版者不详" }
    { "s.n." }
  if$
}

FUNCTION {bbl.sine.loco.sine.nomine}
{ entry.lang lang.zh =
    { "出版地不详: 出版者不详" }
    { "S.l.: s.n." }
  if$
}

FUNCTION {default.self.tokens} { ":,-'–—?.!" }

FUNCTION {latin.upper} { "ÀÁÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIĴĶĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸŹŻŽ" }

FUNCTION {latin.lower} { "àáãäåæçèéêëìíîïðñòóôõöøùúûüýþÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįiıĵķĺļľŀłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷÿźżž" }

FUNCTION {range.delimiters} { "-–—～" }

%    \end{macrocode}
%
% These three functions pop one or two (integer) arguments from the stack
% and push a single one, either 0 or 1.
% The |'skip$| in the `and' and `or' functions are used because
% the corresponding |if$| would be idempotent
%    \begin{macrocode}
FUNCTION {not}
{   { #0 }
    { #1 }
  if$
}

FUNCTION {and}
{   'skip$
    { pop$ #0 }
  if$
}

FUNCTION {or}
{   { pop$ #1 }
    'skip$
  if$
}

FUNCTION {string.length}
{ #0 swap$
    { duplicate$ "" = not }
    { swap$ #1 + swap$
      #2 global.max$ substring$
    }
  while$
  pop$
}

STRINGS { x y }

STRINGS { replace find text }

% The "X Y contains" function checks if string Y is contained in X.
% Although we only need to check if Y is at the beginning of X,
% it makes no practical difference, so just use it as-is.
FUNCTION {contains}
{ 'find :=
  'text :=
  find string.length 'len :=
  text string.length len - #1 + 'charptr :=
    { charptr #0 >
      text charptr len substring$ find = not
      and
    }
    { charptr #1 - 'charptr := }
  while$
  charptr #0 >
}

%    \end{macrocode}
%
% the variables s and t are temporary string holders
%    \begin{macrocode}
STRINGS { s t }

FUNCTION {add.period}
{ "bylanguage" control.bib.punct =
    { is.lang.cjk
        { "．" * }
        'add.period$
      if$
    }
    'add.period$
  if$
}

FUNCTION {after.period.space}
{ is.lang.cjk
  "bylanguage" control.bib.punct = and
    { "" }
    { " " }
  if$
}

FUNCTION {add.comma}
{ "bylanguage" control.bib.punct =
  is.lang.cjk and
  "GB" control.bib.punct = or
    { "，" * }
    { ", " * }
  if$
}

FUNCTION {add.colon}
{ "bylanguage" control.bib.punct =
  is.lang.cjk and
  "GB" control.bib.punct = or
    { "：" * }
    { ": " * }
  if$
}

FUNCTION {add.colon.by.language}
{ is.lang.cjk
    { "：" * }
    { ": " * }
  if$
}

FUNCTION {bbl.pages.colon}
{ "bylanguage" control.bib.punct =
  is.lang.cjk and
  "GB" control.bib.punct = or
    { "：" }
    { control.space.before.pages
        { ": " }
        { ":\allowbreak " }
      if$
    }
  if$
}

FUNCTION {make.parentheses}
{ "bylanguage" control.bib.punct =
  is.lang.cjk and
  "GB" control.bib.punct = or
    { "（" swap$ * "）" * }
    { "(" swap$ * ")" * }
  if$
}

FUNCTION {make.brackets}
{ "[" swap$ * "]" *
}

INTEGERS { find_length }
FUNCTION { find.replace }
{ 'replace :=
  'find :=
  'text :=
  find string.length 'find_length :=
  ""
    { text empty$ not }
    { text #1 find_length substring$ find =
        { replace *
          text #1 find_length + global.max$ substring$ 'text :=
        }
        { text #1 #1 substring$ *
          text #2 global.max$ substring$ 'text :=
        }
      if$
    }
  while$
}

FUNCTION {convert.fullwidth.punctuations}
{ ", " "，" find.replace
  ": " "：" find.replace
  "; " "；" find.replace
  "! " "！" find.replace
  "!" "！" find.replace
  "? " "？" find.replace
  "?" "？" find.replace
  " (" "（" find.replace
  "(" "（" find.replace
  ") " "）" find.replace
  ")" "）" find.replace
}

FUNCTION {convert.halfwidth.punctuations}
{ "．" ". " find.replace
  "，" ", " find.replace
  "：" ": " find.replace
  "；" "; " find.replace
  "！" "! " find.replace
  "？" "? " find.replace
  "（" " (" find.replace
  "）." ")." find.replace
  "）," ")," find.replace
  "）:" "):" find.replace
  "）;" ");" find.replace
  "）}" ")}" find.replace
  "）" ") " find.replace
  % Strip leading and trailing spaces
  duplicate$ #1 #1 substring$ " " =
    { #2 global.max$ substring$ }
    'skip$
  if$
  duplicate$ #-1 #1 substring$ " " =
    { duplicate$ string.length #1 - #1 swap$ substring$ }
    'skip$
  if$
}

FUNCTION {convert.punctuations}
{ "GB" control.bib.punct =
    { "．" ". " find.replace
      convert.fullwidth.punctuations
    }
    { "half" control.bib.punct =
        { convert.halfwidth.punctuations }
        { "bylanguage" control.bib.punct =
            { is.lang.cjk
                { ". " "．" find.replace
                  convert.fullwidth.punctuations
                }
                { convert.halfwidth.punctuations }
              if$
            }
            'skip$
          if$
        }
      if$
    }
  if$
}

FUNCTION {format.punctuations}
{ control.convert.punct
    'convert.punctuations
    'skip$
  if$
  is.lang.cjk
    { "---" "—" find.replace
      "--" "—" find.replace
    }
    { "“" "``" find.replace
      "”" "''" find.replace
      "‘" "`" find.replace
      "’" "'" find.replace
      "—" "---" find.replace
      "–" "--" find.replace
    }
  if$
}

STRINGS { output.punct }

%    \end{macrocode}
%
% This is similar to biblatex's \setunit.
%    \begin{macrocode}
FUNCTION {set.punct}
{ output.state mid.sentence =
  output.state after.punct = or
    { 'output.punct :=
      after.punct 'output.state :=
    }
    'pop$
  if$
}

FUNCTION {output.nonnull}
{ 's :=
  output.state mid.sentence =
    { add.comma write$ }
    { output.state after.block =
        { add.period write$
          newline$
          "\newblock " write$
        }
        { output.state before.all =
            'write$
            { output.state after.punct =
                { output.punct * write$ }
                { add.period after.period.space * write$ }
              if$
            }
          if$
        }
      if$
      mid.sentence 'output.state :=
    }
  if$
  s
}

FUNCTION {output}
{ duplicate$ empty$
    'pop$
    'output.nonnull
  if$
}

FUNCTION {output.check}
{ 't :=
  duplicate$ empty$
    { pop$
      control.warn.empty.field
        { "empty " t * " in " * cite$ * warning$ }
        'skip$
      if$
    }
    'output.nonnull
  if$
}

%    \end{macrocode}
%
% This function finishes all entries.
%
%    \begin{macrocode}
FUNCTION {fin.entry}
{ control.end.dot
    'add.period
    'skip$
  if$
  write$
  newline$
}

FUNCTION {new.block}
{ output.state before.all =
        'skip$
        { after.block 'output.state := }
  if$
}

FUNCTION {new.sentence}
{ output.state after.block =
    'skip$
    { output.state before.all =
            'skip$
            { after.sentence 'output.state := }
      if$
    }
  if$
}

FUNCTION {new.slash}
{ output.state before.all =
    'skip$
    { control.slash
        { bbl.double.slash * write$
          newline$
          ""
          before.all 'output.state :=
        }
        { new.block }
      if$
    }
  if$
}

%    \end{macrocode}
%
% Sometimes we begin a new block only if the block will be big enough.  The
% new.block.checka function issues a new.block if its argument is nonempty;
% new.block.checkb does the same if either of its TWO arguments is nonempty.
%    \begin{macrocode}
FUNCTION {new.block.checka}
{ empty$
    'skip$
    'new.block
  if$
}

FUNCTION {new.block.checkb}
{ empty$
  swap$ empty$
  and
    'skip$
    'new.block
  if$
}

%    \end{macrocode}
%
% The new.sentence.check functions are analogous.
%    \begin{macrocode}
FUNCTION {new.sentence.checka}
{ empty$
    'skip$
    'new.sentence
  if$
}

FUNCTION {new.sentence.checkb}
{ empty$
  swap$ empty$
  and
    'skip$
    'new.sentence
  if$
}

%    \end{macrocode}
%
% In order to support UTF-8 encoding, we need some auxiliary functions.  Below
% are a series of such functions.  We try to make functions loosely-coupled as
% much as possible.  Where the use of variables is inevitable in functions, we
% generally assume it is the caller's responsibility to save and restore those
% variables.  Exceptions are made for some unary functions, where it is
% convenient for the callee to do so.
%    \begin{macrocode}
INTEGERS { a b }

%    \end{macrocode}
%
% Function |is.int.in.range| takes a codepoint and two integers and check if the
% codepoint is between these two integers (inclusive).
%    \begin{macrocode}
% codepoint: int, a: int, b: int -> bool
% variable used: b
FUNCTION {is.int.in.range}
{ 'b :=
  #1 +
  b >
    { #1 - b < }
    { pop$ #0 }
  if$
}

%    \end{macrocode}
%
% Function |mult.power2| takes two integers and returns \(2^nm\).
%    \begin{macrocode}
% m: int, n: int -> int
FUNCTION {mult.power2}
{ { duplicate$ #0 > }
    { swap$
      duplicate$ +
      swap$ #1 -
    }
  while$
  pop$
}

%    \end{macrocode}
%
% Function |find.match.brace| takes two strings, the first of which is assumed
% to be |"{"|, and find the matching brace in the second string.  It returns a
% token (or subtoken) and the rest of the string after the matching brace.  When
% braces are unmatched, it issues a warning and complete the brace
% automatically, following the convention of the original \BibTeX{}.
%    \begin{macrocode}
% "{", str -> subtoken: str, rest: str
% variables used: s, t
FUNCTION {find.match.brace}
{ 's :=
  't :=
  #1
  { duplicate$ #0 >
    s empty$ not and }
    { s #1 #1 substring$ "{" =
        { #1 + }
        { s #1 #1 substring$ "}" =
            { #1 - }
            'skip$
          if$
        }
      if$
      t s #1 #1 substring$ * 't :=
      s #2 global.max$ substring$ 's :=
    }
  while$

  duplicate$ #0 >
    { "Unbalanced brace(s): one or more closing braces are missing" warning$
      { duplicate$ #0 > }
        { t "}" * 't :=
          #1 -
        }
      while$
    }
    'skip$
  if$
  pop$

  t
  s
}

%    \end{macrocode}
%
% Function |split.first.char.from.str| takes a UTF-8 string and return
% the first UTF-8 character and the rest of the string in reverse order.
%    \begin{macrocode}
% str -> str, char
FUNCTION {split.first.char.from.str}
{ duplicate$ "" =
    { "split.first.char.from.str: Trying to split an empty string!" warning$
      ""
    }
    { duplicate$ #1 #1 substring$ chr.to.int$ #128 <
        { duplicate$ #1 #1 substring$ swap$
          #2 global.max$ substring$ swap$
        }
        { duplicate$ #1 #1 substring$ chr.to.int$ #224 <
            { duplicate$ #1 #2 substring$ swap$
              #3 global.max$ substring$ swap$
            }
            { duplicate$ #1 #1 substring$ chr.to.int$ #240 <
                { duplicate$ #1 #3 substring$ swap$
                  #4 global.max$ substring$ swap$
                }
                { duplicate$ #1 #4 substring$ swap$
                  #5 global.max$ substring$ swap$
                }
              if$
            }
          if$
        }
      if$
    }
  if$
}

%    \end{macrocode}
%
% Function |get.first.char.from.str| takes a UTF-8 string and return the
% first UTF-8 character.
%    \begin{macrocode}
% str -> char
FUNCTION {get.first.char.from.str}
{ split.first.char.from.str swap$ pop$ }

%    \end{macrocode}
%
% Function |split.first.tex.char.from.str| is like
% |split.first.char.from.str|.  It takes a UTF-8 string and return the
% first UTF-8 character or first \TeX group and the rest of string in
% reverse order.
%    \begin{macrocode}
% str -> rest: str, texchar
FUNCTION {split.first.tex.char.from.str}
{ duplicate$ #1 #1 substring$ "{" =
    { split.first.char.from.str swap$
      find.match.brace swap$
    }
    'split.first.char.from.str
  if$
}

%    \end{macrocode}
%
% Function |char.to.unicode| takes a UTF-8 character and returns its
% codepoint in Unicode.  It issues a warning and returns \(-1\) if the
% presumed character is an empty string.  For other invalid input, the
% behavior is undefined.
%    \begin{macrocode}
% char -> int
FUNCTION {char.to.unicode}
{ duplicate$ #4 #1 substring$ "" =
    { duplicate$ #3 #1 substring$ "" =
        { duplicate$ #2 #1 substring$ "" =
            { duplicate$ "" =
                { "Empty string is not a char!" warning$
                  pop$ #-1
                }
                { #1 #1 substring$ chr.to.int$ }
              if$
            }
            { duplicate$ #2 #1 substring$ chr.to.int$ #128 - swap$
              #1 #1 substring$ chr.to.int$ #192 -
              #6 mult.power2 +
            }
          if$
        }
        { duplicate$ #3 #1 substring$ chr.to.int$ #128 - swap$
          duplicate$ #2 #1 substring$ chr.to.int$ #128 - swap$
          #1 #1 substring$ chr.to.int$ #224 -
          #6 mult.power2 +
          #6 mult.power2 +
        }
      if$
    }
    { duplicate$ #4 #1 substring$ chr.to.int$ #128 - swap$
      duplicate$ #3 #1 substring$ chr.to.int$ #128 - swap$
      duplicate$ #2 #1 substring$ chr.to.int$ #128 - swap$
      #1 #1 substring$ chr.to.int$ #240 -
      #6 mult.power2 +
      #6 mult.power2 +
      #6 mult.power2 +
    }
  if$
}

%    \end{macrocode}
%
% Function |is.char.in.str| takes a string and a UTF-8 character.  It
% checks whether the character is in the string.  It issues a warning
% and returns \(0\) if the presumed character is an empty string.  It
% also returns \(0\) if the string itself is empty.  For other input,
% the behavior is undefined.
%    \begin{macrocode}
% str, char -> bool
% variable used: t
FUNCTION {is.char.in.str}
{ 't :=
  t "" =
    { "is.char.in.str: Empty string is not a char!" warning$ }
    'skip$
  if$

  #0 swap$
  { duplicate$ "" = not }
    { split.first.char.from.str t =
        { pop$ pop$ #1 "" }
        'skip$
      if$
    }
  while$
  pop$
}

%    \end{macrocode}
%
% Function |is.upper.ascii| takes a UTF-8 character and checks whether
% it is an uppercase ASCII letter.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.upper.ascii}
{ char.to.unicode #65 swap$ #90 swap$ is.int.in.range }

%    \end{macrocode}
%
% Function |is.upper| takes a UTF-8 character and checks whether it is
% uppercase in the range from |U+0000| to |U+017F|.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.upper}
{ duplicate$ is.upper.ascii
    { pop$ #1 }
    { latin.upper swap$ is.char.in.str }
  if$
}

%    \end{macrocode}
%
% Function |is.lower.ascii| takes a UTF-8 character and checks whether
% it is a lowercase ASCII letter.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.lower.ascii}
{ char.to.unicode #97 swap$ #122 swap$ is.int.in.range }

%    \end{macrocode}
%
% Function |is.upper| takes a UTF-8 character and checks whether it is
% lowercase in the range from |U+0000| to |U+017F|.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.lower}
{ duplicate$ is.lower.ascii
    { pop$ #1 }
    { latin.lower swap$ is.char.in.str }
  if$
}

%    \end{macrocode}
%
% Function |is.printable.ascii| takes a UTF-8 character and checks
% whether it is a printable ASCII character.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.printable.ascii}
{ char.to.unicode #32 swap$ #126 swap$ is.int.in.range }

%    \end{macrocode}
%
% Function |is.letter.ascii| takes a UTF-8 character and checks
% whether it is an ASCII letter.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.letter.ascii}
{ duplicate$ is.upper.ascii swap$ is.lower.ascii or }

%    \end{macrocode}
%
% Function |is.symbol.ascii| takes a UTF-8 character and checks whether
% it is a printable ASCII character but not an ASCII letter.
%    \begin{macrocode}
% char -> bool
% variable used: b
FUNCTION {is.symbol.ascii}
{ duplicate$ is.printable.ascii swap$ is.letter.ascii not and }

%    \end{macrocode}
%
% Function |is.all.lower| takes a string and checks whether every
% character in it is lowercase in the range from |U+0000| to |U+017F|.
%    \begin{macrocode}
% str -> bool
% variable used: b
% return true if str is empty
FUNCTION {is.all.lower}
{ #1 swap$
  { duplicate$ "" = not }
    { split.first.char.from.str is.lower
        'skip$
        { pop$ pop$ #0 "" }
      if$
    }
  while$
  pop$
}

% str -> bool
% variable used: b
FUNCTION {is.tex.str.in.title.case}
{ duplicate$ "" =
    { pop$ #0 }
    { split.first.tex.char.from.str purify$
      duplicate$ "" =
        { pop$ pop$ #0 }
        { split.first.char.from.str is.upper
            { duplicate$ "" =  % single-letter word
                { pop$ pop$ #1 }
                { duplicate$ is.all.lower
                    { empty$
                        { duplicate$ "" =
                            { pop$ #0 }
                            'is.all.lower
                          if$
                        }
                        'is.all.lower
                      if$
                    }
                    { pop$ pop$ #0 }
                  if$
                }
              if$
            }
            { pop$ pop$ #0}
          if$
        }
      if$
    }
  if$
}

% char, int -> bool
% variables used: t, b
FUNCTION {is.in.inter.token.chars}
{ duplicate$ #0 =
    { pop$ " " = }
    { #1 =
        { " " range.delimiters * swap$ is.char.in.str }
        'is.letter.ascii
      if$
    }
  if$
}

% str, int -> intertoken: str, rest: str
% variable used: t, b
FUNCTION {skip.inter.token.chars.by}
{ 'b :=
  't :=

  "" t
  { duplicate$ "" = not }
    { split.first.char.from.str
      duplicate$ b is.in.inter.token.chars
        { swap$ 't := * t }
        { swap$ * 't := "" }
      if$
    }
  while$

  pop$ t
}

% str -> intertoken: str, rest: str
% variable used: t, b
FUNCTION {skip.inter.token.chars}
{ #0 skip.inter.token.chars.by }

% str -> intertoken: str, rest: str
% variable used: t, b
FUNCTION {skip.inter.token.command}
{ duplicate$ "" =
    { "" }
    { duplicate$ #1 #1 substring$ is.symbol.ascii
        { split.first.char.from.str swap$ }
        { #2 skip.inter.token.chars.by }
     if$
    }
  if$
}

% cmdstr -> cmdstr
FUNCTION {is.special.char.command}
{ #2 global.max$ substring$ skip.inter.token.command
  empty$
    'skip$
    { "is.special.char.command: cmdstr has extra components!" warning$ }
  if$

  duplicate$ duplicate$ duplicate$ duplicate$ duplicate$ duplicate$
  "oOlLij" swap$ is.char.in.str
  swap$ "oe" = or
  swap$ "OE" = or
  swap$ "ae" = or
  swap$ "AE" = or
  swap$ "aa" = or
  swap$ "AA" = or
}

% str, str, char -> char
% variable used: t
FUNCTION {map.char}
{ 't :=
  split.first.char.from.str
  { swap$ duplicate$ "" = not }
    { swap$ t =
        { pop$ "" t }
        { swap$ split.first.char.from.str pop$ swap$
          split.first.char.from.str
        }
      if$
    }
  while$
  pop$ t =
    'get.first.char.from.str
    { pop$ t }
  if$
}

% char -> char
% variables used: t, b
FUNCTION {to.lower}
{ duplicate$ is.upper.ascii
    { chr.to.int$ #32 + int.to.chr$ }
    { latin.lower swap$ latin.upper swap$ map.char }
  if$
}

% char -> char
% variables used: t, b
FUNCTION {to.upper}
{ duplicate$ is.lower.ascii
    { chr.to.int$ #32 - int.to.chr$ }
    { latin.upper swap$ latin.lower swap$ map.char }
  if$
}

% str -> str
% variables used: t, b
FUNCTION {all.to.lower}
{ "" swap$
  { duplicate$ empty$ not }
    { split.first.char.from.str to.lower swap$ 't := * t }
  while$
  *
}

% texchar -> texchar
% variables used: t, b
FUNCTION {command.to.lower}
{ duplicate$ "" =
    { "command.to.lower: Empty string is not a texchar!" warning$ }
    { duplicate$ #1 #1 substring$ #92 int.to.chr$ =
        { duplicate$ is.special.char.command
            'all.to.lower
            'skip$
          if$
        }
        'to.lower
      if$
    }
  if$
}

% texchar -> texchar
% variables used: t, b
FUNCTION {tex.to.lower}
{ duplicate$ #1 #2 substring$ "{" #92 int.to.chr$ * =
    { "" swap$
      { duplicate$ "" = not }
        { split.first.char.from.str
          duplicate$ #92 int.to.chr$ =
            { swap$ skip.inter.token.command 't := * t
              swap$ command.to.lower
            }
            'to.lower
          if$
          swap$ 't := * t
        }
      while$
      pop$
    }
    { duplicate$ #1 #1 substring$ "{" =
        { split.first.char.from.str swap$ find.match.brace pop$ }
        'command.to.lower
      if$
    }
  if$
}

% str -> str
% variables used: t, b
FUNCTION {all.to.upper}
{ "" swap$
  { duplicate$ empty$ not }
    { split.first.char.from.str to.upper swap$ 't := * t }
  while$
  *
}

% texchar -> texchar
% variables used: t, b
FUNCTION {command.to.upper}
{ duplicate$ "" =
    { "command.to.lower: Empty string is not a texchar!" warning$ }
    { duplicate$ #1 #1 substring$ #92 int.to.chr$ =
        { duplicate$ is.special.char.command
             'all.to.upper
             'skip$
           if$
        }
        'to.upper
      if$
    }
  if$
}

% texchar -> texchar
% variables used: t, b
FUNCTION {tex.to.upper}
{ duplicate$ #1 #2 substring$ "{" #92 int.to.chr$ * =
    { "" swap$
      { duplicate$ "" = not }
      { split.first.char.from.str
        duplicate$ #92 int.to.chr$ =
          { swap$ skip.inter.token.command 't := * t
            swap$ command.to.upper
          }
          'to.upper
        if$
        swap$ 't := * t
      }
      while$
      pop$
    }
    { duplicate$ #1 #1 substring$ "{" =
        { split.first.char.from.str swap$ find.match.brace pop$ }
        'command.to.upper
      if$
    }
  if$
}

% texstr -> texstr
% variable used: t, b
FUNCTION {lower.token.if.in.title.case}
{ duplicate$ is.tex.str.in.title.case
    { split.first.tex.char.from.str tex.to.lower swap$ * }
    'skip$
  if$
}

% int -> str
FUNCTION {self.tokens}
{ #0 =
    'default.self.tokens
    'range.delimiters
  if$
}

% str, int -> token: str, rest: str
% variables used: s, t, b
FUNCTION {tokenize.by}
{ 'b :=
  's :=

  s "" =
    { "" "" }
    { s split.first.char.from.str
      duplicate$ b self.tokens swap$ is.char.in.str
        'swap$
        { duplicate$ #92 int.to.chr$ =
            { swap$ skip.inter.token.command 's := * s }
            { pop$ pop$ "" s
              { duplicate$ "" = not }
                { split.first.char.from.str
                  duplicate$ "\ " b self.tokens * swap$ is.char.in.str
                    { pop$ pop$ "" }
                    { duplicate$ "{" =
                        { swap$ find.match.brace }
                        'swap$
                      if$
                      's := * s
                    }
                  if$
                }
              while$
              pop$ s
            }
          if$
        }
      if$
    }
  if$
}

% str -> str
% variables used: s, t, b
FUNCTION {tokenize}
{ #0 tokenize.by }

% str -> str
% variables used: s, t, b
FUNCTION {smart.sentence.case}
{ tokenize 's :=

  { s "" = not }
    { s skip.inter.token.chars 's := * s
      tokenize swap$
      duplicate$ ":" =
        { swap$ 's := *
          s skip.inter.token.chars 's := * s
          tokenize swap$
          control.capitalize.subtitle
            'skip$
            { lower.token.if.in.title.case }
          if$
        }
        'lower.token.if.in.title.case
      if$
      swap$ 's := *
    }
  while$
}

% str -> str
% variables used: s, t, b
FUNCTION {smart.upper.case}
{ s swap$ t swap$

  "" swap$
  { duplicate$ "" = not }
    { tokenize swap$
      duplicate$ #1 #1 substring$ #92 int.to.chr$ =
        'command.to.upper
        { "" swap$
          { duplicate$ "" = not }
            { split.first.tex.char.from.str tex.to.upper
              swap$ 't := * t
            }
          while$
          pop$
        }
      if$
      swap$ 't := * t
      skip.inter.token.chars 't := * t
    }
  while$
  pop$

  swap$ 't :=
  swap$ 's :=
}

%    \end{macrocode}
%
%
% \subsection{Formatting chunks}
%
% Here are some functions for formatting chunks of an entry.
% By convention they either produce a string that can be followed by
% a comma or period (using |add.period$|, so it is OK to end in a period),
% or they produce the null string.
%
% A useful utility is the field.or.null function, which checks if the
% argument is the result of pushing a `missing' field (one for which no
% assignment was made when the current entry was read in from the database)
% or the result of pushing a string having no non-white-space characters.
% It returns the null string if so, otherwise it returns the field string.
% Its main (but not only) purpose is to guarantee that what's left on the
% stack is a string rather than a missing field.
%
% \begin{pseudocode}
% field.or.null(s) ==
%  BEGIN
%       if empty$(s) then return ""
%       else return s
%  END
% \end{pseudocode}
%
% Another helper function is emphasize, which returns the argument emphasized,
% if that is non-empty, otherwise it returns the null string.  Italic
% corrections aren't used, so this function should be used when punctuation
% will follow the result.
%
% \begin{pseudocode}
% emphasize(s) ==
%  BEGIN
%       if empty$(s) then return ""
%       else return "{\em " * s * "}"
% \end{pseudocode}
%
% The `pop\$' in this function gets rid of the duplicate `empty' value and
% the `skip\$' returns the duplicate field value
%    \begin{macrocode}
FUNCTION {field.or.null}
{ duplicate$ empty$
    { pop$ "" }
    'skip$
  if$
}

FUNCTION {emphasize}
{ duplicate$ empty$
    { pop$ "" }
    { "\emph{" swap$ * "}" * }
  if$
}

FUNCTION {emphasize.book.title}
{ control.emph.booktitle
  is.lang.cjk not and
    'emphasize
    'skip$
  if$
}

%    \end{macrocode}
%
% \subsubsection{Detect Language}
%    \begin{macrocode}
INTEGERS { codepoint tmp.lang }

FUNCTION {get.codepoint.lang}
{ 'codepoint :=
  codepoint #128 <
    { codepoint #64 > codepoint #91 < and codepoint #96 > codepoint #123 < and or
        { lang.en }
        { lang.other }
      if$
    }
%    \end{macrocode}
% 俄文西里尔字母：U+0400 到 U+052F，1024--1328。
%    \begin{macrocode}
    { codepoint #1023 > codepoint #1328 < and
        { lang.ru }
%    \end{macrocode}
% CJK Unified Ideographs: U+4E00--U+9FFF; 19968--40959.
%    \begin{macrocode}
        { codepoint #19967 > codepoint #40960 < and
            { lang.zh }
%    \end{macrocode}
% CJK Unified Ideographs Extension A: U+3400--U+4DBF; 13312--19903.
%    \begin{macrocode}
            { codepoint #13311 > codepoint #19904 < and
                { lang.zh }
%    \end{macrocode}
% 日语假名：U+3040--U+30FF, 12352--12543.
%    \begin{macrocode}
                { codepoint #12351 > codepoint #12544 < and
                    { lang.ja }
%    \end{macrocode}
% 韩语谚文音节：U+AC00--U+D7AF, 44032--55215.
%    \begin{macrocode}
                    { codepoint #44031 > codepoint #55216 < and
                        { lang.ko }
                        { lang.other }
                      if$
                    }
                  if$
                }
              if$
            }
          if$
        }
      if$
    }
  if$
}

FUNCTION {get.str.lang}
{ lang.other 'tmp.lang :=
    { duplicate$ empty$ not }
    { split.first.char.from.str
      char.to.unicode
      get.codepoint.lang
      duplicate$ tmp.lang >
        { 'tmp.lang := }
        'pop$
      if$
    }
  while$
  duplicate$ empty$
    { pop$ }
    { "non-empty string " quote$ swap$ * quote$ " left" warning$ }
  if$
  tmp.lang
}

FUNCTION {check.entry.lang}
{ title field.or.null
  duplicate$ empty$
    { author field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { journal field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { journaltitle field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { booktitle field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { address field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { location field.or.null * }
    'skip$
  if$
  duplicate$ empty$
    { publisher field.or.null * }
    'skip$
  if$
  get.str.lang
}

FUNCTION {set.entry.lang}
{ langid empty$
    { language empty$
        { "" }
        { language }
      if$
    }
    { langid }
  if$
  "l" change.case$
  's :=
  s empty$
    { check.entry.lang }
    { s "english" = s "american" = or s "british" = or
        { lang.en }
        { s "chinese" =
            { lang.zh }
            { s "japanese" =
                { lang.ja }
                { s "korean" =
                    { lang.ko }
                    { s "russian" =
                        { lang.ru }
                        { lang.other }
                      if$
                    }
                  if$
                }
              if$
            }
          if$
        }
      if$
    }
  if$
  'entry.lang :=
  #0 'is.lang.cjk :=
  entry.lang lang.zh =
  entry.lang lang.ja = or
  entry.lang lang.ko = or
    { #1 'is.lang.cjk := }
    'skip$
  if$
}

FUNCTION {set.entry.numbered}
{ type$ "archive" =
  type$ "letter" = or
  type$ "legislation" = or
  type$ "patent" = or
  type$ "regulation" = or
  type$ "report" = or
%<*2005|2015>
  type$ "standard" = or
%</2005|2015>
  type$ "techreport" = or
    { #1 'entry.numbered := }
    { #0 'entry.numbered := }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format names}
%
% The format.names function formats the argument (which should be in
% BibTeX name format) into "First Von Last, Junior", separated by commas
% and with an "and" before the last (but ending with "et~al." if the last
% of multiple authors is "others").  This function's argument should always
% contain at least one name.
%
% \begin{pseudocode}
% VAR: nameptr, namesleft, numnames: INTEGER
% pseudoVAR: nameresult: STRING         (it's what's accumulated on the stack)
%
% format.names(s) ==
%  BEGIN
%       nameptr := 1
%       numnames := num.names$(s)
%       namesleft := numnames
%       while namesleft > 0
%         do
%                               % for full names:
%           t := format.name$(s, nameptr, "{ff~}{vv~}{ll}{, jj}")
%                               % for abbreviated first names:
%           t := format.name$(s, nameptr, "{f.~}{vv~}{ll}{, jj}")
%           if nameptr > 1 then
%               if namesleft > 1 then nameresult := nameresult * ", " * t
%               else if numnames > 2
%                      then nameresult := nameresult * ","
%                    fi
%                    if t = "others"
%                      then nameresult := nameresult * " et~al."
%                      else nameresult := nameresult * " and " * t
%                    fi
%               fi
%           else nameresult := t
%           fi
%           nameptr := nameptr + 1
%           namesleft := namesleft - 1
%         od
%       return nameresult
%  END
% \end{pseudocode}
%
% The format.authors function returns the result of format.names(author)
% if the author is present, or else it returns the null string
%
% \begin{pseudocode}
% format.authors ==
%  BEGIN
%       if empty$(author) then return ""
%       else return format.names(author)
%       fi
%  END
% \end{pseudocode}
%
% Format.editors is like format.authors, but it uses the editor field,
% and appends ", editor" or ", editors"
%
% \begin{pseudocode}
% format.editors ==
%  BEGIN
%       if empty$(editor) then return ""
%       else
%           if num.names$(editor) > 1 then
%               return format.names(editor) * ", editors"
%           else
%               return format.names(editor) * ", editor"
%           fi
%       fi
%  END
% \end{pseudocode}
%
% Other formatting functions are similar, so no "comment version" will be
% given for them.
%    \begin{macrocode}
INTEGERS { nameptr namesleft numnames name.lang }

FUNCTION {is.lowercase.word}
{ #1
    { #0 > }
    { duplicate$ #1 #1 substring$ chr.to.int$
      duplicate$ #96 >
      swap$      #123 <
      and
        { #2 global.max$ substring$
          duplicate$ empty$
            { #0 }
            { #1 }
          if$
        }
        { #0 }
      if$
    }
  while$
  empty$
}

% Assume already changed to lowercase
FUNCTION {is.single.pinyin}
{ " " swap$ * " " *
  pinyin.table swap$ contains
}

% Assume already changed to lowercase
FUNCTION {is.single.pinyin.word}
{ duplicate$ is.lowercase.word
    'is.single.pinyin
    { pop$ #0 }
  if$
}

% Check if the word is pinyin of one or two characters.
% Assume already changed to lowercase.
% This function is extremely slow, so it should be used only when necessary.
FUNCTION {is.double.pinyin}
{ duplicate$ string.length 'a :=
  #1
    { a #0 > and }
    { duplicate$ #1 a substring$ is.single.pinyin
        { duplicate$ a #1 + global.max$ substring$
          duplicate$ empty$
            { pop$ #0 }
            { is.single.pinyin
                { #0 }
                { a #1 - 'a :=
                  #1
                }
              if$
            }
          if$
        }
        { a #1 - 'a :=
          #1
        }
      if$
    }
  while$
  't :=
  t #1 #1 substring$
  t a #1 + #1 substring$
  duplicate$ empty$
    'pop$
    { " " swap$ * * }
  if$
  't :=
  a #0 >
}

FUNCTION {is.double.pinyin.word}
{ duplicate$ is.lowercase.word
    'is.double.pinyin
    { pop$ #0 }
  if$
}

FUNCTION {extract.before}
{ 'find :=
  'text :=
  find string.length 'find_length :=
  ""
    { text empty$ not }
    { text #1 find_length substring$ find =
        { "" 'text := }
        { text #1 #1 substring$ *
          text #2 global.max$ substring$ 'text :=
        }
      if$
    }
  while$
}

FUNCTION {extract.after}
{ 'find :=
  'text :=
  find string.length 'find_length :=
  ""
    { text empty$ not }
    { text #1 find_length substring$ find =
        { text find_length #1 + global.max$ substring$ *
          "" 'text :=
        }
        { text #2 global.max$ substring$ 'text := }
      if$
    }
  while$
}

FUNCTION {format.western.given.name}
{ s nameptr "{ f.}" format.name$
  control.initialize.with.hyphen
    'skip$
    { duplicate$ "-" contains
        { "-" " " find.replace
          "u" change.case$
        }
        'skip$
      if$
    }
  if$
  s nameptr "{, jj}" format.name$ *
  "." "" find.replace
}

FUNCTION {is.family.name.pinyin}
{ s nameptr "{ll}" format.name$
  duplicate$ empty$
    { pop$ #0 }
    { "l" change.case$
      duplicate$ "-" contains
        { duplicate$ "-" extract.before is.single.pinyin.word
            { "-" extract.after is.single.pinyin.word }
            { pop$ #0 }
          if$
        }
        { % 这个函数实在太慢了
          % is.double.pinyin
          is.single.pinyin.word
        }
      if$
    }
  if$
}

FUNCTION {is.given.name.pinyin}
{ s nameptr "{ff}" format.name$
  duplicate$ empty$
    { pop$ #0 }
    { "l" change.case$
      "’" "'" find.replace
      duplicate$ "'" contains
        { duplicate$ "'" extract.before
          duplicate$ #1 #1 substring$ 't :=
          is.single.pinyin.word
            { "'" extract.after
              duplicate$ #1 #1 substring$ t " " * swap$ * 't :=
              is.single.pinyin.word
            }
            { pop$ #0 }
          if$
        }
        'is.double.pinyin.word
      if$
    }
  if$
}

FUNCTION {is.name.pinyin}
{ s nameptr "{vv}{jj}" format.name$ empty$
    { is.family.name.pinyin
        'is.given.name.pinyin
        { #0 }
      if$
    }
    { #0 }
  if$
}

FUNCTION {format.latin.name}
{ s nameptr "{vv~}{ll}" format.name$
  control.uppercase.family
    'smart.upper.case
    'skip$
  if$
  control.check.pinyin
    { is.name.pinyin
        { control.initialize.pinyin
            { " " t "u" change.case$ * }
            { s nameptr "{ ff}" format.name$ }
          if$
        }
        { format.western.given.name }
      if$
    }
    { format.western.given.name }
  if$
  *
}

FUNCTION {format.name}
{ s nameptr "{vv~}{ll}{,~ff}{, jj}" format.name$
  get.str.lang
  duplicate$
  duplicate$ lang.zh = swap$ lang.ja = or
    { pop$
      s nameptr "{vv~}{ll}" format.name$
      s nameptr "{ff}{, jj}" format.name$
      duplicate$ get.str.lang
      duplicate$ lang.zh = swap$ lang.ja = or
        { * }
        { pop$ }
      if$
    }
    { lang.ru =
        { s nameptr "{vv~}{ll}" format.name$
          s nameptr "{ff}{, jj}" format.name$
          "." "" find.replace
          duplicate$ empty$
            'pop$
            { " " swap$ * * }
          if$
        }
        { format.latin.name }
      if$
    }
  if$
}

FUNCTION {inter.word.space}
{ is.lang.cjk
    { "" }
    { " " }
  if$
}

FUNCTION {format.names}
{ 's :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }
    { format.name 't :=
      nameptr #1 >
        { numnames control.max.bib.names >
          nameptr control.min.bib.names > and
            { "others" 't :=
              #1 'namesleft :=
            }
            'skip$
          if$
          namesleft #1 >
            { add.comma t * }
            { t "l" change.case$ "others" =
                { nameptr #2 >
                    { add.comma }
                    { inter.word.space * }
                  if$
                  bbl.et.al *
                }
                { control.bib.final.and
                    { is.lang.cjk not nameptr #2 > and
                        { add.comma }
                        { inter.word.space * }
                      if$
                      bbl.and * inter.word.space *
                    }
                    { add.comma }
                  if$
                  t *
                }
              if$
            }
          if$
        }
        't
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
  format.punctuations
}

FUNCTION {warn.empty.field}
{ control.warn.empty.field
    { "empty " swap$ * " in " * cite$ * warning$ }
    'pop$
  if$
}

FUNCTION {format.anonymous}
{
%<*authoryear>
  bbl.anonymous
%</authoryear>
%<*numeric>
  ""
%</numeric>
}

FUNCTION {format.authors}
{ author empty$
    { "author" warn.empty.field
      format.anonymous
    }
    { author format.names }
  if$
}

FUNCTION {format.editors}
{ editor empty$
    { "editor" warn.empty.field
      format.anonymous
    }
    { editor format.names }
  if$
}

FUNCTION {format.authors.editors}
{ author empty$
    { editor empty$
        { "author and editor" warn.empty.field
          format.anonymous
        }
        { editor format.names }
      if$
    }
    { author format.names }
  if$
}

FUNCTION {format.translators}
{ translator empty$
    { "" }
    { translator format.names
      add.comma bbl.translator *
    }
  if$
}

FUNCTION {format.lab.name}
{ s nameptr "{vv~}{ll}{,~ff}{, jj}" format.name$
  get.str.lang
  duplicate$ lang.zh =
  swap$ lang.ja = or
    { s nameptr "{vv~}{ll}" format.name$
      s nameptr "{ff}{, jj}" format.name$
      duplicate$ get.str.lang
      duplicate$ lang.zh = swap$ lang.ja = or
        { * }
        'pop$
      if$
    }
    { s nameptr "{vv~}{ll}" format.name$ }
  if$
}

FUNCTION {format.full.names}
{ 's :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }
    { format.lab.name 't :=
      nameptr #1 >
        { namesleft #1 >
            { add.comma t * }
            { t "l" change.case$ "others" =
                { nameptr #2 >
                    { add.comma }
                    { inter.word.space * }
                  if$
                  cite.et.al *
                }
                { control.cite.final.and
                    { is.lang.cjk not nameptr #2 > and
                        { add.comma }
                        { inter.word.space * }
                      if$
                      cite.and * inter.word.space *
                    }
                    { add.comma }
                  if$
                  t *
                }
              if$
            }
          if$
        }
        't
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
  format.punctuations
}

FUNCTION {author.editor.full}
{ author empty$
    { editor empty$
        { "" }
        { editor format.full.names }
      if$
    }
    { author format.full.names }
  if$
}

FUNCTION {author.full}
{ author empty$
    { "" }
    { author format.full.names }
  if$
}

FUNCTION {editor.full}
{ editor empty$
    { "" }
    { editor format.full.names }
  if$
}

FUNCTION {holder.author.full}
{ holder empty$
    { author empty$
        { "" }
        { author format.full.names }
      if$
    }
    { holder format.full.names }
  if$
}

FUNCTION {make.full.names}
{ type$ "book" =
  type$ "inbook" = booktitle empty$ not and
  or
    'author.editor.full
    { type$ "collection" =
      type$ "proceedings" =
      or
        'editor.full
        { type$ "patent" =
            'holder.author.full
            'author.full
          if$
        }
      if$
    }
  if$
}

FUNCTION {output.bibitem}
{ newline$
  "\bibitem[" write$
  label ")" *
  make.full.names duplicate$ short.list =
    { pop$ }
    { duplicate$ "]" contains
        { "{" swap$ * "}" * }
        'skip$
      if$
      *
    }
  if$
  "]{" * write$
  cite$ write$
  "}" write$
  newline$
  ""
  before.all 'output.state :=
}

%    \end{macrocode}
%
% \subsubsection{Format title}
%
% The |format.title| function is used for non-book-like titles.
% For most styles we convert to lowercase (except for the very first letter,
% and except for the first one after a colon (followed by whitespace)),
% and hope the user has brace-surrounded words that need to stay capitalized;
% for some styles, however, we leave it as it is in the database.
%    \begin{macrocode}
FUNCTION {format.title.subtitle.titleaddon}
{ title empty$
    { "" }
    { title
      subtitle empty$
        'skip$
        { add.colon.by.language subtitle * }
      if$
      titleaddon empty$
        'skip$
        { add.colon.by.language titleaddon * }
      if$
    }
  if$
}

% 只有英文转为 sentence case，
% 其他语言通常不使用 title case，所以应按照 sentence case 保存，
% 不需要转为 sentence case。
FUNCTION {change.sentence.case}
{ entry.lang lang.en =
    'smart.sentence.case
    'skip$
  if$
}

FUNCTION {add.link}
{ url empty$ not
    { "\href{" url * "}{" * swap$ * "}" * }
    { doi empty$ not
        { "\href{https://doi.org/" doi * "}{" * swap$ * "}" * }
        'skip$
      if$
    }
  if$
}

FUNCTION {format.tr.number}
{ ""
  number empty$
    'skip$
    { type$ "patent" = control.patent.country and
        { country empty$ not
            { country * }
            { nationality empty$ not
                { nationality * }
                { address empty$ not
                    { address * }
                    { location empty$ not
                        { location * }
                        { "" }
                        { entry.lang lang.zh =
                            { "中国" * }
                            'skip$
                          if$
                        }
                      if$
                    }
                  if$
                }
              if$
            }
          if$
        }
        'skip$
      if$
      duplicate$ empty$
        'skip$
        'add.comma
      if$
      number
      duplicate$ get.str.lang lang.zh =
        'format.punctuations
        'skip$
      if$
      *
    }
  if$
}

FUNCTION {entry.has.url.or.doi}
{ control.url require.url or entry.url empty$ not and
  doi empty$ not cstr empty$ not or
  control.doi control.url or require.url or and
  or
}

FUNCTION {format.entry.type.id}
{ control.entry.type.id
    { entry.type.id
      control.entry.medium.id
        { entrymediumid empty$
            { medium empty$
                { entry.has.url.or.doi
                  type$ "online" = or
                  type$ "webpage" = or
                    { "/OL" * }
                    'skip$
                  if$
                }
                { "The field 'medium' for entry medium ID is deprecated. Use 'entrymediumid' instead." warning$
                  "/" * medium *
                }
              if$
            }
            { "/" * entrymediumid * }
          if$
        }
        'skip$
      if$
      make.brackets
      control.space.before.type.id
        { " " }
        { "\allowbreak" }
      if$
      swap$ *
    }
    { "" }
  if$
}

FUNCTION {format.title}
{ title empty$
    { "" }
    { format.title.subtitle.titleaddon
      control.sentence.case.title
        'change.sentence.case
        'skip$
      if$
      format.punctuations
      control.link.title
        'add.link
        'skip$
      if$
      entry.numbered number empty$ not and
        { add.colon
          format.tr.number *
        }
        'skip$
      if$
      format.entry.type.id *
    }
  if$
}

%    \end{macrocode}
%
% For several functions we'll need to connect two strings with a
% tie (|~|) if the second one isn't very long (fewer than 3 characters).
% The tie.or.space.connect function does that.  It concatenates the two
% strings on top of the stack, along with either a tie or space between
% them, and puts this concatenation back onto the stack:
%
% \begin{pseudocode}
% tie.or.space.connect(str1,str2) ==
%    BEGIN
%       if text.length$(str2) < 3
%         then return the concatenation of str1, "~", and str2
%         else return the concatenation of str1, " ", and str2
%    END
% \end{pseudocode}
%    \begin{macrocode}
FUNCTION {tie.or.space.connect}
{ duplicate$ text.length$ #3 <
    { "~" }
    { " " }
  if$
  swap$ * *
}

%    \end{macrocode}
%
% The either.or.check function complains if both fields or an either-or pair
% are nonempty.
%
% \begin{pseudocode}
% either.or.check(t,s) ==
%  BEGIN
%       if empty$(s) then
%           warning$(can't use both " * t * " fields in " * cite$)
%       fi
%  END
% \end{pseudocode}
%    \begin{macrocode}
FUNCTION {either.or.check}
{ empty$
    'pop$
    { "can't use both " swap$ * " fields in " * cite$ * warning$ }
  if$
}

%    \end{macrocode}
%
% The format.bvolume function is for formatting the volume and perhaps
% series name of a multivolume work.  If both a volume and a series field
% are there, we assume the series field is the title of the whole multivolume
% work (the title field should be the title of the thing being referred to),
% and we add an "of <series>".  This function is called in mid-sentence.
%
% The format.number.series function is for formatting the series name
% and perhaps number of a work in a series.  This function is similar to
% format.bvolume, although for this one the series must exist (and the
% volume must not exist).  If the number field is empty we output either
% the series field unchanged if it exists or else the null string.
% If both the number and series fields are there we assume the series field
% gives the name of the whole series (the title field should be the title
% of the work being one referred to), and we add an "in <series>".
% We capitalize Number when this function is used at the beginning of a block.
%    \begin{macrocode}
FUNCTION {is.digit}
{ duplicate$ empty$
    { pop$ #0 }
    { chr.to.int$
      duplicate$ "0" chr.to.int$ <
      { pop$ #0 }
      { "9" chr.to.int$ >
          { #0 }
          { #1 }
        if$
      }
    if$
    }
  if$
}

FUNCTION {is.number}
{ #1
    { #0 > }
    { duplicate$ #1 #1 substring$ chr.to.int$
      duplicate$ #47 >
      swap$      #58 <
      and
        { #2 global.max$ substring$
          duplicate$ empty$
            { #0 }
            { #1 }
          if$
        }
        { #0 }
      if$
    }
  while$
  empty$
}

FUNCTION {format.bvolume}
{ volume empty$
    { "" }
    { volume is.number
        { entry.lang lang.zh =
            { "第" volume tie.or.space.connect " 卷" * }
            { entry.lang lang.ko =
                { "제" volume tie.or.space.connect " 권" * }
                { "v." volume * }
              if$
            }
          if$
        }
        { volume }
      if$
    }
  if$
}

FUNCTION {format.maintitle}
{ maintitle empty$
    { "" }
    { mainsubtitle empty$
        'skip$
        { add.colon.by.language mainsubtitle * }
      if$
      maintitleaddon empty$
        'skip$
        { add.colon.by.language maintitleaddon * }
      if$
    }
  if$
}

FUNCTION {format.maintitle.volume}
{ maintitle empty$
    { "" }
    { maintitle
      volume empty$
        'skip$
        { add.colon.by.language
          format.bvolume *
        }
      if$
    }
  if$
}

FUNCTION {format.title.volume}
{ title empty$
    { "" }
    { format.title.subtitle.titleaddon
      volume empty$
        'skip$
        { add.colon.by.language
          format.bvolume *
        }
      if$
    }
  if$
}

FUNCTION {format.series.volume}
{ series empty$
    { "" }
    { series
      volume empty$
        'skip$
        { add.colon.by.language
          format.bvolume *
        }
      if$
    }
  if$
}

FUNCTION {format.btitle}
{ maintitle empty$
    { series empty$
        { format.title.volume }
        { volume empty$
            { format.title.volume }
            { format.series.volume
              entry.numbered
                'skip$
                { "volume and number" number either.or.check }
              if$
              bbl.wide.space *
              format.title.subtitle.titleaddon
              *
            }
          if$
        }
      if$
    }
    { volume empty$
        { format.maintitle
          add.colon.by.language
          format.title.subtitle.titleaddon *
        }
        { format.maintitle.volume
          bbl.wide.space *
          format.title.subtitle.titleaddon
          *
        }
      if$
    }
  if$
  duplicate$ empty$
    'skip$
    { control.sentence.case.title
        'change.sentence.case
        'skip$
      if$
      format.punctuations
      emphasize.book.title
      control.link.title
        'add.link
        'skip$
      if$
      entry.numbered number empty$ not and
        { add.colon format.tr.number * }
        'skip$
      if$
      type$ "map" =
      scale empty$ not and
        { add.period
          duplicate$ #-1 #1 substring$ "." =
            { " " * }
            'skip$
          if$
          scale *
        }
        'skip$
      if$
      format.entry.type.id *
    }
  if$
}

%    \end{macrocode}
%
%    \begin{macrocode}
FUNCTION {format.booktitle}
{ booktitle empty$
    { "" }
    { booktitle
      control.sentence.case.booktitle
        'change.sentence.case
        'skip$
      if$
      booksubtitle empty$
        'skip$
        { add.colon
          booksubtitle
          control.sentence.case.booktitle
            { "l" change.case$ }
            'skip$
          if$
          *
        }
      if$
      emphasize.book.title
      booktitleaddon empty$
        'skip$
        { add.colon
          booktitleaddon
          control.sentence.case.booktitle
            { "l" change.case$ }
            'skip$
          if$
          *
        }
      if$
      format.punctuations
    }
  if$
}

FUNCTION {format.series.volume.booktitle}
{ series empty$
    { maintitle empty$
        { format.booktitle
          volume empty$
            'skip$
            { add.colon
              format.bvolume *
            }
          if$
        }
        { format.maintitle.volume
          bbl.wide.space *
          format.booktitle *
        }
      if$
    }
    { volume empty$
        { format.booktitle }
        { format.series.volume
          bbl.wide.space *
          format.booktitle *
        }
      if$
    }
  if$
}

FUNCTION {format.in.ed.booktitle}
{ booktitle empty$
    { "" }
    { control.in
        { bbl.in add.colon }
        { "" }
      if$
      editor empty$
        'skip$
        { format.editors * output new.block "" }
      if$
      format.series.volume.booktitle *
    }
  if$
}

FUNCTION {remove.dots}
{ 's :=
  ""
    { s empty$ not }
    { s #1 #1 substring$
      duplicate$ "." =
        'pop$
        { * }
      if$
      s #2 global.max$ substring$ 's :=
    }
  while$
}

FUNCTION {format.journal.title}
{ journal empty$
    { journaltitle empty$
        { "" }
        { journaltitle }
      if$
    }
    { journal }
  if$
  journalsubtitle empty$
    'skip$
    { duplicate$ empty$
        'skip$
        'add.colon.by.language
      if$
      journalsubtitle *
    }
  if$
  journaltitleaddon empty$
    'skip$
    { duplicate$ empty$
        'skip$
        'add.colon.by.language
      if$
      journaltitleaddon *
    }
  if$
  control.sentence.case.journal
    'change.sentence.case
    'skip$
  if$
}

FUNCTION {format.short.journal}
{ shortjournal empty$
    { format.journal.title
      duplicate$ field.or.null " " contains  % The journal title is not a single word
      is.lang.cjk not and
        { "shortjournal" warn.empty.field }
        'skip$
      if$
    }
    { shortjournal
      control.journal.dots
        'skip$
        'remove.dots
      if$
    }
  if$
}

FUNCTION {get.journal.title}
{ control.short.journal
    'format.short.journal
    { format.journal.title
      duplicate$ empty$
        { shortjournal empty$
            { "journal" warn.empty.field }
            { pop$ format.short.journal }
          if$
        }
        { shortjournal empty$ control.journal.dots not and
            'remove.dots
            { duplicate$ shortjournal = control.journal.dots not and
                'remove.dots
                'skip$
              if$
            }
          if$
        }
      if$
    }
  if$
}

FUNCTION {check.arxiv.preprint}
{ "l" change.case$
  duplicate$ #1 #5 substring$ "arxiv" =
    { 'x :=
      "arxiv:" 'y :=
      y text.length$ 'len :=
      x text.length$ len - #1 + 'charptr :=
        { charptr #0 >
          x charptr len substring$ y = not
          and
        }
        { charptr #1 - 'charptr := }
      while$
      charptr #0 >
        { x charptr #6 + global.max$ substring$ 'x :=
          x string.length #1 + 'len :=
          #1 'charptr :=
            { charptr len <
              x charptr #1 substring$ " " = not and
              x charptr #1 substring$ "[" = not and
            }
            { charptr #1 + 'charptr := }
          while$
          x #1 charptr substring$
          duplicate$ empty$
            'pop$
            { "\url{https://arxiv.org/abs/" swap$ * "}" * 'entry.url := }
          if$
        }
        'skip$
      if$
      #1
    }
    { pop$ #0 }
  if$
}

FUNCTION {format.journal}
{ get.journal.title
  duplicate$ empty$
    'skip$
    { format.punctuations
      control.emph.journal is.lang.cjk not and
        'emphasize
        'skip$
      if$
    }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format entry type mark}
%
%    \begin{macrocode}
FUNCTION {set.entry.type.id}
{ entry.type.id empty$
    { entrytypeid empty$
        { entrysubtype empty$
            { mark empty$
                { 'entry.type.id := }
                { pop$
                  "The field 'mark' for entry type ID is deprecated. Use 'entrytypeid' instead." warning$
                  mark 'entry.type.id :=
                }
              if$
            }
            { entrysubtype "newspaper" =
                { pop$ "N" 'entry.type.id := }
                { entrysubtype "inproceedings" =
                    { pop$ "C" 'entry.type.id := }
                    { entrysubtype "report" =
                        { pop$ "R" 'entry.type.id := }
                        { entrysubtype "techreport" =
                            { pop$ "R" 'entry.type.id := }
                            { entrysubtype "standard" =
                                { pop$ "S" 'entry.type.id := }
                                { entrysubtype "patent" =
                                    { pop$ "P" 'entry.type.id := }
                                    { entrysubtype "dataset" =
                                        { pop$ "DS" 'entry.type.id := }
                                        { 'entry.type.id := }
                                      if$
                                    }
                                  if$
                                }
                              if$
                            }
                          if$
                        }
                      if$
                    }
                  if$
                }
              if$
            }
          if$
        }
        { pop$ entrytypeid 'entry.type.id := }
      if$
    }
    'pop$
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format edition}
%
% The format.edition function appends " edition" to the edition, if present.
% We lowercase the edition (it should be something like "Third"), because
% this doesn't start a sentence.
%    \begin{macrocode}
FUNCTION {num.to.ordinal}
{ is.lang.cjk
    'skip$
    { duplicate$ #-2 #1 substring$ "1" =
        { "th" * }
        { duplicate$ #-1 #1 substring$ "1" =
            { "st" * }
            { duplicate$ #-1 #1 substring$ "2" =
                { "nd" * }
                { duplicate$ #-1 #1 substring$ "3" =
                    { "rd" * }
                    { "th" * }
                  if$
                }
              if$
            }
          if$
        }
      if$
    }
  if$
}


% If the string on the top of the stack begins with a number,
% (e.g., 11th) then replace the string with the leading number
% it contains. Otherwise retain the string as-is. s holds the
% extracted number, t holds the part of the string that remains
% to be scanned.
FUNCTION {extract.num}
{ duplicate$ 't :=
  "" 's :=
  { t empty$ not }
  { t #1 #1 substring$
    t #2 global.max$ substring$ 't :=
    duplicate$ is.digit
      { s swap$ * 's := }
      { pop$ "" 't := }
    if$
  }
  while$
  s empty$
    'skip$
    { pop$ s }
  if$
}

% Converts the word number string on the top of the stack to
% Arabic string form. Will be successful up to "tenth".
FUNCTION {word.to.num}
{ duplicate$ "l" change.case$ 's :=
  s "first" =
    { pop$ "1" }
    'skip$
  if$
  s "second" =
    { pop$ "2" }
    'skip$
  if$
  s "third" =
    { pop$ "3" }
    'skip$
  if$
  s "fourth" =
    { pop$ "4" }
    'skip$
  if$
  s "fifth" =
    { pop$ "5" }
    'skip$
  if$
  s "sixth" =
    { pop$ "6" }
    'skip$
  if$
  s "seventh" =
    { pop$ "7" }
    'skip$
  if$
  s "eighth" =
    { pop$ "8" }
    'skip$
  if$
  s "ninth" =
    { pop$ "9" }
    'skip$
  if$
  s "tenth" =
    { pop$ "10" }
    'skip$
  if$
  s "revised edition" =
    { pop$ "Rev. ed." }
    'skip$
  if$
  s "revised ed." =
    { pop$ "Rev. ed." }
    'skip$
  if$
  s "revised" =
    { pop$ "Rev. ed." }
    'skip$
  if$
  s "rev." =
    { pop$ "Rev. ed." }
    'skip$
  if$
  s "修订版" =
    { pop$ "修订版" }
    'skip$
  if$
  s "修订" =
    { pop$ "修订版" }
    'skip$
  if$
}


FUNCTION {format.edition}
{ edition empty$
    { "" }
    { edition
      duplicate$ #1 #1 substring$ is.digit
        'extract.num
        'word.to.num
      if$
      duplicate$ "1" =
        { pop$ "" }
        { duplicate$ #1 #1 substring$ is.digit
            { num.to.ordinal
              " " * bbl.edition *
            }
            'skip$
          if$
        }
      if$
    }
  if$
}

FUNCTION {format.version}
{ version empty$
    { "" }
    { version #1 #1 substring$
      duplicate$ "V" =
        { pop$ version }
        { "v" =
            { "V" version #2 global.max$ substring$ * }
            { "V" version * }
          if$
        }
      if$
    }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format publishing items}
%
% 出版地址和出版社会有 “[S.l.: s.n.]” 的情况，所以必须一起处理。
%    \begin{macrocode}
FUNCTION {format.publisher}
{ publisher empty$
    { institution empty$
        { school empty$
            { "" }
            { school }
          if$
        }
        { institution }
      if$
    }
    { publisher }
  if$
  format.punctuations
}

FUNCTION {format.address.publisher}
{ address empty$
    { location empty$
        { "" }
        { location }
      if$
    }
    { address }
  if$
  format.punctuations
  duplicate$ empty$ not
    { format.publisher empty$ not
        { add.colon format.publisher * }
        { entry.has.url.or.doi not control.unknown.publisher and
            { add.colon bbl.sine.nomine make.brackets * }
            'skip$
          if$
        }
      if$
    }
    { pop$
      entry.has.url.or.doi not control.unknown.publisher and
        { format.publisher empty$ not
            { bbl.sine.loco make.brackets add.colon format.publisher * }
            { bbl.sine.loco.sine.nomine make.brackets }
          if$
        }
        { format.publisher empty$ not
            { format.publisher }
            { "" }
          if$
        }
      if$
    }
  if$
}

FUNCTION {format.eventtitle}
{ eventtitle empty$
    { "" }
    { eventtitle format.punctuations }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format date}
%
% The format.date function is for the month and year, but we give a warning if
% there's an empty year but the month is there, and we return the empty string
% if they're both empty.
%
% 期刊需要著录起止范围，其中年份使用“/”分隔，卷和期使用“--”分隔。
% 版本 v2.0.2 前的年份也使用“--”分隔，仅提供兼容性，不再推荐。
%    \begin{macrocode}
FUNCTION {normalize.year.dash}
{
%<*!(2005|2015)>
  "bylanguage" control.bib.punct =
  is.lang.cjk and
  "GB" control.bib.punct = or
    { "---" "—" find.replace
      "--" "—" find.replace
      "–" "—" find.replace
      "-" "—" find.replace
    }
    { "---" "-" find.replace
      "--" "-" find.replace
      "–" "-" find.replace
      "—" "-" find.replace
    }
  if$
%</!(2005|2015)>
%<*2005|2015>
  "---" "-" find.replace
  "--" "-" find.replace
  "–" "-" find.replace
  "—" "-" find.replace
%</2005|2015>
}

FUNCTION {format.date}
{ year empty$
    { date empty$
        { entry.has.url.or.doi
            { "" }
            { "year" warn.empty.field
              urldate empty$
                { "" }
                { urldate "-" extract.before extra.label * make.brackets }
              if$
            }
          if$
        }
        { date "/" extract.before "-" extract.before
          date "/" contains
            { "-" normalize.year.dash *
              date "/" extract.after "-" extract.before *
            }
            'skip$
          if$
          extra.label *
        }
      if$
    }
    { year normalize.year.dash
      extra.label *
    }
  if$
%<*authoryear>
  control.year.before.title
    { pop$ "" }
    'skip$
  if$
%</authoryear>
}

%    \end{macrocode}
%
% 专利和报纸都是使用完整日期
%    \begin{macrocode}
FUNCTION {format.full.date}
{ date empty$
    { year }
    { control.year.before.title
        { date }
        { date #1 #4 substring$
          extra.label *
          date #5 global.max$ substring$ *
        }
      if$
    }
  if$
}

%    \end{macrocode}
%
% 正文中的引用标注的出版年，若为空则显示“无日期”或“n.d.”，需要带消歧的小写字后缀。
%    \begin{macrocode}
FUNCTION {format.lab.date}
{ year empty$
    { date empty$
        { urldate empty$
            { bbl.no.date
              extra.label empty$
                'skip$
                { "-" * extra.label *}
              if$
            }
            { urldate "-" extract.before extra.label * make.brackets }
          if$
        }
        { date "/" extract.before "-" extract.before
          date "/" contains
            { "-" normalize.year.dash *
              date "/" extract.after "-" extract.before *
            }
            'skip$
          if$
          extra.label *
        }
      if$
    }
    { year normalize.year.dash extra.label * }
  if$
}

%    \end{macrocode}
%
% 著者-出版年制的出版年应置于题名前、著者姓名后。
% 需要根据以下选项控制格式：
%   - \opt{control.year.before.title} 出版年是否置于题名前；
%   - \opt{control.name.year.delim} 著者姓名与出版年之间的分隔符；
%    \begin{macrocode}
%<*authoryear>
FUNCTION {format.date.before.title}
{ control.year.before.title
    { "period" control.name.year.delim =
        'new.sentence
        'skip$
      if$
      format.lab.date
    }
    { "" }
  if$
}

%</authoryear>
%    \end{macrocode}
%
% 7.6 创建/发布或修改日期、引用日期
%    \begin{macrocode}
FUNCTION {format.creation.modification.date}
{ date empty$
    {
%<*2025>
      year empty$
        { "" }
        { year}
      if$
%</2025>
%<*!2025>
      ""
%</!2025>
    }
    { date }
  if$
  duplicate$ empty$
    'skip$
    { make.parentheses
      "\allowbreak " swap$ *
    }
  if$
}

%    \end{macrocode}
%
% 2025 版的引用日期对于以下文献类型是必备：网站、网页、数据集、预印本类型，
% 其余类型不需要著录。
% 2005、2015 版只有电子资源著录 URL/DOI 时才著录引用日期。
%    \begin{macrocode}
FUNCTION {format.urldate}
{
%<*2025>
  control.urldate urldate empty$ not and
%</2025>
%<*!2025>
  entry.has.url.or.doi
  control.urldate urldate empty$ not and and
%</!2025>
    { "\allowbreak" urldate make.brackets * }
    { "" }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format pages}
%
% By default, BibTeX sets the global integer variable |global.max$| to the BibTeX
% constant |glob_str_size|, the maximum length of a global string variable.
% Analogously, BibTeX sets the global integer variable |entry.max$| to
% |ent_str_size|, the maximum length of an entry string variable.
% The style designer may change these if necessary (but this is unlikely)
%
% The n.dashify function makes each single |`-'| in a string a double |`--'|
% if it's not already
%
% \begin{pseudocode}
% pseudoVAR: pageresult: STRING         (it's what's accumulated on the stack)
%
% n.dashify(s) ==
%  BEGIN
%       t := s
%       pageresult := ""
%       while (not empty$(t))
%         do
%           if (first character of t = "-")
%             then
%               if (next character isn't)
%                 then
%                   pageresult := pageresult * "--"
%                   t := t with the "-" removed
%                 else
%                   while (first character of t = "-")
%                     do
%                       pageresult := pageresult * "-"
%                       t := t with the "-" removed
%                     od
%               fi
%             else
%               pageresult := pageresult * the first character
%               t := t with the first character removed
%           fi
%         od
%       return pageresult
%  END
% \end{pseudocode}
%
% 国标里页码范围的连接号使用 hyphen，需要将 dash 转为 hyphen。
%    \begin{macrocode}
% str -> str
% variable used: s, t, b
FUNCTION {normalize.page.range}
{ "" swap$
  { duplicate$ empty$ not }
    { #1 skip.inter.token.chars.by 't :=
      empty$
        { "" }
        'control.page.range.delim
      if$
      * t
      #1 tokenize.by 't :=
      * t
    }
  while$
  pop$
}

%    \end{macrocode}
%
% This function doesn't begin a sentence so "pages" isn't capitalized.
% Other functions that use this should keep that in mind.
%    \begin{macrocode}
FUNCTION {format.pages}
{ pages empty$
    { "" }
    { pages
      control.page.ranges
        'normalize.page.range
        { #1 tokenize.by pop$ }
      if$
    }
  if$
}

FUNCTION {format.dimensions}
{ dimensions empty$
    { "" }
    { dimensions
      "x" "$\times$" find.replace
      "×" "$\times$" find.replace
    }
  if$
}

%    \end{macrocode}
%
% The |format.vol.num.pages| function is for the volume, number, and page range
% of a journal article.  We use the format:  vol(number):pages, with some
% variations for empty fields.  This doesn't begin a sentence.
%
% 报纸在卷号缺失时，期号与前面的日期直接相连，所以必须拆开输出。
%    \begin{macrocode}
FUNCTION {format.journal.volume}
{ volume empty$
    { "" }
    { control.bold.volume
        { "\textbf{" volume * "}" * }
        { volume }
      if$
    }
  if$
}

FUNCTION {format.journal.number}
{ number empty$
    { "" }
    { "\allowbreak " number make.parentheses * }
  if$
}

FUNCTION {format.journal.pages}
{ pages empty$
    { eid empty$
        { "" }
        { eid }
      if$
    }
    { format.pages }
  if$
}

FUNCTION {format.newspaper.pages}
{ pages empty$
    { "" }
    { "\allowbreak " format.pages make.parentheses * }
  if$
}

%    \end{macrocode}
%
% \subsubsection{Format url and doi}
%
% 传统的 \BibTeX{} 习惯使用 howpublished 著录 url，这里提供支持。
%    \begin{macrocode}
FUNCTION {check.electronic}
{ entry.url empty$
    { url empty$
        { howpublished field.or.null #1 #5 substring$ "\url{" =
            { howpublished 'entry.url := }
            { note field.or.null #1 #5 substring$ "\url{" =
                { note "}" extract.before "}" * 'entry.url := }
                'skip$
              if$
            }
          if$
        }
        { "\url{" url * "}" * 'entry.url := }
      if$
    }
    'skip$
  if$
}

FUNCTION {format.url}
{ control.url require.url or
    { entry.url empty$
        { control.doi not require.url or
            { cstr empty$
                { doi empty$
                    'skip$
                    { "\url{https://doi.org/" doi * "}" * 'entry.url := }
                  if$
                }
                { "\url{https://cstr.cn/" cstr * "}" * 'entry.url := }
              if$
            }
            'skip$
          if$
        }
        'skip$
      if$
      entry.url empty$
        { "" }
        { new.block
          entry.url
        }
      if$
    }
    { "" }
  if$
}

%    \end{macrocode}
%
% 需要检测 DOI 是否已经包含在 URL 中。
%    \begin{macrocode}
FUNCTION {either.or}
{ swap$
  duplicate$ empty$
    { pop$
      duplicate$ empty$
        { pop$ "" }
        'skip$
      if$
    }
    { swap$ pop$ }
  if$
}

FUNCTION {format.doi}
{ control.doi
    { control.cstr not cstr empty$ or
        { doi empty$
            { "" }
            { control.url require.url or
              entry.url doi contains and
                { "" }
                { new.block "DOI:\doi{" doi * "}" * }
              if$
            }
          if$
        }
        { control.url require.url or
          entry.url cstr contains and
            { "" }
            { new.block "CSTR:\cstr{" cstr * "}" * }
          if$
        }
      if$
    }
    { "" }
  if$
}

FUNCTION {format.eprint}
{ archiveprefix eprinttype either.or
  duplicate$ empty$
    { pop$
      journal journaltitle either.or shortjournal either.or
      "l" change.case$ #1 #5 substring$ "arxiv" =
        { "arXiv" }
        { "" }
      if$
    }
    { duplicate$ "arxiv" =
        { pop$ "arXiv" }
        { duplicate$ "pubmed" =
            { pop$ "PubMed" }
            'skip$
        if$
        }
      if$
    }
  if$
  entry.url empty$
    { duplicate$ "l" change.case$ "arxiv" = entry.eprint empty$ not and
        { "\url{https://arxiv.org/abs/" entry.eprint * "}" * 'entry.url := }
        'skip$
      if$
    }
    'skip$
  if$
  control.eprint eprint empty$ not and
    { duplicate$ empty$
        'skip$
        { ":" * }
      if$
      eprint *
    }
    'skip$
  if$
}

FUNCTION {format.note}
{ note empty$ not control.note and
    { note }
    { "" }
  if$
}

%    \end{macrocode}
%
% The function empty.misc.check complains if all six fields are empty, and
% if there's been no sorting or alphabetic-label complaint.
%    \begin{macrocode}
FUNCTION {empty.misc.check}
{ author empty$ title empty$
  year empty$
  and and
  key empty$ not and
    { "all relevant fields are empty in " cite$ * warning$ }
    'skip$
  if$
}

FUNCTION {link.open}
{ control.link.journal
    { url empty$
        { doi empty$
            { "" }
            { "\href{https://doi.org/" doi * "}{" * }
          if$
        }
        { "\href{" url * "}{" * }
      if$
      duplicate$ empty$
        { pop$ }
        { "" set.punct
          output
          before.all 'output.state :=
        }
      if$
    }
    'skip$
  if$
}

FUNCTION {link.close}
{ control.link.journal
    { url empty$ doi empty$ and
        'skip$
        { "" set.punct
          "}" output
          before.all 'output.state :=
        }
      if$
    }
    'skip$
  if$
}

%    \end{macrocode}
%
% \subsection{Functions for all entry types}
%
% Now we define the type functions for all entry types that may appear
% in the .BIB file---e.g., functions like `article' and `book'.  These
% are the routines that actually generate the .BBL-file output for
% the entry.  These must all precede the READ command.  In addition, the
% style designer should have a function `default.type' for unknown types.
% Note: The fields (within each list) are listed in order of appearance,
% except as described for an `inbook' or a `proceedings'.
%
% The article function is for an article in a journal.  An article may
% CROSSREF another article.
%
%       Required fields: author, title, journal, year
%
%       Optional fields: volume, number, pages, month, note
%
% The other entry functions are all quite similar, so no "comment version"
% will be given for them.
%    \begin{macrocode}
FUNCTION {article.journal}
{ "J" set.entry.type.id
  volume empty$ number empty$ and entry.url empty$ not and
  date field.or.null text.length$ #9 > and
  entry.type.id "J" = and
    { #1 'require.url := }
    'skip$
  if$
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  control.article.title
    { new.block
      format.title "title" output.check
    }
    'skip$
  if$
  new.block
  format.translators output
  new.block
  link.open
  format.journal "journal" output.check
  entry.type.id "N" =
    { format.full.date "date" output.check
      "" set.punct
      format.newspaper.pages output
    }
    {
%    \end{macrocode}
%
% 卷次和期号均为空时视为在线出版，使用完整日期。
%    \begin{macrocode}
%<*2025>
      volume empty$ number empty$ and date field.or.null text.length$ #9 > and
        { format.full.date "date" output.check }
        { format.date "year" output.check  }
      if$
%</2025>
%<*!2025>
      format.date output
%</!2025>
      format.journal.volume output
      "" set.punct
      format.journal.number output
      bbl.pages.colon set.punct
      format.journal.pages output
    }
  if$
  link.close
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

FUNCTION {archive}
{ "A" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  % new.block
  % format.translators output
  new.block
  format.address.publisher output
%<*2025>
  format.full.date "year" output.check
  bbl.pages.colon set.punct
  format.pages output
%</2025>
%<*!2025>
  format.date output
  bbl.pages.colon set.punct
  format.pages output
  "" set.punct
  format.creation.modification.date output
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
%
% The book function is for a whole book.  A book may CROSSREF another book.
%
%       Required fields: author or editor, title, publisher, year
%
%       Optional fields: volume or number, series, address, edition, month,
%                       note
%    \begin{macrocode}
FUNCTION {book}
{ "M" set.entry.type.id
  output.bibitem
  format.authors.editors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  new.block
  format.translators output
  new.sentence
  format.edition output
  new.block
  format.address.publisher output
  format.date output
  bbl.pages.colon set.punct
  format.pages output
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

FUNCTION {letter} { archive }

FUNCTION {dataset}
{ "DS" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  new.block
  format.version output
  new.block
  publisher "publisher" output.check
  "" set.punct
  format.creation.modification.date output
  "" set.punct
%<*2025>
  format.urldate "urldate" output.check
%</2025>
%<*!2025>
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

FUNCTION {database}
{ "DB" set.entry.type.id
  dataset
}

%    \end{macrocode}
%
% An inbook is a piece of a book: either a chapter and/or a page range.
% It may CROSSREF a book.  If there's no volume field, the type field
% will come before number and series.
%
%       Required: author or editor, title, chapter and/or pages, publisher,year
%
%       Optional: volume or number, series, type, address, edition, month, note
%
% 原生 BibTeX 的数据模型中 \texttt{@inbook} 不含 \texttt{booktitle} ，
% 按照“专著”处理。而 biblatex 的 \texttt{@inbook} 跟 \texttt{incollection} 一样。
% 按照“专著的析出文献”处理。
%    \begin{macrocode}
FUNCTION {inbook}
{ "M" set.entry.type.id
  booktitle empty$
    'book
    { output.bibitem
      format.authors output
%<*authoryear>
      format.date.before.title output
%</authoryear>
      control.article.title
        { new.block
          format.title "title" output.check
        }
        'skip$
      if$
      new.block
      format.translators output
      new.slash
      booktitle empty$
        { "" }
        { control.in
            { bbl.in add.colon }
            { "" }
          if$
          bookauthor empty$
            { editor empty$
                'skip$
                { format.editors * output new.block "" }
              if$
            }
            { bookauthor format.names * output new.block "" }
          if$
          format.series.volume.booktitle *
        }
      if$
      "booktitle" output.check
      new.block
      format.edition output
      new.block
      format.address.publisher output
      format.date output
      bbl.pages.colon set.punct
      format.pages output
%<*!2025>
      "" set.punct
      format.urldate output
%</!2025>
      format.url output
      format.doi output
      new.block
      format.note output
      fin.entry
    }
  if$
}

%    \end{macrocode}
%
% \subsubsection{图书中的析出文献}
%
% An incollection is like inbook, but where there is a separate title
% for the referenced thing (and perhaps an editor for the whole).
% An incollection may CROSSREF a book.
%
%       Required: author, title, booktitle, publisher, year
%
%       Optional: editor, volume or number, series, type, chapter, pages,
%                       address, edition, month, note
%    \begin{macrocode}
FUNCTION {incollection}
{ "M" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  control.article.title
    { new.block
      format.title "title" output.check
    }
    'skip$
  if$
  new.block
  format.translators output
  new.slash
  format.in.ed.booktitle "booktitle" output.check
  new.block
  format.edition output
  new.block
  format.address.publisher output
  format.date output
  bbl.pages.colon set.punct
  format.pages output
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% An inproceedings is an article in a conference proceedings, and it may
% CROSSREF a proceedings.  If there's no address field, the month (\& year)
% will appear just before note.
%
%       Required: author, title, booktitle, year
%
%       Optional: editor, volume or number, series, pages, address, month,
%                       organization, publisher, note
%
% 以图书中的析出文献的形式出版的会议论文，按照 @incollection 处理，使用“//”符号。
% 未正式出版的会议论文使用下的格式，不使用“//”符号。
%    \begin{macrocode}
FUNCTION {inproceedings}
{ "C" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  control.article.title
    { new.block
      format.title "title" output.check
    }
    'skip$
  if$
  new.slash
  booktitle empty$
    { format.eventtitle "eventtitle" output.check }
    { format.in.ed.booktitle "booktitle" output.check
      new.block
      format.edition output
      new.block
      format.address.publisher output
    }
  if$
  format.date output
  bbl.pages.colon set.punct
  format.pages output
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% The conference function is included for Scribe compatibility.
%    \begin{macrocode}
FUNCTION {conference} { inproceedings }

%    \end{macrocode}
%
% \subsubsection{专利文献}
%
% number 域也可以用来表示专利号。
%    \begin{macrocode}
FUNCTION {patent}
{ "P" set.entry.type.id
  output.bibitem
  holder empty$
    { format.authors "holder and author" output.check }
    { holder format.names output.nonnull }
  if$
%<*authoryear>
  format.date.before.title output
%</authoryear>
  control.article.title
    { new.block
      format.title "title" output.check
    }
    'skip$
  if$
  new.block
  format.full.date "year" output.check
%<*2025>
  bbl.pages.colon set.punct
  format.pages output
%</2025>
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% \subsubsection{连续出版物}
%
%    \begin{macrocode}
FUNCTION {format.periodical.volume}
{ volume number either.or
  duplicate$ empty$
    { pop$ "" }
    { format.punctuations
      normalize.year.dash
    }
  if$
}

FUNCTION {periodical}
{ "J" set.entry.type.id
  output.bibitem
  format.editors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.title "title" output.check
  new.block
  format.periodical.volume output
  new.block
  format.address.publisher output
  format.date output
%<*2005|2015>
  "" set.punct
  format.urldate output
%</2005|2015>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% A proceedings is a conference proceedings.
% If there is an organization but no editor field, the organization will
% appear as the first optional field (we try to make the first block nonempty);
% if there's no address field, the month (\& year) will appear just before note.
%
%       Required: title, year
%
%       Optional: editor, volume or number, series, address, month,
%                       organization, publisher, note
%    \begin{macrocode}
FUNCTION {proceedings}
{ "C" set.entry.type.id
  output.bibitem
  format.editors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  new.block
  format.translators output
  new.sentence
  format.edition output
  new.block
  format.address.publisher output
  format.date output
  bbl.pages.colon set.punct
  format.pages output
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% \subsubsection{预印本}
%
%    \begin{macrocode}
FUNCTION {preprint}
{ #1 'require.url :=
%<*2025>
  "PP" set.entry.type.id
%</2025>
%<*2015>
  "A" set.entry.type.id
%</2015>
%<*!(2015|2025)>
  "Z" set.entry.type.id
%</!(2015|2025)>
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  control.article.title
    { new.block
      format.title "title" output.check
    }
    'skip$
  if$
  new.block
  format.version output
  new.block
  format.eprint output
  "" set.punct
  format.creation.modification.date output
  "" set.punct
  format.urldate "urldate" output.check
  format.url "url" output.check
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% A phdthesis is like a mastersthesis.
%
%       Required: author, title, school, year
%
%       Optional: type, address, month, note
%    \begin{macrocode}
FUNCTION {thesis}
{ "D" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  new.block
  format.address.publisher output
  format.date output
  bbl.pages.colon set.punct
  format.pages output
%<*!2025>
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

FUNCTION {phdthesis} { thesis }

%    \end{macrocode}
%
% A mastersthesis is a Master's thesis.
%
%       Required: author, title, school, year
%
%       Optional: type, address, month, note
%    \begin{macrocode}
FUNCTION {mastersthesis} { thesis }

%    \end{macrocode}
%
% A techreport is a technical report.
%
%       Required: author, title, institution, year
%
%       Optional: type, number, address, month, note
%    \begin{macrocode}
FUNCTION {techreport}
{ "R" set.entry.type.id
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  format.btitle "title" output.check
  % new.block
  % format.translators output
  new.block
%<*2025>
  institution empty$ publisher empty$ and
    { format.full.date "year" output.check
      bbl.pages.colon set.punct
      format.pages output
    }
    { format.address.publisher output
      format.date output
      bbl.pages.colon set.punct
      format.pages output
    }
  if$
%</2025>
%<*!2025>
  institution empty$
  pages empty$ and
  control.url and
  entry.url empty$ not and
    'skip$
    { format.address.publisher output
      format.date output
      bbl.pages.colon set.punct
      format.pages output
    }
  if$
  "" set.punct
  format.creation.modification.date output
  "" set.punct
  format.urldate output
%</!2025>
  format.url output
  format.doi output
  new.block
  format.note output
  fin.entry
}

FUNCTION {report} { techreport }

%    \end{macrocode}
%
% A manual is technical documentation.
%
%       Required: title
%
%       Optional: author, organization, address, edition, month, year, note
%    \begin{macrocode}
FUNCTION {manual} { techreport }

%    \end{macrocode}
%
% \subsubsection{电子资源}
%    \begin{macrocode}
FUNCTION {webpage}
{ #1 'require.url :=
  output.bibitem
  format.authors output
%<*authoryear>
  format.date.before.title output
%</authoryear>
  new.block
  "EB" set.entry.type.id
  format.btitle "title" output.check
  new.block
  publisher empty$
    'skip$
    { format.address.publisher output
      format.date output
      bbl.pages.colon set.punct
      format.pages output
      "" set.punct
    }
  if$
  format.creation.modification.date output
  "" set.punct
  format.urldate "urldate" output.check
  format.url "url" output.check
  format.doi output
  new.block
  format.note output
  fin.entry
}

%    \end{macrocode}
%
% \subsubsection{其他文献类型}
%
% A misc is something that doesn't fit elsewhere.
%
%       Required: at least one of the `optional' fields
%
%       Optional: author, title, howpublished, month, year, note
%
% Misc 用来自动判断类型。
%    \begin{macrocode}
FUNCTION {misc}
{ eprint empty$ not archiveprefix empty$ not or eprinttype empty$ not or
    'preprint
    { entry.url empty$
        {
%<*!2005>
          "Z" set.entry.type.id
%</!2005>
%<*2005>
          "M" set.entry.type.id
%</2005>
          book
        }
        'webpage
      if$
    }
  if$
  empty.misc.check
}

FUNCTION {article}
{ entrysubtype field.or.null "l" change.case$ "newspaper" =
    { "N" set.entry.type.id
      article.journal
    }
    { journal journaltitle either.or shortjournal either.or
      duplicate$ empty$
        { pop$
          archiveprefix eprinttype either.or eprint either.or empty$
            'article.journal
            'preprint
          if$
        }
        { check.arxiv.preprint
            'preprint
            'article.journal
          if$
        }
      if$
    }
  if$
}

%    \end{macrocode}
%
% A booklet is a bound thing without a publisher or sponsoring institution.
%
%       Required: title
%
%       Optional: author, howpublished, address, month, year, note
%    \begin{macrocode}
FUNCTION {booklet} { book }

FUNCTION {collection}
{ "G" set.entry.type.id
  book
}

FUNCTION {image} { misc }

FUNCTION {legislation} { archive }


FUNCTION {map}
{ "CM" set.entry.type.id
  booktitle empty$ not
    { incollection }
    { output.bibitem
      format.authors output
%<*authoryear>
      format.date.before.title output
%</authoryear>
      new.block
      format.btitle "title" output.check
      new.block
      format.edition output
      new.block
      format.address.publisher output
      format.date output
      bbl.pages.colon set.punct
      format.pages output
%<*!(2005|2015)>
      new.block
      format.dimensions output
%</!(2005|2015)>
%<*2005|2015>
      "" set.punct
      format.creation.modification.date output
      "" set.punct
      format.urldate output
%</2005|2015>
      format.url output
      format.doi output
      new.block
      format.note output
      fin.entry
    }
  if$
}

FUNCTION {newspaper}
{ "N" set.entry.type.id
  article.journal
}

FUNCTION {online}
{ eprint empty$
  archiveprefix empty$ and
  eprinttype empty$ and
    { webpage }
    { preprint }
  if$
}

FUNCTION {software}
{ "CP" set.entry.type.id
  webpage
}

FUNCTION {standard}
{ "S" set.entry.type.id
%<*!(2005|2015)>
  output.bibitem
%<*authoryear>
  format.authors.editors output
  format.date.before.title output
  new.block
%</authoryear>
  number field.or.null output
  is.lang.cjk
    { "\quad " set.punct }
    { " " set.punct }
  if$
  format.btitle "title" output.check
  new.block
  format.url output
  new.block
  format.doi output
  new.block
  format.note output
  fin.entry
%</!(2005|2015)>
%<*2005|2015>
  booktitle empty$
    'book
    'incollection
  if$
%</2005|2015>
}

%    \end{macrocode}
%
% An unpublished is something that hasn't been published.
%
%       Required: author, title, note
%
%       Optional: month, year
%    \begin{macrocode}
FUNCTION {unpublished} { misc }

FUNCTION {video} { misc }

%    \end{macrocode}
%
% We use entry type `misc' for an unknown type; BibTeX gives a warning.
%    \begin{macrocode}
FUNCTION {default.type} { misc }

%    \end{macrocode}
%
% The following is taken from ttb.pdf.
%    \begin{macrocode}
FUNCTION {mult}
{ 'a :=                        %% we store the first value
 'b :=                        %% we store the second value

 b #0 <                       %% We remember the sign of b, and
    {#-1 #0 b - 'b :=}        %% then consider its absolute value.
    {#1}                      %%
 if$                          %%

 #0                           %% Put 0 on the stack.
 {b #0 >}                     %% While b is strictly positive,
 {                            %% we add a to the value on the stack
   a +                        %% and decrement b.
   b #1 - 'b :=               %%
 }                            %%
 while$                       %%

 swap$                        %% Last, we take the opposite
   'skip$                     %% if b was negative.
   {#0 swap$ -}               %%
 if$                          %%
}

FUNCTION {chr.to.value}       %% The ASCII code of a character
{ chr.to.int$ #48 -           %% ASCII value of "0" -> 48
  duplicate$ duplicate$       %%                "1" -> 49
  #0 < swap$ #9 > or          %%                   ...
  {                           %%                "9" -> 57
      #48 + int.to.chr$
      " is not a number..." *
      warning$                %% Return 0 if it is not a number
      pop$ #0                 %%
    }
  {}
  if$
}

FUNCTION {str.to.int.aux}     %% The auxiliary function
{ { duplicate$ empty$ not }     %% While the string is not empty
    {                              %% consider its first char
      swap$ #10 mult 'a :=    %% and ``add'' it at the end of
      duplicate$ #1 #1 substring$   %% the result.
      chr.to.value a +
      swap$
      #2 global.max$ substring$
    }
  while$
  pop$
}

FUNCTION {str.to.int}
{                             %% Handling negative values
  duplicate$ #1 #1 substring$ "-" =
    {#1 swap$ #2 global.max$ substring$}
    {#0 swap$}
  if$
                              %% Initialization, and then
  #0 swap$ str.to.int.aux     %% call to the aux. function
  swap$
    {#0 swap$ -}              %% And handle the sign.
    {}
  if$
}

FUNCTION {boolean.to.int}
{ "l" change.case$ duplicate$
    "true" =
    { pop$ #1 }
    { duplicate$ "false" =
        { pop$ #0 }
        { "unknown boolean " quote$ * swap$ * quote$ *
          " in " * cite$ * warning$
          #0
        }
      if$
    }
  if$
}

FUNCTION {GBT7714BSTCTL}
{ CTL_bib_punct empty$
    'skip$
    { CTL_bib_punct 'control.bib.punct := }
  if$
  CTL_convert_punct empty$
    'skip$
    { CTL_convert_punct boolean.to.int 'control.convert.punct := }
  if$
  CTL_max_bib_names empty$
    'skip$
    { CTL_max_bib_names str.to.int 'control.max.bib.names := }
  if$
  CTL_min_bib_names empty$
    'skip$
    { CTL_min_bib_names str.to.int 'control.min.bib.names := }
  if$
  CTL_max_cite_names empty$
    'skip$
    { CTL_max_cite_names str.to.int 'control.max.cite.names := }
  if$
  CTL_min_cite_names empty$
    'skip$
    { CTL_min_cite_names str.to.int 'control.min.cite.names := }
  if$
  CTL_uppercase_family empty$
    'skip$
    { CTL_uppercase_family boolean.to.int 'control.uppercase.family := }
  if$
  CTL_initialize_with_hyphen empty$
    'skip$
    { CTL_initialize_with_hyphen boolean.to.int 'control.initialize.with.hyphen := }
  if$
  CTL_check_pinyin empty$
    'skip$
    { CTL_check_pinyin boolean.to.int 'control.check.pinyin := }
  if$
  CTL_initialize_pinyin empty$
    'skip$
    { CTL_initialize_pinyin boolean.to.int 'control.initialize.pinyin := }
  if$
  CTL_bib_final_and empty$
    'skip$
    { CTL_bib_final_and boolean.to.int 'control.bib.final.and := }
  if$
  CTL_cite_final_and empty$
    'skip$
    { CTL_cite_final_and boolean.to.int 'control.cite.final.and := }
  if$
  CTL_cite_lang empty$
    'skip$
    { CTL_cite_lang 'control.cite.lang := }
  if$
  CTL_space_before_et_al empty$
    'skip$
    { CTL_space_before_et_al boolean.to.int 'control.space.before.et.al := }
  if$
  CTL_year_before_title empty$
    'skip$
    { CTL_year_before_title boolean.to.int 'control.year.before.title := }
  if$
  CTL_name_year_delim empty$
    'skip$
    { CTL_name_year_delim 'control.name.year.delim := }
  if$
  CTL_sentence_case empty$
    'skip$
    { CTL_sentence_case boolean.to.int 'control.sentence.case.title :=
      CTL_sentence_case boolean.to.int 'control.sentence.case.booktitle :=
%<*2005|2015>
      CTL_sentence_case boolean.to.int 'control.sentence.case.journal :=
%</2005|2015>
    }
  if$
  CTL_sentence_case_title empty$
    'skip$
    { CTL_sentence_case_title boolean.to.int 'control.sentence.case.title := }
  if$
  CTL_sentence_case_booktitle empty$
    'skip$
    { CTL_sentence_case_booktitle boolean.to.int 'control.sentence.case.booktitle := }
  if$
  CTL_sentence_case_journal empty$
    'skip$
    { CTL_sentence_case_journal boolean.to.int 'control.sentence.case.journal := }
  if$
  CTL_capitalize_subtitle empty$
    'skip$
    { CTL_capitalize_subtitle boolean.to.int 'control.capitalize.subtitle := }
  if$
  CTL_link_title empty$
    'skip$
    { CTL_link_title boolean.to.int 'control.link.title := }
  if$
  CTL_article_title empty$
    'skip$
    { CTL_article_title boolean.to.int 'control.article.title := }
  if$
  CTL_patent_country empty$
    'skip$
    { CTL_patent_country boolean.to.int 'control.patent.country := }
  if$
  CTL_entry_type_id empty$
    'skip$
    { CTL_entry_type_id boolean.to.int 'control.entry.type.id := }
  if$
  CTL_space_before_type_id empty$
    'skip$
    { CTL_space_before_type_id boolean.to.int 'control.space.before.type.id := }
  if$
  CTL_entry_medium_id empty$
    'skip$
    { CTL_entry_medium_id boolean.to.int 'control.entry.medium.id := }
  if$
  CTL_slash empty$
    'skip$
    { CTL_slash boolean.to.int 'control.slash := }
  if$
  CTL_in empty$
    'skip$
    { CTL_in boolean.to.int 'control.in := }
  if$
  CTL_emph_booktitle empty$
    'skip$
    { CTL_emph_booktitle boolean.to.int 'control.emph.booktitle := }
  if$
  CTL_emph_journal empty$
    'skip$
    { CTL_emph_journal boolean.to.int 'control.emph.journal := }
  if$
  CTL_short_journal empty$
    'skip$
    { CTL_short_journal boolean.to.int 'control.short.journal := }
  if$
  CTL_journal_dots empty$
    'skip$
    { CTL_journal_dots boolean.to.int 'control.journal.dots := }
  if$
  CTL_link_journal empty$
    'skip$
    { CTL_link_journal boolean.to.int 'control.link.journal := }
  if$
  CTL_bold_volume empty$
    'skip$
    { CTL_bold_volume boolean.to.int 'control.bold.volume := }
  if$
  CTL_unknown_publisher empty$
    'skip$
    { CTL_unknown_publisher boolean.to.int 'control.unknown.publisher := }
  if$
  CTL_space_before_pages empty$
    'skip$
    { CTL_space_before_pages boolean.to.int 'control.space.before.pages := }
  if$
  CTL_page_ranges empty$
    'skip$
    { CTL_page_ranges boolean.to.int 'control.page.ranges := }
  if$
  CTL_page_range_delim empty$
    'skip$
    { CTL_page_range_delim 'control.page.range.delim := }
  if$
  CTL_urldate empty$
    'skip$
    { CTL_urldate boolean.to.int 'control.urldate := }
  if$
  CTL_url empty$
    'skip$
    { CTL_url boolean.to.int 'control.url := }
  if$
  CTL_doi empty$
    'skip$
    { CTL_doi boolean.to.int 'control.doi := }
  if$
  CTL_eprint empty$
    'skip$
    { CTL_eprint boolean.to.int 'control.eprint := }
  if$
  CTL_note empty$
    'skip$
    { CTL_note boolean.to.int 'control.note := }
  if$
  CTL_end_dot empty$
    'skip$
    { CTL_end_dot boolean.to.int 'control.end.dot := }
  if$
  CTL_warn_empty_field empty$
    'skip$
    { CTL_warn_empty_field boolean.to.int 'control.warn.empty.field := }
  if$
}

FUNCTION {control.pass}
{ type$ "gbt7714bstctl" =
    'GBT7714BSTCTL
    'skip$
  if$
}

FUNCTION {control.check}
{ % TODO
}

%    \end{macrocode}
%
%
% \subsection{Common macros}
%
% Here are macros for common things that may vary from style to style.
% Users are encouraged to use these macros.
%
% Months are either written out in full or abbreviated
%    \begin{macrocode}
MACRO {jan} {"January"}

MACRO {feb} {"February"}

MACRO {mar} {"March"}

MACRO {apr} {"April"}

MACRO {may} {"May"}

MACRO {jun} {"June"}

MACRO {jul} {"July"}

MACRO {aug} {"August"}

MACRO {sep} {"September"}

MACRO {oct} {"October"}

MACRO {nov} {"November"}

MACRO {dec} {"December"}

%    \end{macrocode}
%
% Journals are either written out in full or abbreviated;
% the abbreviations are like those found in ACM publications.
%
% To get a completely different set of abbreviations, it may be best to make
% a separate .bib file with nothing but those abbreviations; users could then
% include that file name as the first argument to the \cs{bibliography} command
%    \begin{macrocode}
MACRO {acmcs} {"ACM Computing Surveys"}

MACRO {acta} {"Acta Informatica"}

MACRO {cacm} {"Communications of the ACM"}

MACRO {ibmjrd} {"IBM Journal of Research and Development"}

MACRO {ibmsj} {"IBM Systems Journal"}

MACRO {ieeese} {"IEEE Transactions on Software Engineering"}

MACRO {ieeetc} {"IEEE Transactions on Computers"}

MACRO {ieeetcad}
 {"IEEE Transactions on Computer-Aided Design of Integrated Circuits"}

MACRO {ipl} {"Information Processing Letters"}

MACRO {jacm} {"Journal of the ACM"}

MACRO {jcss} {"Journal of Computer and System Sciences"}

MACRO {scp} {"Science of Computer Programming"}

MACRO {sicomp} {"SIAM Journal on Computing"}

MACRO {tocs} {"ACM Transactions on Computer Systems"}

MACRO {tods} {"ACM Transactions on Database Systems"}

MACRO {tog} {"ACM Transactions on Graphics"}

MACRO {toms} {"ACM Transactions on Mathematical Software"}

MACRO {toois} {"ACM Transactions on Office Information Systems"}

MACRO {toplas} {"ACM Transactions on Programming Languages and Systems"}

MACRO {tcs} {"Theoretical Computer Science"}

%    \end{macrocode}
%
%
% \subsection{Format labels}
%
% The sortify function converts to lower case after |purify$|ing; it's
% used in sorting and in computing alphabetic labels after sorting
%
% The chop.word(w,len,s) function returns either s or, if the first len
% letters of s equals w (this comparison is done in the third line of the
% function's definition), it returns that part of s after w.
%    \begin{macrocode}
FUNCTION {sortify}
{ purify$
  "l" change.case$
}

%    \end{macrocode}
%
% We need the chop.word stuff for the dubious unsorted-list-with-labels case.
%    \begin{macrocode}
FUNCTION {chop.word}
{ 's :=
  'len :=
  s #1 len substring$ =
    { s len #1 + global.max$ substring$ }
    's
  if$
}

%    \end{macrocode}
%
% The |format.lab.names| function makes a short label by using the initials of
% the von and Last parts of the names (but if there are more than four names,
% (i.e., people) it truncates after three and adds a superscripted "+";
% it also adds such a "+" if the last of multiple authors is "others").
% If there is only one name, and its von and Last parts combined have just
% a single name-token ("Knuth" has a single token, "Brinch Hansen" has two),
% we take the first three letters of the last name.  The boolean
% et.al.char.used tells whether we've used a superscripted "+", so that we
% know whether to include a LaTeX macro for it.
%
% \begin{pseudocode}
% format.lab.names(s) ==
%  BEGIN
%       numnames := num.names$(s)
%       if numnames > 1 then
%           if numnames > 4 then
%               namesleft := 3
%           else
%               namesleft := numnames
%           nameptr := 1
%           nameresult := ""
%           while namesleft > 0
%             do
%               if (name_ptr = numnames) and
%                    format.name$(s, nameptr, "{ff }{vv }{ll}{ jj}") = "others"
%                  then nameresult := nameresult * "{\etalchar{+}}"
%                       et.al.char.used := true
%                  else nameresult := nameresult *
%                               format.name$(s, nameptr, "{v{}}{l{}}")
%               nameptr := nameptr + 1
%               namesleft := namesleft - 1
%             od
%           if numnames > 4 then
%               nameresult := nameresult * "{\etalchar{+}}"
%               et.al.char.used := true
%       else
%           t := format.name$(s, 1, "{v{}}{l{}}")
%           if text.length$(t) < 2 then % there's just one name-token
%               nameresult := text.prefix$(format.name$(s,1,"{ll}"),3)
%           else
%               nameresult := t
%           fi
%       fi
%       return nameresult
%  END
% \end{pseudocode}
%
% Exactly what fields we look at in constructing the primary part of the label
% depends on the entry type; this selectivity (as opposed to, say, always
% looking at author, then editor, then key) helps ensure that "ignored" fields,
% as described in the LaTeX book, really are ignored.  Note that MISC is part
% of the deepest `else' clause in the nested part of calc.label; thus, any
% unrecognized entry type in the database is handled correctly.
%
% There is one auxiliary function for each of the four different sequences of
% fields we use.  The first of these functions looks at the author field, and
% then, if necessary, the key field.  The other three functions, which might
% look at two fields and the key field, are similar, except that the key field
% takes precedence over the organization field (for labels---not for sorting).
%
% The calc.label function calculates the preliminary label of an entry, which
% is formed by taking three letters of information from the author or editor or
% key or organization field (depending on the entry type and on what's empty,
% but ignoring a leading "The " in the organization), and appending the last
% two characters (digits) of the year. It is an error if the appropriate fields
% among author, editor, organization, and key are missing, and we use
% the first three letters of the |cite$| in desperation when this happens.
% The resulting label has the year part, but not the name part, |purify$|ed
% (|purify$|ing the year allows some sorting shenanigans by the user).
%
% This function also calculates the version of the label to be used in sorting.
%
% The final label may need a trailing 'a', 'b', etc., to distinguish it from
% otherwise identical labels, but we can't calculated those "extra.label"s
% until after sorting.
%
% \begin{pseudocode}
% calc.label ==
%  BEGIN
%       if type$ = "book" or "inbook" then
%           author.editor.key.label
%       else if type$ = "proceedings" then
%           editor.key.organization.label
%       else if type$ = "manual" then
%           author.key.organization.label
%       else
%           author.key.label
%       fi fi fi
%       label := label * substring$(purify$(field.or.null(year)), -1, 2)
%               % assuming we will also sort, we calculate a sort.label
%       sort.label := sortify(label), but use the last four, not two, digits
%  END
% \end{pseudocode}
%    \begin{macrocode}
FUNCTION {format.lab.names}
{ 's :=
  #1 'nameptr :=
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }
    { format.lab.name 't :=
      nameptr #1 >
        { numnames control.max.cite.names >
          nameptr control.min.cite.names > and
            { "others" 't :=
              #1 'namesleft :=
            }
            'skip$
          if$
          namesleft #1 >
            { add.comma t * }
            { t "others" =
                { nameptr #2 >
                    { add.comma }
                    { space.precedes.et.al * }
                  if$
                  cite.et.al *
                }
                { control.cite.final.and
                    { is.lang.cjk not nameptr #2 > and
                        { add.comma }
                        { inter.word.space * }
                      if$
                      cite.and * inter.word.space *
                    }
                    { add.comma }
                  if$
                  t *
                }
              if$
            }
          if$
        }
        't
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
  format.punctuations
}

FUNCTION {author.key.label}
{ author empty$
    { bbl.anonymous }
    { author format.lab.names }
  if$
}

FUNCTION {author.editor.key.label}
{ author empty$
    { editor empty$
        { bbl.anonymous }
        { editor format.lab.names }
      if$
    }
    { author format.lab.names }
  if$
}

FUNCTION {author.key.organization.label}
{ author empty$
    { organization empty$
        { bbl.anonymous }
        { "The " #4 organization chop.word #3 text.prefix$ }
      if$
    }
    { author format.lab.names }
  if$
}

FUNCTION {editor.key.organization.label}
{ editor empty$
    { organization empty$
        { bbl.anonymous }
        { "The " #4 organization chop.word #3 text.prefix$ }
      if$
    }
    { editor format.lab.names }
  if$
}

FUNCTION {holder.author.label}
{ holder empty$
    { author empty$
        { bbl.anonymous }
        { author format.lab.names }
      if$
    }
    { holder format.lab.names }
  if$
}

FUNCTION {calc.short.authors}
{ type$ "book" =
  type$ "inbook" = booktitle empty$ not and
  or
    'author.editor.key.label
    { type$ "collection" =
      type$ "proceedings" =
      or
      type$ "periodical" =
      or
        { editor empty$ not
            'editor.key.organization.label
            'author.key.organization.label
          if$
        }
        { type$ "patent" =
            'holder.author.label
            'author.key.label
          if$
        }
      if$
    }
  if$
  'short.list :=
}

%    \end{macrocode}
%
% 如果 label 中有中括号“[”，分别用大括号保护起来，防止 \cs{bibitem} 处理出错。
% 另外为了兼容 \pkg{bibunits}，“name(year)fullname” 的每一项都要分别保护起来，
% 参考 \href{https://github.com/tuna/thuthesis/issues/630}{tuna/thuthesis/\#630}。
%    \begin{macrocode}
FUNCTION {calc.label}
{ calc.short.authors
  short.list
  duplicate$ "]" contains
    { "{" swap$ * "}" * }
    'skip$
  if$
  "("
  *
  format.lab.date
  duplicate$ "]" contains
    { "{" swap$ * "}" * }
    'skip$
  if$
  *
  'label :=
}

%    \end{macrocode}
%
%
% \subsection{Sorting}
%
% When sorting, we compute the sortkey by executing "presort" on each entry.
% The presort key contains a number of "sortify"ed strings, concatenated
% with multiple blanks between them.  This makes things like "brinch  per"
% come before "brinch hansen  per".
%
% The fields used here are: the sort.label for alphabetic labels (as set by
% |calc.label|), followed by the author names (or editor names or organization
% (with a leading "The " removed) or key field, depending on entry type and on
% what's empty), followed by year, followed by the first bit of the title
% (chopping off a leading "The ", "A ", or "An ").
% Names are formatted: Von Last First Junior.
% The names within a part will be separated by a single blank
% (such as "brinch hansen"), two will separate the name parts themselves
% (except the von and last), three will separate the names,
% four will separate the names from year (and from label, if alphabetic),
% and four will separate year from title.
%
% The |sort.format.names| function takes an argument that should be in
% BibTeX name format, and returns a string containing "   "-separated
% names in the format described above.  The function is almost the same
% as format.names.
%    \begin{macrocode}
%<*authoryear>
FUNCTION {sort.language.label}
{ entry.lang lang.zh =
    { lang.zh.order }
    { entry.lang lang.ja =
        { lang.ja.order }
        { entry.lang lang.en =
            { lang.en.order }
            { entry.lang lang.ru =
                { lang.ru.order }
                { lang.other.order }
              if$
            }
          if$
        }
      if$
    }
  if$
  #64 +
  int.to.chr$
}

FUNCTION {sort.format.names}
{ 's :=
  #1 'nameptr :=
  ""
  s num.names$ 'numnames :=
  numnames 'namesleft :=
    { namesleft #0 > }
    { s nameptr "{vv{ } }{ll{ }}{  ff{ }}{  jj{ }}" format.name$ 't :=
      nameptr #1 >
        { "   "  *
          namesleft #1 = t "others" = and
            { "zzzzz" * }
            { numnames #2 > nameptr #2 = and
                { "zz" * year field.or.null * "   " * }
                'skip$
              if$
              t sortify *
            }
          if$
        }
        { t sortify * }
      if$
      nameptr #1 + 'nameptr :=
      namesleft #1 - 'namesleft :=
    }
  while$
}

%    \end{macrocode}
%
% The sort.format.title function returns the argument,
% but first any leading "A "'s, "An "'s, or "The "'s are removed.
% The chop.word function uses s, so we need another string variable, t
%    \begin{macrocode}
FUNCTION {sort.format.title}
{ 't :=
  "A " #2
    "An " #3
      "The " #4 t chop.word
    chop.word
  chop.word
  sortify
  #1 global.max$ substring$
}

%    \end{macrocode}
%
% The auxiliary functions here, for the presort function, are analogous to
% the ones for calc.label; the same comments apply, except that the
% organization field takes precedence here over the key field.  For sorting
% purposes, we still remove a leading "The " from the organization field.
%    \begin{macrocode}
FUNCTION {anonymous.sort}
{ entry.lang lang.zh =
    { "yi4 ming2" }
    { "anon" }
  if$
}

FUNCTION {warn.empty.key}
{ is.lang.cjk
    { "key" warn.empty.field }
    'skip$
  if$
}

FUNCTION {author.sort}
{ key empty$
    { warn.empty.key
      author empty$
        { anonymous.sort }
        { author sort.format.names }
      if$
    }
    { key }
  if$
}

FUNCTION {author.editor.sort}
{ key empty$
    { warn.empty.key
      author empty$
        { editor empty$
            { anonymous.sort }
            { editor sort.format.names }
          if$
        }
        { author sort.format.names }
      if$
    }
    { key }
  if$
}

FUNCTION {author.organization.sort}
{ key empty$
    { warn.empty.key
      author empty$
        { organization empty$
            { anonymous.sort }
            { "The " #4 organization chop.word sortify }
          if$
        }
        { author sort.format.names }
      if$
    }
    { key }
  if$
}

FUNCTION {editor.organization.sort}
{ key empty$
    { warn.empty.key
      editor empty$
        { organization empty$
            { anonymous.sort }
            { "The " #4 organization chop.word sortify }
          if$
        }
        { editor sort.format.names }
      if$
    }
    { key }
  if$
}

%</authoryear>
%    \end{macrocode}
%
% 顺序编码制的排序要简单得多
%    \begin{macrocode}
%<*numeric>
INTEGERS { seq.num }

FUNCTION {init.seq}
{ #0 'seq.num :=}

FUNCTION {int.to.fix}
{ "000000000" swap$ int.to.str$ *
  #-1 #10 substring$
}

%</numeric>
%    \end{macrocode}
%
% There is a limit, |entry.max$|, on the length of an entry string variable
% (which is what its |sort.key$| is), so we take at most that many characters
% of the constructed key, and hope there aren't many references that match
% to that many characters!
%    \begin{macrocode}
FUNCTION {presort}
{ type$ "gbt7714bstctl" =
    {
%<*authoryear>
      "_"
      "    "
%</authoryear>
%<*numeric>
      "_    "
      seq.num  int.to.fix
%</numeric>
    }
    { set.entry.lang
      set.entry.numbered
      "" 'entry.url :=
      #0 'require.url :=
      check.electronic
      calc.label
      label sortify
      "    "
      *
%<*authoryear>
      sort.language.label
      "    "
      *
      type$ "book" =
      type$ "inbook" = booktitle empty$ not and
      or
        'author.editor.sort
        { type$ "collection" =
          type$ "proceedings" =
          or
            'editor.organization.sort
            'author.sort
          if$
        }
      if$
      *
      "    "
      *
      year field.or.null sortify
      *
      "    "
      *
      cite$
      *
      #1 entry.max$ substring$
%</authoryear>
%<*numeric>
      seq.num #1 + 'seq.num :=
      seq.num  int.to.fix
%</numeric>
    }
  if$
  'sort.label :=
  sort.label *
  #1 entry.max$ substring$
  'sort.key$ :=
}

%    \end{macrocode}
%
% Now comes the final computation for alphabetic labels, putting in the 'a's
% and 'b's and so forth if required.  This involves two passes: a forward
% pass to put in the 'b's, 'c's and so on, and a backwards pass
% to put in the 'a's (we don't want to put in 'a's unless we know there
% are 'b's).
% We have to keep track of the longest (in |width$| terms) label, for use
% by the "thebibliography" environment.
%
% \begin{pseudocode}
% VAR: longest.label, last.sort.label, next.extra: string
%      longest.label.width, last.extra.num: integer
%
% initialize.longest.label ==
%  BEGIN
%       longest.label := ""
%       last.sort.label := int.to.chr$(0)
%       next.extra := ""
%       longest.label.width := 0
%       last.extra.num := 0
%  END
%
% forward.pass ==
%  BEGIN
%       if last.sort.label = sort.label then
%           last.extra.num := last.extra.num + 1
%           extra.label := int.to.chr$(last.extra.num)
%       else
%           last.extra.num := chr.to.int$("a")
%           extra.label := ""
%           last.sort.label := sort.label
%       fi
%  END
%
% reverse.pass ==
%  BEGIN
%       if next.extra = "b" then
%           extra.label := "a"
%       fi
%       label := label * extra.label
%       if width$(label) > longest.label.width then
%           longest.label := label
%           longest.label.width := width$(label)
%       fi
%       next.extra := extra.label
%  END
% \end{pseudocode}
%    \begin{macrocode}
STRINGS { longest.label last.label next.extra last.extra.label }

INTEGERS { longest.label.width number.label }

FUNCTION {initialize.longest.label}
{ "" 'longest.label :=
  #0 int.to.chr$ 'last.label :=
  "" 'next.extra :=
  #0 'longest.label.width :=
  #0 'number.label :=
  "" 'last.extra.label :=
}

FUNCTION {forward.pass}
{ type$ "gbt7714bstctl" =
    'skip$
    {
%<*authoryear>
      last.label label =
        { "" 'extra.label :=
          last.extra.label text.length$ 'charptr :=
            { last.extra.label charptr #1 substring$ "z" =
              charptr #0 > and
            }
            { "a" extra.label * 'extra.label :=
              charptr #1 - 'charptr :=
            }
          while$
          charptr #0 >
            { last.extra.label charptr #1 substring$ chr.to.int$ #1 + int.to.chr$
              extra.label * 'extra.label :=
              last.extra.label #1 charptr #1 - substring$
              extra.label * 'extra.label :=
            }
            { "a" extra.label * 'extra.label := }
          if$
          extra.label 'last.extra.label :=
        }
        { "a" 'last.extra.label :=
          "" 'extra.label :=
          label 'last.label :=
        }
      if$
%</authoryear>
      number.label #1 + 'number.label :=
    }
  if$
}

FUNCTION {reverse.pass}
{ type$ "gbt7714bstctl" =
    'skip$
    {
%<*authoryear>
      next.extra "b" =
        { "a" 'extra.label := }
        'skip$
      if$
      extra.label 'next.extra :=
      extra.label
      duplicate$ empty$
        'skip$
        { "{\natexlab{" swap$ * "}}" * }
      if$
      'extra.label :=
%</authoryear>
      label extra.label * 'label :=
    }
  if$
}

FUNCTION {bib.sort.order}
{ sort.label  'sort.key$ := }

%    \end{macrocode}
%
%
% \subsection{Write bbl file}
%
% Now we're ready to start writing the .BBL file.
% We begin, if necessary, with a \LaTeX{} macro for unnamed names in an
% alphabetic label; next comes stuff from the `preamble' command in the
% database files.  Then we give an incantation containing the command
%     |\begin{thebibliography}{...}|
% where the `...' is the longest label.
%
% We also call init.state.consts, for use by the output routines.
%    \begin{macrocode}
FUNCTION {begin.bib}
{   preamble$ empty$
    'skip$
    { preamble$ write$ newline$ }
  if$
  "\begin{thebibliography}{" number.label int.to.str$ * "}" *
  write$ newline$
  control.cite.lang "macro" =
    { "\providecommand{\biband}{和}"
      write$ newline$
      "\providecommand{\bibetal}{等}"
      write$ newline$
    }
    'skip$
  if$
  "\providecommand{\natexlab}[1]{#1}"
  write$ newline$
  "\providecommand{\url}[1]{#1}"
  write$ newline$
  "\expandafter\ifx\csname urlstyle\endcsname\relax\else"
  write$ newline$
  "  \urlstyle{same}\fi"
  write$ newline$
  control.doi control.eprint or
    { "\expandafter\ifx\csname href\endcsname\relax"
      write$ newline$
      control.doi
        { "  \def\doi{\begingroup \urlstyle{same}\Url}"
          write$ newline$
          control.cstr
            { "  \def\cstr{\begingroup \urlstyle{same}\Url}"
              write$ newline$
            }
            'skip$
          if$
        }
        'skip$
      if$
      control.eprint
        { "  \def\eprint#1#2{#2}"
          write$ newline$
        }
        'skip$
      if$
      "\else"
      write$ newline$
      control.doi
        { "  \def\doi#1{\href{https://doi.org/#1}{\nolinkurl{#1}}}"
          write$ newline$
          control.cstr
            { "  \def\cstr#1{\href{https://cstr.cn/#1}{\nolinkurl{#1}}}"
              write$ newline$
            }
            'skip$
          if$
        }
        'skip$
      if$
      control.eprint
        { "  \let\eprint\href"
          write$ newline$
        }
        'skip$
      if$
      "\fi"
      write$ newline$
    }
    'skip$
  if$
}

%    \end{macrocode}
%
% Finally, we finish up by writing the `|\end{thebibliography}|' command.
%    \begin{macrocode}
FUNCTION {end.bib}
{ newline$
  "\end{thebibliography}" write$ newline$
}

%    \end{macrocode}
%
%
% \subsection{Main execution}
%
% Now we read in the .BIB entries.
%    \begin{macrocode}
READ

EXECUTE {init.state.consts}

EXECUTE {load.config}

ITERATE {control.pass}

EXECUTE {control.check}

%<*numeric>
EXECUTE {init.seq}

%</numeric>
ITERATE {presort}

%    \end{macrocode}
%
% And now we can sort
%    \begin{macrocode}
SORT

EXECUTE {initialize.longest.label}

ITERATE {forward.pass}

REVERSE {reverse.pass}

ITERATE {bib.sort.order}

SORT

EXECUTE {begin.bib}

%    \end{macrocode}
%
% Now we produce the output for all the entries
%    \begin{macrocode}
ITERATE {call.type$}

EXECUTE {end.bib}
%    \end{macrocode}
