texlive[64143] trunk: citation-style-language (19aug22)

commits+karl at tug.org commits+karl at tug.org
Fri Aug 19 01:08:31 CEST 2022


Revision: 64143
          http://tug.org/svn/texlive?view=revision&revision=64143
Author:   karl
Date:     2022-08-19 01:08:31 +0200 (Fri, 19 Aug 2022)
Log Message:
-----------
citation-style-language (19aug22)

Modified Paths:
--------------
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
    trunk/Build/source/texk/texlive/linked_scripts/citation-style-language/citeproc.lua
    trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
    trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md
    trunk/Master/texmf-dist/doc/latex/citation-style-language/README.md
    trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.pdf
    trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-choose.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-group.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-layout.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-locale.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-names.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-number.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-sort.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-style.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua
    trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty
    trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-GB.xml
    trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml
    trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-fr-FR.xml
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-chemical-society.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-sociological-association.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-author-date.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-fullnote-bibliography.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-note-bibliography.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/harvard-cite-them-right.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/ieee.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/modern-language-association.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/nature.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/vancouver.csl
    trunk/Master/tlpkg/libexec/ctan2tds
    trunk/Master/tlpkg/tlpsrc/citation-style-language.tlpsrc

Added Paths:
-----------
    trunk/Master/bin/aarch64-linux/citeproc-lua
    trunk/Master/bin/amd64-freebsd/citeproc-lua
    trunk/Master/bin/amd64-netbsd/citeproc-lua
    trunk/Master/bin/armhf-linux/citeproc-lua
    trunk/Master/bin/i386-cygwin/citeproc-lua
    trunk/Master/bin/i386-freebsd/citeproc-lua
    trunk/Master/bin/i386-linux/citeproc-lua
    trunk/Master/bin/i386-netbsd/citeproc-lua
    trunk/Master/bin/i386-solaris/citeproc-lua
    trunk/Master/bin/universal-darwin/citeproc-lua
    trunk/Master/bin/win32/citeproc-lua.exe
    trunk/Master/bin/x86_64-cygwin/citeproc-lua
    trunk/Master/bin/x86_64-darwinlegacy/citeproc-lua
    trunk/Master/bin/x86_64-linux/citeproc-lua
    trunk/Master/bin/x86_64-linuxmusl/citeproc-lua
    trunk/Master/bin/x86_64-solaris/citeproc-lua
    trunk/Master/texmf-dist/doc/latex/citation-style-language/DEPENDS.txt
    trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1
    trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib-data.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-lua.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua

Removed Paths:
-------------
    trunk/Master/texmf-dist/doc/man/man1/citeproc.1
    trunk/Master/texmf-dist/doc/man/man1/citeproc.man1.pdf
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-formats.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-richtext.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/csl-core.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/csl.lua
    trunk/Master/texmf-dist/source/latex/citation-style-language/
    trunk/Master/texmf-dist/tex/latex/citation-style-language/citeproc-bib-data.json

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2022-08-18 23:08:31 UTC (rev 64143)
@@ -115,6 +115,7 @@
 	cachepic/cachepic.tlu \
 	checkcites/checkcites.lua \
 	citation-style-language/citeproc.lua \
+	citation-style-language/citeproc-lua.lua \
 	chklref/chklref.pl \
 	cjk-gs-integrate/cjk-gs-integrate.pl \
 	clojure-pamphlet/pamphletangler \

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2022-08-18 23:08:31 UTC (rev 64143)
@@ -329,6 +329,7 @@
 	cachepic/cachepic.tlu \
 	checkcites/checkcites.lua \
 	citation-style-language/citeproc.lua \
+	citation-style-language/citeproc-lua.lua \
 	chklref/chklref.pl \
 	cjk-gs-integrate/cjk-gs-integrate.pl \
 	clojure-pamphlet/pamphletangler \

Modified: trunk/Build/source/texk/texlive/linked_scripts/citation-style-language/citeproc.lua
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/citation-style-language/citeproc.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Build/source/texk/texlive/linked_scripts/citation-style-language/citeproc.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -10,7 +10,7 @@
 local bib = require("citeproc-bib")
 local util = require("citeproc-util")
 
-citeproc.__VERSION__ = "0.1.1"
+citeproc.__VERSION__ = "0.2.0"
 
 citeproc.new = engine.CiteProc.new
 citeproc.parse_bib = bib.parse

Modified: trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2022-08-18 23:08:31 UTC (rev 64143)
@@ -54,6 +54,7 @@
 cachepic/cachepic.tlu
 checkcites/checkcites.lua
 citation-style-language/citeproc.lua
+citation-style-language/citeproc-lua.lua
 chklref/chklref.pl
 cjk-gs-integrate/cjk-gs-integrate.pl
 clojure-pamphlet/pamphletangler

Added: trunk/Master/bin/aarch64-linux/citeproc-lua
===================================================================
--- trunk/Master/bin/aarch64-linux/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/aarch64-linux/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/aarch64-linux/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-freebsd/citeproc-lua
===================================================================
--- trunk/Master/bin/amd64-freebsd/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/amd64-freebsd/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-freebsd/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-netbsd/citeproc-lua
===================================================================
--- trunk/Master/bin/amd64-netbsd/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/amd64-netbsd/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-netbsd/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/armhf-linux/citeproc-lua
===================================================================
--- trunk/Master/bin/armhf-linux/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/armhf-linux/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/armhf-linux/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-cygwin/citeproc-lua
===================================================================
--- trunk/Master/bin/i386-cygwin/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/i386-cygwin/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-cygwin/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-freebsd/citeproc-lua
===================================================================
--- trunk/Master/bin/i386-freebsd/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/i386-freebsd/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-freebsd/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-linux/citeproc-lua
===================================================================
--- trunk/Master/bin/i386-linux/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/i386-linux/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-linux/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-netbsd/citeproc-lua
===================================================================
--- trunk/Master/bin/i386-netbsd/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/i386-netbsd/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-netbsd/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-solaris/citeproc-lua
===================================================================
--- trunk/Master/bin/i386-solaris/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/i386-solaris/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-solaris/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/universal-darwin/citeproc-lua
===================================================================
--- trunk/Master/bin/universal-darwin/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/universal-darwin/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/universal-darwin/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/win32/citeproc-lua.exe
===================================================================
(Binary files differ)

Index: trunk/Master/bin/win32/citeproc-lua.exe
===================================================================
--- trunk/Master/bin/win32/citeproc-lua.exe	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/bin/win32/citeproc-lua.exe	2022-08-18 23:08:31 UTC (rev 64143)

Property changes on: trunk/Master/bin/win32/citeproc-lua.exe
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Added: trunk/Master/bin/x86_64-cygwin/citeproc-lua
===================================================================
--- trunk/Master/bin/x86_64-cygwin/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-cygwin/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwinlegacy/citeproc-lua
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwinlegacy/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linux/citeproc-lua
===================================================================
--- trunk/Master/bin/x86_64-linux/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/x86_64-linux/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linux/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linuxmusl/citeproc-lua
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linuxmusl/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-solaris/citeproc-lua
===================================================================
--- trunk/Master/bin/x86_64-solaris/citeproc-lua	                        (rev 0)
+++ trunk/Master/bin/x86_64-solaris/citeproc-lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/citation-style-language/citeproc-lua.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-solaris/citeproc-lua
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2022-08-18 23:08:31 UTC (rev 64143)
@@ -7,6 +7,25 @@
 
 ## [Unreleased]
 
+## [v0.2.0] - 2022-08-18
+
+### Added
+
+- The cite grouping, collapsing, and disambiguation features are now implemented.
+
+### Changed
+
+- The `citeproc` executable is renamed to `citeproc-lua` to avoid conflicts with other processor implementations.
+- Package configuration can also be given in the package loading options.
+- A warning is raised instead of and error in case of duplicate entry keys ([#14](https://github.com/zepinglee/citeproc-lua/issues/14)).
+
+### Fixed
+
+- Fix an infinite loop error when bib entry keys contain hyphens or underscores ([#18](https://github.com/zepinglee/citeproc-lua/issues/18)).
+- Fix incorrect item position in note style ([#20](https://github.com/zepinglee/citeproc-lua/issues/20)).
+- Fix compatibility with `\blockquote` of `csquotes` ([#21](https://github.com/zepinglee/citeproc-lua/issues/21)).
+- Fix non-lowercase field names ([#22](https://github.com/zepinglee/citeproc-lua/issues/22)).
+
 ## [v0.1.1] - 2022-03-21
 
 ### Added
@@ -27,6 +46,7 @@
 
 - Initial CTAN release.
 
-[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.1.1...HEAD
+[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.2.0...HEAD
+[v0.2.0]: https://github.com/zepinglee/citeproc-lua/compare/v0.1.1...0.2.0
 [v0.1.1]: https://github.com/zepinglee/citeproc-lua/compare/v0.1.0...v0.1.1
 [v0.1.0]: https://github.com/zepinglee/citeproc-lua/releases/tag/v0.1.0

Added: trunk/Master/texmf-dist/doc/latex/citation-style-language/DEPENDS.txt
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/DEPENDS.txt	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/DEPENDS.txt	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,10 @@
+# The format of this file is described in https://www.tug.org/texlive/pkgcontrib.html#deps.
+
+hard luatex
+hard l3kernel
+hard l3packages
+hard filehook
+hard url
+hard luaxml
+hard lua-uca
+hard lualibs


Property changes on: trunk/Master/texmf-dist/doc/latex/citation-style-language/DEPENDS.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/README.md	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/README.md	2022-08-18 23:08:31 UTC (rev 64143)
@@ -48,7 +48,7 @@
 
 ```bash
 pdflatex example.tex
-citeproc example.aux
+citeproc-lua example.aux
 pdflatex example.tex
 ```
 

Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex	2022-08-18 23:08:31 UTC (rev 64143)
@@ -49,7 +49,7 @@
   }%
 }
 
-\date{2022-03-21 v0.1.1}
+\date{2022-08-18 v0.2.0}
 
 \maketitle
 
@@ -73,7 +73,7 @@
 A LaTeX package (\file{citation-style-language.sty}) is provided to communicate with the processor.
 
 Note that this project is in early development stage and some features of CSL
-are not implemented yet (especially collapsing and disambiguation).
+are not implemented yet.
 Comments, suggestions, and bug reports are welcome.
 
 
@@ -131,14 +131,14 @@
   \end{syntax}
 \end{function}
 
-The behavior of the \pkg{citation-style-language} package is controlled by several key-value
-options which can be set with the \cs{cslsetup} command.
-For example,
+Package options may be set when the package is loaded or at any later stage
+with the \cs{cslsetup} command.
+These two methods are equivalent.
 \begin{LaTeXdemo}
-  \cslsetup{
-    style  = apa,
-    locale = zh-CN,
-  }
+  \usepackage[style = apa]{citation-style-langugage}
+  % OR
+  \usepackage{citation-style-langugage}
+  \cslsetup{style = apa}
 \end{LaTeXdemo}
 
 \DescribeOption{style}
@@ -278,13 +278,11 @@
 If only one optional argument is provided, it is treated as \meta{postnote}.
 The \meta{postnote} is used as a page locator if it consists of only digits.
 
-In other packages, several commands are provided for producing citations in
-different styles such as \cs{citet}, \cs{citep}, \cs{parencite}, and
-\cs{footnotecite}.
-In \pkg{citation-style-language} package, however, the format of citations is
-fixed as defined in CSL style and it is impossible to select another format
-without modifying the \file{.csl} style file.
-Note that narrative citation (like ``Doe (2018)'') will be supported in CSL 1.1.
+For author-date styles, there are also narrative in-text citations where the
+author names appear in running text and only dates are enclosed in parentheses
+(\cs{citet} in \pkg{natbib} pacakge or \cs{textcite} in \pkg{biblatex}).
+However such cite command in this pakcage is not available at the momment and
+it will be implemented in the next release.
 
 \begin{function}{\cites}
   \begin{syntax}
@@ -302,8 +300,19 @@
   \cites[prefix = {See }, page = 6]{key1}[section = 2.3]{key2}\relax [Text]
 \end{LaTeXdemo}
 
+\begin{function}{\nocite}
+  \begin{syntax}
+    \cs{nocite}\marg{keys}
+  \end{syntax}
+\end{function}
 
+This command produces no output but makes the entries included in the
+bibliography, which is the same in standard \LaTeX.
+If the special key |*| is given (\cs{notecite\{*\}}), all the entries in the
+database are included.
 
+
+
 \begin{function}{\printbibliography}
   \begin{syntax}
     \cs{printbibliography}\oarg{options}
@@ -368,16 +377,11 @@
 The \pkg{citation-style-language} package is in early development stage and there are some issues with it.
 
 \begin{itemize}
-  \item The \pkg{citeproc-lua} has not implemented all the features of CSL,
-    especially disambiguation and collapsing.
+  \item The \pkg{citeproc-lua} has not implemented all the features of CSL.
     For detailed information of the coverage on the CSL standard test
     suite\footnote{\url{https://github.com/citation-style-language/test-suite}},
     see \href{https://github.com/zepinglee/citeproc-lua/blob/main/test/citeproc-test.log}{citeproc-test.log}
     in the GitHub repository.
-  \item The processor is much slower than expected compared to other
-    reference engines.
-    This is because little care has been taken in the development so far.
-    Optimization is needed in the future.
   \item When used with \pkg{hyperref}, the citations are not correctly rendered
     as hyperlinks.
   \item The Unicode sorting method is provided by \pkg{lua-uca} package and

Added: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,26 @@
+.TH citeproc-lua 1 "0.1.1"
+.SH NAME
+citeproc-lua \- make CSL citations and bibliography for LaTeX
+.SH SYNOPSIS
+.B citeproc-lua
+.RB [<options>]
+.IR auxname [ \fB.aux\fP ]
+.SH DESCRIPTION
+The citeproc engine is a Citation Style Lanugage processor for LaTeX.
+It reads the auxiliary
+.RB ( .aux )
+file
+.I auxname
+and creates a bibliography
+.RB ( .bbl )
+file
+.SH OPTIONS
+.IP \-h ", " \-\-help
+Prints the usage and exits.
+.IP \-V ", " \-\-version
+Prints the version number and exits.
+.SH AUTHOR
+Zeping Lee <zepinglee at gmail.com>
+.PP
+Please log issues on the GitHub homepage:
+https://github.com/zepinglee/citeproc-lua/issues.


Property changes on: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf	2022-08-18 23:08:31 UTC (rev 64143)

Property changes on: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Deleted: trunk/Master/texmf-dist/doc/man/man1/citeproc.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/citeproc.1	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/doc/man/man1/citeproc.1	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,26 +0,0 @@
-.TH citeproc 1 "0.1.1"
-.SH NAME
-citeproc \- make CSL citations and bibliography for LaTeX
-.SH SYNOPSIS
-.B citeproc
-.RB [<options>]
-.IR auxname [ \fB.aux\fP ]
-.SH DESCRIPTION
-The citeproc engine is a Citation Style Lanugage processor for LaTeX.
-It reads the auxiliary
-.RB ( .aux )
-file
-.I auxname
-and creates a bibliography
-.RB ( .bbl )
-file
-.SH OPTIONS
-.IP \-h ", " \-\-help
-Prints the usage and exits.
-.IP \-V ", " \-\-version
-Prints the version number and exits.
-.SH AUTHOR
-Zeping Lee <zepinglee at gmail.com>
-.PP
-Please log issues on the GitHub homepage:
-https://github.com/zepinglee/citeproc-lua/issues.

Deleted: trunk/Master/texmf-dist/doc/man/man1/citeproc.man1.pdf
===================================================================
(Binary files differ)

Deleted: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,194 +0,0 @@
-#!/usr/bin/env texlua
-
---
--- Copyright (c) 2021-2022 Zeping Lee
--- Released under the MIT license.
--- Repository: https://github.com/zepinglee/citeproc-lua
---
-
-kpse.set_program_name("luatex")
-
-require("lualibs")
-local citeproc = require("citeproc")
-local util = require("citeproc-util")
-local core = require("csl-core")
-
-local function getopt( arg, options )
-  local tab = {}
-  for k, v in ipairs(arg) do
-    if string.sub( v, 1, 2) == "--" then
-      local x = string.find( v, "=", 1, true )
-      if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
-      else      tab[ string.sub( v, 3 ) ] = true
-      end
-    elseif string.sub( v, 1, 1 ) == "-" then
-      local y = 2
-      local l = string.len(v)
-      local jopt
-      while ( y <= l ) do
-        jopt = string.sub( v, y, y )
-        if string.find( options, jopt, 1, true ) then
-          if y < l then
-            tab[ jopt ] = string.sub( v, y+1 )
-            y = l
-          else
-            tab[ jopt ] = arg[ k + 1 ]
-          end
-        else
-          tab[ jopt ] = true
-        end
-        y = y + 1
-      end
-    else
-      if tab.file then
-        error(string.format('Invalid argument "%s"', v))
-      end
-      tab.file = v
-    end
-
-  end
-  return tab
-end
-
-
-local function print_version()
-  io.write(string.format("CiteProc-Lua %s\n", citeproc.__VERSION__))
-end
-
-
-local function print_help()
-  io.write("Usage: citeproc [options] auxname[.aux]\n")
-  io.write("Options:\n")
-  io.write("  -h, --help          Print this message and exit.\n")
-  io.write("  -V, --version       Print the version number and exit.\n")
-end
-
-
-local function convert_bib(path, output_path)
-  local contents = util.read_file(path)
-  local bib = citeproc.parse_bib(contents)
-  if not output_path then
-    output_path = string.gsub(path, "%.bib$", ".json")
-  end
-  local file = io.open(output_path, "w")
-  file:write(utilities.json.tojson(bib))
-  file:write('\n')
-  file:close()
-end
-
-
-
-local function read_aux_file(aux_file)
-  local bib_style = nil
-  local bib_files = {}
-  local citations = {}
-  local csl_options = {}
-
-  local file = io.open(aux_file, "r")
-  if not file then
-    error(string.format('Failed to open "%s"', aux_file))
-    return
-  end
-  for line in file:lines() do
-    local match
-    match = string.match(line, "^\\bibstyle%s*(%b{})")
-    if match then
-      bib_style = string.sub(match, 2, -2)
-    else
-      match = string.match(line, "^\\bibdata%s*(%b{})")
-      if match then
-        for _, bib in ipairs(util.split(string.sub(match, 2, -2), "%s*,%s*")) do
-          table.insert(bib_files, bib)
-        end
-      else
-        match = string.match(line, "^\\citation%s*(%b{})")
-        if match then
-          local citation = core.make_citation(string.sub(match, 2, -2))
-          table.insert(citations, citation)
-        else
-          match = string.match(line, "^\\csloptions%s*(%b{})")
-          if match then
-            for key, value in string.gmatch(match, "([%w-]+)=(%w+)") do
-              csl_options[key] = value
-            end
-          end
-        end
-      end
-    end
-  end
-  file:close()
-
-  return bib_style, bib_files, citations, csl_options
-end
-
-
-local function process_aux_file(aux_file)
-  if not util.endswith(aux_file, ".aux") then
-    aux_file = aux_file .. ".aux"
-  end
-
-  local style_name, bib_files, citations, csl_options = read_aux_file(aux_file)
-
-  local lang = csl_options.locale
-
-  local engine = core.init(style_name, bib_files, lang)
-  if csl_options.linking == "true" then
-    engine:enable_linking()
-  end
-  local style_class = engine:get_style_class()
-
-  local citation_strings = core.process_citations(engine, citations)
-
-  local output_string = ""
-
-  for _, citation in ipairs(citations) do
-    local citation_id = citation.citationID
-    if citation_id ~= "nocite" then
-      local citation_str = citation_strings[citation_id]
-      output_string = output_string .. string.format("\\cslcite{%s}{{%s}{%s}}\n", citation_id, style_class, citation_str)
-    end
-  end
-
-  output_string = output_string .. "\n"
-
-  local result = core.make_bibliography(engine)
-  output_string = output_string .. result
-
-  local output_path = string.gsub(aux_file, "%.aux$", ".bbl")
-  local bbl_file = io.open(output_path, "w")
-  bbl_file:write(output_string)
-  bbl_file:close()
-end
-
-
-local function main()
-  local args = getopt(arg, "o")
-
-  -- for k, v in pairs(args) do
-  --   print( k, v )
-  -- end
-
-  if args.V or args.version then
-    print_version()
-    return
-  elseif args.h or args.help then
-    print_help()
-    return
-  end
-
-  if not args.file then
-    error("citeproc: Need exactly one file argument.\n")
-  end
-
-  local path = args.file
-
-  local output_path = args.o or args.output
-  if util.endswith(path, ".bib") then
-    convert_bib(path, output_path)
-  else
-    process_aux_file(path)
-  end
-
-end
-
-main()

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib-data.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib-data.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib-data.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,1759 @@
+-- This file is generated from citeproc-bib-data.json by update-bib-date.py
+
+return {
+    ["description"] = "Bib CSL mapping",
+    ["types"] = {
+        ["archival"] = {
+            ["csl"] = "collection",
+        },
+        ["archive"] = {
+            ["csl"] = "collection",
+        },
+        ["article"] = {
+            ["csl"] = "article-journal",
+        },
+        ["artifactdataset"] = {
+            ["csl"] = "dataset",
+        },
+        ["artifactsoftware"] = {
+            ["csl"] = "software",
+        },
+        ["artwork"] = {
+            ["csl"] = "graphic",
+        },
+        ["atlas"] = {
+            ["csl"] = nil,
+        },
+        ["audio"] = {
+            ["csl"] = "song",
+        },
+        ["bachelor"] = {
+            ["csl"] = "thesis",
+        },
+        ["bibnote"] = {
+            ["csl"] = nil,
+        },
+        ["book"] = {
+            ["csl"] = "book",
+        },
+        ["bookinbook"] = {
+            ["csl"] = "chapter",
+        },
+        ["booklet"] = {
+            ["csl"] = "pamphlet",
+        },
+        ["brochure"] = {
+            ["csl"] = "pamphlet",
+        },
+        ["cconference"] = {
+            ["csl"] = nil,
+        },
+        ["collection"] = {
+            ["csl"] = "book",
+        },
+        ["comment"] = {
+            ["csl"] = nil,
+        },
+        ["commentary"] = {
+            ["csl"] = "book",
+        },
+        ["commented"] = {
+            ["csl"] = nil,
+        },
+        ["conference"] = {
+            ["csl"] = "paper-conference",
+        },
+        ["ctan"] = {
+            ["csl"] = "software",
+        },
+        ["customa"] = {
+            ["csl"] = nil,
+        },
+        ["customb"] = {
+            ["csl"] = nil,
+        },
+        ["customc"] = {
+            ["csl"] = nil,
+        },
+        ["customd"] = {
+            ["csl"] = nil,
+        },
+        ["custome"] = {
+            ["csl"] = nil,
+        },
+        ["customf"] = {
+            ["csl"] = nil,
+        },
+        ["database"] = {
+            ["csl"] = "dataset",
+        },
+        ["dataset"] = {
+            ["csl"] = "dataset",
+        },
+        ["dictionary"] = {
+            ["csl"] = "book",
+        },
+        ["docthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["electronic"] = {
+            ["csl"] = "webpage",
+        },
+        ["eulegislation"] = {
+            ["csl"] = "legislation",
+        },
+        ["footnote"] = {
+            ["csl"] = nil,
+        },
+        ["game"] = {
+            ["csl"] = "software",
+        },
+        ["govpub"] = {
+            ["csl"] = "regulation",
+        },
+        ["habthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["heading"] = {
+            ["csl"] = nil,
+        },
+        ["hidden"] = {
+            ["csl"] = nil,
+        },
+        ["image"] = {
+            ["csl"] = "graphic",
+        },
+        ["inbook"] = {
+            ["csl"] = "chapter",
+        },
+        ["incollection"] = {
+            ["csl"] = "chapter",
+        },
+        ["inloosecollection"] = {
+            ["csl"] = "chapter",
+        },
+        ["inpress"] = {
+            ["csl"] = "article",
+        },
+        ["inproceedings"] = {
+            ["csl"] = "paper-conference",
+        },
+        ["inreference"] = {
+            ["csl"] = "entry",
+        },
+        ["inserialcollection"] = {
+            ["csl"] = nil,
+        },
+        ["internet"] = {
+            ["csl"] = "webpage",
+        },
+        ["journalpart"] = {
+            ["csl"] = nil,
+        },
+        ["journals"] = {
+            ["csl"] = "periodical",
+        },
+        ["jurisdiction"] = {
+            ["csl"] = nil,
+        },
+        ["jurthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["legal"] = {
+            ["csl"] = "treaty",
+        },
+        ["legislation"] = {
+            ["csl"] = "legislation",
+        },
+        ["letter"] = {
+            ["csl"] = "personal_communication",
+        },
+        ["lexicon"] = {
+            ["csl"] = "book",
+        },
+        ["majorthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["manual"] = {
+            ["csl"] = "report",
+        },
+        ["map"] = {
+            ["csl"] = "map",
+        },
+        ["mastersthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["masterthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["minorthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["misc"] = {
+            ["csl"] = "document",
+        },
+        ["monograph"] = {
+            ["csl"] = "book",
+        },
+        ["monography"] = {
+            ["csl"] = "book",
+        },
+        ["movie"] = {
+            ["csl"] = "motion_picture",
+        },
+        ["music"] = {
+            ["csl"] = "song",
+        },
+        ["mvbook"] = {
+            ["csl"] = "book",
+        },
+        ["mvcollection"] = {
+            ["csl"] = "book",
+        },
+        ["mvproceedings"] = {
+            ["csl"] = "book",
+        },
+        ["mvreference"] = {
+            ["csl"] = "book",
+        },
+        ["news"] = {
+            ["csl"] = "article-newspaper",
+        },
+        ["newspaper"] = {
+            ["csl"] = "article-newspaper",
+        },
+        ["online"] = {
+            ["csl"] = "webpage",
+        },
+        ["other"] = {
+            ["csl"] = nil,
+        },
+        ["patent"] = {
+            ["csl"] = "patent",
+        },
+        ["performance"] = {
+            ["csl"] = "performance",
+        },
+        ["periodical"] = {
+            ["csl"] = "periodical",
+        },
+        ["phdthesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["preamble"] = {
+            ["csl"] = nil,
+        },
+        ["preprint"] = {
+            ["csl"] = "article",
+        },
+        ["presentation"] = {
+            ["csl"] = "speech",
+        },
+        ["proceedings"] = {
+            ["csl"] = "book",
+        },
+        ["program"] = {
+            ["csl"] = "software",
+        },
+        ["reference"] = {
+            ["csl"] = "book",
+        },
+        ["report"] = {
+            ["csl"] = "report",
+        },
+        ["repository"] = {
+            ["csl"] = nil,
+        },
+        ["review"] = {
+            ["csl"] = "review",
+        },
+        ["set"] = {
+            ["csl"] = nil,
+        },
+        ["setup"] = {
+            ["csl"] = nil,
+        },
+        ["software"] = {
+            ["csl"] = "software",
+        },
+        ["standard"] = {
+            ["csl"] = "standard",
+        },
+        ["string"] = {
+            ["csl"] = nil,
+        },
+        ["suppbook"] = {
+            ["csl"] = "chapter",
+        },
+        ["suppcollection"] = {
+            ["csl"] = "chapter",
+        },
+        ["suppperiodical"] = {
+            ["csl"] = "article",
+        },
+        ["techreport"] = {
+            ["csl"] = "report",
+        },
+        ["techstandard"] = {
+            ["csl"] = "standard",
+        },
+        ["thesis"] = {
+            ["csl"] = "thesis",
+        },
+        ["uklegislation"] = {
+            ["csl"] = "legislation",
+        },
+        ["unpublished"] = {
+            ["csl"] = "manuscript",
+        },
+        ["video"] = {
+            ["csl"] = "motion_picture",
+        },
+        ["webpage"] = {
+            ["csl"] = "webpage",
+        },
+        ["website"] = {
+            ["csl"] = nil,
+        },
+        ["www"] = {
+            ["csl"] = "webpage",
+        },
+        ["xdata"] = {
+            ["csl"] = nil,
+        },
+    },
+    ["fields"] = {
+        ["abstract"] = {
+            ["csl"] = "abstract",
+            ["type"] = "literal",
+        },
+        ["account"] = {
+            ["csl"] = nil,
+        },
+        ["acronym"] = {
+            ["csl"] = nil,
+        },
+        ["add"] = {
+            ["csl"] = nil,
+        },
+        ["add1"] = {
+            ["csl"] = nil,
+        },
+        ["addendum"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["address"] = {
+            ["csl"] = "publisher-place",
+            ["type"] = "literal",
+        },
+        ["adsurl"] = {
+            ["csl"] = nil,
+        },
+        ["advisor"] = {
+            ["csl"] = nil,
+        },
+        ["afterword"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["annotate"] = {
+            ["csl"] = "note",
+        },
+        ["annotation"] = {
+            ["csl"] = "note",
+            ["type"] = "literal",
+        },
+        ["annotator"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["annote"] = {
+            ["csl"] = "note",
+            ["type"] = "literal",
+        },
+        ["annotelanguage"] = {
+            ["csl"] = nil,
+        },
+        ["applicant"] = {
+            ["csl"] = nil,
+        },
+        ["archive"] = {
+            ["csl"] = "archive",
+        },
+        ["archiveprefix"] = {
+            ["csl"] = "archive",
+            ["type"] = "literal",
+        },
+        ["archname"] = {
+            ["csl"] = "archive",
+        },
+        ["articleno"] = {
+            ["csl"] = nil,
+        },
+        ["arxiv"] = {
+            ["csl"] = nil,
+        },
+        ["assignee"] = {
+            ["csl"] = nil,
+        },
+        ["author"] = {
+            ["csl"] = "author",
+            ["type"] = "name",
+        },
+        ["authorcountry"] = {
+            ["csl"] = nil,
+        },
+        ["authorfa"] = {
+            ["csl"] = nil,
+        },
+        ["authortype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["bibsource"] = {
+            ["csl"] = nil,
+        },
+        ["biburl"] = {
+            ["csl"] = nil,
+        },
+        ["binding"] = {
+            ["csl"] = nil,
+        },
+        ["birthday"] = {
+            ["csl"] = nil,
+        },
+        ["birthyear"] = {
+            ["csl"] = nil,
+        },
+        ["bookaddress"] = {
+            ["csl"] = nil,
+        },
+        ["bookauthor"] = {
+            ["csl"] = "container-author",
+            ["type"] = "name",
+        },
+        ["booklanguage"] = {
+            ["csl"] = nil,
+        },
+        ["bookpages"] = {
+            ["csl"] = nil,
+        },
+        ["bookpagination"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["booksubtitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["booktitle"] = {
+            ["csl"] = "container-title",
+            ["type"] = "literal",
+        },
+        ["booktitleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["caption"] = {
+            ["csl"] = nil,
+        },
+        ["caption1"] = {
+            ["csl"] = nil,
+        },
+        ["cartographer"] = {
+            ["csl"] = nil,
+        },
+        ["casenumber"] = {
+            ["csl"] = nil,
+        },
+        ["category"] = {
+            ["csl"] = nil,
+        },
+        ["cellular"] = {
+            ["csl"] = nil,
+        },
+        ["chair"] = {
+            ["csl"] = nil,
+        },
+        ["chapter"] = {
+            ["csl"] = "chapter-number",
+            ["type"] = "literal",
+        },
+        ["citedate"] = {
+            ["csl"] = nil,
+        },
+        ["city"] = {
+            ["csl"] = nil,
+        },
+        ["code"] = {
+            ["csl"] = nil,
+        },
+        ["coden"] = {
+            ["csl"] = nil,
+        },
+        ["collaboration"] = {
+            ["csl"] = nil,
+        },
+        ["collator"] = {
+            ["csl"] = nil,
+        },
+        ["commentator"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["commit"] = {
+            ["csl"] = nil,
+        },
+        ["compiler"] = {
+            ["csl"] = nil,
+        },
+        ["condition"] = {
+            ["csl"] = nil,
+        },
+        ["conference-location"] = {
+            ["csl"] = nil,
+        },
+        ["conference-number"] = {
+            ["csl"] = nil,
+        },
+        ["conference-year"] = {
+            ["csl"] = nil,
+        },
+        ["copy"] = {
+            ["csl"] = nil,
+        },
+        ["country"] = {
+            ["csl"] = nil,
+        },
+        ["credits"] = {
+            ["csl"] = nil,
+        },
+        ["crossref"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["ctrl-article-title"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-chapter-title"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-doi"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-etal-firstonly"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-etal-number"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-link-doi"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-use-doi-all"] = {
+            ["csl"] = nil,
+        },
+        ["ctrl-use-title"] = {
+            ["csl"] = nil,
+        },
+        ["dataset"] = {
+            ["csl"] = nil,
+        },
+        ["date"] = {
+            ["csl"] = "issued",
+            ["type"] = "date",
+        },
+        ["day"] = {
+            ["csl"] = nil,
+        },
+        ["dayfiled"] = {
+            ["csl"] = nil,
+        },
+        ["definition"] = {
+            ["csl"] = nil,
+        },
+        ["department"] = {
+            ["csl"] = nil,
+        },
+        ["description"] = {
+            ["csl"] = nil,
+        },
+        ["designator"] = {
+            ["csl"] = nil,
+        },
+        ["dimensions"] = {
+            ["csl"] = nil,
+        },
+        ["dissyear"] = {
+            ["csl"] = nil,
+        },
+        ["doi"] = {
+            ["csl"] = "DOI",
+            ["type"] = "verbatim",
+        },
+        ["dticnumber"] = {
+            ["csl"] = nil,
+        },
+        ["dummy"] = {
+            ["csl"] = nil,
+        },
+        ["edition"] = {
+            ["csl"] = "edition",
+            ["type"] = "literal",
+        },
+        ["editor"] = {
+            ["csl"] = "editor",
+            ["type"] = "name",
+        },
+        ["editora"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["editoratype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["editorb"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["editorbtype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["editorc"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["editorctype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["editortype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["eid"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["email"] = {
+            ["csl"] = nil,
+        },
+        ["endnumber"] = {
+            ["csl"] = nil,
+        },
+        ["endvolume"] = {
+            ["csl"] = nil,
+        },
+        ["endyear"] = {
+            ["csl"] = nil,
+        },
+        ["englishtitle"] = {
+            ["csl"] = nil,
+        },
+        ["entryset"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["entrysubtype"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["epilog"] = {
+            ["csl"] = nil,
+        },
+        ["eprint"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["eprintclass"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["eprints"] = {
+            ["csl"] = nil,
+        },
+        ["eprinttype"] = {
+            ["csl"] = "archive",
+            ["type"] = "literal",
+        },
+        ["erratumeid"] = {
+            ["csl"] = nil,
+        },
+        ["erratumgermanpages"] = {
+            ["csl"] = nil,
+        },
+        ["erratumnumpages"] = {
+            ["csl"] = nil,
+        },
+        ["erratumpages"] = {
+            ["csl"] = nil,
+        },
+        ["erratumvolume"] = {
+            ["csl"] = nil,
+        },
+        ["erratumyear"] = {
+            ["csl"] = nil,
+        },
+        ["eventdate"] = {
+            ["csl"] = "event-date",
+            ["type"] = "date",
+        },
+        ["eventtitle"] = {
+            ["csl"] = "event-title",
+            ["type"] = "literal",
+        },
+        ["eventtitleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["eventyear"] = {
+            ["csl"] = nil,
+        },
+        ["execute"] = {
+            ["csl"] = nil,
+            ["type"] = "code",
+        },
+        ["faddress"] = {
+            ["csl"] = nil,
+        },
+        ["fakeset"] = {
+            ["csl"] = nil,
+        },
+        ["fax"] = {
+            ["csl"] = nil,
+        },
+        ["file"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["firstkey"] = {
+            ["csl"] = nil,
+        },
+        ["fjournal"] = {
+            ["csl"] = nil,
+        },
+        ["flanguage"] = {
+            ["csl"] = nil,
+        },
+        ["font"] = {
+            ["csl"] = nil,
+        },
+        ["foreword"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["founder"] = {
+            ["csl"] = nil,
+        },
+        ["fpublisher"] = {
+            ["csl"] = nil,
+        },
+        ["ftitle"] = {
+            ["csl"] = nil,
+        },
+        ["furtherresp"] = {
+            ["csl"] = nil,
+        },
+        ["fyear"] = {
+            ["csl"] = nil,
+        },
+        ["gender"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["germanpages"] = {
+            ["csl"] = nil,
+        },
+        ["group"] = {
+            ["csl"] = nil,
+        },
+        ["heading"] = {
+            ["csl"] = nil,
+        },
+        ["hereafter"] = {
+            ["csl"] = nil,
+        },
+        ["holder"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["howcited"] = {
+            ["csl"] = nil,
+        },
+        ["howpublished"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["hyphenation"] = {
+            ["csl"] = "language",
+            ["type"] = "literal",
+        },
+        ["ids"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["illustrated"] = {
+            ["csl"] = nil,
+        },
+        ["illustrations"] = {
+            ["csl"] = nil,
+        },
+        ["illustrator"] = {
+            ["csl"] = nil,
+        },
+        ["indexsorttitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["indextitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["institution"] = {
+            ["csl"] = "publisher",
+            ["type"] = "literal",
+        },
+        ["introduction"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["intype"] = {
+            ["csl"] = nil,
+        },
+        ["inventor"] = {
+            ["csl"] = nil,
+        },
+        ["ipc"] = {
+            ["csl"] = nil,
+        },
+        ["isan"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["isbn"] = {
+            ["csl"] = "ISBN",
+            ["type"] = "literal",
+        },
+        ["ismn"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["iso-abbreviation"] = {
+            ["csl"] = nil,
+        },
+        ["iso-author-punctuation"] = {
+            ["csl"] = nil,
+        },
+        ["iso-date-place"] = {
+            ["csl"] = nil,
+        },
+        ["isrn"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["issn"] = {
+            ["csl"] = "ISSN",
+            ["type"] = "literal",
+        },
+        ["issue"] = {
+            ["csl"] = "issue",
+            ["type"] = "literal",
+        },
+        ["issuesubtitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["issuetitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["issuetitleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["iswc"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["jfmnumber"] = {
+            ["csl"] = nil,
+        },
+        ["journal"] = {
+            ["csl"] = "container-title",
+            ["type"] = "literal",
+        },
+        ["journalsubtitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["journaltie"] = {
+            ["csl"] = nil,
+        },
+        ["journaltitle"] = {
+            ["csl"] = "container-title",
+            ["type"] = "literal",
+        },
+        ["journaltitleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["juraauthor"] = {
+            ["csl"] = nil,
+        },
+        ["juratitle"] = {
+            ["csl"] = nil,
+        },
+        ["key"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["keywords"] = {
+            ["csl"] = nil,
+            ["type"] = "keyword",
+        },
+        ["label"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["lang"] = {
+            ["csl"] = nil,
+        },
+        ["langid"] = {
+            ["csl"] = "language",
+            ["type"] = "literal",
+        },
+        ["langidopts"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["language"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["lastaccessed"] = {
+            ["csl"] = "accessed",
+        },
+        ["lastchecked"] = {
+            ["csl"] = "accessed",
+        },
+        ["lccn"] = {
+            ["csl"] = nil,
+        },
+        ["library"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["lista"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["listb"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["listc"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["listd"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["liste"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["listf"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["littype"] = {
+            ["csl"] = nil,
+        },
+        ["location"] = {
+            ["csl"] = "publisher-place",
+            ["type"] = "literal",
+        },
+        ["madadurltest"] = {
+            ["csl"] = nil,
+        },
+        ["main"] = {
+            ["csl"] = nil,
+        },
+        ["main1"] = {
+            ["csl"] = nil,
+        },
+        ["mainsubtitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["maintitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["maintitleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["majorcode"] = {
+            ["csl"] = nil,
+        },
+        ["marginnote"] = {
+            ["csl"] = nil,
+        },
+        ["mark"] = {
+            ["csl"] = nil,
+        },
+        ["max.best.papers"] = {
+            ["csl"] = nil,
+        },
+        ["mcitetail"] = {
+            ["csl"] = nil,
+        },
+        ["media"] = {
+            ["csl"] = "medium",
+        },
+        ["medium"] = {
+            ["csl"] = "medium",
+        },
+        ["meeting"] = {
+            ["csl"] = nil,
+        },
+        ["misctitle"] = {
+            ["csl"] = nil,
+        },
+        ["miscyear"] = {
+            ["csl"] = nil,
+        },
+        ["mobile"] = {
+            ["csl"] = nil,
+        },
+        ["modifydate"] = {
+            ["csl"] = nil,
+        },
+        ["month"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["monthfiled"] = {
+            ["csl"] = nil,
+        },
+        ["mrnumber"] = {
+            ["csl"] = nil,
+        },
+        ["name"] = {
+            ["csl"] = nil,
+        },
+        ["namea"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["nameaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["nameatype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["nameb"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["namebtype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["namec"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["namectype"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["nationality"] = {
+            ["csl"] = nil,
+        },
+        ["nbirthday"] = {
+            ["csl"] = nil,
+        },
+        ["newpage"] = {
+            ["csl"] = nil,
+        },
+        ["newspaper"] = {
+            ["csl"] = "container-title",
+        },
+        ["nickname"] = {
+            ["csl"] = nil,
+        },
+        ["nihms"] = {
+            ["csl"] = nil,
+        },
+        ["noed"] = {
+            ["csl"] = nil,
+        },
+        ["nolink"] = {
+            ["csl"] = nil,
+        },
+        ["normal"] = {
+            ["csl"] = nil,
+        },
+        ["normalauthor"] = {
+            ["csl"] = nil,
+        },
+        ["normaleditor"] = {
+            ["csl"] = nil,
+        },
+        ["note"] = {
+            ["csl"] = "note",
+            ["type"] = "literal",
+        },
+        ["nowarning"] = {
+            ["csl"] = nil,
+        },
+        ["number"] = {
+            ["csl"] = "number",
+            ["type"] = "literal",
+        },
+        ["numpages"] = {
+            ["csl"] = nil,
+        },
+        ["oaddress"] = {
+            ["csl"] = nil,
+        },
+        ["options"] = {
+            ["csl"] = nil,
+            ["type"] = "option",
+        },
+        ["opublisher"] = {
+            ["csl"] = nil,
+        },
+        ["org-short"] = {
+            ["csl"] = nil,
+        },
+        ["organization"] = {
+            ["csl"] = "publisher",
+            ["type"] = "literal",
+        },
+        ["origdate"] = {
+            ["csl"] = "original-date",
+            ["type"] = "date",
+        },
+        ["originaladdress"] = {
+            ["csl"] = "original-publisher-place",
+        },
+        ["originalbooktitle"] = {
+            ["csl"] = nil,
+        },
+        ["originaledition"] = {
+            ["csl"] = nil,
+        },
+        ["originaleditor"] = {
+            ["csl"] = nil,
+        },
+        ["originaljournal"] = {
+            ["csl"] = nil,
+        },
+        ["originalnumber"] = {
+            ["csl"] = nil,
+        },
+        ["originalpages"] = {
+            ["csl"] = nil,
+        },
+        ["originalpublisher"] = {
+            ["csl"] = "original-publisher",
+        },
+        ["originalvolume"] = {
+            ["csl"] = nil,
+        },
+        ["originalyear"] = {
+            ["csl"] = "original-date",
+        },
+        ["origlanguage"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["origlocation"] = {
+            ["csl"] = "original-publisher-place",
+            ["type"] = "literal",
+        },
+        ["origpublisher"] = {
+            ["csl"] = "original-publisher",
+            ["type"] = "literal",
+        },
+        ["origtitle"] = {
+            ["csl"] = "original-title",
+            ["type"] = "literal",
+        },
+        ["oyear"] = {
+            ["csl"] = nil,
+        },
+        ["pagename"] = {
+            ["csl"] = nil,
+        },
+        ["pages"] = {
+            ["csl"] = "page",
+            ["type"] = "range",
+        },
+        ["pagetotal"] = {
+            ["csl"] = "number-of-pages",
+            ["type"] = "literal",
+        },
+        ["pagination"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["paper"] = {
+            ["csl"] = nil,
+        },
+        ["part"] = {
+            ["csl"] = "part",
+            ["type"] = "literal",
+        },
+        ["patentid"] = {
+            ["csl"] = nil,
+        },
+        ["pdf"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["phone"] = {
+            ["csl"] = nil,
+        },
+        ["pid"] = {
+            ["csl"] = nil,
+        },
+        ["pii"] = {
+            ["csl"] = nil,
+        },
+        ["pmcid"] = {
+            ["csl"] = "PMCID",
+        },
+        ["prebibitem"] = {
+            ["csl"] = nil,
+        },
+        ["preface"] = {
+            ["csl"] = nil,
+        },
+        ["preprint"] = {
+            ["csl"] = nil,
+        },
+        ["presort"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["price"] = {
+            ["csl"] = nil,
+        },
+        ["primaryclass"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["printing"] = {
+            ["csl"] = nil,
+        },
+        ["prioritycountry"] = {
+            ["csl"] = nil,
+        },
+        ["prioritydate"] = {
+            ["csl"] = nil,
+        },
+        ["prioritynumber"] = {
+            ["csl"] = nil,
+        },
+        ["pseudonym"] = {
+            ["csl"] = nil,
+        },
+        ["publication"] = {
+            ["csl"] = nil,
+        },
+        ["publicationdate"] = {
+            ["csl"] = nil,
+        },
+        ["publisher"] = {
+            ["csl"] = "publisher",
+            ["type"] = "literal",
+        },
+        ["pubmed"] = {
+            ["csl"] = "PMID",
+        },
+        ["pubstate"] = {
+            ["csl"] = nil,
+            ["type"] = "key",
+        },
+        ["related"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["relatedoptions"] = {
+            ["csl"] = nil,
+            ["type"] = "option",
+        },
+        ["relatedstring"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["relatedtype"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["reprinted-from"] = {
+            ["csl"] = nil,
+        },
+        ["reprinted-text"] = {
+            ["csl"] = nil,
+        },
+        ["reprinttitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["requestdate"] = {
+            ["csl"] = nil,
+        },
+        ["requestnumber"] = {
+            ["csl"] = nil,
+        },
+        ["responsible"] = {
+            ["csl"] = nil,
+        },
+        ["review"] = {
+            ["csl"] = nil,
+        },
+        ["revision"] = {
+            ["csl"] = nil,
+        },
+        ["school"] = {
+            ["csl"] = "publisher",
+            ["type"] = "literal",
+        },
+        ["score"] = {
+            ["csl"] = nil,
+        },
+        ["section"] = {
+            ["csl"] = nil,
+        },
+        ["series"] = {
+            ["csl"] = "collection-title",
+            ["type"] = "literal",
+        },
+        ["seriesedition"] = {
+            ["csl"] = nil,
+        },
+        ["short"] = {
+            ["csl"] = nil,
+        },
+        ["shortarchive"] = {
+            ["csl"] = nil,
+        },
+        ["shortauthor"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["shorteditor"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["shorthand"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["shorthandintro"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["shortjournal"] = {
+            ["csl"] = "container-title-short",
+            ["type"] = "literal",
+        },
+        ["shortseries"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["shortsubarchive"] = {
+            ["csl"] = nil,
+        },
+        ["shorttitle"] = {
+            ["csl"] = "title-short",
+            ["type"] = "literal",
+        },
+        ["shorturl"] = {
+            ["csl"] = nil,
+        },
+        ["sig1"] = {
+            ["csl"] = nil,
+        },
+        ["sig2"] = {
+            ["csl"] = nil,
+        },
+        ["sig3"] = {
+            ["csl"] = nil,
+        },
+        ["sig4"] = {
+            ["csl"] = nil,
+        },
+        ["size"] = {
+            ["csl"] = nil,
+        },
+        ["slaccitation"] = {
+            ["csl"] = nil,
+        },
+        ["sort-short"] = {
+            ["csl"] = nil,
+        },
+        ["sort-word"] = {
+            ["csl"] = nil,
+        },
+        ["sortas"] = {
+            ["csl"] = nil,
+        },
+        ["sortkey"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["sortname"] = {
+            ["csl"] = nil,
+            ["type"] = "name",
+        },
+        ["sortshorthand"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["sorttitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["sortyear"] = {
+            ["csl"] = nil,
+            ["type"] = "integer",
+        },
+        ["source1"] = {
+            ["csl"] = nil,
+        },
+        ["specialitycode"] = {
+            ["csl"] = nil,
+        },
+        ["ssedition"] = {
+            ["csl"] = nil,
+        },
+        ["standard"] = {
+            ["csl"] = nil,
+        },
+        ["startnumber"] = {
+            ["csl"] = nil,
+        },
+        ["startvolume"] = {
+            ["csl"] = nil,
+        },
+        ["startyear"] = {
+            ["csl"] = nil,
+        },
+        ["state"] = {
+            ["csl"] = nil,
+        },
+        ["status"] = {
+            ["csl"] = nil,
+        },
+        ["stdcode"] = {
+            ["csl"] = nil,
+        },
+        ["stitle"] = {
+            ["csl"] = nil,
+        },
+        ["street"] = {
+            ["csl"] = nil,
+        },
+        ["subarchive"] = {
+            ["csl"] = nil,
+        },
+        ["subtitle"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["symposium"] = {
+            ["csl"] = nil,
+        },
+        ["text"] = {
+            ["csl"] = nil,
+        },
+        ["timestamp"] = {
+            ["csl"] = nil,
+        },
+        ["title"] = {
+            ["csl"] = "title",
+            ["type"] = "literal",
+        },
+        ["titleaddon"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["titlenote"] = {
+            ["csl"] = nil,
+        },
+        ["totalpages"] = {
+            ["csl"] = nil,
+        },
+        ["transissue"] = {
+            ["csl"] = nil,
+        },
+        ["transjournal"] = {
+            ["csl"] = nil,
+        },
+        ["translation"] = {
+            ["csl"] = nil,
+        },
+        ["translator"] = {
+            ["csl"] = "translator",
+            ["type"] = "name",
+        },
+        ["transnumber"] = {
+            ["csl"] = nil,
+        },
+        ["transpages"] = {
+            ["csl"] = nil,
+        },
+        ["transsection"] = {
+            ["csl"] = nil,
+        },
+        ["transvolume"] = {
+            ["csl"] = nil,
+        },
+        ["transyear"] = {
+            ["csl"] = nil,
+        },
+        ["type"] = {
+            ["csl"] = "genre",
+            ["type"] = "key",
+        },
+        ["typeoflit"] = {
+            ["csl"] = nil,
+        },
+        ["umfnumber"] = {
+            ["csl"] = nil,
+        },
+        ["updated"] = {
+            ["csl"] = nil,
+        },
+        ["url"] = {
+            ["csl"] = "URL",
+            ["type"] = "uri",
+        },
+        ["urlaccessdate"] = {
+            ["csl"] = "accessed",
+        },
+        ["urldate"] = {
+            ["csl"] = "accessed",
+            ["type"] = "date",
+        },
+        ["urlnewline"] = {
+            ["csl"] = nil,
+        },
+        ["urltype"] = {
+            ["csl"] = nil,
+        },
+        ["urlyear"] = {
+            ["csl"] = nil,
+        },
+        ["urn"] = {
+            ["csl"] = nil,
+        },
+        ["usera"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["userb"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["userc"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["userd"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["usere"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["userf"] = {
+            ["csl"] = nil,
+            ["type"] = "literal",
+        },
+        ["value"] = {
+            ["csl"] = nil,
+        },
+        ["venue"] = {
+            ["csl"] = "event-place",
+            ["type"] = "literal",
+        },
+        ["verba"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["verbb"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["verbc"] = {
+            ["csl"] = nil,
+            ["type"] = "verbatim",
+        },
+        ["version"] = {
+            ["csl"] = "version",
+            ["type"] = "literal",
+        },
+        ["versiontype"] = {
+            ["csl"] = nil,
+        },
+        ["volformat"] = {
+            ["csl"] = nil,
+        },
+        ["volume"] = {
+            ["csl"] = "volume",
+            ["type"] = "integer",
+        },
+        ["volumes"] = {
+            ["csl"] = "number-of-volumes",
+            ["type"] = "integer",
+        },
+        ["volumetitle"] = {
+            ["csl"] = nil,
+        },
+        ["word"] = {
+            ["csl"] = nil,
+        },
+        ["xdata"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["xid"] = {
+            ["csl"] = nil,
+        },
+        ["xref"] = {
+            ["csl"] = nil,
+            ["type"] = "entrykey",
+        },
+        ["year"] = {
+            ["csl"] = nil,
+            ["type"] = "date",
+        },
+        ["year-presented"] = {
+            ["csl"] = nil,
+        },
+        ["yearcomp"] = {
+            ["csl"] = nil,
+        },
+        ["yearfiled"] = {
+            ["csl"] = nil,
+        },
+        ["zblnumber"] = {
+            ["csl"] = nil,
+        },
+        ["zip"] = {
+            ["csl"] = nil,
+        },
+    },
+    ["macros"] = {
+        ["jan"] = {
+            ["value"] = "1",
+        },
+        ["feb"] = {
+            ["value"] = "2",
+        },
+        ["mar"] = {
+            ["value"] = "3",
+        },
+        ["apr"] = {
+            ["value"] = "4",
+        },
+        ["may"] = {
+            ["value"] = "5",
+        },
+        ["jun"] = {
+            ["value"] = "6",
+        },
+        ["jul"] = {
+            ["value"] = "7",
+        },
+        ["aug"] = {
+            ["value"] = "8",
+        },
+        ["sep"] = {
+            ["value"] = "9",
+        },
+        ["oct"] = {
+            ["value"] = "10",
+        },
+        ["nov"] = {
+            ["value"] = "11",
+        },
+        ["dec"] = {
+            ["value"] = "12",
+        },
+        ["acmcs"] = {
+            ["value"] = "ACM Computing Surveys",
+        },
+        ["acta"] = {
+            ["value"] = "Acta Informatica",
+        },
+        ["cacm"] = {
+            ["value"] = "Communications of the ACM",
+        },
+        ["ibmjrd"] = {
+            ["value"] = "IBM Journal of Research and Development",
+        },
+        ["ibmsj"] = {
+            ["value"] = "IBM Systems Journal",
+        },
+        ["ieeese"] = {
+            ["value"] = "IEEE Transactions on Software Engineering",
+        },
+        ["ieeetc"] = {
+            ["value"] = "IEEE Transactions on Computers",
+        },
+        ["ieeetcad"] = {
+            ["value"] = "IEEE Transactions on Computer-Aided Design of Integrated Circuits",
+        },
+        ["ipl"] = {
+            ["value"] = "Information Processing Letters",
+        },
+        ["jacm"] = {
+            ["value"] = "Journal of the ACM",
+        },
+        ["jcss"] = {
+            ["value"] = "Journal of Computer and System Sciences",
+        },
+        ["scp"] = {
+            ["value"] = "Science of Computer Programming",
+        },
+        ["sicomp"] = {
+            ["value"] = "SIAM Journal on Computing",
+        },
+        ["tocs"] = {
+            ["value"] = "ACM Transactions on Computer Systems",
+        },
+        ["tods"] = {
+            ["value"] = "ACM Transactions on Database Systems",
+        },
+        ["tog"] = {
+            ["value"] = "ACM Transactions on Graphics",
+        },
+        ["toms"] = {
+            ["value"] = "ACM Transactions on Mathematical Software",
+        },
+        ["toois"] = {
+            ["value"] = "ACM Transactions on Office Information Systems",
+        },
+        ["toplas"] = {
+            ["value"] = "ACM Transactions on Programming Languages and Systems",
+        },
+        ["tcs"] = {
+            ["value"] = "Theoretical Computer Science",
+        },
+    },
+}
\ No newline at end of file


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib-data.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bib.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -16,24 +16,12 @@
 
 local bib = {}
 
-require("lualibs")
 local unicode = require("unicode")
+bib.bib_data = require("citeproc-bib-data")
 
 local util = require("citeproc-util")
 
 
-local path = "citeproc-bib-data.json"
-if kpse then
-  path = kpse.find_file(path)
-end
-if path then
-  local contents = util.read_file(path)
-  if not contents then
-    error(string.format('Failed to find "%s"', path))
-  end
-  bib.bib_data = utilities.json.tolua(contents)
-end
-
 function bib.parse(contents)
   local items = {}
   for item_contents in string.gmatch(contents, "(@%w+%b{})") do
@@ -80,9 +68,9 @@
 function bib.parse_fields(contents)
   local fields = {}
   local field_patterns = {
-    "^(%w+)%s*=%s*(%b{}),?%s*(.-)$",
-    '^(%w+)%s*=%s*"([^"]*)",?%s*(.-)$',
-    "^(%w+)%s*=%s*(%w+),?%s*(.-)$",
+    "^([^,}%s]+)%s*=%s*(%b{}),?%s*(.-)$",
+    '^([^,}%s]+)%s*=%s*"([^"]*)",?%s*(.-)$',
+    "^([^,}%s]+)%s*=%s*(%w+),?%s*(.-)$",
   }
 
   while #contents > 0 do
@@ -91,6 +79,7 @@
     for pattern_index, pattern in ipairs(field_patterns) do
       field, value, rest = string.match(contents, pattern)
       if value then
+        field = string.lower(field)
         if pattern_index == 1 then
           -- Strip braces "{}"
           value = string.sub(value, 2, -2)
@@ -106,10 +95,14 @@
           end
         end
         fields[field] = value
-        contents = rest
         break
       end
     end
+    if value then
+      contents = rest
+    else
+      break
+    end
   end
   return fields
 end

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,196 @@
+
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local cli = {}
+
+
+require("lualibs")
+local citeproc = require("citeproc")
+local util = require("citeproc-util")
+local core = require("citeproc-latex-core")
+
+
+local function getopt( arg, options )
+  local tab = {}
+  for k, v in ipairs(arg) do
+    if string.sub( v, 1, 2) == "--" then
+      local x = string.find( v, "=", 1, true )
+      if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )
+      else      tab[ string.sub( v, 3 ) ] = true
+      end
+    elseif string.sub( v, 1, 1 ) == "-" then
+      local y = 2
+      local l = string.len(v)
+      local jopt
+      while ( y <= l ) do
+        jopt = string.sub( v, y, y )
+        if string.find( options, jopt, 1, true ) then
+          if y < l then
+            tab[ jopt ] = string.sub( v, y+1 )
+            y = l
+          else
+            tab[ jopt ] = arg[ k + 1 ]
+          end
+        else
+          tab[ jopt ] = true
+        end
+        y = y + 1
+      end
+    else
+      if tab.file then
+        error(string.format('Invalid argument "%s"', v))
+      end
+      tab.file = v
+    end
+
+  end
+  return tab
+end
+
+
+local function print_version()
+  io.write(string.format("citeproc-lua %s\n", citeproc.__VERSION__))
+end
+
+
+local function print_help()
+  io.write("Usage: citeproc-lua [options] auxname[.aux]\n")
+  io.write("Options:\n")
+  io.write("  -h, --help          Print this message and exit.\n")
+  io.write("  -V, --version       Print the version number and exit.\n")
+end
+
+
+local function convert_bib(path, output_path)
+  local contents = util.read_file(path)
+  local bib = citeproc.parse_bib(contents)
+  if not output_path then
+    output_path = string.gsub(path, "%.bib$", ".json")
+  end
+  local file = io.open(output_path, "w")
+  file:write(utilities.json.tojson(bib))
+  file:write('\n')
+  file:close()
+end
+
+
+
+local function read_aux_file(aux_file)
+  local bib_style = nil
+  local bib_files = {}
+  local citations = {}
+  local csl_options = {}
+
+  local file = io.open(aux_file, "r")
+  if not file then
+    error(string.format('Failed to open "%s"', aux_file))
+    return
+  end
+  for line in file:lines() do
+    local match
+    match = string.match(line, "^\\bibstyle%s*(%b{})")
+    if match then
+      bib_style = string.sub(match, 2, -2)
+    else
+      match = string.match(line, "^\\bibdata%s*(%b{})")
+      if match then
+        for _, bib in ipairs(util.split(string.sub(match, 2, -2), "%s*,%s*")) do
+          table.insert(bib_files, bib)
+        end
+      else
+        match = string.match(line, "^\\citation%s*(%b{})")
+        if match then
+          local citation = core.make_citation(string.sub(match, 2, -2))
+          table.insert(citations, citation)
+        else
+          match = string.match(line, "^\\csloptions%s*(%b{})")
+          if match then
+            for key, value in string.gmatch(match, "([%w-]+)=(%w+)") do
+              csl_options[key] = value
+            end
+          end
+        end
+      end
+    end
+  end
+  file:close()
+
+  return bib_style, bib_files, citations, csl_options
+end
+
+
+local function process_aux_file(aux_file)
+  if not util.endswith(aux_file, ".aux") then
+    aux_file = aux_file .. ".aux"
+  end
+
+  local style_name, bib_files, citations, csl_options = read_aux_file(aux_file)
+
+  local lang = csl_options.locale
+
+  local engine = core.init(style_name, bib_files, lang)
+  if csl_options.linking == "true" then
+    engine:enable_linking()
+  end
+  local style_class = engine:get_style_class()
+
+  local citation_strings = core.process_citations(engine, citations)
+
+  local output_string = ""
+
+  for _, citation in ipairs(citations) do
+    local citation_id = citation.citationID
+    if citation_id ~= "@nocite" then
+      local citation_str = citation_strings[citation_id]
+      output_string = output_string .. string.format("\\cslcite{%s}{{%s}{%s}}\n", citation_id, style_class, citation_str)
+    end
+  end
+
+  output_string = output_string .. "\n"
+
+  local result = core.make_bibliography(engine)
+  output_string = output_string .. result
+
+  local output_path = string.gsub(aux_file, "%.aux$", ".bbl")
+  local bbl_file = io.open(output_path, "w")
+  bbl_file:write(output_string)
+  bbl_file:close()
+end
+
+
+function cli.main()
+  local args = getopt(arg, "o")
+
+  -- for k, v in pairs(args) do
+  --   print( k, v )
+  -- end
+
+  if args.V or args.version then
+    print_version()
+    return
+  elseif args.h or args.help then
+    print_help()
+    return
+  end
+
+  if not args.file then
+    error("citeproc: Need exactly one file argument.\n")
+  end
+
+  local path = args.file
+
+  local output_path = args.o or args.output
+  if util.endswith(path, ".bib") then
+    convert_bib(path, output_path)
+  else
+    process_aux_file(path)
+  end
+
+end
+
+
+return cli


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,335 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local context = {}
+
+local LocalizedQuotes = require("citeproc-output").LocalizedQuotes
+
+local util = require("citeproc-util")
+
+
+local Context = {
+  reference = nil,
+  format = nil,
+  cite_id = nil,
+  style = nil,
+  locale = nil,
+  name_citation = nil,
+  names_delimiter = nil,
+
+  position = nil,
+
+  disamb_pass = nil,
+
+  cite = nil,
+  bib_number = nil,
+
+  in_bibliography = false,
+  sort_key = nil,
+
+  year_suffix = nil,
+}
+
+function Context:new()
+  local o = {
+    in_bibliography = false
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function Context:get_variable(name, form)
+  local variable_type = util.variable_types[name]
+  if variable_type == "number" then
+    return self:get_number(name)
+  -- elseif variable_type == "date" then
+  --   return self:get_date(name)
+  elseif variable_type == "name" then
+    return self:get_name(name)
+  else
+    return self:get_ordinary(name, form)
+  end
+end
+
+function Context:get_number(name)
+  if name == "locator" then
+    return self.cite.locator
+  elseif name == "citation-number" then
+    -- return self.bib_number
+    return self.reference["citation-number"]
+  elseif name == "first-reference-note-number" then
+    return self.cite["first-reference-note-number"]
+  elseif name == "page-first" then
+    return self.page_first(self.reference.page)
+  else
+    return self.reference[name]
+  end
+end
+
+function Context:get_ordinary(name, form)
+  local res = nil
+  local variable_name = name
+  if form and form ~= "long" then
+    variable_name = variable_name .. "-" .. form
+  end
+
+  if variable_name == "locator" or variable_name == "label" then
+    res = self.cite[variable_name]
+  else
+    res = self.reference[variable_name]
+  end
+  if res then
+    return res
+  end
+
+  if variable_name == "container-title-short" then
+    res = self.reference["journalAbbreviation"]
+    if res then
+      return res
+    end
+  end
+
+  if form then
+    res = self.reference[name]
+    if res then
+      return res
+    end
+  end
+
+  -- if name == "title-short" or name == "container-title-short" then
+  --   variable_name = string.gsub(name, "%-short$", "")
+  --   res = self.reference[variable_name]
+  -- end
+
+  return res
+end
+
+-- TODO: optimize: process only once
+-- TODO: organize the name parsing code
+function Context:get_name(variable_name)
+  local names = self.reference[variable_name]
+  if names then
+    for _, name in ipairs(names) do
+      if name.family == "" then
+        name.family = nil
+      end
+      if name.given == "" then
+        name.given = nil
+      end
+      if name.given and not name.family then
+        name.family = name.given
+        name.given = nil
+      end
+      self:parse_name_suffix(name)
+      self:split_ndp_family(name)
+      -- self:split_given_ndp(name)
+      self:split_given_dp(name)
+    end
+  end
+  return names
+end
+
+function Context:parse_name_suffix(name)
+  if not name.suffix and name.family and string.match(name.family, ",") then
+    local words = util.split(name.family, ",%s*")
+    name.suffix = words[#words]
+    name.family = table.concat(util.slice(words, 1, -2), ", ")
+  end
+  if not name.suffix and name.given and string.match(name.given, ",") then
+    -- Split name suffix: magic_NameSuffixNoComma.txt
+    -- "John, III" => given: "John", suffix: "III"
+    local words = util.split(name.given, ",%s*")
+    name.suffix = words[#words]
+    name.given = table.concat(util.slice(words, 1, -2), ", ")
+  end
+end
+
+function Context:split_ndp_family(name)
+  if name["non-dropping-particle"] or not name.family then
+    return
+  end
+  if util.startswith(name.family, '"') and util.endswith(name.family, '"') then
+    -- Stop parsing family name if surrounded by quotation marks
+    -- bugreports_parseName.txt
+    name.family = string.gsub(name.family, '"', "")
+    return
+  end
+  local ndp_parts = {}
+  local family_parts = {}
+  local parts = util.split(name.family)
+  for i, part in ipairs(parts) do
+    local ndp, family
+    -- d'Aubignac
+    ndp, family = string.match(part, "^(%l')(.+)$")
+    if ndp and family then
+      table.insert(ndp_parts, ndp)
+      parts[i] = family
+    else
+      ndp, family = string.match(part, "^(%l’)(.+)$")
+      if ndp and family then
+        table.insert(ndp_parts, ndp)
+        parts[i] = family
+      else
+        -- al-Aswānī
+        ndp, family = string.match(part, "^(%l+%-)(.+)$")
+        if ndp and family then
+          table.insert(ndp_parts, ndp)
+          parts[i] = family
+        elseif i < #parts and util.is_lower(part) then
+          table.insert(ndp_parts, part)
+        end
+      end
+    end
+    if ndp or i == #parts then
+      for j = i, #parts do
+        table.insert(family_parts, parts[j])
+      end
+      break
+    end
+    if not util.is_lower(part) then
+      for j = i, #parts do
+        table.insert(family_parts, parts[j])
+      end
+      break
+    end
+  end
+  if #ndp_parts > 0 then
+    name["non-dropping-particle"] = table.concat(ndp_parts, " ")
+    name.family = table.concat(family_parts, " ")
+  end
+end
+
+function Context:split_given_dp(name)
+  if name["dropping-particle"] or not name.given then
+    return
+  end
+  local dp_parts = {}
+  local given_parts = {}
+  local parts = util.split(name.given)
+  for i = #parts, 1, -1 do
+    local part = parts[i]
+    if i == 1 or not util.is_lower(part) then
+      for j = 1, i do
+        table.insert(given_parts, parts[j])
+      end
+      break
+    end
+  -- name_ParsedDroppingParticleWithApostrophe.txt
+  -- given: "François Hédelin d'" =>
+  -- given: "François Hédelin", dropping-particle: "d'"
+    if string.match(part, "^%l+'?$") or string.match(part, "^%l+’$") then
+      table.insert(dp_parts, 1, part)
+    end
+  end
+  if #dp_parts > 0 then
+    name["dropping-particle"] = table.concat(dp_parts, " ")
+    name.given = table.concat(given_parts, " ")
+  end
+end
+
+-- function Context:split_given_ndp(name)
+--   if name["non-dropping-particle"] or not name.given then
+--     return
+--   end
+
+--   if not (string.match(name.given, "%l'$") or string.match(name.given, "%l’$")) then
+--     return
+--   end
+
+--   local words = util.split(name.given)
+--   if #words < 2 then
+--     return
+--   end
+--   local last_word = words[#words]
+--   if util.endswith(last_word, "'") or util.endswith(last_word, util.unicode["apostrophe"]) then
+--     name["non-dropping-particle"] = last_word
+--     name.given = table.concat(util.slice(words, 1, -2), " ")
+--   end
+--   util.debug(name)
+-- end
+
+function Context:get_localized_date(form)
+  return self.locale.dates[form]
+end
+
+function Context:get_macro(name)
+  local res = self.style.macros[name]
+  if not res then
+    util.error(string.format('Undefined macro "%s"', name))
+  end
+  return res
+end
+
+function Context:get_simple_term(name, form, plural)
+  assert(self.locale)
+  return self.locale:get_simple_term(name, form, plural)
+end
+
+function Context:get_localized_quotes()
+  return LocalizedQuotes:new(
+    self:get_simple_term("open-quote"),
+    self:get_simple_term("close-quote"),
+    self:get_simple_term("open-inner-quote"),
+    self:get_simple_term("close-inner-quote"),
+    self.locale.style_options.punctuation_in_quote
+  )
+end
+
+function Context.page_first(page)
+  local page_first = util.split(page, "%s*[&,-]%s*")[1]
+  return util.split(page_first, util.unicode["en dash"])[1]
+end
+
+-- https://docs.citationstyles.org/en/stable/specification.html#non-english-items
+function Context:is_english()
+  local language = self:get_variable("language")
+  if util.startswith(self.engine.lang, "en") then
+    if not language or util.startswith(language, "en") then
+      return true
+    else
+      return false
+    end
+  else
+    if language and util.startswith(language, "en") then
+      return true
+    else
+      return false
+    end
+  end
+end
+
+
+local IrState = {}
+
+function IrState:new(style, cite_id, cite, reference)
+  local o = {
+    macro_stack = {},
+    suppressed = {},
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function IrState:push_macro(macro_name)
+  for _, name in ipairs(macro_name) do
+    if name == macro_name then
+      util.error(string.format('Recursive macro "%s".', macro_name))
+    end
+    table.insert(self.macro_stack, macro_name)
+  end
+end
+
+function IrState:pop_macro(macro_name)
+  table.remove(self.macro_stack)
+end
+
+
+context.Context = Context
+context.IrState = IrState
+
+return context


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,411 +6,537 @@
 
 local element = {}
 
-local unicode = require("unicode")
+local SeqIr = require("citeproc-ir-node").SeqIr
 
-local richtext = require("citeproc-richtext")
+local InlineElement = require("citeproc-output").InlineElement
+local Micro = require("citeproc-output").Micro
+
 local util = require("citeproc-util")
 
 
 local Element = {
-  default_options = {},
+  element_name = nil,
+  children = nil,
+  element_type_map = {},
 }
 
-function Element:new ()
-  local o = {}
+function Element:new(element_name)
+  local o = {
+    element_name = element_name or self.element_name,
+  }
   setmetatable(o, self)
   self.__index = self
   return o
 end
 
-Element.option_type = {
-  ["et-al-min"] = "integer",
-  ["et-al-use-first"] = "integer",
-  ["et-al-subsequent-min"] = "integer",
-  ["et-al-subsequent-use-first"] = "integer",
-  ["near-note-distance"] = "integer",
-  ["line-spacing"] = "integer",
-  ["entry-spacing"] = "integer",
-  ["names-min"] = "integer",
-  ["names-use-first"] = "integer",
-  ["limit-day-ordinals-to-day-1"] = "boolean",
-  ["punctuation-in-quote"] = "boolean",
-  ["et-al-use-last"] = "boolean",
-  ["initialize"] = "boolean",
-  ["initialize-with-hyphen"] = "boolean",
-  ["disambiguate-add-names"] = "boolean",
-  ["disambiguate-add-givenname"] = "boolean",
-  ["disambiguate-add-year-suffix"] = "boolean",
-  ["hanging-indent"] = "boolean",
-  ["names-use-last"] = "boolean",
-  ["quotes"] = "boolean",
-  ["strip-periods"] = "boolean",
-}
+function Element:derive(element_name, default_options)
+  local o = {
+    element_name = element_name or self.element_name,
+    children = nil,
+  }
 
-Element.inheritable_options = {
-  -- Style
-  ["initialize-with-hyphen"] = true,
-  ["page-range-format"] = true,
-  ["demote-non-dropping-particle"] = true,
-  -- Citation
-  ["disambiguate-add-givenname"] = true,
-  ["givenname-disambiguation-rule"] = true,
-  ["disambiguate-add-names"] = true,
-  ["disambiguate-add-year-suffix"] = true,
-  ["cite-group-delimiter"] = true,
-  ["collapse"] = true,
-  ["year-suffix-delimiter"] = true,
-  ["after-collapse-delimiter"] = true,
-  ["near-note-distance"] = true,
-  -- Bibliography
-  ["second-field-align"] = true,  -- for use in layout
-  ["subsequent-author-substitute"] = true,
-  ["subsequent-author-substitute-rule"] = true,
-  -- Date
-  ["date-parts"] = true,
-  -- Names
-  ["and"] = true,
-  ["delimiter-precedes-et-al"] = true,
-  ["delimiter-precedes-last"] = true,
-  ["et-al-min"] = true,
-  ["et-al-use-first"] = true,
-  ["et-al-use-last"] = true,
-  ["et-al-subsequent-min"] = true,
-  ["et-al-subsequent-use-first"] = true,
-  ["names-min"] = true,
-  ["names-use-first"] = true,
-  ["names-use-last"] = true,
-  ["initialize-with"] = true,
-  ["name-as-sort-order"] = true,
-  ["sort-separator"] = true,
-  ["name-form"] = true,
-  ["name-delimiter"] = true,
-  ["names-delimiter"] = true,
-}
+  if default_options then
+    for key, value in pairs(default_options) do
+      o[key] = value
+    end
+  end
 
-function Element:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  return self:render_children(item, context)
+  Element.element_type_map[element_name] = o
+  setmetatable(o, self)
+  self.__index = self
+  return o
 end
 
-function Element:render_children (item, context)
-  local output = {}
-  for i, child in ipairs(self:get_children()) do
-    if child:is_element() then
-      if child.render == nil then
-        local element_name = child:get_element_name()
-        util.warning("Unkown type \"" .. element_name .. "\"")
-      end
-      local str = child:render(item, context)
-      table.insert(output, str)
-    end
+function Element:from_node(node, parent)
+  local o = self:new()
+  o.element_name = self.element_name or node:get_element_name()
+  return o
+end
+
+function Element:set_attribute(node, attribute)
+  local value = node:get_attribute(attribute)
+  if value then
+    local key = string.gsub(attribute, "%-" , "_")
+    self[key] = value
   end
-  return self:concat(output, context)
 end
 
-function Element:set_base_class (node)
-  if node:is_element() then
-    local org_meta_table = getmetatable(node)
-    setmetatable(node, {__index = function (_, key)
-      if self[key] then
-        return self[key]
-      else
-        return org_meta_table[key]
-      end
-    end})
+function Element:set_bool_attribute(node, attribute)
+  local value = node:get_attribute(attribute)
+  if value == "true" then
+    local key = string.gsub(attribute, "%-" , "_")
+    self[key] = true
+  elseif value == "false" then
+    local key = string.gsub(attribute, "%-" , "_")
+    self[key] = false
   end
 end
 
-function Element:debug_info (context, debug)
-  -- debug = true
-  if debug then
-    local text = ""
-    local level = 0
-    if context and context.level then
-      level = context.level + 1
-    end
-    text = text .. string.rep(" ", 2 * level)
-    text = text .. self:get_element_name()
-    local attrs = {}
-    if self._attr then
-      for attr, value in pairs(self._attr) do
-        table.insert(attrs, attr .. "=\"" .. value .. "\"")
-      end
-      text = text .. "[" .. table.concat(attrs, " ") .. "]"
-    end
-    io.stderr:write(text .. "\n")
+function Element:set_number_attribute(node, attribute)
+  local value = node:get_attribute(attribute)
+  if value then
+    local key = string.gsub(attribute, "%-" , "_")
+    self[key] = tonumber(value)
   end
 end
 
-function Element:get_child (type)
-  for _, child in ipairs(self:get_children()) do
-    if child:get_element_name() == type then
-      return child
+function Element:process_children_nodes(node)
+  if not self.children then
+    self.children = {}
+  end
+  for _, child in ipairs(node:get_children()) do
+    if child:is_element() then
+      local element_name = child:get_element_name()
+      local element_type = self.element_type_map[element_name] or Element
+      local child_element = element_type:from_node(child, self)
+      table.insert(self.children, child_element)
     end
   end
-  return nil
-end
 
-function Element:get_style ()
-  local style = self:root_node().style
-  assert(style ~= nil)
-  return style
 end
 
-function Element:get_engine ()
-  local engine = self:root_node().engine
-  assert(engine ~= nil)
-  return engine
+function Element:build_ir(engine, state, context)
+  return self:build_children_ir(engine, state, context)
 end
 
-function Element:process_context (context)
-  local state = {
-    -- The `build` table is directly passed to new context.
-    build = context.build or {},
-    -- The `option` table is copied.
-    options = {},
-    -- Other items in `context` is copied.
-  }
-  for key, value in pairs(self.default_options) do
-    state.options[key] = value
-  end
-  if context then
-    local element_name = self:get_element_name()
-    for key, value in pairs(context) do
-      if key == "options" then
-        for k, v in pairs(context.options) do
-          if self.inheritable_options[k] then
-            state.options[k] = v
-            if element_name == "name" then
-              if k == "name-form" then
-                state.options["form"] = v
-              end
-              if k == "name-delimiter" then
-                state.options["delimiter"] = v
-              end
-            elseif element_name == "names" then
-              if k == "names-delimiter" then
-                state.options["delimiter"] = v
-              end
-            end
-          end
+function Element:build_children_ir(engine, state, context)
+  local child_irs = {}
+  local ir_sort_key
+  local group_var = "plain"
+  if self.children then
+    for _, child_element in ipairs(self.children) do
+      local child_ir = child_element:build_ir(engine, state, context)
+      if child_ir then
+        if child_ir.sort_key ~= nil then
+          ir_sort_key = child_ir.sort_key
         end
-      else
-        state[key] = value
+        if child_ir.group_var == "important" then
+          group_var = "important"
+        end
+        table.insert(child_irs, child_ir)
       end
     end
-    if state.level then
-      state.level = state.level + 1
-    else
-      state.level = 0
-    end
   end
-  if self._attr then
-    for key, value in pairs(self._attr) do
-      if self.option_type[key] == "integer" then
-        value = tonumber(value)
-      elseif self.option_type[key] == "boolean" then
-        value = (value == "true")
-      end
-      state.options[key] = value
-    end
+  local ir = SeqIr:new(child_irs, self)
+  ir.sort_key = ir_sort_key
+  ir.group_var = group_var
+  if #child_irs == 0 then
+    ir.group_var = "missing"
+  else
+    ir.group_var = group_var
   end
-  return state
+  return ir
 end
 
-function Element:get_option (key, context)
-  assert(context ~= nil)
-  return context.options[key]
-end
-
-function Element:get_locale_option (key)
-  local locales = self:get_style():get_locales()
-  for i, locale in ipairs(locales) do
-    local option = locale:get_option(key)
-    if option ~= nil then
-      return option
-    end
+-- Used in cs:group and cs:macro
+function Element:build_group_ir(engine, state, context)
+  if not self.children then
+    return nil
   end
-  return nil
-end
+  local irs = {}
+  local name_count
+  local ir_sort_key
+  local group_var = "plain"
 
-function Element:get_variable (item, name, context)
-  if context.suppressed_variables and context.suppressed_variables[name] then
-    return nil
-  else
-    local res = item[name]
-    if type(res) == "table" and res._type == "RichText" then
-      -- TODO: should be deep copy
-      res = res:shallow_copy()
-    end
+  for _, child_element in ipairs(self.children) do
+    -- util.debug(child_element.element_name)
+    local child_ir = child_element:build_ir(engine, state, context)
+    -- util.debug(child_ir)
+    -- util.debug(child_ir.group_var)
 
-    if res and res ~= "" then
-      if context.suppress_subsequent_variables then
-        context.suppressed_variables[name] = true
+    if child_ir then
+      -- cs:group and its child elements are suppressed if
+      --   a) at least one rendering element in cs:group calls a variable (either
+      --      directly or via a macro), and
+      --   b) all variables that are called are empty. This accommodates
+      --      descriptive cs:text and `cs:label` elements.
+      local child_group_var = child_ir.group_var
+      if child_group_var == "important" then
+        group_var = "important"
+      elseif child_group_var == "missing" and child_ir._type ~= "YearSuffix" then
+        if group_var == "plain" then
+          group_var = "missing"
+        end
       end
+
+      if child_ir.name_count then
+        if not name_count then
+          name_count = 0
+        end
+        name_count = name_count + child_ir.name_count
+      end
+
+      if child_ir.sort_key ~= nil then
+        ir_sort_key = child_ir.sort_key
+      end
+
+      table.insert(irs, child_ir)
     end
-    return res
   end
-end
 
-function Element:get_macro (name)
-  local query = string.format("macro[name=\"%s\"]", name)
-  local macro = self:root_node():query_selector(query)[1]
-  if not macro then
-    error(string.format("Failed to find %s.", query))
+  -- A non-empty nested cs:group is treated as a non-empty variable for the
+  -- puropses of determining suppression of the outer cs:group.
+  if #irs > 0 and group_var == "plain" then
+    group_var = "important"
   end
-  return macro
-end
 
-function Element:get_term (name, form, number, gender)
-  return self:get_style():get_term(name, form, number, gender)
-end
+  local ir = SeqIr:new(irs, self)
+  ir.name_count = name_count
+  ir.sort_key = ir_sort_key
+  ir.group_var = group_var
 
--- Formatting
-function Element:escape (str, context)
-  return str
-  -- return self:get_engine().formatter.text_escape(str)
+  return ir
 end
 
-function Element:format(text, context)
-  if not text or text == "" then
-    return nil
+function Element:render_text_inlines(str, context)
+  if str == "" then
+    return {}
   end
-  if text._type ~= "RichText" then
-    text = richtext.new(text)
+
+  str = self:apply_strip_periods(str)
+  -- TODO: try links
+
+  local output_format = context.format
+  local localized_quotes = nil
+  if self.quotes then
+    localized_quotes = context:get_localized_quotes()
   end
-  local attributes = {
+
+  local inlines = InlineElement:parse(str, context)
+  local is_english = context:is_english()
+  output_format:apply_text_case(inlines, self.text_case, is_english)
+  inlines = {Micro:new(inlines)}
+  inlines = output_format:with_format(inlines, self.formatting)
+  inlines = output_format:affixed_quoted(inlines, self.affixes, localized_quotes)
+  return output_format:with_display(inlines, self.display)
+end
+
+function Element:set_formatting_attributes(node)
+  for _, attribute in ipairs({
     "font-style",
     "font-variant",
     "font-weight",
     "text-decoration",
     "vertical-align",
-  }
-  for _, attribute in ipairs(attributes) do
-    local value = context.options[attribute]
+  }) do
+    local value = node:get_attribute(attribute)
     if value then
-      if text.formats[attribute] then
-        local new = richtext.new()
-        new.contents = {text}
-        text = new
+      if not self.formatting then
+        self.formatting = {}
       end
-      text:add_format(attribute, value)
+      self.formatting[attribute] = value
     end
   end
-  return text
 end
 
--- Affixes
-function Element:wrap (str, context)
-  if not str or str == "" then
-    return nil
+function Element:set_affixes_attributes(node)
+  for _, attribute in ipairs({"prefix", "suffix"}) do
+    local value = node:get_attribute(attribute)
+    if value then
+      if not self.affixes then
+        self.affixes = {}
+      end
+      self.affixes[attribute] = value
+    end
   end
-  local prefix = context.options["prefix"]
-  local suffix = context.options["suffix"]
-  local res = str
-  if prefix and prefix ~= "" then
-    local linkable = false
-    local variable_name = context.options["variable"]
-    if variable_name == "DOI" or variable_name == "PMID" or variable_name == "PMCID" then
-      linkable = true
+end
+
+function Element:get_delimiter_attribute(node)
+  self:set_attribute(node, "delimiter")
+end
+
+function Element:set_display_attribute(node)
+  self:set_attribute(node, "display")
+end
+
+function Element:set_quotes_attribute(node)
+  self:set_bool_attribute(node, "quotes")
+end
+
+function Element:set_strip_periods_attribute(node)
+  self:set_bool_attribute(node, "strip-periods")
+end
+
+function Element:set_text_case_attribute(node)
+  self:set_attribute(node, "text-case")
+end
+
+-- function Element:apply_formatting(ir)
+--   local attributes = {
+--     "font_style",
+--     "font_variant",
+--     "font_weight",
+--     "text_decoration",
+--     "vertical_align",
+--   }
+--   for _, attribute in ipairs(attributes) do
+--     local value = self[attribute]
+--     if value then
+--       if not ir.formatting then
+--         ir.formatting = {}
+--       end
+--       ir.formatting[attribute] = value
+--     end
+--   end
+--   return ir
+-- end
+
+function Element:apply_affixes(ir)
+  if ir then
+    if self.prefix then
+      ir.prefix = self.prefix
     end
-    if variable_name == "URL" or (linkable and not string.match(prefix, "^https?://")) then
-      res:add_format(variable_name, "true")
+    if self.suffix then
+      ir.suffix = self.suffix
     end
-    res = richtext.concat(prefix, res)
-    if linkable and string.match(prefix, "^https?://") then
-      res:add_format("URL", "true")
-    end
   end
-  if suffix and suffix ~= "" then
-    res = richtext.concat(res, suffix)
+  return ir
+end
+
+function Element:apply_delimiter(ir)
+  if ir and ir.children then
+    ir.delimiter = self.delimiter
   end
-  return res
+  return ir
 end
 
--- Delimiters
-function Element:concat (strings, context)
-  local delimiter = context.options["delimiter"]
-  return richtext.concat_list(strings, delimiter)
+function Element:apply_display(ir)
+  ir.display = self.display
+  return ir
 end
 
--- Display
-function Element:display(text, context)
-  if not text then
-    return text
+function Element:apply_quotes(ir)
+  if ir and self.quotes then
+    ir.quotes = true
+    ir.children = {ir}
+    ir.open_quote = nil
+    ir.close_quote = nil
+    ir.open_inner_quote = nil
+    ir.close_inner_quote = nil
+    ir.punctuation_in_quote = false
   end
-  local value = context.options["display"]
-  if not value then
-    return text
+  return ir
+end
+
+function Element:apply_strip_periods(str)
+  local res = str
+  if str and self.strip_periods then
+    res = string.gsub(str, "%.", "")
   end
-  if type(text) == "string" then
-    text = richtext.new(text)
+  return res
+end
+
+
+function Element:format_number(number, variable, form, context)
+  -- if not util.is_numeric(number) then
+  --   number = string.gsub(number, "\\%-", "-")
+  --   return number
+  -- end
+  number = util.strip(number)
+  if variable == "locator" then
+    variable = context:get_variable("label")
   end
-  text:add_format("display", value)
-  return text
+  form = form or "numeric"
+  local number_part_list = self:split_number_parts(number, context)
+  -- {
+  --   {"1", "",  " & "}
+  --   {"5", "8", ", "}
+  -- }
+  -- util.debug(number_part_list)
+
+  for _, number_parts in ipairs(number_part_list) do
+    if form == "roman" then
+      self:format_roman_number_parts(number_parts)
+    elseif form == "ordinal" or form == "long-ordinal" then
+      local gender = context.locale:get_number_gender(variable)
+      self:format_ordinal_number_parts(number_parts, form, gender, context)
+    elseif number_parts[2] ~= "" and variable == "page" then
+      local page_range_format = context.style.page_range_format
+      self:format_page_range(number_parts, page_range_format)
+    else
+      self:format_numeric_number_parts(number_parts)
+    end
+  end
+
+  local range_delimiter = util.unicode["en dash"]
+  if variable == "page" then
+    local page_range_delimiter = context:get_simple_term("page-range-delimiter")
+    if page_range_delimiter then
+      range_delimiter = page_range_delimiter
+    end
+  end
+
+  local res = ""
+  for _, number_parts in ipairs(number_part_list) do
+    res = res .. number_parts[1]
+    if number_parts[2] ~= "" then
+      res = res .. range_delimiter
+      res = res .. number_parts[2]
+    end
+    res = res .. number_parts[3]
+  end
+  return res
+
 end
 
--- Quotes
-function Element:quote (str, context)
-  if not str then
-    return nil
+function Element:split_number_parts(number, context)
+  -- number = string.gsub(number, util.unicode["en dash"], "-")
+  local and_symbol
+  and_symbol = context.locale:get_simple_term("and", "symbol")
+  if and_symbol then
+    and_symbol = " " .. and_symbol .. " "
   end
-  if context.sorting then
-    return str
+  local number_part_list = {}
+  for _, tuple in ipairs(util.split_multiple(number, "%s*[,&]%s*", true)) do
+    local single_number, delim = table.unpack(tuple)
+    delim = util.strip(delim)
+    if delim == "," then
+      delim = ", "
+    elseif delim == "&" then
+      delim = and_symbol or " & "
+    end
+    local start = single_number
+    local stop = ""
+    local splits = util.split(start, "%s*%-%s*")
+    if #splits == 2 then
+      start, stop = table.unpack(splits)
+      if util.endswith(start, "\\") then
+        start = string.sub(start, 1, -2)
+        start = start .. "-" .. stop
+        stop = ""
+      end
+      -- if string.match(start, "^%a*%d+%a*$") and string.match(stop, "^%a*%d+%a*$") then
+      --   if s
+        table.insert(number_part_list, {start, stop, delim})
+      -- else
+        -- table.insert(number_part_list, {start .. "-" .. stop, "", delim})
+      -- end
+    else
+      table.insert(number_part_list, {start, stop, delim})
+    end
   end
-  if not str._type == "RichText" then
-    str = richtext.new(str)
+  return number_part_list
+end
+
+function Element:format_roman_number_parts(number_parts)
+  for i = 1, 2 do
+    local part = number_parts[i]
+    if string.match(part, "%d+") then
+      number_parts[i] = util.convert_roman(tonumber(part))
+    end
   end
-  local quotes = context.options["quotes"] or false
-  if quotes then
-    str:add_format("quotes", "true")
+end
+
+function Element:format_ordinal_number_parts(number_parts, form, gender, context)
+  for i = 1, 2 do
+    local part = number_parts[i]
+    if string.match(part, "%d+") then
+      local number = tonumber(part)
+      if form == "long-ordinal" and number >= 1 and number <= 10 then
+        number_parts[i] = context:get_simple_term(string.format("long-ordinal-%02d", number))
+      else
+        local suffix = context.locale:get_ordinal_term(number, gender)
+        if suffix then
+          number_parts[i] = number_parts[i] .. suffix
+        end
+      end
+    end
   end
-  return str
 end
 
--- Strip periods
-function Element:strip_periods (str, context)
-  if not str then
-    return nil
+function Element:format_numeric_number_parts(number_parts)
+  -- if number_parts[2] ~= "" then
+  --   local first_prefix = string.match(number_parts[1], "^(.-)%d+")
+  --   local second_prefix = string.match(number_parts[2], "^(.-)%d+")
+  --   if first_prefix == second_prefix then
+  --     number_parts[1] = number_parts[1] .. "-" .. number_parts[2]
+  --     number_parts[2] = ""
+  --   end
+  -- end
+end
+
+-- https://docs.citationstyles.org/en/stable/specification.html#appendix-v-page-range-formats
+function Element:format_page_range(number_parts, page_range_format)
+  local start = number_parts[1]
+  local stop = number_parts[2]
+  local start_prefix, start_num  = string.match(start, "^(.-)(%d+)$")
+  local stop_prefix, stop_num = string.match(stop, "^(.-)(%d+)$")
+  if start_prefix ~= stop_prefix then
+    -- Not valid range: "n11564-1568" -> "n11564-1568"
+    -- 110-N6
+    -- N110-P5
+    number_parts[1] = start .. "-" .. stop
+    number_parts[2] = ""
+    return
   end
-  if str._type ~= "RichText" then
-    str = richtext.new(str)
+
+  if not page_range_format then
+    return
   end
-  local strip_periods = context.options["strip-periods"]
-  if strip_periods then
-    str:strip_periods()
+  if page_range_format == "chicago-16" then
+    stop = self:_format_range_chicago_16(start_num, stop_num)
+  elseif page_range_format == "chicago-15" then
+    stop = self:_format_range_chicago_15(start_num, stop_num)
+  elseif page_range_format == "expanded" then
+    stop = stop_prefix .. self:_format_range_expanded(start_num, stop_num)
+  elseif page_range_format == "minimal" then
+    stop = self:_format_range_minimal(start_num, stop_num)
+  elseif page_range_format == "minimal-two" then
+    stop = self:_format_range_minimal(start_num, stop_num, 2)
   end
-  return str
+  number_parts[2] = stop
 end
 
--- Text-case
-function Element:case (text, context)
-  if not text or text == "" then
-    return nil
+function Element:_format_range_chicago_16(start, stop)
+  if #start < 3 or string.sub(start, -2) == "00" then
+    return self:_format_range_expanded(start, stop)
+  elseif string.sub(start, -2, -2) == "0" then
+    return self:_format_range_minimal(start, stop)
+  else
+    return self:_format_range_minimal(start, stop, 2)
   end
-  if text._type ~= "RichText" then
-    text = richtext.new(text)
+  return stop
+end
+
+function Element:_format_range_chicago_15(start, stop)
+  if #start < 3 or string.sub(start, -2) == "00" then
+    return self:_format_range_expanded(start, stop)
+  else
+    local changed_digits = self:_format_range_minimal(start, stop)
+    if string.sub(start, -2, -2) == "0" then
+      return changed_digits
+    elseif #start == 4 and #changed_digits == 3 then
+      return self:_format_range_expanded(start, stop)
+    else
+      return self:_format_range_minimal(start, stop, 2)
+    end
   end
-  local text_case = context.options["text-case"]
-  if not text_case then
-    return text
+  return stop
+end
+
+function Element:_format_range_expanded(start, stop)
+  -- Expand  "1234–56" -> "1234–1256"
+  if #start <= #stop then
+    return stop
   end
-  if text_case == "title" then
-    -- title case conversion only affects English-language items
-    local language = context.item["language"]
-    if not language then
-      language = self:get_style():get_attribute("default-locale") or "en-US"
+  return string.sub(start, 1, #start - #stop) .. stop
+end
+
+function Element:_format_range_minimal(start, stop, threshold)
+  threshold = threshold or 1
+  if #start < #stop then
+    return stop
+  end
+  local offset = #start - #stop
+  for i = 1, #stop - threshold do
+    local j = i + offset
+    if string.sub(stop, i, i) ~= string.sub(start, j, j) then
+      return string.sub(stop, i)
     end
-    if not util.startswith(language, "en") then
-      return text
-    end
   end
-  text:add_format("text-case", text_case)
-  return text
+  return string.sub(stop, -threshold)
 end
 
-
 element.Element = Element
 
 return element

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -8,17 +8,29 @@
 
 local dom = require("luaxml-domobject")
 
-local richtext = require("citeproc-richtext")
-local element = require("citeproc-element")
 local nodes = require("citeproc-nodes")
-local formats = require("citeproc-formats")
+local Style = require("citeproc-node-style").Style
+local Locale = require("citeproc-node-locale").Locale
+local Context = require("citeproc-context").Context
+local IrState = require("citeproc-context").IrState
+local Rendered = require("citeproc-ir-node").Rendered
+local YearSuffix = require("citeproc-ir-node").YearSuffix
+-- local OutputFormat = require("citeproc-output").OutputFormat
+local LatexWriter = require("citeproc-output").LatexWriter
+local HtmlWriter = require("citeproc-output").HtmlWriter
+local SortStringFormat = require("citeproc-output").SortStringFormat
+local DisamStringFormat = require("citeproc-output").DisamStringFormat
+local InlineElement = require("citeproc-output").InlineElement
+local Micro = require("citeproc-output").Micro
+local Formatted = require("citeproc-output").Formatted
+local PlainText = require("citeproc-output").PlainText
 local util = require("citeproc-util")
 
 
 local CiteProc = {}
 
-function CiteProc.new (sys, style, lang, force_lang)
-  if sys == nil then
+function CiteProc.new(sys, style, lang, force_lang)
+  if not sys then
     error("\"citeprocSys\" required")
   end
   if sys.retrieveLocale == nil then
@@ -28,213 +40,1579 @@
     error("\"citeprocSys.retrieveItem\" required")
   end
   local o = {}
+
+  o.style = Style:parse(style)
+
+  o.sys = sys
+  o.locales = {}
+  o.system_locales = {}
+
+  o.lang = o.style.default_locale
+  if not o.lang or force_lang then
+    o.lang = lang or "en-US"
+  end
+
+  o.output_format = LatexWriter:new()
+
+  o.opt = {
+    -- Similar to citeproc-js's development_extensions.wrap_url_and_doi
+    url_link = false,
+    doi_link = false,  -- also applied to PMID and PMCID
+    title_link = false,
+  }
+
   o.registry = {
-    citations = {},  -- A map
-    citation_strings = {},  -- A list
-    registry = {},  -- A map
-    reflist = {},  -- A list
+    citations_by_id = {},  -- A map
+    citation_list = {},  -- A list
+    citations_by_item_id = {},  -- A map from item id to a map of citations
+    registry = {},  -- A map of bibliographic meta data
+    reflist = {},  -- list of cited ids
+    uncited_list = {},
     previous_citation = nil,
     requires_sorting = false,
+    longest_label = "",
+    maxoffset = 0,
   }
 
-  o.sys = sys
-  o.system_locales = {}
+  o.cite_first_note_numbers = {}
+  o.cite_last_note_numbers = {}
+  o.note_citations_map = {}
 
-  if type(style) == "string" then
-    o.csl = dom.parse(style)
-  else
-    o.csl = style
-  end
-  o.csl:traverse_elements(CiteProc.set_base_class)
-  o.csl:root_node().engine = o
-  o.style = o.csl:get_path("style")[1]
-  o.style.lang = lang
-  o.csl:root_node().style = o.style
+  o.tainted_item_ids = {}
 
-  o.style:set_lang(lang, force_lang)
+  o.disam_irs = {}
+  -- { <ir1>, <ir2>, ...  }
 
-  o.formatter = formats.latex
-  o.linking_enabled = false
+  o.cite_irs_by_output = {}
+  -- {
+  --   ["Roe, J"] = {<ir1>},
+  --   ["Doe, J"] = {<ir2>, <ir3>},
+  --   ["Doe, John"] = {<ir2>},
+  --   ["Doe, Jack"] = {<ir2>},
+  -- }
 
+  o.person_names = {}
+  o.person_names_by_output = {}
+
   setmetatable(o, { __index = CiteProc })
   return o
 end
 
-function CiteProc:updateItems (ids)
+function CiteProc:updateItems(ids)
   self.registry.reflist = {}
   self.registry.registry = {}
+  self.person_names = {}
+  self.person_names_by_output = {}
+  self.disam_irs = {}
+  self.cite_irs_by_output = {}
+
+  local cite_items = {}
+  local loaded_ids = {}
+
   for _, id in ipairs(ids) do
-    self:get_item(id)
+    table.insert(cite_items, {id = id})
+    loaded_ids[id] = true
   end
+  for _, id in ipairs(self.registry.uncited_list) do
+    if not loaded_ids[id] then
+      table.insert(cite_items, {id = id})
+      loaded_ids[id] = true
+    end
+  end
+
+  -- TODO: optimize this
+  self:makeCitationCluster(cite_items)
+
+  self.registry.previous_citation = nil
+  self.cite_first_note_numbers = {}
+  self.cite_last_note_numbers = {}
+  self.note_citations_map = {}
 end
 
-function CiteProc:updateUncitedItems(ids)
-  for _, id in ipairs(ids) do
-    if not self.registry.registry[id] then
-      self:get_item(id)
+function CiteProc:updateUncitedItems(uncited_ids)
+  -- self.registry.reflist = {}
+  self.registry.registry = {}
+  self.registry.uncited_list = {}
+  self.person_names = {}
+  self.person_names_by_output = {}
+  self.disam_irs = {}
+  self.cite_irs_by_output = {}
+
+  local cite_items = {}
+  local loaded_ids = {}
+
+  for _, id in ipairs(self.registry.reflist) do
+    if not loaded_ids[id] then
+      table.insert(cite_items, {id = id})
+      loaded_ids[id] = true
     end
   end
-  -- TODO: disambiguation
+  self.registry.reflist = {}
+
+  for _, id in ipairs(uncited_ids) do
+    if not loaded_ids[id] then
+      table.insert(cite_items, {id = id})
+      loaded_ids[id] = true
+    end
+  end
+
+  loaded_ids = {}
+  for _, id in ipairs(uncited_ids) do
+    if not loaded_ids[id] then
+      table.insert(self.registry.uncited_list, id)
+      loaded_ids[id] = true
+    end
+  end
+
+  -- TODO: optimize this
+  self:makeCitationCluster(cite_items)
+
+  self.registry.previous_citation = nil
+  self.cite_first_note_numbers = {}
+  self.cite_last_note_numbers = {}
+  self.note_citations_map = {}
 end
 
 function CiteProc:processCitationCluster(citation, citationsPre, citationsPost)
-  -- citation = {
-  --   citationID = "CITATION-3",
-  --   citationItems = {
-  --     { id = "ITEM-1" },
-  --     { id = "ITEM-2" },
-  --   },
-  --   properties = {
-  --     noteIndex = 3,
-  --   },
-  -- }
-  -- citationsPre = {
-  --   {"CITATION-1", 1},
-  --   {"CITATION-2", 2},
-  -- }
-  -- citationsPost = {
-  --   {"CITATION-4", 4},
-  -- }
-  -- returns = {
-  --   {
-  --     bibchange = true,
-  --     citation_errors = {},
-  --   },
-  --   {
-  --     { 2, "[1,2]", "CITATION-3" }
-  --   }
-  -- }
-  self.registry.citations[citation.citationID] = citation
+  -- util.debug(citation)
+  -- util.debug(citationsPre)
+  -- Fix missing noteIndex: sort_CitationNumberPrimaryAscendingViaMacroCitation.txt
+  if not citation.properties then
+    citation.properties = {}
+  end
+  if not citation.properties.noteIndex then
+    citation.properties.noteIndex = 0
+  end
 
-  local items = {}
+  -- Registor citation
+  self.registry.citations_by_id[citation.citationID] = citation
 
+  local citation_note_pairs = {}
+  util.extend(citation_note_pairs, citationsPre)
+  table.insert(citation_note_pairs, {citation.citationID, citation.properties.noteIndex})
+  util.extend(citation_note_pairs, citationsPost)
+  -- util.debug(citation_note_pairs)
+
+  local citations_by_id = {}
+  local citation_list = {}
+  for _, pair in ipairs(citation_note_pairs) do
+    local citation_id, note_number = table.unpack(pair)
+    local citation_ = self.registry.citations_by_id[citation_id]
+    if not citation_ then
+      util.error("Citation not in registry.")
+    end
+    citations_by_id[citation_.citationID] = citation_
+    table.insert(citation_list, citation_)
+  end
+  self.registry.citations_by_id = citations_by_id
+  self.registry.citation_list = citation_list
+
+  -- update self.registry.citations_by_item_id
+  local item_ids = {}
+  self.registry.citations_by_item_id = {}
+  for _, citation_ in ipairs(citation_list) do
+    for _, cite_item in ipairs(citation_.citationItems) do
+      if not self.registry.citations_by_item_id[cite_item.id] then
+        self.registry.citations_by_item_id[cite_item.id] = {}
+        table.insert(item_ids, cite_item.id)
+      end
+      self.registry.citations_by_item_id[cite_item.id][citation_.citationID] = true
+    end
+  end
+  self:updateItems(item_ids)
+
+  local params = {
+    bibchange = false,
+    citation_errors = {},
+  }
+  local output = {}
+
+  local tainted_citation_ids = self:get_tainted_citaion_ids(citation_note_pairs)
+  -- util.debug(tainted_citation_ids)
+
+  -- params.bibchange = #tainted_citation_ids > 0
+  for citation_id, _ in pairs(tainted_citation_ids) do
+    local citation_ = self.registry.citations_by_id[citation_id]
+
+    local citation_index = citation_.citation_index
+    local citation_str = self:build_citation_str(citation_)
+    table.insert(output, {citation_index, citation_str, citation_id})
+  end
+
+  -- util.debug(output)
+  return {params, output}
+end
+
+-- A variant of processCitationCluster() for easy use with LaTeX.
+-- It should be run after refreshing the registry (updateItems()) with all items
+function CiteProc:process_citation(citation)
+  -- util.debug(citation)
+  -- util.debug(citationsPre)
+  -- Fix missing noteIndex: sort_CitationNumberPrimaryAscendingViaMacroCitation.txt
+  if not citation.properties then
+    citation.properties = {}
+  end
+  if not citation.properties.noteIndex then
+    citation.properties.noteIndex = 0
+  end
+
+  -- Registor citation
+  self.registry.citations_by_id[citation.citationID] = citation
+
+  table.insert(self.registry.citation_list, citation)
+
+  local citation_note_pairs = {}
+  for _, citation_ in ipairs(self.registry.citation_list) do
+    table.insert(citation_note_pairs, {citation_.citationID, citation_.properties.noteIndex})
+  end
+
+  -- update self.registry.citations_by_item_id
   for _, cite_item in ipairs(citation.citationItems) do
+    if not self.registry.citations_by_item_id[cite_item.id] then
+      self.registry.citations_by_item_id[cite_item.id] = {}
+    end
+    self.registry.citations_by_item_id[cite_item.id][citation.citationID] = true
+  end
+
+  -- self:updateItems(item_ids)
+  for i, cite_item in ipairs(citation.citationItems) do
     cite_item.id = tostring(cite_item.id)
-    local position_first = (self.registry.registry[cite_item.id] == nil)
-    local item_data = self:get_item(cite_item.id)
+    self:get_item(cite_item.id)
+  end
 
-    if item_data then
-      -- Create a wrapper of the orignal item from registry so that
-      -- it may hold different `locator` or `position` values for cites.
-      local item = setmetatable({}, {__index = function (_, key)
-        if cite_item[key] then
-          return cite_item[key]
-        else
-          return item_data[key]
+  local tainted_citation_ids = self:get_tainted_citaion_ids(citation_note_pairs)
+  local citation_str = self:build_citation_str(citation)
+
+  return citation_str
+end
+
+
+function CiteProc:get_tainted_citaion_ids(citation_note_pairs)
+  local tainted_citation_ids = {}
+
+  self.cite_first_note_numbers = {}
+  self.cite_last_note_numbers = {}
+  self.note_citations_map = {}
+  -- {
+  --   1 = {"citation-1", "citation-2"},
+  --   2 = {"citation-2"},
+  -- }
+
+  local previous_citation
+  for citation_index, pair in ipairs(citation_note_pairs) do
+    local citation_id, note_number = table.unpack(pair)
+    -- util.debug(citation_id)
+    local citation = self.registry.citations_by_id[citation_id]
+    citation.properties.noteIndex = note_number
+    citation.citation_index = citation_index
+
+    local tainted = false
+
+    local previous_cite
+    for _, cite_item in ipairs(citation.citationItems) do
+      tainted = self:set_cite_item_position(cite_item, note_number, previous_cite, previous_citation)
+      self.cite_last_note_numbers[cite_item.id] = note_number
+      previous_cite = cite_item
+    end
+
+    if tainted then
+      tainted_citation_ids[citation.citationID] = true
+    end
+
+    if not self.note_citations_map[note_number] then
+      self.note_citations_map[note_number] = {}
+    end
+    table.insert(self.note_citations_map[note_number], citation.citationID)
+    previous_citation = citation
+  end
+
+  -- Update tainted citation ids because of citation-number's change
+  -- The self.tainted_item_ids were added in the sort_bibliography() procedure.
+  for item_id, _ in pairs(self.tainted_item_ids) do
+    if self.registry.citations_by_item_id[item_id] then
+      for citation_id, _ in pairs(self.registry.citations_by_item_id[item_id]) do
+        tainted_citation_ids[citation_id] = true
+      end
+    end
+  end
+
+  return tainted_citation_ids
+end
+
+function CiteProc:set_cite_item_position(cite_item, note_number, previous_cite, previous_citation)
+  local position = util.position_map["first"]
+
+  local first_reference_note_number = self.cite_first_note_numbers[cite_item.id]
+  if first_reference_note_number then
+    position = util.position_map["subsequent"]
+  else
+    self.cite_first_note_numbers[cite_item.id] = note_number
+  end
+
+  local preceding_cite_item = self:get_preceding_cite_item(cite_item, previous_cite, previous_citation, note_number)
+  if preceding_cite_item then
+    position = self:_get_cite_position(cite_item, preceding_cite_item)
+  end
+
+  local near_note = false
+  local last_note_number = self.cite_last_note_numbers[cite_item.id]
+  if last_note_number then
+    local note_distance = note_number - last_note_number
+    if note_distance <= self.style.citation.near_note_distance then
+      near_note = true
+    end
+  end
+
+  local tainted = false
+  if cite_item.position_level ~= position then
+    tainted = true
+    cite_item.position_level = position
+  end
+  if cite_item["first-reference-note-number"] ~= first_reference_note_number then
+    tainted = true
+    cite_item["first-reference-note-number"] = first_reference_note_number
+  end
+  if cite_item.near_note ~= near_note then
+    tainted = true
+    cite_item.near_note = near_note
+  end
+  return tainted
+end
+
+-- Find the preceding cite referencing the same item
+function CiteProc:get_preceding_cite_item(cite_item, previous_cite, previous_citation, note_number)
+  if previous_cite then
+    -- a. the current cite immediately follows on another cite, within the same
+    --    citation, that references the same item
+    if cite_item.id == previous_cite.id then
+      return previous_cite
+    end
+  elseif previous_citation then
+    -- (hidden) The previous citation is the only one in the previous note.
+    --    See also
+    --    https://github.com/citation-style-language/documentation/issues/121
+    --    position_IbidWithMultipleSoloCitesInBackref.txt
+    -- b. the current cite is the first cite in the citation, and the previous
+    --    citation consists of a single cite referencing the same item
+    local previous_note_number = previous_citation.properties.noteIndex
+    local num_previous_note_citations = #self.note_citations_map[previous_note_number]
+    if (previous_note_number == note_number - 1 and num_previous_note_citations == 1)
+        or previous_note_number == note_number then
+      if #previous_citation.citationItems == 1 then
+        previous_cite = previous_citation.citationItems[1]
+        if previous_cite.id == cite_item.id then
+          return previous_cite
         end
-      end})
+      end
+    end
+  end
+  return nil
+end
 
-      if not item.position and position_first then
-        item.position = util.position_map["first"]
+function CiteProc:_get_cite_position(item, preceding_cite)
+  if preceding_cite.locator then
+    if item.locator then
+      if item.locator == preceding_cite.locator and item.label == preceding_cite.label then
+        return util.position_map["ibid"]
+      else
+        return util.position_map["ibid-with-locator"]
       end
+    else
+      return util.position_map["subsequent"]
+    end
+  else
+    if item.locator then
+      return util.position_map["ibid-with-locator"]
+    else
+      return util.position_map["ibid"]
+    end
+  end
+end
 
-      local first_reference_note_number = nil
-      for _, pre_citation in ipairs(citationsPre) do
-        pre_citation = self.registry.citations[pre_citation[1]]
-        for _, pre_cite_item in ipairs(pre_citation.citationItems) do
-          if pre_cite_item.id == cite_item.id then
-            first_reference_note_number = pre_citation.properties.noteIndex
+function CiteProc:build_citation_str(citation)
+  -- util.debug(citation.citationID)
+  local items = {}
+  for i, cite_item in ipairs(citation.citationItems) do
+    cite_item.id = tostring(cite_item.id)
+    -- util.debug(cite_item.id)
+
+    -- Use "page" as locator label if missing
+    -- label_PluralWithAmpersand.txt
+    if cite_item.locator and not cite_item.label then
+      cite_item.label = "page"
+    end
+
+    table.insert(items, cite_item)
+  end
+
+  if self.registry.requires_sorting then
+    self:sort_bibliography()
+  end
+
+  local citation_str = self:build_cluster(items)
+  return citation_str
+end
+
+function CiteProc:build_cluster(citation_items)
+  local output_format = self.output_format
+  local irs = {}
+  citation_items = self:sorted_citation_items(citation_items)
+  for _, cite_item in ipairs(citation_items) do
+    local ir = self:build_fully_disambiguated_ir(cite_item, output_format)
+    table.insert(irs, ir)
+  end
+
+  if self.style.citation.cite_grouping then
+    irs = self:group_cites(irs)
+  else
+    local citation_collapse = self.style.citation.collapse
+    if citation_collapse == "year" or citation_collapse == "year-suffix" or
+        citation_collapse == "year-suffix-ranged" then
+      irs = self:group_cites(irs)
+    end
+  end
+
+  if self.style.citation.collapse then
+    self:collapse_cites(irs)
+  end
+
+  -- Capitalize first
+  for i, ir in ipairs(irs) do
+      -- local layout_prefix
+      -- local layout_affixes = self.style.citation.layout.affixes
+      -- if layout_affixes then
+      --   layout_prefix = layout_affixes.prefix
+      -- end
+    local prefix = citation_items[i].prefix
+    if prefix then
+      if prefix and string.match(prefix, "[.!?]%s*$") and #util.split(util.strip(prefix)) > 1 then
+        ir:capitalize_first_term()
+      end
+    else
+      local delimiter = self.style.citation.layout.delimiter
+      if i == 1 or not delimiter or string.match(delimiter, "[.!?]%s*$") then
+        ir:capitalize_first_term()
+      end
+    end
+  end
+
+  -- util.debug(irs)
+
+  local citation_delimiter = self.style.citation.layout.delimiter
+  local citation_stream = {}
+
+  local context = Context:new()
+  context.engine = self
+  context.style = self.style
+  context.area = self.style.citation
+  context.in_bibliography = false
+  context.locale = self:get_locale(self.lang)
+  context.name_inheritance = self.style.citation.name_inheritance
+  context.format = output_format
+
+  local previous_ir
+  for i, ir in ipairs(irs) do
+    local cite_prefix = citation_items[i].prefix
+    local cite_suffix = citation_items[i].suffix
+    if not ir.collapse_suppressed then
+      local ir_inlines = ir:flatten(output_format)
+      if #ir_inlines > 0 then
+        -- Make sure ir_inlines has outputs contents.
+        -- collapse_AuthorCollapseNoDateSorted.txt
+        if previous_ir then
+          if previous_ir.own_delimiter then
+            table.insert(citation_stream, PlainText:new(previous_ir.own_delimiter))
+          elseif citation_delimiter and not (cite_prefix and util.startswith(cite_prefix, ",")) then
+            table.insert(citation_stream, PlainText:new(citation_delimiter))
           end
-          break
         end
-        if first_reference_note_number then
-          break
+
+        if cite_prefix then
+          table.insert(citation_stream, Micro:new(InlineElement:parse(cite_prefix, context)))
         end
+
+        -- util.debug(ir)
+        util.extend(citation_stream, ir_inlines)
+        previous_ir = ir
+
+        if cite_suffix then
+          table.insert(citation_stream, Micro:new(InlineElement:parse(cite_suffix, context)))
+        end
       end
-      item["first-reference-note-number"] = first_reference_note_number
+    end
+  end
+  -- util.debug(citation_stream)
 
-      table.insert(items, item)
+  if context.area.layout.affixes then
+    local affixes = context.area.layout.affixes
+    if affixes.prefix then
+      table.insert(citation_stream, 1, PlainText:new(affixes.prefix))
     end
+    if affixes.suffix then
+      table.insert(citation_stream, PlainText:new(affixes.suffix))
+    end
   end
+  -- util.debug(citation_stream)
 
-  if #citationsPre > 0 then
-    local previous_citation_id = citationsPre[#citationsPre][1]
-    local previous_citation = self.registry.citations[previous_citation_id]
-    self.registry.previous_citation = previous_citation
+  if #citation_stream > 0 and context.area.layout.formatting then
+    citation_stream = {Formatted:new(citation_stream, context.area.layout.formatting)}
   end
 
-  if self.registry.requires_sorting then
-    self:sort_bibliography()
+  if #citation_stream == 0 then
+    citation_stream = {PlainText:new("[CSL STYLE ERROR: reference with no printed form.]")}
   end
 
-  local params = {
-    bibchange = false,
-    citation_errors = {},
-  }
+  -- util.debug(citation_stream)
+  local str = output_format:output(citation_stream, context)
+  str = util.strip(str)
 
-  local citation_id_note_list = {}
-  for _, citation_id_note in ipairs(citationsPre) do
-    table.insert(citation_id_note_list, citation_id_note)
+  return str
+end
+
+function CiteProc:sorted_citation_items(items)
+  local citation_sort = self.style.citation.sort
+  if not citation_sort then
+    return items
   end
-  local note_index = 0
-  if citation.properties and citation.properties.noteIndex then
-    note_index = citation.properties.noteIndex
+
+  local state = IrState:new()
+  local context = Context:new()
+  context.engine = self
+  context.style = self.style
+  context.area = self.style.citation
+  context.in_bibliography = false
+  context.locale = self:get_locale(self.lang)
+  context.name_inheritance = self.style.citation.name_inheritance
+  context.format = SortStringFormat:new()
+  -- context.id = id
+  context.cite = nil
+  -- context.reference = self:get_item(id)
+
+  items = citation_sort:sort(items, state, context)
+  return items
+end
+
+function CiteProc:build_fully_disambiguated_ir(cite_item, output_format)
+  local cite_ir = self:build_ambiguous_ir(cite_item, output_format)
+  -- util.debug(cite_ir)
+  cite_ir = self:disambiguate_add_givenname(cite_ir)
+  cite_ir = self:disambiguate_add_names(cite_ir)
+  cite_ir = self:disambiguate_conditionals(cite_ir)
+  cite_ir = self:disambiguate_add_year_suffix(cite_ir)
+  return cite_ir
+end
+
+function CiteProc:build_ambiguous_ir(cite_item, output_format)
+  local state = IrState:new(self.style)
+  cite_item.id = tostring(cite_item.id)
+  local context = Context:new()
+  context.engine = self
+  context.style = self.style
+  context.area = self.style.citation
+  context.locale = self:get_locale(self.lang)
+  context.name_inheritance = self.style.citation.name_inheritance
+  context.format = output_format
+  context.id = cite_item.id
+  context.cite = cite_item
+  -- context.reference = self:get_item(cite_item.id)
+  context.reference = self.registry.registry[cite_item.id]
+
+  local ir
+  if context.reference then
+    ir = self.style.citation:build_ir(self, state, context)
+  else
+    -- The warning has been given in the retrieving procedure.
+    -- util.warning(string.format('Failed to find the entry "%s" in database.', cite_item.id))
+    ir = Rendered:new({Formatted:new({PlainText:new(cite_item.id)}, {["font-weight"] = "bold"})}, self)
   end
-  table.insert(citation_id_note_list, {citation.citationID, note_index})
-  for _, citation_id_note in ipairs(citationsPost) do
-    table.insert(citation_id_note_list, citation_id_note)
+
+  ir.cite_item = cite_item
+  ir.reference = context.reference
+  ir.ir_index = #self.disam_irs + 1
+  table.insert(self.disam_irs, ir)
+  ir.is_ambiguous = false
+  ir.disam_level = 0
+
+  -- Formattings like font-style are ignored for disambiguation.
+  local disam_format = DisamStringFormat:new()
+  local inlines = ir:flatten(disam_format)
+  local disam_str = disam_format:output(inlines, context)
+  ir.disam_str = disam_str
+
+  if not self.cite_irs_by_output[disam_str] then
+    self.cite_irs_by_output[disam_str] = {}
   end
 
-  local citation_id_cited = {}
-  for _, citation_id_note in ipairs(citation_id_note_list) do
-    citation_id_cited[citation_id_note[1]] = true
+  for ir_index, ir_ in pairs(self.cite_irs_by_output[disam_str]) do
+    if ir_.cite_item.id ~= cite_item.id then
+      ir.is_ambiguous = true
+      break
+    end
   end
-  for citation_id, _ in pairs(self.registry.citations) do
-    if not citation_id_cited[citation_id] then
-      self.registry.citations[citation_id] = nil
-      self.registry.citation_strings[citation_id] = nil
+  self.cite_irs_by_output[disam_str][ir.ir_index] = ir
+
+  return ir
+end
+
+function CiteProc:disambiguate_add_givenname(cite_ir)
+  if self.style.citation.disambiguate_add_givenname then
+    -- util.debug("disambiguate_add_givenname: " .. cite_ir.cite_item.id)
+
+    local gn_disam_rule = self.style.citation.givenname_disambiguation_rule
+    if gn_disam_rule == "all-names" or gn_disam_rule == "all-names-with-initials" then
+      cite_ir = self:disambiguate_add_givenname_all_names(cite_ir)
+    elseif gn_disam_rule == "primary-name" or gn_disam_rule == "primary-name-with-initials" then
+      cite_ir = self:disambiguate_add_givenname_primary_name(cite_ir)
+    elseif gn_disam_rule == "by-cite" then
+      cite_ir = self:disambiguate_add_givenname_by_cite(cite_ir)
     end
   end
+  return cite_ir
+end
 
-  local output = {}
+-- TODO: reorganize this code
+function CiteProc:disambiguate_add_givenname_all_names(cite_ir)
+  -- util.debug("disambiguate_add_givenname_all_names: " .. cite_ir.cite_item.id)
+  if not cite_ir.person_name_irs or #cite_ir.person_name_irs == 0 then
+    return cite_ir
+  end
 
-  for i, citation_id_note in ipairs(citation_id_note_list) do
-    local citation_id = citation_id_note[1]
-    -- local note_index = citation_id_note[2]
-    if citation_id == citation.citationID then
-      local context = {
-        build = {},
-        engine = self,
-      }
-      local citation_str = self.style:render_citation(items, context)
+  -- util.debug(cite_ir.disam_str)
 
-      self.registry.citation_strings[citation_id] = citation_str
-      table.insert(output, {i - 1, citation_str, citation_id})
+  for _, person_name_ir in ipairs(cite_ir.person_name_irs) do
+    local name_output = person_name_ir.name_output
+    -- util.debug(name_output)
+
+    if not person_name_ir.person_name_index then
+      person_name_ir.person_name_index = #self.person_names + 1
+      table.insert(self.person_names, person_name_ir)
+    end
+
+    if not self.person_names_by_output[name_output] then
+      self.person_names_by_output[name_output] = {}
+    end
+    self.person_names_by_output[name_output][person_name_ir.person_name_index] = person_name_ir
+
+    local ambiguous_name_irs = {}
+    local ambiguous_same_output_irs = {}
+
+    for _, pn_ir in pairs(self.person_names_by_output[person_name_ir.name_output]) do
+      if pn_ir.full_name ~= person_name_ir.full_name then
+        table.insert(ambiguous_name_irs, pn_ir)
+      end
+      if pn_ir.name_output == person_name_ir.name_output then
+        table.insert(ambiguous_same_output_irs, pn_ir)
+      end
+    end
+
+    -- util.debug(person_name_ir.name_output)
+    -- util.debug(person_name_ir.full_name)
+    -- util.debug(#ambiguous_name_irs)
+    -- util.debug(person_name_ir.disam_variants_index)
+    -- util.debug(person_name_ir.disam_variants)
+
+    while person_name_ir.disam_variants_index < #person_name_ir.disam_variants do
+      if #ambiguous_name_irs == 0 then
+        break
+      end
+
+      for _, pn_ir in ipairs(ambiguous_same_output_irs) do
+        -- expand one name
+        if pn_ir.disam_variants_index < #pn_ir.disam_variants then
+          pn_ir.disam_variants_index = pn_ir.disam_variants_index + 1
+          pn_ir.name_output = pn_ir.disam_variants[pn_ir.disam_variants_index]
+          pn_ir.inlines = pn_ir.disam_inlines[pn_ir.name_output]
+
+          if not self.person_names_by_output[pn_ir.name_output] then
+            self.person_names_by_output[pn_ir.name_output] = {}
+          end
+          self.person_names_by_output[pn_ir.name_output][pn_ir.person_name_index] = pn_ir
+        end
+      end
+
+      -- util.debug(person_name_ir.name_output)
+
+      -- update ambiguous_name_irs and ambiguous_same_output_irs
+      ambiguous_name_irs = {}
+      ambiguous_same_output_irs = {}
+      for _, pn_ir in pairs(self.person_names_by_output[person_name_ir.name_output]) do
+        if pn_ir.full_name ~= person_name_ir.full_name then
+          -- util.debug(pn_ir.full_name .. ": " .. pn_ir.name_output)
+          table.insert(ambiguous_name_irs, pn_ir)
+        end
+        if pn_ir.name_output == person_name_ir.name_output then
+          table.insert(ambiguous_same_output_irs, pn_ir)
+        end
+      end
+
+    end
+  end
+
+  -- update cite_ir output
+  local disam_format = DisamStringFormat:new()
+  local inlines = cite_ir:flatten(disam_format)
+  local disam_str = disam_format:output(inlines, context)
+  cite_ir.disam_str = disam_str
+  if not self.cite_irs_by_output[disam_str] then
+    self.cite_irs_by_output[disam_str] = {}
+  end
+  self.cite_irs_by_output[disam_str][cite_ir.ir_index] = cite_ir
+
+  -- update ambiguous_cite_irs and ambiguous_same_output_irs
+  local ambiguous_cite_irs = {}
+  for ir_index, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+    -- util.debug(ir_.cite_item.id)
+    if ir_.cite_item.id ~= cite_ir.cite_item.id then
+      table.insert(ambiguous_cite_irs, ir_)
+    end
+  end
+  if #ambiguous_cite_irs == 0 then
+    cite_ir.is_ambiguous = false
+  end
+
+  return cite_ir
+end
+
+function CiteProc:disambiguate_add_givenname_primary_name(cite_ir)
+  if not cite_ir.person_name_irs or #cite_ir.person_name_irs == 0 then
+    return cite_ir
+  end
+  local person_name_ir = cite_ir.person_name_irs[1]
+  local name_output = person_name_ir.name_output
+  -- util.debug(name_output)
+
+  if not person_name_ir.person_name_index then
+    person_name_ir.person_name_index = #self.person_names + 1
+    table.insert(self.person_names, person_name_ir)
+  end
+  if not self.person_names_by_output[name_output] then
+    self.person_names_by_output[name_output] = {}
+  end
+  self.person_names_by_output[name_output][person_name_ir.person_name_index] = person_name_ir
+
+  local ambiguous_name_irs = {}
+  local ambiguous_same_output_irs = {}
+
+  for _, pn_ir in pairs(self.person_names_by_output[person_name_ir.name_output]) do
+    if pn_ir.full_name ~= person_name_ir.full_name then
+      table.insert(ambiguous_name_irs, pn_ir)
+    end
+    if pn_ir.name_output == person_name_ir.name_output then
+      table.insert(ambiguous_same_output_irs, pn_ir)
+    end
+  end
+
+  for _, name_variant in ipairs(person_name_ir.disam_variants) do
+    if #ambiguous_name_irs == 0 then
+      break
+    end
+
+    for _, pn_ir in ipairs(ambiguous_same_output_irs) do
+      -- expand one name
+      if pn_ir.disam_variants_index < #pn_ir.disam_variants then
+        pn_ir.disam_variants_index = pn_ir.disam_variants_index + 1
+        pn_ir.name_output = pn_ir.disam_variants[pn_ir.disam_variants_index]
+        pn_ir.inlines = pn_ir.disam_inlines[pn_ir.name_output]
+
+        if not self.person_names_by_output[pn_ir.name_output] then
+          self.person_names_by_output[pn_ir.name_output] = {}
+        end
+        self.person_names_by_output[pn_ir.name_output][person_name_ir.person_name_index] = person_name_ir
+      end
+    end
+
+    -- update ambiguous_name_irs and ambiguous_same_output_irs
+    ambiguous_name_irs = {}
+    ambiguous_same_output_irs = {}
+    for _, pn_ir in pairs(self.person_names_by_output[person_name_ir.name_output]) do
+      if pn_ir.full_name ~= person_name_ir.full_name then
+        table.insert(ambiguous_name_irs, pn_ir)
+      end
+      if pn_ir.name_output == person_name_ir.name_output then
+        table.insert(ambiguous_same_output_irs, pn_ir)
+      end
+    end
+  end
+
+  return cite_ir
+end
+
+function CiteProc:disambiguate_add_givenname_by_cite(cite_ir)
+  if not cite_ir.is_ambiguous then
+    return cite_ir
+  end
+  if not cite_ir.person_name_irs or #cite_ir.person_name_irs == 0 then
+    return cite_ir
+  end
+
+  for _, ir_ in ipairs(self.disam_irs) do
+    -- util.debug(ir_.cite_item.id)
+    -- util.debug(ir_.disam_str)
+  end
+
+  local disam_format = DisamStringFormat:new()
+
+  local ambiguous_cite_irs = {}
+  local ambiguous_same_output_irs = {}
+
+  for ir_index, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+    if ir_.cite_item.id ~= cite_ir.cite_item.id then
+      table.insert(ambiguous_cite_irs, ir_)
+    end
+    if ir_.disam_str == cite_ir.disam_str then
+      table.insert(ambiguous_same_output_irs, ir_)
+    end
+  end
+
+  for i, person_name_ir in ipairs(cite_ir.person_name_irs) do
+    -- util.debug(person_name_ir.name_output)
+    -- util.debug(person_name_ir.disam_variants)
+    if #ambiguous_cite_irs == 0 then
+      cite_ir.is_ambiguous = false
+      break
+    end
+
+    -- util.debug(person_name_ir.disam_variants)
+    while person_name_ir.disam_variants_index < #person_name_ir.disam_variants do
+      -- util.debug(person_name_ir.name_output)
+
+      local is_different_name = false
+      for _, ir_ in ipairs(ambiguous_cite_irs) do
+        if ir_.person_name_irs[i] then
+          if ir_.person_name_irs[i].full_name ~= person_name_ir.full_name then
+            -- util.debug(ir_.cite_item.id)
+            is_different_name = true
+            break
+          end
+        end
+      end
+      -- util.debug(is_different_name)
+      if not is_different_name then
+        break
+      end
+
+      for _, ir_ in ipairs(ambiguous_same_output_irs) do
+        -- util.debug(ir_.cite_item.id)
+        local person_name_ir_ = ir_.person_name_irs[i]
+        if person_name_ir_ then
+          if person_name_ir_.disam_variants_index < #person_name_ir_.disam_variants then
+            person_name_ir_.disam_variants_index = person_name_ir_.disam_variants_index + 1
+            local disam_variant = person_name_ir_.disam_variants[person_name_ir_.disam_variants_index]
+            person_name_ir_.name_output = disam_variant
+            -- util.debug(disam_variant)
+            person_name_ir_.inlines = person_name_ir_.disam_inlines[disam_variant]
+            -- Update cite ir output
+            local inlines = ir_:flatten(disam_format)
+            local disam_str = disam_format:output(inlines, context)
+            ir_.disam_str = disam_str
+            if not self.cite_irs_by_output[disam_str] then
+              self.cite_irs_by_output[disam_str] = {}
+            end
+            self.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+          end
+        end
+      end
+
+      -- update ambiguous_cite_irs and ambiguous_same_output_irs
+      ambiguous_cite_irs = {}
+      ambiguous_same_output_irs = {}
+      for ir_index, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+        -- util.debug(ir_.cite_item.id)
+        if ir_.cite_item.id ~= cite_ir.cite_item.id then
+          table.insert(ambiguous_cite_irs, ir_)
+        end
+        if ir_.disam_str == cite_ir.disam_str then
+          table.insert(ambiguous_same_output_irs, ir_)
+        end
+      end
+
+      -- util.debug(#ambiguous_cite_irs)
+
+      if #ambiguous_cite_irs == 0 then
+        cite_ir.is_ambiguous = false
+        return cite_ir
+      end
+
+    end
+  end
+
+  return cite_ir
+end
+
+local function find_first_name_ir(ir)
+  if ir._type == "NameIr" then
+    return ir
+  end
+  if ir.children then
+    for _, child in ipairs(ir.children) do
+      local name_ir = find_first_name_ir(child)
+      if name_ir then
+        return name_ir
+      end
+    end
+  end
+  return nil
+end
+
+function CiteProc:disambiguate_add_names(cite_ir)
+  if not self.style.citation.disambiguate_add_names then
+    return cite_ir
+  end
+
+  if not cite_ir.name_ir then
+    cite_ir.name_ir = find_first_name_ir(cite_ir)
+  end
+  local name_ir =  cite_ir.name_ir
+
+  if not cite_ir.is_ambiguous then
+    return cite_ir
+  end
+
+  if not name_ir or not name_ir.et_al_abbreviation then
+    return cite_ir
+  end
+
+  -- util.debug("disambiguate_add_names: " .. cite_ir.cite_item.id)
+
+  if name_ir then
+    -- util.debug(cite_ir.disam_str)
+    -- util.debug(cite_ir.name_ir.full_name_str)
+    -- util.debug(cite_ir.is_ambiguous)
+  end
+
+  local disam_format = DisamStringFormat:new()
+
+  while cite_ir.is_ambiguous do
+    if #cite_ir.name_ir.hidden_name_irs == 0 then
+      break
+    end
+
+    local ambiguous_cite_irs = {}
+    local ambiguous_same_output_irs = {}
+    for _, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+      if ir_.cite_item.id ~= cite_ir.cite_item.id then
+        table.insert(ambiguous_cite_irs, ir_)
+      end
+      if ir_.disam_str == cite_ir.disam_str then
+        table.insert(ambiguous_same_output_irs, ir_)
+      end
+    end
+
+    -- util.debug(#ambiguous_same_output_irs)
+    if #ambiguous_cite_irs == 0 then
+      cite_ir.is_ambiguous = false
+      break
+    end
+
+    -- check if the cite can be (fully) disambiguated by adding names
+    local can_be_disambuguated = false
+    for _, ir_ in ipairs(ambiguous_cite_irs) do
+      if ir_.name_ir.full_name_str ~= cite_ir.name_ir.full_name_str then
+        can_be_disambuguated = true
+        break
+      end
+    end
+    -- util.debug(can_be_disambuguated)
+    if not can_be_disambuguated then
+      break
+    end
+
+    for _, ir_ in ipairs(ambiguous_same_output_irs) do
+      local added_person_name_ir = ir_.name_ir.name_inheritance:expand_one_name(ir_.name_ir)
+      if added_person_name_ir then
+        -- util.debug("Updated: " .. ir_.cite_item.id)
+        table.insert(ir_.person_name_irs, added_person_name_ir)
+
+        if not added_person_name_ir.person_name_index then
+          added_person_name_ir.person_name_index = #self.person_names + 1
+          table.insert(self.person_names, added_person_name_ir)
+        end
+        local name_output = added_person_name_ir.name_output
+        if not self.person_names_by_output[name_output] then
+          self.person_names_by_output[name_output] = {}
+        end
+        self.person_names_by_output[name_output][added_person_name_ir.person_name_index] = added_person_name_ir
+
+        -- Update ir output
+        local inlines = ir_:flatten(disam_format)
+        local disam_str = disam_format:output(inlines, context)
+        -- util.debug(disam_str)
+        ir_.disam_str = disam_str
+        if not self.cite_irs_by_output[disam_str] then
+          self.cite_irs_by_output[disam_str] = {}
+        end
+        self.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+      end
+    end
+
+    -- util.debug("disambiguate_add_givenname")
+
+    if self.style.citation.disambiguate_add_givenname then
+      local gn_disam_rule = self.style.citation.givenname_disambiguation_rule
+      if gn_disam_rule == "all-names" or gn_disam_rule == "all-names-with-initials" then
+        cite_ir = self:disambiguate_add_givenname_all_names(cite_ir)
+      elseif gn_disam_rule == "by-cite" then
+        cite_ir = self:disambiguate_add_givenname_by_cite(cite_ir)
+      end
+    end
+
+    cite_ir.is_ambiguous = self:check_ambiguity(cite_ir)
+
+    for _, ir_ in ipairs(self.disam_irs) do
+      -- util.debug(ir_.cite_item.id .. ": " .. ir_.disam_str)
+    end
+
+  end
+
+
+  return cite_ir
+end
+
+function CiteProc:collect_irs_with_disambiguate_branch(ir)
+  local irs_with_disambiguate_branch = {}
+  if ir.children then
+    for i, child_ir in ipairs(ir.children) do
+      if child_ir.disambiguate_branch_ir then
+        table.insert(irs_with_disambiguate_branch, child_ir)
+      elseif child_ir.children then
+        util.extend(irs_with_disambiguate_branch,
+          self:collect_irs_with_disambiguate_branch(child_ir))
+      end
+    end
+  end
+  return irs_with_disambiguate_branch
+end
+
+function CiteProc:disambiguate_conditionals(cite_ir)
+  -- util.debug(cite_ir)
+
+  cite_ir.irs_with_disambiguate_branch = self:collect_irs_with_disambiguate_branch(cite_ir)
+
+  local disam_format = DisamStringFormat:new()
+
+  while cite_ir.is_ambiguous do
+    if #cite_ir.irs_with_disambiguate_branch == 0 then
+      break
+    end
+
+    -- util.debug(cite_ir.cite_item.id)
+    -- util.debug(cite_ir.disam_str)
+
+    -- update ambiguous_same_output_irs
+    local ambiguous_same_output_irs = {}
+    for _, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+      if ir_.disam_str == cite_ir.disam_str then
+        table.insert(ambiguous_same_output_irs, ir_)
+      end
+    end
+
+    for _, ir_ in ipairs(ambiguous_same_output_irs) do
+      if #ir_.irs_with_disambiguate_branch > 0 then
+        -- Disambiguation is incremental
+        -- disambiguate_IncrementalExtraText.txt
+        local condition_ir = ir_.irs_with_disambiguate_branch[1]
+        condition_ir.children[1] = condition_ir.disambiguate_branch_ir
+        condition_ir.group_var = condition_ir.disambiguate_branch_ir.group_var
+        table.remove(ir_.irs_with_disambiguate_branch, 1)
+        -- disambiguate_DisambiguateTrueReflectedInBibliography.txt
+        ir_.reference.disambiguate = true
+
+        -- Update ir output
+        local inlines = ir_:flatten(disam_format)
+        local disam_str = disam_format:output(inlines, context)
+        -- util.debug("update: " .. ir_.cite_item.id .. ": " .. disam_str)
+        ir_.disam_str = disam_str
+        if not self.cite_irs_by_output[disam_str] then
+          self.cite_irs_by_output[disam_str] = {}
+        end
+        self.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+      end
+    end
+
+    cite_ir.is_ambiguous = self:check_ambiguity(cite_ir)
+    -- util.debug(cite_ir.is_ambiguous)
+    for _, ir_ in ipairs(self.disam_irs) do
+      -- util.debug(ir_.cite_item.id .. ": " .. ir_.disam_str)
+    end
+
+  end
+  return cite_ir
+end
+
+function CiteProc:check_ambiguity(cite_ir)
+  for _, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+    if ir_.cite_item.id ~= cite_ir.cite_item.id then
+      return true
+    end
+  end
+  return false
+end
+
+function CiteProc:get_same_output_irs(cite_ir)
+  local ambiguous_same_output_irs = {}
+  for _, ir_ in pairs(self.cite_irs_by_output[cite_ir.disam_str]) do
+    if ir_.disam_str == cite_ir.disam_str then
+      table.insert(ambiguous_same_output_irs, ir_)
+    end
+  end
+  return ambiguous_same_output_irs
+end
+
+function CiteProc:disambiguate_add_year_suffix(cite_ir)
+  if not cite_ir.is_ambiguous or not self.style.citation.disambiguate_add_year_suffix then
+    return cite_ir
+  end
+
+  local same_output_irs = self:get_same_output_irs(cite_ir)
+
+  table.sort(same_output_irs, function (a, b)
+    -- return a.ir_index < b.ir_index
+    return a.reference["citation-number"] < b.reference["citation-number"]
+  end)
+
+  local year_suffix_number = 0
+  -- util.debug(cite_ir)
+
+  local disam_format = DisamStringFormat:new()
+
+  for _, ir_ in ipairs(same_output_irs) do
+    ir_.reference.year_suffix_number = nil
+  end
+
+  for _, ir_ in ipairs(same_output_irs) do
+    -- print(ir_.cite_item.id)
+    -- print(ir_.reference)
+    if not ir_.reference.year_suffix_number then
+      year_suffix_number = year_suffix_number + 1
+      ir_.reference.year_suffix_number = year_suffix_number
+      ir_.reference["year-suffix"] = self:render_year_suffix(year_suffix_number)
+    end
+
+    if not ir_.year_suffix_irs then
+      ir_.year_suffix_irs = self:collect_year_suffix_irs(ir_)
+      if #ir_.year_suffix_irs == 0 then
+        -- By default, the year-suffix is appended the first year rendered through cs:date
+        local year_ir = self:find_first_year_ir(ir_)
+        -- util.debug(year_ir)
+        if year_ir then
+          local year_suffix_ir = YearSuffix:new({}, self.style.citation)
+          table.insert(year_ir.children, year_suffix_ir)
+          table.insert(ir_.year_suffix_irs, year_suffix_ir)
+        end
+      end
+    end
+
+    for _, year_suffix_ir in ipairs(ir_.year_suffix_irs) do
+      year_suffix_ir.inlines = {PlainText:new(ir_.reference["year-suffix"])}
+      year_suffix_ir.year_suffix_number = ir_.reference.year_suffix_number
+      year_suffix_ir.group_var = "important"
+    end
+
+    local inlines = ir_:flatten(disam_format)
+    local disam_str = disam_format:output(inlines, context)
+    -- util.debug("update: " .. ir_.cite_item.id .. ": " .. disam_str)
+    ir_.disam_str = disam_str
+    if not self.cite_irs_by_output[disam_str] then
+      self.cite_irs_by_output[disam_str] = {}
+    end
+    self.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+
+  end
+
+  cite_ir.is_ambiguous = false
+
+  return cite_ir
+end
+
+function CiteProc:collect_year_suffix_irs(ir)
+  local year_suffix_irs = {}
+  if ir.children then
+    for i, child_ir in ipairs(ir.children) do
+      if child_ir._type == "YearSuffix" then
+        table.insert(year_suffix_irs, child_ir)
+      elseif child_ir.children then
+        util.extend(year_suffix_irs,
+          self:collect_year_suffix_irs(child_ir))
+      end
+    end
+  end
+  return year_suffix_irs
+end
+
+function CiteProc:find_first_year_ir(ir)
+  if ir.is_year then
+    return ir
+  end
+  if ir.children then
+    for _, child_ir in ipairs(ir.children) do
+      local year_ir = self:find_first_year_ir(child_ir)
+      if year_ir then
+        return year_ir
+      end
+    end
+  end
+  return nil
+end
+
+function CiteProc:render_year_suffix(year_suffix_number)
+  if year_suffix_number <= 0 then
+    return nil
+  end
+  local year_suffix = ""
+  while year_suffix_number > 0 do
+    local i = (year_suffix_number - 1) % 26
+    year_suffix = string.char(i + 97) .. year_suffix
+    year_suffix_number = (year_suffix_number - 1) // 26
+  end
+  -- util.debug(year_suffix)
+  return year_suffix
+end
+
+local function find_first(ir, check)
+  if check(ir) then
+    return ir
+  end
+  if ir.children then
+    for _, child in ipairs(ir.children) do
+      local target_ir = find_first(child, check)
+      if target_ir then
+        return target_ir
+      end
+    end
+  end
+  return nil
+end
+
+function CiteProc:group_cites(irs)
+  local disam_format = DisamStringFormat:new()
+  for _, ir in ipairs(irs) do
+    local first_names_ir = ir.first_names_ir
+    if not first_names_ir then
+      first_names_ir = find_first(ir, function (ir_)
+        return ir_._element == "names"
+      end)
+      if first_names_ir then
+        local inlines = first_names_ir:flatten(disam_format)
+        first_names_ir.disam_str = disam_format:output(inlines, context)
+      end
+      ir.first_names_ir = first_names_ir
+    end
+  end
+
+  local irs_by_name = {}
+  local name_list = {}
+
+  for _, ir in ipairs(irs) do
+    local name_str = ""
+    if ir.first_names_ir then
+      name_str = ir.first_names_ir.disam_str
+    end
+    if not irs_by_name[name_str] then
+      irs_by_name[name_str] = {}
+      table.insert(name_list, name_str)
+    end
+    table.insert(irs_by_name[name_str], ir)
+  end
+
+  local grouped = {}
+  for _, name_str in ipairs(name_list) do
+    local irs_with_same_name = irs_by_name[name_str]
+    for i, ir in ipairs(irs_with_same_name) do
+      if i < #irs_with_same_name then
+        ir.own_delimiter = self.style.citation.cite_group_delimiter
+      end
+      table.insert(grouped, ir)
+    end
+  end
+  return grouped
+end
+
+function CiteProc:collapse_cites(irs)
+  if self.style.citation.collapse == "citation-number" then
+    self:collapse_cites_by_citation_number(irs)
+  elseif self.style.citation.collapse == "year" then
+    self:collapse_cites_by_year(irs)
+  elseif self.style.citation.collapse == "year-suffix" then
+    self:collapse_cites_by_year_suffix(irs)
+  elseif self.style.citation.collapse == "year-suffix-ranged" then
+    self:collapse_cites_by_year_suffix_ranged(irs)
+  end
+end
+
+function CiteProc:collapse_cites_by_citation_number(irs)
+  local cite_groups = {}
+  local current_group = {}
+  local previous_citation_number
+  for i, ir in ipairs(irs) do
+    local citation_number
+    local only_citation_number_ir = self:get_only_citation_number(ir)
+    if only_citation_number_ir then
+      -- Other irs like locators are not rendered.
+      -- collapse_CitationNumberRangesWithAffixesGrouped.txt
+      citation_number = only_citation_number_ir.citation_number
+    end
+    if i == 1 then
+      table.insert(current_group, ir)
+    elseif citation_number and previous_citation_number and
+      previous_citation_number + 1 == citation_number then
+      table.insert(current_group, ir)
     else
-      -- TODO: correct note_index
-      -- TODO: update other citations after disambiguation
-      local citation_str = self.registry.citation_strings[citation_id]
-      if self.registry.citation_strings[citation_id] ~= citation_str then
-        params.bibchange = true
-        self.registry.citation_strings[citation_id] = citation_str
-        table.insert(output, {i - 1, citation_str, citation_id})
+      table.insert(cite_groups, current_group)
+      current_group = {ir}
+    end
+    previous_citation_number = citation_number
+  end
+  table.insert(cite_groups, current_group)
+
+  for _, cite_group in ipairs(cite_groups) do
+    if #cite_group >= 3 then
+      cite_group[1].own_delimiter = util.unicode["en dash"]
+      for i = 2, #cite_group - 1 do
+        cite_group[i].collapse_suppressed = true
       end
+      cite_group[#cite_group].own_delimiter = self.style.citation.after_collapse_delimiter
     end
   end
+end
 
-  return {params, output}
+function CiteProc:get_only_citation_number(ir)
+  if ir.citation_number then
+    return ir
+  end
+  if not ir.children then
+    return nil
+  end
+  local only_citation_number_ir
+  for _, child in ipairs(ir.children) do
+    if child.group_var ~= "missing" then
+      local citation_number_ir = self:get_only_citation_number(child)
+      if citation_number_ir then
+        if only_citation_number_ir then
+          return nil
+        else
+          only_citation_number_ir = citation_number_ir
+        end
+      else
+        return false
+      end
+    end
+  end
+  return only_citation_number_ir
 end
 
-function CiteProc:makeCitationCluster (citation_items)
+function CiteProc:collapse_cites_by_year(irs)
+  local cite_groups = {{}}
+  local previous_name_str
+  for i, ir in ipairs(irs) do
+    local name_str
+    if ir.first_names_ir then
+      name_str = ir.first_names_ir.disam_str
+    end
+    if i == 1 then
+      table.insert(cite_groups[#cite_groups], ir)
+    elseif name_str and name_str == previous_name_str then
+      -- ir.fist_names_ir was set in the cite grouping stage
+      -- TODO: and not previous cite suffix
+      table.insert(cite_groups[#cite_groups], ir)
+    else
+      table.insert(cite_groups, {ir})
+    end
+    previous_name_str = name_str
+  end
+
+  for _, cite_group in ipairs(cite_groups) do
+    if #cite_group > 1 then
+      for i, cite_ir in ipairs(cite_group) do
+        if i > 1 and cite_ir.first_names_ir then
+          cite_ir.first_names_ir.collapse_suppressed = true
+        end
+        if i == #cite_group then
+          cite_ir.own_delimiter = self.style.citation.after_collapse_delimiter
+        elseif i < #cite_group then
+          -- The delimiter depends on the citation > sort.
+          -- https://github.com/citation-style-language/test-suite/issues/39#issuecomment-687901688
+          if cite_ir.cite_item.locator then
+            -- Special hack for
+            cite_ir.own_delimiter = self.style.citation.after_collapse_delimiter
+          elseif self.style.citation.cite_grouping then
+            if self.style.citation.sort then
+              cite_ir.own_delimiter = self.style.citation.cite_group_delimiter
+            else
+              cite_ir.own_delimiter = self.style.citation.layout.delimiter
+            end
+          else
+            if self.style.citation.sort then
+              cite_ir.own_delimiter = self.style.citation.cite_group_delimiter
+            else
+              -- disambiguate_YearCollapseWithInstitution.txt
+              -- disambiguate_InitializeWithButNoDisambiguation.txt ?
+              cite_ir.own_delimiter = self.style.citation.layout.delimiter
+            end
+          end
+        end
+      end
+    end
+  end
+end
+
+local function find_rendered_year_suffix(ir)
+  if ir._type == "YearSuffix" then
+    return ir
+  end
+  if ir.children then
+    for _, child in ipairs(ir.children) do
+      if child.group_var ~= "missing" then
+        local year_suffix = find_rendered_year_suffix(child)
+        if year_suffix then
+          return year_suffix
+        end
+      end
+    end
+  end
+  return nil
+end
+
+function CiteProc:collapse_cites_by_year_suffix(irs)
+  self:collapse_cites_by_year(irs)
+  -- Group by disam_str
+  -- The year-suffix is ommitted in DisamStringFormat
+  local cite_groups = {{}}
+  local previous_ir
+  local previous_year_suffix
+  for i, ir in ipairs(irs) do
+    local year_suffix = find_rendered_year_suffix(ir)
+    ir.rendered_year_suffix_ir = year_suffix
+    if i == 1 then
+      table.insert(cite_groups[#cite_groups], ir)
+    elseif year_suffix and previous_ir.disam_str == ir.disam_str and previous_year_suffix then
+      -- TODO: and not previous cite suffix
+      table.insert(cite_groups[#cite_groups], ir)
+    else
+      table.insert(cite_groups, {ir})
+    end
+    previous_ir = ir
+    previous_year_suffix = year_suffix
+  end
+
+  for _, cite_group in ipairs(cite_groups) do
+    if #cite_group > 1 then
+      for i, cite_ir in ipairs(cite_group) do
+        if i > 1 then
+          -- cite_ir.children = {cite_ir.rendered_year_suffix_ir}
+          -- Set the collapse_suppressed flag rather than removing the child irs.
+          -- This leaves the disamb ir structure unchanged.
+          self:suppress_ir_except_child(cite_ir, cite_ir.rendered_year_suffix_ir)
+        end
+        if i < #cite_group then
+          if self.style.citation.cite_grouping then
+            -- In the current citeproc-js impplementation, explicitly set
+            -- cite-group-delimiter takes precedence over year-suffix-delimiter.
+            -- May be changed in the future.
+            -- https://github.com/citation-style-language/test-suite/issues/50
+            cite_ir.own_delimiter = self.style.citation.cite_group_delimiter
+          else
+            cite_ir.own_delimiter = self.style.citation.year_suffix_delimiter
+          end
+        elseif i == #cite_group then
+          cite_ir.own_delimiter = self.style.citation.after_collapse_delimiter
+        end
+      end
+    end
+  end
+end
+
+function CiteProc:suppress_ir_except_child(ir, target)
+  if ir == target then
+    ir.collapse_suppressed = false
+    return false
+  end
+  ir.collapse_suppressed = true
+  if ir.children then
+    for _, child in ipairs(ir.children) do
+      if child.group_var ~= "missing" and not child.collapse_suppressed then
+        if not self:suppress_ir_except_child(child, target) then
+          ir.collapse_suppressed = false
+        end
+      end
+    end
+  end
+  return ir.collapse_suppressed
+end
+
+function CiteProc:collapse_cites_by_year_suffix_ranged(irs)
+  self:collapse_cites_by_year_suffix(irs)
+  -- Group by disam_str
+  local cite_groups = {{}}
+  local previous_ir
+  local previous_year_suffix
+  for i, ir in ipairs(irs) do
+    local year_suffix_ir = find_rendered_year_suffix(ir)
+    ir.rendered_year_suffix_ir = year_suffix_ir
+    if i == 1 then
+      table.insert(cite_groups[#cite_groups], ir)
+    elseif year_suffix_ir and previous_ir.disam_str == ir.disam_str and previous_year_suffix and
+        year_suffix_ir.year_suffix_number == previous_year_suffix.year_suffix_number + 1 then
+      -- TODO: and not previous cite suffix
+      table.insert(cite_groups[#cite_groups], ir)
+    else
+      table.insert(cite_groups, {ir})
+    end
+    previous_ir = ir
+    previous_year_suffix = year_suffix_ir
+  end
+
+  for _, cite_group in ipairs(cite_groups) do
+    if #cite_group > 2 then
+      for i, cite_ir in ipairs(cite_group) do
+        if i == 1 then
+          cite_ir.own_delimiter = util.unicode["en dash"]
+        elseif i < #cite_group then
+          cite_ir.collapse_suppressed = true
+        end
+      end
+    end
+  end
+end
+
+function CiteProc:makeCitationCluster(citation_items)
   local items = {}
-  for _, cite_item in ipairs(citation_items) do
+  for i, cite_item in ipairs(citation_items) do
     cite_item.id = tostring(cite_item.id)
-    local position_first = (self.registry.registry[cite_item.id] == nil)
     local item_data = self:get_item(cite_item.id)
 
     -- Create a wrapper of the orignal item from registry so that
     -- it may hold different `locator` or `position` values for cites.
-    local item = setmetatable({}, {__index = function (_, key)
-      if cite_item[key] then
-        return cite_item[key]
-      else
-        return item_data[key]
+    local item = setmetatable(cite_item, {__index = item_data})
+
+    -- Use "page" as locator label if missing
+    -- label_PluralWithAmpersand.txt
+    if item.locator and not item.label then
+      item.label = "page"
+    end
+
+    item.position_level = util.position_map["first"]
+    if self.cite_first_note_numbers[cite_item.id] then
+      item.position_level = util.position_map["subsequent"]
+    else
+      self.cite_first_note_numbers[cite_item.id] = 0
+    end
+
+    local preceding_cite
+    if i == 1 then
+      local previous_citation = self.registry.previous_citation
+      if previous_citation then
+        if #previous_citation.citationItems == 1 and previous_citation.citationItems[1].id == item.id then
+          preceding_cite = previous_citation.citationItems[1]
+        end
       end
-    end})
+    elseif citation_items[i - 1].id == item.id then
+      preceding_cite = citation_items[i - 1]
+    end
 
-    if not item.position and position_first then
-      item.position = util.position_map["first"]
+    if preceding_cite then
+      item.position_level = self:_get_cite_position(item, preceding_cite)
     end
+
     table.insert(items, item)
   end
 
@@ -242,16 +1620,19 @@
     self:sort_bibliography()
   end
 
-  local context = {
-    build = {},
-    engine=self,
-  }
-  local res = self.style:render_citation(items, context)
+  local res = self:build_cluster(items)
+
+  -- local context = {
+  --   build = {},
+  --   engine=self,
+  -- }
+  -- local res = self.style:render_citation(items, context)
+
   self.registry.previous_citation = {
     citationID = "pseudo-citation",
     citationItems = items,
     properties = {
-      noteIndex = 1,
+      noteIndex = 0,
     }
   }
   return res
@@ -258,54 +1639,148 @@
 end
 
 function CiteProc:makeBibliography()
-  local items = {}
+  if not self.style.bibliography then
+    return {{}, {}}
+  end
 
+  local output_format = self.output_format
+
+  local res = {}
+
+  self.registry.longest_label = ""
+  self.registry.maxoffset = 0
+
+  for _, id in ipairs(self:get_sorted_refs()) do
+    local ref = self.registry.registry[id]
+
+    local state = IrState:new()
+    local context = Context:new()
+    context.engine = self
+    context.style = self.style
+    context.area = self.style.bibliography
+    context.in_bibliography = true
+    context.locale = self:get_locale(self.lang)
+    context.name_inheritance = self.style.bibliography.name_inheritance
+    context.format = output_format
+    context.id = id
+    context.cite = nil
+    context.reference = self:get_item(id)
+
+    local ir = self.style.bibliography:build_ir(self, state, context)
+    -- util.debug(ir)
+    ir.reference = context.reference
+
+    -- Add year-suffix
+    self:add_bibliography_year_suffix(ir)
+
+    -- The layout output may be empty: sort_OmittedBibRefNonNumericStyle.txt
+    if ir then
+      local flat = ir:flatten(output_format)
+      local str = output_format:output_bibliography_entry(flat, context)
+      table.insert(res, str)
+    end
+  end
+
+  local bib_start = self.output_format.markups["bibstart"]
+  local bib_end = self.output_format.markups["bibend"]
+  if type(bib_start) == "function" then
+    bib_start = bib_start(self)
+  end
+  if type(bib_end) == "function" then
+    bib_end = bib_end(self)
+  end
+
+  local params = {
+    hangingindent = self.style.bibliography.hanging_indent,
+    ["second-field-align"] = self.style.bibliography.second_field_align ~= nil,
+    linespacing = self.style.bibliography.line_spacing,
+    entryspacing = self.style.bibliography.entry_spacing,
+    maxoffset = self.registry.maxoffset,
+    bibstart = bib_start,
+    bibend = bib_end,
+    entry_ids = util.clone(self.registry.reflist),
+  }
+
+  return {params, res}
+end
+
+function CiteProc:get_sorted_refs()
   if self.registry.requires_sorting then
     self:sort_bibliography()
   end
+  return self.registry.reflist
+end
 
-  for _, id in ipairs(self.registry.reflist) do
-    local item = self.registry.registry[id]
-    table.insert(items, item)
+function CiteProc:add_bibliography_year_suffix(ir)
+  if not ir.reference.year_suffix_number then
+    return
   end
 
-  local context = {
-    build = {},
-    engine=self,
-  }
-  local res = self.style:render_biblography(items, context)
-  return res
+  local year_suffix_number = ir.reference.year_suffix_number
+
+  if not ir.year_suffix_irs then
+    ir.year_suffix_irs = self:collect_year_suffix_irs(ir)
+    if #ir.year_suffix_irs == 0 then
+      local year_ir = self:find_first_year_ir(ir)
+      -- util.debug(year_ir)
+      if year_ir then
+        local year_suffix_ir = YearSuffix:new({}, self.style.citation)
+        table.insert(year_ir.children, year_suffix_ir)
+        table.insert(ir.year_suffix_irs, year_suffix_ir)
+      end
+    end
+  end
+
+  for _, year_suffix_ir in ipairs(ir.year_suffix_irs) do
+    year_suffix_ir.inlines = {PlainText:new(ir.reference["year-suffix"])}
+    year_suffix_ir.group_var = "important"
+  end
+
+
 end
 
-function CiteProc:set_formatter(format)
-  self.formatter = formats[format]
+function CiteProc:set_output_format(format)
+  if format == "latex" then
+    self.output_format = LatexWriter:new()
+  elseif format == "html" then
+    self.output_format = HtmlWriter:new()
+  end
 end
 
 function CiteProc:enable_linking()
-  self.linking_enabled = true
+  self.opt.url_link = true
+  self.opt.doi_link = true
 end
 
 function CiteProc:disable_linking()
-  self.linking_enabled = false
+  self.opt.url_link = false
+  self.opt.doi_link = false
 end
 
-function CiteProc.set_base_class (node)
-  if node:is_element() then
-    local name = node:get_element_name()
-    local element_class = nodes[name]
-    if element_class then
-      element_class:set_base_class(node)
-    else
-      element.Element:set_base_class(node)
+function CiteProc.create_element_tree(node)
+  local element_name = node:get_element_name()
+  local element_class = nodes[element_name]
+  local el = nil
+  if element_class then
+    el = element_class:from_node(node)
+  end
+  if el then
+    for i, child in ipairs(node:get_children()) do
+      if child:is_element() then
+        local child_element = CiteProc.create_element_tree(child)
+        if child_element then
+          if not el.children then
+            el.children = {}
+          end
+          table.insert(el.children, child_element)
+        end
+      end
     end
   end
+  return el
 end
 
-function CiteProc:get_style_class()
-  return self.style:get_attribute("class") or "in-text"
-end
-
-function CiteProc:get_item (id)
+function CiteProc:get_item(id)
   local item = self.registry.registry[id]
   if not item then
     item = self:_retrieve_item(id)
@@ -312,22 +1787,24 @@
     if not item then
       return nil
     end
+    item = self:process_extra_note(item)
     table.insert(self.registry.reflist, id)
     item["citation-number"] = #self.registry.reflist
     self.registry.registry[id] = item
     self.registry.requires_sorting = true
   end
-  local res = {}
-  setmetatable(res, {__index = item})
-  return res
+  -- local res = {}
+  -- setmetatable(res, {__index = item})
+  -- return res
+  return item
 end
 
-function CiteProc:_retrieve_item (id)
+function CiteProc:_retrieve_item(id)
   -- Retrieve, copy, and normalize
   local res = {}
   local item = self.sys.retrieveItem(id)
   if not item then
-    util.warning(string.format('Failed to retrieve item "%s"', id))
+    util.warning(string.format('Failed to find entry "%s"', id))
     return nil
   end
 
@@ -334,42 +1811,61 @@
   item.id = tostring(item.id)
 
   for key, value in pairs(item) do
-    if key == "title" then
-      value = self.normalize_string(value)
-    end
     res[key] = value
   end
 
-  if res["page"] and not res["page-first"] then
-    local page_first = util.split(res["page"], "%s*[&,-]%s*")[1]
-    page_first = util.split(page_first, util.unicode["en dash"])[1]
-    res["page-first"] = page_first
-  end
+  -- if res["page"] and not res["page-first"] then
+  --   local page_first = util.split(res["page"], "%s*[&,-]%s*")[1]
+  --   page_first = util.split(page_first, util.unicode["en dash"])[1]
+  --   res["page-first"] = page_first
+  -- end
 
   return res
 end
 
-function CiteProc.normalize_string (str)
-  if not str or str == "" then
-    return str
+-- TODO: Nomalize all inputs
+function CiteProc:process_extra_note(item)
+  if item.note then
+    local note_fields = {}
+    for _, line in ipairs(util.split(item.note, "%s*\r?\n%s*")) do
+      -- util.debug(line)
+      local splits = util.split(line, ":%s+", 1)
+      -- util.debug(splits)
+      if #splits == 2 then
+        local field, value = table.unpack(splits)
+        -- util.debug(field)
+
+        local variable_type = util.variable_types[field]
+        if not item[field] or field == "type" or variable_type == "date" then
+          if variable_type == "number" then
+            item[field] = value
+          elseif variable_type == "date" then
+            item[field] = util.parse_iso_date(value)
+          elseif variable_type == "name" then
+            if not note_fields[field] then
+              note_fields[field] = {}
+            end
+            table.insert(note_fields[field], util.parse_extra_name(value))
+          else
+            item[field] = value
+          end
+        end
+      end
+    end
+    for field, value in pairs(note_fields) do
+      item[field] = value
+    end
   end
-  -- French punctuation spacing
-  if type(str) == "string" then
-    str = string.gsub(str, " ;", util.unicode["narrow no-break space"] .. ";")
-    str = string.gsub(str, " %?", util.unicode["narrow no-break space"] .. "?")
-    str = string.gsub(str, " !", util.unicode["narrow no-break space"] .. "!")
-    str = string.gsub(str, " »", util.unicode["narrow no-break space"] .. "»")
-    str = string.gsub(str, "« ", "«" .. util.unicode["narrow no-break space"])
-  end
-  -- local text = str
-  local text = richtext.new(str)
-  return text
+  return item
 end
 
 function CiteProc:sort_bibliography()
   -- Sort the items in registry according to the `sort` in `bibliography.`
   -- This will update the `citation-number` of each item.
-  local bibliography_sort = self.style:get_path("style bibliography sort")[1]
+  local bibliography_sort = nil
+  if self.style.bibliography and self.style.bibliography.sort then
+    bibliography_sort = self.style.bibliography.sort
+  end
   if not bibliography_sort then
     return
   end
@@ -378,44 +1874,119 @@
     table.insert(items, self.registry.registry[id])
   end
 
-  local context = {
-    engine = self,
-    style = self.style,
-    mode = "bibliography",
-  }
-  context = self.style:process_context(context)
-  context = self.style:get_path("style bibliography")[1]:process_context(context)
+  local state = IrState:new()
+  local context = Context:new()
+  context.engine = self
+  context.style = self.style
+  context.area = self.style.bibliography
+  context.in_bibliography = true
+  context.locale = self:get_locale(self.lang)
+  context.name_inheritance = self.style.bibliography.name_inheritance
+  context.format = SortStringFormat:new()
+  -- context.id = id
+  context.cite = nil
+  -- context.reference = self:get_item(id)
 
-  bibliography_sort:sort(items, context)
+  bibliography_sort:sort(items, state, context)
   self.registry.reflist = {}
+  self.tainted_item_ids = {}
   for i, item in ipairs(items) do
+    if item["citation-number"] ~= i then
+      self.tainted_item_ids[item.id] = true
+    end
     item["citation-number"] = i
-    table.insert(self.registry.reflist, item.id)
+    self.registry.reflist[i] = item.id
   end
   self.registry.requires_sorting = false
 end
 
-function CiteProc:get_system_locale (lang)
+function CiteProc:get_locale(lang)
+  if string.len(lang) == 2 then
+    lang = util.primary_dialects[lang] or lang
+  end
+  local locale = self.locales[lang]
+  if locale then
+    return locale
+  else
+    return self:get_merged_locales(lang)
+  end
+end
+
+function CiteProc:get_merged_locales(lang)
+  local fall_back_locales = {}
+
+  local language = string.sub(lang, 1, 2)
+  local primary_dialect = util.primary_dialects[language]
+
+  -- 1. In-style cs:locale elements
+  --    i. `xml:lang` set to chosen dialect, “de-AT”
+  table.insert(fall_back_locales, self.style.locales[lang])
+
+  --    ii. `xml:lang` set to matching language, “de” (German)
+  if language and language ~= lang then
+    table.insert(fall_back_locales, self.style.locales[language])
+  end
+
+  --    iii. `xml:lang` not set
+  table.insert(fall_back_locales, self.style.locales["@generic"])
+
+  -- 2. Locale files
+  --    iv. `xml:lang` set to chosen dialect, “de-AT”
+  if lang then
+    table.insert(fall_back_locales, self:get_system_locale(lang))
+  end
+
+  --    v. `xml:lang` set to matching primary dialect, “de-DE” (Standard German)
+  --       (only applicable when the chosen locale is a secondary dialect)
+  if primary_dialect and primary_dialect ~= lang then
+    table.insert(fall_back_locales, self:get_system_locale(primary_dialect))
+  end
+
+  --    vi. `xml:lang` set to “en-US” (American English)
+  if lang ~= "en-US" and primary_dialect ~= "en-US" then
+    table.insert(fall_back_locales, self:get_system_locale("en-US"))
+  end
+
+  -- Merge locales
+
+  local locale = Locale:new()
+  for i = #fall_back_locales, 1, -1 do
+    local fall_back_locale = fall_back_locales[i]
+    locale:merge(fall_back_locale)
+  end
+
+  self.locales[lang] = locale
+  return locale
+end
+
+function CiteProc:get_system_locale(lang)
   local locale = self.system_locales[lang]
-  if not locale then
-    locale = self.sys.retrieveLocale(lang)
-    if not locale then
-      util.warning(string.format("Failed to retrieve locale \"%s\"", lang))
-      return nil
-    end
-    if type(locale) == "string" then
-      locale = dom.parse(locale)
-    end
-    locale:traverse_elements(self.set_base_class)
-    locale = locale:get_path("locale")[1]
-    locale:root_node().engine = self
-    locale:root_node().style = self.style
-    self.system_locales[lang] = locale
+  if locale then
+    return locale
   end
+
+  local locale_str = self.sys.retrieveLocale(lang)
+  if not locale_str then
+    util.warning(string.format("Failed to retrieve locale \"%s\"", lang))
+    return nil
+  end
+  local locale_xml = dom.parse(locale_str)
+  local root_element = locale_xml:get_path("locale")[1]
+  locale = Locale:from_node(root_element)
+  self.system_locales[lang] = locale
   return locale
 end
 
 
+function CiteProc:get_style_class()
+  if self.style and self.style.class then
+    return self.style.class
+  else
+    return nil
+  end
+end
+
+
 engine.CiteProc = CiteProc
 
 return engine

Deleted: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-formats.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-formats.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-formats.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,211 +0,0 @@
---
--- Copyright (c) 2021-2022 Zeping Lee
--- Released under the MIT license.
--- Repository: https://github.com/zepinglee/citeproc-lua
---
-
-local util = require("citeproc-util")
-
-
-local formats = {}
-
-formats.html = {
-  ["text_escape"] = function (str)
-    str = string.gsub(str, "%&", "&")
-    str = string.gsub(str, "<", "<")
-    str = string.gsub(str, ">", ">")
-    for char, sub in pairs(util.superscripts) do
-      str = string.gsub(str, char, "<sup>" .. sub .. "</sup>")
-    end
-    return str
-  end,
-  ["bibstart"] = "<div class=\"csl-bib-body\">\n",
-  ["bibend"] = "</div>",
-  ["@font-style/italic"] = "<i>%s</i>",
-  ["@font-style/oblique"] = "<em>%s</em>",
-  ["@font-style/normal"] = '<span style="font-style:normal;">%s</span>',
-  ["@font-variant/small-caps"] = '<span style="font-variant:small-caps;">%s</span>',
-  ["@font-variant/normal"] = '<span style="font-variant:normal;">%s</span>',
-  ["@font-weight/bold"] = "<b>%s</b>",
-  ["@font-weight/normal"] = '<span style="font-weight:normal;">%s</span>',
-  ["@font-weight/light"] = false,
-  ["@text-decoration/none"] = '<span style="text-decoration:none;">%s</span>',
-  ["@text-decoration/underline"] = '<span style="text-decoration:underline;">%s</span>',
-  ["@vertical-align/sup"] = "<sup>%s</sup>",
-  ["@vertical-align/sub"] = "<sub>%s</sub>",
-  ["@vertical-align/baseline"] = '<span style="baseline">%s</span>',
-  ["@quotes/true"] = function (str, context)
-    local open_quote = context.style:get_term("open-quote"):render(context)
-    local close_quote = context.style:get_term("close-quote"):render(context)
-    return open_quote .. str .. close_quote
-  end,
-  ["@quotes/inner"] = function (str, context)
-    local open_quote = context.style:get_term("open-inner-quote"):render(context)
-    local close_quote = context.style:get_term("close-inner-quote"):render(context)
-    return open_quote .. str .. close_quote
-  end,
-  ["@bibliography/entry"] = function (str, context)
-    return '<div class="csl-entry">' .. str .. "</div>\n"
-  end,
-  ["@display/block"] = function (str, state)
-    return '\n\n    <div class="csl-block">' .. str .. "</div>\n"
-  end,
-  ["@display/left-margin"] = function (str, state)
-    return '\n    <div class="csl-left-margin">' .. str .. "</div>"
-  end,
-  ["@display/right-inline"] = function (str, state)
-    str = util.rstrip(str)
-    return '<div class="csl-right-inline">' .. str .. "</div>\n  "
-  end,
-  ["@display/indent"] = function (str, state)
-    return '<div class="csl-indent">' .. str .. "</div>\n  "
-  end,
-  ["@URL/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      return string.format('<a href="%s">%s</a>', str, str)
-    else
-      return str
-    end
-  end,
-  ["@DOI/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://doi.org/" .. str;
-      end
-      return string.format('<a href="%s">%s</a>', href, str)
-    else
-      return str
-    end
-  end,
-  ["@PMID/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://www.ncbi.nlm.nih.gov/pubmed/" .. str;
-      end
-      return string.format('<a href="%s">%s</a>', href, str)
-    else
-      return str
-    end
-  end,
-  ["@PMCID/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. str;
-      end
-      return string.format('<a href="%s">%s</a>', href, str)
-    else
-      return str
-    end
-  end,
-}
-
-formats.latex = {
-  ["text_escape"] = function (str)
-    str = str:gsub("\\", "\\textbackslash")
-    str = str:gsub("#", "\\#")
-    str = str:gsub("%$", "\\$")
-    str = str:gsub("%%", "\\%%")
-    str = str:gsub("&", "\\&")
-    str = str:gsub("{", "\\{")
-    str = str:gsub("}", "\\}")
-    str = str:gsub("_", "\\_")
-    str = str:gsub(util.unicode["no-break space"], "~")
-    for char, sub in pairs(util.superscripts) do
-      str = string.gsub(str, char, "\\textsuperscript{" .. sub .. "}")
-    end
-    return str
-  end,
-  ["bibstart"] = function (context)
-    return string.format("\\begin{thebibliography}{%s}\n", context.build.longest_label)
-  end,
-  ["bibend"] = "\\end{thebibliography}",
-  ["@font-style/normal"] = "{\\normalshape %s}",
-  ["@font-style/italic"] = "\\emph{%s}",
-  ["@font-style/oblique"] = "\\textsl{%s}",
-  ["@font-variant/normal"] = "{\\normalshape %s}",
-  ["@font-variant/small-caps"] = "\\textsc{%s}",
-  ["@font-weight/normal"] = "\\fontseries{m}\\selectfont %s",
-  ["@font-weight/bold"] = "\\textbf{%s}",
-  ["@font-weight/light"] = "\\fontseries{l}\\selectfont %s",
-  ["@text-decoration/none"] = false,
-  ["@text-decoration/underline"] = "\\underline{%s}",
-  ["@vertical-align/sup"] = "\\textsuperscript{%s}",
-  ["@vertical-align/sub"] = "\\textsubscript{%s}",
-  ["@vertical-align/baseline"] = false,
-  ["@quotes/true"] = function (str, context)
-    local open_quote = context.style:get_term("open-quote"):render(context)
-    local close_quote = context.style:get_term("close-quote"):render(context)
-    return open_quote .. str .. close_quote
-  end,
-  ["@quotes/inner"] = function (str, context)
-    local open_quote = context.style:get_term("open-inner-quote"):render(context)
-    local close_quote = context.style:get_term("close-inner-quote"):render(context)
-    return open_quote .. str .. close_quote
-  end,
-  ["@bibliography/entry"] = function (str, context)
-    if not string.match(str, "\\bibitem") then
-      str =  "\\bibitem{".. context.item.id .. "}\n" .. str
-    end
-    return str .. "\n"
-  end,
-  ["@display/block"] = function (str, state)
-    return str
-  end,
-  ["@display/left-margin"] = function (str, state)
-    if #str > #state.build.longest_label then
-      state.build.longest_label = str
-    end
-    if string.match(str, "%]") then
-      str = "{" .. str .. "}"
-    end
-    return string.format("\\bibitem[%s]{%s}\n", str, state.item.id)
-  end,
-  ["@display/right-inline"] = function (str, state)
-    return str
-  end,
-  ["@display/indent"] = function (str, state)
-    return str
-  end,
-  ["@URL/true"] = function (str, state)
-    return "\\url{" .. str .. "}"
-  end,
-  ["@DOI/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://doi.org/" .. str;
-      end
-      return string.format("\\href{%s}{%s}", href, str)
-    else
-      return str
-    end
-  end,
-  ["@PMID/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://www.ncbi.nlm.nih.gov/pubmed/" .. str;
-      end
-      return string.format("\\href{%s}{%s}", href, str)
-    else
-      return str
-    end
-  end,
-  ["@PMCID/true"] = function (str, state)
-    if state.engine.linking_enabled then
-      local href = str
-      if not string.match(href, "^https?://") then
-        href = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. str;
-      end
-      return string.format("\\href{%s}{%s}", href, str)
-    else
-      return str
-    end
-  end,
-}
-
-
-return formats

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,153 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local irnode = {}
+
+local util = require("citeproc-util")
+
+
+local IrNode = {
+  _element = nil,
+  _type = "IrNode",
+  _base_class = "IrNode",
+  text = nil,
+  formatting = nil,
+  affixes = nil,
+  children = nil,
+  delimiter = nil,
+}
+
+function IrNode:new(children, element)
+  local o = {
+    _element = element.element_name,
+    _type = self._type,
+    children = children,
+    group_var = "plain",
+  }
+
+  o.group_var = "missing"
+  for _, child_ir in ipairs(children) do
+    if child_ir.group_var == "important" then
+      o.group_var = "important"
+      break
+    elseif child_ir.group_var == "plain" then
+      o.group_var = "plain"
+    end
+  end
+
+  o.person_name_irs = {}
+  if children then
+    for _, child in ipairs(children) do
+      if child.person_name_irs then
+        util.extend(o.person_name_irs, child.person_name_irs)
+      end
+    end
+  end
+
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function IrNode:derive(type)
+  local o = {
+    _type = type,
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function IrNode:flatten(format)
+  return format:flatten_ir(self)
+end
+
+function IrNode:capitalize_first_term()
+  -- util.debug(self)
+  if self._type == "Rendered" and self.element and (self.element.term == "ibid" or self.element.term == "and") then
+    self.inlines[1]:capitalize_first_term()
+  elseif self._type == "SeqIr" and self.children[1] then
+    self.children[1]:capitalize_first_term()
+  end
+end
+
+
+
+local Rendered = IrNode:derive("Rendered")
+
+function Rendered:new(inlines, element)
+  local o = {
+    _element = element.element_name,
+    _type = self._type,
+    element = element,  -- required for capitalizing first term
+    inlines = inlines,
+    group_var = "plain",
+  }
+
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+
+local YearSuffix = IrNode:derive("YearSuffix")
+
+function YearSuffix:new(inlines, element)
+  local o = {
+    _element = element.element_name,
+    _type = self._type,
+    element = element,
+    inlines = inlines,
+    group_var = "plain",
+  }
+
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+
+local NameIr = IrNode:derive("NameIr")
+
+
+local PersonNameIr = IrNode:derive("PersonNameIr")
+
+function PersonNameIr:new(inlines, element)
+  local o = {
+    _element = element.element_name,
+    _type = self._type,
+    inlines = inlines,
+    group_var = "plain",
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+
+local SeqIr = IrNode:derive("SeqIr")
+
+-- function SeqIr:new(children)
+--   o = IrNode.new(self, children)
+--   local o = {
+--     children = children,
+--     group_var = "plain",
+--   }
+--   setmetatable(o, self)
+--   self.__index = self
+--   return o
+-- end
+
+
+
+irnode.IrNode = IrNode
+irnode.Rendered = Rendered
+irnode.YearSuffix = YearSuffix
+irnode.NameIr = NameIr
+irnode.PersonNameIr = PersonNameIr
+irnode.SeqIr = SeqIr
+
+return irnode


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,302 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local core = {}
+
+local citeproc = require("citeproc")
+local util = citeproc.util
+require("lualibs")
+
+
+core.locale_file_format = "csl-locales-%s.xml"
+core.uncited_ids = {}
+core.uncite_all_items = false
+
+function core.error(message)
+  if luatexbase then
+    luatexbase.module_error("csl", message)
+  else
+    util.error(message)
+  end
+end
+
+function core.warning(message)
+  if luatexbase then
+    luatexbase.module_warning("csl", message)
+  else
+    util.warning(message)
+  end
+end
+
+function core.info(message)
+  if luatexbase then
+    luatexbase.module_info("csl", message)
+  else
+    util.info(message)
+  end
+end
+
+
+function core.read_file(file_name, ftype, file_info)
+  file_info = file_info or "file"
+  local path = kpse.find_file(file_name, ftype)
+  if not path then
+    if ftype and not util.endswith(file_name, ftype) then
+      file_name = file_name .. ftype
+    end
+    core.error(string.format('Cannot find %s "%s"', file_info, file_name))
+    return nil
+  end
+  local file = io.open(path, "r")
+  if not file then
+    core.error(string.format('Cannot open %s "%s"', file_info, path))
+    return nil
+  end
+  local contents = file:read("*a")
+  file:close()
+  return contents
+end
+
+
+local function read_data_file(data_file)
+  local file_name = data_file
+  local extension = nil
+  local contents = nil
+
+  if util.endswith(data_file, ".json") then
+    extension = ".json"
+    contents = core.read_file(data_file, nil, "database file")
+  elseif util.endswith(data_file, ".bib") then
+    extension = ".bib"
+    contents = core.read_file(data_file, "bib", "database file")
+  else
+    local path = kpse.find_file(data_file .. ".json")
+    if path then
+      file_name = data_file .. ".json"
+      extension = ".json"
+      contents = core.read_file(data_file .. ".json", nil, "database file")
+    else
+      path = kpse.find_file(data_file, "bib")
+      if path then
+        file_name = data_file .. ".bib"
+        extension = ".bib"
+        contents = core.read_file(data_file, "bib", "database file")
+      else
+        core.error(string.format('Cannot find database file "%s"', data_file .. ".json"))
+      end
+    end
+  end
+
+  local csl_items = nil
+
+  if extension == ".json" then
+    csl_items = utilities.json.tolua(contents)
+  elseif extension == ".bib" then
+    csl_items = citeproc.parse_bib(contents)
+  end
+
+  return file_name, csl_items
+end
+
+
+local function read_data_files(data_files)
+  local bib = {}
+  for _, data_file in ipairs(data_files) do
+    local file_name, csl_items = read_data_file(data_file)
+
+    -- TODO: parse bib entries on demand
+    for _, item in ipairs(csl_items) do
+      local id = item.id
+      if bib[id] then
+        core.warning(string.format('Duplicate entry key "%s" in "%s".', id, file_name))
+      else
+        bib[id] = item
+      end
+    end
+  end
+  return bib
+end
+
+
+
+function core.make_citeproc_sys(data_files)
+  core.bib = read_data_files(data_files)
+  local citeproc_sys = {
+    retrieveLocale = function (lang)
+      local locale_file_format = core.locale_file_format or "locales-%s.xml"
+      local filename = string.format(locale_file_format, lang)
+      return core.read_file(filename)
+    end,
+    retrieveItem = function (id)
+      local res = core.bib[id]
+      -- if not res then
+      --   core.warning(string.format('Failed to find entry "%s".', id))
+      -- end
+      return res
+    end
+  }
+
+  return citeproc_sys
+end
+
+function core.init(style_name, data_files, lang)
+  if style_name == "" or #data_files == 0 then
+    return nil
+  end
+  local style = core.read_file(style_name .. ".csl", nil, "style file")
+  if not style then
+    core.error(string.format('Failed to load style "%s.csl"', style_name))
+    return nil
+  end
+
+  local force_lang = nil
+  if lang and lang ~= "" then
+    force_lang = true
+  else
+    lang = nil
+  end
+
+  local citeproc_sys = core.make_citeproc_sys(data_files)
+  local engine = citeproc.new(citeproc_sys, style, lang, force_lang)
+  return engine
+end
+
+local function parse_latex_seq(s)
+  local t = {}
+  for item in string.gmatch(s, "(%b{})") do
+    item = string.sub(item, 2, -2)
+    table.insert(t, item)
+  end
+  return t
+end
+
+local function parse_latex_prop(s)
+  local t = {}
+  for key, value in string.gmatch(s, "([%w%-]+)%s*=%s*(%b{})") do
+    value = string.sub(value, 2, -2)
+    if value == "true" then
+      value = true
+    elseif value == "false" then
+      value = false
+    end
+    t[key] = value
+  end
+  return t
+end
+
+function core.make_citation(citation_info)
+  -- `citation_info`: "citationID={ITEM-1 at 2},citationItems={{id={ITEM-1},label={page},locator={6}}},properties={noteIndex={3}}"
+  local citation = parse_latex_prop(citation_info)
+  assert(citation.citationID)
+  assert(citation.citationItems)
+  assert(citation.properties)
+
+ citation.citationItems = parse_latex_seq(citation.citationItems)
+
+  for i, item in ipairs(citation.citationItems) do
+    citation.citationItems[i] = parse_latex_prop(item)
+  end
+
+  citation.properties = parse_latex_prop(citation.properties)
+  local note_index = citation.properties.noteIndex
+  if not note_index or note_index == "" then
+    citation.properties.noteIndex = 0
+  elseif type(note_index) == "string" and string.match(note_index, "^%d+$") then
+    citation.properties.noteIndex = tonumber(note_index)
+  else
+    util.error(string.format('Invalid note index "%s".', note_index))
+  end
+
+  return citation
+end
+
+
+function core.process_citations(engine, citations)
+  local citations_pre = {}
+
+  core.update_uncited_items(engine, citations)
+  local citation_strings = {}
+
+  for _, citation in ipairs(citations) do
+    if citation.citationID ~= "@nocite" then
+      local citation_str = engine:process_citation(citation)
+        citation_strings[citation.citationID] = citation_str
+
+      table.insert(citations_pre, {citation.citationID, citation.properties.noteIndex})
+    end
+  end
+
+  return citation_strings
+end
+
+
+function core.update_uncited_items(engine, citations)
+  -- util.debug(core.uncite_all_items)
+  if core.uncite_all_items then
+    -- \nocite{*}
+    for id, _ in pairs(core.bib) do
+      table.insert(core.uncited_ids, id)
+    end
+  else
+    for _, citation in ipairs(citations) do
+      if citation.citationID == "@nocite" then
+        for _, cite_item in ipairs(citation.citationItems) do
+          table.insert(core.uncited_ids, cite_item.id)
+          if cite_item.id == "*" then
+            for id, _ in pairs(core.bib) do
+              table.insert(core.uncited_ids, id)
+            end
+          else
+            table.insert(core.uncited_ids, cite_item.id)
+          end
+        end
+      end
+    end
+  end
+  engine:updateUncitedItems(core.uncited_ids)
+end
+
+
+function core.make_bibliography(engine)
+  local result = engine:makeBibliography()
+
+  local params = result[1]
+  local bib_items = result[2]
+
+  local res = ""
+
+  local bib_options = ""
+  if params["hangingindent"] then
+    bib_options = bib_options .. "\n  hanging-indent = true,"
+  end
+  if params["linespacing"] then
+    bib_options = bib_options .. string.format("\n  line-spacing = %d,", params["linespacing"])
+  end
+  if params["entryspacing"] then
+    bib_options = bib_options .. string.format("\n  entry-spacing = %d,", params["entryspacing"])
+  end
+
+  if bib_options ~= "" then
+    bib_options = "\\cslsetup{" .. bib_options .. "\n}\n\n"
+    res = res .. bib_options
+  end
+
+  if params.bibstart then
+    res = res .. params.bibstart
+  end
+
+  for _, bib_item in ipairs(bib_items) do
+    res = res .. "\n" .. bib_item
+  end
+
+  if params.bibend then
+    res = res .. "\n" .. params.bibend
+  end
+  return res
+end
+
+
+return core


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,150 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local csl = {}
+
+local citeproc = require("citeproc")
+local util = citeproc.util
+require("lualibs")
+local core = require("citeproc-latex-core")
+
+
+csl.initialized = "false"
+csl.id_list = {}
+csl.id_map = {}  -- Boolean map for checking if id in list
+csl.uncited_id_list = {}
+csl.uncited_id_map = {}
+csl.citations_pre = {}
+
+csl.preview_mode = false  -- Whether to use citeproc:preview_citation
+
+
+function csl.error(str)
+  luatexbase.module_error("csl", str)
+end
+function csl.warning(str)
+  luatexbase.module_warning("csl", str)
+end
+function csl.info(str)
+  luatexbase.module_info("csl", str)
+end
+
+
+function csl.init(style_name, bib_files, lang)
+  bib_files = util.split(util.strip(bib_files), "%s*,%s*")
+
+  csl.engine = core.init(style_name, bib_files, lang)
+
+  if csl.engine then
+    csl.initialized = "true"
+  else
+    return
+  end
+
+  -- csl.init is called via \AtBeginDocument and it's executed after
+  -- loading .aux file.  The csl.id_list are already registered.
+  csl.engine:updateItems(csl.id_list)
+
+  if core.uncite_all_items then
+    for id, _ in pairs(core.bib) do
+      if not csl.uncited_id_map[id] then
+        table.insert(csl.uncited_id_list, id)
+        csl.uncited_id_map[id] = true
+      end
+    end
+  end
+  csl.engine:updateUncitedItems(csl.uncited_id_list)
+  csl.style_class = csl.engine:get_style_class()
+end
+
+
+function csl.get_style_class()
+  tex.sprint(csl.style_class)
+end
+
+
+function csl.register_citation_info(citation_info)
+  local citation = core.make_citation(citation_info)
+  for _, cite_item in ipairs(citation.citationItems) do
+    if not csl.id_map[cite_item.id] then
+      table.insert(csl.id_list, cite_item.id)
+      csl.id_map[cite_item.id] = true
+    end
+  end
+end
+
+
+function csl.enable_linking()
+  csl.engine:enable_linking()
+end
+
+
+function csl.cite(citation_info)
+  -- "citationID={ITEM-UNAVAILABLE at 1},citationItems={{id={ITEM-UNAVAILABLE}}},properties={noteIndex={1}}"
+  -- util.debug(citation_info)
+  if not csl.engine then
+    csl.error("CSL engine is not initialized.")
+  end
+
+  local citation = core.make_citation(citation_info)
+
+  local citation_str
+  if csl.preview_mode then
+    -- TODO: preview mode in first pass of \blockquote of csquotes
+    -- citation_str = csl.engine:preview_citation(citation)
+    citation_str = ""
+  else
+    citation_str = csl.engine:process_citation(citation)
+  end
+
+  tex.sprint(string.format("{%s}{%s}", csl.style_class, citation_str))
+  -- tex.sprint(citation_str)
+
+  table.insert(csl.citations_pre, {citation.citationID, citation.properties.noteIndex})
+end
+
+
+function csl.nocite(ids_string)
+  local uncited_ids = util.split(ids_string, "%s*,%s*")
+  for _, uncited_id in ipairs(uncited_ids) do
+    if uncited_id == "*" then
+      if csl.engine then
+        for id, _ in pairs(core.bib) do
+          if not csl.uncited_id_map[id] then
+            table.insert(csl.uncited_id_list, id)
+            csl.uncited_id_map[id] = true
+          end
+        end
+        csl.engine:updateUncitedItems(csl.uncited_id_list)
+      else
+        core.uncite_all_items = true
+      end
+    else
+      if not csl.uncited_id_map[uncited_id] then
+        table.insert(csl.uncited_id_list, uncited_id)
+        csl.uncited_id_map[uncited_id] = true
+        if csl.engine then
+          csl.engine:updateUncitedItems(csl.uncited_id_list)
+        end
+      end
+    end
+  end
+end
+
+
+function csl.bibliography()
+  if not csl.engine then
+    csl.error("CSL engine is not initialized.")
+    return
+  end
+
+  local result = core.make_bibliography(csl.engine)
+
+  tex.print(util.split(result, "\n"))
+end
+
+
+return csl


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-lua.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-lua.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-lua.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,12 @@
+#!/usr/bin/env texlua
+
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+kpse.set_program_name("luatex")
+
+local cli = require("citeproc-cli")
+cli.main()


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-lua.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-choose.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-choose.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-choose.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,124 +6,300 @@
 
 local choose = {}
 
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local SeqIr = require("citeproc-ir-node").SeqIr
 local util = require("citeproc-util")
 
+-- [Choose](https://docs.citationstyles.org/en/stable/specification.html#choose)
+local Choose = Element:derive("choose")
 
-local Choose = element.Element:new()
+function Choose:from_node(node)
+  local o = Choose:new()
+  o.children = {}
+  o:process_children_nodes(node)
+  if #o.children == 0 then
+    return nil
+  end
+  return o
+end
 
-function Choose:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  for i, child in ipairs(self:get_children()) do
-    if child:is_element() then
-      local result, status = child:render(item, context)
-      if status then
-        return result
+function Choose:build_ir(engine, state, context)
+  local branch
+  local branch_ir
+  for _, child in ipairs(self.children) do
+    if child:evaluate_conditions(engine, state, context) then
+      branch = child
+      branch_ir = child:build_ir(engine, state, context)
+      break
+    end
+  end
+
+  if not branch_ir then
+    branch_ir = SeqIr:new({}, self)
+    branch_ir.group_var = "missing"
+  end
+
+  local ir = SeqIr:new({branch_ir}, self)
+  ir.group_var = branch_ir.group_var
+  ir.name_count = branch_ir.name_count
+  ir.sort_key = branch_ir.sort_key
+
+  if not context.disambiguate then
+    context.disambiguate = true
+
+    for _, child in ipairs(self.children) do
+      if child:evaluate_conditions(engine, state, context) then
+        if child ~= branch then
+          ir.disambiguate_branch_ir = child:build_ir(engine, state, context)
+        end
+        break
       end
     end
+
+    context.disambiguate = false
   end
-  return nil
+
+  return ir
 end
 
 
-local If = element.Element:new()
+local Condition = {
+  condition = nil,
+  value = nil,
+  match_type = "all",
+}
 
-If.render = function (self, item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  local results = {}
+function Condition:new(condition, value, match_type)
+  local o = {
+    condition = condition,
+    value = value,
+    match_type = match_type or "all"
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
 
-  local variable_names = context.options["is-numeric"]
-  if variable_names then
-    for _, variable_name in ipairs(util.split(variable_names)) do
-      local variable = self:get_variable(item, variable_name, context)
-      table.insert(results, util.is_numeric(variable))
-    end
+
+local If = Element:derive("if", {
+  conditions = nil,
+  match = "all"
+})
+
+function If:new()
+  local o = Element.new(self)
+  o.conditions = {}
+  o.match = "all"
+  return o
+end
+
+function If:from_node(node)
+  local o = If:new()
+  -- TODO: disambiguate
+  -- o:set_bool_attribute(node, "disambiguate")
+
+  o:add_conditions(node, "disambiguate")
+  o:add_conditions(node, "is-numeric")
+  o:add_conditions(node, "is-uncertain-date")
+  o:add_conditions(node, "locator")
+  o:add_conditions(node, "position")
+  o:add_conditions(node, "type")
+  o:add_conditions(node, "variable")
+
+  o.match = node:get_attribute("match")
+
+  o:process_children_nodes(node)
+
+  return o
+end
+
+function If:add_conditions(node, attribute)
+  local values = node:get_attribute(attribute)
+  if not values then
+    return
   end
+  for _, value in ipairs(util.split(values)) do
+    local condition = Condition:new(attribute, value, self.match)
+    table.insert(self.conditions, condition)
+  end
+end
 
-  variable_names = context.options["is-uncertain-date"]
-  if variable_names then
-    for _, variable_name in ipairs(util.split(variable_names)) do
-      local variable = self:get_variable(item, variable_name, context)
-      table.insert(results, util.is_uncertain_date(variable))
-    end
+function If:build_children_ir(engine, state, context)
+  if not self.children then
+    return nil
   end
+  local irs = {}
+  local name_count
+  local ir_sort_key
+  local group_var = "plain"
 
-  local locator_types = context.options["locator"]
-  if locator_types then
-    for _, locator_type in ipairs(util.split(locator_types)) do
-      local locator_label = item.label or "page"
-      local res = locator_label == locator_type
-      if locator_type == "sub-verbo" then
-        res = locator_label == "sub-verbo" or locator_label == "sub verbo"
+  for _, child_element in ipairs(self.children) do
+    local child_ir = child_element:build_ir(engine, state, context)
+
+    if child_ir then
+      local child_group_var = child_ir.group_var
+      if child_group_var == "important" then
+        group_var = "important"
+      elseif child_group_var == "missing" and child_ir._type ~= "YearSuffix" then
+        if group_var == "plain" then
+          group_var = "missing"
+        end
       end
-      table.insert(results, res)
-    end
-  end
 
-  local positions = context.options["position"]
-  if positions then
-    for _, position in ipairs(util.split(positions)) do
-      local res = false
-      if context.mode == "citation" then
-        if position == "first" then
-          res = (item.position == util.position_map["first"])
-        elseif position == "near-note" then
-          res = item["near-note"] ~= nil and item["near-note"] ~= false
-        else
-          res = (item.position >= util.position_map[position])
+      if child_ir.name_count then
+        if not name_count then
+          name_count = 0
         end
+        name_count = name_count + child_ir.name_count
       end
-      table.insert(results, res)
+
+      if child_ir.sort_key ~= nil then
+        ir_sort_key = child_ir.sort_key
+      end
+
+      -- The condition can be simplified
+      table.insert(irs, child_ir)
     end
   end
 
-  local type_names = context.options["type"]
-  if type_names then
-    for _, type_name in ipairs(util.split(type_names)) do
-      table.insert(results, item["type"] == type_name)
+  if #irs == 0 then
+    group_var = "missing"
+  end
+
+  local ir = SeqIr:new(irs, self)
+  ir.name_count = name_count
+  ir.sort_key = ir_sort_key
+  ir.group_var = group_var
+  return ir
+end
+
+function If:build_ir(engine, state, context)
+  if not self:evaluate_conditions(engine, state, context) then
+    return nil
+  end
+
+  local ir = self:build_children_ir(engine, state, context)
+  if not ir then
+    return nil
+  end
+
+  ir.should_inherit_delim = true
+  return ir
+end
+
+function If:evaluate_conditions(engine, state, context)
+  local res = false
+  -- util.debug(self.conditions)
+  for _, condition in ipairs(self.conditions) do
+    if self:evaluate_condition(condition, state, context) then
+      if self.match == "any" then
+        return true
+      elseif self.match == "none" then
+        return false
+      end
+    else
+      if self.match == "all" then
+        return false
+      end
     end
   end
+  if self.match == "any" then
+    return false
+  else
+    return true
+  end
+end
 
-  variable_names = context.options["variable"]
-  if variable_names then
-    for _, variable_name in ipairs(util.split(variable_names)) do
-      local variable = self:get_variable(item, variable_name, context)
-      local res = (variable ~= nil and variable ~= "")
-      table.insert(results, res)
+function If:evaluate_condition(condition, state, context)
+  -- util.debug(context.cite)
+  -- util.debug(condition)
+  if condition.condition == "disambiguate" then
+    return context.disambiguate or (context.in_bibliography and context.reference.disambiguate)
+
+  elseif condition.condition == "is-numeric" then
+    local variable = context:get_variable(condition.value)
+    return util.is_numeric(variable)
+
+  elseif condition.condition == "is-uncertain-date" then
+    local variable = context:get_variable(condition.value)
+    -- TODO
+    return self:is_uncertain_date(variable)
+
+  elseif condition.condition == "locator" then
+    local locator_label = context:get_variable("label")
+    if locator_label == "sub verbo" then
+      locator_label = "sub-verbo"
     end
+    return locator_label == condition.value
+
+  elseif condition.condition == "position" then
+    return self:check_position(condition.value, context)
+
+  elseif condition.condition == "type" then
+    local item_type = context:get_variable("type")
+    return item_type == condition.value
+
+  elseif condition.condition == "variable" then
+    local var = condition.value
+    if var == "locator" or var == "label" then
+      if context.in_bibliography then
+        return false
+      else
+        return context.cite[var] ~= nil
+      end
+    else
+      local res = context.reference[var] ~= nil
+      return res
+    end
   end
+end
 
-  local match = context.options["match"] or "all"
-  local status = false
-  if match == "any" then
-    status = util.any(results)
-  elseif match == "none" then
-    status = not util.any(results)
-  else
-    status = util.all(results)
+function If:is_uncertain_date(variable)
+  if variable == nil then
+    return false
   end
-  if status then
-    return self:render_children(item, context), status
+  local circa = variable["circa"]
+  return circa and circa ~= ""
+end
+
+function If:check_position(position, context)
+  -- util.debug(context.cite)
+  if context.in_bibliography then
+    return false
+  end
+
+  local position_level = context.cite.position or context.cite.position_level or 0
+  -- context.cite.position is for hacking in debugging
+  -- bugreports_DemoPageFullCiteCruftOnSubsequent.txt
+  if position == "first" then
+    return (position_level == util.position_map["first"])
+  elseif position == "near-note" then
+    return context.cite["near-note"] or context.cite.near_note
   else
-    return nil, false
+    return (position_level >= util.position_map[position])
   end
 end
 
 
-local ElseIf = If:new()
+local ElseIf = If:derive("else-if")
 
 
-local Else = element.Element:new()
+local Else = Element:derive("else")
 
-Else.render = function (self, item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  return self:render_children(item, context), true
+function Else:from_node(node)
+  local o = Else:new()
+  o:process_children_nodes(node)
+  return o
 end
 
+function Else:evaluate_conditions(engine, state, context)
+  return true
+end
 
+Else.build_children_ir = If.build_children_ir
+Else.build_ir = If.build_ir
+
+
 choose.Choose = Choose
 choose.If = If
 choose.ElseIf = ElseIf

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,131 +6,312 @@
 
 local date_module = {}
 
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local IrNode = require("citeproc-ir-node").IrNode
+local Rendered = require("citeproc-ir-node").Rendered
+local SeqIr = require("citeproc-ir-node").SeqIr
+local PlainText = require("citeproc-output").PlainText
 local util = require("citeproc-util")
 
 
-local Date = element.Element:new()
+-- [Date](https://docs.citationstyles.org/en/stable/specification.html#date)
+local Date = Element:derive("date")
 
-function Date:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+function Date:from_node(node)
+  local o = Date:new()
 
-  if context.sorting then
-    return self:render_sort_key(item, context)
+  o:set_attribute(node, "variable")
+  o:set_attribute(node, "form")
+  o:set_attribute(node, "date-parts")
+
+  if o.form and not o.date_parts then
+    o.date_parts = "year-month-day"
   end
 
-  local variable_name = context.options["variable"]
+  o:get_delimiter_attribute(node)
+  o:set_formatting_attributes(node)
+  o:set_affixes_attributes(node)
+  o:set_display_attribute(node)
+  o:set_text_case_attribute(node)
 
-  local is_locale_date
-  if variable_name then
-    context.variable = variable_name
-    is_locale_date = false
-  else
-    variable_name = context.variable
-    is_locale_date = true
+  o.children = {}
+  o:process_children_nodes(node)
+
+  for _, date_part in ipairs(o.children) do
+    o[date_part.name] = date_part
   end
 
-  local date = self:get_variable(item, variable_name, context)
-  if not date then
-    return nil
+  return o
+end
+
+function Date:build_ir(engine, state, context)
+  local variable
+  if not state.suppressed[self.variable] then
+    variable = context:get_variable(self.variable)
   end
 
-  local res = nil
-  local form = context.options["form"]
-  if form and not is_locale_date then
-    for _, date_part in ipairs(self:query_selector("date-part")) do
-      local name = date_part:get_attribute("name")
-      if not context.date_part_attributes then
-        context.date_part_attributes = {}
+  if not variable then
+    local ir = Rendered:new({}, self)
+    ir.group_var = "missing"
+    return ir
+  end
+
+  local ir
+
+  if variable["date-parts"] and #variable["date-parts"] > 0 then
+
+    -- TODO: move input normlization in one place
+    for i = 1, 2 do
+      if variable["date-parts"][i] then
+        for j = 1, 3 do
+          local variabel_part = variable["date-parts"][i][j]
+          if variabel_part == 0 or variabel_part == "" then
+            variable["date-parts"][i][j] = nil
+          else
+            variable["date-parts"][i][j] = tonumber(variabel_part)
+          end
+        end
       end
-      if not context.date_part_attributes[name] then
-        context.date_part_attributes[name] = {}
+    end
+    if variable["season"] and not variable["date-parts"][1][2] then
+      variable["date-parts"][1][2] = 20 + tonumber(variable["season"])
+    end
+
+    variable = variable["date-parts"]
+    if self.form then
+      ir = self:build_localized_date_ir(variable, engine, state, context)
+    else
+      ir = self:build_independent_date_ir(variable, engine, state, context)
+    end
+    ir.affixes = self.affixes
+
+  elseif variable["literal"] then
+    local inlines = self:render_text_inlines(variable["literal"], context)
+    ir = Rendered:new(inlines, self)
+    ir.group_var = "important"
+
+  elseif variable["raw"] then
+    local inlines = self:render_text_inlines(variable["raw"], context)
+    ir = Rendered:new(inlines, self)
+    ir.group_var = "important"
+
+  end
+
+  if not ir then
+    -- date_LiteralFailGracefullyIfNoValue.txt
+    ir = Rendered:new({}, self)
+    if context.sort_key then
+      ir.sort_key = false
+    end
+    ir.group_var = "missing"
+    return ir
+  end
+
+  if ir.group_var == "important" then
+    -- Suppress substituted name variable
+    if state.name_override and not context.sort_key then
+      state.suppressed[self.variable] = true
+    end
+  end
+
+  if context.sort_key then
+    ir.sort_key = self:render_sort_key(engine, state, context)
+  end
+
+  return ir
+end
+
+function Date:build_independent_date_ir(variable, engine, state, context)
+  -- else
+  --   local literal = variable["literal"]
+  --   if literal then
+  --     res = literal
+  --   else
+  --     local raw = variable["raw"]
+  --     if raw then
+  --       res = raw
+  --     end
+  --   end
+
+  return self:build_date_parts(self.children, variable, self.delimiter, engine, state, context)
+end
+
+function Date:build_localized_date_ir(variable, engine, state, context)
+  local date_part_mask = {}
+  for _, part in ipairs(util.split(self.date_parts or "year-month-day", "%-")) do
+    date_part_mask[part] = true
+  end
+  -- local date_parts = {}
+  -- for _, date_part in ipairs(self.children) do
+  --   date_parts[date_part.name] = date_part
+  -- end
+  local localized_date = context:get_localized_date(self.form)
+  local date_parts = {}
+  for _, date_part in ipairs(localized_date.children) do
+    if date_part_mask[date_part.name] then
+      date_part = date_part:copy()
+      local local_date_part = self[date_part.name]
+      if local_date_part then
+        local_date_part:override(date_part)
       end
+      table.insert(date_parts, date_part)
+    end
+  end
+  return self:build_date_parts(date_parts, variable, localized_date.delimiter, engine, state, context)
+end
 
-      for attr, value in pairs(date_part._attr) do
-        if attr ~= name then
-          context.date_part_attributes[name][attr] = value
-        end
+function Date:build_date_parts(date_parts, variable, delimiter, engine, state, context)
+  if #variable >= 2 then
+    return self:build_date_range(date_parts, variable, delimiter, engine, state, context)
+  elseif #variable == 1 then
+    return self:build_single_date(date_parts, variable[1], delimiter, engine, state, context)
+  end
+end
+
+function Date:build_single_date(date_parts, single_date, delimiter, engine, state, context)
+  local irs = {}
+  for _, date_part in ipairs(date_parts) do
+    local part_ir = date_part:build_ir(single_date, engine, state, context)
+    table.insert(irs, part_ir)
+  end
+
+  local ir = SeqIr:new(irs, self)
+  ir.delimiter = self.delimiter
+
+  -- return Rendered:new(inlines, self)
+  return ir
+end
+
+local date_part_index = {
+  year = 1,
+  month = 2,
+  day = 3,
+}
+
+function Date:build_date_range(date_parts, variable, delimiter, engine, state, context)
+  local first, second = variable[1], variable[2]
+  local diff_level = 4
+  for _, date_part in ipairs(date_parts) do
+    local part_index = date_part_index[date_part.name]
+    if first[part_index] and first[part_index] ~= second[part_index] then
+      if part_index < diff_level then
+        diff_level = part_index
       end
     end
-    res = self:get_locale_date(context, form):render(item, context)
-  else
-    if not date["date-parts"] or #date["date-parts"] == 0 then
-      local literal = date["literal"]
-      if literal then
-        res = literal
+  end
+
+  local irs = {}
+
+  local range_part_queue = {}
+  local range_delimiter
+  for i, date_part in ipairs(date_parts) do
+    local part_index = date_part_index[date_part.name]
+    if part_index == diff_level then
+      range_delimiter = date_part.range_delimiter or util.unicode["en dash"]
+    end
+    if first[part_index] then
+      if part_index >= diff_level then
+        table.insert(range_part_queue, date_part)
       else
-        local raw = date["raw"]
-        if raw then
-          res = raw
+        if #range_part_queue > 0 then
+          table.insert(irs, self:build_date_range_parts(range_part_queue, variable,
+              delimiter, engine, state, context, range_delimiter))
+          range_part_queue = {}
         end
+        table.insert(irs, date_part:build_ir(first, engine, state, context))
       end
-
-    else
-      if #date["date-parts"] == 1 then
-        res = self:_render_single_date(date, context)
-      elseif #date["date-parts"] == 2 then
-        res = self:_render_date_range(date, context)
-      end
     end
   end
+  if #range_part_queue > 0 then
+    table.insert(irs, self:build_date_range_parts(range_part_queue, variable,
+    delimiter, engine, state, context, range_delimiter))
+  end
 
-  table.insert(context.variable_attempt, res ~= nil)
+  local ir = SeqIr:new(irs, self)
+  ir.delimiter = delimiter
 
-  res = self:format(res, context)
-  res = self:wrap(res, context)
-  return res
+  return ir
 end
 
-function Date:get_locale_date(context, form)
-  local date = nil
-  local style = context.style
-  local query = string.format("date[form=\"%s\"]", form)
-  for _, locale in ipairs(style:get_locales()) do
-    date = locale:query_selector(query)[1]
-    if date then
-      break
+function Date:build_date_range_parts(range_part_queue, variable, delimiter, engine, state, context, range_delimiter)
+  local irs = {}
+  local first, second = variable[1], variable[2]
+
+  local date_part_irs = {}
+  for i, diff_part in ipairs(range_part_queue) do
+    -- if delimiter and i > 1 then
+    --   table.insert(date_part_irs, PlainText:new(delimiter))
+    -- end
+    if i == #range_part_queue then
+      table.insert(date_part_irs, diff_part:build_ir(first, engine, state, context, "suffix"))
+    else
+      table.insert(date_part_irs, diff_part:build_ir(first, engine, state, context))
     end
   end
-  if not date then
-    error(string.format("Failed to find '%s'", query))
+  local range_part_ir = SeqIr:new(date_part_irs, self)
+  range_part_ir.delimiter = delimiter
+  table.insert(irs, range_part_ir)
+
+  table.insert(irs, Rendered:new({PlainText:new(range_delimiter)}, self))
+
+  date_part_irs = {}
+  for i, diff_part in ipairs(range_part_queue) do
+    if i == 1 then
+      table.insert(date_part_irs, diff_part:build_ir(second, engine, state, context, "prefix"))
+    else
+      table.insert(date_part_irs, diff_part:build_ir(second, engine, state, context))
+    end
   end
-  return date
+  range_part_ir = SeqIr:new(date_part_irs, self)
+  range_part_ir.delimiter = delimiter
+  table.insert(irs, range_part_ir)
+
+  local ir = SeqIr:new(irs, self)
+
+  return ir
 end
 
-function Date:render_sort_key (item, context)
-  local variable_name = context.options["variable"]
-  local date = self:get_variable(item, variable_name, context)
-  if not date or not date["date-parts"] then
-    return nil
+function Date:render_sort_key(engine, state, context)
+  local date = context:get_variable(self.variable)
+  if not date then
+    return false
   end
+  if not date["date-parts"] then
+    if self.literal then
+      return "1" .. self.literal
+    else
+      return false
+    end
+  end
+
   local show_parts = {
     year = false,
     month = false,
     day = false,
   }
-  if self:get_attribute("form") then
-    local date_parts = self:get_attribute("date-parts") or "year-month-day"
-    for _, dp_name in ipairs(util.split(date_parts, "%-")) do
+  if self.form then
+    for _, dp_name in ipairs(util.split(self.date_parts, "%-")) do
       show_parts[dp_name] = true
     end
   else
-    for _, child in ipairs(self:query_selector("date-part")) do
-      show_parts[child:get_attribute("name")] = true
+    for _, date_part in ipairs(self.children) do
+      show_parts[date_part.name] = true
     end
   end
   local res = ""
-  for _, date_parts in ipairs(date["date-parts"]) do
+  for _, range_part in ipairs(date["date-parts"]) do
+    if res ~= "" then
+      res = res .. "/"
+    end
     for i, dp_name in ipairs({"year", "month", "day"}) do
-      local value = date_parts[i]
-      if not value or not show_parts[dp_name] then
-        value = 0
+      local value = 0
+      if show_parts[dp_name] and range_part[i] then
+        value = range_part[i]
       end
       if i == 1 then
         res = res .. string.format("%05d", value + 10000)
       else
-        res = res .. string.format("%02d", value)
+        res = res .. "-" .. string.format("%02d", value)
       end
     end
   end
@@ -137,245 +318,169 @@
   return res
 end
 
-function Date:_render_single_date (date, context)
-  local show_parts = self:_get_show_parts(context)
 
-  local output = {}
-  for _, child in ipairs(self:query_selector("date-part")) do
-    if show_parts[child:get_attribute("name")] then
-      table.insert(output, child:render(date, context))
-    end
+-- [Date-part](https://docs.citationstyles.org/en/stable/specification.html#date-part)
+local DatePart = Element:derive("date-part")
+
+function DatePart:from_node(node)
+  local o = DatePart:new()
+  o:set_attribute(node, "name")
+  o:set_attribute(node, "form")
+  if o.name == "month" then
+    o:set_strip_periods_attribute(node)
   end
-  return self:concat(output, context)
+  o:set_formatting_attributes(node)
+  o:set_text_case_attribute(node)
+  o:set_attribute(node, "range-delimiter")
+  o:set_affixes_attributes(node)
+  return o
 end
 
-function Date:_render_date_range (date, context)
-  local show_parts = self:_get_show_parts(context)
-  local part_index = {}
+function DatePart:build_ir(single_date, engine, state, context, suppressed_affix)
+  local text
+  if self.name == "year" then
+    text = self:render_year(single_date[1], engine, state, context)
+  elseif self.name == "month" then
+    text = self:render_month(single_date[2], engine, state, context)
+  elseif self.name == "day" then
+    text = self:render_day(single_date[3], single_date[2], engine, state, context)
+  end
 
-  local largest_diff_part = nil
-  for i, name in ipairs({"year", "month", "day"}) do
-    part_index[name] = i
-    local part_value1 = date["date-parts"][1][i]
-    if show_parts[name] and part_value1 then
-      if not largest_diff_part then
-        largest_diff_part = name
-      end
-    end
+  if not text then
+    local ir = Rendered:new({}, self)
+    ir.group_var = "missing"
+    return ir
   end
 
-  local date_parts = {}
-  for _, date_part in ipairs(self:query_selector("date-part")) do
-    if show_parts[date_part:get_attribute("name")] then
-      table.insert(date_parts, date_part)
-    end
+  local inlines = {PlainText:new(text)}
+  local output_format = context.format
+  if not context.is_english then
+    print(debug.traceback())
   end
+  local is_english = context:is_english()
+  output_format:apply_text_case(inlines, self.text_case, is_english)
+  inlines = output_format:with_format(inlines, self.formatting)
 
-  local diff_begin = 0
-  local diff_end = #date_parts
-  local range_delimiter = nil
+  local ir = Rendered:new(inlines, self)
+  ir.group_var = "important"
 
-  for i, date_part in ipairs(date_parts) do
-    local name = date_part:get_attribute("name")
-    if name == largest_diff_part then
-      range_delimiter = date_part:get_attribute("range-delimiter")
-      if not range_delimiter then
-        range_delimiter = util.unicode["en dash"]
-      end
-    end
+  if self.name == "year" then
+    ir = SeqIr:new({ir}, self)
+    ir.is_year = true
+  end
 
-    local index = part_index[name]
-    local part_value1 = date["date-parts"][1][index]
-    local part_value2 = date["date-parts"][2][index]
-    if part_value1 and part_value1 ~= part_value2 then
-      if diff_begin == 0 then
-        diff_begin = i
-      end
-      diff_end = i
-    end
+  ir.affixes = util.clone(self.affixes)
+  if ir.affixes and suppressed_affix then
+    ir.affixes[suppressed_affix] = nil
   end
 
-  local same_prefix = {}
-  local range_begin = {}
-  local range_end = {}
-  local same_suffix = {}
+  return ir
+end
 
-  local no_suffix_context = self:process_context(context)
-  no_suffix_context.options["suffix"] = nil
-
-  for i, date_part in ipairs(date_parts) do
-    local res = nil
-    if i == diff_end then
-      res = date_part:render(date, no_suffix_context, true)
-    else
-      res = date_part:render(date, context)
+function DatePart:render_day(day, month, engine, state, context)
+  if not day or day == "" then
+    return nil
+  end
+  day = tonumber(day)
+  if day < 1 or day > 31 then
+    return nil
+  end
+  local form = self.form or "numeric"
+  if form == "ordinal" then
+    local limit_day_1 = context.locale.style_options.limit_day_ordinals_to_day_1
+    if limit_day_1 and day > 1 then
+      form = "numeric"
     end
-    if i < diff_begin then
-      table.insert(same_prefix, res)
-    elseif i <= diff_end then
-      table.insert(range_begin, res)
-      table.insert(range_end, date_part:render(date, context, false, true))
-    else
-      table.insert(same_suffix, res)
-    end
   end
-
-  local prefix_output = self:concat(same_prefix, context) or ""
-  local range_begin_output = self:concat(range_begin, context) or ""
-  local range_end_output = self:concat(range_end, context) or ""
-  local suffix_output = self:concat(same_suffix, context)
-  local range_output = range_begin_output .. range_delimiter .. range_end_output
-
-  local res = self:concat({prefix_output, range_output, suffix_output}, context)
-
-  return res
+  if form == "numeric-leading-zeros" then
+    return string.format("%02d", day)
+  elseif form == "ordinal" then
+    -- When the “day” date-part is rendered in the “ordinal” form, the ordinal
+    -- gender is matched against that of the month term.
+    local gender = context.locale:get_number_gender(string.format("month-%02d", month))
+    local suffix = context.locale:get_ordinal_term(day, gender)
+    return tostring(day) .. suffix
+  else  -- numeric
+    return tostring(day)
+  end
 end
 
-function Date:_get_show_parts (context)
-  local show_parts = {}
-  local date_parts = context.options["date-parts"] or "year-month-day"
-  for _, date_part in ipairs(util.split(date_parts, "%-")) do
-    show_parts[date_part] = true
+function DatePart:render_month(month, engine, state, context)
+  if not month or month == "" then
+    return nil
   end
-  return show_parts
-end
-
-
-local DatePart = element.Element:new()
-
-DatePart.render = function (self, date, context, last_range_begin, range_end)
-  self:debug_info(context)
-  context = self:process_context(context)
-  local name = context.options["name"]
-  local range_delimiter = context.options["range-delimiter"] or false
-
-  -- The attributes set on cs:date-part elements of a cs:date with form
-  -- attribute override those specified for the localized date formats
-  if context.date_part_attributes then
-    local context_attributes = context.date_part_attributes[name]
-    if context_attributes then
-      for attr, value in pairs(context_attributes) do
-        context.options[attr] = value
+  month = tonumber(month)
+  if not month or month < 1 or month > 24 then
+    return nil
+  end
+  local form = self.form or "long"
+  local res
+  if form == "long" or form == "short" then
+    if month >= 1 and month <= 12 then
+      res = context:get_simple_term(string.format("month-%02d", month), form)
+    else
+      local season = month % 4
+      if season == 0 then
+        season = 4
       end
+      res = context:get_simple_term(string.format("season-%02d", season))
     end
+  elseif form == "numeric-leading-zeros" then
+    res = string.format("%02d", month)
+  else
+    res = tostring(month)
   end
+  return self:apply_strip_periods(res)
+end
 
-  if last_range_begin then
-    context.options["suffix"] = ""
+function DatePart:render_year(year, engine, state, context)
+  if not year or year == "" then
+    return nil
   end
-
-  local date_parts_index = 1
-  if range_end then
-    date_parts_index = 2
+  year = tonumber(year)
+  if year == 0 then
+    return nil
   end
-
-  local res = nil
-  if name == "day" then
-    local day = date["date-parts"][date_parts_index][3]
-    if not day then
-      return nil
+  local form = self.form or "long"
+  if form == "long" then
+    if year < 0 then
+      return tostring(-year) .. context:get_simple_term("bc")
+    elseif year < 1000 then
+      return tostring(year) .. context:get_simple_term("ad")
+    else
+      return tostring(year)
     end
-    day = tonumber(day)
-    -- range open
-    if day == 0 then
-      return nil
-    end
-    local form = context.options["form"] or "numeric"
+  elseif form == "short" then
+    return string.sub(tostring(year), -2)
+  end
+end
 
-    if form == "ordinal" then
-      local option = self:get_locale_option("limit-day-ordinals-to-day-1")
-      if option and option ~= "false" and day > 1 then
-        form = "numeric"
+function DatePart:copy()
+  local o = {}
+  for key, value in pairs(self) do
+    if type(value) == "table" then
+      o[key] = {}
+      for k, v in pairs(value) do
+        o[key][k] = v
       end
+    else
+      o[key] = value
     end
-    if form == "numeric" then
-      res = tostring(day)
-    elseif form == "numeric-leading-zeros" then
-      -- TODO: day == nil?
-      if not day then
-        return nil
-      end
-      res = string.format("%02d", day)
-    elseif form == "ordinal" then
-      res = util.to_ordinal(day)
-    end
+  end
+  setmetatable(o, DatePart)
+  return o
+end
 
-  elseif name == "month" then
-    local form = context.options["form"] or "long"
-
-    local month = date["date-parts"][date_parts_index][2]
-    if month then
-      month = tonumber(month)
-      -- range open
-      if month == 0 then
-        return nil
+function DatePart:override(localized_date_part)
+  for key, value in pairs(self) do
+    if type(value) == "table" and localized_date_part[key] then
+      for k, v in pairs(value) do
+        localized_date_part[key][k] = v
       end
+    else
+      localized_date_part[key] = value
     end
-
-    if form == "long" or form == "short" then
-      local term_name = nil
-      if month then
-        if month >= 1 and month <= 12 then
-          term_name = string.format("month-%02d", month)
-        elseif month >= 13 and month <= 24 then
-          local season = month % 4
-          if season == 0 then
-            season = 4
-          end
-          term_name = string.format("season-%02d", season)
-        else
-          util.warning("Invalid month value")
-          return nil
-        end
-      else
-        local season = date["season"]
-        if season then
-          season = tonumber(season)
-          term_name = string.format("season-%02d", season)
-        else
-          return nil
-        end
-      end
-      res = self:get_term(term_name, form):render(context)
-    elseif form == "numeric" then
-      res = tostring(month)
-    elseif form == "numeric-leading-zeros" then
-      -- TODO: month == nil?
-      if not month then
-        return nil
-      end
-      res = string.format("%02d", month)
-    end
-    res = self:strip_periods(res, context)
-
-  elseif name == "year" then
-    local year = date["date-parts"][date_parts_index][1]
-    if year then
-      year = tonumber(year)
-      -- range open
-      if year == 0 then
-        return nil
-      end
-      local form = context.options["form"] or "long"
-      if form == "long" then
-        year = tonumber(year)
-        if year < 0 then
-          res = tostring(-year) .. self:get_term("bc"):render(context)
-        elseif year < 1000 then
-          res = tostring(year) .. self:get_term("ad"):render(context)
-        else
-          res = tostring(year)
-        end
-      elseif form == "short" then
-        res = string.sub(tostring(year), -2)
-      end
-    end
   end
-  res = self:case(res, context)
-  res = self:format(res, context)
-  res = self:wrap(res, context)
-  res = self:display(res, context)
-  return res
 end
 
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-group.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-group.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-group.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,33 +6,36 @@
 
 local group = {}
 
-local element = require("citeproc-element")
+local SeqIr = require("citeproc-ir-node").SeqIr
+local Element = require("citeproc-element").Element
 local util = require("citeproc-util")
 
 
-local Group = element.Element:new()
+local Group = Element:derive("group")
 
-function Group:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+function Group:from_node(node)
+  local o = Group:new()
+  o:get_delimiter_attribute(node)
+  o:set_affixes_attributes(node)
+  o:set_display_attribute(node)
+  o:set_formatting_attributes(node)
 
-  local num_variable_attempt = #context.variable_attempt
+  o:process_children_nodes(node)
 
-  local res = self:render_children(item, context)
+  return o
+end
 
-  if #context.variable_attempt > num_variable_attempt then
-    if not util.any(util.slice(context.variable_attempt, num_variable_attempt + 1)) then
-      res = nil
-    end
+function Group:build_ir(engine, state, context)
+  local ir = self:build_group_ir(engine, state, context)
+  if ir then
+    ir.delimiter = self.delimiter
+    ir.formatting = util.clone(self.formatting)
+    ir.affixes = util.clone(self.affixes)
+    ir.display = self.display
   end
-
-  res = self:format(res, context)
-  res = self:wrap(res, context)
-  res = self:display(res, context)
-  return res
+  return ir
 end
 
-
 group.Group = Group
 
 return group

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,104 +6,88 @@
 
 local label = {}
 
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local IrNode = require("citeproc-ir-node").IrNode
+local Rendered = require("citeproc-ir-node").Rendered
+local PlainText = require("citeproc-output").PlainText
 local util = require("citeproc-util")
 
 
-local Label = element.Element:new()
+-- [Label](https://docs.citationstyles.org/en/stable/specification.html#label)
+local Label = Element:derive("label")
 
-function Label:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+Label.form = "long"
+Label.plural = "contextual"
 
-  local variable_name
-  if context.names_element then
-    -- The `variable` attribute of names may hold multiple roles.
-    -- Each of them may call `Label:render()` to render the term.
-    -- When used in `names` element, the role name is the first argument
-    -- and the item is accessed via `context.item`.
-    -- Bad design
-    -- TODO: Redesign the arguments of render()
-    variable_name = item
-  else
-    variable_name = context.options["variable"]
-  end
+function Label:from_node(node)
+  local o = Label:new()
+  o:set_attribute(node, "variable")
+  o:set_attribute(node, "form")
+  o:set_attribute(node, "plural")
+  o:set_affixes_attributes(node)
+  o:set_formatting_attributes(node)
+  o:set_text_case_attribute(node)
+  o:set_strip_periods_attribute(node)
+  return o
+end
 
-  local form = context.options["form"]
-  local plural = context.options["plural"] or "contextual"
+function Label:build_ir(engine, state, context)
+  -- local variable = context:get_variable(self.variable, self.form)
 
-  if not context.names_element then
-    local variable_type = util.variable_types[variable_name]
-    -- variable must be or one of the number variables.
-    if variable_type ~= "number" then
-      return nil
-    end
-    -- The term is only rendered if the selected variable is non-empty
-    local variable = item[variable_name]
-    if not variable then
-      return nil
-    end
-    if type(variable) == "string" then
-      if not (string.match(variable, "^%d") or util.is_numeric(variable)) then
-        return nil
-      end
-    end
+  local is_plural = false
+  if self.plural == "always" then
+    is_plural = true
+  elseif self.plural == "never" then
+    is_plural = false
+  elseif self.plural == "contextual" then
+    is_plural = self:_is_variable_plural(self.variable, context)
   end
 
-  local term
-  if variable_name == "locator" then
-    local locator_type = item.label or "page"
-    term = self:get_term(locator_type, form)
-  else
-    term = self:get_term(variable_name, form)
+  local variable = self.variable
+  if variable == "locator" then
+    variable = context:get_variable("label") or "page"
+    if variable == "sub verbo" then
+      -- bugreports_MovePunctuationInsideQuotesForLocator.txt
+      variable = "sub-verbo"
+    end
   end
+  local text = context:get_simple_term(variable, self.form, is_plural)
+  if not text or text == "" then
+    return nil
+  end
 
-  local res = nil
-  if term then
-    if plural == "contextual" and self:_is_plural(variable_name, context) or plural == "always" then
-      res = term:render(context, true)
-    else
-      res = term:render(context, false)
-    end
+  local inlines = self:render_text_inlines(text, context)
+  return Rendered:new(inlines, self)
+end
 
-    res = self:strip_periods(res, context)
-    res = self:case(res, context)
-    res = self:format(res, context)
-    res = self:wrap(res, context)
+function Label:_is_variable_plural(variable, context)
+  local value = context:get_variable(variable)
+  if not value then
+    return false
   end
-  return res
-end
-
-function Label:_is_plural (variable_name, context)
-  local variable_type = util.variable_types[variable_name]
-  -- Don't use self:get_variable here
-  local variable = context.item[variable_name]
-  local res = false
+  local variable_type = util.variable_types[variable]
   if variable_type == "name" then
-    -- Label inside `names`
-    res = #variable > 1
-
+    return #variable > 1
   elseif variable_type == "number" then
-    if util.startswith(variable_name, "number-of-") then
-      res = tonumber(variable) > 1
+    if util.startswith(variable, "number-of-") then
+      return tonumber(value) > 1
     else
-      variable = tostring(variable)
-      variable = string.gsub(variable, "\\%-", "")
-      if #util.split(variable, "%s*[,&-]%s*") > 1 then
-        -- check if contains multiple numbers
-        -- "i–ix": true
-        -- res = string.match(tostring(variable), "%d+%D+%d+") ~= nil
-        res = true
-      elseif string.match(variable, "%Aand%A") or string.match(variable, "%Aet%A") then
-        res = true
-      else
-        res = false
+      value = tostring(value)
+      -- label_CollapsedPageNumberPluralDetection.txt
+      -- 327\-30 => single
+      value = string.gsub(value, "\\%-", "")
+      if string.match(value, "[,&-]") then
+        return true
+      elseif string.match(value, util.unicode["en dash"]) then
+        return true
+      elseif string.match(value, "%Wand%W") then
+        return true
+      elseif string.match(value, "%Wet%W") then
+        return true
       end
     end
-  else
-    util.warning("Invalid attribute \"variable\".")
   end
-  return res
+  return false
 end
 
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-layout.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-layout.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-layout.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,229 +6,56 @@
 
 local layout = {}
 
-local richtext = require("citeproc-richtext")
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local SeqIr = require("citeproc-ir-node").SeqIr
 local util = require("citeproc-util")
 
 
-local Layout = element.Element:new()
+local Layout = Element:derive("layout")
 
-function Layout:render (items, context)
-  self:debug_info(context)
+function Layout:from_node(node)
+  local o = Layout:new()
+  o:set_affixes_attributes(node)
+  o:set_formatting_attributes(node)
+  o:get_delimiter_attribute(node)
 
-  context.items = items
+  o:process_children_nodes(node)
 
-  -- When used within cs:citation, the delimiter attribute may be used to specify a delimiter for cites within a citation.
-  -- Thus the processing of context is put after render_children().
-  if context.mode == "citation" then
-    if context.options["collapse"] == "citation-number" then
-      context.build.item_citation_numbers = {}
-      context.build.item_citation_number_text = {}
-    end
-  elseif context.mode == "bibliography" then
-    context.build.longest_label = ""
-    context.build.preceding_first_rendered_names = nil
-    context = self:process_context(context)
-  end
-
-  local output = {}
-  local previous_cite = nil
-  for _, item in ipairs(items) do
-
-    context.item = item
-    context.variable_attempt = {}
-    context.suppressed_variables = {}
-    context.suppress_subsequent_variables = false
-    if context.mode == "bibliography" then
-      context.build.first_rendered_names = {}
-    end
-
-    if not item.position then
-      item.position = self:_get_position(item, previous_cite, context)
-    end
-
-    local first = nil
-    local second = {}
-    local element_index = 0
-    for _, child in ipairs(self:get_children()) do
-      if child:is_element() then
-        element_index = element_index + 1
-        local text = child:render(item, context)
-        if element_index == 1 then
-          first = text
-        else
-          table.insert(second, text)
-        end
-      end
-    end
-    second = self:concat(second, context)
-
-    if context.mode == "bibliography" then
-      if first and context.options["prefix"] then
-        first = richtext.new(context.options["prefix"]) .. first
-      end
-      if second and context.options["suffix"] then
-        second = second .. richtext.new(context.options["suffix"])
-      end
-    end
-
-    local res = nil
-    if context.options["second-field-align"] == "flush" then
-      if first then
-        first:add_format("display", "left-margin")
-        res = first
-      end
-      if second then
-        second:add_format("display", "right-inline")
-        if res then
-          res = richtext.concat(res, second)
-        else
-          res = second
-        end
-      end
-    else
-      res = self:concat({first, second}, context)
-    end
-
-    if context.mode == "citation" then
-      if res and item["prefix"] then
-        res = richtext.new(item["prefix"]) .. res
-      end
-      if res and item["suffix"] then
-        res = res .. richtext.new(item["suffix"])
-      end
-    elseif context.mode == "bibliography" then
-      if not res then
-        res = richtext.new("[CSL STYLE ERROR: reference with no printed form.]")
-      end
-      res = self:wrap(res, context)
-      -- util.debug(text)
-      res = res:render(context.engine.formatter, context)
-      res = context.engine.formatter["@bibliography/entry"](res, context)
-    end
-    table.insert(output, res)
-    previous_cite = item
-  end
-
-  if context.mode == "citation" then
-    if next(output) == nil then
-      return "[CSL STYLE ERROR: reference with no printed form.]"
-    end
-
-    context = self:process_context(context)
-    local res
-    if context.options["collapse"] then
-      res = self:_collapse_citations(output, context)
-    else
-      res = self:concat(output, context)
-    end
-    res = self:wrap(res, context)
-    res = self:format(res, context)
-    if res then
-      -- util.debug(res)
-      res = res:render(context.engine.formatter, context)
-    end
-    return res
-
-  else
-    local params = {
-      maxoffset = #context.build.longest_label,
-    }
-
-    return {params, output}
-  end
+  return o
 end
 
-function Layout:_get_position (item, previous_cite, context)
-  local engine = context.engine
-  if not engine.registry.registry[item.id] then
-    return util.position_map["first"]
+function Layout:build_ir(engine, state, context)
+  local ir = self:build_children_ir(engine, state, context)
+  if not ir then
+    return nil
   end
+  if context.in_bibliography then
+    ir.delimiter = self.delimiter
 
-  local position = util.position_map["subsequent"]
-  -- Find the preceding cite referencing the same item
-  local preceding_cite = nil
-  if previous_cite then
-    -- a. the current cite immediately follows on another cite
-    if item.id == previous_cite.id then
-      preceding_cite = previous_cite
-    end
-  elseif engine.registry.previous_citation then
-    -- b. first cite in the citation and previous citation exists
-    for _, cite in ipairs(engine.registry.previous_citation.citationItems) do
-      if item.id == cite.id then
-        preceding_cite = cite
-        break
-      end
-    end
-  end
-
-  if preceding_cite then
-    if preceding_cite.locator then
-      -- Preceding cite does have a locator
-      if item.locator then
-        if item.locator == preceding_cite.locator then
-          position = util.position_map["ibid"]
-        else
-          position = util.position_map["ibid-with-locator"]
+    -- Move affixes of `bibliography > layout` into the right-inline element
+    -- bugreports_SmallCapsEscape.txt
+    local has_right_inline = false
+    if self.affixes or self.formatting then
+      for i, child_ir in ipairs(ir.children) do
+        if child_ir.display == "right-inline" then
+          has_right_inline = true
+          local right_inline_with_affixes = SeqIr:new({child_ir}, self)
+          right_inline_with_affixes.affixes = util.clone(self.affixes)
+          right_inline_with_affixes.formatting = util.clone(self.formatting)
+          child_ir.display = nil
+          right_inline_with_affixes.display = "right-inline"
+          ir.children[i] = right_inline_with_affixes
+          break
         end
-      else
-        -- the current cite lacks a locator
-        position = util.position_map["subsequent"]
       end
-    else
-      -- Preceding cite does not have a locator
-      if item.locator then
-        position = util.position_map["ibid-with-locator"]
-      else
-        position = util.position_map["ibid"]
-      end
     end
-  end
-  return position
-end
 
-
-function Layout:_collapse_citations(output, context)
-  if context.options["collapse"] == "citation-number" then
-    assert(#output == #context.items)
-    local citation_numbers = {}
-    for i, item in ipairs(context.items) do
-      citation_numbers[i] = context.build.item_citation_numbers[item.id] or 0
+    if not has_right_inline then
+      ir.affixes = util.clone(self.affixes)
+      ir.formatting = util.clone(self.formatting)
     end
-
-    local collapsed_output = {}
-    local citation_number_range_delimiter = util.unicode["en dash"]
-    local index = 1
-    while index <= #citation_numbers do
-      local stop_index = index + 1
-      if output[index] == context.build.item_citation_number_text[index] then
-        while stop_index <= #citation_numbers  do
-          if output[stop_index] ~= context.build.item_citation_number_text[stop_index] then
-            break
-          end
-          if citation_numbers[stop_index - 1] + 1 ~= citation_numbers[stop_index] then
-            break
-          end
-          stop_index = stop_index + 1
-        end
-      end
-
-      if stop_index >= index + 3 then
-        local range_text = output[index] .. citation_number_range_delimiter .. output[stop_index - 1]
-        table.insert(collapsed_output, range_text)
-      else
-        for i = index, stop_index - 1 do
-          table.insert(collapsed_output, output[i])
-        end
-      end
-
-      index = stop_index
-    end
-
-    return self:concat(collapsed_output, context)
   end
-  return self:concat(output, context)
+  return ir
 end
 
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-locale.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-locale.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-locale.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,127 +6,227 @@
 
 local locale = {}
 
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local util = require("citeproc-util")
 
 
-local Locale = element.Element:new()
+local Locale = Element:derive("locale")
 
-function Locale:get_option (key)
-  local query = string.format("style-options[%s]", key)
-  local option = self:query_selector(query)[1]
-  if option then
-    local value = option:get_attribute(key)
-      if self.option_type[key] == "integer" then
-        value = tonumber(value)
-      elseif self.option_type[key] == "boolean" then
-        value = (value == "true")
-      end
-    return value
-  else
-    return nil
-  end
+function Locale:new()
+  local o = {
+    xml_lang = nil,
+    terms = {},
+    ordinal_terms = nil,
+    dates = {},
+    style_options = {
+      -- limit_day_ordinals_to_day_1 = false,
+      -- punctuation_in_quote = false,
+    },
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
 end
 
-function Locale:get_term (name, form, number, gender)
+function Locale:from_node(node)
+  local o = Locale:new()
+  o.xml_lang = node:get_attribute("xml:lang")
+  o:process_children_nodes(node)
 
-  if form == "long" then
-    form = nil
+  for _, child in ipairs(o.children) do
+    local element_name = child.element_name
+    if element_name == "terms" then
+      o.terms = child.terms_map
+      o.ordinal_terms = child.ordinal_terms
+    elseif element_name == "date" then
+      o.dates[child.form] = child
+    end
   end
 
-  local match_last
-  local match_last_two
-  local match_whole
-  if number then
-    assert(type(number) == "number")
-    match_last = string.format("%s-%02d", name, number % 10)
-    match_last_two = string.format("%s-%02d", name, number % 100)
-    match_whole = string.format("%s-%02s", name, number)
+  for _, child in ipairs(node:get_children()) do
+    if child:is_element() and child:get_element_name() == "style-options" then
+      local style_options = child
+      o.style_options.limit_day_ordinals_to_day_1 = util.to_boolean(
+        style_options:get_attribute("limit-day-ordinals-to-day-1")
+      )
+      o.style_options.punctuation_in_quote = util.to_boolean(
+        style_options:get_attribute("punctuation-in-quote")
+      )
+    end
   end
 
-  local res = nil
-  for _, term in ipairs(self:query_selector("term")) do
-    -- Use get_path?
-    local match_name = name
+  return o
+end
 
-    if number then
-      local term_match = term:get_attribute("last-two-digits")
-      if term_match == "whole-number" then
-        match_name = match_whole
-      elseif term_match == "last-two-digits" then
-        match_name = match_last_two
-      elseif number < 10 then
-        -- "13" can match only "ordinal-13" not "ordinal-03"
-        -- It is sliced to "3" in a later checking pass.
-        match_name = match_last_two
+function Locale:merge(other)
+  for key, value in pairs(other.terms) do
+    self.terms[key] = value
+  end
+  -- https://docs.citationstyles.org/en/stable/specification.html#ordinal-suffixes
+  -- The “ordinal” term, and “ordinal-00” through “ordinal-99” terms, behave
+  -- differently from other terms when it comes to Locale Fallback.
+  -- Whereas other terms can be (re)defined individually, (re)defining any of
+  -- the ordinal terms through cs:locale replaces all previously defined
+  -- ordinal terms.
+  if other.ordinal_terms then
+    self.ordinal_terms = other.ordinal_terms
+  end
+  for key, value in pairs(other.dates) do
+    self.dates[key] = value
+  end
+  for key, value in pairs(other.style_options) do
+    if value ~= nil then
+      self.style_options[key] = value
+    end
+  end
+  return self
+end
+
+Locale.form_fallbacks = {
+  ["verb-short"] = {"verb-short", "verb", "long"},
+  ["verb"]       = {"verb", "long"},
+  ["symbol"]     = {"symbol", "short", "long"},
+  ["short"]      = {"short", "long"},
+  ["long"]       = {"long"},
+}
+
+-- Keep in sync with Terms:from_node
+function Locale:get_simple_term(name, form, plural)
+  form = form or "long"
+  for _, fallback_form in ipairs(self.form_fallbacks[form]) do
+    local key = name
+    if form ~= "long" then
+      if not key then print(debug.traceback()) end
+      key = key .. "/form-" .. fallback_form
+    end
+    local term = self.terms[key]
+    if term then
+      if plural then
+        return term.multiple or term.text
       else
-        match_name = match_last
+        return term.single or term.text
       end
     end
+  end
+  return nil
+end
 
-    local term_name = term:get_attribute("name")
-    local term_form = term:get_attribute("form")
-    if term_form == "long" then
-      term_form = nil
-    end
-    local term_gender = term:get_attribute("gender-form")
+function Locale:get_ordinal_term(number, gender)
+  -- TODO: match and gender
 
-    if term_name == match_name and term_form == form and term_gender == gender then
-      return term
+  local keys = {}
+
+  if gender then
+    if number < 100 then
+      table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-whole-number", number, gender))
     end
-
+    table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-last-two-digits", number % 100, gender))
+    table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-last-digit", number % 10, gender))
   end
 
-  -- Fallback
-  if form == "verb-sort" then
-    return self:get_term(name, "verb")
-  elseif form == "symbol" then
-    return self:get_term(name, "short")
-  elseif form == "verb" then
-    return self:get_term(name, "long")
-  elseif form == "short" then
-    return self:get_term(name, "long")
+  if number < 100 then
+    table.insert(keys, string.format("ordinal-%02d/match-whole-number", number))
   end
+  table.insert(keys, string.format("ordinal-%02d/match-last-two-digits", number % 100))
+  table.insert(keys, string.format("ordinal-%02d/match-last-digit", number % 10))
+  table.insert(keys, "ordinal")
 
-  if number and number > 10 then
-    return self:get_term(name, nil, number % 10, gender)
+  for _, key in ipairs(keys) do
+    local term = self.ordinal_terms[key]
+    if term then
+      return term.text
+    end
   end
+  return nil
+end
 
-  if gender then
-    return self:get_term(name, nil, number, nil)
+function Locale:get_number_gender(name)
+  local term = self.terms[name]
+  if term and term.gender then
+    return term.gender
+  else
+    return nil
   end
+end
 
-  if number then
-    return self:get_term(name, nil, nil, nil)
-  end
 
-  return nil
+local Terms = Element:derive("terms")
+
+function Terms:new()
+  local o = Element.new(self)
+  o.element_name = "terms"
+  o.children = {}
+  o.terms_map = {}
+  o.ordinal_terms = nil
+  return o
 end
 
 
-local Term = element.Element:new()
+function Terms:from_node(node)
+  local o = Terms:new()
+  o:process_children_nodes(node)
+  for _, term in ipairs(o.children) do
+    local form = term.form
+    local gender = term.gender
+    local gender_form = term.gender_form
+    local match
+    if util.startswith(term.name, "ordinal-0") then
+      match = term.match or "last-digit"
+    elseif util.startswith(term.name, "ordinal-") then
+      match = term.match or "last-two-digits"
+    end
 
-function Term:render (context, is_plural)
-  self:debug_info(context)
-  context = self:process_context(context)
+    local key = term.name
+    if form then
+      key = key .. '/form-' .. form
+    end
+    -- if gender then
+    --   key = key .. '/gender-' .. gender
+    -- end
+    if gender_form then
+      key = key .. '/gender-form-' .. gender_form
+    end
+    if match then
+      key = key .. '/match-' .. match
+    end
 
-  local output = {
-    single = self:get_text(),
-  }
-  for _, child in ipairs(self:get_children()) do
-    if child:is_element() then
-      output[child:get_element_name()] = self:escape(child:get_text())
+    if term.name == "ordinal" or util.startswith(term.name, "ordinal-") then
+      if not o.ordinal_terms then
+        o.ordinal_terms = {}
+      end
+      o.ordinal_terms[key] = term
+    else
+      o.terms_map[key] = term
     end
   end
-  local res = output.single
-  if is_plural then
-    if output.multiple then
-      res = output.multiple
+  return o
+end
+
+
+local Term = Element:derive("term")
+
+function Term:from_node(node)
+  local o = Term:new()
+
+  o.name = node:get_attribute("name")
+  o.form = node:get_attribute("form")
+  o.match = node:get_attribute("match")
+  o.gender = node:get_attribute("gender")
+  o.gender_form = node:get_attribute("gender-form")
+  o.text = node:get_text()
+  for _, child in ipairs(node:get_children()) do
+    if child:is_element() then
+      local element_name = child:get_element_name()
+      if element_name == "single" then
+        o.single = child:get_text()
+        o.text = o.single
+      elseif element_name == "multiple" then
+        o.multiple = child:get_text()
+      end
     end
   end
-  if res == "" then
-    return nil
-  end
-  return res
+
+  return o
 end
 
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-names.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-names.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-names.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -8,320 +8,839 @@
 
 local unicode = require("unicode")
 
-local richtext = require("citeproc-richtext")
-local element = require("citeproc-element")
+local IrNode = require("citeproc-ir-node").IrNode
+local NameIr = require("citeproc-ir-node").NameIr
+local PersonNameIr = require("citeproc-ir-node").PersonNameIr
+local SeqIr = require("citeproc-ir-node").SeqIr
+local Rendered = require("citeproc-ir-node").Rendered
+local InlineElement = require("citeproc-output").InlineElement
+local PlainText = require("citeproc-output").PlainText
+local SortStringFormat = require("citeproc-output").SortStringFormat
+
+local Element = require("citeproc-element").Element
 local util = require("citeproc-util")
 
 
-local Name = element.Element:new()
+local Names = Element:derive("names")
+local Name = Element:derive("name", {
+  delimiter = ", ",
+  delimiter_precedes_et_al = "contextual",
+  delimiter_precedes_last = "contextual",
+  et_al_use_last = false,
+  form = "long",
+  initialize = true,
+  sort_separator = ", ",
+})
+local NamePart = Element:derive("name-part")
+local EtAl = Element:derive("et-al")
+local Substitute = Element:derive("substitute")
 
-Name.default_options = {
-  ["delimiter"] = ", ",
-  ["delimiter-precedes-et-al"] = "contextual",
-  ["delimiter-precedes-last"] = "contextual",
-  ["et-al-min"] = nil,
-  ["et-al-use-first"] = nil,
-  ["et-al-subsequent-min"] = nil,
-  ["et-al-subsequent-use-first "] = nil,
-  ["et-al-use-last"] = false,
-  ["form"] = "long",
-  ["initialize"] = true,
-  ["initialize-with"] = false,
-  ["name-as-sort-order"] = false,
-  ["sort-separator"] = ", ",
-  ["prefix"] = "",
-  ["suffix"] = "",
-}
 
-function Name:render (names, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+-- [Names](https://docs.citationstyles.org/en/stable/specification.html#names)
+function Names:new()
+  local o = Element.new(self)
+  o.name = nil
+  o.et_al = nil
+  o.substitute = nil
+  o.label = nil
+  return o
+end
 
-  local and_ = context.options["and"]
-  local delimiter = context.options["delimiter"]
-  local delimiter_precedes_et_al = context.options["delimiter-precedes-et-al"]
-  local delimiter_precedes_last = context.options["delimiter-precedes-last"]
-  local et_al_min = context.options["et-al-min"]
-  local et_al_use_first = context.options["et-al-use-first"]
-  local et_al_subsequent_min = context.options["et-al-subsequent-min"]
-  local et_al_subsequent_use_first = context.options["et-al-subsequent-use-first "]
-  local et_al_use_last = context.options["et-al-use-last"]
+function Names:from_node(node)
+  local o = Names:new()
+  o:set_attribute(node, "variable")
+  o.name = nil
+  o.et_al = nil
+  o.substitute = nil
+  o.label = nil
+  o.children = {}
+  o:process_children_nodes(node)
+  for _, child in ipairs(o.children) do
+    local element_name = child.element_name
+    if element_name == "name" then
+      o.name = child
+    elseif element_name == "et-al" then
+      o.et_al = child
+    elseif element_name == "substitute" then
+      o.substitute = child
+    elseif element_name == "label" then
+      o.label = child
+      if o.name then
+        child.after_name = true
+      end
+    else
+      util.warning(string.format('Unkown element "{}".', element_name))
+    end
+  end
+  o:get_delimiter_attribute(node)
+  o:set_affixes_attributes(node)
+  o:set_display_attribute(node)
+  o:set_formatting_attributes(node)
+  o:set_text_case_attribute(node)
+  return o
+end
 
-  -- sorting
-  if context.options["names-min"] then
-    et_al_min = context.options["names-min"]
+function Names:build_ir(engine, state, context)
+  -- names_inheritance: names and name attributes inherited from cs:style
+  --   and cs:citaiton or cs:bibliography
+  -- name_override: names, name, et-al, label elements inherited in substitute element
+  local names_inheritance = Names:new()
+  names_inheritance.delimiter = context.name_inheritance.names_delimiter
+
+  names_inheritance.variable = self.variable
+
+  for _, attr in ipairs({"delimiter", "affixes", "formatting", "display"}) do
+    if self[attr] then
+      names_inheritance[attr] = util.clone(self[attr])
+    elseif state.name_override and state.name_override[attr] then
+      names_inheritance[attr] = util.clone(state.name_override[attr])
+    end
   end
-  if context.options["names-use-first"] then
-    et_al_use_first = context.options["names-use-first"]
+
+  if self.name then
+    names_inheritance.name = util.clone(context.name_inheritance)
+    for key, value in pairs(self.name) do
+      names_inheritance.name[key] = util.clone(value)
+    end
+  else
+    if state.name_override then
+      names_inheritance.name = util.clone(state.name_override.name)
+    else
+      names_inheritance.name = util.clone(context.name_inheritance)
+    end
   end
-  if context.options["names-use-last"] ~= nil then
-    et_al_use_last = context.options["names-use-last"]
+
+  if self.et_al then
+    names_inheritance.et_al = util.clone(self.et_al)
+  elseif state.name_override then
+    names_inheritance.et_al = util.clone(state.name_override.et_al)
+  else
+    names_inheritance.et_al = EtAl:new()
   end
 
-  local form = context.options["form"]
+  if self.label then
+    names_inheritance.label = util.clone(self.label)
+  elseif state.name_override then
+    names_inheritance.label = util.clone(state.name_override.label)
+  end
 
-  local et_al_truncate = et_al_min and et_al_use_first and #names >= et_al_min
-  local et_al_last = et_al_use_last and et_al_use_first <= et_al_min - 2
+  if context.cite then
+    local position_level = context.cite.position or context.cite.position_level
+    if position_level and position_level >= util.position_map["subsequent"] then
+      if names_inheritance.name.et_al_subsequent_min then
+        names_inheritance.name.et_al_min = names_inheritance.name.et_al_subsequent_min
+      end
+      if names_inheritance.name.et_al_subsequent_use_first then
+        names_inheritance.name.et_al_use_first = names_inheritance.name.et_al_subsequent_use_first
+      end
+    end
+  end
 
-  if form == "count" then
-    if et_al_truncate then
-      return et_al_use_first
-    else
-      return #names
+  local irs = {}
+  local num_names = 0
+  -- util.debug(self.name)
+
+  -- The names element may not hold a variable attribute.
+  -- substitute_SubstituteOnlyOnceString.txt
+  if names_inheritance.variable then
+    for _, variable in ipairs(util.split(names_inheritance.variable)) do
+      local name_ir = names_inheritance.name:build_ir(variable, names_inheritance.et_al, names_inheritance.label, engine, state, context)
+      if name_ir and names_inheritance.name.form == "count" then
+        num_names = num_names + name_ir.name_count
+      end
+      if name_ir and name_ir.group_var ~= "missing" then
+        table.insert(irs, name_ir)
+      end
     end
   end
 
-  local output = nil
+  if names_inheritance.name.form == "count" then
+    if num_names > 0 then
+      local ir = Rendered:new({PlainText:new(tostring(num_names))}, self)
+      ir.name_count = num_names
+      ir.group_var = "important"
+      ir = NameIr:new({ir}, self)
+      ir.name_count = num_names
+      ir.group_var = "important"
+      -- util.debug(ir)
+      return ir
+    end
+  else
+    if #irs > 0 then
+      local ir = SeqIr:new(irs, self)
+      ir.group_var = "important"
+      ir.delimiter = names_inheritance.delimiter
+      ir.formatting = util.clone(names_inheritance.formatting)
+      ir.affixes = util.clone(names_inheritance.affixes)
+      ir.display = names_inheritance.display
+      return ir
+    end
+  end
 
-  local res = nil
-  local inverted = false
-
-  for i, name in ipairs(names) do
-    if et_al_truncate and i > et_al_use_first then
-      if et_al_last then
-        if i == #names then
-          output = richtext.concat(output, delimiter)
-          output = output .. util.unicode["horizontal ellipsis"]
-          output = output .. " "
-          res = self:render_single_name(name, i, context)
-          output = output .. res
+  if self.substitute then
+    local new_state = util.clone(state)
+    new_state.name_override = names_inheritance
+    for _, substitution in ipairs(self.substitute.children) do
+      local ir = substitution:build_ir(engine, new_state, context)
+      if ir and ir.group_var ~= "missing" then
+        if not ir.person_name_irs or #ir.person_name_irs == 0 then
+          -- In case of a <text variable="title"/> in <substitute>
+          local name_count = ir.name_count
+          ir = NameIr:new({ir}, self)
+          ir.name_count = name_count  -- sort_AguStyle.txt
+          ir.group_var = "important"
         end
-      else
-        if not self:_check_delimiter(delimiter_precedes_et_al, i, inverted) then
-          delimiter = " "
-        end
-        if output then
-          output = richtext.concat_list({output, context.et_al:render(context)}, delimiter)
-        end
-        break
+        return ir
       end
-    else
-      if i > 1 then
-        if i == #names and context.options["and"] then
-          if self:_check_delimiter(delimiter_precedes_last, i, inverted) then
-            output = richtext.concat(output, delimiter)
-          else
-            output = output .. " "
-          end
-          local and_term = ""
-          if context.options["and"] == "text" then
-            and_term = self:get_term("and"):render(context)
-          elseif context.options["and"] == "symbol" then
-            and_term = self:escape("&")
-          end
-          output = output .. and_term .. " "
+    end
+  end
+
+  local ir = Rendered:new({}, self)
+  ir.group_var = "missing"
+  return ir
+
+end
+
+
+function Names:substitute_single_field(result, context)
+  if not result then
+    return nil
+  end
+  if context.build.first_rendered_names and #context.build.first_rendered_names == 0 then
+    context.build.first_rendered_names[1] = result
+  end
+  result = self:substitute_names(result, context)
+  return result
+end
+
+function Names:substitute_names(result, context)
+  if not context.build.first_rendered_names then
+     return result
+  end
+  local name_strings = {}
+  local match_all
+
+  if #context.build.first_rendered_names > 0 then
+    match_all = true
+  else
+    match_all = false
+  end
+  for i, text in ipairs(context.build.first_rendered_names) do
+    local str = text:render(context.engine.formatter, context)
+    name_strings[i] = str
+    if context.build.preceding_first_rendered_names and str ~= context.build.preceding_first_rendered_names[i] then
+      match_all = false
+    end
+  end
+
+  if context.build.preceding_first_rendered_names then
+    local sub_str = context.options["subsequent-author-substitute"]
+    local sub_rule = context.options["subsequent-author-substitute-rule"]
+
+    if sub_rule == "complete-all" then
+      if match_all then
+        if sub_str == "" then
+          result = nil
         else
-          output = richtext.concat(output, delimiter)
+          result.contents = {sub_str}
         end
       end
-      res, inverted = self:render_single_name(name, i, context)
 
-      if res and res ~= "" then
-        res = richtext.new(res)
-        if context.build.first_rendered_names then
-          table.insert(context.build.first_rendered_names, res)
+    elseif sub_rule == "complete-each" then
+      -- In-place substitution
+      if match_all then
+        for _, text in ipairs(context.build.first_rendered_names) do
+          text.contents = {sub_str}
         end
+        result = self:concat(context.build.first_rendered_names, context)
+      end
 
-        if output then
-          output = richtext.concat(output, res)
+    elseif sub_rule == "partial-each" then
+      for i, text in ipairs(context.build.first_rendered_names) do
+        if name_strings[i] == context.build.preceding_first_rendered_names[i] then
+          text.contents = {sub_str}
         else
-          output = res
+          break
         end
       end
+      result = self:concat(context.build.first_rendered_names, context)
+
+    elseif sub_rule == "partial-first" then
+      if name_strings[1] == context.build.preceding_first_rendered_names[1] then
+        context.build.first_rendered_names[1].contents = {sub_str}
+      end
+      result = self:concat(context.build.first_rendered_names, context)
     end
   end
 
-  local ret = self:format(output, context)
-  ret = self:wrap(ret, context)
-  return ret
+  if #context.build.first_rendered_names > 0 then
+    context.build.first_rendered_names = nil
+  end
+  context.build.preceding_first_rendered_names = name_strings
+  return result
 end
 
-function Name:_check_delimiter (delimiter_attribute, index, inverted)
-  -- `delimiter-precedes-et-al` and `delimiter-precedes-last`
-  if delimiter_attribute == "always" then
-    return true
-  elseif delimiter_attribute == "never" then
-    return false
-  elseif delimiter_attribute == "contextual" then
-    if index > 2 then
-      return true
-    else
-      return false
+-- [Name](https://docs.citationstyles.org/en/stable/specification.html#name)
+function Name:new()
+  local o = Element.new(self, "name")
+
+  o.family = NamePart:new("family")
+  o.given = NamePart:new("given")
+  return o
+end
+
+function Name:from_node(node)
+  local o = Name:new()
+  o:set_attribute(node, "and")
+  o:get_delimiter_attribute(node)
+  o:set_attribute(node, "delimiter-precedes-et-al")
+  o:set_attribute(node, "delimiter-precedes-last")
+  o:set_number_attribute(node, "et-al-min")
+  o:set_number_attribute(node, "et-al-use-first")
+  o:set_number_attribute(node, "et-al-subsequent-min")
+  o:set_number_attribute(node, "et-al-subsequent-use-first")
+  o:set_bool_attribute(node, "et-al-use-last")
+  o:set_attribute(node, "form")
+  o:set_bool_attribute(node, "initialize")
+  o:set_attribute(node, "initialize-with")
+  o:set_attribute(node, "name-as-sort-order")
+  o:set_attribute(node, "sort-separator")
+  o:set_affixes_attributes(node)
+  o:set_formatting_attributes(node)
+  o:process_children_nodes(node)
+  for _, child in ipairs(o.children) do
+    if child.name == "family" then
+      o.family = child
+    elseif child.name == "given" then
+      o.given = child
     end
-  elseif delimiter_attribute == "after-inverted-name" then
-    if inverted then
-      return true
-    else
-      return false
-    end
   end
-  return false
+  if not o.family then
+    o.family = NamePart:new()
+    o.family.name = "family"
+  end
+  if not o.given then
+    o.given = NamePart:new()
+    o.family.name = "given"
+  end
+  return o
 end
 
-function Name:render_single_name (name, index, context)
-  local form = context.options["form"]
-  local initialize = context.options["initialize"]
-  local initialize_with = context.options["initialize-with"]
-  local name_as_sort_order = context.options["name-as-sort-order"]
-  if context.sorting then
-    name_as_sort_order = "all"
+
+function Name:build_ir(variable, et_al, label, engine, state, context)
+  -- Returns NameIR
+  local names
+  if not state.suppressed[variable] then
+    names = context:get_variable(variable)
   end
-  local sort_separator = context.options["sort-separator"]
+  if not names then
+    return nil
+  end
 
-  local demote_non_dropping_particle = context.options["demote-non-dropping-particle"]
+  if context.sort_key then
+    self.delimiter = "   "
+    self.name_as_sort_order = "all"
+    if context.sort_key.names_min then
+      self.et_al_min = context.sort_key.names_min
+    end
+    if context.sort_key.names_use_first then
+      self.et_al_use_first = context.sort_key.names_use_first
+    end
+    if context.sort_key.names_use_last then
+      self.et_al_use_last = context.sort_key.names_use_last
+    end
+    et_al = nil
+    label = nil
+  end
 
-  -- TODO: make it a module
-  local function _strip_quotes(str)
-    if str then
-      str = string.gsub(str, '"', "")
-      str = string.gsub(str, "'", util.unicode["apostrophe"])
+  local et_al_abbreviation = self.et_al_min and self.et_al_use_first and #names >= self.et_al_min
+  local use_last = et_al_abbreviation and self.et_al_use_last and self.et_al_use_first <= self.et_al_min - 2
+
+  if self.form == "count" then
+    local count
+    if et_al_abbreviation then
+      count = self.et_al_use_first
+    else
+      count = #names
     end
-    return str
+    local ir = Rendered:new({PlainText:new(tostring(count))}, {})
+    ir.name_count = count
+    ir.group_var = "important"
+    return ir
   end
 
-  local family = _strip_quotes(name["family"]) or ""
-  local given = _strip_quotes(name["given"]) or ""
-  local dp = _strip_quotes(name["dropping-particle"]) or ""
-  local ndp = _strip_quotes(name["non-dropping-particle"]) or ""
-  local suffix = _strip_quotes(name["suffix"]) or ""
-  local literal = _strip_quotes(name["literal"]) or ""
+  -- TODO: only build names as needed
+  local full_name_irs = {}
+  local full_name_str = ""
+  for i, name_var in ipairs(names) do
+    local person_name_ir = self:build_person_name_ir(name_var, i == 1, context)
+    table.insert(full_name_irs, person_name_ir)
 
-  if family == "" then
-    family = literal
-    if family == "" then
-      family = given
-      given = ""
+    local name_variants = person_name_ir.disam_variants
+    if full_name_str ~= "" then
+      full_name_str = full_name_str .. "     "
     end
-    if family ~= "" then
-      return family
-    else
-      error("Name not avaliable")
+    full_name_str = full_name_str .. name_variants[#name_variants]
+  end
+
+  local person_name_irs  -- TODO: rename to rendered_name_irs
+  local hidden_name_irs
+  if et_al_abbreviation then
+    person_name_irs = util.slice(full_name_irs, 1, self.et_al_use_first)
+    hidden_name_irs = util.slice(full_name_irs, self.et_al_use_first + 1, #full_name_irs)
+    if use_last then
+      table.insert(person_name_irs, full_name_irs[#full_name_irs])
+      table.remove(hidden_name_irs, #hidden_name_irs)
     end
+  else
+    person_name_irs = util.slice(full_name_irs, 1, #full_name_irs)
+    hidden_name_irs = {}
   end
 
-  if initialize_with then
-    given = self:initialize(given, initialize_with, context)
+
+  local and_term_ir
+  if not context.sort_key then
+    -- sort_WithAndInOneEntry.txt
+    local and_term
+    if self["and"] == "text" then
+      and_term = context.locale:get_simple_term("and")
+    elseif self["and"] == "symbol" then
+      and_term = "&"
+    end
+    if and_term then
+      and_term_ir = Rendered:new({PlainText:new(and_term .. " ")}, {})
+    end
   end
 
-  local demote_ndp = false  -- only active when form == "long"
-  if demote_non_dropping_particle == "display-and-sort" or
-  demote_non_dropping_particle == "sort-only" and context.sorting then
-    demote_ndp = true
-  else  -- demote_non_dropping_particle == "never"
-    demote_ndp = false
+  local et_al_ir
+  if et_al and et_al_abbreviation and not use_last then
+    et_al_ir = et_al:build_ir(engine, state, context)
   end
 
-  local family_name_part = nil
-  local given_name_part = nil
-  for _, child in ipairs(self:get_children()) do
-    if child:is_element() and child:get_element_name() == "name-part" then
-      local name_part = child:get_attribute("name")
-      if name_part == "family" then
-        family_name_part = child
-      elseif name_part == "given" then
-        given_name_part = child
+  local irs = self:join_person_name_irs(person_name_irs, and_term_ir, et_al_ir, use_last)
+
+  local ir = NameIr:new(irs, self)
+
+  ir.name_inheritance = self
+  ir.name_variable = names
+  ir.and_term_ir = and_term_ir
+  ir.et_al_ir = et_al_ir
+  ir.et_al_abbreviation = et_al_abbreviation
+  ir.use_last = use_last
+
+  ir.full_name_irs = full_name_irs
+  ir.full_name_str = full_name_str
+  ir.person_name_irs = person_name_irs
+  ir.hidden_name_irs = hidden_name_irs
+
+  -- etal_UseZeroFirst.txt: et-al-use-first="0"
+  if #irs == 0 then
+    ir.group_var = "missing"
+    return ir
+  else
+    ir.group_var = "important"
+  end
+
+  ir.formatting = self.formatting
+  ir.affixes = self.affixes
+
+  irs = {ir}
+
+  if label then
+    local is_plural = (label.plural == "always" or (label.plural == "contextual" and #names > 1))
+    local label_term = context.locale:get_simple_term(variable, label.form, is_plural)
+    if label_term and label_term ~= "" then
+      local inlines = label:render_text_inlines(label_term, context)
+      local label_ir = Rendered:new(inlines, label)
+      if label.after_name then
+        table.insert(irs, label_ir)
+      else
+        table.insert(irs, 1, label_ir)
       end
     end
   end
 
-  local res = nil
-  local inverted = false
-  if form == "long" then
-    local order
-    local suffix_separator = sort_separator
-    if not util.has_romanesque_char(name["family"]) then
-      order = {family, given}
-      inverted = true
-      sort_separator = ""
-    elseif name_as_sort_order == "all" or (name_as_sort_order == "first" and index == 1) then
+  ir = SeqIr:new(irs, self)
 
-      -- "Alan al-One"
-      local hyphen_parts = util.split(family, "%-", 1)
-      if #hyphen_parts > 1 then
-        local particle
-        particle, family = table.unpack(hyphen_parts)
-        particle = particle .. "-"
-        ndp = richtext.concat(ndp, particle)
+  -- Suppress substituted name variable
+  if state.name_override and not context.sort_key then
+    state.suppressed[variable] = true
+  end
+
+  return ir
+end
+
+function Name:build_person_name_ir(name, is_first, context)
+  local is_latin = util.has_romanesque_char(name.family)
+  local is_inverted = (name.family and name.family ~= "" and is_latin and
+    (self.name_as_sort_order == "all" or (self.name_as_sort_order == "first" and is_first)))
+
+  local inlines = self:render_person_name(name, is_first, is_latin, is_inverted, context)
+  local person_name_ir = PersonNameIr:new(inlines, self)
+
+  person_name_ir.is_inverted = is_inverted
+
+  local output_format = SortStringFormat:new()
+  person_name_ir.name_output = output_format:output(inlines)
+  person_name_ir.disam_variants_index = 1
+
+  person_name_ir.disam_variants = {person_name_ir.name_output}
+  person_name_ir.disam_inlines = {inlines}
+
+  if context.area.disambiguate_add_givenname and not context.sort_key then
+    local disam_name = util.clone(self)
+    if disam_name.form == "short" then
+      disam_name.form = "long"
+      if disam_name.initialize and disam_name.initialize_with then
+        local name_inlines = disam_name:render_person_name(name, is_first, is_latin, is_inverted, context)
+        local disam_variant = output_format:output(name_inlines)
+        local last_variant = person_name_ir.disam_variants[#person_name_ir.disam_variants]
+        if disam_variant ~= last_variant then
+          table.insert(person_name_ir.disam_variants, disam_variant)
+          person_name_ir.disam_inlines[disam_variant] = name_inlines
+        end
       end
+    end
 
-      if family_name_part then
-        family = family_name_part:format_name_part(family, context)
-        ndp = family_name_part:format_name_part(ndp, context)
+    local givenname_disambiguation_rule = context.area.givenname_disambiguation_rule
+    local only_initials = (givenname_disambiguation_rule == "all-names-with-initials" or
+      givenname_disambiguation_rule == "primary-name-with-initials")
+    if disam_name.initialize and not only_initials then
+      disam_name.initialize = false
+      local name_inlines = disam_name:render_person_name(name, is_first, is_latin, is_inverted, context)
+      local disam_variant = output_format:output(name_inlines)
+      local last_variant = person_name_ir.disam_variants[#person_name_ir.disam_variants]
+      if disam_variant ~= last_variant then
+        table.insert(person_name_ir.disam_variants, disam_variant)
+        person_name_ir.disam_inlines[disam_variant] = name_inlines
       end
-      if given_name_part then
-        given = given_name_part:format_name_part(given, context)
-        dp = family_name_part:format_name_part(dp, context)
+    end
+
+    context.sort_key = true
+    local full_name_inlines = disam_name:render_person_name(name, is_first, is_latin, is_inverted, context)
+    -- full_name is used for comparison in disambiguation
+    person_name_ir.full_name = output_format:output(full_name_inlines)
+    context.sort_key = false
+  end
+
+  return person_name_ir
+end
+
+function Name:render_person_name(name, is_first, is_latin, is_inverted, context)
+  -- Return: inlines
+  -- TODO
+  local is_sort = context.sort_key
+  local demote_ndp = (context.style.demote_non_dropping_particle == "display-and-sort" or
+    (is_sort and context.style.demote_non_dropping_particle == "sort-only"))
+
+  local name_part_tokens = self:get_display_order(name, self.form, is_latin, is_sort, is_inverted, demote_ndp)
+  -- util.debug(name)
+  -- util.debug(name_part_tokens)
+
+  local inlines = {}
+  for i, token in ipairs(name_part_tokens) do
+    if token == "family" or token == "ndp-family" or token == "dp-ndp-family-suffix" then
+      local family_inlines = self:render_family(name, token, context)
+      util.extend(inlines, family_inlines)
+
+    elseif token == "given" or token == "given-dp" or token == "given-dp-ndp" then
+      local given_inlines = self:render_given(name, token, context)
+      util.extend(inlines, given_inlines)
+
+    elseif token == "dp" or token == "dp-ndp" then
+      local particle_inlines = self:render_particle(name, token, context)
+      util.extend(inlines, particle_inlines)
+
+    elseif token == "suffix" then
+      local text = name.suffix or ""
+      util.extend(inlines, InlineElement:parse(text, context))
+
+    elseif token == "literal" then
+    local literal_inlines = self.family:format_text_case(name.literal, context)
+      util.extend(inlines, literal_inlines)
+
+    elseif token == "space" then
+      table.insert(inlines, PlainText:new(" "))
+
+    elseif token == "wide-space" then
+      table.insert(inlines, PlainText:new("   "))
+
+    elseif token == "sort-separator" then
+      table.insert(inlines, PlainText:new(self.sort_separator))
+    end
+  end
+  -- util.debug(inlines)
+  return inlines
+end
+
+-- Name-part Order
+-- https://docs.citationstyles.org/en/stable/specification.html#name-part-order
+function Name:get_display_order(name, form, is_latin, is_sort, is_inverted, demote_ndp)
+  if is_sort then
+    if not name.family then
+      -- The literal is compared with the literal
+      if self.form == "long" then
+        return {"literal", "wide-space", "wide-space", "wide-space"}
+      else
+        return {"literal", "wide-space"}
       end
+    end
 
+    if not is_latin then
+      if form == "long" and name.given then
+        return {"family", "given"}
+      else
+        return {"family"}
+      end
+    end
+
+    if self.form == "long" then
       if demote_ndp then
-        given = richtext.concat_list({given, dp, ndp}, " ")
+        return {"family", "wide-space", "dp-ndp", "wide-space", "given", "wide-space", "suffix"}
       else
-        family = richtext.concat_list({ndp, family}, " ")
-        given = richtext.concat_list({given, dp}, " ")
+        return {"ndp-family", "wide-space", "dp", "wide-space", "given", "wide-space", "suffix"}
       end
+    else
+      if demote_ndp then
+        return {"family", "wide-space", "dp-ndp"}
+      else
+        return {"ndp-family", "wide-space", "dp"}
+      end
+    end
+  end
 
-      if family_name_part then
-        family = family_name_part:wrap_name_part(family, context)
+  if not name.family then
+    if name.literal then
+      return {"literal"}
+    else
+      util.error("Invalid name")
+    end
+  end
+
+  if not is_latin then
+    if form == "long" and name.given then
+      return {"family", "given"}
+    else
+      return {"family"}
+    end
+  end
+
+  if form == "short" then
+    return {"ndp-family"}
+  end
+
+  local ndp = name["non-dropping-particle"]
+  local dp = name["dropping-particle"]
+
+  local name_part_tokens = {"family"}
+  if name.given then
+    if is_inverted then
+      if demote_ndp then
+        name_part_tokens = {"family", "sort-separator", "given-dp-ndp"}
+      else
+        name_part_tokens = {"ndp-family", "sort-separator", "given-dp"}
       end
-      if given_name_part then
-        given = given_name_part:wrap_name_part(given, context)
+    else
+      name_part_tokens = {"given", "space", "dp-ndp-family-suffix"}
+    end
+  else
+    if is_inverted then
+      if demote_ndp then
+        if ndp or dp then
+          name_part_tokens = {"family", "sort-separator", "dp-ndp"}
+        else
+          name_part_tokens = {"family"}
+        end
+      else
+        name_part_tokens = {"ndp-family"}
       end
+    else
+      name_part_tokens = {"dp-ndp-family-suffix"}
+    end
+  end
 
-      order = {family, given, suffix}
-      inverted = true
+  if name.suffix and is_inverted then
+    if is_inverted or name["comma-suffix"] then
+      table.insert(name_part_tokens, "sort-separator")
+      table.insert(name_part_tokens, "suffix")
+    elseif string.match(name.suffix, "^%p") then
+      table.insert(name_part_tokens, "sort-separator")
+      table.insert(name_part_tokens, "suffix")
     else
-      if family_name_part then
-        family = family_name_part:format_name_part(family, context)
-        ndp = family_name_part:format_name_part(ndp, context)
+      table.insert(name_part_tokens, "space")
+      table.insert(name_part_tokens, "suffix")
+    end
+  end
+
+  return name_part_tokens
+end
+
+function Name:render_family(name, token, context)
+  local inlines = {}
+  local name_part
+
+  if token == "dp-ndp-family-suffix" then
+    local dp_part = name["dropping-particle"]
+    if dp_part then
+      name_part = dp_part
+      local dp_inlines = self.given:format_text_case(dp_part, context)
+      util.extend(inlines, dp_inlines)
+    end
+  end
+
+  if token == "dp-ndp-family-suffix" or token == "ndp-family" then
+    local ndp_part = name["non-dropping-particle"]
+    if ndp_part then
+      if context.sort_key then
+        ndp_part = self:format_sort_particle(ndp_part)
       end
-      if given_name_part then
-        given = given_name_part:format_name_part(given, context)
-        dp = family_name_part:format_name_part(dp, context)
+      if #inlines > 0 then
+        table.insert(inlines, PlainText:new(" "))
       end
+      name_part = ndp_part
+      local ndp_inlines = self.family:format_text_case(ndp_part, context)
+      util.extend(inlines, ndp_inlines)
+    end
+  end
 
-      family = richtext.concat_list({dp, ndp, family}, " ")
-      if name["comma-suffix"] then
-        suffix_separator = ", "
+  local family = name.family
+  if context.sort_key then
+    -- Remove brackets for sorting: sort_NameVariable.txt
+    family = string.gsub(family, "[%[%]]", "")
+  end
+
+  local family_inlines = self.family:format_text_case(family, context)
+  if #inlines > 0 then
+    if not string.match(name_part, "^%l'$") and
+        not string.match(name_part, "^%l’$") and
+        not util.endswith(name_part, "-") then
+      table.insert(inlines, PlainText:new(" "))
+    end
+  end
+  util.extend(inlines, family_inlines)
+
+  if token == "dp-ndp-family-suffix" then
+    local suffix_part = name.suffix
+    if suffix_part then
+      if name["comma-suffix"] or util.startswith(suffix_part, "!") then
+        -- force use sort-separator exclamation prefix: magic_NameSuffixWithComma.txt
+        -- "! Jr." => "Jr."
+        table.insert(inlines, PlainText:new(self.sort_separator))
+        suffix_part = string.gsub(suffix_part, "^%p%s*", "")
       else
-        suffix_separator = " "
+        table.insert(inlines, PlainText:new(" "))
       end
-      family = richtext.concat_list({family, suffix}, suffix_separator)
+      table.insert(inlines, PlainText:new(suffix_part))
+    end
+  end
 
-      if family_name_part then
-        family = family_name_part:wrap_name_part(family, context)
-      end
-      if given_name_part then
-        given = given_name_part:wrap_name_part(given, context)
-      end
+  inlines = self.family:affixed(inlines)
+  return inlines
+end
 
-      order = {given, family}
-      sort_separator = " "
+function Name:render_given(name, token, context)
+  local given = name.given
+
+  if context.sort_key then
+    -- The empty given name is needed for evaluate the sort key.
+    if not given then
+      return {PlainText:new("")}
     end
-    res = richtext.concat_list(order, sort_separator)
+    -- Remove brackets for sorting: sort_NameVariable.txt
+    given = string.gsub(given, "[%[%]]", "")
+  end
 
-  elseif form == "short" then
-    if family_name_part then
-      family = family_name_part:format_name_part(family, context)
-      ndp = family_name_part:format_name_part(ndp, context)
+  if self.initialize_with then
+    given = self:initialize_name(given, self.initialize_with, context.style.initialize_with_hyphen)
+  end
+  local inlines = self.given:format_text_case(given, context)
+
+  if token == "given-dp" or token == "given-dp-ndp" then
+    local name_part = name["dropping-particle"]
+    if name_part then
+      table.insert(inlines, PlainText:new(" "))
+      local dp_inlines = self.given:format_text_case(name_part, context)
+      util.extend(inlines, dp_inlines)
     end
-    family = util.concat({ndp, family}, " ")
-      if family_name_part then
-        family = family_name_part:wrap_name_part(family, context)
+  end
+
+  if token == "given-dp-ndp" then
+    local name_part = name["non-dropping-particle"]
+    if name_part then
+      table.insert(inlines, PlainText:new(" "))
+      local ndp_inlines = self.family:format_text_case(name_part, context)
+      util.extend(inlines, ndp_inlines)
+    end
+  end
+
+  inlines = self.given:affixed(inlines)
+  return inlines
+end
+
+-- sort_LeadingApostropheOnNameParticle.txt
+-- "’t " => "t"
+function Name:format_sort_particle(particle)
+  particle = string.gsub(particle, "^'", "")
+  particle = string.gsub(particle, "^’", "")
+  return particle
+end
+
+function Name:render_particle(name, token, context)
+  local inlines = {}
+
+  local dp_part = name["dropping-particle"]
+  if dp_part then
+    dp_part = self:format_sort_particle(dp_part)
+    local dp_inlines = self.given:format_text_case(dp_part, context)
+    util.extend(inlines, dp_inlines)
+  end
+
+  if token == "dp-ndp" then
+    local ndp_part = name["non-dropping-particle"]
+    if ndp_part then
+      if #inlines > 0 then
+        table.insert(inlines, PlainText:new(" "))
       end
-    res = family
-  else
-    error(string.format('Invalid attribute form="%s" of "name".', form))
+      ndp_part = self:format_sort_particle(ndp_part)
+      local ndp_inlines = self.family:format_text_case(ndp_part, context)
+      util.extend(inlines, ndp_inlines)
+    end
   end
-  return res, inverted
+
+  return inlines
 end
 
-function Name:initialize (given, terminator, context)
+function Name:_check_delimiter(delimiter_attribute, num_first_names, inverted)
+  -- `delimiter-precedes-et-al` and `delimiter-precedes-last`
+  if delimiter_attribute == "always" then
+    return true
+  elseif delimiter_attribute == "never" then
+    return false
+  elseif delimiter_attribute == "contextual" then
+    if num_first_names > 1 then
+      return true
+    else
+      return false
+    end
+  elseif delimiter_attribute == "after-inverted-name" then
+    if inverted then
+      return true
+    else
+      return false
+    end
+  end
+  return false
+end
+
+-- TODO: initialize name with markups
+--   name_InTextMarkupInitialize.txt
+--   name_InTextMarkupNormalizeInitials.txt
+function Name:initialize_name(given, with, initialize_with_hyphen)
   if not given or given == "" then
     return ""
   end
 
-  local initialize = context.options["initialize"]
-  if context.options["initialize-with-hyphen"] == false then
+  if initialize_with_hyphen == false then
     given = string.gsub(given, "-", " ")
   end
 
@@ -367,9 +886,9 @@
         name_list[i-1] = name_list[i-1] .. " "
       end
     elseif is_abbreviation then
-      name_list[i] = name .. terminator
+      name_list[i] = name .. with
     else
-      if initialize then
+      if self.initialize then
         if util.is_upper(name) then
           name = first_letter
         else
@@ -389,7 +908,7 @@
           end
           name = abbreviation
         end
-        name_list[i] = name .. terminator
+        name_list[i] = name .. with
       else
         name_list[i] = name .. " "
       end
@@ -412,287 +931,176 @@
 
 end
 
-local NamePart = element.Element:new()
-
-function NamePart:format_name_part(name_part, context)
-  context = self:process_context(context)
-  local res = self:case(name_part, context)
-  res = self:format(res, context)
-  return res
-end
-
-function NamePart:wrap_name_part(name_part, context)
-  context = self:process_context(context)
-  local res = self:wrap(name_part, context)
-  return res
-end
-
-
-local EtAl = element.Element:new()
-
-EtAl.default_options = {
-  term = "et-al",
-}
-
-EtAl.render = function (self, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  local res = self:get_term(context.options["term"]):render(context)
-  res = self:format(res, context)
-  return res
-end
-
-
-local Substitute = element.Element:new()
-
-function Substitute:render (item, context)
-  self:debug_info(context)
-
-  if context.suppressed_variables then
-    -- true in layout, not in sort
-    context.suppress_subsequent_variables = true
+function Name:join_person_name_irs(rendered_name_irs, and_term_ir, et_al_ir, use_last)
+  local first_items = rendered_name_irs
+  local last_item
+  if et_al_ir then
+    first_items = rendered_name_irs
+    last_item = et_al_ir
+  elseif #rendered_name_irs > 1 then
+    first_items = util.slice(rendered_name_irs, 1, #rendered_name_irs - 1)
+    last_item = rendered_name_irs[#rendered_name_irs]
   end
 
-  for i, child in ipairs(self:get_children()) do
-    if child:is_element() then
-      local result = child:render(item, context)
-      if result and result ~= "" then
-        return result
-      end
-    end
-  end
-  return nil
-end
+  local irs = {}
 
-
-local Names = element.Element:new()
-
-function Names:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-
-  local names_delimiter = context.options["names-delimiter"]
-  if names_delimiter then
-    context.options["delimiter"] = names_delimiter
-  end
-
-  -- Inherit attributes of parent `names` element
-  local names_element = context.names_element
-  if names_element then
-    for key, value in pairs(names_element._attr) do
-      context.options[key] = value
+  for i, person_name_ir in ipairs(first_items) do
+    if i > 1 then
+      table.insert(irs, Rendered:new({PlainText:new(self.delimiter)}, self))
     end
-    for key, value in pairs(self._attr) do
-      context.options[key] = value
-    end
-  else
-    context.names_element = self
-    context.variable = context.options["variable"]
+    table.insert(irs, person_name_ir)
   end
 
-  local name, et_al, label
-  -- The position of cs:label relative to cs:name determines the order of
-  -- the name and label in the rendered text.
-  local label_position = nil
-  for _, child in ipairs(self:get_children()) do
-    if child:is_element() then
-      local element_name = child:get_element_name()
-      if element_name == "name" then
-        name = child
-        if label then
-          label_position = "before"
+  if last_item then
+    if use_last then
+      local delimiter = self.delimiter .. util.unicode["horizontal ellipsis"] .. " "
+      table.insert(irs, Rendered:new({PlainText:new(delimiter)}, self))
+      table.insert(irs, last_item)
+    elseif et_al_ir then
+      if #first_items > 0 then
+        local inverted = first_items[#first_items].is_inverted
+        local use_delimiter = self:_check_delimiter(self.delimiter_precedes_et_al, #first_items, inverted)
+        if use_delimiter then
+          table.insert(irs, Rendered:new({PlainText:new(self.delimiter)}, self))
+        elseif not et_al_ir.starts_with_cjk then
+          -- name_EtAlWithCombined.txt
+          table.insert(irs, Rendered:new({PlainText:new(" ")}, self))
         end
-      elseif element_name == "et-al" then
-        et_al = child
-      elseif element_name == "label" then
-        label = child
-        if name then
-          label_position = "after"
-        end
+        table.insert(irs, last_item)
       end
+    else
+      local inverted = first_items[#first_items].is_inverted
+      local use_delimiter = self:_check_delimiter(self.delimiter_precedes_last, #first_items, inverted)
+      if use_delimiter or not and_term_ir then
+        table.insert(irs, Rendered:new({PlainText:new(self.delimiter)}, self))
+      else
+        table.insert(irs, Rendered:new({PlainText:new(" ")}, self))
+      end
+      if and_term_ir and not et_al_ir then
+        table.insert(irs, and_term_ir)
+      end
+      table.insert(irs, last_item)
     end
   end
-  if label_position then
-    context.label_position = label_position
-  else
-    label_position = context.label_position or "after"
-  end
 
-  -- local name = self:get_child("name")
-  if not name then
-    name = context.name_element
-  end
-  if not name then
-    name = self:create_element("name", {}, self)
-    Name:set_base_class(name)
-  end
-  context.name_element = name
+  return irs
+end
 
-  -- local et_al = self:get_child("et-al")
-  if not et_al then
-    et_al = context.et_al
+-- For use in disambiguate-add-names
+function Name:expand_one_name(name_ir)
+  local rendered_name_irs = name_ir.person_name_irs
+  local hidden_name_irs = name_ir.hidden_name_irs
+  if #hidden_name_irs == 0 then
+    return nil
   end
-  if not et_al then
-    et_al = self:create_element("et-al", {}, self)
-    EtAl:set_base_class(et_al)
-  end
-  context.et_al = et_al
-
-  -- local label = self:get_child("label")
-  if label then
-    context.label = label
+  local person_name_ir_to_add = hidden_name_irs[1]
+  if name_ir.use_last then
+    table.insert(rendered_name_irs, #rendered_name_irs, person_name_ir_to_add)
   else
-    label = context.label
+    table.insert(rendered_name_irs, person_name_ir_to_add)
   end
+  table.remove(hidden_name_irs, 1)
+  if #hidden_name_irs == 0 then
+    if name_ir.et_al_abbreviation then
+      name_ir.et_al_abbreviation  = false
+    end
+    if name_ir.use_last then
+      name_ir.use_last = false
+    end
+  end
 
-  local sub_str = nil
-  if context.mode == "bibliography" and not context.sorting then
-    sub_str = context.options["subsequent-author-substitute"]
-  --   if sub_str and #context.build.preceding_first_rendered_names == 0 then
-  --     context.rendered_names = {}
-  --   else
-  --     sub_str = nil
-  --     context.rendered_names = nil
-  --   end
+  local and_term_ir = name_ir.and_term_ir
+  local et_al_ir
+  if name_ir.et_al_abbreviation then
+    et_al_ir = name_ir.et_al_ir
   end
+  local use_last = name_ir.use_last
 
-  local variable_names = context.options["variable"] or context.variable
-  local ret = nil
+  name_ir.children = self:join_person_name_irs(rendered_name_irs, and_term_ir, et_al_ir, use_last)
+  return person_name_ir_to_add
+end
 
-  if variable_names then
-    local output = {}
-    local num_names = 0
-    for _, role in ipairs(util.split(variable_names)) do
-      local names = self:get_variable(item, role, context)
 
-      table.insert(context.variable_attempt, names ~= nil)
+-- [Name-part](https://docs.citationstyles.org/en/stable/specification.html#name-part-formatting)
+function NamePart:new(name)
+  local o = Element.new(self)
+  o.name = name
+  return o
+end
 
-      if names then
-        local res = name:render(names, context)
-        if res then
-          if type(res) == "number" then  -- name[form="count"]
-            num_names = num_names + res
-          elseif label and not context.sorting then
-            -- drop name label in sorting
-            local label_result = label:render(role, context)
-            if label_result then
-              if label_position == "before" then
-                res = richtext.concat(label_result, res)
-              else
-                res = richtext.concat(res, label_result)
-              end
-            end
-          end
-        end
-        table.insert(output, res)
-      end
-    end
+function NamePart:from_node(node)
+  local o = NamePart:new()
+  o:set_attribute(node, "name")
+  o:set_formatting_attributes(node)
+  o:set_text_case_attribute(node)
+  o:set_affixes_attributes(node)
+  return o
+end
 
-    if num_names > 0 then
-      ret = tostring(num_names)
-    else
-      ret = self:concat(output, context)
-      if ret and sub_str and context.build.first_rendered_names then
-        ret = self:substitute_names(ret, context)
-      end
-    end
+function NamePart:format_text_case(text, context)
+  local output_format = context.format
+  local inlines = InlineElement:parse(text, context)
+  local is_english = context:is_english()
+  if not output_format then
+    print(debug.traceback())
   end
+  output_format:apply_text_case(inlines, self.text_case, is_english)
 
-  if ret then
-    ret = self:format(ret, context)
-    ret = self:wrap(ret, context)
-    ret = self:display(ret, context)
-    return ret
-  else
-    local substitute = self:get_child("substitute")
-    if substitute then
-      ret = substitute:render(item, context)
+  inlines = output_format:with_format(inlines, self.formatting)
+  return inlines
+end
+
+function NamePart:affixed(inlines)
+  if self.affixes then
+    if self.affixes.prefix then
+      table.insert(inlines, 1, PlainText:new(self.affixes.prefix))
     end
-    if ret and sub_str then
-      ret = self:substitute_single_field(ret, context)
+    if self.affixes.suffix then
+      table.insert(inlines, PlainText:new(self.affixes.suffix))
     end
-    return ret
   end
+  return inlines
 end
 
-function Names:substitute_single_field(result, context)
-  if not result then
-    return nil
-  end
-  if context.build.first_rendered_names and #context.build.first_rendered_names == 0 then
-    context.build.first_rendered_names[1] = result
-  end
-  result = self:substitute_names(result, context)
-  return result
+
+-- [Et-al](https://docs.citationstyles.org/en/stable/specification.html#et-al)
+EtAl.term = "et-al"
+
+function EtAl:from_node(node)
+  local o = EtAl:new()
+  o:set_attribute(node, "term")
+  o:set_formatting_attributes(node)
+  return o
 end
 
-function Names:substitute_names(result, context)
-  if not context.build.first_rendered_names then
-     return result
+function EtAl:build_ir(engine, state, context)
+  local term = context.locale:get_simple_term(self.term)
+  if not term then
+    return term
   end
-  local name_strings = {}
-  local match_all
-
-  if #context.build.first_rendered_names > 0 then
-    match_all = true
-  else
-    match_all = false
+  local inlines = InlineElement:parse(term, context)
+  if #inlines == 0 then
+    return nil
   end
-  for i, text in ipairs(context.build.first_rendered_names) do
-    local str = text:render(context.engine.formatter, context)
-    name_strings[i] = str
-    if context.build.preceding_first_rendered_names and str ~= context.build.preceding_first_rendered_names[i] then
-      match_all = false
-    end
-  end
 
-  if context.build.preceding_first_rendered_names then
-    local sub_str = context.options["subsequent-author-substitute"]
-    local sub_rule = context.options["subsequent-author-substitute-rule"]
+  inlines = context.format:with_format(inlines, self.formatting)
 
-    if sub_rule == "complete-all" then
-      if match_all then
-        if sub_str == "" then
-          result = nil
-        else
-          result.contents = {sub_str}
-        end
-      end
+  local ir = Rendered:new(inlines, self)
 
-    elseif sub_rule == "complete-each" then
-      -- In-place substitution
-      if match_all then
-        for _, text in ipairs(context.build.first_rendered_names) do
-          text.contents = {sub_str}
-        end
-        result = self:concat(context.build.first_rendered_names, context)
-      end
-
-    elseif sub_rule == "partial-each" then
-      for i, text in ipairs(context.build.first_rendered_names) do
-        if name_strings[i] == context.build.preceding_first_rendered_names[i] then
-          text.contents = {sub_str}
-        else
-          break
-        end
-      end
-      result = self:concat(context.build.first_rendered_names, context)
-
-    elseif sub_rule == "partial-first" then
-      if name_strings[1] == context.build.preceding_first_rendered_names[1] then
-        context.build.first_rendered_names[1].contents = {sub_str}
-      end
-      result = self:concat(context.build.first_rendered_names, context)
-    end
+  if util.is_cjk_char(utf8.codepoint(term, 1)) then
+    ir.starts_with_cjk = true
   end
 
-  if #context.build.first_rendered_names > 0 then
-    context.build.first_rendered_names = nil
-  end
-  context.build.preceding_first_rendered_names = name_strings
-  return result
+  return ir
 end
 
+function Substitute:from_node(node)
+  local o = Substitute:new()
+  o:process_children_nodes(node)
+  return o
+end
+
+
 names_module.Names = Names
 names_module.Name = Name
 names_module.NamePart = NamePart

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-number.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-number.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-number.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,97 +6,63 @@
 
 local number_module = {}
 
-local element = require("citeproc-element")
-local util = require("citeproc-util")
+local Element = require("citeproc-element").Element
 
+local Rendered = require("citeproc-ir-node").Rendered
 
-local Number = element.Element:new()
+local util = require("citeproc-util")
 
-function Number:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  local variable = context.options["variable"]
-  local content = self:get_variable(item, variable, context)
 
-  table.insert(context.variable_attempt, content ~= nil)
+local Number = Element:derive("number")
 
-  if not content then
-    return nil
-  end
+function Number:new(node)
+  local o = {
+    element_name = "number",
+    form = "numeric",
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
 
-  local numbers = {}
-  local punct_list = {}
-  local last_position = 1
-  for number, punct, pos in string.gmatch(content, "(.-)%s*([-,&])%s*()") do
-    table.insert(numbers, number)
-    table.insert(punct_list, punct)
-    last_position = pos
-  end
-  table.insert(numbers, string.sub(content, last_position))
-
-  local res = ""
-  for i, number in ipairs(numbers) do
-    local punct = punct_list[i]
-    number = self:_format_single_number(number, context)
-    res = res .. number
-
-    if punct == "-" then
-      res = res .. punct
-    elseif punct == "," then
-      res = res .. punct .. " "
-    elseif punct == "&" then
-      res = res .. " " .. punct .. " "
-    end
-  end
-
-  res = self:case(res, context)
-  res = self:wrap(res, context)
-  res = self:display(res, context)
-
-  return res
+function Number:from_node(node)
+  local o = Number:new()
+  o:set_attribute(node, "variable")
+  o:set_attribute(node, "form")
+  o:set_affixes_attributes(node)
+  o:set_display_attribute(node)
+  o:set_formatting_attributes(node)
+  o:set_text_case_attribute(node)
+  return o
 end
 
-function Number:_format_single_number(number, context)
-  local form = context.options["form"] or "numeric"
-  if form  == "numeric" or not string.match(number, "^%d+$") then
-    return number
+function Number:build_ir(engine, state, context)
+  local number
+  if not state.suppressed[self.variable] then
+    number = context:get_variable(self.variable, self.form)
   end
-  number = tonumber(number)
-  if form == "ordinal" or form == "long-ordinal" then
-    return self:_format_oridinal(number, form, context)
-  elseif form == "roman" then
-    return util.convert_roman(number)
+  if not number then
+    local ir = Rendered:new({}, self)
+    ir.group_var = "missing"
+    return ir
   end
-end
 
-function Number:_format_oridinal(number, form, context)
-  assert(type(number) == "number")
-  local variable = context.options["variable"]
-
-  if form == "long-ordinal" then
-    if number < 1 or number > 10 then
-      form = "ordinal"
-    end
+  if type(number) == "number" then
+    number = tostring(number)
+    number = self:format_number(number, self.variable, self.form, context)
+  elseif util.is_numeric(number) then
+    number = self:format_number(number, self.variable, self.form, context)
   end
 
-  local gender = nil
-  local term = self:get_term(variable)
-  if term then
-    gender = term:get_attribute("gender")
-  end
+  local inlines = self:render_text_inlines(number, context)
+  local ir = Rendered:new(inlines, self)
+  ir.group_var = "important"
 
-  term = self:get_term(form, nil, number, gender)
-  local res = term:render(context)
-  if form == "ordinal" then
-    if res then
-      return tostring(number) .. res
-    else
-      res = tostring(number)
-    end
-  else
-    return res
+  -- Suppress substituted name variable
+  if state.name_override and not context.sort_key then
+    state.suppressed[self.variable] = true
   end
-  return res
+  return ir
 end
 
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-sort.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-sort.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-sort.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -8,57 +8,80 @@
 
 local unicode = require("unicode")
 
-local element = require("citeproc-element")
+local Element = require("citeproc-element").Element
+local Date = require("citeproc-node-date").Date
 local names = require("citeproc-node-names")
-local date = require("citeproc-node-date")
+local InlineElement = require("citeproc-output").InlineElement
 local util = require("citeproc-util")
 
 
-local Sort = element.Element:new()
+-- [Sorting](https://docs.citationstyles.org/en/stable/specification.html#sorting)
+local Sort = Element:derive("sort")
 
-function Sort:sort (items, context)
+function Sort:from_node(node)
+  local o = Sort:new()
+  o.children = {}
+
+  o:process_children_nodes(node)
+  o.sort_directions = {}
+  for i, key in ipairs(o.children) do
+    o.sort_directions[i] = (key.sort ~= "descending")
+  end
+  table.insert(o.sort_directions, true)
+
+  return o
+end
+
+function Sort:sort(items, state, context)
   -- key_map = {
   --   id1 = {key1, key2, ...},
   --   id2 = {key1, key2, ...},
   --   ...
   -- }
-  context.variable_attempt = {}
-
   local key_map = {}
-  local sort_directions = {}
+  local sort_directions = self.sort_directions
   -- true: ascending
   -- false: descending
 
-  if not Sort.collator_obj then
-    local lang = context.style.lang
+  if not Sort.collator then
+    local lang = context.engine.lang
     local language = string.sub(lang, 1, 2)
     -- It's 6 seconds slower to run the whole test-suite if these package
     -- loading statements are put in the header.
-    local ducet = require("lua-uca.lua-uca-ducet")
-    local collator = require("lua-uca.lua-uca-collator")
-    local languages = require("lua-uca.lua-uca-languages")
-    local collator_obj = collator.new(ducet)
-    if languages[language] then
-      Sort.collator_obj = languages[language](collator_obj)
-    else
-      util.warning(string.format('Lcoale "%s" is not supported.', lang))
+    local uca_ducet = require("lua-uca.lua-uca-ducet")
+    local uca_collator = require("lua-uca.lua-uca-collator")
+    Sort.collator = uca_collator.new(uca_ducet)
+    if language ~= "en" then
+      local uca_languages = require("lua-uca.lua-uca-languages")
+      if uca_languages[language] then
+        Sort.collator = uca_languages[language](Sort.collator)
+      else
+        util.warning(string.format('Locale "%s" is not provided by lua-uca. The sorting order may be incorrect.', lang))
+      end
     end
   end
 
-  for _, item in ipairs(items) do
-    if not key_map[item.id] then
-      key_map[item.id] = {}
+  -- TODO: optimize: use cached values for fixed keys
+  for i, item in ipairs(items) do
+    key_map[item.id] = {}
 
-      context.item = item
-      for i, key in ipairs(self:query_selector("key")) do
-        if sort_directions[i] == nil then
-          local direction = (key:get_attribute("sort") ~= "descending")
-          sort_directions[i] = direction
-        end
-        local value = key:render(item, context)
-        table.insert(key_map[item.id], value)
+    context.id = item.id
+    context.cite = item
+    context.reference = context.engine.registry.registry[item.id]
+
+    for j, key in ipairs(self.children) do
+      if context.reference then
+        context.sort_key = key
+        local key_str = key:eval(context.engine, state, context)
+        key_map[item.id][j] = key_str
+      else
+        -- The entry is missing
+        key_map[item.id][j] = false
       end
     end
+    -- To preserve the original order of items with same sort keys
+    -- sort_NameImplicitSortOrderAndForm.txt
+    table.insert(key_map[item.id], i)
   end
 
   -- util.debug(key_map)
@@ -66,6 +89,7 @@
   local function compare_entry(item1, item2)
     return self.compare_entry(key_map, sort_directions, item1, item2)
   end
+  -- util.debug(items)
   table.sort(items, compare_entry)
 
   return items
@@ -80,8 +104,8 @@
 end
 
 function Sort.compare_strings(str1, str2)
-  if Sort.collator_obj then
-    return Sort.collator_obj:compare_strings(str1, str2)
+  if Sort.collator then
+    return Sort.collator:compare_strings(str1, str2)
   else
     return str1 < str2
   end
@@ -109,78 +133,99 @@
   end
 end
 
-local Key = element.Element:new()
+local Key = Element:derive("key")
 
-function Key:render (item, context)
-  context = self:process_context(context)
-  context.options["name-as-sort-order"] = "all"
-  context.sorting = true
-  local variable = self:get_attribute("variable")
-  local res = nil
-  if variable then
-    context.variable = variable
-    local variable_type = util.variable_types[variable]
+function Key:new()
+  local o = Element.new(self)
+  Key.sort = "ascending"
+  return o
+end
+
+function Key:from_node(node)
+  local o = Key:new()
+  o:set_attribute(node, "sort")
+  o:set_attribute(node, "variable")
+  o:set_attribute(node, "macro")
+  o:set_number_attribute(node, "names-min")
+  o:set_number_attribute(node, "names-use-first")
+  o:set_bool_attribute(node, "names-use-last")
+  return o
+end
+
+function Key:eval(engine, state, context)
+  local res
+  if self.variable then
+    local variable_type = util.variable_types[self.variable]
     if variable_type == "name" then
-      res = self:_render_name(item, context)
+      res = self:eval_name(engine, state, context)
     elseif variable_type == "date" then
-      res = self:_render_date(item, context)
+      res = self:eval_date(context)
     elseif variable_type == "number" then
-      res = item[variable]
+      local value = context:get_variable(self.variable)
+      if type(value) == "string" and string.match(value, "%s+") then
+        value = tonumber(value)
+      end
+      res = value
     else
-      res = item[variable]
+      res = context:get_variable(self.variable)
+      if type(res) == "string" then
+        local inlines = InlineElement:parse(res, context)
+        res = context.format:output(inlines)
+      end
     end
-  else
-    local macro = self:get_attribute("macro")
-    if macro then
-      res = self:get_macro(macro):render(item, context)
+  elseif self.macro then
+    local macro = context:get_macro(self.macro)
+    state:push_macro(self.macro)
+    local ir = macro:build_ir(engine, state, context)
+    state:pop_macro(self.macro)
+    if ir.name_count then
+      return ir.name_count
+    elseif ir.sort_key ~= nil then
+      return ir.sort_key
     end
+    local output_format = context.format
+    local inlines = ir:flatten(output_format)
+    -- util.debug(inlines)
+    local str = output_format:output(inlines)
+    return str
   end
   if res == nil then
+    -- make table.insert(_, nil) work
     res = false
-  elseif type(res) == "table" and res._type == "RichText" then
-    res = res:render(nil, context)
   end
-  if type(res) == "string" then
-    res = self._normalize_string(res)
-  end
   return res
 end
 
-function Key:_render_name (item, context)
-  if not self.names then
-    self.names = self:create_element("names", {}, self)
-    names.Names:set_base_class(self.names)
-    self.names:set_attribute("variable", context.options["variable"])
-    self.names:set_attribute("form", "long")
+function Key:eval_name(engine, state, context)
+  if not self.name_inheritance then
+    self.name_inheritance = util.clone(context.name_inheritance)
   end
-  local res = self.names:render(item, context)
-  return res
+  local name = context:get_variable(self.variable)
+  if not name then
+    return false
+  end
+  local ir = self.name_inheritance:build_ir(self.variable, nil, nil, engine, state, context)
+  if ir.name_count then
+    -- name count
+    return ir.name_count
+  end
+  local output_format = context.format
+  local inlines = ir:flatten(output_format)
+
+  local str = output_format:output(inlines)
+
+  return str
 end
 
-function Key:_render_date (item, context)
+function Key:eval_date(context)
   if not self.date then
-    self.date = self:create_element("date", {}, self)
-    date.Date:set_base_class(self.date)
-    self.date:set_attribute("variable", context.options["variable"])
-    self.date:set_attribute("form", "numeric")
+    self.date = Date:new()
+    self.date.variable = self.variable
+    self.date.form = "numeric"
+    self.date.date_parts = "year-month-day"
   end
-  local res = self.date:render(item, context)
-  return res
+  return self.date:render_sort_key(context.engine, nil, context)
 end
-function Key._normalize_string(str)
-  str = unicode.utf8.lower(str)
-  str = string.gsub(str, "[%[%]]", "")
-  local words = {}
-  for _, word in ipairs(util.split(str, " ")) do
-    -- TODO: strip leading prepositions
-    -- remove leading apostrophe on name particle
-    word = string.gsub(word, "^" .. util.unicode["apostrophe"], "")
-    table.insert(words, word)
-  end
-  str = table.concat(words, " ")
-  str = string.gsub(str, util.unicode["apostrophe"], "'")
-  return str
-end
 
 
 sort.Sort = Sort

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-style.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-style.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-style.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -4,15 +4,126 @@
 -- Repository: https://github.com/zepinglee/citeproc-lua
 --
 
-local style = {}
+local style_module = {}
 
-local element = require("citeproc-element")
+local dom = require("luaxml-domobject")
+
+local Element = require("citeproc-element").Element
+local IrNode = require("citeproc-ir-node").IrNode
+local Rendered = require("citeproc-ir-node").Rendered
+local SeqIr = require("citeproc-ir-node").SeqIr
+local PlainText = require("citeproc-output").PlainText
+local DisamStringFormat = require("citeproc-output").DisamStringFormat
 local util = require("citeproc-util")
 
 
-local Style = element.Element:new()
+local Style = Element:derive("style")
 
-Style.default_options = {
+function Style:new()
+  local o = {
+    children = {},
+    macros = {},
+    locales = {},
+    initialize_with_hyphen = true,
+    demote_non_dropping_particle = "display-and-sort",
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function Style:parse(xml_str)
+  local csl_xml = dom.parse(xml_str)
+  if not csl_xml then
+    error("Failed to parse CSL style.")
+  end
+  local style_node = csl_xml:get_path("style")[1]
+  if not csl_xml then
+    error('Element "style" not found.')
+  end
+  return Style:from_node(style_node)
+end
+
+local function make_name_inheritance(name, node)
+  name:set_attribute(node, "and")
+  name:set_attribute(node, "delimiter-precedes-et-al")
+  name:set_attribute(node, "delimiter-precedes-last")
+  name:set_number_attribute(node, "et-al-min")
+  name:set_number_attribute(node, "et-al-use-first")
+  name:set_number_attribute(node, "et-al-subsequent-min")
+  name:set_number_attribute(node, "et-al-subsequent-use-first")
+  name:set_bool_attribute(node, "et-al-use-last")
+  name:set_bool_attribute(node, "initialize")
+  name:set_attribute(node, "initialize-with")
+  name:set_attribute(node, "name-as-sort-order")
+  name:set_attribute(node, "sort-separator")
+  local delimiter = node:get_attribute("name-delimiter")
+  if delimiter then
+    name.delimiter = delimiter
+  end
+  local form = node:get_attribute("name-form")
+  if form then
+    name.form = form
+  end
+  local names_delimiter = node:get_attribute("names-delimiter")
+  if names_delimiter then
+    name.names_delimiter = names_delimiter
+  end
+end
+
+function Style:from_node(node)
+  local o = Style:new()
+
+  o:set_attribute(node, "class")
+  o:set_attribute(node, "default-locale")
+  o:set_attribute(node, "version")
+
+  -- Global Options
+  o.initialize_with_hyphen = true
+  o:set_bool_attribute(node, "initialize-with-hyphen")
+  o:set_attribute(node, "page-range-format")
+  o:set_attribute(node, "demote-non-dropping-particle")
+
+  -- Inheritable Name Options
+  -- https://docs.citationstyles.org/en/stable/specification.html#inheritable-name-options
+  o.name_inheritance = require("citeproc-node-names").Name:new()
+  make_name_inheritance(o.name_inheritance, node)
+
+  if o.page_range_format == "chicago" then
+    if o.version < "1.1" then
+      o.page_range_format = "chicago-15"
+    else
+      o.page_range_format = "chicago-16"
+    end
+  end
+
+  o.macros = {}
+  o.locales = {}
+
+  o.children = {}
+  o:process_children_nodes(node)
+
+  for _, child in ipairs(o.children) do
+    local element_name = child.element_name
+    if element_name == "info" then
+      o.info = child
+    elseif element_name == "citation" then
+      o.citation = child
+    elseif element_name == "bibliography" then
+      o.bibliography = child
+    elseif element_name == "macro" then
+      o.macros[child.name] = child
+    elseif element_name == "locale" then
+      local xml_lang = child.xml_lang or "@generic"
+      o.locales[xml_lang] = child
+    end
+  end
+
+  return o
+end
+
+
+Style._default_options = {
   ["initialize-with-hyphen"] = true,
   ["page-range-format"] = nil,
   ["demote-non-dropping-particle"] = "display-and-sort",
@@ -29,171 +140,385 @@
   end
 end
 
-function Style:render_citation (items, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  context.style = self
-  local citation = self:get_child("citation")
-  return citation:render(items, context)
-end
 
-function Style:render_biblography (items, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  context.style = self
-  local bibliography = self:get_child("bibliography")
-  return bibliography:render(items, context)
-end
+local Info = Element:derive("info")
 
-function Style:get_version ()
-  return self:get_attribute("version")
-end
 
-function Style:get_locales()
-  if not self.locale_dict then
-    self.locale_dict = {}
+function Info:from_node(node)
+  local o = Info:new()
+
+  -- o.authors = nil
+  -- o.contributors = nil
+  o.categories = {}
+  o.id = nil
+  -- o.issn = nil
+  -- o.eissn = nil
+  -- o.issnl = nil
+  o.links = {
+    independent_parent = nil,
+  }
+  -- o.published = nil
+  -- o.rights = nil
+  -- o.summary = nil
+  o.title = nil
+  -- o.title_short = nil
+  o.updated = nil
+
+  for _, child in ipairs(node:get_children()) do
+    if child:is_element() then
+      local element_name = child:get_element_name()
+      if element_name == "category" then
+        local citation_format = child:get_attribute("citation-format")
+        if citation_format then
+          o.categories.citation_format = citation_format
+        end
+
+      elseif element_name == "id" then
+        o.id = child:get_text()
+
+      elseif element_name == "link" then
+        local href = child:get_attribute("href")
+        local rel = child:get_attribute("rel")
+        if href and rel == "independent-parent" then
+          o.links.independent_parent = href
+        end
+
+      elseif element_name == "title" then
+        o.title = child:get_text()
+
+      elseif element_name == "updated" then
+        o.updated = child:get_text()
+
+      end
+    end
   end
-  local locales = self.locale_dict[self.lang]
-  if not locales then
-    locales = self:get_locale_list(self.lang)
-    self.locale_dict[self.lang] = locales
-  end
-  return locales
+
+  return o
 end
 
-function Style:get_locale_list (lang)
-  assert(lang ~= nil)
-  local language = string.sub(lang, 1, 2)
-  local primary_dialect = util.primary_dialects[language]
-  if not primary_dialect then
-    -- util.warning(string.format("Failed to find primary dialect of \"%s\"", language))
-  end
-  local locale_list = {}
 
-  -- 1. In-style cs:locale elements
-  --    i. `xml:lang` set to chosen dialect, “de-AT”
-  if lang == language then
-    lang = primary_dialect
-  end
-  table.insert(locale_list, self:get_in_style_locale(lang))
+local Citation = Element:derive("citation", {
+  givenname_disambiguation_rule = "by-cite",
+  -- https://github.com/citation-style-language/schema/issues/338
+  -- The cite_group_delimiter may be changed to inherit the delimiter in citaion > layout.
+  cite_group_delimiter = ", ",
+  near_note_distance = 5,
+})
 
-  --    ii. `xml:lang` set to matching language, “de” (German)
-  if language and language ~= lang then
-    table.insert(locale_list, self:get_in_style_locale(language))
-  end
+function Citation:from_node(node, style)
 
-  --    iii. `xml:lang` not set
-  table.insert(locale_list, self:get_in_style_locale(nil))
+  local o = Citation:new()
+  o.children = {}
 
-  -- 2. Locale files
-  --    iv. `xml:lang` set to chosen dialect, “de-AT”
-  if lang then
-    table.insert(locale_list, self:get_engine():get_system_locale(lang))
+  o:process_children_nodes(node)
+
+  -- o.layouts = nil  -- CSL-M extension
+
+  for _, child in ipairs(o.children) do
+    local element_name = child.element_name
+    if element_name == "layout" then
+      o.layout = child
+    elseif element_name == "sort" then
+      o.sort = child
+    end
   end
 
-  --    v. `xml:lang` set to matching primary dialect, “de-DE” (Standard German)
-  --       (only applicable when the chosen locale is a secondary dialect)
-  if primary_dialect and primary_dialect ~= lang then
-    table.insert(locale_list, self:get_engine():get_system_locale(primary_dialect))
+  -- Disambiguation
+  o:set_bool_attribute(node, "disambiguate-add-givenname")
+  o:set_attribute(node, "givenname-disambiguation-rule")
+  o:set_bool_attribute(node, "disambiguate-add-names")
+  o:set_bool_attribute(node, "disambiguate-add-year-suffix")
+
+  -- Cite Grouping
+  o:set_attribute(node, "cite-group-delimiter")
+  -- In the current citeproc-js implementation and test suite,
+  -- cite grouping is activated by setting the cite-group-delimiter
+  -- attribute or the collapse attributes on cs:citation.
+  -- It may be changed to an independent procedure.
+  -- https://github.com/citation-style-language/schema/issues/338
+  if node:get_attribute("cite-group-delimiter") then
+    o.cite_grouping = true
+  else
+    o.cite_grouping = false
   end
 
-  --    vi. `xml:lang` set to “en-US” (American English)
-  if lang ~= "en-US" and primary_dialect ~= "en-US" then
-    table.insert(locale_list, self:get_engine():get_system_locale("en-US"))
+
+  -- Cite Collapsing
+  o:set_attribute(node, "collapse")
+  o:set_attribute(node, "year-suffix-delimiter")
+  if not o.year_suffix_delimiter then
+    o.year_suffix_delimiter = o.layout.delimiter
   end
+  o:set_attribute(node, "after-collapse-delimiter")
+  if not o.after_collapse_delimiter then
+    o.after_collapse_delimiter = o.layout.delimiter
+  end
 
-  return locale_list
-end
+  -- Note Distance
+  o:set_number_attribute(node, "near-note-distance")
 
-function Style:get_in_style_locale (lang)
-  for _, locale in ipairs(self:query_selector("locale")) do
-    if locale:get_attribute("xml:lang") == lang then
-      return locale
+  local name_inheritance = require("citeproc-node-names").Name:new()
+  for key, value in pairs(style.name_inheritance) do
+    if value ~= nil then
+      name_inheritance[key] = value
     end
   end
-  return nil
+  make_name_inheritance(name_inheritance, node)
+  o.name_inheritance = name_inheritance
+
+  -- update_mode = "plain" or "numeric" or "position" (or "both"?)
+
+  return o
 end
 
-function Style:get_term (...)
-  for _, locale in ipairs(self:get_locales()) do
-    local res = locale:get_term(...)
-    if res then
-      return res
-    end
+function Citation:build_ir(engine, state, context)
+  if not self.layout then
+    util.error("Missing citation layout.")
   end
-  return nil
+  return self.layout:build_ir(engine, state, context)
 end
 
 
-local Citation = element.Element:new()
+local Bibliography = Element:derive("bibliography", {
+  hanging_indent = false,
+  line_spacing = 1,
+  entry_spacing = 1,
+  subsequent_author_substitute_rule = "complete-all"
+})
 
-function Citation:render (items, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+function Bibliography:from_node(node, style)
+  local o = Bibliography:new()
+  o.children = {}
 
-  context.mode = "citation"
-  context.citation = self
+  o:process_children_nodes(node)
 
-  local sort = self:get_child("sort")
-  if sort then
-    sort:sort(items, context)
+  -- o.layouts = nil  -- CSL-M extension
+
+  for _, child in ipairs(o.children) do
+    local element_name = child.element_name
+    if element_name == "layout" then
+      o.layout = child
+    elseif element_name == "sort" then
+      o.sort = child
+    end
   end
 
-  local layout = self:get_child("layout")
-  return layout:render(items, context)
+  -- Whitespace
+  o:set_bool_attribute(node, "hanging-indent")
+  o:set_attribute(node, "second-field-align")
+  o:set_number_attribute(node, "line-spacing")
+  o:set_number_attribute(node, "entry-spacing")
+
+  -- Reference Grouping
+  o:set_attribute(node, "subsequent-author-substitute")
+  o:set_attribute(node, "subsequent-author-substitute-rule")
+
+  local name_inheritance = require("citeproc-node-names").Name:new()
+  for key, value in pairs(style.name_inheritance) do
+    if value ~= nil then
+      name_inheritance[key] = value
+    end
+  end
+  make_name_inheritance(name_inheritance, node)
+  o.name_inheritance = name_inheritance
+
+  return o
 end
 
+function Bibliography:build_ir(engine, state, context)
+  if not self.layout then
+    util.error("Missing bibliography layout.")
+  end
+  local ir = self.layout:build_ir(engine, state, context)
+  -- util.debug(ir)
+  if self.second_field_align == "flush" and #ir.children >= 2 then
+    ir.children[1].display = "left-margin"
+    local right_inline_ir = SeqIr:new(util.slice(ir.children, 2), self)
+    right_inline_ir.display = "right-inline"
+    if ir.affixes then
+      right_inline_ir.affixes = ir.affixes
+      right_inline_ir.formatting = ir.formatting
+      ir.affixes = nil
+      ir.formatting = nil
+    end
+    ir.children = {ir.children[1], right_inline_ir}
+  end
 
-local Bibliography = element.Element:new()
+  if self.subsequent_author_substitute then
+    self:substitute_subsequent_authors(engine, ir)
+  end
 
-Bibliography.default_options = {
-  ["hanging-indent"] = false,
-  ["second-field-align"] = nil,
-  ["line-spacing"] = 1,
-  ["entry-spacing"] = 1,
-  ["subsequent-author-substitute"] = nil,
-  ["subsequent-author-substitute-rule"] = "complete-all",
-}
+  if not ir then
+    ir = Rendered:new(PlainText:new("[CSL STYLE ERROR: reference with no printed form.]"), self)
+  end
+  return ir
+end
 
-function Bibliography:render (items, context)
-  self:debug_info(context)
-  context = self:process_context(context)
-  -- util.debug(context)
+function Bibliography:substitute_subsequent_authors(engine, ir)
+  ir.first_name_ir = self:find_first_name_ir(ir)  -- should be a SeqIr wiht _element = "names"
+  if not ir.first_name_ir then
+    engine.previous_bib_names_ir = nil
+    return
+  end
+  if self.subsequent_author_substitute_rule == "complete-all" then
+    self:substitute_subsequent_authors_complete_all(engine, ir)
+  elseif self.subsequent_author_substitute_rule == "complete-each" then
+    self:substitute_subsequent_authors_complete_each(engine, ir)
+  elseif self.subsequent_author_substitute_rule == "partial-each" then
+    self:substitute_subsequent_authors_partial_each(engine, ir)
+  elseif self.subsequent_author_substitute_rule == "partial-first" then
+    self:substitute_subsequent_authors_partial_first(engine, ir)
+  end
+  engine.previous_bib_names_ir = ir.first_name_ir
+end
 
-  context.mode = "bibliography"
-  context.bibliography = self
+function Bibliography:find_first_name_ir(ir)
+  if ir._type == "NameIr" then
+    return ir
+  elseif ir.children then
+    for _, child_ir in ipairs(ir.children) do
+      local first_name_ir = self:find_first_name_ir(child_ir)
+      if first_name_ir then
+        return first_name_ir
+      end
+    end
+  end
+  return nil
+end
 
-  -- Already sorted in CiteProc:sort_bibliography()
+function Bibliography:substitute_subsequent_authors_complete_all(engine, ir)
+  local bib_names_str = ""
 
-  local layout = self:get_child("layout")
-  local res = layout:render(items, context)
+  if #ir.first_name_ir.person_name_irs > 0 then
+    for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+      if bib_names_str ~= "" then
+        bib_names_str = bib_names_str .. "     "
+      end
+      local name_variants = person_name_ir.disam_variants
+      bib_names_str = bib_names_str .. name_variants[#name_variants]
+    end
+  else
+    -- In case of a <text variable="title"/> in <substitute>
+    local disam_format = DisamStringFormat:new()
+    local inlines = ir.first_name_ir:flatten(disam_format)
+    bib_names_str = disam_format:output(inlines)
+  end
+  ir.first_name_ir.bib_names_str = bib_names_str
 
-  local params = res[1]
+  if engine.previous_bib_names_ir and
+      engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+    local text = self.subsequent_author_substitute
+    if text == "" then
+      ir.first_name_ir.children = {}
+      ir.first_name_ir.group_var = "missing"
+    else
+      -- the output of label is not substituted
+      -- util.debug(ir.first_name_ir)
+      ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+    end
+  end
+end
 
-  params.entryspacing = context.options["entry-spacing"]
-  params.linespacing = context.options["line-spacing"]
-  params.hangingindent = context.options["hanging-indent"]
-  params["second-field-align"] = context.options["second-field-align"]
-  for _, key in ipairs({"bibstart", "bibend"}) do
-    local value = context.engine.formatter[key]
-    if type(value) == "function" then
-      value = value(context)
+function Bibliography:substitute_subsequent_authors_complete_each(engine, ir)
+  local bib_names_str = ""
+
+  if #ir.first_name_ir.person_name_irs > 0 then
+    for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+      if bib_names_str ~= "" then
+        bib_names_str = bib_names_str .. "     "
+      end
+      local name_variants = person_name_ir.disam_variants
+      bib_names_str = bib_names_str .. name_variants[#name_variants]
     end
-    params[key] = value
+  else
+    -- In case of a <text variable="title"/> in <substitute>
+    local disam_format = DisamStringFormat:new()
+    local inlines = ir.first_name_ir:flatten(disam_format)
+    bib_names_str = disam_format:output(inlines)
   end
+  ir.first_name_ir.bib_names_str = bib_names_str
 
-  params.bibliography_errors = {}
-  params.entry_ids = {}
-  for _, item in ipairs(items) do
-    table.insert(params.entry_ids, item.id)
+  if engine.previous_bib_names_ir and
+      engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+    local text = self.subsequent_author_substitute
+    if #ir.first_name_ir.person_name_irs > 0 then
+      for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+        person_name_ir.inlines = {PlainText:new(text)}
+      end
+    else
+      -- In case of a <text variable="title"/> in <substitute>
+      if text == "" then
+        ir.first_name_ir.children = {}
+        ir.first_name_ir.group_var = "missing"
+      else
+        ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+      end
+    end
   end
+end
 
-  return res
+function Bibliography:substitute_subsequent_authors_partial_each(engine, ir)
+  local bib_names_str = ""
+
+  if #ir.first_name_ir.person_name_irs > 0 then
+    if engine.previous_bib_names_ir then
+      for i, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+        local prev_name_ir = engine.previous_bib_names_ir.person_names[i]
+        if prev_name_ir then
+          local prev_name_variants = prev_name_ir.disam_variants
+          local prev_full_name_str = prev_name_variants[#prev_name_variants]
+          local name_variants = person_name_ir.disam_variants
+          local full_name_str = name_variants[#name_variants]
+          if prev_full_name_str == full_name_str then
+            local text = self.subsequent_author_substitute
+            person_name_ir.inlines = {PlainText:new(text)}
+          else
+            break
+          end
+        end
+      end
+    end
+  else
+    -- In case of a <text variable="title"/> in <substitute>
+    local disam_format = DisamStringFormat:new()
+    local inlines = ir.first_name_ir:flatten(disam_format)
+    bib_names_str = disam_format:output(inlines)
+    ir.first_name_ir.bib_names_str = bib_names_str
+    if engine.previous_bib_names_ir and
+        engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+      local text = self.subsequent_author_substitute
+      if text == "" then
+        ir.first_name_ir.children = {}
+        ir.first_name_ir.group_var = "missing"
+      else
+        ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+      end
+    end
+  end
 end
 
-style.Style = Style
-style.Citation = Citation
-style.Bibliography = Bibliography
+function Bibliography:substitute_subsequent_authors_partial_first(engine, ir)
+end
 
+local Macro = Element:derive("macro")
 
-return style
+function Macro:from_node(node)
+  local o = Macro:new()
+  o.children = {}
+  o:set_attribute(node, "name")
+  o:process_children_nodes(node)
+  return o
+end
+
+function Macro:build_ir(engine, state, context)
+  local ir = self:build_group_ir(engine, state, context)
+  return ir
+end
+
+
+style_module.Style = Style
+style_module.Citation = Citation
+style_module.Bibliography = Bibliography
+
+
+return style_module

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -4,224 +4,196 @@
 -- Repository: https://github.com/zepinglee/citeproc-lua
 --
 
-local text = {}
+local text_module = {}
 
-local element = require("citeproc-element")
-local richtext = require("citeproc-richtext")
+local Element = require("citeproc-element").Element
+local Rendered = require("citeproc-ir-node").Rendered
+local YearSuffix = require("citeproc-ir-node").YearSuffix
+local PlainText = require("citeproc-output").PlainText
+local Linked = require("citeproc-output").Linked
 local util = require("citeproc-util")
 
 
-local Text = element.Element:new()
+-- [Text](https://docs.citationstyles.org/en/stable/specification.html#text)
+local Text = Element:derive("text", {
+  -- Default attributes
+  variable = nil,
+  form = "long",
+  macro = nil,
+  term = nil,
+  plural = false,
+  value = nil,
+  -- Style behavior
+  formatting = nil,
+  affixes = nil,
+  delimiter = nil,
+  display = nil,
+  quotes = false,
+  strip_periods = false,
+  text_case = nil,
+})
 
-function Text:render (item, context)
-  self:debug_info(context)
-  context = self:process_context(context)
+function Text:from_node(node)
+  local o = Text:new()
+  o:set_attribute(node, "variable")
+  o:set_attribute(node, "form")
+  o:set_attribute(node, "macro")
+  o:set_attribute(node, "term")
+  o:set_bool_attribute(node, "plural")
+  o:set_attribute(node, "value")
 
-  local res = nil
+  o:set_formatting_attributes(node)
+  o:set_affixes_attributes(node)
+  o:set_display_attribute(node)
+  o:set_quotes_attribute(node)
+  o:set_strip_periods_attribute(node)
+  o:set_text_case_attribute(node)
+  return o
+end
 
-  local variable = nil
-  local variable_name = self:get_attribute("variable")
-  if variable_name then
-    local form = self:get_attribute("form")
-    if form == "short" then
-      variable = self:get_variable(item, variable_name  .. "-" .. form, context)
-    end
-    if not variable then
-      variable = self:get_variable(item, variable_name, context)
-    end
-    if variable then
-      res = variable
-      if type(res) == "number" then
-        res = tostring(res)
-      end
-      if variable_name == "page" or variable_name == "locator" then
-        res = util.lstrip(res)
-        res = self:_format_page(res, context)
-      end
-    end
-
-    table.insert(context.variable_attempt, res ~= nil)
+function Text:build_ir(engine, state, context)
+  local ir = nil
+  if self.variable then
+    ir = self:build_variable_ir(engine, state, context)
+  elseif self.macro then
+    ir = self:build_macro_ir(engine, state, context)
+  elseif self.term then
+    ir = self:build_term_ir(engine, state, context)
+  elseif self.value then
+    ir = self:build_value_ir(engine, state, context)
   end
+  return ir
+end
 
-  local macro_name = self:get_attribute("macro")
-  if macro_name then
-    local macro = self:get_macro(macro_name)
-    res = macro:render(item, context)
-  end
+function Text:build_variable_ir(engine, state, context)
+  local variable = self.variable
+  local text
 
-  local term_name = self:get_attribute("term")
-  if term_name then
-    local form = self:get_attribute("form")
-
-    local term = self:get_term(term_name, form)
-    if term then
-      res = term:render(context)
-    end
+  if variable == "year-suffix" then
+    return self:build_year_suffix_ir(engine, state, context)
   end
 
-  local value = self:get_attribute("value")
-  if value then
-    res = value
-    res = self:escape(res)
+  if not state.suppressed[variable] then
+    text = context:get_variable(variable, self.form)
   end
 
-  if type(res) == "string" and res ~= "" then
-    res = richtext.new(res)
+  if not text then
+    local ir = Rendered:new({}, self)
+    ir.group_var = "missing"
+    return ir
   end
-
-  if res and variable_name == "URL" then
-    res:add_format("URL", "true")
+  if type(text) == "number" then
+    text = tostring(text)
   end
-
-  res = self:strip_periods(res, context)
-  res = self:case(res, context)
-  res = self:format(res, context)
-  res = self:quote(res, context)
-  res = self:wrap(res, context)
-  res = self:display(res, context)
-
-  if variable_name == "citation-number" then
-    res = self:_process_citation_number(variable, res, context)
+  if util.variable_types[variable] == "number" then
+    text = self:format_number(text, variable, "numeric", context)
   end
 
-  return res
-end
-
-
-function Text:_process_citation_number(citation_number, res, context)
-  if context.mode == "citation" and not context.sorting and context.options["collapse"] == "citation-number" then
-    context.build.item_citation_numbers[context.item.id] = citation_number
-    if type(res) == "string" then
-      res = richtext.new(res)
-    end
-    table.insert(context.build.item_citation_number_text, res)
+  local inlines
+  if (variable == "URL" and engine.opt.url_link) or
+     (variable == "DOI" and engine.opt.doi_link) or
+     (variable == "PMID" and engine.opt.doi_link) or
+     (variable == "PMID" and engine.opt.doi_link) then
+    inlines = self:render_linked(engine, state, context, variable, text)
+  else
+    inlines = self:render_text_inlines(text, context)
   end
-  return res
-end
 
+  local ir = Rendered:new(inlines, self)
+  ir.group_var = "important"
 
-function Text:_format_page (page, context)
-  local res = nil
-
-  local page_range_delimiter = self:get_term("page-range-delimiter"):render(context) or util.unicode["en dash"]
-  local page_range_format = context.options["page-range-format"]
-  if page_range_format == "chicago" then
-    if self:get_style():get_version() >= "1.1" then
-      page_range_format = "chicago-16"
-    else
-      page_range_format = "chicago-15"
-    end
+  if variable == "citation-number" then
+    ir.citation_number = context.reference["citation-number"]
   end
 
-  local last_position = 1
-  local page_parts = {}
-  local punct_list = {}
-  for part, punct, pos in string.gmatch(page, "(.-)%s*([,&])%s*()") do
-    table.insert(page_parts, part)
-    table.insert(punct_list, punct)
-    last_position = pos
+  -- Suppress substituted name variable
+  if state.name_override and not context.sort_key then
+    state.suppressed[variable] = true
   end
-  table.insert(page_parts, string.sub(page, last_position))
 
-  res = ""
-  for i, part in ipairs(page_parts) do
-    res = res .. self:_format_range(part, page_range_format, page_range_delimiter)
-    local punct = punct_list[i]
-    if punct then
-      if punct == "&" then
-        res = res .. " " .. punct .. " "
-      else
-        res = res .. punct .. " "
-      end
-    end
-  end
-  res = self:escape(res)
-  return res
+  return ir
 end
 
-function Text:_format_range (str, format, range_delimiter)
-  local start, delimiter, stop = string.match(str, "(%w+)%s*(%-+)%s*(%S*)")
-  if not stop or stop == "" then
-    return string.gsub(str, "\\%-", "-")
+function Text:render_linked(engine, state, context, variable, text)
+  local href
+  if variable == "URL" then
+    href = text
+  elseif self.affixes and self.affixes.prefix then
+    if string.match(self.affixes.prefix, "https?://") then
+      text = self.affixes.prefix .. text
+      self.affixes.prefix = nil
+      href = text
+    end
+  elseif variable == "DOI" then
+    href = "https://doi.org/" .. text
+  elseif variable == "PMID" then
+    href = "https://www.ncbi.nlm.nih.gov/pubmed/" .. text
+  elseif variable == "PMCID" then
+    href = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. text
   end
-
-
-  local start_prefix, start_num  = string.match(start, "(.-)(%d*)$")
-  local stop_prefix, stop_num = string.match(stop, "(.-)(%d*)$")
-
-  if start_prefix ~= stop_prefix then
-    -- Not valid range: "n11564-1568" -> "n11564-1568"
-    -- 110-N6
-    -- N110-P5
-    return start .. delimiter .. stop
+  local inlines = {Linked:new(text, href)}
+  local output_format = context.format
+  local localized_quotes = nil
+  if self.quotes then
+    localized_quotes = context:get_localized_quotes()
   end
-
-  if format == "chicago-16" then
-    stop = self:_format_range_chicago_16(start_num, stop_num)
-  elseif format == "chicago-15" then
-    stop = self:_format_range_chicago_15(start_num, stop_num)
-  elseif format == "expanded" then
-    stop = stop_prefix .. self:_format_range_expanded(start_num, stop_num)
-  elseif format == "minimal" then
-    stop = self:_format_range_minimal(start_num, stop_num)
-  elseif format == "minimal-two" then
-    stop = self:_format_range_minimal(start_num, stop_num, 2)
-  end
-
-  return start .. range_delimiter .. stop
+  inlines = output_format:with_format(inlines, self.formatting)
+  inlines = output_format:affixed_quoted(inlines, self.affixes, localized_quotes)
+  return output_format:with_display(inlines, self.display)
 end
 
-function Text:_format_range_chicago_16(start, stop)
-  if #start < 3 or string.sub(start, -2) == "00" then
-    return self:_format_range_expanded(start, stop)
-  elseif string.sub(start, -2, -2) == "0" then
-    return self:_format_range_minimal(start, stop)
+function Text:build_year_suffix_ir(engine, state, context)
+  local text = context:get_variable(self.variable, self.form)
+  local group_var
+  if text then
+    group_var = "important"
   else
-    return self:_format_range_minimal(start, stop, 2)
+    text = ""
+    group_var = "missing"
   end
-  return stop
+
+  local ir = YearSuffix:new({PlainText:new(text)}, self)
+  ir.group_var = group_var
+  ir.affixes = util.clone(self.affixes)
+  ir.display = self.display
+  ir.formatting = util.clone(self.formatting)
+  if self.quotes then
+    ir.quotes = context:get_localized_quotes()
+  end
+  return ir
 end
 
-function Text:_format_range_chicago_15(start, stop)
-  if #start < 3 or string.sub(start, -2) == "00" then
-    return self:_format_range_expanded(start, stop)
-  else
-    local changed_digits = self:_format_range_minimal(start, stop)
-    if string.sub(start, -2, -2) == "0" then
-      return changed_digits
-    elseif #start == 4 and #changed_digits == 3 then
-      return self:_format_range_expanded(start, stop)
-    else
-      return self:_format_range_minimal(start, stop, 2)
+function Text:build_macro_ir(engine, state, context)
+  local macro = context:get_macro(self.macro)
+  state:push_macro(self.macro)
+  local ir = macro:build_ir(engine, state, context)
+  state:pop_macro(self.macro)
+  if ir then
+    ir.affixes = util.clone(self.affixes)
+    ir.display = self.display
+    ir.formatting = util.clone(self.formatting)
+    if self.quotes then
+      ir.quotes = context:get_localized_quotes()
     end
   end
-  return stop
+  return ir
 end
 
-function Text:_format_range_expanded(start, stop)
-  -- Expand  "1234–56" -> "1234–1256"
-  if #start <= #stop then
-    return stop
+function Text:build_term_ir(engine, state, context)
+  local str = context:get_simple_term(self.term, self.form, self.plural)
+  if not str then
+    return nil
   end
-  return string.sub(start, 1, #start - #stop) .. stop
+  local inlines = self:render_text_inlines(str, context)
+  return Rendered:new(inlines, self)
 end
 
-function Text:_format_range_minimal(start, stop, threshold)
-  threshold = threshold or 1
-  if #start < #stop then
-    return stop
-  end
-  local offset = #start - #stop
-  for i = 1, #stop - threshold do
-    local j = i + offset
-    if string.sub(stop, i, i) ~= string.sub(start, j, j) then
-      return string.sub(stop, i)
-    end
-  end
-  return string.sub(stop, -threshold)
+function Text:build_value_ir(engine, state, context)
+  local inlines = self:render_text_inlines(self.value, context)
+  return Rendered:new(inlines, self)
 end
 
 
-text.Text = Text
+text_module.Text = Text
 
-return text
+return text_module

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -0,0 +1,1534 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local output_module = {}
+
+local unicode = require("unicode")
+local dom = require("luaxml-domobject")
+
+local util = require("citeproc-util")
+
+
+local LocalizedQuotes = {
+  outer_open = util.unicode['left double quotation mark'],
+  outer_close = util.unicode['right double quotation mark'],
+  inner_open = util.unicode['left single quotation mark'],
+  inner_close = util.unicode['right single quotation mark'],
+  punctuation_in_quote = false,
+}
+
+function LocalizedQuotes:new(outer_open, outer_close, inner_open, inner_close, punctuation_in_quote)
+  local o = {
+    outer_open = outer_open or util.unicode['left double quotation mark'],
+    outer_close = outer_close or util.unicode['right double quotation mark'],
+    inner_open = inner_open or util.unicode['left single quotation mark'],
+    inner_close = inner_close or util.unicode['right single quotation mark'],
+    punctuation_in_quote = punctuation_in_quote,
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+
+local InlineElement = {
+  _type = "InlineElement",
+  _base_class = "InlineElement",
+}
+
+function InlineElement:derive(type)
+  local o = {
+    _type  = type,
+  }
+  self[type] = o
+  setmetatable(o, self)
+  self.__index = self
+  o.__index = o
+  return o
+end
+
+function InlineElement:new(inlines)
+  local o = {
+    inlines = inlines,
+    _type  = self._type,
+  }
+  setmetatable(o, self)
+  return o
+end
+
+
+local PlainText = InlineElement:derive("PlainText")
+
+function PlainText:new(value)
+  local o = InlineElement.new(self)
+  o.value = value
+  setmetatable(o, self)
+  return o
+end
+
+
+local Formatted = InlineElement:derive("Formatted")
+
+function Formatted:new(inlines, formatting)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  o.formatting = formatting
+  setmetatable(o, self)
+  return o
+end
+
+
+local Micro = InlineElement:derive("Micro")
+
+-- This is how we can flip-flop only user-supplied styling.
+-- Inside this is parsed micro html
+function Micro:new(inlines)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  setmetatable(o, self)
+  return o
+end
+
+
+local Quoted = InlineElement:derive("Quoted")
+
+function Quoted:new(inlines, localized_quotes)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  o.is_inner = false
+  if localized_quotes then
+    o.quotes = localized_quotes
+  else
+    o.quotes = LocalizedQuotes:new()
+  end
+
+  setmetatable(o, self)
+  return o
+end
+
+
+local NoCase = InlineElement:derive("NoCase")
+
+function NoCase:new(inlines)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  setmetatable(o, self)
+  return o
+end
+
+
+local NoDecor = InlineElement:derive("NoDecor")
+
+function NoDecor:new(inlines)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  setmetatable(o, self)
+  return o
+end
+
+
+local Linked = InlineElement:derive("Linked")
+
+function Linked:new(value, href)
+  local o = InlineElement.new(self)
+  o.value = value
+  o.href = href
+  setmetatable(o, self)
+  return o
+end
+
+
+local Div = InlineElement:derive("Div")
+
+function Div:new(inlines, display)
+  local o = InlineElement.new(self)
+  o.inlines = inlines
+  o.div = display
+  setmetatable(o, self)
+  return o
+end
+
+
+function InlineElement:parse(str, context)
+  -- Return a list of inlines
+  if type(str) ~= "string" then
+    print(debug.traceback())
+  end
+  local html_str = "<div>" .. str .. "</div>"
+  local ok, html = pcall(dom.parse, html_str)
+  local inlines
+  if ok then
+    local div = html:get_path("div")[1]
+    local el = InlineElement:from_node(div)
+    inlines = el.inlines
+  else
+    local el = PlainText:new(str)
+    inlines = {el}
+  end
+
+  inlines = InlineElement:parse_quotes(inlines, context)
+  return inlines
+end
+
+function InlineElement:from_node(node)
+  local tag_name = node:get_element_name()
+  local inlines = {}
+
+  for _, child in ipairs(node:get_children()) do
+    local inline
+    if child:is_text() then
+      inline = PlainText:new(child:get_text())
+    elseif child:is_element() then
+      inline = InlineElement:from_node(child)
+    end
+    table.insert(inlines, inline)
+  end
+
+  if tag_name == "i" then
+    return Formatted:new(inlines, {["font-style"] = "italic"})
+  elseif tag_name == "b" then
+    return Formatted:new(inlines, {["font-weight"] = "bold"})
+  elseif tag_name == "sup" then
+    return Formatted:new(inlines, {["vertical-align"] = "sup"})
+  elseif tag_name == "sub" then
+    return Formatted:new(inlines, {["vertical-align"] = "sub"})
+  elseif tag_name == "sc" then
+    return Formatted:new(inlines, {["font-variant"] = "small-caps"})
+  elseif tag_name == "span" then
+    local style = node:get_attribute("style")
+    local class = node:get_attribute("class")
+    if style == "font-variant:small-caps;" or style == "font-variant: small-caps;" then
+      return Formatted:new(inlines, {["font-variant"] = "small-caps"})
+    elseif class == "nocase" then
+      return NoCase:new(inlines)
+    elseif class == "nodecor" then
+      return NoDecor:new(inlines)
+    end
+  end
+
+  return InlineElement:new(inlines)
+end
+
+function InlineElement:parse_quotes(inlines, context)
+  local quote_fragments = InlineElement:get_quote_fragments(inlines)
+  -- util.debug(quote_fragments)
+
+  local quote_stack = {}
+  local text_stack = {{}}
+
+  local localized_quotes = context:get_localized_quotes()
+
+  for _, fragment in ipairs(quote_fragments) do
+    local top_text_list = text_stack[#text_stack]
+
+    if type(fragment) == "table" then
+      if fragment.inlines then
+        fragment.inlines = self:parse_quotes(fragment.inlines, context)
+      end
+      table.insert(top_text_list, fragment)
+
+    elseif type(fragment) == "string" then
+      local quote = fragment
+      local top_quote = quote_stack[#quote_stack]
+
+      if quote == "'" then
+        if top_quote == "'" and #top_text_list > 0 then
+          table.remove(quote_stack)
+          local quoted = Quoted:new(top_text_list, localized_quotes)
+          table.remove(text_stack)
+          table.insert(text_stack[#text_stack], quoted)
+        else
+          table.insert(quote_stack, quote)
+          table.insert(text_stack, {})
+        end
+
+      elseif quote == '"' then
+        if top_quote == '"' then
+          table.remove(quote_stack)
+          local quoted = Quoted:new(top_text_list, localized_quotes)
+          table.remove(text_stack)
+          table.insert(text_stack[#text_stack], quoted)
+        else
+          table.insert(quote_stack, quote)
+          table.insert(text_stack, {})
+        end
+
+      elseif quote == util.unicode["left single quotation mark"] or
+             quote == util.unicode["left double quotation mark"] or
+             quote == util.unicode["left-pointing double angle quotation mark"] then
+        table.insert(quote_stack, quote)
+        table.insert(text_stack, {})
+
+      elseif (quote == util.unicode["right single quotation mark"] and
+              top_quote == util.unicode["left single quotation mark"]) or
+             (quote == util.unicode["right double quotation mark"] and
+              top_quote == util.unicode["left double quotation mark"]) or
+             (quote == util.unicode["right-pointing double angle quotation mark"] and
+              top_quote == util.unicode["left-pointing double angle quotation mark"]) then
+          table.remove(quote_stack)
+          local quoted = Quoted:new(top_text_list, localized_quotes)
+          table.remove(text_stack)
+          table.insert(text_stack[#text_stack], quoted)
+
+      else
+        local last_inline = top_text_list[#top_text_list]
+        if last_inline and last_inline._type == "PlainText" then
+          last_inline.value = last_inline.value .. fragment
+        else
+          table.insert(top_text_list, PlainText:new(fragment))
+        end
+      end
+
+    end
+  end
+
+  local elements = text_stack[1]
+  if #text_stack > 1 then
+    assert(#text_stack == #quote_stack + 1)
+    for i, quote in ipairs(quote_stack) do
+      if quote == "'" then
+        quote = util.unicode["apostrophe"]
+      end
+      local last_inline = elements[#elements]
+      if last_inline and last_inline._type == "PlainText" then
+        last_inline.value = last_inline.value .. quote
+      else
+        table.insert(elements, PlainText:new(quote))
+      end
+
+      for _, inline in ipairs(text_stack[i + 1]) do
+        if inline._type == "PlainText" then
+          local last_inline = elements[#elements]
+          if last_inline and last_inline._type == "PlainText" then
+            last_inline.value = last_inline.value .. inline.value
+          else
+            table.insert(elements, inline)
+          end
+        else
+          table.insert(elements, inline)
+        end
+      end
+    end
+  end
+
+  return elements
+end
+
+local function merge_fragments_at(fragments, i)
+  if type(fragments[i+1]) == "string" then
+    fragments[i] = fragments[i] .. fragments[i+1]
+    table.remove(fragments, i+1)
+  end
+  if type(fragments[i-1]) == "string" then
+    fragments[i-1] = fragments[i-1] .. fragments[i]
+    table.remove(fragments, i)
+  end
+end
+
+-- Return a list of strings and InlineElement
+function InlineElement:get_quote_fragments(inlines)
+  local fragments = {}
+  for _, inline in ipairs(inlines) do
+    if inline._type == "PlainText" then
+      local quote_tuples = {}
+      for _, quote in ipairs({
+        "'",
+        '"',
+        util.unicode["left single quotation mark"],
+        util.unicode["right single quotation mark"],
+        util.unicode["left double quotation mark"],
+        util.unicode["right double quotation mark"],
+        util.unicode["left-pointing double angle quotation mark"],
+        util.unicode["right-pointing double angle quotation mark"],
+      }) do
+        string.gsub(inline.value, "()(" .. quote .. ")()", function (idx, qt, next_idx)
+          table.insert(quote_tuples, {idx, qt, next_idx})
+        end)
+      end
+      table.sort(quote_tuples, function (a, b)
+        return a[1] < b[1]
+      end)
+      local start_idx = 1
+      for _, quote_tuple in ipairs(quote_tuples) do
+        local idx, qt, next_idx = table.unpack(quote_tuple)
+        local fragment = string.sub(inline.value, start_idx, idx-1)
+        if fragment ~= "" then
+          table.insert(fragments, fragment)
+        end
+        table.insert(fragments, qt)
+        start_idx = next_idx
+      end
+      -- Insert last fragment
+      local fragment = string.sub(inline.value, start_idx, #inline.value)
+      if fragment ~= "" then
+        table.insert(fragments, fragment)
+      end
+    else
+      table.insert(fragments, inline)
+    end
+
+    for i = #fragments, 1, -1 do
+      local fragment = fragments[i]
+      if fragment == "'" or fragment == '"' then
+        local left, right
+        if i > 1 then
+          left = fragments[i - 1]
+          if type(left) == "table" then
+            left = left:get_right_most_string()
+          end
+        end
+        if i < #fragments then
+          right = fragments[i + 1]
+          if type(right) == "table" then
+            right = right:get_left_most_string()
+          end
+        end
+        -- TODO: consider utf-8
+        if left and right then
+          if string.match(left, "%s$") and string.match(right, "^%s") then
+            -- Orphan quote: bugreports_SingleQuote.txt
+            if fragment == "'" then
+              fragments[i] = util.unicode['apostrophe']
+            end
+            merge_fragments_at(fragments, i)
+          end
+          if not string.match(left, "[%s%p]$") and not string.match(right, "^[%s%p]") then
+            if fragment == "'" then
+              fragments[i] = util.unicode['apostrophe']
+            end
+            merge_fragments_at(fragments, i)
+          end
+        end
+      end
+    end
+  end
+  return fragments
+end
+
+function InlineElement:get_left_most_string()
+  if self.value then
+    return self.value
+  elseif self.inlines then
+    return self.inlines[1]:get_left_most_string()
+  end
+end
+
+function InlineElement:get_right_most_string()
+  if self.value then
+    return self.value
+  elseif self.inlines then
+    return self.inlines[#self.inlines]:get_left_most_string()
+  end
+end
+
+function InlineElement:capitalize_first_term()
+  -- util.debug(self)
+  if self._type == "PlainText" then
+    self.value = util.capitalize(self.value)
+  elseif self.inlines[1] then
+    self.inlines[1]:capitalize_first_term()
+  end
+end
+
+
+local OutputFormat = {}
+
+function OutputFormat:new(format_name)
+  local o = {
+    name = format_name,
+  }
+  setmetatable(o, self)
+  self.__index = self
+  return o
+end
+
+function OutputFormat:flatten_ir(ir)
+  if self.group_var == "missing" or self.collapse_suppressed then
+    return {}
+  end
+  local inlines = {}
+  if ir._type == "SeqIr" or ir._type == "NameIr" then
+    inlines = self:flatten_seq_ir(ir)
+  else
+    inlines = self:with_format(ir.inlines, ir.formatting)
+    inlines = self:affixed_quoted(inlines, ir.affixes, ir.quotes);
+    inlines = self:with_display(inlines, ir.display);
+  end
+  return inlines
+end
+
+function OutputFormat:flatten_seq_ir(ir)
+  if not ir.children then
+    print(debug.traceback())
+  end
+  if #ir.children == 0 then
+    return {}
+  end
+  local inlines_list = {}
+  for _, child in ipairs(ir.children) do
+    if child.group_var ~= "missing" and not child.collapse_suppressed then
+      local child_inlines = self:flatten_ir(child)
+      if #child_inlines > 0 then
+        table.insert(inlines_list, child_inlines)
+      end
+    end
+  end
+
+  if #inlines_list == 0 then
+    return {}
+  end
+
+  local inlines = self:group(inlines_list, ir.delimiter, ir.formatting)
+  -- assert ir.quotes == localized quotes
+  inlines = self:affixed_quoted(inlines, ir.affixes, ir.quotes);
+  inlines = self:with_display(inlines, ir.display);
+  return inlines
+end
+
+function OutputFormat:group(inlines_list, delimiter, formatting)
+  -- Each node is list of InlineElements
+  if #inlines_list == 1 then
+    return self:format_list(inlines_list[1], formatting)
+  else
+    local inlines = OutputFormat:join_many(inlines_list, delimiter)
+    return self:format_list(inlines, formatting)
+  end
+end
+
+function OutputFormat:format_list(inlines, formatting)
+  if formatting then
+    return {Formatted:new(inlines, formatting)}
+  else
+    return inlines
+  end
+end
+
+function OutputFormat:join_many(lists, delimiter)
+  local res = {}
+  for i, list in ipairs(lists) do
+    if delimiter and i > 1 then
+      table.insert(res, PlainText:new(delimiter))
+    end
+    for _, el in ipairs(list) do
+      table.insert(res, el)
+    end
+  end
+  return res
+end
+
+function OutputFormat:with_format(inlines, formatting)
+  return self:format_list(inlines, formatting)
+end
+
+function OutputFormat:apply_text_case(inlines, text_case, is_english)
+  if not inlines or #inlines == 0 or not text_case then
+    return
+  end
+  -- Title case conversion only affects English-language items.
+  if text_case == "title" and not is_english then
+    return
+  end
+  local is_uppercase = false  -- TODO
+  self:apply_text_case_inner(inlines, text_case, false, is_uppercase)
+end
+
+local function string_contains_word(str)
+  return string.match(str, "%w") ~= nil
+end
+
+local function inline_contains_word(inline)
+  if inline._type == "PlainText" then
+    return string_contains_word(inline.value)
+  elseif inline.inlines then
+    for _, el in ipairs(inline.inlines) do
+      if inline_contains_word(el) then
+        return true
+      end
+    end
+  end
+  return false
+end
+
+function OutputFormat:apply_text_case_inner(inlines, text_case, seen_one, is_uppercase)
+  for i, inline in ipairs(inlines) do
+    if seen_one and text_case == "capitalize-first" then
+      break
+    end
+    local is_last = (i == #inlines)
+    if inline._type == "PlainText" then
+      inline.value = self:transform_case(inline.value, text_case, seen_one, is_last, is_uppercase);
+      seen_one = seen_one or string_contains_word(inline.value)
+    elseif inline._type == "NoCase" or
+           inline._type == "NoDecor" or
+           (inline._type == "Formatted" and inline.formatting["font-variant"] == "small-caps") or
+           (inline._type == "Formatted" and inline.formatting["vertical-align"] == "sup") or
+           (inline._type == "Formatted" and inline.formatting["vertical-align"] == "sub") then
+      seen_one = seen_one or inline_contains_word(inline)
+
+    elseif inline._type == "Formatted" or inline._type == "Quoted" then
+      seen_one = self:apply_text_case_inner(inline.inlines, text_case, seen_one, is_uppercase) or seen_one
+    end
+  end
+  return seen_one
+end
+
+local function transform_lowercase(str)
+  return string.gsub(str, utf8.charpattern, unicode.utf8.lower)
+end
+
+local function transform_uppercase(str)
+  -- TODO: locale specific uppercase: textcase_LocaleUnicode.txt
+  return string.gsub(str, utf8.charpattern, unicode.utf8.upper)
+end
+
+local function transform_first_word(str, transform)
+  -- TODO: [Unicode word boundaries](https://www.unicode.org/reports/tr29/#Word_Boundaries)
+  local segments = util.segment_words(str)
+  for _, segment in ipairs(segments) do
+    if segment[1] ~= "" then
+      segment[1] = transform(segment[1])
+      break
+    end
+  end
+  local res = ""
+  for _, segment in ipairs(segments) do
+    res = res .. segment[1] .. segment[2]
+  end
+  return res
+end
+
+local function transform_each_word(str, seen_one, is_last, transform)
+  -- util.debug(str)
+  local segments = util.segment_words(str)
+  -- util.debug(segments)
+  -- print(debug.traceback())
+
+  local first_idx
+  local last_idx
+  for i, segment in ipairs(segments) do
+    if segment[1] ~= "" then
+      if not first_idx then
+        first_idx = i
+      end
+      last_idx = i
+    end
+  end
+
+  local immediate_before = ""
+  local last_punct = ""
+  for i, segment in ipairs(segments) do
+    local is_first_word = not seen_one and i == first_idx
+    local is_last_word = is_last and i == last_idx
+    local follows_colon = (
+      last_punct == ":" or
+      last_punct == "!" or
+      last_punct == "?" or
+      last_punct == "?")
+    local no_stop_word = is_first_word or is_last_word or follows_colon or (segment[2] == "-" and immediate_before ~= "-")
+
+    if (immediate_before == "." or immediate_before == "-") and #segment[1] == 1 then
+    else
+      segment[1] = transform(segment[1], no_stop_word)
+    end
+
+    if segment[1] ~= "" then
+      immediate_before = segment[1]
+      last_punct = string.match(segment[1], "(%S)%s*$") or last_punct
+    end
+    if segment[2] ~= "" then
+      immediate_before = segment[2]
+      last_punct = string.match(segment[2], "(%S)%s*$") or last_punct
+    end
+  end
+  local res = ""
+  for _, segment in ipairs(segments) do
+    res = res .. segment[1] .. segment[2]
+  end
+  return res
+end
+
+local function transform_capitalize_word_if_lower(word)
+  if util.is_lower(word) then
+    return string.gsub(word, utf8.charpattern, unicode.utf8.upper, 1)
+  else
+    return word
+  end
+end
+
+local function title_case_word(word, no_stop_word)
+  -- Entirely non-English
+  -- e.g. "β" in "β-Carotine"
+  if string.match(word, "%a") and (not util.stop_words[word] or no_stop_word) and util.is_lower(word) then
+    return string.gsub(word, utf8.charpattern, unicode.utf8.upper, 1)
+  else
+    return word
+  end
+end
+
+function OutputFormat:transform_case(str, text_case, seen_one, is_last, is_uppercase)
+  local res = str
+  if text_case == "lowercase" then
+    res = transform_lowercase(str)
+  elseif text_case == "uppercase" then
+    res = transform_uppercase(str)
+  elseif text_case == "capitalize-first" then
+    res = transform_first_word(str, transform_capitalize_word_if_lower)
+  elseif text_case == "capitalize-all" then
+    res = transform_each_word(str, false, false, transform_capitalize_word_if_lower)
+  elseif text_case == "sentence" then
+    -- TODO: if uppercase convert all to lowercase
+    res = transform_first_word(str, transform_capitalize_word_if_lower)
+  elseif text_case == "title" then
+    -- TODO: if uppercase convert all to lowercase
+    res = transform_each_word(str, seen_one, is_last, title_case_word)
+  end
+  return res
+end
+
+function OutputFormat:affixed(inlines, affixes)
+  if affixes and affixes.prefix then
+    table.insert(inlines, 1, PlainText:new(affixes.prefix))
+  end
+  if affixes and affixes.suffix then
+    table.insert(inlines, PlainText:new(affixes.suffix))
+  end
+  return inlines
+end
+
+function OutputFormat:affixed_quoted(inlines, affixes, localized_quotes)
+  inlines = util.clone(inlines)
+  if localized_quotes then
+    inlines = self:quoted(inlines, localized_quotes)
+  end
+  if affixes and affixes.prefix then
+    table.insert(inlines, 1, PlainText:new(affixes.prefix))
+  end
+  if affixes and affixes.suffix then
+    table.insert(inlines, PlainText:new(affixes.suffix))
+  end
+  return inlines
+end
+
+function OutputFormat:quoted(inlines, localized_quotes)
+  return {Quoted:new(inlines, localized_quotes)}
+end
+
+function OutputFormat:with_display(nodes, display)
+  if display then
+    return {Div:new(nodes, display)}
+  else
+    return nodes
+  end
+end
+
+function OutputFormat:output(inlines, context)
+  self:flip_flop_inlines(inlines)
+
+  self:move_punctuation(inlines)
+
+  -- util.debug(inlines)
+
+  return self:write_inlines(inlines, context)
+end
+
+function OutputFormat:output_bibliography_entry(inlines, context)
+  self:flip_flop_inlines(inlines)
+  -- util.debug(inlines)
+  self:move_punctuation(inlines)
+  -- TODO:
+  -- if self.format == "html" then
+  -- elseif self.format == "latex" then
+  -- end
+  local res = self:write_inlines(inlines, context)
+  local markup = self.markups["@bibliography/entry"]
+  if type(markup) == "string" then
+    res = string.format(markup, res)
+  elseif type(markup) == "function" then
+    res = markup(res, context)
+  end
+  return res
+end
+
+function OutputFormat:flip_flop_inlines(inlines)
+  local flip_flop_state = {
+    ["font-style"] = "normal",
+    ["font-variant"] = "normal",
+    ["font-weight"] = "normal",
+    ["text-decoration"] = "none",
+    ["vertical-alignment"] = "baseline",
+    in_inner_quotes = false,
+  }
+  self:flip_flop(inlines, flip_flop_state)
+end
+
+function OutputFormat:flip_flop(inlines, state)
+  for i, inline in ipairs(inlines) do
+    if inline._type == "Micro" then
+      self:flip_flop_micro_inlines(inline.inlines, state)
+
+    elseif inline._type == "Formatted" then
+      local new_state = util.clone(state)
+      local formatting = inline.formatting
+
+      for _, attribute in ipairs({"font-style", "font-variant", "font-weight"}) do
+        local value = formatting[attribute]
+        if value then
+          if value == state[attribute] then
+            if value == "normal" then
+              formatting[attribute] = nil
+            -- Formatting outside Micro is not reset to "normal".
+            -- else
+            --   formatting[attribute] = "normal"
+            end
+          end
+          new_state[attribute] = value
+        end
+      end
+      self:flip_flop(inline.inlines, new_state)
+
+    elseif inline._type == "Quoted" then
+      inline.is_inner = state.in_inner_quotes
+      local new_state = util.clone(state)
+      new_state.in_inner_quotes = not new_state.in_inner_quotes
+      self:flip_flop(inline.inlines, new_state)
+
+    elseif inline._type == "NoDecor" then
+      local new_state = {
+        ["font-style"] = "normal",
+        ["font-variant"] = "normal",
+        ["font-weight"] = "normal",
+        ["text-decoration"] = "none",
+      }
+      self:flip_flop(inline.inlines, new_state)
+      for attr, value in pairs(new_state) do
+        if value and state[attr] ~= value then
+          if not inline.formatting then
+            inline._type = "Formatted"
+            inline.formatting = {}
+          end
+          inline.formatting[attr] = value
+        end
+      end
+
+    elseif inline.inlines then  -- Div, ...
+      self:flip_flop(inline.inlines, state)
+    end
+  end
+end
+
+function OutputFormat:flip_flop_micro_inlines(inlines, state)
+  for i, inline in ipairs(inlines) do
+    if inline._type == "Micro" then
+      self:flip_flop_micro_inlines(inline.inlines, state)
+
+    elseif inline._type == "Formatted" then
+      local new_state = util.clone(state)
+      local formatting = inline.formatting
+
+      for _, attribute in ipairs({"font-style", "font-variant", "font-weight"}) do
+        local value = formatting[attribute]
+        if value then
+          if value == state[attribute] then
+            if value == "normal" then
+              formatting[attribute] = nil
+            else
+              -- Formatting inside Micro is reset to "normal".
+              formatting[attribute] = "normal"
+              value = "normal"
+            end
+          end
+          new_state[attribute] = value
+        end
+      end
+      self:flip_flop_micro_inlines(inline.inlines, new_state)
+
+    elseif inline._type == "Quoted" then
+      inline.is_inner = state.in_inner_quotes
+      local new_state = util.clone(state)
+      new_state.in_inner_quotes = not new_state.in_inner_quotes
+      self:flip_flop_micro_inlines(inline.inlines, new_state)
+
+    elseif inline._type == "NoDecor" then
+      local new_state = {
+        ["font-style"] = "normal",
+        ["font-variant"] = "normal",
+        ["font-weight"] = "normal",
+        ["text-decoration"] = "none",
+      }
+      self:flip_flop_micro_inlines(inline.inlines, new_state)
+      for attr, value in pairs(new_state) do
+        if value and state[attr] ~= value then
+          if not inline.formatting then
+            inline._type = "Formatted"
+            inline.formatting = {}
+          end
+          inline.formatting[attr] = value
+        end
+      end
+
+    elseif inline.inlines then  -- Div, ...
+      self:flip_flop_micro_inlines(inline.inlines, state)
+    end
+  end
+end
+
+local function find_left(inline)
+  if not inline then
+    print(debug.traceback())
+  end
+  if inline._type == "PlainText" then
+    return inline
+  -- elseif inline._type == "Micro" then
+  --   return nil
+  elseif inline.inlines and inline._type~="Quoted" then
+    return find_left(inline.inlines[1])
+  else
+    return nil
+  end
+end
+
+local function find_right(inline)
+  if inline._type == "PlainText" then
+    return inline
+  -- elseif inline._type == "Micro" then
+  --   return nil
+  elseif inline.inlines and #inline.inlines > 0 and inline._type ~= "Quoted" then
+    return find_right(inline.inlines[#inline.inlines])
+  else
+    return nil
+  end
+end
+
+local function find_right_in_quoted(inline)
+  if inline._type == "PlainText" then
+    return inline
+  -- elseif inline._type == "Micro" then
+  --   return nil
+  elseif inline.inlines and #inline.inlines > 0 then
+    return find_right_in_quoted(inline.inlines[#inline.inlines])
+  else
+    return nil
+  end
+end
+
+-- "'Foo,' bar" => ,
+local function find_right_quoted(inline)
+  if inline._type == "Quoted" and #inline.inlines > 0 then
+    return find_right_in_quoted(inline.inlines[#inline.inlines]), inline.quotes.punctuation_in_quote
+  -- elseif inline._type == "Micro" then
+  --   return nil
+  elseif inline.inlines and #inline.inlines > 0 then
+    return find_right_quoted(inline.inlines[#inline.inlines])
+  else
+    return nil, false
+  end
+end
+
+local function smash_string_push(first, second)
+  local first_char = string.sub(first.value, -1)
+  local second_char = string.sub(second.value, 1, 1)
+  -- util.debug(first_char)
+  -- util.debug(second_char)
+
+  local punct_map = output_module.quote_punctuation_map
+  if second_char == " " and (first_char == " " or
+      util.endswith(first.value, util.unicode["no-break space"])) then
+    second.value = string.sub(second.value, 2)
+  elseif punct_map[first_char] then
+    if first_char == second_char then
+      second.value = string.sub(second.value, 2)
+    else
+      local combined = punct_map[first_char][second_char]
+      if combined and #combined == 1 then
+        second.value = string.sub(second.value, 2)
+        first.value = string.sub(first.value, 1, -2) .. combined
+      end
+    end
+  end
+end
+
+local function smash_just_punc(first, second)
+  first = find_right(first)  -- PlainText
+  second = find_left(second)  -- PlainText
+  if first and second then
+    local first_char = string.sub(first.value, -1)
+    local second_char = string.sub(second.value, 1, 1)
+    -- util.debug(first_char)
+    -- util.debug(second_char)
+
+    local punct_map = output_module.quote_punctuation_map
+    if second_char == " " and (first_char == " " or
+        util.endswith(first.value, util.unicode["no-break space"])) then
+      second.value = string.sub(second.value, 2)
+      return true
+    elseif punct_map[first_char] then
+      if first_char == second_char then
+        second.value = string.sub(second.value, 2)
+        return true
+      else
+        local combined = punct_map[first_char][second_char]
+        if combined and #combined == 1 then
+          second.value = string.sub(second.value, 2)
+          first.value = string.sub(first.value, 1, -2) .. combined
+          return true
+        end
+      end
+    end
+  else
+    return false
+  end
+end
+
+local function normalise_text_elements(inlines)
+  -- 1. Merge punctuations: "?." => "?"
+  -- 2. Merge spaces: "  " => " "
+  local idx = 1
+  while idx < #inlines do
+    local first = inlines[idx]
+    local second = inlines[idx+1]
+
+    if first._type == "PlainText" and second._type == "PlainText" then
+      smash_string_push(first, second)
+      first.value = first.value .. second.value
+      table.remove(inlines, idx + 1)
+
+    elseif first._type == "Micro" and second._type == "PlainText" then
+      local success = smash_just_punc(first, second)
+      if success then
+        if second.value == "" then
+          table.remove(inlines, idx + 1)
+        end
+      else
+        idx = idx + 1
+      end
+
+    elseif first._type == "Formatted" and second._type == "PlainText" then
+      local success = smash_just_punc(first, second)
+      if success then
+        if second.value == "" then
+          table.remove(inlines, idx + 1)
+        end
+      else
+        idx = idx + 1
+      end
+
+    else
+      idx = idx + 1
+    end
+  end
+
+end
+
+local function move_in_quotes(first, second)
+  local first_char = string.sub(first.value, -1)
+  local second_char = string.sub(second.value, 1, 1)
+  local success = false
+  if output_module.move_in_puncts[second_char] then
+    if first_char == second_char then
+      second.value = string.sub(second.value, 2)
+      success = true
+    elseif output_module.quote_punctuation_map[first_char] then
+      local combined = output_module.quote_punctuation_map[first_char][second_char]
+      first.value = string.sub(first.value, 1, -2) .. combined
+      second.value = string.sub(second.value, 2)
+      success = true
+    else
+      first.value = first.value .. second_char
+      second.value = string.sub(second.value, 2)
+      success = true
+    end
+  end
+  return success
+end
+
+local function move_out_quotes(first, second)
+  local first_char = string.sub(first.value, -1)
+  local second_char = string.sub(second.value, 1, 1)
+  local success = false
+  if output_module.move_out_puncts[first_char] then
+    if first_char == second_char then
+      first.value = string.sub(first.value, 1, -2)
+      success = true
+    elseif output_module.quote_punctuation_map[second_char] then
+      local combined = output_module.quote_punctuation_map[first_char][second_char]
+      first.value = string.sub(first.value, 1, -2)
+      second.value = combined .. string.sub(second.value, 2)
+      success = true
+    else
+      first.value = string.sub(first.value, 1, -2)
+      second.value = first_char .. second.value
+      success = true
+    end
+  end
+  return success
+end
+
+local function move_around_quote(inlines)
+  local idx = 1
+
+  while idx < #inlines do
+    -- Move punctuation into quotes as needed
+    local first, punctuation_in_quote = find_right_quoted(inlines[idx])
+
+    local second = find_left(inlines[idx+1])
+    local success = false
+    if first and second then
+      if punctuation_in_quote then
+        success = move_in_quotes(first, second)
+      else
+        success = move_out_quotes(first, second)
+      end
+      if not success then
+        idx = idx + 1
+      end
+    else
+      idx = idx + 1
+    end
+  end
+end
+
+function OutputFormat:move_punctuation(inlines, piq)
+  -- Merge punctuations
+  normalise_text_elements(inlines)
+
+  move_around_quote(inlines)
+
+  for _, inline in ipairs(inlines) do
+    if inline._type == "Quoted" or inline._type == "Formatted" or
+        inline._type == "Div" then
+      self:move_punctuation(inline.inlines)
+    end
+  end
+end
+
+function OutputFormat:write_inlines(inlines, context)
+  local res = ""
+  for _, inline in ipairs(inlines) do
+    res = res .. self:write_inline(inline, context)
+  end
+  return res
+end
+
+function OutputFormat:write_inline(inline, context)
+  if inline.value then
+    return self:write_escaped(inline.value)
+  elseif inline.inlines then
+    return self:write_inlines(inline.inlines)
+  end
+  return ""
+end
+
+function OutputFormat:write_escaped(str, context)
+  return str
+end
+
+function OutputFormat:write_link(inline, context)
+  return self:write_escaped(inline.value)
+end
+
+
+
+local Markup = OutputFormat:new()
+
+function Markup:write_inline(inline, context)
+  -- Should be deprecated code
+  if type(inline) == "string" then
+    return self:write_escaped(inline, context)
+
+  elseif type(inline) == "table" then
+    -- util.debug(inline._type)
+    if inline._type == "PlainText" then
+      return self:write_escaped(inline.value, context)
+
+    elseif inline._type == "InlineElement" then
+      return self:write_children(inline, context)
+
+    elseif inline._type == "Formatted" then
+      return self:write_formatted(inline, context)
+
+    elseif inline._type == "Quoted" then
+      return self:write_quoted(inline, context)
+
+    elseif inline._type == "Div" then
+      return self:write_display(inline, context)
+
+    elseif inline._type == "Linked" then
+      return self:write_link(inline, context)
+
+    elseif inline._type == "NoCase" or inline._type == "NoDecor" then
+      return self:write_inlines(inline.inlines)
+
+    else
+      return self:write_inlines(inline.inlines)
+    end
+  end
+  return ""
+end
+
+function Markup:write_children(inline, context)
+  local res = ""
+  for _, child_inline in ipairs(inline.inlines) do
+    res = res .. self:write_inline(child_inline, context)
+  end
+  return res
+end
+
+function Markup:write_quoted(inline, context)
+  local res = self:write_children(inline, context)
+  local quotes = inline.quotes
+  if inline.is_inner then
+    return quotes.inner_open .. res .. quotes.inner_close
+  else
+    return quotes.outer_open .. res .. quotes.outer_close
+  end
+end
+
+
+local LatexWriter = Markup:new()
+
+LatexWriter.markups = {
+  ["bibstart"] = function (engine)
+    return string.format("\\begin{thebibliography}{%s}\n\n", engine.registry.longest_label)
+  end,
+  ["bibend"] = "\\end{thebibliography}",
+  ["@font-style/normal"] = "{\\normalshape %s}",
+  ["@font-style/italic"] = "\\textit{%s}",
+  ["@font-style/oblique"] = "\\textsl{%s}",
+  ["@font-variant/normal"] = "{\\normalshape %s}",
+  ["@font-variant/small-caps"] = "\\textsc{%s}",
+  ["@font-weight/normal"] = "{\\fontseries{m}\\selectfont %s}",
+  ["@font-weight/bold"] = "\\textbf{%s}",
+  ["@font-weight/light"] = "{\\fontseries{l}\\selectfont %s}",
+  ["@text-decoration/none"] = false,
+  ["@text-decoration/underline"] = "\\underline{%s}",
+  ["@vertical-align/sup"] = "\\textsuperscript{%s}",
+  ["@vertical-align/sub"] = "\\textsubscript{%s}",
+  ["@vertical-align/baseline"] = false,
+  ["@cite/entry"] = false,
+  ["@bibliography/entry"] = function (str, context)
+    if not string.match(str, "\\bibitem") then
+      str =  "\\bibitem{".. context.id .. "}\n" .. str .. "\n"
+    end
+    return str
+  end,
+  ["@display/block"] = false,
+  ["@display/left-margin"] = '\n    <div class="csl-left-margin">%s</div>',
+  ["@display/right-inline"] = '<div class="csl-right-inline">%s</div>\n  ',
+  ["@display/indent"] = '<div class="csl-indent">%s</div>\n  ',
+}
+
+function LatexWriter:write_escaped(str, context)
+  -- TeXbook, p. 38
+  str = str:gsub("\\", "\\textbackslash{}")
+  str = str:gsub("{", "\\{")
+  str = str:gsub("}", "\\}")
+  str = str:gsub("%$", "\\$")
+  str = str:gsub("&", "\\&")
+  str = str:gsub("#", "\\#")
+  str = str:gsub("%^", "\\^")
+  str = str:gsub("_", "\\_")
+  str = str:gsub("%%", "\\%%")
+  str = str:gsub("~", "\\~")
+  str = str:gsub(util.unicode["em space"], "\\quad ")
+  str = str:gsub(util.unicode["no-break space"], "~")
+  for char, sub in pairs(util.superscripts) do
+    str = string.gsub(str, char, "\\textsuperscript{" .. sub .. "}")
+  end
+  return str
+end
+
+function LatexWriter:write_formatted(inline, context)
+  local res = self:write_children(inline, context)
+  for _, key in ipairs({"font-style", "font-variant", "font-weight", "text-decoration", "vertical-align"}) do
+    local value = inline.formatting[key]
+    if value then
+      key = "@" .. key .. "/" .. value
+      local format_str = self.markups[key]
+      if format_str then
+        res = string.format(format_str, res)
+      end
+    end
+  end
+  return res
+end
+
+function LatexWriter:write_display(inline, context)
+  local plainter_text_writer = output_module.PlainTextWriter:new()
+  local str = plainter_text_writer:write_inline(inline)
+  local len = utf8.len(str)
+  if len > context.engine.registry.maxoffset then
+    context.engine.registry.maxoffset = len
+    context.engine.registry.longest_label = str
+  end
+
+  local res = self:write_children(inline, context)
+  if inline.display == "left-margin" then
+    if string.match(res, "%]") then
+      res = "{" .. res .. "}"
+    end
+    res = string.format("\\bibitem[%s]{%s}\n", res, context.id)
+
+  elseif inline.display == "right-inline" then
+    return res
+
+  elseif inline.display == "block" then
+    return ""
+  end
+  return res
+end
+
+function LatexWriter:write_link(inline, context)
+  if inline.href == inline.value then
+    return string.format("\\url{%s}", inline.value)
+  else
+    return string.format("\\href{%s}{%s}", inline.href, inline.value)
+  end
+end
+
+
+local HtmlWriter = Markup:new()
+
+HtmlWriter.markups = {
+  ["bibstart"] = "<div class=\"csl-bib-body\">\n",
+  ["bibend"] = "</div>",
+  ["@font-style/italic"] = "<i>%s</i>",
+  ["@font-style/oblique"] = "<em>%s</em>",
+  ["@font-style/normal"] = '<span style="font-style:normal;">%s</span>',
+  ["@font-variant/small-caps"] = '<span style="font-variant:small-caps;">%s</span>',
+  ["@font-variant/normal"] = '<span style="font-variant:normal;">%s</span>',
+  ["@font-weight/bold"] = "<b>%s</b>",
+  ["@font-weight/normal"] = '<span style="font-weight:normal;">%s</span>',
+  ["@font-weight/light"] = false,
+  ["@text-decoration/none"] = '<span style="text-decoration:none;">%s</span>',
+  ["@text-decoration/underline"] = '<span style="text-decoration:underline;">%s</span>',
+  ["@vertical-align/sup"] = "<sup>%s</sup>",
+  ["@vertical-align/sub"] = "<sub>%s</sub>",
+  ["@vertical-align/baseline"] = '<span style="baseline">%s</span>',
+  ["@cite/entry"] = nil,
+  ["@bibliography/entry"] = "<div class=\"csl-entry\">%s</div>\n",
+  ["@display/block"] = '\n\n    <div class="csl-block">%s</div>\n',
+  ["@display/left-margin"] = '\n    <div class="csl-left-margin">%s</div>',
+  ["@display/right-inline"] = '<div class="csl-right-inline">%s</div>\n  ',
+  ["@display/indent"] = '<div class="csl-indent">%s</div>\n  ',
+}
+
+function HtmlWriter:write_escaped(str, context)
+  str = string.gsub(str, "%&", "&")
+  str = string.gsub(str, "<", "<")
+  str = string.gsub(str, ">", ">")
+  for char, sub in pairs(util.superscripts) do
+    str = string.gsub(str, char, "<sup>" .. sub .. "</sup>")
+  end
+  return str
+end
+
+function HtmlWriter:write_formatted(inline, context)
+  local res = self:write_children(inline, context)
+  for _, key in ipairs({"font-style", "font-variant", "font-weight", "text-decoration", "vertical-align"}) do
+    local value = inline.formatting[key]
+    if value then
+      key = "@" .. key .. "/" .. value
+      local format_str = self.markups[key]
+      if format_str then
+        res = string.format(format_str, res)
+      end
+    end
+  end
+  return res
+end
+
+function HtmlWriter:write_display(inline, context)
+  local plainter_text_writer = output_module.PlainTextWriter:new()
+  local str = plainter_text_writer:write_inline(inline)
+  local len = utf8.len(str)
+  if len > context.engine.registry.maxoffset then
+    context.engine.registry.maxoffset = len
+    context.engine.registry.longest_label = str
+  end
+
+  if #inline.inlines == 0 then
+    return ""
+  end
+  local res = self:write_children(inline, context)
+  local key = string.format("@display/%s", inline.div)
+  if inline.div == "right-inline" then
+    -- Strip trailing spaces
+    -- variables_ContainerTitleShort.txt
+    res = string.gsub(res, "%s+$", "")
+  end
+  local format_str = self.markups[key]
+  res = string.format(format_str, res)
+  return res
+end
+
+function HtmlWriter:write_link(inline, context)
+  return string.format('<a href="%s">%s</a>', inline.href, inline.value)
+end
+
+
+local PlainTextWriter = Markup:new()
+
+PlainTextWriter.markups = {}
+
+function PlainTextWriter:write_escaped(str, context)
+  return str
+end
+
+function PlainTextWriter:write_formatted(inline, context)
+  return self:write_children(inline, context)
+end
+
+function PlainTextWriter:write_display(inline, context)
+  return self:write_children(inline, context)
+end
+
+
+local SortStringFormat = OutputFormat:new()
+
+function SortStringFormat:output(inlines, context)
+  -- self:flip_flop_inlines(inlines)
+  -- self:move_punctuation(inlines)
+  return self:write_inlines(inlines, context)
+end
+
+function SortStringFormat:write_escaped(str, context)
+  str = string.gsub(str, ",", "")
+  return str
+end
+
+
+local DisamStringFormat = OutputFormat:new()
+
+function DisamStringFormat:output(inlines, context)
+  -- self:flip_flop_inlines(inlines)
+  -- self:move_punctuation(inlines)
+  return self:write_inlines(inlines, context)
+end
+
+function DisamStringFormat:flatten_ir(ir)
+  if self.group_var == "missing" then
+    return {}
+  end
+  local inlines
+  if ir._type == "SeqIr" or ir._type == "NameIr" then
+    inlines = self:flatten_seq_ir(ir)
+  elseif ir._type == "YearSuffix" then
+    -- Don't include year-suffix in disambiguation
+    inlines = {}
+  else
+    inlines = self:affixed_quoted(ir.inlines, ir.affixes, ir.quotes);
+    inlines = self:with_display(inlines, ir.display);
+  end
+  return inlines
+end
+
+function DisamStringFormat:flatten_seq_ir(ir)
+  if not ir.children then
+    print(debug.traceback())
+  end
+  if #ir.children == 0 then
+    return {}
+  end
+  local inlines_list = {}
+  for _, child in ipairs(ir.children) do
+    if child.group_var ~= "missing" then
+      -- and not child.collapse_suppressed
+      -- Suppressed irs are stil kept in the DisamStringFormat
+      table.insert(inlines_list, self:flatten_ir(child))
+    end
+  end
+
+  local inlines = self:group(inlines_list, ir.delimiter, ir.formatting)
+  -- assert ir.quotes == localized quotes
+  inlines = self:affixed_quoted(inlines, ir.affixes, ir.quotes);
+  inlines = self:with_display(inlines, ir.display);
+  return inlines
+end
+
+
+output_module.move_in_puncts = {
+  ["."] = true,
+  ["!"] = true,
+  ["?"] = true,
+  [","] = true,
+}
+
+output_module.move_out_puncts = {
+  [","] = true,
+  [";"] = true,
+  [":"] = true,
+}
+
+-- https://github.com/Juris-M/citeproc-js/blob/aa2683f48fe23be459f4ed3be3960e2bb56203f0/src/queue.js#L724
+-- Also merge duplicate punctuations.
+output_module.quote_punctuation_map = {
+  ["!"] = {
+    ["."] = "!",
+    ["?"] = "!?",
+    [":"] = "!",
+    [","] = "!,",
+    [";"] = "!;",
+  },
+  ["?"] = {
+    ["!"] = "?!",
+    ["."] = "?",
+    [":"] = "?",
+    [","] = "?,",
+    [";"] = "?;",
+  },
+  ["."] = {
+    ["!"] = ".!",
+    ["?"] = ".?",
+    [":"] = ".:",
+    [","] = ".,",
+    [";"] = ".;",
+  },
+  [":"] = {
+    ["!"] = "!",
+    ["?"] = "?",
+    ["."] = ":",
+    [","] = ":,",
+    [";"] = ":;",
+  },
+  [","] = {
+    ["!"] = ",!",
+    ["?"] = ",?",
+    [":"] = ",:",
+    ["."] = ",.",
+    [";"] = ",;",
+  },
+  [";"] = {
+    ["!"] = "!",
+    ["?"] = "?",
+    [":"] = ";",
+    [","] = ";,",
+    ["."] = ";",
+  }
+}
+
+
+output_module.LocalizedQuotes = LocalizedQuotes
+
+output_module.InlineElement = InlineElement
+output_module.PlainText = PlainText
+output_module.Formatted = Formatted
+output_module.Micro = Micro
+output_module.Quoted = Quoted
+output_module.Linked = Linked
+output_module.Div = Div
+output_module.NoCase = NoCase
+output_module.NoDecor = NoDecor
+
+output_module.OutputFormat = OutputFormat
+
+output_module.Markup = Markup
+output_module.LatexWriter = LatexWriter
+output_module.HtmlWriter = HtmlWriter
+output_module.PlainTextWriter = PlainTextWriter
+output_module.DisamStringFormat = DisamStringFormat
+output_module.SortStringFormat = SortStringFormat
+
+return output_module


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Deleted: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-richtext.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-richtext.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-richtext.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,779 +0,0 @@
---
--- Copyright (c) 2021-2022 Zeping Lee
--- Released under the MIT license.
--- Repository: https://github.com/zepinglee/citeproc-lua
---
-
-local richtext = {}
-
-local unicode = require("unicode")
-
-local util = require("citeproc-util")
-
-
-local RichText = {
-  contents = nil,
-  formats = nil,
-  _type = "RichText",
-}
-
-function RichText:shallow_copy()
-  local res = richtext.new()
-  for _, text in ipairs(self.contents) do
-    table.insert(res.contents, text)
-  end
-  for key, value in pairs(self.formats) do
-    res.formats[key] = value
-  end
-  return res
-end
-
-function RichText:render(formatter, context, punctuation_in_quote)
-  self:merge_punctuations()
-
-  if punctuation_in_quote == nil and context then
-    punctuation_in_quote = context.style:get_locale_option("punctuation-in-quote")
-  end
-  if punctuation_in_quote then
-    self:move_punctuation_in_quote()
-  end
-
-  self:change_case()
-
-  self:flip_flop()
-
-  self:clean_formats()
-
-  return self:_render(formatter, context)
-end
-
-function RichText:_render(formatter, context)
-  local res = ""
-  for _, text in ipairs(self.contents) do
-    local str
-    if type(text) == "string" then
-      if formatter and formatter.text_escape then
-        str = formatter.text_escape(text)
-      else
-        str = text
-      end
-    else  -- RichText
-      str = text:_render(formatter, context)
-    end
-    -- Remove leading spaces
-    if string.sub(res, -1) == " " and string.sub(str, 1, 1) == " " then
-      str = string.gsub(str, "^%s+", "")
-    end
-    res = res .. str
-  end
-  for _, attr in ipairs(richtext.format_sequence) do
-    local value = self.formats[attr]
-    if value then
-      local key = string.format("@%s/%s", attr, value)
-      if formatter then
-        local format = formatter[key]
-        if type(format) == "string" then
-          res = string.format(format, res)
-        elseif type(format) == "function" then
-          res = format(res, context)
-        end
-      end
-    end
-  end
-  return res
-end
-
-function RichText:merge_punctuations(contents, index)
-  for i, text in ipairs(self.contents) do
-    if text._type == "RichText" then
-      contents, index = text:merge_punctuations(contents, index)
-    elseif type(text) == "string" then
-      if contents and index then
-        local previous_string = contents[index]
-        local last_char = string.sub(previous_string, -1)
-        local right_punct_map = richtext.punctuation_map[last_char]
-        if right_punct_map then
-          local first_char = string.sub(text, 1, 1)
-          local new_punctuations = nil
-          if first_char == last_char then
-            new_punctuations = last_char
-          elseif contents == self.contents then
-            new_punctuations = right_punct_map[first_char]
-          end
-          if new_punctuations then
-            if #text == 1 then
-              table.remove(self.contents, i)
-            else
-              self.contents[i] = string.sub(text, 2)
-            end
-            contents[index] = string.sub(previous_string, 1, -2) .. new_punctuations
-          end
-        end
-      end
-      contents = self.contents
-      index = i
-    end
-  end
-  return contents, index
-end
-
-function RichText:move_punctuation_in_quote()
-  local i = 1
-  while i <= #self.contents do
-    local text = self.contents[i]
-    if type(text) == "table" and text._type == "RichText" then
-      text:move_punctuation_in_quote()
-
-      if text.formats["quotes"] then
-        local contents = self.contents
-        local last_string = text
-        while type(last_string) == "table" and last_string._type == "RichText" do
-          contents = last_string.contents
-          last_string = contents[#contents]
-        end
-
-        local done = false
-        while not done do
-          done = true
-          last_string = contents[#contents]
-          local last_char = string.sub(last_string, -1)
-          if i < #self.contents then
-            local next_text = self.contents[i + 1]
-            if type(next_text) == "string" then
-              local first_char = string.sub(next_text, 1, 1)
-              if richtext.in_quote_punctuations[first_char] then
-                done = false
-                local right_punct_map = richtext.punctuation_map[last_char]
-                if right_punct_map then
-                  first_char  = right_punct_map[first_char]
-                  last_string = string.sub(last_string, 1, -2)
-                end
-                contents[#contents] = last_string .. first_char
-                if #next_text == 1 then
-                  table.remove(self.contents, i + 1)
-                else
-                  self.contents[i + 1] = string.sub(next_text, 2)
-                end
-              end
-            end
-          end
-        end
-      end
-    end
-    i = i + 1
-  end
-end
-
-function RichText:change_case()
-  for _, text in ipairs(self.contents) do
-    if type(text) == "table" and text._type == "RichText" then
-      text:change_case()
-    end
-  end
-  local text_case = self.formats["text-case"]
-  if text_case then
-    if text_case == "lowercase" then
-      self:lowercase()
-    elseif text_case == "uppercase" then
-      self:uppercase()
-    elseif text_case == "capitalize-first" then
-      self:capitalize_first()
-    elseif text_case == "capitalize-all" then
-      self:capitalize_all()
-    elseif text_case == "sentence" then
-      self:sentence()
-    elseif text_case == "title" then
-      self:title()
-    end
-  end
-end
-
-function RichText:_change_word_case(state, word_transform, first_tranform, is_phrase)
-  if self.formats["text-case"] == "nocase" then
-    return
-  end
-  if is_phrase and (self.formats["vertical-align"] == "sup" or
-      self.formats["vertical-align"] == "sub" or
-      self.formats["font-variant"] == "small-caps") then
-    return
-  end
-  state = state or "after-sentence"
-  word_transform = word_transform or function (x) return x end
-  first_tranform = first_tranform or word_transform
-  for i, text in ipairs(self.contents) do
-    if type(text) == "string" then
-
-      local res = ""
-      local word_seps = {
-        " ",
-        "%-",
-        "/",
-        util.unicode["no-break space"],
-        util.unicode["en dash"],
-        util.unicode["em dash"],
-      }
-      for _, tuple in ipairs(util.split(text, word_seps, nil, true)) do
-        local word, punctuation = table.unpack(tuple)
-        if state == "after-sentence" then
-          res = res .. first_tranform(word)
-          if string.match(word, "%w") then
-            state = "after-word"
-          end
-        else
-          res = res .. word_transform(word, punctuation)
-        end
-        res = res .. punctuation
-        if string.match(word, "[.!?:]%s*$") then
-          state = "after-sentence"
-        end
-      end
-
-      -- local word_index = 0
-      -- local res = string.gsub(text, "%w+", function (word)
-      --   word_index = word_index + 1
-      --   if word_index == 1 then
-      --     return first_tranform(word)
-      --   else
-      --     return word_transform(word)
-      --   end
-      -- end)
-      -- if string.match(res, "[.!?:]%s*$") then
-      --   state = "after-sentence"
-      -- end
-
-      self.contents[i] = res
-    else
-      state = text:_change_word_case(state, word_transform, first_tranform, true)
-    end
-  end
-  return state
-end
-
-function RichText:lowercase()
-  local word_transform = unicode.utf8.lower
-  self:_change_word_case("after-sentence", word_transform)
-end
-
-function RichText:uppercase()
-  local word_transform = unicode.utf8.upper
-  self:_change_word_case("after-sentence", word_transform)
-end
-
-local function capitalize(str)
-  local res = string.gsub(str, utf8.charpattern, unicode.utf8.upper, 1)
-  return res
-end
-
-local function capitalize_if_lower(word)
-  if util.is_lower(word) then
-    return capitalize(word)
-  else
-    return word
-  end
-end
-
-function RichText:capitalize_first(state)
-  local first_tranform = capitalize_if_lower
-  self:_change_word_case("after-sentence", nil, first_tranform)
-end
-
-function RichText:capitalize_all()
-  local word_transform = capitalize_if_lower
-  self:_change_word_case("after-sentence", word_transform)
-end
-
-function RichText:is_upper()
-  for _, text in ipairs(self.contents) do
-    if type(text) == "string" then
-      if not util.is_upper(text) then
-        return false
-      end
-    else
-      local res = text:is_upper()
-      if not res then
-        return false
-      end
-    end
-  end
-  return true
-end
-
-function RichText:sentence()
-  if self:is_upper() then
-    local first_tranform = function(word)
-      return capitalize(unicode.utf8.lower(word))
-    end
-    local word_transform = unicode.utf8.lower
-    self:_change_word_case("after-sentence", word_transform, first_tranform)
-  else
-    local first_tranform = capitalize_if_lower
-    self:_change_word_case("after-sentence", nil, first_tranform)
-  end
-end
-
-function RichText:title()
-  if self:is_upper() then
-    local first_tranform = function(word)
-      return capitalize(unicode.utf8.lower(word))
-    end
-    local word_transform = function(word, sep)
-      local res = unicode.utf8.lower(word)
-      if not util.stop_words[res] then
-        res = capitalize(res)
-      end
-      return res
-    end
-    self:_change_word_case("after-sentence", word_transform, first_tranform)
-  else
-    local first_tranform = capitalize_if_lower
-    local word_transform = function(word, sep)
-      local lower = unicode.utf8.lower(word)
-      -- Stop word before hyphen is treated as a normal word.
-      if util.stop_words[lower] and sep ~= "-" then
-        return lower
-      elseif word == lower then
-        return capitalize(word)
-      else
-        return word
-      end
-    end
-    self:_change_word_case("after-sentence", word_transform, first_tranform)
-  end
-end
-
-function richtext.concat(str1, str2)
-  assert(str1 and str2)
-
-  if type(str1) == "string" then
-    str1 = richtext.new(str1)
-  end
-
-  local res
-  if next(str1.formats) == nil or str2 == "" then
-    -- shallow copy
-    res = str1
-  else
-    res = richtext.new()
-    res.contents = {str1}
-  end
-
-  if str2._type == "RichText" then
-    if next(str2.formats) == nil then
-      for _, text in ipairs(str2.contents) do
-        table.insert(res.contents, text)
-      end
-    else
-      table.insert(res.contents, str2)
-    end
-  elseif str2 ~= "" then
-    table.insert(res.contents, str2)
-  end
-  return res
-end
-
-function richtext.concat_list(list, delimiter)
-  -- Strings in the list may be nil thus ipairs() should be avoided.
-  -- The delimiter may be nil.
-  local res = nil
-  for i = 1, #list do
-    local text = list[i]
-    if text and text ~= "" then
-      if res then
-        if delimiter and delimiter ~= "" then
-          res = richtext.concat(res, delimiter)
-        end
-        res = richtext.concat(res, text)
-      else
-        if type(text) == "string" then
-          text = richtext.new(text)
-        end
-        res = text
-      end
-    end
-  end
-  return res
-end
-
-function RichText:strip_periods()
-  local last_string = self
-  local contents = self.contents
-  while last_string._type == "RichText" do
-    contents = last_string.contents
-    last_string = contents[#contents]
-  end
-  if string.sub(last_string, -1) == "." then
-    contents[#contents] = string.sub(last_string, 1, -2)
-  end
-end
-
-function RichText:add_format(attr, value)
-  self.formats[attr] = value
-end
-
-function RichText:flip_flop(attr, value)
-  if not attr then
-    for attr, _ in pairs(richtext.flip_flop_formats) do
-      self:flip_flop(attr)
-    end
-    return
-  end
-
-  local default_value = richtext.default_formats[attr]
-
-  if value and value ~= default_value and self.formats[attr] == value then
-    self.formats[attr] = richtext.flip_flop_values[attr][value]
-  end
-  if self.formats[attr] then
-    value = self.formats[attr]
-  end
-
-  for _, text in ipairs(self.contents) do
-    if type(text) == "table" and text._type == "RichText" then
-      text:flip_flop(attr, value)
-    end
-  end
-end
-
-function RichText:clean_formats(format)
-  -- Remove the formats that are default values
-  if not format then
-    for format, _ in pairs(richtext.default_formats) do
-      self:clean_formats(format)
-    end
-    return
-  end
-  if self.formats[format] then
-    if self.formats[format] == richtext.default_formats[format] then
-      self.formats[format] = nil
-    else
-      return
-    end
-  end
-  for _, text in ipairs(self.contents) do
-    if type(text) == "table" and text._type == "RichText" then
-      text:clean_formats(format)
-    end
-  end
-end
-
-local RichText_mt = {
-  __index = RichText,
-  __concat = richtext.concat,
-}
-
-local function table_update(t, new_t)
-  for key, value in pairs(new_t) do
-    t[key] = value
-  end
-  return t
-end
-
-function RichText._split_tags(str)
-  -- Normalize markup
-  str = string.gsub(str, '<span%s+style="font%-variant:%s*small%-caps;?">', '<span style="font-variant:small-caps;">')
-  str = string.gsub(str, '<span%s+class="nocase">', '<span class="nocase">')
-  str = string.gsub(str, '<span%s+class="nodecor">', '<span class="nodecor">')
-
-  local strings = {}
-
-  local start_index = 1
-  local i = 1
-
-  while i <= #str do
-    local substr = string.sub(str, i)
-    local starts_with_tag = false
-    for tag, _ in pairs(richtext.tags) do
-      if util.startswith(substr, tag) then
-        if start_index <= i - 1 then
-          table.insert(strings, string.sub(str, start_index, i-1))
-        end
-        table.insert(strings, tag)
-        i = i + #tag
-        start_index = i
-        starts_with_tag = true
-        break
-      end
-    end
-    if not starts_with_tag then
-      i = i + 1
-    end
-  end
-  if start_index <= #str then
-    table.insert(strings, string.sub(str, start_index))
-  end
-
-  for i = 1, #strings do
-    str = strings[i]
-    if str == "'" or str == util.unicode["apostrophe"] then
-      local previous_str = strings[i - 1]
-      local next_str = strings[i + 1]
-      if previous_str and next_str then
-        local previous_code_point = nil
-        for _, code_point in utf8.codes(previous_str) do
-          previous_code_point = code_point
-        end
-        local next_code_point = utf8.codepoint(next_str)
-        if util.is_romanesque(previous_code_point) and util.is_romanesque(next_code_point) then
-          -- An apostrophe
-          strings[i-1] = strings[i-1] .. util.unicode["apostrophe"] .. strings[i+1]
-          table.remove(strings, i+1)
-          table.remove(strings, i)
-        end
-      end
-    end
-  end
-
-  return strings
-end
-
-function richtext.new(text, formats)
-  local res = {
-    contents = {},
-    formats = formats or {},
-  }
-
-  setmetatable(res, RichText_mt)
-
-  if not text then
-    return res
-  end
-
-  if type(text) == "string" then
-
-    local strings = RichText._split_tags(text)
-    local contents = {}
-    for _, str in ipairs(strings) do
-      table.insert(contents, str)
-
-      local end_tag = nil
-      if str == '"' then
-        local last_text = contents[#contents - 1]
-        if last_text and type(last_text) == "string" and string.match(last_text, "%s$") then
-          end_tag = nil
-        else
-          end_tag = str
-        end
-      elseif richtext.end_tags[str] then
-        end_tag = str
-      end
-
-      if end_tag then
-        for i = #contents - 1, 1, -1 do
-          local start_tag = contents[i]
-          if type(start_tag) == "string" and richtext.tag_pairs[start_tag] == end_tag then
-            local subtext = richtext.new()
-            -- subtext.contents = util.slice(contents, i + 1, #contents - 1)
-            if start_tag == "'" and end_tag == "'" and i == #contents - 1 then
-              contents[i] = util.unicode["apostrophe"]
-              contents[#contents] = util.unicode["apostrophe"]
-              break
-            end
-
-            for j = i + 1, #contents - 1 do
-              local substr = contents[j]
-              if substr == "'" then
-                substr = util.unicode["apostrophe"]
-              end
-              local last_text = subtext.contents[#subtext.contents]
-              if type(substr) == "string" and type(last_text) == "string" then
-                subtext.contents[#subtext.contents] = last_text .. substr
-              else
-                table.insert(subtext.contents, substr)
-              end
-            end
-
-            if start_tag == '<span class="nodecor">' then
-              for attr, value in pairs(richtext.default_formats) do
-                subtext.formats[attr] = value
-              end
-              subtext.formats["text-case"] = "nocase"
-            else
-              for attr, value in pairs(richtext.tag_formats[start_tag]) do
-                subtext.formats[attr] = value
-              end
-            end
-
-            for j = #contents, i, -1 do
-              table.remove(contents, j)
-            end
-            table.insert(contents, subtext)
-            break
-          end
-        end
-      end
-    end
-
-    for i = #contents, 1, -1 do
-      if contents[i] == "'" then
-        contents[i] = util.unicode["apostrophe"]
-      end
-      if type(contents[i]) == "string" and type(contents[i+1]) == "string" then
-        contents[i] = contents[i] .. contents[i+1]
-        table.remove(contents, i+1)
-      end
-    end
-
-    if #contents == 1 and type(contents[1]) == "table" then
-      res = contents[1]
-    else
-      res.contents = contents
-    end
-
-    return res
-
-  elseif type(text) == "table" and text._type == "RichText" then
-    return text
-
-  elseif type(text) == "table" then
-    return text
-  end
-  return nil
-end
-
-richtext.tag_formats = {
-  ["<i>"] = {["font-style"] = "italic"},
-  ["<b>"] = {["font-weight"] = "bold"},
-  ["<sup>"] = {["vertical-align"] = "sup"},
-  ["<sub>"] = {["vertical-align"] = "sub"},
-  ["<sc>"] = {["font-variant"] = "small-caps"},
-  ['<span style="font-variant:small-caps;">'] = {["font-variant"] = "small-caps"},
-  ['<span class="nocase">'] = {["text-case"] = "nocase"},
-  ['"'] = {["quotes"] = "true"},
-  [util.unicode['left double quotation mark']] = {["quotes"] = "true"},
-  ["'"] = {["quotes"] = "true"},
-  [util.unicode['left single quotation mark']] = {["quotes"] = "true"},
-}
-
-richtext.default_formats = {
-  ["URL"] = "false",
-  ["DOI"] = "false",
-  ["PMID"] = "false",
-  ["PMCID"] = "false",
-  ["font-style"] = "normal",
-  ["font-variant"] = "normal",
-  ["font-weight"] = "normal",
-  ["text-decoration"] = "none",
-  ["vertical-align"] = "baseline",
-  ["quotes"] = "false",
-}
-
-richtext.format_sequence = {
-  "URL",
-  "DOI",
-  "PMID",
-  "PMCID",
-  "font-style",
-  "font-variant",
-  "font-weight",
-  "text-decoration",
-  "vertical-align",
-  "quotes",
-  "display",
-}
-
-richtext.flip_flop_formats = {
-  ["font-style"] = true,
-  ["font-weight"] = true,
-  ["font-variant"] = true,
-  ["quotes"] = true,
-}
-
-richtext.flip_flop_values = {
-  ["font-style"] = {
-    italic = "normal",
-    normal = "italic",
-  },
-  ["font-weight"] = {
-    bold = "normal",
-    normal = "bold",
-  },
-  ["font-variant"] = {
-    ["small-caps"] = "normal",
-    normal = "small-caps",
-  },
-  ["quotes"] = {
-    ["true"] = "inner",
-    inner = "true",
-  },
-}
-
--- https://github.com/Juris-M/citeproc-js/blob/aa2683f48fe23be459f4ed3be3960e2bb56203f0/src/queue.js#L724
--- Also merge duplicate punctuations.
-richtext.punctuation_map = {
-  ["!"] = {
-    ["."] = "!",
-    ["?"] = "!?",
-    [":"] = "!",
-    [","] = "!,",
-    [";"] = "!;",
-  },
-  ["?"] = {
-    ["!"] = "?!",
-    ["."] = "?",
-    [":"] = "?",
-    [","] = "?,",
-    [";"] = "?;",
-  },
-  ["."] = {
-    ["!"] = ".!",
-    ["?"] = ".?",
-    [":"] = ".:",
-    [","] = ".,",
-    [";"] = ".;",
-  },
-  [":"] = {
-    ["!"] = "!",
-    ["?"] = "?",
-    ["."] = ":",
-    [","] = ":,",
-    [";"] = ":;",
-  },
-  [","] = {
-    ["!"] = ",!",
-    ["?"] = ",?",
-    [":"] = ",:",
-    ["."] = ",.",
-    [";"] = ",;",
-  },
-  [";"] = {
-    ["!"] = "!",
-    ["?"] = "?",
-    [":"] = ";",
-    [","] = ";,",
-    ["."] = ";",
-  }
-}
-
-richtext.in_quote_punctuations = {
-  [","] = true,
-  ["."] = true,
-  ["?"] = true,
-  ["!"] = true,
-}
-
-richtext.tag_pairs = {
-  ["<i>"] = "</i>",
-  ["<b>"] = "</b>",
-  ["<sup>"] = "</sup>",
-  ["<sub>"] = "</sub>",
-  ["<sc>"] = "</sc>",
-  ['<span style="font-variant:small-caps;">'] = "</span>",
-  ['<span class="nocase">'] = "</span>",
-  ['<span class="nodecor">'] = "</span>",
-  ['"'] = '"',
-  [util.unicode['left double quotation mark']] = util.unicode['right double quotation mark'],
-  ["'"] = "'",
-  [util.unicode['left single quotation mark']] = util.unicode['right single quotation mark'],
-}
-
-richtext.tags = {}
-
-richtext.end_tags = {}
-
-for start_tag, end_tag in pairs(richtext.tag_pairs) do
-  richtext.tags[start_tag] = true
-  richtext.tags[end_tag] = true
-  richtext.end_tags[end_tag] = true
-end
-
-return richtext

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -6,11 +6,55 @@
 
 -- load `slnunicode` from LuaTeX
 local unicode = require("unicode")
-local inspect = require("inspect")
+local inspect  -- only load it when debugging
 
 
 local util = {}
 
+function util.clone(obj)
+  if type(obj) == "table" then
+    local res = {}
+    for key, value in pairs(obj) do
+      res[key] = value
+    end
+    return setmetatable(res, getmetatable(obj))
+  else
+    return obj
+  end
+end
+
+function util.join(list, delimiter)
+  local res = {}
+  for i, item in ipairs(list) do
+    if i > 1 then
+      table.insert(res, delimiter)
+    end
+    table.insert(res, item)
+  end
+  return res
+end
+
+function util.to_boolean(str)
+  if not str then
+    return false
+  end
+  if str == "true" then
+    return true
+  elseif str == "false" then
+    return false
+  else
+    util.warning(string.format('Invalid boolean string "%s"', str))
+    return false
+  end
+end
+
+function util.to_list(str)
+  if not str then
+    return nil
+  end
+  return util.split(str)
+end
+
 function util.to_ordinal (n)
   assert(type(n) == "number")
   local last_digit = n % 10
@@ -44,21 +88,60 @@
   end
 end
 
-local function remove_all_metatables(item, path)
-  if path[#path] ~= inspect.METATABLE then return item end
-end
+local remove_all_metatables = nil
 
-function util.debug(...)
-  -- io.stderr:write(inspect(..., {process = remove_all_metatables}))
-  io.stderr:write(inspect(...))
+function util.debug(obj)
+  if not inspect then
+    inspect = require("inspect")
+    remove_all_metatables = function (item, path)
+      if path[#path] ~= inspect.METATABLE then
+        return item
+      end
+    end
+  end
+  io.stderr:write("[")
+  io.stderr:write(debug.getinfo(2, "S").source:sub(2))
+  io.stderr:write(":")
+  io.stderr:write(debug.getinfo(2, "l").currentline)
+  io.stderr:write("] ")
+  io.stderr:write(inspect(obj, {process = remove_all_metatables}))
   io.stderr:write("\n")
 end
 
 -- Similar to re.split() in Python
-function util.split(str, seps, maxsplit, include_sep)
+function util.split(str, sep, maxsplit)
   if not str then
-    error("Invalid string.")
+    util.error("Invalid string.")
   end
+  sep = sep or "%s+"
+  if sep == "" then
+    util.error("Empty separator")
+  end
+  if string.find(str, sep) == nil then
+    return { str }
+  end
+
+  if maxsplit == nil or maxsplit < 0 then
+    maxsplit = -1    -- No limit
+  end
+  local result = {}
+  local pattern = "(.-)" .. sep .. "()"
+  local num_splits = 0
+  local lastPos = 1
+  for part, pos in string.gmatch(str, pattern) do
+    if num_splits == maxsplit then
+      break
+    end
+    num_splits = num_splits + 1
+    result[num_splits] = part
+    lastPos = pos
+  end
+  -- Handle the last field
+  result[num_splits + 1] = string.sub(str, lastPos)
+  return result
+end
+
+function util.split_multiple(str, seps, include_sep)
   seps = seps or "%s+"
   if seps == "" then
     error("Empty separator")
@@ -97,6 +180,15 @@
   return res
 end
 
+-- TODO: [Unicode word boundaries](https://www.unicode.org/reports/tr29/#Word_Boundaries)
+-- Returns: {
+--   {word, boundary},
+--   {word, boundary},
+-- }
+function util.segment_words(str)
+  return util.split_multiple(str, util.word_boundaries, true)
+end
+
 function util.slice (t, start, stop)
   start = start or 1
   stop = stop or #t
@@ -132,6 +224,31 @@
   return res
 end
 
+-- Python list.extend()
+function util.extend(first, second)
+  if not second then
+    print(debug.traceback())
+  end
+  local l = #first
+  for i, element in ipairs(second) do
+    first[l + i] = element
+  end
+  return first
+end
+
+-- Concat two lists in place
+function util.concat_list(first, second)
+  local res
+  for i, element in ipairs(first) do
+    res[i] = element
+  end
+  local i = #res
+  for j, element in ipairs(second) do
+    res[i + j] = element
+  end
+  return res
+end
+
 function util.lstrip (str)
   if not str then
     return nil
@@ -153,13 +270,23 @@
 end
 
 function util.startswith (str, prefix)
+  if not str then
+    return false
+  end
   return string.sub(str, 1, #prefix) == prefix
 end
 
 function util.endswith (str, suffix)
+  if not str then
+    return false
+  end
   return string.sub(str, -#suffix) == suffix
 end
 
+function util.is_punct(str)
+  return string.match(str, "^%p$")
+end
+
 function util.is_numeric (str)
   if str == nil or str == "" then
     return false
@@ -166,9 +293,11 @@
   end
   local res = true
   for w in string.gmatch(str, "%w+") do
-    if string.match(w, "^[a-zA-Z]*%d+[a-zA-Z]*$") == nil then
-      res = false
-      break
+    if not string.match(w, "^%a*%d+%a*$") and
+        not string.match(w, "^[MDCLXVI]+$") and
+        not string.match(w, "^[mdclxvi]+$") then
+      -- Roman number withou validation
+      return false
     end
   end
   for w in string.gmatch(str, "%W+") do
@@ -180,19 +309,84 @@
   return res
 end
 
-function util.is_uncertain_date (variable)
-  if variable == nil then
-    return false
-  end
-  local value = variable["circa"]
-  return value ~= nil and value ~= ""
-end
-
 util.variable_types = {}
 
 -- schema/schemas/styles/csl-variables.rnc
 util.variables = {}
 
+-- -- Standard variables
+-- util.variables.standard = {
+--   "abstract",
+--   "annote",
+--   "archive",
+--   "archive_collection",
+--   "archive_location",
+--   "archive-place",
+--   "authority",
+--   "call-number",
+--   "citation-key",
+--   "citation-label",
+--   "collection-title",
+--   "container-title",
+--   "container-title-short",
+--   "dimensions",
+--   "division",
+--   "DOI",
+--   "event",
+--   "event-title",
+--   "event-place",
+--   "genre",
+--   "ISBN",
+--   "ISSN",
+--   "jurisdiction",
+--   "keyword",
+--   "language",
+--   "license",
+--   "medium",
+--   "note",
+--   "original-publisher",
+--   "original-publisher-place",
+--   "original-title",
+--   "part-title",
+--   "PMCID",
+--   "PMID",
+--   "publisher",
+--   "publisher-place",
+--   "references",
+--   "reviewed-genre",
+--   "reviewed-title",
+--   "scale",
+--   "source",
+--   "status",
+--   "title",
+--   "title-short",
+--   "URL",
+--   "volume-title",
+--   "year-suffix",
+-- }
+
+-- Number variables
+util.variables.number = {
+  "chapter-number",
+  "citation-number",
+  "collection-number",
+  "edition",
+  "first-reference-note-number",
+  "issue",
+  "locator",
+  "number",
+  "number-of-pages",
+  "number-of-volumes",
+  "page",
+  "page-first",
+  "part-number",
+  "printing-number",
+  "section",
+  "supplement-number",
+  "version",
+  "volume",
+}
+
 -- Date variables
 util.variables.date = {
   "accessed",
@@ -234,28 +428,6 @@
   "translator",
 }
 
--- Number variables
-util.variables.number = {
-  "chapter-number",
-  "citation-number",
-  "collection-number",
-  "edition",
-  "first-reference-note-number",
-  "issue",
-  "locator",
-  "number",
-  "number-of-pages",
-  "number-of-volumes",
-  "page",
-  "page-first",
-  "part-number",
-  "printing-number",
-  "section",
-  "supplement-number",
-  "version",
-  "volume",
-}
-
 util.variable_types = {}
 
 for type, variables in pairs(util.variables) do
@@ -327,11 +499,23 @@
   ["apostrophe"] = "\u{2019}",
   ["left double quotation mark"] = "\u{201C}",
   ["right double quotation mark"] = "\u{201D}",
+  ["left-pointing double angle quotation mark"] = "\u{00AB}",
+  ["right-pointing double angle quotation mark"] = "\u{00BB}",
   ["horizontal ellipsis"] = "\u{2026}",
   ["narrow no-break space"] = "\u{202F}",
 }
 
+util.word_boundaries = {
+  ":",
+  " ",
+  "%-",
+  "/",
+  util.unicode["no-break space"],
+  util.unicode["en dash"],
+  util.unicode["em dash"],
+}
 
+
 -- Text-case
 
 function util.is_lower (str)
@@ -342,9 +526,11 @@
   return unicode.utf8.upper(str) == str
 end
 
-function util.capitalize (str)
-  str = unicode.utf8.lower(str)
-  local res = string.gsub(str, "%w", unicode.utf8.upper, 1)
+function util.capitalize(str)
+  -- if not str then
+  --   print(debug.traceback())
+  -- end
+  local res = string.gsub(str, utf8.charpattern, unicode.utf8.upper, 1)
   return res
 end
 
@@ -635,7 +821,7 @@
   local output = {}
   for _, tuple in ipairs(util.roman_numerals) do
     local letter, value = table.unpack(tuple)
-    table.insert(output, string.rep(letter, number // value))
+    table.insert(output, string.rep(letter, math.floor(number / value)))
     number = number % value
   end
   return table.concat(output, "")
@@ -793,4 +979,35 @@
 end
 
 
+function util.parse_iso_date(str)
+  local date
+  local date_parts = util.split(str, "/")
+  if #date_parts <= 2 then
+    date = {["date-parts"] = {}}
+    for _, date_part in ipairs(date_parts) do
+      table.insert(date["date-parts"], util.split(date_part, "%-"))
+    end
+  end
+  if not date then
+    date = {literal = str}
+  end
+  return date
+end
+
+
+function util.parse_extra_name(str)
+  local name
+  local name_parts = util.split(str, "%s*||%s*")
+  if #name_parts == 2 then
+    name = {
+      family = name_parts[1],
+      given = name_parts[2],
+    }
+  else
+    name = {literal = str}
+  end
+  return name
+end
+
+
 return util

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -10,7 +10,7 @@
 local bib = require("citeproc-bib")
 local util = require("citeproc-util")
 
-citeproc.__VERSION__ = "0.1.1"
+citeproc.__VERSION__ = "0.2.0"
 
 citeproc.new = engine.CiteProc.new
 citeproc.parse_bib = bib.parse

Deleted: trunk/Master/texmf-dist/scripts/citation-style-language/csl-core.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/csl-core.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/csl-core.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,306 +0,0 @@
---
--- Copyright (c) 2021-2022 Zeping Lee
--- Released under the MIT license.
--- Repository: https://github.com/zepinglee/citeproc-lua
---
-
-local core = {}
-
-local citeproc = require("citeproc")
-local util = citeproc.util
-require("lualibs")
-
-
-core.locale_file_format = "csl-locales-%s.xml"
-core.ids = {}
-core.loaded_ids = {}
-core.uncite_all_items = false
-
-function core.error(message)
-  if luatexbase then
-    luatexbase.module_error("csl", message)
-  else
-    util.error(message)
-  end
-end
-
-function core.warning(message)
-  if luatexbase then
-    luatexbase.module_warning("csl", message)
-  else
-    util.warning(message)
-  end
-end
-
-function core.info(message)
-  if luatexbase then
-    luatexbase.module_info("csl", message)
-  else
-    util.info(message)
-  end
-end
-
-
-function core.read_file(file_name, ftype, file_info)
-  file_info = file_info or "file"
-  local path = kpse.find_file(file_name, ftype)
-  if not path then
-    if ftype and not util.endswith(file_name, ftype) then
-      file_name = file_name .. ftype
-    end
-    core.error(string.format('Cannot find %s "%s"', file_info, file_name))
-    return nil
-  end
-  local file = io.open(path, "r")
-  if not file then
-    core.error(string.format('Cannot open %s "%s"', file_info, path))
-    return nil
-  end
-  local contents = file:read("*a")
-  file:close()
-  return contents
-end
-
-
-local function read_data_file(data_file)
-  local file_name = data_file
-  local extension = nil
-  local contents = nil
-
-  if util.endswith(data_file, ".json") then
-    extension = ".json"
-    contents = core.read_file(data_file, nil, "database file")
-  elseif util.endswith(data_file, ".bib") then
-    extension = ".bib"
-    contents = core.read_file(data_file, "bib", "database file")
-  else
-    local path = kpse.find_file(data_file .. ".json")
-    if path then
-      file_name = data_file .. ".json"
-      extension = ".json"
-      contents = core.read_file(data_file .. ".json", nil, "database file")
-    else
-      path = kpse.find_file(data_file, "bib")
-      if path then
-        file_name = data_file .. ".bib"
-        extension = ".bib"
-        contents = core.read_file(data_file, "bib", "database file")
-      else
-        core.error(string.format('Cannot find database file "%s"', data_file .. ".json"))
-      end
-    end
-  end
-
-  local csl_items = nil
-
-  if extension == ".json" then
-    csl_items = utilities.json.tolua(contents)
-  elseif extension == ".bib" then
-    csl_items = citeproc.parse_bib(contents)
-  end
-
-  return file_name, csl_items
-end
-
-
-local function read_data_files(data_files)
-  local bib = {}
-  for _, data_file in ipairs(data_files) do
-    local file_name, csl_items = read_data_file(data_file)
-
-    -- TODO: parse bib entries on demand
-    for _, item in ipairs(csl_items) do
-      local id = item.id
-      if bib[id] then
-        core.error(string.format('Duplicate entry key "%s" in "%s".', id, file_name))
-      end
-      bib[id] = item
-    end
-  end
-  return bib
-end
-
-
-
-function core.make_citeproc_sys(data_files)
-  core.bib = read_data_files(data_files)
-  local citeproc_sys = {
-    retrieveLocale = function (lang)
-      local locale_file_format = core.locale_file_format or "locales-%s.xml"
-      local filename = string.format(locale_file_format, lang)
-      return core.read_file(filename)
-    end,
-    retrieveItem = function (id)
-      local res = core.bib[id]
-      -- if not res then
-      --   core.warning(string.format('Failed to find entry "%s".', id))
-      -- end
-      return res
-    end
-  }
-
-  return citeproc_sys
-end
-
-function core.init(style_name, data_files, lang)
-  if style_name == "" or #data_files == 0 then
-    return nil
-  end
-  local style = core.read_file(style_name .. ".csl", nil, "style file")
-  if not style then
-    core.error(string.format('Failed to load style "%s.csl"', style_name))
-    return nil
-  end
-
-  local force_lang = nil
-  if lang and lang ~= "" then
-    force_lang = true
-  else
-    lang = nil
-  end
-
-  local citeproc_sys = core.make_citeproc_sys(data_files)
-  local engine = citeproc.new(citeproc_sys, style, lang, force_lang)
-  return engine
-end
-
-
-function core.make_citation(citation_info)
-  -- `citation_info`: "{ITEM-1 at 2}{{id={ITEM-1},label={page},locator={6}}}{3}"
-  local arguments = {}
-  for argument in string.gmatch(citation_info, "(%b{})") do
-    table.insert(arguments, string.sub(argument, 2, -2))
-  end
-  if #arguments ~= 3 then
-    error(string.format('Invalid citation "%s"', citation_info))
-    return nil
-  end
-  local citation_id, cite_items_str, note_index = table.unpack(arguments)
-
-  local cite_items = {}
-  if citation_id == "nocite" then
-    for _, cite_id in ipairs(util.split(cite_items_str, "%s*,%s*")) do
-      table.insert(cite_items, {id = cite_id})
-    end
-
-  else
-    for item_str in string.gmatch(cite_items_str, "(%b{})") do
-      item_str = string.sub(item_str, 2, -2)
-      local cite_item = {}
-      for key, value in string.gmatch(item_str, "([%w%-]+)=(%b{})") do
-        value = string.sub(value, 2, -2)
-        cite_item[key] = value
-      end
-      table.insert(cite_items, cite_item)
-    end
-  end
-
-  local citation = {
-    citationID = citation_id,
-    citationItems = cite_items,
-    properties = {
-      noteIndex = tonumber(note_index),
-    },
-  }
-
-  return citation
-end
-
-
-function core.process_citations(engine, citations)
-  local citations_pre = {}
-
-  -- Save the time of bibliography sorting by update all ids at one time.
-  core.update_item_ids(engine, citations)
-  local citation_strings = {}
-
-  for _, citation in ipairs(citations) do
-    if citation.citationID ~= "nocite" then
-      local res = engine:processCitationCluster(citation, citations_pre, {})
-
-      for _, citation_res in ipairs(res[2]) do
-        local citation_str = citation_res[2]
-        local citation_id = citation_res[3]
-        citation_strings[citation_id] = citation_str
-      end
-
-      table.insert(citations_pre, {citation.citationID, citation.properties.noteIndex})
-    end
-  end
-
-  return citation_strings
-end
-
-
-function core.update_item_ids(engine, citations)
-  if core.uncite_all_items then
-    for item_id, _ in pairs(core.bib) do
-      if not core.loaded_ids[item_id] then
-        table.insert(core.ids, item_id)
-        core.loaded_ids[item_id] = true
-      end
-    end
-  end
-  for _, citation in ipairs(citations) do
-    for _, cite_item in ipairs(citation.citationItems) do
-      local id = cite_item.id
-      if id == "*" then
-        for item_id, _ in pairs(core.bib) do
-          if not core.loaded_ids[item_id] then
-            table.insert(core.ids, item_id)
-            core.loaded_ids[item_id] = true
-          end
-        end
-      else
-        if not core.loaded_ids[id] then
-          table.insert(core.ids, id)
-          core.loaded_ids[id] = true
-        end
-      end
-    end
-  end
-  engine:updateItems(core.ids)
-  -- TODO: engine:updateUncitedItems(ids)
-end
-
-
-function core.make_bibliography(engine)
-  local result = engine:makeBibliography()
-
-  local params = result[1]
-  local bib_items = result[2]
-
-  local res = ""
-
-  local bib_options = ""
-  if params["hangingindent"] then
-    bib_options = bib_options .. "\n  hanging-indent = true,"
-  end
-  if params["linespacing"] then
-    bib_options = bib_options .. string.format("\n  line-spacing = %d,", params["linespacing"])
-  end
-  if params["entryspacing"] then
-    bib_options = bib_options .. string.format("\n  entry-spacing = %d,", params["entryspacing"])
-  end
-
-  if bib_options ~= "" then
-    bib_options = "\\cslsetup{" .. bib_options .. "\n}\n\n"
-    res = res .. bib_options
-  end
-
-  if params.bibstart then
-    res = res .. params.bibstart
-  end
-
-  for _, bib_item in ipairs(bib_items) do
-    res = res .. "\n" .. bib_item
-  end
-
-  if params.bibend then
-    res = res .. "\n" .. params.bibend
-  end
-  return res
-end
-
-
-return core

Deleted: trunk/Master/texmf-dist/scripts/citation-style-language/csl.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/csl.lua	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/csl.lua	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,143 +0,0 @@
---
--- Copyright (c) 2021-2022 Zeping Lee
--- Released under the MIT license.
--- Repository: https://github.com/zepinglee/citeproc-lua
---
-
-local csl = {}
-
-local citeproc = require("citeproc")
-local util = citeproc.util
-require("lualibs")
-local core = require("csl-core")
-
-
-csl.initialized = "false"
-csl.citations = {}
-csl.citations_pre = {}
-
-
-function csl.error(str)
-  luatexbase.module_error("csl", str)
-end
-function csl.warning(str)
-  luatexbase.module_warning("csl", str)
-end
-function csl.info(str)
-  luatexbase.module_info("csl", str)
-end
-
-
-function csl.init(style_name, bib_files, lang)
-  bib_files = util.split(util.strip(bib_files), "%s*,%s*")
-
-  csl.engine = core.init(style_name, bib_files, lang)
-
-  if csl.engine then
-    csl.initialized = "true"
-  else
-    return
-  end
-
-  -- csl.init is called via \AtBeginDocument and it's executed after
-  -- loading .aux file.  The csl.ids are already registered.
-  csl.citation_strings = core.process_citations(csl.engine, csl.citations)
-  csl.style_class = csl.engine:get_style_class()
-
-  for _, citation in ipairs(csl.citations) do
-    local citation_id = citation.citationID
-    local citation_str = csl.citation_strings[citation_id]
-    local bibcite_command = string.format("\\bibcite{%s}{{%s}{%s}}", citation.citationID, csl.style_class, citation_str)
-    tex.sprint(bibcite_command)
-  end
-
-end
-
-
-function csl.register_citation_info(citation_info)
-  local citation = core.make_citation(citation_info)
-  table.insert(csl.citations, citation)
-end
-
-
-function csl.enable_linking()
-  csl.engine:enable_linking()
-end
-
-
-function csl.cite(citation_info)
-  if not csl.engine then
-    csl.error("CSL engine is not initialized.")
-  end
-
-  local citation = core.make_citation(citation_info)
-
-  local res = csl.engine:processCitationCluster(citation, csl.citations_pre, {})
-
-  local citation_str
-  for _, citation_res in ipairs(res[2]) do
-    local citation_id = citation_res[3]
-    -- csl.citation_strings[citation_id] = citation_res[2]
-    if citation_id == citation.citationID then
-      citation_str = citation_res[2]
-    end
-  end
-  tex.sprint(citation_str)
-
-  table.insert(csl.citations_pre, {citation.citationID, citation.properties.noteIndex})
-end
-
-
-function csl.nocite(ids_string)
-  local cite_ids = util.split(ids_string, "%s*,%s*")
-  if csl.engine then
-    local ids = {}
-    for _, cite_id in ipairs(cite_ids) do
-      if cite_id == "*" then
-        for item_id, _ in pairs(core.bib) do
-          table.insert(ids, item_id)
-        end
-      else
-        table.insert(ids, cite_id)
-      end
-    end
-    csl.engine:updateUncitedItems(ids)
-  else
-    -- `\nocite` in preamble, where csl.engine is not initialized yet
-    for _, cite_id in ipairs(cite_ids) do
-      if cite_id == "*" then
-        core.uncite_all_items = true
-      else
-        if not core.loaded_ids[cite_id] then
-          table.insert(core.ids, cite_id)
-          core.loaded_ids[cite_id] = true
-        end
-      end
-    end
-  end
-end
-
-
-function csl.bibliography()
-  if not csl.engine then
-    csl.error("CSL engine is not initialized.")
-    return
-  end
-
-  -- if csl.include_all_items then
-  --   for id, _ in pairs(csl.bib) do
-  --     if not csl.loaded_ids[id] then
-  --       table.insert(csl.ids, id)
-  --       csl.loaded_ids[id] = true
-  --     end
-  --   end
-  -- end
-  -- csl.engine:updateItems(csl.ids)
-
-  local result = core.make_bibliography(csl.engine)
-
-  tex.print(util.split(result, "\n"))
-end
-
-
-return csl

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty	2022-08-18 23:08:31 UTC (rev 64143)
@@ -9,9 +9,10 @@
 \RequirePackage{expl3}
 \RequirePackage{xparse}
 
-\ProvidesExplPackage {citation-style-language} {2022-01-22} {v0.1.0}
+\ProvidesExplPackage {citation-style-language} {2022-08-18} {v0.2.0}
   {Citation Style Language for LaTeX}
 
+\RequirePackage { l3keys2e }
 \RequirePackage { filehook }
 \RequirePackage { url }
 
@@ -34,7 +35,7 @@
   \__csl_load_check:n
 
 \sys_if_engine_luatex:T
-  { \lua_now:n { csl = require("csl") } }
+  { \lua_now:n { csl = require("citeproc-latex") } }
 
 
 \NewDocumentCommand \cslsetup { m }
@@ -82,8 +83,11 @@
   }
 
 
+% Appends the cite key into \l__csl_cite_keys_seq and cite-items into
+% \l__csl_cite_items_seq
+% #1, #2: prenote/postnote
+% #3: keys
 \cs_new:Npn \__csl_process_cite_items:nnn #1#2#3
-  % #1,#2: prenote/postnote, #3: keys
   {
     \tl_if_novalue:nTF {#2}
       {
@@ -120,18 +124,20 @@
   {
     \prop_clear:N \l__csl_cite_item_prop
     \prop_put:Nnn \l__csl_cite_item_prop { id } {#3}
+    % \bool_if:T \l__csl_suppress_author_bool
+    %   { \prop_put:Nnn \l__csl_cite_item_prop { suppress-author } { true } }
+    % \bool_if:T \l__csl_author_only_bool
+    %   { \prop_put:Nnn \l__csl_cite_item_prop { author-only } { true } }
     \tl_if_empty:nF {#1}
       {
         \tl_set:Nn \l__csl_prefix_tl {#1}
         \tl_put_right:NV \l__csl_prefix_tl \l__csl_prefix_separator_tl
-        \prop_put:NnV \l__csl_cite_item_prop { prefix } { \l__csl_prefix_tl }
+        \prop_put:NnV \l__csl_cite_item_prop { prefix } \l__csl_prefix_tl
       }
-    \tl_if_in:nnTF {#2} { = }
+    \tl_if_empty:nF {#2}
       {
-        \keys_set:nn { csl / cite-item } {#2}
-      }
-      {
-        \tl_if_empty:nF {#2}
+        \tl_if_in:nnTF {#2} { = }
+          { \keys_set:nn { csl / cite-item } {#2} }
           {
             \regex_match:nnTF { \d+ } {#2}
               { \__csl_set_locator:nn { page } {#2} }
@@ -138,7 +144,7 @@
               {
                 \tl_set:Nn \l__csl_suffix_tl {#2}
                 \tl_put_left:NV \l__csl_suffix_tl \l__csl_suffix_separator_tl
-                \prop_put:NnV \l__csl_cite_item_prop { suffix } { \l__csl_suffix_tl }
+                \prop_put:NnV \l__csl_cite_item_prop { suffix } \l__csl_suffix_tl
               }
           }
       }
@@ -202,20 +208,26 @@
 \tl_new:N \l__csl_citation_info_tl
 \tl_new:N \l__csl_citation_tl
 \prop_new:N \g__csl_citations_prop
+\prop_clear:N \l__csl_citation_prop
 
 \cs_new:Npn \__csl_make_citation:NN #1#2
   % #1: \l__csl_cite_keys_seq
   % #2: \l__csl_cite_items_seq
   {
+    \prop_clear:N \l__csl_citation_prop
     \__csl_process_citation_id:NN \l__csl_citation_id_tl #1
+    \prop_put:NnV \l__csl_citation_prop { citationID } \l__csl_citation_id_tl
     \tl_set:Nx \l__csl_cite_items_tl
       { \seq_use:Nn #2 { , } }
-    \exp_args:NNV \__csl_process_note_index:Nn \l__csl_note_index_tl \l__csl_citation_id_tl
-    \tl_set:Nx \l__csl_citation_info_tl
+    \prop_put:NnV \l__csl_citation_prop { citationItems } \l__csl_cite_items_tl
+    \__csl_get_note_index:N \l__csl_note_index_tl
+    \prop_put:Nnx \l__csl_citation_prop { properties } { noteIndex = { \l__csl_note_index_tl } }
+    \tl_clear:N \l__csl_citation_info_tl
+    \prop_map_inline:Nn \l__csl_citation_prop
       {
-        { \l__csl_citation_id_tl }
-        { \l__csl_cite_items_tl }
-        { \l__csl_note_index_tl }
+        \tl_if_empty:NF \l__csl_citation_info_tl
+          { \tl_put_right:Nn \l__csl_citation_info_tl { , } }
+        \tl_put_right:Nn \l__csl_citation_info_tl { ##1 = { ##2 } }
       }
     \exp_args:NV \__csl_write_aux_citation:n \l__csl_citation_info_tl
     \prop_get:NVNTF \g__csl_citations_prop \l__csl_citation_id_tl
@@ -224,8 +236,9 @@
       {
         \bool_if:NTF \l__csl_engine_initialized_bool
           {
-            \tl_set:Nx \l__csl_citation_tl
+            \tl_set:Nf \l__csl_citation_tl
               { \lua_now:e { csl.cite("\l__csl_citation_info_tl") } }
+            \exp_args:NV \__csl_print_citation:n \l__csl_citation_tl
           }
           {
             \exp_args:Nx \__csl_print_undefined_citation:n
@@ -261,26 +274,18 @@
   }
 
 
-\int_new:N \l__csl_note_index_int
-\int_gzero_new:N \g__csl_last_note_index_int
-\int_gzero_new:N \g__csl_citation_note_count_int
-
-\cs_new:Npn \__csl_process_note_index:Nn #1#2
-  % #1 \l__csl_note_index_tl
-  % #2: citationID
+% Save the note number to \l__csl_note_index_tl
+% TODO: multiple citations in a note
+\cs_new:Npn \__csl_get_note_index:N #1
+  % #1: \l__csl_note_index_tl
   {
-    \int_set_eq:Nc \l__csl_note_index_int { c@ \@mpfn }
-    \prop_if_in:NnTF \g__csl_citations_prop {#2}
-      { \int_incr:N \l__csl_note_index_int }
-      { \int_gincr:N \g__csl_citation_note_count_int }
-    \int_add:Nn \l__csl_note_index_int { \g__csl_citation_note_count_int }
-    \tl_set:Nx #1 { \int_use:N \l__csl_note_index_int }
-    \int_gset_eq:NN \g__csl_last_note_index_int \l__csl_note_index_int
-    \int_compare:nT { \l__csl_note_index_int < \g__csl_last_note_index_int }
+    \bool_if:NTF \l__csl_note_style_bool
       {
-        \int_gzero:N \g__csl_last_note_index_int
-        \int_gzero:N \g__csl_citation_note_count_int
+        \int_set_eq:Nc \l_tmpa_int { c@ \@mpfn }
+        \int_incr:N \l_tmpa_int
+        \tl_set:Nx #1 { \int_use:N \l_tmpa_int }
       }
+      { \tl_set:Nn #1 { 0 } }
   }
 
 
@@ -294,6 +299,16 @@
   }
 
 
+% \cs_new:Npn \__csl_print_citation:n #1
+%   % #1: citation text
+%   {
+%     \bool_if:NTF \l__csl_note_style_bool
+%       {
+%         \footnote {#1}
+%       }
+%       {#1}
+%   }
+
 \cs_new:Npn \__csl_print_citation:n #1
   % #1: "{<type>}{<citation text>}"
   { \__csl_print_citation_aux:nn #1 }
@@ -330,17 +345,36 @@
 
 \cs_new:Npn \__csl_no_cite:n #1
   {
-    \__csl_if_preamble:TF
+    \seq_clear:N \l__csl_cite_keys_seq
+    \seq_clear:N \l__csl_cite_items_seq
+    \__csl_process_cite_items:nnn { } { } {#1}
+    \tl_set:Nx \l__csl_cite_items_tl
+      { \seq_use:Nn \l__csl_cite_items_seq { , } }
+    \tl_set:Nx \l__csl_citation_info_tl
       {
-        \AtBeginDocument
-          { \__csl_write_aux_citation:n { { nocite } {#1} { } } }
+        citationID    = { @nocite } ,
+        citationItems = { \l__csl_cite_items_tl } ,
+        properties    = { noteIndex = { 0 } }
       }
-      { \__csl_write_aux_citation:n { { nocite } {#1} { } } }
+    \exp_args:NV \__csl_no_cite_write_aux:n \l__csl_citation_info_tl
     \sys_if_engine_luatex:T
       { \lua_now:n { csl.nocite("#1") } }
 }
 
 
+\cs_new:Npn \__csl_no_cite_write_aux:n #1
+  {
+    \__csl_if_preamble:TF
+      {
+        \AtBeginDocument
+          { \exp_args:Nx \__csl_write_aux_citation:n { #1 } }
+      }
+      {
+        \exp_args:Nx \__csl_write_aux_citation:n { #1 }
+      }
+  }
+
+
 \prg_new_conditional:Nnn \__csl_if_preamble: { T , F , TF }
   {
     \if_meaning:w \@begindocumenthook \@undefined
@@ -352,6 +386,7 @@
 
 
 % Used in aux files to register cite items.
+% #1: a citation object
 \cs_set:Npn \citation #1
   {
     \sys_if_engine_luatex:T
@@ -362,9 +397,9 @@
 \cs_new:Npn \cslcite #1#2
   {
     \bibcite {#1} {#2}
-    \if at filesw
-      \iow_now:Nn \@auxout { \bibcite {#1} {#2} }
-    \fi
+    % \if at filesw
+    %   \iow_now:Nn \@auxout { \bibcite {#1} {#2} }
+    % \fi
   }
 
 
@@ -444,6 +479,8 @@
     entry-spacing = { 1 } ,
   }
 
+\ProcessKeysPackageOptions { csl }
+
 \bool_new:N \l__csl_engine_initialized_bool
 
 \prop_set_from_keyval:Nn \l__csl_babel_locale_mapping_prop
@@ -590,6 +627,7 @@
           }
         \str_if_eq:eeT { \lua_now:n { tex.print(csl.initialized) } } { true }
           { \bool_set_true:N \l__csl_engine_initialized_bool }
+        \__csl_get_style_class:
         \@ifpackageloaded { hyperref }
           { \lua_now:n { csl.enable_linking() } }
           { }
@@ -614,6 +652,21 @@
     \fi
   }
 
+
+\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: {
+  \bool_if:NT \l__csl_engine_initialized_bool
+    {
+      \str_set:Nx \l__csl_style_class_str { \lua_now:n { csl.get_style_class() } }
+      \str_if_eq:VnT \l__csl_style_class_str { note }
+        { \bool_set_true:N \l__csl_note_style_bool }
+    }
+}
+
+
 \clist_new:N \l__csl_options_clist
 
 \cs_new:Npn \__csl_write_aux_csl_options:
@@ -972,6 +1025,19 @@
   }
 
 
+% csquotes
+
+\AtEndOfPackageFile* { csquotes }
+  {
+    \BlockquoteDisable
+      {
+        \cs_set_eq:NN \__csl_make_citation:NN \use_none:nn
+        % \sys_if_engine_luatex:T
+        %   { \lua_now:n { csl.preview_mode = true } }
+      }
+  }
+
+
 % hyperref
 
 % The hyperref package also patches \bibcite but it cannot provide hyperlinks

Deleted: trunk/Master/texmf-dist/tex/latex/citation-style-language/citeproc-bib-data.json
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/citeproc-bib-data.json	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/citeproc-bib-data.json	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,2349 +0,0 @@
-{
-    "description": "Bib CSL mapping",
-    "types": {
-        "archival": {
-            "csl": "collection",
-            "source": "jurabib.bst"
-        },
-        "archive": {
-            "csl": "collection",
-            "notes": "New in CSL v1.0.2.",
-            "source": "gbt7714-numerical.bst"
-        },
-        "article": {
-            "csl": "article-journal",
-            "notes": "May also be `article-magazine` or `article-newspaper` depending upon the field `entrysubtype`.",
-            "source": "bibtex"
-        },
-        "artifactdataset": {
-            "csl": "dataset",
-            "source": "ACM-Reference-Format.bst"
-        },
-        "artifactsoftware": {
-            "csl": "software",
-            "source": "ACM-Reference-Format.bst"
-        },
-        "artwork": {
-            "csl": "graphic",
-            "source": "biblatex"
-        },
-        "atlas": {
-            "csl": null,
-            "source": "ametsoc2014.bst"
-        },
-        "audio": {
-            "csl": "song",
-            "notes": "CSL's `song` can be used for any audio recording (not only music).",
-            "source": "biblatex"
-        },
-        "bachelor": {
-            "csl": "thesis",
-            "source": "dlfltxbbibtex.bst"
-        },
-        "bibnote": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex"
-        },
-        "book": {
-            "csl": "book",
-            "source": "bibtex"
-        },
-        "bookinbook": {
-            "csl": "chapter",
-            "source": "biblatex"
-        },
-        "booklet": {
-            "csl": "pamphlet",
-            "source": "bibtex"
-        },
-        "brochure": {
-            "csl": "pamphlet",
-            "source": "thesnumb.bst"
-        },
-        "cconference": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "collection": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "comment": {
-            "csl": null,
-            "notes": "Special entry type for Scribe compatibility",
-            "source": "bibtex"
-        },
-        "commentary": {
-            "csl": "book",
-            "notes": "Not supported.",
-            "source": "biblatex"
-        },
-        "commented": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "conference": {
-            "alias": "inproceedings",
-            "csl": "paper-conference",
-            "source": "bibtex"
-        },
-        "ctan": {
-            "csl": "software",
-            "source": "tugboat.bst"
-        },
-        "customa": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "customb": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "customc": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "customd": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "custome": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "customf": {
-            "csl": null,
-            "source": "biblatex"
-        },
-        "database": {
-            "csl": "dataset",
-            "source": "gbt7714-numerical.bst"
-        },
-        "dataset": {
-            "csl": "dataset",
-            "source": "biblatex"
-        },
-        "dictionary": {
-            "csl": "book",
-            "source": "vancouver.bst"
-        },
-        "docthesis": {
-            "csl": "thesis",
-            "source": "gost2003.bst"
-        },
-        "electronic": {
-            "alias": "online",
-            "csl": "webpage",
-            "source": "biblatex"
-        },
-        "eulegislation": {
-            "csl": "legislation",
-            "source": "bath.bst"
-        },
-        "footnote": {
-            "csl": null,
-            "source": "apsrev4-2.bst"
-        },
-        "game": {
-            "csl": "software",
-            "source": "ACM-Reference-Format.bst"
-        },
-        "govpub": {
-            "csl": "regulation",
-            "source": "thesnumb.bst"
-        },
-        "habthesis": {
-            "csl": "thesis",
-            "source": "bestpapers-export.bst"
-        },
-        "heading": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "hidden": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "image": {
-            "csl": "graphic",
-            "source": "biblatex"
-        },
-        "inbook": {
-            "csl": "chapter",
-            "source": "bibtex"
-        },
-        "incollection": {
-            "csl": "chapter",
-            "source": "bibtex"
-        },
-        "inloosecollection": {
-            "csl": "chapter",
-            "source": "resphilosophica.bst"
-        },
-        "inpress": {
-            "csl": "article",
-            "notes": "Use for preprints, working papers, and similar works posted on a platform where some level of persistence or stewardship is expected (e.g. arXiv or other preprint repositories, working paper series).",
-            "source": "bjnano.bst"
-        },
-        "inproceedings": {
-            "csl": "paper-conference",
-            "source": "bibtex"
-        },
-        "inreference": {
-            "csl": "entry",
-            "notes": "May also be `entry`, `entry-dictionary` or `entry-encyclopedia`.",
-            "source": "biblatex"
-        },
-        "inserialcollection": {
-            "csl": null,
-            "source": "asmejour.bst"
-        },
-        "internet": {
-            "csl": "webpage",
-            "source": "IEEEtran.bst"
-        },
-        "journalpart": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "journals": {
-            "csl": "periodical",
-            "source": "bestpapers-export.bst"
-        },
-        "jurisdiction": {
-            "csl": null,
-            "notes": "\"Court decisions, court recordings, and similar things.\"",
-            "source": "biblatex"
-        },
-        "jurthesis": {
-            "csl": "thesis",
-            "source": "jurabib.bst"
-        },
-        "legal": {
-            "csl": "treaty",
-            "notes": "\"Legal documents such as treaties.\"",
-            "source": "biblatex"
-        },
-        "legislation": {
-            "csl": "legislation",
-            "notes": "\"Laws, bills, legislative proposals, and similar things.\" May also be `bill`",
-            "source": "biblatex"
-        },
-        "letter": {
-            "csl": "personal_communication",
-            "source": "biblatex"
-        },
-        "lexicon": {
-            "csl": "book",
-            "source": "jurabib.bst"
-        },
-        "majorthesis": {
-            "csl": "thesis",
-            "source": "achicago.bst"
-        },
-        "manual": {
-            "csl": "report",
-            "nots": "CSL's `report` is also used for manuals and similar technical documentation (e.g. a software, instrument, or test manual).",
-            "source": "bibtex"
-        },
-        "map": {
-            "csl": "map",
-            "source": "vancouver.bst"
-        },
-        "mastersthesis": {
-            "alias": "thesis",
-            "csl": "thesis",
-            "source": "bibtex"
-        },
-        "masterthesis": {
-            "csl": "thesis",
-            "source": "asaetr.bst"
-        },
-        "minorthesis": {
-            "csl": "thesis",
-            "source": "achicago.bst"
-        },
-        "misc": {
-            "csl": "document",
-            "notes": "New in CSL v1.0.2.",
-            "source": "bibtex"
-        },
-        "monograph": {
-            "csl": "book",
-            "source": "gbt7714-numerical.bst"
-        },
-        "monography": {
-            "csl": "book",
-            "source": "abntex2-alf.bst"
-        },
-        "movie": {
-            "csl": "motion_picture",
-            "source": "biblatex"
-        },
-        "music": {
-            "csl": "song",
-            "source": "biblatex"
-        },
-        "mvbook": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "mvcollection": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "mvproceedings": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "mvreference": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "news": {
-            "csl": "article-newspaper",
-            "source": "seuthesix.bst"
-        },
-        "newspaper": {
-            "csl": "article-newspaper",
-            "source": "gbt7714-numerical.bst"
-        },
-        "online": {
-            "csl": "webpage",
-            "source": "biblatex"
-        },
-        "other": {
-            "csl": null,
-            "source": "bjnano.bst"
-        },
-        "patent": {
-            "csl": "patent",
-            "source": "biblatex"
-        },
-        "performance": {
-            "csl": "performance",
-            "notes": "New in CSL v1.0.2.",
-            "source": "biblatex"
-        },
-        "periodical": {
-            "csl": "periodical",
-            "notes": "New in CSL v1.0.2.",
-            "source": "biblatex"
-        },
-        "phdthesis": {
-            "alias": "thesis",
-            "csl": "thesis",
-            "source": "bibtex"
-        },
-        "preamble": {
-            "csl": null,
-            "notes": "Special entry type for inserting commands or text in the bbl",
-            "source": "bibtex"
-        },
-        "preprint": {
-            "csl": "article",
-            "notes": "Use for preprints, working papers, and similar works posted on a platform where some level of persistence or stewardship is expected (e.g. arXiv or other preprint repositories, working paper series).",
-            "source": "gbt7714-numerical.bst"
-        },
-        "presentation": {
-            "csl": "speech",
-            "notes": "A speech or other presentation (e.g. a paper, talk, poster, or symposium at a conference).",
-            "source": "apsrev4-2.bst"
-        },
-        "proceedings": {
-            "csl": "book",
-            "source": "bibtex"
-        },
-        "program": {
-            "csl": "software",
-            "source": "ChemCommun.bst"
-        },
-        "reference": {
-            "csl": "book",
-            "source": "biblatex"
-        },
-        "report": {
-            "csl": "report",
-            "source": "biblatex"
-        },
-        "review": {
-            "csl": "review",
-            "notes": "\"A more specific variant of the `@article` type\"",
-            "source": "biblatex"
-        },
-        "set": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex"
-        },
-        "setup": {
-            "csl": null,
-            "source": "bestpapers-export.bst"
-        },
-        "software": {
-            "csl": "software",
-            "notes": "New in CSL v1.0.2.",
-            "source": "biblatex"
-        },
-        "standard": {
-            "csl": "standard",
-            "notes": "New in CSL v1.0.2.",
-            "source": "biblatex"
-        },
-        "string": {
-            "csl": null,
-            "notes": "Special entry type for defining abbreviations",
-            "source": "bibtex"
-        },
-        "suppbook": {
-            "csl": "chapter",
-            "notes": "lossy mapping; \"Supplemental material in a `@book`. This type is closely related to the @inbook entry type. While `@inbook` is primarily intended for a part of a book with its own title (e. g., a single essay in a collection of essays by the same author), this type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only. Style guides may require such items to be formatted differently from other `@inbook` items.\"",
-            "source": "biblatex"
-        },
-        "suppcollection": {
-            "csl": "chapter",
-            "notes": "lossy mapping; see `suppbook`",
-            "source": "biblatex"
-        },
-        "suppperiodical": {
-            "csl": "article",
-            "notes": "see `article`",
-            "source": "biblatex"
-        },
-        "techreport": {
-            "alias": "report",
-            "csl": "report",
-            "source": "bibtex"
-        },
-        "techstandard": {
-            "csl": "standard",
-            "notes": "New in CSL v1.0.2.",
-            "source": "udesoftec.bst"
-        },
-        "thesis": {
-            "csl": "thesis",
-            "source": "biblatex"
-        },
-        "uklegislation": {
-            "csl": "legislation",
-            "source": "bath.bst"
-        },
-        "unpublished": {
-            "csl": "manuscript",
-            "notes": "For unpublished works not made widely available or only hosted on personal websites, use manuscript",
-            "source": "bibtex"
-        },
-        "video": {
-            "csl": "motion_picture",
-            "source": "biblatex"
-        },
-        "webpage": {
-            "csl": "webpage",
-            "source": "IEEEtran.bst"
-        },
-        "www": {
-            "alias": "online",
-            "csl": "webpage",
-            "source": "biblatex"
-        },
-        "xdata": {
-            "csl": null,
-            "notes": "special item type: \"`@xdata` entries hold data which may be inherited by other entries using the xdata field. Entries of this type only serve as data containers; they may not be cited or added to the bibliography.\"",
-            "source": "biblatex"
-        }
-    },
-    "fields": {
-        "abstract": {
-            "csl": "abstract",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "account": {
-            "csl": null,
-            "source": "address-html.bst"
-        },
-        "acronym": {
-            "csl": null,
-            "source": "export.bst"
-        },
-        "add": {
-            "csl": null,
-            "source": "figbib.bst"
-        },
-        "add1": {
-            "csl": null,
-            "source": "figbib1.bst"
-        },
-        "addendum": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "address": {
-            "alias": "location",
-            "csl": "publisher-place",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "adsurl": {
-            "csl": null,
-            "source": "mnras.bst"
-        },
-        "advisor": {
-            "csl": null,
-            "source": "ACM-Reference-Format.bst"
-        },
-        "afterword": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "name"
-        },
-        "annotate": {
-            "csl": "note",
-            "source": "apacite.bst"
-        },
-        "annotation": {
-            "csl": "note",
-            "notes": "Descriptive text or notes about an item (e.g. in an annotated bibliography)",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "annotator": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "annote": {
-            "alias": "annotation",
-            "csl": "note",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "annotelanguage": {
-            "csl": null,
-            "source": "bababbr3-fl.bst"
-        },
-        "applicant": {
-            "csl": null,
-            "source": "seuthesix.bst"
-        },
-        "archive": {
-            "csl": "archive",
-            "source": "apsrev4-2.bst"
-        },
-        "archiveprefix": {
-            "alias": "eprinttype",
-            "csl": "archive",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "archname": {
-            "csl": "archive",
-            "source": "jurabib.bst"
-        },
-        "articleno": {
-            "csl": null,
-            "source": "ACM-Reference-Format.bst"
-        },
-        "arxiv": {
-            "csl": null,
-            "source": "aomalpha.bst"
-        },
-        "assignee": {
-            "csl": null,
-            "source": "vancouver.bst"
-        },
-        "author": {
-            "csl": "author",
-            "source": "bibtex",
-            "type": "name"
-        },
-        "authorcountry": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "authorfa": {
-            "csl": null,
-            "source": "asa-fa.bst"
-        },
-        "authortype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "bibsource": {
-            "csl": null,
-            "source": "export.bst"
-        },
-        "biburl": {
-            "csl": null,
-            "source": "export.bst"
-        },
-        "binding": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "birthday": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "birthyear": {
-            "csl": null,
-            "source": "address-html.bst"
-        },
-        "bookaddress": {
-            "csl": null,
-            "source": "apsrev4-2.bst"
-        },
-        "bookauthor": {
-            "csl": "container-author",
-            "source": "biblatex",
-            "type": "name"
-        },
-        "booklanguage": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "bookpages": {
-            "csl": null,
-            "source": "ACM-Reference-Format.bst"
-        },
-        "bookpagination": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "booksubtitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "booktitle": {
-            "csl": "container-title",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "booktitleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "caption": {
-            "csl": null,
-            "source": "figbib.bst"
-        },
-        "caption1": {
-            "csl": null,
-            "source": "figbib1.bst"
-        },
-        "cartographer": {
-            "csl": null,
-            "source": "vancouver.bst"
-        },
-        "casenumber": {
-            "csl": null,
-            "source": "bath.bst"
-        },
-        "category": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "cellular": {
-            "csl": null,
-            "source": "address-html.bst"
-        },
-        "chair": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "chapter": {
-            "csl": "chapter-number",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "citedate": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "city": {
-            "csl": null,
-            "source": "ACM-Reference-Format.bst"
-        },
-        "coden": {
-            "csl": null,
-            "source": "is-abbrv.bst"
-        },
-        "collaboration": {
-            "csl": null,
-            "source": "apsrev4-2.bst"
-        },
-        "collator": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "commentator": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "compiler": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "condition": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "conference-location": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "conference-number": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "conference-year": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "copy": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "country": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "credits": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "crossref": {
-            "csl": null,
-            "notes": "Inherits data from a parent entry.",
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "ctrl-article-title": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "ctrl-chapter-title": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "ctrl-doi": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "ctrl-etal-firstonly": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "ctrl-etal-number": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "ctrl-link-doi": {
-            "csl": null,
-            "source": "angew.bst"
-        },
-        "ctrl-use-doi-all": {
-            "csl": null,
-            "source": "angew.bst"
-        },
-        "ctrl-use-title": {
-            "csl": null,
-            "source": "achemso.bst"
-        },
-        "dataset": {
-            "csl": null,
-            "source": "apalike-ejor.bst"
-        },
-        "date": {
-            "csl": "issued",
-            "source": "biblatex",
-            "type": "date"
-        },
-        "day": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "dayfiled": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "definition": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "department": {
-            "csl": null,
-            "source": "ametsoc2014.bst"
-        },
-        "description": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "designator": {
-            "csl": null,
-            "source": "thesnumb.bst"
-        },
-        "dimensions": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "dissyear": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "doi": {
-            "csl": "DOI",
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "dticnumber": {
-            "csl": null,
-            "source": "thesnumb.bst"
-        },
-        "dummy": {
-            "csl": null,
-            "source": "expcites.bst"
-        },
-        "edition": {
-            "csl": "edition",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "editor": {
-            "csl": "editor",
-            "source": "bibtex",
-            "type": "name"
-        },
-        "editora": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "editoratype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "editorb": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "editorbtype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "editorc": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "editorctype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "editortype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "eid": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "email": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "endnumber": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "endvolume": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "endyear": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "englishtitle": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "entryset": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "entrysubtype": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "epilog": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "eprint": {
-            "csl": null,
-            "notes": "Mapped to `PMID` if `eprinttype` is \"PubMed\".",
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "eprintclass": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "eprints": {
-            "csl": null,
-            "source": "databib.bst"
-        },
-        "eprinttype": {
-            "csl": "archive",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "erratumeid": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "erratumgermanpages": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "erratumnumpages": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "erratumpages": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "erratumvolume": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "erratumyear": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "eventdate": {
-            "csl": "event-date",
-            "source": "biblatex",
-            "type": "date"
-        },
-        "eventtitle": {
-            "csl": "event-title",
-            "notes": "Supercedes `event` in CSL v1.0.2.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "eventtitleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "eventyear": {
-            "csl": null,
-            "source": "bath.bst"
-        },
-        "execute": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "code"
-        },
-        "faddress": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "fakeset": {
-            "csl": null,
-            "source": "biblatex.bst"
-        },
-        "fax": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "file": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "firstkey": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "fjournal": {
-            "csl": null,
-            "source": "ijmart.bst"
-        },
-        "flanguage": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "font": {
-            "csl": null,
-            "source": "nederlands.bst"
-        },
-        "foreword": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "founder": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "fpublisher": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "ftitle": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "furtherresp": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "fyear": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "gender": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "germanpages": {
-            "csl": null,
-            "source": "ChemCommun.bst"
-        },
-        "group": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "heading": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "hereafter": {
-            "csl": null,
-            "source": "opcit.bst"
-        },
-        "holder": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "howcited": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "howpublished": {
-            "csl": null,
-            "notes": "Check if a URL is contained.",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "hyphenation": {
-            "alias": "langid",
-            "csl": "language",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "ids": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "illustrated": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "illustrations": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "illustrator": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "indexsorttitle": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "indextitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "institution": {
-            "csl": "publisher",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "introduction": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "intype": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "inventor": {
-            "csl": null,
-            "source": "vancouver.bst"
-        },
-        "ipc": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "isan": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "isbn": {
-            "csl": "ISBN",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "ismn": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "iso-abbreviation": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "iso-author-punctuation": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "iso-date-place": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "isrn": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "issn": {
-            "csl": "ISSN",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "issue": {
-            "csl": "issue",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "issuesubtitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "issuetitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "issuetitleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "iswc": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "jfmnumber": {
-            "csl": null,
-            "source": "aomalpha.bst"
-        },
-        "journal": {
-            "alias": "journaltitle",
-            "csl": "container-title",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "journalsubtitle": {
-            "csl": null,
-            "notes": "It should be concatenated to the `container-title`.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "journaltie": {
-            "csl": null,
-            "source": "tugboat.bst"
-        },
-        "journaltitle": {
-            "csl": "container-title",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "journaltitleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "juraauthor": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "juratitle": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "key": {
-            "alias": "sortkey",
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "keywords": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "keyword"
-        },
-        "label": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "lang": {
-            "csl": null,
-            "source": "hustthesis.bst"
-        },
-        "langid": {
-            "csl": "language",
-            "notes": "The language id of the bibliography entry. The identifier must be a language name known to the babel/polyglossia packages. It should be converted to ISO 639-1 language code in CSL.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "langidopts": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "language": {
-            "csl": null,
-            "notes": "The `language` field in `biblatex` has no special internal meaning thus it is used only when langid is missing.",
-            "source": "biblatex",
-            "type": "key"
-        },
-        "lastaccessed": {
-            "csl": "accessed",
-            "source": "ACM-Reference-Format.bst"
-        },
-        "lastchecked": {
-            "csl": "accessed",
-            "source": "apacite.bst"
-        },
-        "lccn": {
-            "csl": null,
-            "source": "is-abbrv.bst"
-        },
-        "library": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "lista": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "listb": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "listc": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "listd": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "liste": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "listf": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "littype": {
-            "csl": null,
-            "source": "seuthesix.bst"
-        },
-        "location": {
-            "csl": "publisher-place",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "madadurltest": {
-            "csl": null,
-            "source": "alpha-persian.bst"
-        },
-        "main": {
-            "csl": null,
-            "source": "figbib.bst"
-        },
-        "main1": {
-            "csl": null,
-            "source": "figbib1.bst"
-        },
-        "mainsubtitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "maintitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "maintitleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "majorcode": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "marginnote": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "mark": {
-            "csl": null,
-            "source": "gbt7714-numerical.bst"
-        },
-        "max.best.papers": {
-            "csl": null,
-            "source": "bestpapers-export.bst"
-        },
-        "mcitetail": {
-            "csl": null,
-            "source": "apsrmpM.bst"
-        },
-        "media": {
-            "csl": "medium",
-            "source": "gost2003.bst"
-        },
-        "medium": {
-            "csl": "medium",
-            "source": "gbt7714-numerical.bst"
-        },
-        "meeting": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "misctitle": {
-            "csl": null,
-            "source": "aasjournal.bst"
-        },
-        "miscyear": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "mobile": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "modifydate": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "month": {
-            "csl": null,
-            "notes": "Used only when `date` is empty.",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "monthfiled": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "mrnumber": {
-            "csl": null,
-            "source": "amsplain.bst"
-        },
-        "name": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "namea": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "nameaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "nameatype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "nameb": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "namebtype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "namec": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "namectype": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "nationality": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "nbirthday": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "newpage": {
-            "csl": null,
-            "source": "tugboat.bst"
-        },
-        "newspaper": {
-            "csl": "container-title",
-            "source": "seuthesix.bst"
-        },
-        "nickname": {
-            "csl": null,
-            "source": "address-html.bst"
-        },
-        "nihms": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "noed": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "normal": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "normalauthor": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "normaleditor": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "note": {
-            "csl": "note",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "nowarning": {
-            "csl": null,
-            "source": "tugboat.bst"
-        },
-        "number": {
-            "csl": "number",
-            "notes": "It is mapped to `issue` in `@ariticle` but to `number` in `@patent` or `@report`.",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "numpages": {
-            "csl": null,
-            "source": "ACM-Reference-Format.bst"
-        },
-        "oaddress": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "options": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "option"
-        },
-        "opublisher": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "org-short": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "organization": {
-            "csl": "publisher",
-            "notes": "It is mapped to `author` (in `institution` property) if possible.",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "origdate": {
-            "csl": "original-date",
-            "source": "biblatex",
-            "type": "date"
-        },
-        "originaladdress": {
-            "csl": "original-publisher-place",
-            "source": "apacite.bst"
-        },
-        "originalbooktitle": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originaledition": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originaleditor": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originaljournal": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originalnumber": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originalpages": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originalpublisher": {
-            "csl": "original-publisher",
-            "source": "apacite.bst"
-        },
-        "originalvolume": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "originalyear": {
-            "csl": "original-date",
-            "source": "apacite.bst"
-        },
-        "origlanguage": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "origlocation": {
-            "csl": "original-publisher-place",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "origpublisher": {
-            "csl": "original-publisher",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "origtitle": {
-            "csl": "original-title",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "oyear": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "pagename": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "pages": {
-            "csl": "page",
-            "source": "bibtex",
-            "type": "range"
-        },
-        "pagetotal": {
-            "csl": "number-of-pages",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "pagination": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "paper": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "part": {
-            "csl": "part",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "patentid": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "pdf": {
-            "alias": "file",
-            "csl": null,
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "phone": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "pid": {
-            "csl": null,
-            "source": "seuthesix.bst"
-        },
-        "pii": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "pmcid": {
-            "csl": "PMCID",
-            "source": "apacite.bst"
-        },
-        "prebibitem": {
-            "csl": null,
-            "source": "tugboat.bst"
-        },
-        "preface": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "preprint": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "presort": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "price": {
-            "csl": null,
-            "source": "is-abbrv.bst"
-        },
-        "primaryclass": {
-            "alias": "eprintclass",
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "printing": {
-            "csl": null,
-            "source": "dk-abbrv.bst"
-        },
-        "prioritycountry": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "prioritydate": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "prioritynumber": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "pseudonym": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "publication": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "publicationdate": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "publisher": {
-            "csl": "publisher",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "pubmed": {
-            "csl": "PMID",
-            "source": "urlbst"
-        },
-        "pubstate": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "key"
-        },
-        "related": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "relatedoptions": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "option"
-        },
-        "relatedstring": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "relatedtype": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "reprinted-from": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "reprinted-text": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "reprinttitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "requestdate": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "requestnumber": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "responsible": {
-            "csl": null,
-            "source": "rusnat.bst"
-        },
-        "review": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "revision": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "school": {
-            "alias": "institution",
-            "csl": "publisher",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "score": {
-            "csl": null,
-            "source": "bestpapers-export.bst"
-        },
-        "section": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "series": {
-            "csl": "collection-title",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "seriesedition": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "short": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "shortarchive": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "shortauthor": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "shorteditor": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "name"
-        },
-        "shorthand": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "shorthandintro": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "shortjournal": {
-            "csl": "container-title-short",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "shortseries": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "shortsubarchive": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "shorttitle": {
-            "csl": "title-short",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "sig1": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "sig2": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "sig3": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "sig4": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "size": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "slaccitation": {
-            "csl": null,
-            "source": "apsrev4-2.bst"
-        },
-        "sort-short": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "sort-word": {
-            "csl": null,
-            "source": "glsplain.bst"
-        },
-        "sortas": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "sortkey": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "sortname": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "name"
-        },
-        "sortshorthand": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "sorttitle": {
-            "csl": null,
-            "notes": "Not supported.",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "sortyear": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "integer"
-        },
-        "source": {
-            "csl": null,
-            "source": "figbib.bst"
-        },
-        "source1": {
-            "csl": null,
-            "source": "figbib1.bst"
-        },
-        "specialitycode": {
-            "csl": null,
-            "source": "gost2003.bst"
-        },
-        "ssedition": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "standard": {
-            "csl": null,
-            "source": "udesoftec.bst"
-        },
-        "startnumber": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "startvolume": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "startyear": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "state": {
-            "csl": null,
-            "source": "address-html.bst"
-        },
-        "status": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "stdcode": {
-            "csl": null,
-            "source": "seuthesix.bst"
-        },
-        "stitle": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "street": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        },
-        "subarchive": {
-            "csl": null,
-            "source": "jurabib.bst"
-        },
-        "subtitle": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "symposium": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "text": {
-            "csl": null,
-            "source": "apacite.bst"
-        },
-        "timestamp": {
-            "csl": null,
-            "source": "export.bst"
-        },
-        "title": {
-            "csl": "title",
-            "source": "bibtex",
-            "type": "literal"
-        },
-        "titleaddon": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "titlenote": {
-            "csl": null,
-            "source": "aomalpha.bst"
-        },
-        "totalpages": {
-            "csl": null,
-            "source": "aomalpha.bst"
-        },
-        "transissue": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "transjournal": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "translation": {
-            "csl": null,
-            "source": "apsrev4-2.bst"
-        },
-        "translator": {
-            "csl": "translator",
-            "source": "biblatex",
-            "type": "name"
-        },
-        "transnumber": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "transpages": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "transsection": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "transvolume": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "transyear": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "type": {
-            "csl": "genre",
-            "source": "bibtex",
-            "type": "key"
-        },
-        "typeoflit": {
-            "csl": null,
-            "source": "seuthesis.bst"
-        },
-        "umfnumber": {
-            "csl": null,
-            "source": "thesnumb.bst"
-        },
-        "updated": {
-            "csl": null,
-            "source": "vancouver.bst"
-        },
-        "url": {
-            "csl": "URL",
-            "source": "biblatex",
-            "type": "uri"
-        },
-        "urlaccessdate": {
-            "csl": "accessed",
-            "source": "abntex2-alf.bst"
-        },
-        "urldate": {
-            "csl": "accessed",
-            "source": "biblatex",
-            "type": "date"
-        },
-        "urlnewline": {
-            "csl": null,
-            "source": "tugboat.bst"
-        },
-        "urltype": {
-            "csl": null,
-            "source": "asmeconf.bst"
-        },
-        "urlyear": {
-            "csl": null,
-            "source": "bath.bst"
-        },
-        "urn": {
-            "csl": null,
-            "source": "export.bst"
-        },
-        "usera": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "userb": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "userc": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "userd": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "usere": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "userf": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "value": {
-            "csl": null,
-            "source": "bookdb.bst"
-        },
-        "venue": {
-            "csl": "event-place",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "verba": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "verbb": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "verbc": {
-            "csl": null,
-            "source": "biblatex",
-            "type": "verbatim"
-        },
-        "version": {
-            "csl": "version",
-            "source": "biblatex",
-            "type": "literal"
-        },
-        "versiontype": {
-            "csl": null,
-            "source": "asmeconf.bst"
-        },
-        "volformat": {
-            "csl": null,
-            "source": "jox.bst"
-        },
-        "volume": {
-            "csl": "volume",
-            "source": "bibtex",
-            "type": "integer"
-        },
-        "volumes": {
-            "csl": "number-of-volumes",
-            "source": "biblatex",
-            "type": "integer"
-        },
-        "volumetitle": {
-            "csl": null,
-            "source": "iopart-num.bst"
-        },
-        "word": {
-            "csl": null,
-            "source": "vancouver.bst"
-        },
-        "xdata": {
-            "csl": null,
-            "notes": "inherits fields from other items.",
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "xid": {
-            "csl": null,
-            "source": "amsra.bst"
-        },
-        "xref": {
-            "csl": null,
-            "notes": "Establishes a parent-child relationship in biblatex, but without inheriting data => no need to parse this.",
-            "source": "biblatex",
-            "type": "entrykey"
-        },
-        "year": {
-            "csl": null,
-            "notes": "Used only when `date` is empty.",
-            "source": "bibtex",
-            "type": "date"
-        },
-        "year-presented": {
-            "csl": null,
-            "source": "abntex2-alf.bst"
-        },
-        "yearcomp": {
-            "csl": null,
-            "source": "achicago.bst"
-        },
-        "yearfiled": {
-            "csl": null,
-            "source": "IEEEtran.bst"
-        },
-        "zblnumber": {
-            "csl": null,
-            "source": "aomalpha.bst"
-        },
-        "zip": {
-            "csl": null,
-            "source": "adrbirthday.bst"
-        }
-    },
-    "macros": {
-        "jan": {
-            "value": "1",
-            "notes": "Use numeric form for easy conversion.",
-            "source": "bibtex"
-        },
-        "feb": {
-            "value": "2",
-            "source": "bibtex"
-        },
-        "mar": {
-            "value": "3",
-            "source": "bibtex"
-        },
-        "apr": {
-            "value": "4",
-            "source": "bibtex"
-        },
-        "may": {
-            "value": "5",
-            "source": "bibtex"
-        },
-        "jun": {
-            "value": "6",
-            "source": "bibtex"
-        },
-        "jul": {
-            "value": "7",
-            "source": "bibtex"
-        },
-        "aug": {
-            "value": "8",
-            "source": "bibtex"
-        },
-        "sep": {
-            "value": "9",
-            "source": "bibtex"
-        },
-        "oct": {
-            "value": "10",
-            "source": "bibtex"
-        },
-        "nov": {
-            "value": "11",
-            "source": "bibtex"
-        },
-        "dec": {
-            "value": "12",
-            "source": "bibtex"
-        },
-        "acmcs": {
-            "value": "ACM Computing Surveys",
-            "source": "bibtex"
-        },
-        "acta": {
-            "value": "Acta Informatica",
-            "source": "bibtex"
-        },
-        "cacm": {
-            "value": "Communications of the ACM",
-            "source": "bibtex"
-        },
-        "ibmjrd": {
-            "value": "IBM Journal of Research and Development",
-            "source": "bibtex"
-        },
-        "ibmsj": {
-            "value": "IBM Systems Journal",
-            "source": "bibtex"
-        },
-        "ieeese": {
-            "value": "IEEE Transactions on Software Engineering",
-            "source": "bibtex"
-        },
-        "ieeetc": {
-            "value": "IEEE Transactions on Computers",
-            "source": "bibtex"
-        },
-        "ieeetcad": {
-            "value": "IEEE Transactions on Computer-Aided Design of Integrated Circuits",
-            "source": "bibtex"
-        },
-        "ipl": {
-            "value": "Information Processing Letters",
-            "source": "bibtex"
-        },
-        "jacm": {
-            "value": "Journal of the ACM",
-            "source": "bibtex"
-        },
-        "jcss": {
-            "value": "Journal of Computer and System Sciences",
-            "source": "bibtex"
-        },
-        "scp": {
-            "value": "Science of Computer Programming",
-            "source": "bibtex"
-        },
-        "sicomp": {
-            "value": "SIAM Journal on Computing",
-            "source": "bibtex"
-        },
-        "tocs": {
-            "value": "ACM Transactions on Computer Systems",
-            "source": "bibtex"
-        },
-        "tods": {
-            "value": "ACM Transactions on Database Systems",
-            "source": "bibtex"
-        },
-        "tog": {
-            "value": "ACM Transactions on Graphics",
-            "source": "bibtex"
-        },
-        "toms": {
-            "value": "ACM Transactions on Mathematical Software",
-            "source": "bibtex"
-        },
-        "toois": {
-            "value": "ACM Transactions on Office Information Systems",
-            "source": "bibtex"
-        },
-        "toplas": {
-            "value": "ACM Transactions on Programming Languages and Systems",
-            "source": "bibtex"
-        },
-        "tcs": {
-            "value": "Theoretical Computer Science",
-            "source": "bibtex"
-        }
-    }
-}

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-GB.xml
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-GB.xml	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-GB.xml	2022-08-18 23:08:31 UTC (rev 64143)
@@ -11,7 +11,7 @@
       <name>Rintze M. Zelle</name>
     </translator>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
-    <updated>2022-01-01T21:32:03-05:00</updated>
+    <updated>2022-03-12T05:55:43-05:00</updated>
   </info>
   <style-options punctuation-in-quote="false"/>
   <date form="text">
@@ -309,39 +309,39 @@
     </term>
 
     <!-- SHORT LOCATOR FORMS -->
-    <term name="appendix">			 
+    <term name="appendix" form="short">			 
       <single>app.</single>
       <multiple>apps.</multiple>						 
     </term>
-    <term name="article-locator">			 
+    <term name="article-locator" form="short">			 
       <single>art.</single>
       <multiple>arts.</multiple>
     </term>
-    <term name="elocation">
+    <term name="elocation" form="short">
       <single>loc.</single>
       <multiple>locs.</multiple>
     </term>
-    <term name="equation">			 
+    <term name="equation" form="short">			 
       <single>eq.</single>
       <multiple>eqs.</multiple>
     </term>
-    <term name="rule">			 
+    <term name="rule" form="short">			 
       <single>r.</single>
       <multiple>rr.</multiple>						 
     </term>
-    <term name="scene">			 
+    <term name="scene" form="short">			 
       <single>sc.</single>
       <multiple>scs.</multiple>						 
     </term>
-    <term name="table">			 
+    <term name="table" form="short">			 
       <single>tbl.</single>
       <multiple>tbls.</multiple>						 
     </term>
-    <term name="timestamp"> <!-- generally blank -->
+    <term name="timestamp" form="short"> <!-- generally blank -->
       <single></single>
       <multiple></multiple>						 
     </term>
-    <term name="title-locator">			 
+    <term name="title-locator" form="short">			 
       <single>tit.</single>
       <multiple>tits.</multiple>
     </term>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml	2022-08-18 23:08:31 UTC (rev 64143)
@@ -17,7 +17,7 @@
       <name>Brenton M. Wiernik</name>
     </translator>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
-    <updated>2022-01-01T21:32:03-05:00</updated>
+    <updated>2022-03-12T05:55:43-05:00</updated>
   </info>
   <style-options punctuation-in-quote="true"/>
   <date form="text">
@@ -315,39 +315,39 @@
     </term>
 
     <!-- SHORT LOCATOR FORMS -->
-    <term name="appendix">			 
+    <term name="appendix" form="short">			 
       <single>app.</single>
       <multiple>apps.</multiple>						 
     </term>
-    <term name="article-locator">			 
+    <term name="article-locator" form="short">			 
       <single>art.</single>
       <multiple>arts.</multiple>
     </term>
-    <term name="elocation">			 
+    <term name="elocation" form="short">			 
       <single>loc.</single>
       <multiple>locs.</multiple>
     </term>
-    <term name="equation">			 
+    <term name="equation" form="short">			 
       <single>eq.</single>
       <multiple>eqs.</multiple>
     </term>
-    <term name="rule">			 
+    <term name="rule" form="short">			 
       <single>r.</single>
       <multiple>rr.</multiple>						 
     </term>
-    <term name="scene">			 
+    <term name="scene" form="short">			 
       <single>sc.</single>
       <multiple>scs.</multiple>						 
     </term>
-    <term name="table">			 
+    <term name="table" form="short">			 
       <single>tbl.</single>
       <multiple>tbls.</multiple>						 
     </term>
-    <term name="timestamp"> <!-- generally blank -->
+    <term name="timestamp" form="short"> <!-- generally blank -->
       <single></single>
       <multiple></multiple>						 
     </term>
-    <term name="title-locator">			 
+    <term name="title-locator" form="short">			 
       <single>tit.</single>
       <multiple>tits.</multiple>
     </term>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-fr-FR.xml
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-fr-FR.xml	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-fr-FR.xml	2022-08-18 23:08:31 UTC (rev 64143)
@@ -4,8 +4,11 @@
     <translator>
       <name>Grégoire Colly</name>
     </translator>
+    <translator>
+      <name>Collectif Zotero francophone</name>
+    </translator>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
-    <updated>2022-01-01T21:32:03-05:00</updated>
+    <updated>2022-04-01T14:30:37+02:00</updated>
   </info>
   <style-options punctuation-in-quote="false" limit-day-ordinals-to-day-1="true"/>
   <date form="text">
@@ -19,33 +22,33 @@
     <date-part name="year"/>
   </date>
   <terms>
-    <term name="advance-online-publication">advance online publication</term>
+    <term name="advance-online-publication">publication en ligne anticipée</term>
     <term name="album">album</term>
-    <term name="audio-recording">audio recording</term>
+    <term name="audio-recording">enregistrement audio</term>
     <term name="film">film</term>
-    <term name="henceforth">henceforth</term>
-    <term name="loc-cit">loc. cit.</term> <!-- like ibid., the abbreviated form is the regular form  -->
-    <term name="no-place">no place</term>
-    <term name="no-place" form="short">n.p.</term>
-    <term name="no-publisher">no publisher</term> <!-- sine nomine -->
-    <term name="no-publisher" form="short">n.p.</term>
-    <term name="on">on</term>
-    <term name="op-cit">op. cit.</term> <!-- like ibid., the abbreviated form is the regular form  -->
-    <term name="original-work-published">original work published</term>
+    <term name="henceforth">désormais</term>
+    <term name="loc-cit">loc. cit.</term> <!-- like ibid., the abbreviated form is the regular form  -->
+    <term name="no-place">sans lieu</term>
+    <term name="no-place" form="short">s. l.</term>
+    <term name="no-publisher">sans nom</term> <!-- sine nomine -->
+    <term name="no-publisher" form="short">s. n.</term>
+    <term name="on">sur</term>
+    <term name="op-cit">op. cit.</term> <!-- like ibid., the abbreviated form is the regular form  -->
+    <term name="original-work-published">édition originale</term>
     <term name="personal-communication">communication personnelle</term>
     <term name="podcast">podcast</term>
-    <term name="podcast-episode">podcast episode</term>
-    <term name="preprint">preprint</term>
-    <term name="radio-broadcast">radio broadcast</term>
-    <term name="radio-series">radio series</term>
-    <term name="radio-series-episode">radio series episode</term>
-    <term name="special-issue">special issue</term>
-    <term name="special-section">special section</term>
-    <term name="television-broadcast">television broadcast</term>
-    <term name="television-series">television series</term>
-    <term name="television-series-episode">television series episode</term>
-    <term name="video">video</term>
-    <term name="working-paper">working paper</term>
+    <term name="podcast-episode">épisode de podcast</term>
+    <term name="preprint">prépublication</term>
+    <term name="radio-broadcast">émission de radio</term>
+    <term name="radio-series">série radiophonique</term>
+    <term name="radio-series-episode">épisode de série radiophonique</term>
+    <term name="special-issue">numéro spécial</term>
+    <term name="special-section">section spéciale</term>
+    <term name="television-broadcast">émission de télévision</term>
+    <term name="television-series">série télévisée</term>
+    <term name="television-series-episode">épisode de série télévisée</term>
+    <term name="video">vidéo</term>
+    <term name="working-paper">document de travail</term>
     <term name="accessed">consulté le</term>
     <term name="and">et</term>
     <term name="and others">et autres</term>
@@ -88,74 +91,74 @@
     <term name="version">version</term>
 
     <!-- LONG ITEM TYPE FORMS -->
-    <term name="article">preprint</term>
-    <term name="article-journal">journal article</term>
-    <term name="article-magazine">magazine article</term>
-    <term name="article-newspaper">newspaper article</term>
-    <term name="bill">bill</term>
-    <term name="book">book</term>
-    <term name="broadcast">broadcast</term>
-    <term name="chapter">book chapter</term>
-    <term name="classic">classic</term>
+    <term name="article">article</term>
+    <term name="article-journal">article de revue</term>
+    <term name="article-magazine">article de magazine</term>
+    <term name="article-newspaper">article de presse</term>
+    <term name="bill">projet de loi</term>
+    <term name="book">livre</term>
+    <term name="broadcast">émission</term>
+    <term name="chapter">chapitre de livre</term>
+    <term name="classic">classique</term>
     <term name="collection">collection</term>
-    <term name="dataset">dataset</term>
+    <term name="dataset">jeu de données</term>
     <term name="document">document</term>
-    <term name="entry">entry</term>
-    <term name="entry-dictionary">dictionary entry</term>
-    <term name="entry-encyclopedia">encyclopedia entry</term>
-    <term name="event">event</term>
+    <term name="entry">entrée</term>
+    <term name="entry-dictionary">entrée de dictionnaire</term>
+    <term name="entry-encyclopedia">entrée d'encyclopédie</term>
+    <term name="event">événement</term>
     <!-- figure is in the list of locator terms -->
-    <term name="graphic">graphic</term>
-    <term name="hearing">hearing</term>
+    <term name="graphic">image</term>
+    <term name="hearing">audience</term>
     <term name="interview">interview</term>
-    <term name="legal_case">legal case</term>
-    <term name="legislation">legislation</term>
-    <term name="manuscript">manuscript</term>
-    <term name="map">map</term>
-    <term name="motion_picture">video recording</term>
-    <term name="musical_score">musical score</term>
+    <term name="legal_case">affaire</term>
+    <term name="legislation">acte juridique</term>
+    <term name="manuscript">manuscrit</term>
+    <term name="map">carte</term>
+    <term name="motion_picture">enregistrement vidéo</term>
+    <term name="musical_score">partition</term>
     <term name="pamphlet">pamphlet</term>
-    <term name="paper-conference">conference paper</term>
-    <term name="patent">patent</term>
-    <term name="performance">performance</term>
-    <term name="periodical">periodical</term>
+    <term name="paper-conference">article de colloque</term>
+    <term name="patent">brevet</term>
+    <term name="performance">interprétation</term>
+    <term name="periodical">périodique</term>
     <term name="personal_communication">communication personnelle</term>
-    <term name="post">post</term>
-    <term name="post-weblog">blog post</term>
-    <term name="regulation">regulation</term>
-    <term name="report">report</term>
-    <term name="review">review</term>
-    <term name="review-book">book review</term>
-    <term name="software">software</term>
-    <term name="song">audio recording</term>
-    <term name="speech">presentation</term>
-    <term name="standard">standard</term>
-    <term name="thesis">thesis</term>
-    <term name="treaty">treaty</term>
-    <term name="webpage">webpage</term>
+    <term name="post">billet</term>
+    <term name="post-weblog">billet de blog</term>
+    <term name="regulation">règlement</term>
+    <term name="report">rapport</term>
+    <term name="review">recension</term>
+    <term name="review-book">recension de livre</term>
+    <term name="software">logiciel</term>
+    <term name="song">chanson</term>
+    <term name="speech">présentation</term>
+    <term name="standard">norme</term>
+    <term name="thesis">thèse</term>
+    <term name="treaty">traité</term>
+    <term name="webpage">page web</term>
 
     <!-- SHORT ITEM TYPE FORMS -->
-    <term name="article-journal" form="short">journal art.</term>
-    <term name="article-magazine" form="short">mag. art.</term>
-    <term name="article-newspaper" form="short">newspaper art.</term>
-    <term name="book" form="short">bk.</term>
-    <term name="chapter" form="short">bk. chap.</term>
+    <term name="article-journal" form="short">art. de revue</term>
+    <term name="article-magazine" form="short">art. de mag.</term>
+    <term name="article-newspaper" form="short">art. de presse</term>
+    <term name="book" form="short">liv.</term>
+    <term name="chapter" form="short">chap. de liv.</term>
     <term name="document" form="short">doc.</term>
     <!-- figure is in the list of locator terms -->
     <term name="graphic" form="short">graph.</term>
     <term name="interview" form="short">interv.</term>
-    <term name="manuscript" form="short">MS</term>
-    <term name="motion_picture" form="short">video rec.</term>
-    <term name="report" form="short">rep.</term>
-    <term name="review" form="short">rev.</term>
-    <term name="review-book" form="short">bk. rev.</term>
-    <term name="song" form="short">audio rec.</term>
+    <term name="manuscript" form="short">ms</term>
+    <term name="motion_picture" form="short">enr. vidéo</term>
+    <term name="report" form="short">rap.</term>
+    <term name="review" form="short">recens.</term>
+    <term name="review-book" form="short">recens. de liv.</term>
+    <term name="song" form="short">enr. audio</term>
 
     <!-- HISTORICAL ERA TERMS -->
     <term name="ad">apr. J.-C.</term>
     <term name="bc">av. J.-C.</term>
-    <term name="bce">BCE</term>
-    <term name="ce">CE</term>
+    <term name="bce">av. n. è.</term>
+    <term name="ce">n. è.</term>
 
     <!-- PUNCTUATION -->
     <term name="open-quote">« </term>
@@ -163,15 +166,15 @@
     <term name="open-inner-quote">“</term>
     <term name="close-inner-quote">”</term>
     <term name="page-range-delimiter">‑</term> <!-- non-breaking hyphen -->
-    <term name="colon">:</term>
+    <term name="colon"> :</term>
     <term name="comma">,</term>
-    <term name="semicolon">;</term>
+    <term name="semicolon"> ;</term>
 
     <!-- ORDINALS -->
     <term name="ordinal">ᵉ</term>
     <term name="ordinal-01" gender-form="feminine" match="whole-number">ʳᵉ</term>
     <term name="ordinal-01" gender-form="masculine" match="whole-number">ᵉʳ</term>
-    
+
     <!-- LONG ORDINALS -->
     <term name="long-ordinal-01">premier</term>
     <term name="long-ordinal-02">deuxième</term>
@@ -185,49 +188,49 @@
     <term name="long-ordinal-10">dixième</term>
 
     <!-- LONG LOCATOR FORMS -->
-    <term name="act">			 
-      <single>act</single>
-      <multiple>acts</multiple>						 
+    <term name="act">
+      <single>acte</single>
+      <multiple>actes</multiple>
     </term>
-    <term name="appendix">			 
-      <single>appendix</single>
-      <multiple>appendices</multiple>						 
+    <term name="appendix">
+      <single>appendice</single>
+      <multiple>appendices</multiple>
     </term>
-    <term name="article-locator">			 
+    <term name="article-locator">
       <single>article</single>
-      <multiple>articles</multiple>						 
+      <multiple>articles</multiple>
     </term>
-    <term name="canon">			 
+    <term name="canon">
       <single>canon</single>
-      <multiple>canons</multiple>						 
+      <multiple>canons</multiple>
     </term>
-    <term name="elocation">			 
-      <single>location</single>
-      <multiple>locations</multiple>						 
+    <term name="elocation">
+      <single>emplacement</single>
+      <multiple>emplacements</multiple>
     </term>
-    <term name="equation">			 
-      <single>equation</single>
-      <multiple>equations</multiple>						 
+    <term name="equation">
+      <single>équation</single>
+      <multiple>équations</multiple>
     </term>
-    <term name="rule">			 
-      <single>rule</single>
-      <multiple>rules</multiple>						 
+    <term name="rule">
+      <single>règle</single>
+      <multiple>règles</multiple>
     </term>
-    <term name="scene">			 
-      <single>scene</single>
-      <multiple>scenes</multiple>						 
+    <term name="scene">
+      <single>scène</single>
+      <multiple>scènes</multiple>
     </term>
-    <term name="table">			 
-      <single>table</single>
-      <multiple>tables</multiple>						 
+    <term name="table">
+      <single>tableau</single>
+      <multiple>tableaux</multiple>
     </term>
     <term name="timestamp"> <!-- generally blank -->
       <single></single>
-      <multiple></multiple>						 
+      <multiple></multiple>
     </term>
-    <term name="title-locator">			 
-      <single>title</single>
-      <multiple>titles</multiple>						 
+    <term name="title-locator">
+      <single>titre</single>
+      <multiple>titres</multiple>
     </term>
     <term name="book">
       <single>livre</single>
@@ -299,41 +302,41 @@
     </term>
 
     <!-- SHORT LOCATOR FORMS -->
-    <term name="appendix">			 
-      <single>app.</single>
-      <multiple>apps.</multiple>						 
+    <term name="appendix">
+      <single>append.</single>
+      <multiple>append.</multiple>
     </term>
-    <term name="article-locator">			 
+    <term name="article-locator">
       <single>art.</single>
-      <multiple>arts.</multiple>
+      <multiple>art.</multiple>
     </term>
-    <term name="elocation">			 
-      <single>loc.</single>
-      <multiple>locs.</multiple>
+    <term name="elocation">
+      <single>emplact</single>
+      <multiple>emplact</multiple>
     </term>
-    <term name="equation">			 
+    <term name="equation">
       <single>eq.</single>
-      <multiple>eqs.</multiple>
+      <multiple>eq.</multiple>
     </term>
-    <term name="rule">			 
-      <single>r.</single>
-      <multiple>rr.</multiple>						 
+    <term name="rule">
+      <single>règle</single>
+      <multiple>règles</multiple>
     </term>
-    <term name="scene">			 
+    <term name="scene">
       <single>sc.</single>
-      <multiple>scs.</multiple>						 
+      <multiple>sc.</multiple>
     </term>
-    <term name="table">			 
-      <single>tbl.</single>
-      <multiple>tbls.</multiple>						 
+    <term name="table">
+      <single>tab.</single>
+      <multiple>tab.</multiple>
     </term>
     <term name="timestamp"> <!-- generally blank -->
       <single></single>
-      <multiple></multiple>						 
+      <multiple></multiple>
     </term>
-    <term name="title-locator">			 
+    <term name="title-locator">
       <single>tit.</single>
-      <multiple>tits.</multiple>
+      <multiple>tit.</multiple>
     </term>
     <term name="book" form="short">liv.</term>
     <term name="chapter" form="short">chap.</term>
@@ -386,56 +389,56 @@
 
     <!-- LONG ROLE FORMS -->
     <term name="chair">
-      <single>chair</single>
-      <multiple>chairs</multiple>
+      <single>président</single>
+      <multiple>présidents</multiple>
     </term>
     <term name="compiler">
-      <single>compiler</single>
-      <multiple>compilers</multiple>
+      <single>compilateur</single>
+      <multiple>compilateurs</multiple>
     </term>
     <term name="contributor">
-      <single>contributor</single>
-      <multiple>contributors</multiple>
+      <single>contributeur</single>
+      <multiple>contributeurs</multiple>
     </term>
     <term name="curator">
-      <single>curator</single>
-      <multiple>curators</multiple>
+      <single>commissaire</single>
+      <multiple>commissaires</multiple>
     </term>
     <term name="executive-producer">
-      <single>executive producer</single>
-      <multiple>executive producers</multiple>
+      <single>producteur exécutif</single>
+      <multiple>producteurs exécutifs</multiple>
     </term>
     <term name="guest">
-      <single>guest</single>
-      <multiple>guests</multiple>
+      <single>invité</single>
+      <multiple>invités</multiple>
     </term>
     <term name="host">
-      <single>host</single>
-      <multiple>hosts</multiple>
+      <single>hôte</single>
+      <multiple>hôtes</multiple>
     </term>
     <term name="narrator">
-      <single>narrator</single>
-      <multiple>narrators</multiple>
+      <single>narrateur</single>
+      <multiple>narrateurs</multiple>
     </term>
     <term name="organizer">
-      <single>organizer</single>
-      <multiple>organizers</multiple>
+      <single>organisateur</single>
+      <multiple>organisateurs</multiple>
     </term>
     <term name="performer">
-      <single>performer</single>
-      <multiple>performers</multiple>
+      <single>interprète</single>
+      <multiple>interprètes</multiple>
     </term>
     <term name="producer">
-      <single>producer</single>
-      <multiple>producers</multiple>
+      <single>producteur</single>
+      <multiple>producteurs</multiple>
     </term>
     <term name="script-writer">
-      <single>writer</single>
-      <multiple>writers</multiple>
+      <single>scénariste</single>
+      <multiple>scénaristes</multiple>
     </term>
     <term name="series-creator">
-      <single>series creator</single>
-      <multiple>series creators</multiple>
+      <single>créateur de série</single>
+      <multiple>créateurs de série</multiple>
     </term>
     <term name="director">
       <single>réalisateur</single>
@@ -464,44 +467,44 @@
 
     <!-- SHORT ROLE FORMS -->
     <term name="compiler" form="short">
-      <single>comp.</single>
-      <multiple>comps.</multiple>
+      <single>compil.</single>
+      <multiple>compil.</multiple>
     </term>
     <term name="contributor" form="short">
       <single>contrib.</single>
-      <multiple>contribs.</multiple>
+      <multiple>contrib.</multiple>
     </term>
     <term name="curator" form="short">
-      <single>cur.</single>
-      <multiple>curs.</multiple>
+      <single>commiss.</single>
+      <multiple>commiss.</multiple>
     </term>
     <term name="executive-producer" form="short">
-      <single>exec. prod.</single>
-      <multiple>exec. prods.</multiple>
+      <single>prod. exé.</single>
+      <multiple>prod. exé.</multiple>
     </term>
     <term name="narrator" form="short">
       <single>narr.</single>
-      <multiple>narrs.</multiple>
+      <multiple>narr.</multiple>
     </term>
     <term name="organizer" form="short">
       <single>org.</single>
-      <multiple>orgs.</multiple>
+      <multiple>org.</multiple>
     </term>
     <term name="performer" form="short">
-      <single>perf.</single>
-      <multiple>perfs.</multiple>
+      <single>interpr.</single>
+      <multiple>interpr.</multiple>
     </term>
     <term name="producer" form="short">
       <single>prod.</single>
-      <multiple>prods.</multiple>
+      <multiple>prod.</multiple>
     </term>
     <term name="script-writer" form="short">
-      <single>writ.</single>
-      <multiple>writs.</multiple>
+      <single>scénar.</single>
+      <multiple>scénar.</multiple>
     </term>
     <term name="series-creator" form="short">
-      <single>cre.</single>
-      <multiple>cres.</multiple>
+      <single>créat.</single>
+      <multiple>créat.</multiple>
     </term>
     <term name="director" form="short">
       <single>réal.</single>
@@ -529,19 +532,19 @@
     </term>
 
     <!-- VERB ROLE FORMS -->
-    <term name="chair" form="verb">chaired by</term>
-    <term name="compiler" form="verb">compiled by</term>
-    <term name="contributor" form="verb">with</term>
-    <term name="curator" form="verb">curated by</term>
-    <term name="executive-producer" form="verb">executive produced by</term>
-    <term name="guest" form="verb">with guest</term>
-    <term name="host" form="verb">hosted by</term>
-    <term name="narrator" form="verb">narrated by</term>
-    <term name="organizer" form="verb">organized by</term>
-    <term name="performer" form="verb">performed by</term>
-    <term name="producer" form="verb">produced by</term>
-    <term name="script-writer" form="verb">written by</term>
-    <term name="series-creator" form="verb">created by</term>
+    <term name="chair" form="verb">présidé par</term>
+    <term name="compiler" form="verb">compilé par</term>
+    <term name="contributor" form="verb">avec</term>
+    <term name="curator" form="verb">organisé par</term>
+    <term name="executive-producer" form="verb">production exécutive par</term>
+    <term name="guest" form="verb">avec pour invité</term>
+    <term name="host" form="verb">animé par</term>
+    <term name="narrator" form="verb">lu par</term>
+    <term name="organizer" form="verb">organisé par</term>
+    <term name="performer" form="verb">interprété par</term>
+    <term name="producer" form="verb">produit par</term>
+    <term name="script-writer" form="verb">scénario de</term>
+    <term name="series-creator" form="verb">créé par</term>
     <term name="container-author" form="verb">par</term>
     <term name="director" form="verb">réalisé par</term>
     <term name="editor" form="verb">édité par</term>
@@ -554,18 +557,18 @@
     <term name="editortranslator" form="verb">édité et traduit par</term>
 
     <!-- SHORT VERB ROLE FORMS -->
-    <term name="compiler" form="verb-short">comp. by</term>
-    <term name="contributor" form="verb-short">w.</term>
-    <term name="curator" form="verb-short">cur. by</term>
-    <term name="executive-producer" form="verb-short">exec. prod. by</term>
-    <term name="guest" form="verb-short">w. guest</term>
-    <term name="host" form="verb-short">hosted by</term>
-    <term name="narrator" form="verb-short">narr. by</term>
-    <term name="organizer" form="verb-short">org. by</term>
-    <term name="performer" form="verb-short">perf. by</term>
-    <term name="producer" form="verb-short">prod. by</term>
-    <term name="script-writer" form="verb-short">writ. by</term>
-    <term name="series-creator" form="verb-short">cre. by</term>
+    <term name="compiler" form="verb-short">compil. par</term>
+    <term name="contributor" form="verb-short">ac</term>
+    <term name="curator" form="verb-short">org. par</term>
+    <term name="executive-producer" form="verb-short">prod. exé. par</term>
+    <term name="guest" form="verb-short">ac pr inv.</term>
+    <term name="host" form="verb-short">anim. par</term>
+    <term name="narrator" form="verb-short">lu par</term>
+    <term name="organizer" form="verb-short">org. par</term>
+    <term name="performer" form="verb-short">interpr. par</term>
+    <term name="producer" form="verb-short">prod. par</term>
+    <term name="script-writer" form="verb-short">scénar. de</term>
+    <term name="series-creator" form="verb-short">créé par</term>
     <term name="director" form="verb-short">réal. par</term>
     <term name="editor" form="verb-short">éd. par</term>
     <term name="editorial-director" form="verb-short">ss la dir. de</term>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-chemical-society.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-chemical-society.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-chemical-society.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -21,7 +21,7 @@
     <category citation-format="numeric"/>
     <category field="chemistry"/>
     <summary>The American Chemical Society style</summary>
-    <updated>2021-05-22T12:00:00+00:00</updated>
+    <updated>2022-05-04T19:44:25-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -250,14 +250,17 @@
           </group>
         </else-if>
         <else-if type="webpage">
-          <group delimiter=" ">
-            <text variable="title"/>
-            <text variable="URL"/>
-            <date variable="accessed" prefix="(accessed " suffix=")" delimiter=" ">
-              <date-part name="year"/>
-              <date-part name="month" prefix="-" form="numeric-leading-zeros"/>
-              <date-part name="day" prefix="-" form="numeric-leading-zeros"/>
-            </date>
+          <group delimiter=". ">
+            <text variable="title" font-style="italic"/>
+            <text variable="container-title"/>
+            <group delimiter=" ">
+              <text variable="URL"/>
+              <date variable="accessed" prefix="(accessed " suffix=")">
+                <date-part name="year"/>
+                <date-part name="month" prefix="-" form="numeric-leading-zeros"/>
+                <date-part name="day" prefix="-" form="numeric-leading-zeros"/>
+              </date>
+            </group>
           </group>
         </else-if>
         <else>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -27,7 +27,7 @@
     <category citation-format="numeric"/>
     <category field="medicine"/>
     <summary>The American Medical Association style as used in JAMA. Version 11 as per November-2019.</summary>
-    <updated>2021-10-28T13:38:04+00:00</updated>
+    <updated>2022-03-17T08:48:24+00:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -86,7 +86,7 @@
   </macro>
   <macro name="title">
     <choose>
-      <if type="bill book graphic legal_case legislation motion_picture report song" match="any">
+      <if type="bill book graphic legal_case legislation motion_picture report song thesis" match="any">
         <text variable="title" font-style="italic" text-case="title"/>
       </if>
       <else>
@@ -247,6 +247,15 @@
             <text variable="event-place"/>
           </group>
         </else-if>
+        <else-if type="thesis" match="any">
+          <group delimiter=". " prefix=" " suffix=".">
+            <text variable="genre"/>
+            <group delimiter="; ">
+              <text variable="publisher"/>
+              <date date-parts="year" form="text" variable="issued"/>
+            </group>
+          </group>
+        </else-if>
         <else>
           <text macro="editor" prefix=" " suffix="."/>
           <group prefix=" " suffix=".">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-sociological-association.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-sociological-association.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-sociological-association.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -16,7 +16,7 @@
     <category citation-format="author-date"/>
     <category field="sociology"/>
     <summary>The ASA style following the 6th edition of the Style Guide</summary>
-    <updated>2020-09-18T10:38:12+00:00</updated>
+    <updated>2022-05-08T17:48:56-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -125,13 +125,25 @@
   <macro name="year-date">
     <choose>
       <if variable="issued">
-        <group delimiter=" ">
-          <date variable="original-date" form="numeric" date-parts="year" prefix="[" suffix="]"/>
-          <date variable="issued" form="numeric" date-parts="year"/>
+        <group>
+          <group delimiter=" ">
+            <date variable="original-date" form="numeric" date-parts="year" prefix="[" suffix="]"/>
+            <date variable="issued" form="numeric" date-parts="year"/>
+          </group>
+          <text variable="year-suffix"/>
         </group>
       </if>
+      <else-if variable="status">
+        <group>
+          <text variable="status" text-case="lowercase"/>
+          <text variable="year-suffix" prefix="-"/>
+        </group>
+      </else-if>
       <else>
-        <text term="no date" form="short"/>
+        <group>
+          <text term="no date" form="short"/>
+          <text variable="year-suffix" prefix="-"/>
+        </group>
       </else>
     </choose>
   </macro>
@@ -140,6 +152,12 @@
       <if variable="issued">
         <date variable="issued" form="numeric" date-parts="year"/>
       </if>
+      <else-if variable="status">
+        <group>
+          <text variable="status" text-case="lowercase"/>
+          <text variable="year-suffix" prefix="-"/>
+        </group>
+      </else-if>
       <else>
         <text term="no date" form="short"/>
       </else>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -14,7 +14,7 @@
     <category citation-format="author-date"/>
     <category field="psychology"/>
     <category field="generic-base"/>
-    <updated>2022-01-31T09:43:56-05:00</updated>
+    <updated>2022-08-11T09:14:19-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -320,8 +320,8 @@
   <!-- General categories of item types:
        Periodical: article-journal article-magazine article-newspaper post-weblog review review-book
        Periodical or Booklike: paper-conference
-       Booklike: article book broadcast chapter dataset entry entry-dictionary entry-encyclopedia figure 
-                 graphic interview manuscript map motion_picture musical_score pamphlet patent 
+       Booklike: article book broadcast chapter dataset entry entry-dictionary entry-encyclopedia figure
+                 graphic interview manuscript map motion_picture musical_score pamphlet patent
                  personal_communication report song speech thesis post webpage
        Legal: bill legal_case legislation treaty
   -->
@@ -389,7 +389,7 @@
       </if>
       <else-if type="interview personal_communication" match="any">
         <choose>
-          <!-- These variables indicate that the letter is retrievable by the reader. 
+          <!-- These variables indicate that the letter is retrievable by the reader.
                 If not, then use the APA in-text-only personal communication format -->
           <if variable="archive container-title DOI publisher URL" match="none">
             <group delimiter=", ">
@@ -470,7 +470,7 @@
                   </if>
                 </choose>
               </else-if>
-              <!-- Only year: article article-journal book chapter entry entry-dictionary entry-encyclopedia dataset figure graphic 
+              <!-- Only year: article article-journal book chapter entry entry-dictionary entry-encyclopedia dataset figure graphic
                    manuscript map musical_score paper-conference[published] patent report review review-book thesis -->
             </choose>
           </if>
@@ -530,7 +530,7 @@
                 <if type="interview personal_communication" match="any">
                   <choose>
                     <if variable="archive container-title DOI publisher URL" match="none">
-                      <!-- These variables indicate that the communication is retrievable by the reader. 
+                      <!-- These variables indicate that the communication is retrievable by the reader.
                            If not, then use the in-text-only personal communication format -->
                       <date variable="issued" form="text"/>
                     </if>
@@ -1295,7 +1295,7 @@
   <macro name="reviewed-title">
     <choose>
       <if variable="reviewed-title">
-        <!-- Not possible to distinguish TV series episode from other reviewed 
+        <!-- Not possible to distinguish TV series episode from other reviewed
               works [Ex. 69] -->
         <text variable="reviewed-title" font-style="italic"/>
       </if>
@@ -1553,7 +1553,7 @@
   </macro>
   <macro name="event">
     <choose>
-      <if variable="event">
+      <if variable="event event-title" match="any">
         <!-- To prevent Zotero from printing event-place due to its double-mapping of all 'place' to
              both publisher-place and event-place. Remove this 'choose' when that is changed. -->
         <choose>
@@ -1560,7 +1560,7 @@
           <if variable="collection-editor editor editorial-director issue page volume" match="none">
             <!-- Don't print event info if published in a proceedings -->
             <group delimiter=", ">
-              <text variable="event"/>
+              <text macro="event-title"/>
               <text variable="event-place"/>
             </group>
           </if>
@@ -1568,6 +1568,20 @@
       </if>
     </choose>
   </macro>
+  <macro name="event-title">
+    <choose>
+      <!-- TODO: We expect "event-title" to be used,
+           but processors and applications may not be updated yet.
+           This macro ensures that either "event" or "event-title" can be accpeted.
+           Remove if procesor logic and application adoption can handle this. -->
+      <if variable="event-title">
+        <text variable="event-title"/>
+      </if>
+      <else>
+        <text variable="event"/>
+      </else>
+    </choose>
+  </macro>
   <!-- After 'source', APA also prints publication history (original publication, reprint info, retraction info) -->
   <macro name="publication-history">
     <choose>
@@ -1575,7 +1589,7 @@
         <group prefix="(" suffix=")">
           <choose>
             <if variable="references">
-              <!-- This provides the option for more elaborate description 
+              <!-- This provides the option for more elaborate description
                    of publication history, such as full "reprinted" references
                    (examples 11, 43, 44) or retracted references -->
               <text variable="references"/>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-author-date.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-author-date.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-author-date.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -45,6 +45,30 @@
       <term name="translator" form="short">trans.</term>
     </terms>
   </locale>
+  <locale xml:lang="pt-PT">
+    <terms>
+      <term name="accessed">acedido a</term>
+    </terms>
+  </locale>
+  <locale xml:lang="pt">
+    <terms>
+      <term name="editor" form="verb">editado por</term>
+      <term name="editor" form="verb-short">ed.</term>
+      <term name="container-author" form="verb">por</term>
+      <term name="translator" form="verb-short">traduzido por</term>
+      <term name="translator" form="short">trad.</term>
+      <term name="editortranslator" form="verb">editado e traduzido por</term>
+      <term name="and">e</term>
+      <term name="no date" form="long">s.d</term>
+      <term name="no date" form="short">s.d.</term>
+      <term name="in">em</term>
+      <term name="at">em</term>
+      <term name="by">por</term>
+      <!-- PUNCTUATION -->
+      <term name="open-quote">"</term>
+      <term name="close-quote">"</term>
+    </terms>
+  </locale>
   <macro name="secondary-contributors">
     <choose>
       <if type="chapter entry-dictionary entry-encyclopedia paper-conference" match="none">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-fullnote-bibliography.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-fullnote-bibliography.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-fullnote-bibliography.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -34,7 +34,7 @@
     <category citation-format="note"/>
     <category field="generic-base"/>
     <summary>Chicago format with full notes and bibliography</summary>
-    <updated>2022-01-16T14:46:01-05:00</updated>
+    <updated>2017-10-12T12:00:00+00:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-note-bibliography.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-note-bibliography.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/chicago-note-bibliography.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -34,7 +34,7 @@
     <category citation-format="note"/>
     <category field="generic-base"/>
     <summary>Chicago format with short notes and bibliography</summary>
-    <updated>2022-01-16T14:46:01-05:00</updated>
+    <updated>2017-10-12T12:00:00+00:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/harvard-cite-them-right.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/harvard-cite-them-right.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/harvard-cite-them-right.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only" default-locale="en-GB">
   <info>
-    <title>Cite Them Right 11th edition - Harvard</title>
+    <title>Cite Them Right 12th edition - Harvard</title>
     <id>http://www.zotero.org/styles/harvard-cite-them-right</id>
     <link href="http://www.zotero.org/styles/harvard-cite-them-right" rel="self"/>
-    <link href="http://www.zotero.org/styles/harvard-cite-them-right-10th-edition" rel="template"/>
+    <link href="http://www.zotero.org/styles/harvard-cite-them-right-11th-edition" rel="template"/>
     <link href="http://www.citethemrightonline.com/" rel="documentation"/>
     <author>
       <name>Patrick O'Brien</name>
@@ -12,7 +12,7 @@
     <category citation-format="author-date"/>
     <category field="generic-base"/>
     <summary>Harvard according to Cite Them Right, 11th edition.</summary>
-    <updated>2021-09-01T07:43:59+00:00</updated>
+    <updated>2022-06-28T22:44:10-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en-GB">
@@ -34,7 +34,7 @@
         <choose>
           <if variable="container-author" match="none">
             <names variable="editor translator" delimiter=", ">
-              <name and="text" initialize-with="." name-as-sort-order="all"/>
+              <name and="text" initialize-with="."/>
               <label form="short" prefix=" (" suffix=")"/>
             </names>
           </if>
@@ -98,7 +98,10 @@
   <macro name="access">
     <choose>
       <if variable="DOI">
-        <text variable="DOI" prefix="doi:"/>
+        <group delimiter=": ">
+          <text term="available at" text-case="capitalize-first"/>
+          <text variable="DOI" prefix="https://doi.org/"/>
+        </group>
       </if>
       <else-if variable="URL">
         <text term="available at" suffix=": " text-case="capitalize-first"/>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/ieee.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/ieee.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/ieee.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -45,7 +45,7 @@
     <category field="engineering"/>
     <category field="generic-base"/>
     <summary>IEEE style as per the 2021 guidelines, V 01.29.2021.</summary>
-    <updated>2022-02-05T21:19:36-05:00</updated>
+    <updated>2021-05-07T00:52:46+10:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/modern-language-association.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/modern-language-association.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/modern-language-association.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -12,7 +12,7 @@
     <category citation-format="author"/>
     <category field="generic-base"/>
     <summary>This style adheres to the MLA 9th edition handbook. Follows the structure of references as outlined in the MLA Manual closely</summary>
-    <updated>2022-01-23T06:46:03-05:00</updated>
+    <updated>2022-04-24T09:59:40-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -205,7 +205,7 @@
       <if type="book chapter paper-conference motion_picture" match="any">
         <date variable="issued" form="numeric" date-parts="year"/>
       </if>
-      <else-if type="article-journal article-magazine" match="any">
+      <else-if type="article-journal" match="any">
         <date variable="issued" form="text" date-parts="year-month"/>
       </else-if>
       <else-if type="speech" match="none">

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/nature.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/nature.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/nature.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -15,7 +15,7 @@
     <category field="generic-base"/>
     <issn>0028-0836</issn>
     <eissn>1476-4687</eissn>
-    <updated>2019-10-08T13:18:12+00:00</updated>
+    <updated>2022-07-02T09:18:26-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <macro name="title">
@@ -37,7 +37,7 @@
   </macro>
   <macro name="access">
     <choose>
-      <if variable="volume"/>
+      <if variable="volume" type="article" match="any"/>
       <else-if variable="DOI">
         <text variable="DOI" prefix="doi:"/>
       </else-if>
@@ -55,6 +55,28 @@
           </group>
         </group>
       </if>
+      <else-if type="article">
+        <group delimiter=" ">
+          <choose>
+            <if variable="genre" match="any">
+              <text variable="genre" text-case="capitalize-first"/>
+            </if>
+            <else>
+              <text term="article" text-case="capitalize-first"/>
+            </else>
+          </choose>
+          <text term="at"/>
+          <choose>
+            <if variable="DOI" match="any">
+              <text variable="DOI" prefix="https://doi.org/"/>
+            </if>
+            <else>
+              <text variable="URL"/>
+            </else>
+          </choose>
+          <date date-parts="year" form="text" variable="issued" prefix="(" suffix=")"/>
+        </group>
+      </else-if>
       <else-if type="report webpage post post-weblog" match="any">
         <group delimiter=" ">
           <text variable="URL"/>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/vancouver.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/vancouver.csl	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/vancouver.csl	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only" page-range-format="minimal">
+<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only" initialize-with-hyphen="false" page-range-format="minimal">
   <info>
     <title>Vancouver</title>
     <id>http://www.zotero.org/styles/vancouver</id>

Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/tlpkg/libexec/ctan2tds	2022-08-18 23:08:31 UTC (rev 64143)
@@ -3495,7 +3495,7 @@
  'checkcites'           => '\.lua$',
  'checklistings'        => '\.sh$',
  'chklref'		=> '\.pl',
- 'citation-style-language' => 'citeproc\.lua$',
+ 'citation-style-language' => 'citeproc(-lua)?\.lua$',
  'cjk-gs-integrate'     => '\.pl$',
  'clojure-pamphlet'	=> 'pamphletangler$',
  'cluttex'		=> 'cluttex\.lua$', # moved by prehook

Modified: trunk/Master/tlpkg/tlpsrc/citation-style-language.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/citation-style-language.tlpsrc	2022-08-18 22:55:41 UTC (rev 64142)
+++ trunk/Master/tlpkg/tlpsrc/citation-style-language.tlpsrc	2022-08-18 23:08:31 UTC (rev 64143)
@@ -1,2 +1,13 @@
-docpattern +f texmf-dist/doc/man/man1/citeproc.*
+#docpattern +f texmf-dist/doc/man/man1/citeproc.*
+docpattern +f texmf-dist/doc/man/man1/citeproc-lua.*
+binpattern f bin/${ARCH}/citeproc-lua
 binpattern f bin/${ARCH}/citeproc
+
+depend luatex
+depend l3kernel
+depend l3packages
+depend filehook
+depend url
+depend luaxml
+depend lua-uca
+depend lualibs



More information about the tex-live-commits mailing list.