%
% Copyright (c) 2021-2025 Zeping Lee
% Released under the MIT License.
% Repository:
https://github.com/zepinglee/citeproc-lua
%
% ## Initialization at `\begin{document}`
\hook_gput_code:nnn { begindocument } { . } { \__csl_at_begin_document_hook: }
\cs_new:Npn \__csl_at_begin_document_hook:
{
\__csl_write_aux_info:
\sys_if_engine_luatex:TF
{ \__csl_initialize_lua_module: }
{
\__csl_load_bbl:
\__csl_get_style_class:
}
\__csl_read_entry_ids:
\__csl_set_ref_section_level:
}
\clist_new:N \g__csl_aux_bib_files_clist
\cs_new:Npn \__csl_write_aux_info:
{
\tl_if_empty:NF \l__csl_style_tl
{ \exp_args:NV \__csl_write_aux_bibstyle:n \l__csl_style_tl }
\tl_if_empty:NT \l__csl_style_tl
{ \tl_set_eq:NN \l__csl_style_tl \g__csl_aux_bibstyle_tl }
\tl_if_empty:NT \l__csl_style_tl
{
\msg_warning:nn { citation-style-language } { missing-style-name }
\tl_set:Nn \l__csl_style_tl { apa }
}
\tl_set_eq:NN \csl@style \l__csl_style_tl
\clist_if_empty:NTF \l__csl_bib_resources_clist
{
\clist_if_empty:NTF \g__csl_aux_bib_files_clist
{ \msg_warning:nn { citation-style-language } { empty-bib-resources } }
{
\clist_map_inline:Nn \g__csl_aux_bib_files_clist
{ \addbibresource {##1} }
}
}
{
\clist_map_inline:Nn \l__csl_bib_resources_clist
{
\tl_clear:N \l_tmpa_tl
\prop_get:NnN \g__csl_bib_resource_options_prop {##1} \l_tmpa_tl
\exp_args:NnV \__csl_write_aux_bibdata:nn {##1} \l_tmpa_tl
}
}
\__csl_write_aux_csl_options:
}
\cs_new:Npn \__csl_write_aux_bibstyle:n #1
{
\if@filesw
\iow_now:Ne \@auxout
{
\token_to_str:N \csl@aux@style
{ \int_use:N \g__csl_ref_section_index_int } {#1}
}
\fi
}
\cs_new:Npn \__csl_write_aux_bibdata:nn #1#2
{
\if@filesw
% Full expansion for files like \jobname.bib
\iow_now:Ne \@auxout
{
\token_to_str:N \csl@aux@data
{ \int_use:N \g__csl_ref_section_index_int } {#1} {#2}
}
\fi
}
% In earlier time, \bibdata{xxx.json} was used but this causes latexmk unable
% to find xxx.json.bib and it refuses to run the $bibtex procedure.
% John Collins suggests using a different command than \bibdata.
\cs_new:Npn \csl@aux@data #1#2#3
{
\str_if_eq:nnT {#1} { 0 }
{
\clist_map_inline:nn {#2}
{
\clist_gput_right:Nn \g__csl_aux_bib_files_clist {##1}
\prop_gput:Nnn \g__csl_bib_resource_options_prop {##1} {#3}
}
}
}
\cs_new:Npn \__csl_initialize_lua_module:
{
\bool_if:NT \l__csl_regression_test_bool
{
\lua_now:n { csl_citation_manager.regression_test = true }
}
\lua_now:e
{
csl_citation_manager:init(
"\l__csl_style_tl",
"\l__csl_locale_tl"
)
}
\str_if_eq:eeTF
{
\lua_now:n
{
tex.print(tostring(
csl_citation_manager.ref_section.initialized
))
}
}
{ true }
{ \bool_set_true:N \l__csl_engine_initialized_bool }
{ \bool_set_false:N \l__csl_engine_initialized_bool }
\__csl_get_style_class_luatex:
\@ifpackageloaded { hyperref }
{ \lua_now:n { csl_citation_manager:enable_linking() } }
{ }
}
\msg_new:nnn { citation-style-language } { missing-style-name }
{ Missing~ style~ name.~ Will~ use~ default~ APA~ style. }
\msg_new:nnn { citation-style-language } { empty-bib-resources }
{ Empty~ bibliographic~ resources.~ Use~ \token_to_str:N \addbibresource. }
% \str_new:N \l__csl_style_class_str
% In-text (including numeric or author-date) or note style
% \bool_new:N \l__csl_note_style_bool
\cs_new:Npn \__csl_get_style_class_luatex: {
\bool_set_false:N \l__csl_note_bool
\bool_if:NT \l__csl_engine_initialized_bool
{
\tl_set:Ne \l__csl_class_tl { \lua_now:n { tex.print(csl_citation_manager:get_style_class()) } }
\tl_if_eq:NnT \l__csl_class_tl { note }
{ \bool_set_true:N \l__csl_note_bool }
}
\int_compare:nNnT { \g__csl_ref_section_index_int } = { 0 }
{ \bool_gset_eq:NN \g__csl_global_note_class_bool \l__csl_note_bool }
}
\bool_new:N \l__csl_engine_initialized_bool
\prop_new:N \l__csl_language_code_map_prop
\prop_set_from_keyval:Nn \l__csl_language_code_map_prop
{
acadian = fr-CA,
american = en-US,
australian = en-AU,
afrikaans = af-ZA,
albanian = sq-AL,
amharic = am-ET,
arabic = ar,
armenian = hy-AM,
asturian = ast-ES,
austrian = de-AT,
bahasa = id-ID,
bahasai = id-ID,
bahasam = id-ID,
basque = eu-ES,
bengali = bn-BD,
bgreek = el-GR,
brazil = pt-BR,
brazilian = pt-BR,
breton = br-FR,
british = en-GB,
bulgarian = bg-BG,
canadian = en-CA,
canadien = fr-CA,
catalan = ca-AD,
coptic = cop,
croatian = hr-HR,
czech = cs-CZ,
danish = da-DK,
divehi = dv-MV,
dutch = nl-NL,
english = en-US,
esperanto = eo-001,
estonian = et-EE,
ethiopia = am-ET,
farsi = fa-IR,
finnish = fi-FI,
francais = fr-FR,
french = fr-FR,
frenchle = fr-FR,
friulan = fur-IT,
galician = gl-ES,
german = de-DE,
germanb = de-DE,
greek = el-GR,
hebrew = he-IL,
hindi = hi-IN,
ibygreek = el-CY,
icelandic = is-IS,
indon = id-ID,
indonesia = id-ID,
interlingua = ia-FR,
irish = ga-IE,
italian = it-IT,
japanese = ja-JP,
kannada = kn-IN,
lao = lo-LA,
latin = la-Latn,
latvian = lv-LV,
lithuanian = lt-LT,
lowersorbian = dsb-DE,
lsorbian = dsb-DE,
magyar = hu-HU,
malay = id-ID,
malayalam = ml-IN,
marathi = mr-IN,
meyalu = id-ID,
mongolian = mn-Cyrl,
naustrian = de-AT,
newzealand = en-NZ,
ngerman = de-DE,
nko = ha-NG,
norsk = nb-NO,
norwegian = nn-NO,
nynorsk = nn-NO,
occitan = oc-FR,
piedmontese = pms-IT,
pinyin = pny,
polish = pl-PL,
polutonikogreek = el-GR,
portuges = pt-PT,
portuguese = pt-PT,
romanian = ro-RO,
romansh = rm-CH,
russian = ru-RU,
samin = se-NO,
sanskrit = sa-IN,
scottish = gd-GB,
serbian = sr-Latn,
serbianc = sr-Cyrl,
slovak = sk-SK,
slovene = sl-SI,
slovenian = sl-SI,
spanish = es-ES,
swedish = sv-SE,
swiss = de-CH,
swissgerman = de-CH,
nswissgerman = de-CH,
syriac = syc,
tamil = ta-IN,
telugu = te-IN,
thai = th-TH,
thaicjk = th-TH,
tibetan = bo-CN,
turkish = tr-TR,
turkmen = tk-TM,
ukrainian = uk-UA,
urdu = ur-IN,
UKenglish = en-GB,
uppersorbian = hsb-DE,
USenglish = en-US,
usorbian = hsb-DE,
vietnamese = vi-VN,
welsh = cy-GB,
}
\cs_new:Npn \__csl_write_aux_csl_options:
{
\clist_clear:N \l_tmpa_clist % list of options to write to aux file
% locale
\tl_if_empty:NT \l__csl_locale_tl
{ \__csl_get_locale_from_babel: }
\tl_if_empty:NF \l__csl_locale_tl
{
\clist_put_right:Ne \l_tmpa_clist
{ locale = \l__csl_locale_tl }
}
% linking
\@ifpackageloaded { hyperref }
{ \clist_put_right:Nn \l_tmpa_clist { linking = true } }
{ }
% write to aux file
\prop_if_empty:NF \l_tmpa_clist
{
\exp_args:Ne \__csl_write_aux_options:n
{ \clist_use:Nn \l_tmpa_clist { , } }
}
}
% This is the hook for `babel` package.
\cs_new:Npn \__csl_get_locale_from_babel: { }
\cs_new:Npn \__csl_write_aux_options:n #1
{
\if@filesw
\iow_now:Ne \@auxout
{
\token_to_str:N \csl@aux@options
{ \int_use:N \g__csl_ref_section_index_int } {#1}
}
\fi
}
\tl_new:N \l__csl_ref_section_index_tl
\cs_new:Npn \csl@aux@options #1#2
{
\tl_set:Nn \l__csl_ref_section_index_tl {#1}
\keys_set:nn { csl / options } {#2}
}
% Load .bbl at the beginning of document to save one pass of latex.
% In this procedure, the \cslcitation command is processed and the contents
% of `thebibliography` is stored into \g__csl_bibliographies_prop.
\cs_new:Npn \__csl_load_bbl:
{
% The \@input@ prints "No file ....bbl" in the .log file from which
% the latexmk decides to run $bibtex or not.
\__csl_collect_bibliography:n { \@input@ { \jobname .bbl } }
}
% A document may have multiple bibliographies or biblists and they are stored
% in `\g__csl_bibliographies_prop` by their index.
\prop_new:N \g__csl_bibliographies_prop
% Collection the bibliography into \g__csl_bibliographies_prop
\cs_new:Npn \__csl_collect_bibliography:n #1
{
\group_begin:
% URLs may contain "%" and "#" characters.
\char_set_catcode_other:N \%
\char_set_catcode_other:N \#
\RenewDocumentEnvironment { thebibliography } { m +b }
{
\tl_set:Nn \l__csl_bib_index_tl { 1 }
\keys_set:nn { csl / bib-options } {##1}
\prop_gput:NVn \g__csl_bibliographies_prop \l__csl_bib_index_tl
{
\begin { thebibliography } {##1}
##2
\end { thebibliography }
}
}
{ }
% Perform the execution
#1
\group_end:
}
% At the moment, the `\csloptions` only reads the style class from `.bbl`
% generated by `citeproc-lua`.
% #1: refsection index
% #2: refsection options
\NewDocumentCommand \csloptions { m m }
{
\tl_set:Nn \l__csl_ref_section_index_tl {#1}
\keys_set:nn { csl / options } {#2}
}
% The class option is in the `<style>=<class>` form
% (e.g. `\csloptions{class={apa=in-text}}`).
\keys_define:nn { csl / options }
{
class .code:n = { \prop_gput_from_keyval:Nn \g__csl_style_class_prop {#1} },
entry-ids .code:n = { \__csl_process_entry_ids:n {#1} } ,
excluded-ids .code:n = { \__csl_process_excluded_ids:n {#1} } ,
unknown .code:n = { } ,
}
\cs_new:Npn \__csl_process_entry_ids:n #1 { }
\cs_new:Npn \__csl_process_excluded_ids:n #1 { }
\prop_new:N \g__csl_style_class_prop
% The `\textcite` bahaves differently with note styles.
% Thus we read the style class from the `.csl` file with non-LuaTeX engines.
\ior_new:N \l__csl_style_ior
\cs_new:Npn \__csl_get_style_class:
{
\tl_clear:N \l__csl_class_tl
\bool_set_false:N \l__csl_note_bool
\prop_get:NVNF \g__csl_style_class_prop \l__csl_style_tl \l__csl_class_tl
{ \__csl_read_style_class: }
\tl_if_empty:NF \l__csl_class_tl
{
\str_case:VnTF \l__csl_class_tl
{
{ note } { \bool_set_true:N \l__csl_note_bool }
{ in-text } { \bool_set_false:N \l__csl_note_bool }
}
{
\int_compare:nNnT { \g__csl_ref_section_index_int } = { 0 }
{
\bool_gset_eq:NN \g__csl_global_note_class_bool \l__csl_note_bool
}
}
{
\msg_error:nnV { citation-style-language } { invalid-style-class }
\l__csl_class_tl
}
}
}
\cs_new:Npn \__csl_read_style_class:
{
\tl_if_blank:VF \l__csl_style_tl
{
\exp_args:NNe \ior_open:Nn \l__csl_style_ior { \l__csl_style_tl .csl }
\ior_map_inline:Nn \l__csl_style_ior
{
% \tl_show:n {##1}
\tl_if_in:nnTF {##1} { class="note" }
{
\tl_set:Nn \l__csl_class_tl { note }
\bool_set_true:N \l__csl_note_bool
\prop_gput:NVn \g__csl_style_class_prop \l__csl_style_tl { note }
\ior_map_break:
}
{
\tl_if_in:nnT {##1} { class="in-text" }
{
\tl_set:Nn \l__csl_class_tl { in-text }
\bool_set_false:N \l__csl_note_bool
\prop_gput:NVn \g__csl_style_class_prop \l__csl_style_tl { in-text }
\ior_map_break:
}
}
}
\ior_close:N \l__csl_style_ior
}
}
\msg_new:nnn { citation-style-language } { invalid-style-class }
{ Invalid~ style~ class~ '#1'. }
% \msg_new:nnn { citation-style-language } { file-non-exist }
% { No~ file~ #1. }
% This is a hook for `hyperref`.
\cs_new:Npn \__csl_read_entry_ids: {}
\cs_new:Npn \__csl_set_ref_section_level:
{
\str_case:Vn \l__csl_bib_ref_section_str
{
{ none } { }
{ part }
{ \__csl_patch_ref_section:n { part } }
{ chapter }
{ \__csl_patch_ref_section:n { chapter } }
{ chapter+ }
{
\__csl_patch_ref_section:n { part }
\__csl_patch_ref_section:n { chapter }
}
{ section }
{ \__csl_patch_ref_section:n { section } }
{ section+ }
{
\__csl_patch_ref_section:n { part }
\__csl_patch_ref_section:n { chapter }
\__csl_patch_ref_section:n { section }
}
{ subsection }
{ \__csl_patch_ref_section:n { subsection } }
{ subsection+ }
{
\__csl_patch_ref_section:n { part }
\__csl_patch_ref_section:n { chapter }
\__csl_patch_ref_section:n { section }
\__csl_patch_ref_section:n { subsection }
}
}
}
\cs_new:Npn \__csl_patch_ref_section:n #1
{
\hook_gput_code:nnn { cmd / #1 / before } { . } { \newrefsection }
}