texlive[65357] Master/texmf-dist: citation-style-language (25dec22)

commits+karl at tug.org commits+karl at tug.org
Sun Dec 25 22:19:05 CET 2022


Revision: 65357
          http://tug.org/svn/texlive?view=revision&revision=65357
Author:   karl
Date:     2022-12-25 22:19:05 +0100 (Sun, 25 Dec 2022)
Log Message:
-----------
citation-style-language (25dec22)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.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/doc/man/man1/citeproc-lua.man1.pdf
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bibtex-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-element.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.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-node-date.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.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-style.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-nodes.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.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

Added Paths:
-----------
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-parser.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua

Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2022-12-25 21:19:05 UTC (rev 65357)
@@ -7,6 +7,26 @@
 
 ## [Unreleased]
 
+## [v0.3.0] - 2022-12-25
+
+### Added
+
+- Add author only command `\citeauthor`.
+- Add narrative citation commands `\textcite` and `\citet` ([#17](https://github.com/zepinglee/citeproc-lua/issues/17)).
+- Add parenthetical citation commands `\parencite` and `\citep` for compatibility.
+
+### Changed
+
+- The "-locator" suffixes are removed from the `article-locator` and `title-locator` options.
+- The LaTeX markups in citation prefix is now correctly precessed ([#33](https://github.com/zepinglee/citeproc-lua/issues/33)).
+
+### Fixed
+
+- Fix an error in checking the plurity of `number-of-pages` ([#27](https://github.com/zepinglee/citeproc-lua/issues/27)).
+- Fix an error in converting value `"2nd"` to its ordinal form ([#27](https://github.com/zepinglee/citeproc-lua/issues/27)).
+- Fix missing DOI prefix when used with `hyperref` ([#28](https://github.com/zepinglee/citeproc-lua/issues/28)).
+- Fix special characters (`#` and `%`) in URL ([#30](https://github.com/zepinglee/citeproc-lua/issues/30)).
+
 ## [v0.2.2] - 2022-09-23
 
 ### Fixed
@@ -63,7 +83,8 @@
 
 - Initial CTAN release.
 
-[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.2.2...HEAD
+[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.3.0...HEAD
+[v0.3.0]: https://github.com/zepinglee/citeproc-lua/compare/v0.2.2...0.3.0
 [v0.2.2]: https://github.com/zepinglee/citeproc-lua/compare/v0.2.1...0.2.2
 [v0.2.1]: https://github.com/zepinglee/citeproc-lua/compare/v0.2.0...0.2.1
 [v0.2.0]: https://github.com/zepinglee/citeproc-lua/compare/v0.1.1...0.2.0

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex	2022-12-25 21:19:05 UTC (rev 65357)
@@ -49,7 +49,7 @@
   }%
 }
 
-\date{2022-09-23 v0.2.2}
+\date{2022-12-25 v0.3.0}
 
 \maketitle
 
@@ -290,16 +290,16 @@
   \label{tab:locators}
   \begin{tabular}{lll}
     \toprule
-    \opt{act}             & \opt{folio}     & \opt{section}       \\
-    \opt{appendix}        & \opt{issue}     & \opt{sub-verbo}     \\
-    \opt{article-locator} & \opt{line}      & \opt{supplement}    \\
-    \opt{book}            & \opt{note}      & \opt{table}         \\
-    \opt{canon}           & \opt{opus}      & \opt{timestamp}     \\
-    \opt{chapter}         & \opt{page}      & \opt{title-locator} \\
-    \opt{column}          & \opt{paragraph} & \opt{verse}         \\
-    \opt{elocation}       & \opt{part}      & \opt{version}       \\
-    \opt{equation}        & \opt{rule}      & \opt{volume}        \\
-    \opt{figure}          & \opt{scene}     &                     \\
+    \opt{act}       & \opt{folio}     & \opt{section}    \\
+    \opt{appendix}  & \opt{issue}     & \opt{sub-verbo}  \\
+    \opt{article}   & \opt{line}      & \opt{supplement} \\
+    \opt{book}      & \opt{note}      & \opt{table}      \\
+    \opt{canon}     & \opt{opus}      & \opt{timestamp}  \\
+    \opt{chapter}   & \opt{page}      & \opt{title}      \\
+    \opt{column}    & \opt{paragraph} & \opt{verse}      \\
+    \opt{elocation} & \opt{part}      & \opt{version}    \\
+    \opt{equation}  & \opt{rule}      & \opt{volume}     \\
+    \opt{figure}    & \opt{scene}     &                  \\
     \bottomrule
   \end{tabular}
 \end{table}
@@ -310,12 +310,33 @@
 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.
 
-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}{\parencite,\citep}
+  \begin{syntax}
+    \cs{parencite}\oarg{options}\marg{keys}
+  \end{syntax}
+\end{function}
 
+The \cs{parencite} and \cs{citep} command are aliases of \cs{cite}.
+They are added for compatibility with \pkg{biblatex} and \pkg{natbib} packages.
+If the citation format defined in the CSL style does not have affixes,
+these commands in \pkg{citation-style-language} do not enclose the output with
+brackets, which is different from other packages.
+
+\begin{function}{\textcite,\citet}
+  \begin{syntax}
+    \cs{textcite}\oarg{options}\marg{keys}
+  \end{syntax}
+\end{function}
+
+\DescribeOption{infix}
+These commands proceduce narrative in-text citation where the author name is
+part of the running text followed by the year in parentheses.
+These commands only work with author-date styles.
+An extra option \opt{infix} can be given to specify the text inserted between
+then author and year parts. For example, “Kesey’s early work (1962)” can be
+produced by |\textcite[infix={'s early work}]{ITEM-1}|.
+By default the infix is a space.
+
 \begin{function}{\cites}
   \begin{syntax}
     \cs{cites}\oarg{options}\marg{key}...[options]\marg{key}
@@ -332,6 +353,18 @@
   \cites[prefix = {See }, page = 6]{key1}[section = 2.3]{key2}\relax [Text]
 \end{LaTeXdemo}
 
+\begin{function}{\citeauthor}
+  \begin{syntax}
+    \cs{citeauthor}\marg{key}
+  \end{syntax}
+\end{function}
+
+This command prints the author name.
+If the orginal citation does not contain the author name (e.g., a numeric
+style), an optional |<intext>| element can be suppplied as a sibling to the
+|<citation>| and |<bibliography>| elements in the CSL style (see
+\href{https://citeproc-js.readthedocs.io/en/latest/running.html#citation-flags-with-processcitationcluster}{citeproc-js's documentation} for details).
+
 \begin{function}{\nocite}
   \begin{syntax}
     \cs{nocite}\marg{keys}

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

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bibtex-data.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bibtex-data.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-bibtex-data.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -3,6 +3,12 @@
 return {
   description = "BibTeX CSL mapping",
   types = {
+    abstract = {
+      csl = nil,
+    },
+    ancienttext = {
+      csl = "classic",
+    },
     archival = {
       csl = "collection",
     },
@@ -12,6 +18,15 @@
     article = {
       csl = "article-journal",
     },
+    ["article-journal"] = {
+      csl = "article-journal",
+    },
+    ["article-magazine"] = {
+      csl = "article-magazine",
+    },
+    ["article-newspaper"] = {
+      csl = "article-newspaper",
+    },
     artifactdataset = {
       csl = "dataset",
     },
@@ -33,21 +48,81 @@
     bibnote = {
       csl = nil,
     },
+    bill = {
+      csl = "bill",
+    },
     book = {
       csl = "book",
     },
+    bookinarticle = {
+      csl = nil,
+    },
     bookinbook = {
       csl = "chapter",
     },
+    bookincollection = {
+      csl = nil,
+    },
+    bookininarticle = {
+      csl = nil,
+    },
+    bookininbook = {
+      csl = nil,
+    },
+    bookinincollection = {
+      csl = nil,
+    },
+    bookininproceedings = {
+      csl = nil,
+    },
+    bookininreference = {
+      csl = nil,
+    },
+    bookinjournal = {
+      csl = nil,
+    },
+    bookinmathesis = {
+      csl = nil,
+    },
+    bookinphdthesis = {
+      csl = nil,
+    },
+    bookinproceedings = {
+      csl = nil,
+    },
+    bookinreference = {
+      csl = nil,
+    },
+    bookinthesis = {
+      csl = nil,
+    },
     booklet = {
       csl = "pamphlet",
     },
+    broadcast = {
+      csl = "broadcast",
+    },
     brochure = {
       csl = "pamphlet",
     },
-    cconference = {
+    catalog = {
       csl = nil,
     },
+    cconference = {
+      csl = "paper-conference",
+    },
+    chapter = {
+      csl = "chapter",
+    },
+    classic = {
+      csl = "classic",
+    },
+    classictext = {
+      csl = "classic",
+    },
+    codefragment = {
+      csl = "software",
+    },
     collection = {
       csl = "book",
     },
@@ -60,9 +135,18 @@
     commented = {
       csl = nil,
     },
+    committee = {
+      csl = nil,
+    },
     conference = {
       csl = "paper-conference",
     },
+    conferencepaper = {
+      csl = "paper-conference",
+    },
+    constitution = {
+      csl = "legislation",
+    },
     ctan = {
       csl = "software",
     },
@@ -84,6 +168,9 @@
     customf = {
       csl = nil,
     },
+    data = {
+      csl = nil,
+    },
     database = {
       csl = "dataset",
     },
@@ -90,6 +177,9 @@
     dataset = {
       csl = "dataset",
     },
+    degree = {
+      csl = nil,
+    },
     dictionary = {
       csl = "book",
     },
@@ -96,21 +186,51 @@
     docthesis = {
       csl = "thesis",
     },
+    document = {
+      csl = "document",
+    },
     electronic = {
       csl = "webpage",
     },
+    entry = {
+      csl = "entry",
+    },
+    ["entry-dictionary"] = {
+      csl = "entry-dictionary",
+    },
+    ["entry-encyclopedia"] = {
+      csl = "entry-encyclopedia",
+    },
     eulegislation = {
       csl = "legislation",
     },
+    event = {
+      csl = "event",
+    },
+    exhibcatalog = {
+      csl = nil,
+    },
+    figure = {
+      csl = "figure",
+    },
     footnote = {
       csl = nil,
     },
+    funding = {
+      csl = nil,
+    },
     game = {
       csl = "software",
     },
+    gitcommit = {
+      csl = nil,
+    },
     govpub = {
       csl = "regulation",
     },
+    graphic = {
+      csl = "graphic",
+    },
     habthesis = {
       csl = "thesis",
     },
@@ -117,6 +237,9 @@
     heading = {
       csl = nil,
     },
+    hearing = {
+      csl = "hearing",
+    },
     hidden = {
       csl = nil,
     },
@@ -123,15 +246,60 @@
     image = {
       csl = "graphic",
     },
+    inarticle = {
+      csl = nil,
+    },
+    inaudio = {
+      csl = nil,
+    },
     inbook = {
       csl = "chapter",
     },
+    incatalog = {
+      csl = nil,
+    },
     incollection = {
       csl = "chapter",
     },
+    incommentary = {
+      csl = nil,
+    },
+    inexhibcatalog = {
+      csl = nil,
+    },
+    ininarticle = {
+      csl = nil,
+    },
+    ininbook = {
+      csl = nil,
+    },
+    inincollection = {
+      csl = nil,
+    },
+    ininproceedings = {
+      csl = nil,
+    },
+    ininreference = {
+      csl = nil,
+    },
+    inlexicon = {
+      csl = nil,
+    },
     inloosecollection = {
       csl = "chapter",
     },
+    inmathesis = {
+      csl = nil,
+    },
+    inmovie = {
+      csl = nil,
+    },
+    inmusic = {
+      csl = nil,
+    },
+    inphdthesis = {
+      csl = nil,
+    },
     inpress = {
       csl = "article",
     },
@@ -147,6 +315,15 @@
     internet = {
       csl = "webpage",
     },
+    interview = {
+      csl = "interview",
+    },
+    inthesis = {
+      csl = nil,
+    },
+    invideo = {
+      csl = nil,
+    },
     journalpart = {
       csl = nil,
     },
@@ -154,20 +331,32 @@
       csl = "periodical",
     },
     jurisdiction = {
-      csl = nil,
+      csl = "legal_case",
     },
     jurthesis = {
       csl = "thesis",
     },
+    legadminmaterial = {
+      csl = "regulation",
+    },
     legal = {
       csl = "treaty",
     },
+    legal_case = {
+      csl = "legal_case",
+    },
     legislation = {
       csl = "legislation",
     },
+    legmaterial = {
+      csl = "legislation",
+    },
     letter = {
       csl = "personal_communication",
     },
+    letters = {
+      csl = "personal_communication",
+    },
     lexicon = {
       csl = "book",
     },
@@ -177,6 +366,9 @@
     manual = {
       csl = "report",
     },
+    manuscript = {
+      csl = "manuscript",
+    },
     map = {
       csl = "map",
     },
@@ -192,6 +384,9 @@
     misc = {
       csl = "document",
     },
+    mlasource = {
+      csl = nil,
+    },
     monograph = {
       csl = "book",
     },
@@ -198,6 +393,9 @@
     monography = {
       csl = "book",
     },
+    motion_picture = {
+      csl = "motion_picture",
+    },
     movie = {
       csl = "motion_picture",
     },
@@ -204,6 +402,9 @@
     music = {
       csl = "song",
     },
+    musical_score = {
+      csl = "musical_score",
+    },
     mvbook = {
       csl = "book",
     },
@@ -210,6 +411,12 @@
     mvcollection = {
       csl = "book",
     },
+    mvcommentary = {
+      csl = nil,
+    },
+    mvlexicon = {
+      csl = nil,
+    },
     mvproceedings = {
       csl = "book",
     },
@@ -216,9 +423,15 @@
     mvreference = {
       csl = "book",
     },
+    nameonly = {
+      csl = nil,
+    },
     news = {
       csl = "article-newspaper",
     },
+    newsarticle = {
+      csl = "article-newspaper",
+    },
     newspaper = {
       csl = "article-newspaper",
     },
@@ -228,6 +441,12 @@
     other = {
       csl = nil,
     },
+    pamphlet = {
+      csl = "pamphlet",
+    },
+    ["paper-conference"] = {
+      csl = "paper-conference",
+    },
     patent = {
       csl = "patent",
     },
@@ -237,9 +456,18 @@
     periodical = {
       csl = "periodical",
     },
+    personal_communication = {
+      csl = "personal_communication",
+    },
     phdthesis = {
       csl = "thesis",
     },
+    post = {
+      csl = "post",
+    },
+    ["post-weblog"] = {
+      csl = "post-weblog",
+    },
     preamble = {
       csl = nil,
     },
@@ -258,6 +486,9 @@
     reference = {
       csl = "book",
     },
+    regulation = {
+      csl = "regulation",
+    },
     report = {
       csl = "report",
     },
@@ -267,6 +498,15 @@
     review = {
       csl = "review",
     },
+    ["review-book"] = {
+      csl = "review-book",
+    },
+    school = {
+      csl = nil,
+    },
+    seminarpaper = {
+      csl = nil,
+    },
     set = {
       csl = nil,
     },
@@ -276,6 +516,18 @@
     software = {
       csl = "software",
     },
+    softwaremodule = {
+      csl = "software",
+    },
+    softwareversion = {
+      csl = nil,
+    },
+    song = {
+      csl = "song",
+    },
+    speech = {
+      csl = "speech",
+    },
     standard = {
       csl = "standard",
     },
@@ -282,6 +534,9 @@
     string = {
       csl = nil,
     },
+    student = {
+      csl = nil,
+    },
     suppbook = {
       csl = "chapter",
     },
@@ -291,6 +546,12 @@
     suppperiodical = {
       csl = "article",
     },
+    talk = {
+      csl = "speech",
+    },
+    teaching = {
+      csl = nil,
+    },
     techreport = {
       csl = "report",
     },
@@ -300,6 +561,9 @@
     thesis = {
       csl = "thesis",
     },
+    treaty = {
+      csl = "treaty",
+    },
     uklegislation = {
       csl = "legislation",
     },
@@ -313,7 +577,7 @@
       csl = "webpage",
     },
     website = {
-      csl = nil,
+      csl = "webpage",
     },
     www = {
       csl = "webpage",
@@ -327,12 +591,28 @@
       csl = "abstract",
       type = "literal",
     },
+    abstractloc = {
+      csl = nil,
+      type = "literal",
+    },
+    abstracturl = {
+      csl = nil,
+      type = "verbatim",
+    },
+    accessed = {
+      csl = "accessed",
+    },
     account = {
       csl = nil,
     },
     acronym = {
       csl = nil,
+      type = "literal",
     },
+    actor = {
+      csl = "performer",
+      type = "name",
+    },
     add = {
       csl = nil,
     },
@@ -352,11 +632,28 @@
     },
     advisor = {
       csl = nil,
+      type = "name",
     },
     afterword = {
       csl = nil,
       type = "name",
     },
+    alternatetitle = {
+      csl = nil,
+      type = "literal",
+    },
+    alternatetitlescript = {
+      csl = nil,
+      type = "literal",
+    },
+    amendment = {
+      csl = nil,
+      type = "literal",
+    },
+    amount = {
+      csl = nil,
+      type = "integer",
+    },
     annotate = {
       csl = "note",
     },
@@ -375,12 +672,33 @@
     annotelanguage = {
       csl = nil,
     },
+    anonstring = {
+      csl = nil,
+      type = "name",
+    },
+    appentry = {
+      csl = nil,
+      type = "literal",
+    },
     applicant = {
       csl = nil,
     },
+    arachne = {
+      csl = nil,
+      type = "verbatim",
+    },
     archive = {
       csl = "archive",
     },
+    ["archive-place"] = {
+      csl = "archive-place",
+    },
+    archive_collection = {
+      csl = "archive_collection",
+    },
+    archive_location = {
+      csl = "archive_location",
+    },
     archiveprefix = {
       csl = "archive",
       type = "literal",
@@ -388,8 +706,21 @@
     archname = {
       csl = "archive",
     },
+    arthist = {
+      csl = nil,
+      type = "integer",
+    },
+    arthistdate = {
+      csl = nil,
+      type = "date",
+    },
+    article = {
+      csl = nil,
+      type = "literal",
+    },
     articleno = {
       csl = nil,
+      type = "literal",
     },
     arxiv = {
       csl = nil,
@@ -401,16 +732,27 @@
       csl = "author",
       type = "name",
     },
+    authoraddon = {
+      csl = nil,
+      type = "name",
+    },
     authorcountry = {
       csl = nil,
+      type = "literal",
     },
     authorfa = {
       csl = nil,
     },
+    authority = {
+      csl = "authority",
+    },
     authortype = {
       csl = nil,
       type = "key",
     },
+    ["available-date"] = {
+      csl = "available-date",
+    },
     bibsource = {
       csl = nil,
     },
@@ -426,16 +768,92 @@
     birthyear = {
       csl = nil,
     },
+    blogsubtitle = {
+      csl = nil,
+      type = "literal",
+    },
+    blogtitle = {
+      csl = "title",
+      type = "literal",
+    },
+    blogtitleaddon = {
+      csl = nil,
+      type = "literal",
+    },
+    book = {
+      csl = nil,
+      type = "range",
+    },
     bookaddress = {
+      csl = "publisher-place",
+    },
+    bookafterword = {
       csl = nil,
+      type = "name",
     },
+    bookannotator = {
+      csl = nil,
+      type = "name",
+    },
     bookauthor = {
       csl = "container-author",
       type = "name",
     },
+    bookauthortype = {
+      csl = nil,
+      type = "literal",
+    },
+    bookbooksubtitle = {
+      csl = nil,
+      type = "literal",
+    },
+    bookbooktitle = {
+      csl = nil,
+      type = "literal",
+    },
+    bookbooktitleaddon = {
+      csl = nil,
+      type = "literal",
+    },
+    bookbooktitlekey = {
+      csl = nil,
+      type = "key",
+    },
+    bookbookvolume = {
+      csl = nil,
+      type = "literal",
+    },
+    bookcommentator = {
+      csl = nil,
+      type = "name",
+    },
+    bookcredits = {
+      csl = nil,
+      type = "literal",
+    },
+    bookeditor = {
+      csl = "editor",
+      type = "name",
+    },
+    bookforeword = {
+      csl = nil,
+      type = "name",
+    },
+    bookineditor = {
+      csl = nil,
+      type = "name",
+    },
+    bookintroduction = {
+      csl = nil,
+      type = "name",
+    },
     booklanguage = {
       csl = nil,
     },
+    bookoriglanguage = {
+      csl = nil,
+      type = "key",
+    },
     bookpages = {
       csl = nil,
     },
@@ -443,6 +861,10 @@
       csl = nil,
       type = "key",
     },
+    books = {
+      csl = nil,
+      type = "literal",
+    },
     booksubtitle = {
       csl = nil,
       type = "literal",
@@ -455,6 +877,29 @@
       csl = nil,
       type = "literal",
     },
+    booktitlekey = {
+      csl = nil,
+      type = "key",
+    },
+    booktitlenote = {
+      csl = nil,
+      type = "literal",
+    },
+    booktranslator = {
+      csl = "translator",
+      type = "name",
+    },
+    bookvolume = {
+      csl = nil,
+      type = "literal",
+    },
+    bookyear = {
+      csl = nil,
+      type = "literal",
+    },
+    ["call-number"] = {
+      csl = "call-number",
+    },
     caption = {
       csl = nil,
     },
@@ -467,6 +912,10 @@
     casenumber = {
       csl = nil,
     },
+    catalog = {
+      csl = nil,
+      type = "literal",
+    },
     category = {
       csl = nil,
     },
@@ -474,12 +923,19 @@
       csl = nil,
     },
     chair = {
-      csl = nil,
+      csl = "chair",
     },
     chapter = {
       csl = "chapter-number",
       type = "literal",
     },
+    ["chapter-number"] = {
+      csl = "chapter-number",
+    },
+    citation = {
+      csl = nil,
+      type = "literal",
+    },
     citedate = {
       csl = nil,
     },
@@ -486,6 +942,18 @@
     city = {
       csl = nil,
     },
+    classes = {
+      csl = nil,
+      type = "entrykey",
+    },
+    claves = {
+      csl = nil,
+      type = "literal",
+    },
+    claves_definition = {
+      csl = nil,
+      type = "literal",
+    },
     code = {
       csl = nil,
     },
@@ -494,10 +962,28 @@
     },
     collaboration = {
       csl = nil,
+      type = "literal",
     },
     collator = {
       csl = nil,
     },
+    collection = {
+      csl = nil,
+      type = "literal",
+    },
+    ["collection-editor"] = {
+      csl = "collection-editor",
+    },
+    ["collection-number"] = {
+      csl = "collection-number",
+    },
+    ["collection-title"] = {
+      csl = "collection-title",
+    },
+    columns = {
+      csl = nil,
+      type = "literal",
+    },
     commentator = {
       csl = nil,
       type = "name",
@@ -505,8 +991,23 @@
     commit = {
       csl = nil,
     },
+    commithash = {
+      csl = nil,
+      type = "literal",
+    },
+    committee = {
+      csl = nil,
+      type = "name",
+    },
     compiler = {
+      csl = "compiler",
+    },
+    composer = {
+      csl = "composer",
+    },
+    concentration = {
       csl = nil,
+      type = "literal",
     },
     condition = {
       csl = nil,
@@ -520,50 +1021,89 @@
     ["conference-year"] = {
       csl = nil,
     },
+    ["container-author"] = {
+      csl = "container-author",
+    },
+    ["container-title"] = {
+      csl = "container-title",
+    },
+    ["container-title-short"] = {
+      csl = "container-title-short",
+    },
+    contents = {
+      csl = nil,
+      type = "literal",
+    },
+    contributor = {
+      csl = "contributor",
+    },
+    contributora = {
+      csl = nil,
+      type = "literal",
+    },
+    contributorb = {
+      csl = nil,
+      type = "literal",
+    },
+    copublisher = {
+      csl = nil,
+      type = "literal",
+    },
     copy = {
       csl = nil,
     },
     country = {
-      csl = nil,
+      csl = "jurisdiction",
     },
     credits = {
       csl = nil,
+      type = "literal",
     },
     crossref = {
       csl = nil,
       type = "entrykey",
     },
-    ["ctrl-article-title"] = {
-      csl = nil,
+    curator = {
+      csl = "curator",
     },
-    ["ctrl-chapter-title"] = {
+    currency = {
       csl = nil,
+      type = "literal",
     },
-    ["ctrl-doi"] = {
+    custom = {
+      csl = "custom",
+    },
+    dataset = {
       csl = nil,
     },
-    ["ctrl-etal-firstonly"] = {
+    date = {
+      csl = "issued",
+      type = "date",
+    },
+    ["date+an"] = {
       csl = nil,
+      type = "literal",
     },
-    ["ctrl-etal-number"] = {
+    datea = {
       csl = nil,
+      type = "literal",
     },
-    ["ctrl-link-doi"] = {
+    dateaddon = {
       csl = nil,
+      type = "literal",
     },
-    ["ctrl-use-doi-all"] = {
+    dateb = {
       csl = nil,
+      type = "literal",
     },
-    ["ctrl-use-title"] = {
+    datetype = {
       csl = nil,
+      type = "literal",
     },
-    dataset = {
+    dating = {
       csl = nil,
+      type = "literal",
     },
-    date = {
-      csl = "issued",
-      type = "date",
-    },
     day = {
       csl = nil,
     },
@@ -570,11 +1110,28 @@
     dayfiled = {
       csl = nil,
     },
+    decision = {
+      csl = nil,
+      type = "literal",
+    },
+    decisionname = {
+      csl = nil,
+      type = "literal",
+    },
     definition = {
       csl = nil,
     },
+    degree = {
+      csl = nil,
+      type = "literal",
+    },
+    degreelist = {
+      csl = nil,
+      type = "entrykey",
+    },
     department = {
       csl = nil,
+      type = "literal",
     },
     description = {
       csl = nil,
@@ -583,11 +1140,22 @@
       csl = nil,
     },
     dimensions = {
-      csl = nil,
+      csl = "dimensions",
     },
+    director = {
+      csl = "director",
+      type = "name",
+    },
     dissyear = {
       csl = nil,
     },
+    distinctURL = {
+      csl = nil,
+      type = "literal",
+    },
+    division = {
+      csl = "division",
+    },
     doi = {
       csl = "DOI",
       type = "verbatim",
@@ -598,10 +1166,22 @@
     dummy = {
       csl = nil,
     },
+    duration = {
+      csl = nil,
+      type = "literal",
+    },
+    durationtype = {
+      csl = nil,
+      type = "literal",
+    },
     edition = {
       csl = "edition",
       type = "literal",
     },
+    editioncredits = {
+      csl = nil,
+      type = "literal",
+    },
     editor = {
       csl = "editor",
       type = "name",
@@ -610,6 +1190,14 @@
       csl = nil,
       type = "name",
     },
+    editoraaddon = {
+      csl = nil,
+      type = "literal",
+    },
+    editoraddon = {
+      csl = nil,
+      type = "name",
+    },
     editoratype = {
       csl = nil,
       type = "key",
@@ -630,6 +1218,9 @@
       csl = nil,
       type = "key",
     },
+    ["editorial-director"] = {
+      csl = "editorial-director",
+    },
     editortype = {
       csl = nil,
       type = "key",
@@ -641,6 +1232,10 @@
     email = {
       csl = nil,
     },
+    endbookyear = {
+      csl = nil,
+      type = "literal",
+    },
     endnumber = {
       csl = nil,
     },
@@ -650,6 +1245,10 @@
     endyear = {
       csl = nil,
     },
+    englishabstract = {
+      csl = nil,
+      type = "literal",
+    },
     englishtitle = {
       csl = nil,
     },
@@ -672,6 +1271,18 @@
       csl = nil,
       type = "literal",
     },
+    eprintdate = {
+      csl = nil,
+      type = "date",
+    },
+    eprintday = {
+      csl = nil,
+      type = "datepart",
+    },
+    eprintmonth = {
+      csl = nil,
+      type = "datepart",
+    },
     eprints = {
       csl = nil,
     },
@@ -679,6 +1290,10 @@
       csl = "archive",
       type = "literal",
     },
+    eprintyear = {
+      csl = nil,
+      type = "datepart",
+    },
     erratumeid = {
       csl = nil,
     },
@@ -697,10 +1312,34 @@
     erratumyear = {
       csl = nil,
     },
+    event = {
+      csl = "event-title",
+    },
+    ["event-date"] = {
+      csl = "event-date",
+    },
+    ["event-place"] = {
+      csl = "event-place",
+    },
+    ["event-title"] = {
+      csl = "event-title",
+    },
     eventdate = {
       csl = "event-date",
       type = "date",
     },
+    eventdatelanguage = {
+      csl = nil,
+      type = "verbatim",
+    },
+    eventnumber = {
+      csl = nil,
+      type = "integer",
+    },
+    eventsubtitle = {
+      csl = nil,
+      type = "literal",
+    },
     eventtitle = {
       csl = "event-title",
       type = "literal",
@@ -709,13 +1348,64 @@
       csl = nil,
       type = "literal",
     },
+    eventtype = {
+      csl = nil,
+      type = "literal",
+    },
     eventyear = {
       csl = nil,
     },
+    execdirector = {
+      csl = "director",
+      type = "name",
+    },
+    execproducer = {
+      csl = "executive-producer",
+      type = "name",
+    },
     execute = {
       csl = nil,
       type = "code",
     },
+    ["executive-producer"] = {
+      csl = "executive-producer",
+    },
+    exhibfirstdate = {
+      csl = "event-date",
+      type = "date",
+    },
+    exhibfirstlocation = {
+      csl = "event-place",
+      type = "literal",
+    },
+    exhibfirstmuseum = {
+      csl = nil,
+      type = "literal",
+    },
+    exhibseconddate = {
+      csl = nil,
+      type = "date",
+    },
+    exhibsecondlocation = {
+      csl = nil,
+      type = "literal",
+    },
+    exhibsecondmuseum = {
+      csl = nil,
+      type = "literal",
+    },
+    exhibthirddate = {
+      csl = nil,
+      type = "date",
+    },
+    exhibthirdlocation = {
+      csl = nil,
+      type = "literal",
+    },
+    exhibthirdmuseum = {
+      csl = nil,
+      type = "literal",
+    },
     faddress = {
       csl = nil,
     },
@@ -725,6 +1415,14 @@
     fax = {
       csl = nil,
     },
+    festschrift = {
+      csl = nil,
+      type = "name",
+    },
+    festschriftaddon = {
+      csl = nil,
+      type = "literal",
+    },
     file = {
       csl = nil,
       type = "verbatim",
@@ -751,9 +1449,21 @@
     fpublisher = {
       csl = nil,
     },
+    frenchabstract = {
+      csl = nil,
+      type = "literal",
+    },
     ftitle = {
       csl = nil,
     },
+    fulleventdate = {
+      csl = nil,
+      type = "date",
+    },
+    funder = {
+      csl = nil,
+      type = "literal",
+    },
     furtherresp = {
       csl = nil,
     },
@@ -764,14 +1474,37 @@
       csl = nil,
       type = "literal",
     },
+    genre = {
+      csl = "genre",
+    },
     germanpages = {
       csl = nil,
     },
+    gpa = {
+      csl = nil,
+      type = "literal",
+    },
     group = {
       csl = nil,
     },
+    guest = {
+      csl = "guest",
+    },
+    hal_id = {
+      csl = nil,
+      type = "verbatim",
+    },
+    hal_version = {
+      csl = "version",
+      type = "verbatim",
+    },
+    handle = {
+      csl = nil,
+      type = "literal",
+    },
     heading = {
       csl = nil,
+      type = "literal",
     },
     hereafter = {
       csl = nil,
@@ -780,6 +1513,13 @@
       csl = nil,
       type = "name",
     },
+    honors = {
+      csl = nil,
+      type = "literal",
+    },
+    host = {
+      csl = "host",
+    },
     howcited = {
       csl = nil,
     },
@@ -802,8 +1542,24 @@
       csl = nil,
     },
     illustrator = {
+      csl = "illustrator",
+    },
+    impactfactor = {
       csl = nil,
+      type = "literal",
     },
+    inafterword = {
+      csl = nil,
+      type = "name",
+    },
+    inannotator = {
+      csl = nil,
+      type = "name",
+    },
+    incommentator = {
+      csl = nil,
+      type = "name",
+    },
     indexsorttitle = {
       csl = nil,
       type = "literal",
@@ -812,10 +1568,37 @@
       csl = nil,
       type = "literal",
     },
+    ineditor = {
+      csl = nil,
+      type = "name",
+    },
+    inforeword = {
+      csl = nil,
+      type = "name",
+    },
+    inintroduction = {
+      csl = nil,
+      type = "name",
+    },
     institution = {
       csl = "publisher",
       type = "literal",
     },
+    interviewee = {
+      csl = nil,
+      type = "name",
+    },
+    interviewer = {
+      csl = "interviewer",
+    },
+    intranslator = {
+      csl = nil,
+      type = "name",
+    },
+    introducedin = {
+      csl = nil,
+      type = "literal",
+    },
     introduction = {
       csl = nil,
       type = "name",
@@ -824,10 +1607,11 @@
       csl = nil,
     },
     inventor = {
-      csl = nil,
+      csl = "author",
     },
     ipc = {
       csl = nil,
+      type = "literal",
     },
     isan = {
       csl = nil,
@@ -862,6 +1646,13 @@
       csl = "issue",
       type = "literal",
     },
+    issued = {
+      csl = "issued",
+    },
+    issues = {
+      csl = nil,
+      type = "literal",
+    },
     issuesubtitle = {
       csl = nil,
       type = "literal",
@@ -881,10 +1672,21 @@
     jfmnumber = {
       csl = nil,
     },
+    jointauthor = {
+      csl = nil,
+      type = "name",
+    },
+    jointauthortype = {
+      csl = nil,
+      type = "literal",
+    },
     journal = {
       csl = "container-title",
       type = "literal",
     },
+    journalabbreviation = {
+      csl = "container-title-short",
+    },
     journalsubtitle = {
       csl = nil,
       type = "literal",
@@ -900,6 +1702,14 @@
       csl = nil,
       type = "literal",
     },
+    journalyear = {
+      csl = nil,
+      type = "literal",
+    },
+    jstor = {
+      csl = nil,
+      type = "verbatim",
+    },
     juraauthor = {
       csl = nil,
     },
@@ -906,10 +1716,16 @@
     juratitle = {
       csl = nil,
     },
+    jurisdiction = {
+      csl = "jurisdiction",
+    },
     key = {
       csl = nil,
       type = "literal",
     },
+    keyword = {
+      csl = "keyword",
+    },
     keywords = {
       csl = nil,
       type = "keyword",
@@ -935,10 +1751,15 @@
     },
     lastaccessed = {
       csl = "accessed",
+      type = "literal",
     },
     lastchecked = {
       csl = "accessed",
     },
+    layer = {
+      csl = nil,
+      type = "literal",
+    },
     lccn = {
       csl = nil,
     },
@@ -946,6 +1767,14 @@
       csl = nil,
       type = "literal",
     },
+    librarylocation = {
+      csl = "archive-place",
+      type = "literal",
+    },
+    license = {
+      csl = "license",
+      type = "literal",
+    },
     lista = {
       csl = nil,
       type = "literal",
@@ -977,6 +1806,14 @@
       csl = "publisher-place",
       type = "literal",
     },
+    locationa = {
+      csl = nil,
+      type = "literal",
+    },
+    locationb = {
+      csl = nil,
+      type = "literal",
+    },
     madadurltest = {
       csl = nil,
     },
@@ -986,10 +1823,22 @@
     main1 = {
       csl = nil,
     },
+    mainauthor = {
+      csl = nil,
+      type = "name",
+    },
+    maineditor = {
+      csl = nil,
+      type = "name",
+    },
     mainsubtitle = {
       csl = nil,
       type = "literal",
     },
+    maintainer = {
+      csl = nil,
+      type = "name",
+    },
     maintitle = {
       csl = nil,
       type = "literal",
@@ -998,6 +1847,18 @@
       csl = nil,
       type = "literal",
     },
+    maintitlenote = {
+      csl = nil,
+      type = "literal",
+    },
+    maintranslator = {
+      csl = nil,
+      type = "name",
+    },
+    major = {
+      csl = nil,
+      type = "literal",
+    },
     majorcode = {
       csl = nil,
     },
@@ -1015,6 +1876,7 @@
     },
     media = {
       csl = "medium",
+      type = "key",
     },
     medium = {
       csl = "medium",
@@ -1022,6 +1884,10 @@
     meeting = {
       csl = nil,
     },
+    minor = {
+      csl = nil,
+      type = "literal",
+    },
     misctitle = {
       csl = nil,
     },
@@ -1044,8 +1910,17 @@
     mrnumber = {
       csl = nil,
     },
+    multieventdate = {
+      csl = nil,
+      type = "literal",
+    },
+    multivenue = {
+      csl = nil,
+      type = "literal",
+    },
     name = {
       csl = nil,
+      type = "name",
     },
     namea = {
       csl = nil,
@@ -1075,6 +1950,10 @@
       csl = nil,
       type = "key",
     },
+    narrator = {
+      csl = "narrator",
+      type = "name",
+    },
     nationality = {
       csl = nil,
     },
@@ -1084,8 +1963,13 @@
     newpage = {
       csl = nil,
     },
+    newsdate = {
+      csl = nil,
+      type = "date",
+    },
     newspaper = {
       csl = "container-title",
+      type = "literal",
     },
     nickname = {
       csl = nil,
@@ -1119,18 +2003,57 @@
       csl = "number",
       type = "literal",
     },
+    ["number-of-pages"] = {
+      csl = "number-of-pages",
+    },
+    ["number-of-volumes"] = {
+      csl = "number-of-volumes",
+    },
+    numbera = {
+      csl = nil,
+      type = "literal",
+    },
+    numberb = {
+      csl = nil,
+      type = "literal",
+    },
+    numcites = {
+      csl = nil,
+      type = "integer",
+    },
+    numlectures = {
+      csl = nil,
+      type = "integer",
+    },
     numpages = {
+      csl = "number-of-pages",
+      type = "literal",
+    },
+    numstudents = {
       csl = nil,
+      type = "integer",
     },
     oaddress = {
+      csl = "original-publisher-place",
+    },
+    officialpages = {
       csl = nil,
+      type = "literal",
     },
+    officialvolume = {
+      csl = nil,
+      type = "literal",
+    },
+    openaccess = {
+      csl = nil,
+      type = "literal",
+    },
     options = {
       csl = nil,
       type = "option",
     },
     opublisher = {
-      csl = nil,
+      csl = "original-publisher",
     },
     ["org-short"] = {
       csl = nil,
@@ -1139,10 +2062,40 @@
       csl = "publisher",
       type = "literal",
     },
+    organizer = {
+      csl = "organizer",
+    },
     origdate = {
       csl = "original-date",
       type = "date",
     },
+    ["origdate+an"] = {
+      csl = nil,
+      type = "literal",
+    },
+    origdatetype = {
+      csl = nil,
+      type = "literal",
+    },
+    origin = {
+      csl = nil,
+      type = "literal",
+    },
+    ["original-author"] = {
+      csl = "original-author",
+    },
+    ["original-date"] = {
+      csl = "original-date",
+    },
+    ["original-publisher"] = {
+      csl = "original-publisher",
+    },
+    ["original-publisher-place"] = {
+      csl = "original-publisher-place",
+    },
+    ["original-title"] = {
+      csl = "original-title",
+    },
     originaladdress = {
       csl = "original-publisher-place",
     },
@@ -1185,13 +2138,24 @@
       csl = "original-publisher",
       type = "literal",
     },
+    origrealdate = {
+      csl = nil,
+      type = "date",
+    },
     origtitle = {
       csl = "original-title",
       type = "literal",
     },
+    owner = {
+      csl = nil,
+      type = "name",
+    },
     oyear = {
       csl = nil,
     },
+    page = {
+      csl = "page",
+    },
     pagename = {
       csl = nil,
     },
@@ -1210,10 +2174,41 @@
     paper = {
       csl = nil,
     },
+    pardate = {
+      csl = nil,
+      type = "date",
+    },
+    parpages = {
+      csl = nil,
+      type = "range",
+    },
+    parreporter = {
+      csl = nil,
+      type = "literal",
+    },
+    parseries = {
+      csl = nil,
+      type = "literal",
+    },
     part = {
       csl = "part",
       type = "literal",
     },
+    ["part-title"] = {
+      csl = "part-title",
+    },
+    parts = {
+      csl = nil,
+      type = "literal",
+    },
+    parttitle = {
+      csl = nil,
+      type = "literal",
+    },
+    parvolume = {
+      csl = nil,
+      type = "integer",
+    },
     patentid = {
       csl = nil,
     },
@@ -1221,6 +2216,13 @@
       csl = nil,
       type = "verbatim",
     },
+    peerreview = {
+      csl = nil,
+      type = "literal",
+    },
+    performer = {
+      csl = "performer",
+    },
     phone = {
       csl = nil,
     },
@@ -1230,18 +2232,36 @@
     pii = {
       csl = nil,
     },
+    pmc = {
+      csl = nil,
+      type = "literal",
+    },
     pmcid = {
       csl = "PMCID",
+      type = "literal",
     },
+    pmid = {
+      csl = "PMID",
+      type = "literal",
+    },
     prebibitem = {
       csl = nil,
     },
     preface = {
       csl = nil,
+      type = "name",
     },
     preprint = {
       csl = nil,
     },
+    presentationtype = {
+      csl = nil,
+      type = "literal",
+    },
+    presenter = {
+      csl = nil,
+      type = "name",
+    },
     presort = {
       csl = nil,
       type = "literal",
@@ -1256,35 +2276,92 @@
     printing = {
       csl = nil,
     },
+    ["printing-number"] = {
+      csl = "printing-number",
+    },
     prioritycountry = {
       csl = nil,
+      type = "literal",
     },
     prioritydate = {
       csl = nil,
+      type = "date",
     },
     prioritynumber = {
       csl = nil,
+      type = "literal",
     },
+    producer = {
+      csl = "producer",
+      type = "name",
+    },
+    production = {
+      csl = nil,
+      type = "literal",
+    },
+    protocollessurl = {
+      csl = nil,
+      type = "uri",
+    },
     pseudonym = {
       csl = nil,
     },
     publication = {
       csl = nil,
+      type = "literal",
     },
     publicationdate = {
       csl = nil,
+      type = "date",
     },
+    publicprice = {
+      csl = nil,
+      type = "literal",
+    },
     publisher = {
       csl = "publisher",
       type = "literal",
     },
+    ["publisher-place"] = {
+      csl = "publisher-place",
+    },
+    publishera = {
+      csl = nil,
+      type = "literal",
+    },
+    publisherb = {
+      csl = nil,
+      type = "literal",
+    },
     pubmed = {
       csl = "PMID",
     },
     pubstate = {
-      csl = nil,
+      csl = "status",
       type = "key",
     },
+    rawauthor = {
+      csl = nil,
+      type = "literal",
+    },
+    realauthor = {
+      csl = nil,
+      type = "name",
+    },
+    realdate = {
+      csl = nil,
+      type = "date",
+    },
+    realeditor = {
+      csl = nil,
+      type = "name",
+    },
+    recipient = {
+      csl = "recipient",
+    },
+    references = {
+      csl = "references",
+    },
     related = {
       csl = nil,
       type = "entrykey",
@@ -1301,6 +2378,14 @@
       csl = nil,
       type = "literal",
     },
+    reportnumber = {
+      csl = nil,
+      type = "literal",
+    },
+    repository = {
+      csl = nil,
+      type = "uri",
+    },
     ["reprinted-from"] = {
       csl = nil,
     },
@@ -1316,33 +2401,129 @@
     },
     requestnumber = {
       csl = nil,
+      type = "literal",
     },
     responsible = {
       csl = nil,
     },
+    revdauthor = {
+      csl = nil,
+      type = "name",
+    },
+    revdeditor = {
+      csl = nil,
+      type = "name",
+    },
+    revdshorttitle = {
+      csl = nil,
+      type = "literal",
+    },
+    revdsubtitle = {
+      csl = nil,
+      type = "literal",
+    },
+    revdtitle = {
+      csl = nil,
+      type = "literal",
+    },
+    revdtitleaddon = {
+      csl = nil,
+      type = "literal",
+    },
     review = {
       csl = nil,
     },
+    ["reviewed-author"] = {
+      csl = "reviewed-author",
+    },
+    ["reviewed-genre"] = {
+      csl = "reviewed-genre",
+    },
+    ["reviewed-title"] = {
+      csl = "reviewed-title",
+    },
+    reviewer = {
+      csl = nil,
+      type = "name",
+    },
+    reviewerinstitution = {
+      csl = nil,
+      type = "literal",
+    },
     revision = {
       csl = nil,
     },
+    role = {
+      csl = nil,
+      type = "key",
+    },
+    scale = {
+      csl = "scale",
+    },
     school = {
       csl = "publisher",
       type = "literal",
     },
+    science = {
+      csl = nil,
+      type = "literal",
+    },
     score = {
       csl = nil,
     },
+    scribe = {
+      csl = nil,
+      type = "name",
+    },
+    script = {
+      csl = nil,
+      type = "literal",
+    },
+    ["script-writer"] = {
+      csl = "script-writer",
+    },
+    scriptwriter = {
+      csl = nil,
+      type = "name",
+    },
     section = {
+      csl = "section",
+      type = "literal",
+    },
+    semesters = {
       csl = nil,
+      type = "entrykey",
     },
     series = {
       csl = "collection-title",
       type = "literal",
     },
+    ["series-creator"] = {
+      csl = "series-creator",
+    },
+    seriesaddon = {
+      csl = nil,
+      type = "literal",
+    },
     seriesedition = {
       csl = nil,
     },
+    serieseditor = {
+      csl = nil,
+      type = "name",
+    },
+    seriesseries = {
+      csl = nil,
+      type = "literal",
+    },
+    setlabel = {
+      csl = nil,
+      type = "literal",
+    },
+    shelfmark = {
+      csl = nil,
+      type = "literal",
+    },
     short = {
       csl = nil,
     },
@@ -1353,10 +2534,30 @@
       csl = nil,
       type = "name",
     },
+    shortblog = {
+      csl = nil,
+      type = "literal",
+    },
+    shortbookauthor = {
+      csl = nil,
+      type = "name",
+    },
+    shortbooktitle = {
+      csl = nil,
+      type = "literal",
+    },
+    shortcollection = {
+      csl = nil,
+      type = "literal",
+    },
     shorteditor = {
       csl = nil,
       type = "name",
     },
+    shortform = {
+      csl = nil,
+      type = "literal",
+    },
     shorthand = {
       csl = nil,
       type = "literal",
@@ -1365,10 +2566,34 @@
       csl = nil,
       type = "literal",
     },
+    shortissuetitle = {
+      csl = nil,
+      type = "literal",
+    },
     shortjournal = {
       csl = "container-title-short",
       type = "literal",
     },
+    shortlabeltitle = {
+      csl = nil,
+      type = "literal",
+    },
+    shortlibrary = {
+      csl = nil,
+      type = "literal",
+    },
+    shortmaintitle = {
+      csl = nil,
+      type = "literal",
+    },
+    shortorganization = {
+      csl = nil,
+      type = "literal",
+    },
+    shortserie = {
+      csl = nil,
+      type = "literal",
+    },
     shortseries = {
       csl = nil,
       type = "literal",
@@ -1376,10 +2601,22 @@
     shortsubarchive = {
       csl = nil,
     },
+    shortsubseries = {
+      csl = nil,
+      type = "literal",
+    },
+    shortsubsubseries = {
+      csl = nil,
+      type = "literal",
+    },
     shorttitle = {
       csl = "title-short",
       type = "literal",
     },
+    shorttranslator = {
+      csl = nil,
+      type = "name",
+    },
     shorturl = {
       csl = nil,
     },
@@ -1395,6 +2632,10 @@
     sig4 = {
       csl = nil,
     },
+    sign = {
+      csl = nil,
+      type = "literal",
+    },
     size = {
       csl = nil,
     },
@@ -1401,6 +2642,10 @@
     slaccitation = {
       csl = nil,
     },
+    smfprice = {
+      csl = nil,
+      type = "literal",
+    },
     ["sort-short"] = {
       csl = nil,
     },
@@ -1418,6 +2663,10 @@
       csl = nil,
       type = "name",
     },
+    sortshelfmark = {
+      csl = nil,
+      type = "literal",
+    },
     sortshorthand = {
       csl = nil,
       type = "literal",
@@ -1426,6 +2675,10 @@
       csl = nil,
       type = "literal",
     },
+    sortvolume = {
+      csl = nil,
+      type = "literal",
+    },
     sortyear = {
       csl = nil,
       type = "integer",
@@ -1433,8 +2686,17 @@
     source1 = {
       csl = nil,
     },
+    specdata = {
+      csl = nil,
+      type = "literal",
+    },
+    speciality = {
+      csl = nil,
+      type = "literal",
+    },
     specialitycode = {
       csl = nil,
+      type = "literal",
     },
     ssedition = {
       csl = nil,
@@ -1455,10 +2717,11 @@
       csl = nil,
     },
     status = {
-      csl = nil,
+      csl = "status",
+      type = "key",
     },
     stdcode = {
-      csl = nil,
+      csl = "number",
     },
     stitle = {
       csl = nil,
@@ -1469,16 +2732,86 @@
     subarchive = {
       csl = nil,
     },
+    submitted = {
+      csl = "submitted",
+    },
+    subnumber = {
+      csl = nil,
+      type = "integer",
+    },
+    subseries = {
+      csl = nil,
+      type = "literal",
+    },
+    subsubnumber = {
+      csl = nil,
+      type = "literal",
+    },
+    subsubseries = {
+      csl = nil,
+      type = "literal",
+    },
     subtitle = {
       csl = nil,
       type = "literal",
     },
+    summarytitle = {
+      csl = nil,
+      type = "literal",
+    },
+    supervisor = {
+      csl = nil,
+      type = "name",
+    },
+    supplement = {
+      csl = "supplement",
+    },
+    supplemental = {
+      csl = nil,
+      type = "literal",
+    },
+    supplementala = {
+      csl = nil,
+      type = "literal",
+    },
+    supplementalb = {
+      csl = nil,
+      type = "literal",
+    },
+    support = {
+      csl = nil,
+      type = "literal",
+    },
+    swhid = {
+      csl = nil,
+      type = "verbatim",
+    },
+    swhidcore = {
+      csl = nil,
+      type = "verbatim",
+    },
     symposium = {
       csl = nil,
     },
+    systemreq = {
+      csl = nil,
+      type = "literal",
+    },
     text = {
       csl = nil,
     },
+    thesisdate = {
+      csl = nil,
+      type = "date",
+    },
+    thesistype = {
+      csl = nil,
+      type = "verbatim",
+    },
+    thumbnail = {
+      csl = nil,
+      type = "verbatim",
+    },
     timestamp = {
       csl = nil,
     },
@@ -1486,13 +2819,37 @@
       csl = "title",
       type = "literal",
     },
+    ["title-short"] = {
+      csl = "title-short",
+    },
+    titlea = {
+      csl = nil,
+      type = "literal",
+    },
     titleaddon = {
       csl = nil,
       type = "literal",
     },
+    titleb = {
+      csl = nil,
+      type = "literal",
+    },
+    titleisdescription = {
+      csl = nil,
+      type = "literal",
+    },
+    titlekey = {
+      csl = nil,
+      type = "key",
+    },
     titlenote = {
       csl = nil,
+      type = "literal",
     },
+    titletype = {
+      csl = nil,
+      type = "literal",
+    },
     totalpages = {
       csl = nil,
     },
@@ -1509,6 +2866,26 @@
       csl = "translator",
       type = "name",
     },
+    translatora = {
+      csl = nil,
+      type = "name",
+    },
+    translatoratype = {
+      csl = nil,
+      type = "key",
+    },
+    translatorb = {
+      csl = nil,
+      type = "name",
+    },
+    translatorbtype = {
+      csl = nil,
+      type = "key",
+    },
+    translatortype = {
+      csl = nil,
+      type = "key",
+    },
     transnumber = {
       csl = nil,
     },
@@ -1528,6 +2905,10 @@
       csl = "genre",
       type = "key",
     },
+    typeaddon = {
+      csl = nil,
+      type = "literal",
+    },
     typeoflit = {
       csl = nil,
     },
@@ -1534,6 +2915,10 @@
     umfnumber = {
       csl = nil,
     },
+    update = {
+      csl = nil,
+      type = "date",
+    },
     updated = {
       csl = nil,
     },
@@ -1548,17 +2933,26 @@
       csl = "accessed",
       type = "date",
     },
+    urldescription = {
+      csl = nil,
+      type = "literal",
+    },
     urlnewline = {
       csl = nil,
     },
+    urls = {
+      csl = nil,
+      type = "uri",
+    },
     urltype = {
       csl = nil,
     },
     urlyear = {
-      csl = nil,
+      csl = "accessed",
     },
     urn = {
       csl = nil,
+      type = "verbatim",
     },
     usera = {
       csl = nil,
@@ -1584,6 +2978,10 @@
       csl = nil,
       type = "literal",
     },
+    usualtitle = {
+      csl = nil,
+      type = "literal",
+    },
     value = {
       csl = nil,
     },
@@ -1607,6 +3005,14 @@
       csl = "version",
       type = "literal",
     },
+    versiona = {
+      csl = nil,
+      type = "literal",
+    },
+    versionb = {
+      csl = nil,
+      type = "literal",
+    },
     versiontype = {
       csl = nil,
     },
@@ -1613,20 +3019,110 @@
     volformat = {
       csl = nil,
     },
+    volsorder = {
+      csl = nil,
+      type = "literal",
+    },
     volume = {
       csl = "volume",
       type = "integer",
     },
+    ["volume-title"] = {
+      csl = "volume-title",
+    },
+    ["volume-title-short"] = {
+      csl = "volume-title-short",
+    },
     volumes = {
       csl = "number-of-volumes",
       type = "integer",
     },
     volumetitle = {
+      csl = "volume-title",
+    },
+    with = {
       csl = nil,
+      type = "name",
     },
+    withauthor = {
+      csl = nil,
+      type = "name",
+    },
+    withauthortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withbookauthor = {
+      csl = nil,
+      type = "name",
+    },
+    withbookauthortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withbookeditor = {
+      csl = nil,
+      type = "name",
+    },
+    withbookeditortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withbooktranslator = {
+      csl = nil,
+      type = "name",
+    },
+    withbooktranslatortype = {
+      csl = nil,
+      type = "literal",
+    },
+    witheditor = {
+      csl = nil,
+      type = "name",
+    },
+    witheditortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withmainauthor = {
+      csl = nil,
+      type = "name",
+    },
+    withmainauthortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withmaineditor = {
+      csl = nil,
+      type = "name",
+    },
+    withmaineditortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withmaintranslator = {
+      csl = nil,
+      type = "name",
+    },
+    withmaintranslatortype = {
+      csl = nil,
+      type = "literal",
+    },
+    withtranslator = {
+      csl = nil,
+      type = "name",
+    },
+    withtranslatortype = {
+      csl = nil,
+      type = "literal",
+    },
     word = {
       csl = nil,
     },
+    writer = {
+      csl = nil,
+      type = "name",
+    },
     xdata = {
       csl = nil,
       type = "entrykey",
@@ -1654,6 +3150,10 @@
     zblnumber = {
       csl = nil,
     },
+    zenon = {
+      csl = nil,
+      type = "verbatim",
+    },
     zip = {
       csl = nil,
     },

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-cli.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -143,6 +143,12 @@
   end
   local style_class = engine:get_style_class()
 
+  if style_class == "in-text" then
+    for _, citation in ipairs(citations) do
+      citation.properties.noteIndex = 0
+    end
+  end
+
   local citation_strings = core.process_citations(engine, citations)
 
   -- util.debug(citation_strings)
@@ -153,7 +159,7 @@
     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)
+      output_string = output_string .. string.format("\\cslcite{%s}{%s}\n", citation_id, citation_str)
     end
   end
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -265,7 +265,7 @@
 end
 
 function Context:get_simple_term(name, form, plural)
-  assert(self.locale)
+  -- assert(self.locale)
   return self.locale:get_simple_term(name, form, plural)
 end
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -95,6 +95,34 @@
 
 end
 
+function Element.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 Element:build_ir(engine, state, context)
   return self:build_children_ir(engine, state, context)
 end
@@ -430,7 +458,8 @@
 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
+    -- Values like "2nd" are kept the in the original form.
+    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))

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -9,21 +9,16 @@
 local dom = require("luaxml-domobject")
 
 local nodes = require("citeproc-nodes")
+local Element = require("citeproc-element").Element
 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 InlineElement = require("citeproc-output").InlineElement
 -- 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")
 
 
@@ -56,8 +51,7 @@
 
   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
+    wrap_url_and_doi = false,
     title_link = false,
   }
 
@@ -173,16 +167,10 @@
   self.note_citations_map = {}
 end
 
+
 function CiteProc:processCitationCluster(citation, citationsPre, citationsPost)
   -- 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
+  citation = self:normalize_citation_input(citation)
 
   -- Registor citation
   self.registry.citations_by_id[citation.citationID] = citation
@@ -228,6 +216,7 @@
   local output = {}
 
   local tainted_citation_ids = self:get_tainted_citaion_ids(citation_note_pairs)
+  tainted_citation_ids[citation.citationID] = true
   -- util.debug(tainted_citation_ids)
 
   -- params.bibchange = #tainted_citation_ids > 0
@@ -235,19 +224,37 @@
     local citation_ = self.registry.citations_by_id[citation_id]
 
     local citation_index = citation_.citation_index
-    local citation_str = self:build_citation_str(citation_)
+
+    local mode = citation_.properties.mode
+    if mode == "suppress-author" and self.style.class == "note" then
+      mode = nil
+    end
+    local citation_elemement = self.style.citation
+    if mode == "author-only" and self.style.intext then
+      citation_elemement = self.style.intext
+    end
+
+    local citation_str = citation_elemement:build_citation_str(citation_, self)
     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)
+function CiteProc:normalize_citation_input(citation)
+  citation = util.deep_copy(citation)
+
+  if not citation.citationID then
+    citation.citationID = "CITATION-" .. tostring(#self.registry.citation_list)
+  end
+
+  if not citation.citationItems then
+    citation.citationItems = {}
+  end
+  for i, cite_item in ipairs(citation.citationItems) do
+    citation.citationItems[i] = self:normalize_cite_item(cite_item)
+  end
+
   -- Fix missing noteIndex: sort_CitationNumberPrimaryAscendingViaMacroCitation.txt
   if not citation.properties then
     citation.properties = {}
@@ -256,6 +263,48 @@
     citation.properties.noteIndex = 0
   end
 
+  return citation
+end
+
+function CiteProc:normalize_cite_item(cite_item)
+  -- Shallow copy
+  cite_item = util.clone(cite_item)
+  cite_item.id = tostring(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
+
+  if cite_item.prefix then
+    -- Assert CSL rich-text or HTML-like tagged string
+    if cite_item.prefix == "" then
+      cite_item.prefix = nil
+    else
+      cite_item.prefix = InlineElement:parse(cite_item.prefix)
+    end
+  end
+  if cite_item.suffix then
+    if cite_item.suffix == "" then
+      cite_item.suffix = nil
+    else
+      cite_item.suffix = InlineElement:parse(cite_item.suffix)
+    end
+  end
+
+  return cite_item
+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
+
+  citation = self:normalize_citation_input(citation)
+
   -- Registor citation
   self.registry.citations_by_id[citation.citationID] = citation
 
@@ -276,13 +325,22 @@
 
   -- self:updateItems(item_ids)
   for i, cite_item in ipairs(citation.citationItems) do
-    cite_item.id = tostring(cite_item.id)
     self:get_item(cite_item.id)
   end
 
   local tainted_citation_ids = self:get_tainted_citaion_ids(citation_note_pairs)
-  local citation_str = self:build_citation_str(citation)
 
+  local mode = citation.properties.mode
+  if mode == "suppress-author" and self.style.class == "note" then
+    mode = nil
+  end
+  local citation_elemement = self.style.citation
+  if mode == "author-only" and self.style.intext then
+    citation_elemement = self.style.intext
+  end
+
+  local citation_str = citation_elemement:build_citation_str(citation, self)
+
   return citation_str
 end
 
@@ -310,9 +368,14 @@
 
     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
+      tainted = self:set_cite_item_position(cite_item, note_number, previous_cite, previous_citation, citation)
+
+      -- https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#citations
+      -- Citations within the main text of the document have a noteIndex of zero.
+      if (self.style.class == "note" and note_number > 0) or citation.properties.mode ~= "author-only" then
+        self.cite_last_note_numbers[cite_item.id] = note_number
+        previous_cite = cite_item
+      end
     end
 
     if tainted then
@@ -323,7 +386,9 @@
       self.note_citations_map[note_number] = {}
     end
     table.insert(self.note_citations_map[note_number], citation.citationID)
-    previous_citation = citation
+    if citation.properties.mode ~= "author-only" then
+      previous_citation = citation
+    end
   end
 
   -- Update tainted citation ids because of citation-number's change
@@ -339,9 +404,20 @@
   return tainted_citation_ids
 end
 
-function CiteProc:set_cite_item_position(cite_item, note_number, previous_cite, previous_citation)
+function CiteProc:set_cite_item_position(cite_item, note_number, previous_cite, previous_citation, citation)
   local position = util.position_map["first"]
 
+  -- https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#citations
+  -- Citations within the main text of the document have a noteIndex of zero.
+  if self.style.class == "note" and note_number == 0 then
+    cite_item.position_level = position
+    return false
+  elseif citation.properties.mode == "author-only" then
+    -- discretionary_IbidInAuthorDateStyleWithoutIntext.txt
+    cite_item.position_level = position
+    return false
+  end
+
   local first_reference_note_number = self.cite_first_note_numbers[cite_item.id]
   if first_reference_note_number then
     position = util.position_map["subsequent"]
@@ -429,1153 +505,12 @@
   end
 end
 
-function CiteProc:build_citation_str(citation)
-  -- util.debug(citation.citationID)
+function CiteProc:makeCitationCluster(citation_items)
+  local special_form = nil
   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
-        end
-
-        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
-    end
-  end
-  -- util.debug(citation_stream)
-
-  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 #citation_stream > 0 and context.area.layout.formatting then
-    citation_stream = {Formatted:new(citation_stream, context.area.layout.formatting)}
-  end
-
-  if #citation_stream == 0 then
-    citation_stream = {PlainText:new("[CSL STYLE ERROR: reference with no printed form.]")}
-  end
-
-  -- util.debug(citation_stream)
-  local str = output_format:output(citation_stream, context)
-  str = util.strip(str)
-
-  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 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
-    ir = Rendered:new({Formatted:new({PlainText:new(cite_item.id)}, {["font-weight"] = "bold"})}, self)
-  end
-
-  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
-
-  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
-  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
-
--- 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
-
-  -- util.debug(cite_ir.disam_str)
-
-  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
-      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
-
-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: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 i, cite_item in ipairs(citation_items) do
-    cite_item.id = tostring(cite_item.id)
+    cite_item = self:normalize_cite_item(cite_item)
     local item_data = self:get_item(cite_item.id)
 
     -- Create a wrapper of the orignal item from registry so that
@@ -1582,10 +517,12 @@
     -- it may hold different `locator` or `position` values for cites.
     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"
+    if not special_form then
+      for _, form in ipairs({"author-only", "suppress-author", "coposite"}) do
+        if cite_item[form] then
+          special_form = form
+        end
+      end
     end
 
     item.position_level = util.position_map["first"]
@@ -1618,8 +555,13 @@
     self:sort_bibliography()
   end
 
-  local res = self:build_cluster(items)
+  local citation_element = self.style.citation
+  if special_form == "author-only" and self.style.intext then
+    citation_element = self.style.intext
+  end
 
+  local res = citation_element:build_cluster(items, self)
+
   -- local context = {
   --   build = {},
   --   engine=self,
@@ -1641,8 +583,6 @@
     return {{}, {}}
   end
 
-  local output_format = self.output_format
-
   local res = {}
 
   self.registry.longest_label = ""
@@ -1649,34 +589,8 @@
   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
+    local str = self.style.bibliography:build_bibliography_str(id, self)
+    table.insert(res, str)
   end
 
   local bib_start = self.output_format.markups["bibstart"]
@@ -1709,34 +623,6 @@
   return self.registry.reflist
 end
 
-function CiteProc:add_bibliography_year_suffix(ir)
-  if not ir.reference.year_suffix_number then
-    return
-  end
-
-  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_output_format(format)
   if format == "latex" then
     self.output_format = LatexWriter:new()
@@ -1746,13 +632,11 @@
 end
 
 function CiteProc:enable_linking()
-  self.opt.url_link = true
-  self.opt.doi_link = true
+  self.opt.wrap_url_and_doi = true
 end
 
 function CiteProc:disable_linking()
-  self.opt.url_link = false
-  self.opt.doi_link = false
+  self.opt.wrap_url_and_doi = false
 end
 
 function CiteProc.create_element_tree(node)
@@ -1806,6 +690,7 @@
     return nil
   end
 
+  -- TODO: normalize data input
   item.id = tostring(item.id)
 
   for key, value in pairs(item) do
@@ -1985,6 +870,22 @@
 end
 
 
+local Macro = Element:derive("macro")
+
+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
+
+
 engine.CiteProc = CiteProc
 
 return engine

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -74,8 +74,37 @@
   end
 end
 
+function IrNode:collect_year_suffix_irs()
+  local year_suffix_irs = {}
+  if self.children then
+    for i, child_ir in ipairs(self.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,
+          child_ir:collect_year_suffix_irs())
+      end
+    end
+  end
+  return year_suffix_irs
+end
 
+function IrNode:find_first_year_ir()
+  if self.is_year then
+    return self
+  end
+  if self.children then
+    for _, child_ir in ipairs(self.children) do
+      local year_ir = child_ir:find_first_year_ir()
+      if year_ir then
+        return year_ir
+      end
+    end
+  end
+  return nil
+end
 
+
 local Rendered = IrNode:derive("Rendered")
 
 function Rendered:new(inlines, element)

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-core.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -10,6 +10,7 @@
 local bibtex  -- = require("citeproc-bibtex")  -- load on demand
 local util = citeproc.util
 require("lualibs")
+local latex_parser = require("citeproc-latex-parser")
 
 
 core.locale_file_format = "csl-locales-%s.xml"
@@ -74,7 +75,13 @@
   local csl_items = nil
 
   if extension == ".json" then
-    csl_items = utilities.json.tolua(contents)
+    local status, res = pcall(utilities.json.tolua, contents)
+    if status and res then
+      csl_items = res
+    else
+      util.error(string.format('JSON decoding error in file "%s"', data_file))
+      csl_items = {}
+    end
   elseif extension == ".bib" then
     bibtex = bibtex or require("citeproc-bibtex")
     csl_items = bibtex.parse(contents)
@@ -169,14 +176,21 @@
 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)
+  -- assert(citation.citationID)
+  -- assert(citation.citationItems)
+  -- assert(citation.properties)
 
- citation.citationItems = parse_latex_seq(citation.citationItems)
+  citation.citationItems = parse_latex_seq(citation.citationItems)
 
   for i, item in ipairs(citation.citationItems) do
-    citation.citationItems[i] = parse_latex_prop(item)
+    local citation_item = parse_latex_prop(item)
+    if citation_item.prefix then
+      citation_item.prefix = latex_parser.convert_latex_to_rich_text(citation_item.prefix)
+    end
+    if citation_item.suffix then
+      citation_item.suffix = latex_parser.convert_latex_to_rich_text(citation_item.suffix)
+    end
+    citation.citationItems[i] = citation_item
   end
 
   citation.properties = parse_latex_prop(citation.properties)
@@ -269,21 +283,37 @@
 
   local res = ""
 
-  local bib_options = ""
-  if params["hangingindent"] then
-    bib_options = bib_options .. "\n  hanging-indent = true,"
+  local bib_options = {}
+  bib_options["class"] = engine:get_style_class()
+  local bib_option_list = {"class"}
+
+  local bib_option_map = {
+    ["entry-spacing"] = "entryspacing",
+    ["line-spacing"] = "linespacing",
+    ["hanging-indent"] = "hangingindent",
+  }
+  local bib_option_order = {
+    "class",
+    "hanging-indent",
+    "line-spacing",
+    "entry-spacing",
+  }
+
+  for option, param in pairs(bib_option_map) do
+    if params[param] then
+      bib_options[option] = params[param]
+    end
   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
+  local bib_options_str = "\\cslsetup{\n"
+  for _, option in ipairs(bib_option_order) do
+    local value = bib_options[option]
+    if value then
+      bib_options_str = bib_options_str .. string.format("  %s = %s,\n", option, tostring(value))
+    end
   end
+  bib_options_str = bib_options_str .. "}\n"
+  res = res .. bib_options_str .. "\n"
 
   -- util.debug(params.bibstart)
   if params.bibstart then

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-parser.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-parser.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-parser.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -0,0 +1,259 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local latex_parser = {}
+
+local lpeg = require("lpeg")
+local unicode = require("unicode")
+local bibtex_data = require("citeproc-bibtex-data")
+local util = require("citeproc-util")
+
+
+function latex_parser.get_latex_grammar()
+  local P = lpeg.P
+  local R = lpeg.R
+  local S = lpeg.S
+  local C = lpeg.C
+  local Cc = lpeg.Cc
+  local Cf = lpeg.Cf
+  local Cg = lpeg.Cg
+  local Cmt = lpeg.Cmt
+  local Cp = lpeg.Cp
+  local Ct = lpeg.Ct
+  local V = lpeg.V
+
+  local space = S(" \t\r\n")^0
+  local specials = P"\\$" / "$"
+                   + P"\\%" / "%"
+                   + P"\\&" / "&"
+                   + P"\\#" / "#"
+                   + P"\\_" / "_"
+                   + P"\\{" / "{"
+                   + P"\\}" / "}"
+                   + P"~" / util.unicode["no-break space"]
+  local control_sequence = C(P"\\" * (R("AZ", "az")^1 + 1) * space) / function (cs)
+    return {
+      type = "control_sequence",
+      name = util.rstrip(cs),
+      raw = cs,
+    }
+  end
+  local math = P"$" * C((P"\\$" + 1 - S"$")^0) * P"$" / function (math_text)
+    return {
+      type = "math",
+      contents = math_text,
+    }
+  end
+  local ligatures = P"``" / util.unicode["left double quotation mark"]
+                    + P"`" / util.unicode["left single quotation mark"]
+                    + P"''" / util.unicode["right double quotation mark"]
+                    + P"'" / util.unicode["right single quotation mark"]
+                    + P"---" / util.unicode["em dash"]
+                    + P"--" / util.unicode["en dash"]
+  local plain_text = C(1 - S"{}$\\")
+  local latex_grammar = P{
+    "latex_text";
+    latex_text = Ct((specials + control_sequence + math + ligatures + specials + V"group" + plain_text)^0),
+    group = P"{" * V"latex_text" * P"}" / function (group_contents)
+      return {
+        type = "group",
+        contents = group_contents,
+      }
+    end,
+  }
+  return latex_grammar
+end
+
+
+latex_parser.latex_grammar = latex_parser.get_latex_grammar()
+
+
+function latex_parser.convert_latex_to_rich_text(str)
+  local ast = latex_parser.latex_grammar:match(str)
+  latex_parser.convert_accents_to_unicode(ast)
+  local res = latex_parser.convert_ast_to_rich_text(ast)
+  return res
+end
+
+
+local format_commands_with_argment = {
+  ["\\textbf"] = "bold",
+  ["\\textit"] = "italic",
+  ["\\textsl"] = "italic",
+  ["\\emph"] = "italic",
+  ["\\enquote"] = "quote",
+  ["\\textsc"] = "sc",
+  ["\\sout"] = "strike",  -- from ulem package
+  ["\\st"] = "strike", -- from soul package
+  ["\\textsuperscript"] = "sup",
+  ["\\textsubscript"] = "sub",
+}
+
+local format_commands_without_argment = {
+  ["\\bf"] = "bold",
+  ["\\bfseries"] = "bold",
+  ["\\it"] = "italic",
+  ["\\itshape"] = "italic",
+  ["\\sl"] = "italic",
+  ["\\slshape"] = "italic",
+  ["\\em"] = "italic",
+  ["\\scshape"] = "sc",
+  ["\\sc"] = "sc",
+}
+
+
+function latex_parser.convert_accents_to_unicode(tokens)
+  local res = ""
+  local i = 1
+  while i <= #tokens do
+    if type(token) == "table" and token.type == "control_sequence" then
+      local cs = token
+      local code_point = bibtex_data.unicode_commands[cs.name]
+      if code_point then
+        local unicode_char
+        if type(code_point) == "string" then
+          unicode_char = utf8.char(tonumber(code_point, 16))
+          tokens[i] = unicode_char
+
+        elseif type(code_point) == "table" then
+          -- The command takes an argument (\"{o})
+          local arg
+          if i < #tokens then
+            local next_token = tokens[i + 1]
+            if type(next_token) == "string" then
+              arg = next_token
+            elseif type(next_token) == "table" then
+              if next_token.type == "control_sequence" then
+                arg = next_token.name
+              elseif next_token.type == "group" then
+                if #next_token.contents == 0 then
+                  arg = "{}"
+                elseif #next_token.contents == 1 then
+                  next_token = next_token.contents[1]
+                  if type(next_token) == "string" then
+                    arg = next_token
+                  elseif type(next_token) == "table" then
+                    arg = next_token.name
+                  end
+                end
+              end
+            end
+          end
+          if arg and code_point[arg] then
+            unicode_char = utf8.char(tonumber(code_point[arg], 16))
+            tokens[i] = unicode_char
+            table.remove(tokens, i + 1)
+          end
+        end
+      end
+    end
+    i = i + 1
+  end
+  return res
+end
+
+
+
+function latex_parser.convert_ast_to_rich_text(tokens)
+  local res = {}
+
+  local tmp_str = ""
+  local i = 1
+  while i <= #tokens do
+    local token = tokens[i]
+    if type(token) == "string" then
+      tmp_str = tmp_str .. token
+    elseif type(token) == "table" then
+      if tmp_str ~= "" then
+        table.insert(res, tmp_str)
+        tmp_str = ""
+      end
+      if token.type == "control_sequence" then
+        local format_with_argment = format_commands_with_argment[token.name]
+        local format_without_argment = format_commands_without_argment[token.name]
+        if format_with_argment then
+          if i < #tokens then
+            local next_token = tokens[i + 1]
+            local rich_text
+            if type(next_token) == "string" then
+              rich_text = {[format_with_argment] = next_token}
+            elseif type(next_token) == "table" then
+              if next_token.type == "control_sequence" then
+                local content = {code = next_token.raw}
+                rich_text = {[format_with_argment] = {content}}
+              elseif next_token.type == "group" then
+                rich_text = {[format_with_argment] = latex_parser.convert_ast_to_rich_text(next_token.contents)}
+              elseif next_token.type == "math" then
+                local content = latex_parser.convert_ast_to_rich_text(next_token.contents)
+                rich_text = {[format_with_argment] = {{["math-tex"] = content}}}
+              end
+            end
+            if rich_text then
+              table.insert(res, rich_text)
+              i = i + 1
+            end
+          else
+            table.insert(res, {code = token.raw})
+          end
+
+        elseif format_without_argment then
+          local rest_tokens = {}
+          for j = i + 1, #tokens do
+            table.insert(rest_tokens, tokens[j])
+          end
+          local rich_text = {[format_without_argment] = latex_parser.convert_ast_to_rich_text(rest_tokens)}
+          table.insert(res, rich_text)
+          i = #tokens
+
+        else
+          local rich_text = {code = token.raw}
+          table.insert(res, rich_text)
+
+        end
+
+      elseif token.type == "group" then
+        table.insert(res, {code = "{"})
+        for _, rich_text in ipairs(latex_parser.convert_ast_to_rich_text(token.contents)) do
+          table.insert(res, rich_text)
+        end
+        table.insert(res, {code = "}"})
+
+      elseif token.type == "math" then
+        table.insert(res, {["math-tex"] = token.contents})
+      end
+
+    end
+    i = i + 1
+  end
+
+  if tmp_str ~= "" then
+    table.insert(res, tmp_str)
+  end
+
+  -- Merge tokens
+  for i = #res - 1, 1, -1 do
+    local token = res[i]
+    local next_token = res[i + 1]
+    local token_type = type(token)
+    local next_token_type = type(next_token)
+    if token_type == "string" and next_token_type == "string" then
+      res[i] = token .. next_token
+      table.remove(res, i + 1)
+    elseif token_type == "table" and token.code and
+        next_token_type == "table" and next_token.code then
+      token.code = token.code .. next_token.code
+      table.remove(res, i + 1)
+    end
+  end
+
+  if #res == 1 and type(res[1]) == "string" then
+    res = res[1]
+  end
+
+  return res
+end
+
+return latex_parser


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex-parser.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-latex.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-latex.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -87,11 +87,11 @@
 
 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
 
+  -- util.debub(citation_info)
   local citation = core.make_citation(citation_info)
 
   local citation_str
@@ -103,8 +103,11 @@
     citation_str = csl.engine:process_citation(citation)
   end
 
-  tex.sprint(string.format("{%s}{%s}", csl.style_class, citation_str))
+  -- util.debug(citation_str)
   -- tex.sprint(citation_str)
+  -- tex.setcatcode(35, 12)  -- #
+  -- tex.setcatcode(37, 12)  -- %
+  token.set_macro("l__csl_citation_tl", citation_str)
 
   table.insert(csl.citations_pre, {citation.citationID, citation.properties.noteIndex})
 end

Added: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -0,0 +1,307 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local bibliography_module = {}
+
+local dom = require("luaxml-domobject")
+
+local Context = require("citeproc-context").Context
+local IrState = require("citeproc-context").IrState
+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 YearSuffix = require("citeproc-ir-node").YearSuffix
+local util = require("citeproc-util")
+
+
+local Bibliography = Element:derive("bibliography", {
+  hanging_indent = false,
+  line_spacing = 1,
+  entry_spacing = 1,
+  subsequent_author_substitute_rule = "complete-all"
+})
+
+function Bibliography:from_node(node, style)
+  local o = Bibliography:new()
+  o.children = {}
+
+  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
+
+  -- 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
+  Element.make_name_inheritance(name_inheritance, node)
+  o.name_inheritance = name_inheritance
+
+  return o
+end
+
+function Bibliography:build_bibliography_str(id, engine)
+    local output_format = engine.output_format
+
+    local state = IrState:new()
+    local context = Context:new()
+    context.engine = engine
+    context.style = engine.style
+    context.area = self
+    context.in_bibliography = true
+    context.locale = engine:get_locale(engine.lang)
+    context.name_inheritance = self.name_inheritance
+    context.format = output_format
+    context.id = id
+    context.cite = nil
+    context.reference = engine:get_item(id)
+
+    local ir = self:build_ir(engine, state, context)
+    -- util.debug(ir)
+    ir.reference = context.reference
+
+    -- Add year-suffix
+    self:add_bibliography_year_suffix(ir, engine)
+
+    -- The layout output may be empty: sort_OmittedBibRefNonNumericStyle.txt
+    if not ir then
+      return nil
+    end
+
+    local flat = ir:flatten(output_format)
+    local str = output_format:output_bibliography_entry(flat, context)
+    return str
+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
+
+  if self.subsequent_author_substitute then
+    self:substitute_subsequent_authors(engine, ir)
+  end
+
+  if not ir then
+    ir = Rendered:new({PlainText:new("[CSL STYLE ERROR: reference with no printed form.]")}, self)
+  end
+  return ir
+end
+
+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
+
+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
+
+function Bibliography:substitute_subsequent_authors_complete_all(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
+  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
+
+  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
+
+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
+  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
+
+  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
+
+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
+
+function Bibliography:substitute_subsequent_authors_partial_first(engine, ir)
+end
+
+function Bibliography:add_bibliography_year_suffix(ir, engine)
+  if not ir.reference.year_suffix_number then
+    return
+  end
+
+  local year_suffix_number = ir.reference.year_suffix_number
+
+  if not ir.year_suffix_irs then
+    ir.year_suffix_irs = ir:collect_year_suffix_irs()
+    if #ir.year_suffix_irs == 0 then
+      local year_ir = ir:find_first_year_ir()
+      -- util.debug(year_ir)
+      if year_ir then
+        local year_suffix_ir = YearSuffix:new({}, engine.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
+
+
+bibliography_module.Bibliography = Bibliography
+
+
+return bibliography_module


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.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-node-citation.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -0,0 +1,1417 @@
+
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local citation_module = {}
+
+local dom = require("luaxml-domobject")
+
+local Context = require("citeproc-context").Context
+local IrState = require("citeproc-context").IrState
+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 YearSuffix = require("citeproc-ir-node").YearSuffix
+local Micro = require("citeproc-output").Micro
+local Formatted = require("citeproc-output").Formatted
+local PlainText = require("citeproc-output").PlainText
+local InlineElement = require("citeproc-output").InlineElement
+local DisamStringFormat = require("citeproc-output").DisamStringFormat
+local SortStringFormat = require("citeproc-output").SortStringFormat
+local util = require("citeproc-util")
+
+
+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,
+})
+
+function Citation:from_node(node, style)
+
+  local o = self:new()
+  o.children = {}
+
+  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
+
+  -- 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
+
+
+  -- 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
+
+  -- Note Distance
+  o:set_number_attribute(node, "near-note-distance")
+
+  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
+  Element.make_name_inheritance(name_inheritance, node)
+  o.name_inheritance = name_inheritance
+
+  -- update_mode = "plain" or "numeric" or "position" (or "both"?)
+
+  return o
+end
+
+function Citation:build_citation_str(citation, engine)
+  if engine.registry.requires_sorting then
+    engine:sort_bibliography()
+  end
+
+  local citation_str = self:build_cluster(citation.citationItems, engine, citation.properties)
+  return citation_str
+end
+
+-- Formatting is stripped from the author-only and composite renderings
+-- of the author name
+local function remove_name_formatting(ir)
+  if ir._element == "name" then
+    ir.formatting = nil
+  end
+  if ir.children then
+    for _, child in ipairs(ir.children) do
+      remove_name_formatting(child)
+    end
+  end
+end
+
+function Citation:build_cluster(citation_items, engine, properties)
+  properties = properties or {}
+  local output_format = engine.output_format
+  local irs = {}
+  citation_items = self:sorted_citation_items(citation_items, engine)
+  for _, cite_item in ipairs(citation_items) do
+    local ir = self:build_fully_disambiguated_ir(cite_item, output_format, engine, properties)
+    table.insert(irs, ir)
+  end
+
+  -- Special citation forms
+  -- https://citeproc-js.readthedocs.io/en/latest/running.html#special-citation-forms
+  self:_apply_special_citation_form(irs, properties, output_format, engine)
+
+  if self.cite_grouping then
+    irs = self:group_cites(irs)
+  else
+    local citation_collapse = self.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.collapse then
+    self:collapse_cites(irs)
+  end
+
+  -- Capitalize first
+  for i, ir in ipairs(irs) do
+      -- local layout_prefix
+      -- local layout_affixes = self.layout.affixes
+      -- if layout_affixes then
+      --   layout_prefix = layout_affixes.prefix
+      -- end
+    local prefix = citation_items[i].prefix
+    if prefix then
+      -- Prefix is inlines
+      local right_most_str = prefix[#prefix]:get_right_most_string()
+      if string.match(right_most_str, "[.!?]%s*$") and InlineElement.has_space(prefix) then
+        ir:capitalize_first_term()
+      end
+    else
+      local delimiter = self.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.layout.delimiter
+  local citation_stream = {}
+
+  local context = Context:new()
+  context.engine = engine
+  context.style = engine.style
+  context.area = self
+  context.in_bibliography = false
+  context.locale = engine:get_locale(engine.lang)
+  context.name_inheritance = self.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))
+          else
+            local left_most_str
+            if cite_prefix then
+              left_most_str = cite_prefix[1]:get_left_most_string()
+            end
+            if citation_delimiter and not (cite_prefix and util.startswith(left_most_str, ",")) then
+              table.insert(citation_stream, PlainText:new(citation_delimiter))
+            end
+          end
+        end
+
+        if cite_prefix then
+          table.insert(citation_stream, Micro:new(cite_prefix))
+        end
+
+        -- util.debug(ir)
+        util.extend(citation_stream, ir_inlines)
+        previous_ir = ir
+
+        if cite_suffix then
+          table.insert(citation_stream, Micro:new(cite_suffix))
+        end
+      end
+    end
+  end
+
+  local has_printed_form = true
+  if #citation_items == 0 then
+    -- bugreports_AuthorOnlyFail.txt
+    citation_stream = {PlainText:new("[NO_PRINTED_FORM]")}
+    has_printed_form = false
+  elseif #citation_stream == 0 then
+    -- date_DateNoDateNoTest.txt
+    has_printed_form = false
+    citation_stream = {PlainText:new("[CSL STYLE ERROR: reference with no printed form.]")}
+  elseif #citation_stream == 1 and citation_stream[1].value == "[NO_PRINTED_FORM]" then
+    has_printed_form = false
+  end
+
+  local author_only_mode = (properties.mode == "author-only" or
+    (#citation_items >= 1 and citation_items[1]["author-only"]))
+  if has_printed_form and context.area.layout.affixes and not author_only_mode 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
+
+  if has_printed_form and context.area.layout.formatting then
+    citation_stream = {Formatted:new(citation_stream, context.area.layout.formatting)}
+  end
+
+  if properties.mode == "composite" then
+    local author_ir
+    if irs[1] then
+      author_ir = irs[1].author_ir
+    end
+    if author_ir then
+      local infix = properties.infix
+      if infix then
+        if string.match(infix, "^%w") then
+          -- discretionary_SingleNarrativeCitation.txt
+          infix = " " .. infix
+        end
+        if string.match(infix, "%w$") then
+          infix = infix .. " "
+        end
+        if infix == "" then
+          -- discretionary_AuthorOnlySuppressLocator.txt
+          infix = " "
+        end
+        for i, inline in ipairs(InlineElement:parse(infix, context)) do
+          table.insert(citation_stream, i, inline)
+        end
+      else
+        table.insert(citation_stream, 1, PlainText:new(" "))
+      end
+
+      local author_inlines = author_ir:flatten(output_format)
+      for i, inline in ipairs(author_inlines) do
+        table.insert(citation_stream, i, inline)
+      end
+    end
+  end
+
+  local str = output_format:output(citation_stream, context)
+  str = util.strip(str)
+
+  return str
+end
+
+function Citation:sorted_citation_items(items, engine)
+  local citation_sort = self.sort
+  if not citation_sort then
+    return items
+  end
+
+  local state = IrState:new()
+  local context = Context:new()
+  context.engine = engine
+  context.style = engine.style
+  context.area = self
+  context.in_bibliography = false
+  context.locale = engine:get_locale(engine.lang)
+  context.name_inheritance = self.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 Citation:build_fully_disambiguated_ir(cite_item, output_format, engine, properties)
+  local cite_ir = self:build_ambiguous_ir(cite_item, output_format, engine)
+  -- util.debug(cite_ir)
+  cite_ir = self:apply_disambiguate_add_givenname(cite_ir, engine)
+  cite_ir = self:apply_disambiguate_add_names(cite_ir, engine)
+  cite_ir = self:apply_disambiguate_conditionals(cite_ir, engine)
+  cite_ir = self:apply_disambiguate_add_year_suffix(cite_ir, engine)
+
+  return cite_ir
+end
+
+function Citation:build_ambiguous_ir(cite_item, output_format, engine)
+  local state = IrState:new(engine.style)
+  local context = Context:new()
+  context.engine = engine
+  context.style = engine.style
+  context.area = self
+  context.locale = engine:get_locale(engine.lang)
+  context.name_inheritance = self.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 = engine.registry.registry[cite_item.id]
+
+  local ir
+  if context.reference then
+    ir = self:build_ir(engine, state, context)
+  else
+    ir = Rendered:new({Formatted:new({PlainText:new(cite_item.id)}, {["font-weight"] = "bold"})}, self)
+  end
+
+  ir.cite_item = cite_item
+  ir.reference = context.reference
+  ir.ir_index = #engine.disam_irs + 1
+  table.insert(engine.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 engine.cite_irs_by_output[disam_str] then
+    engine.cite_irs_by_output[disam_str] = {}
+  end
+
+  for ir_index, ir_ in pairs(engine.cite_irs_by_output[disam_str]) do
+    if ir_.cite_item.id ~= cite_item.id then
+      ir.is_ambiguous = true
+      break
+    end
+  end
+  engine.cite_irs_by_output[disam_str][ir.ir_index] = ir
+
+  return ir
+end
+
+function Citation:build_ir(engine, state, context)
+  if not self.layout then
+    util.error("Missing citation layout.")
+  end
+  return self.layout:build_ir(engine, state, context)
+end
+
+function Citation:apply_disambiguate_add_givenname(cite_ir, engine)
+  if self.disambiguate_add_givenname then
+    -- util.debug("disambiguate_add_givenname: " .. cite_ir.cite_item.id)
+
+    local gn_disam_rule = self.givenname_disambiguation_rule
+    if gn_disam_rule == "all-names" or gn_disam_rule == "all-names-with-initials" then
+      cite_ir = self:apply_disambiguate_add_givenname_all_names(cite_ir, engine)
+    elseif gn_disam_rule == "primary-name" or gn_disam_rule == "primary-name-with-initials" then
+      cite_ir = self:apply_disambiguate_add_givenname_primary_name(cite_ir, engine)
+    elseif gn_disam_rule == "by-cite" then
+      cite_ir = self:apply_disambiguate_add_givenname_by_cite(cite_ir, engine)
+    end
+  end
+  return cite_ir
+end
+
+-- TODO: reorganize this code
+function Citation:apply_disambiguate_add_givenname_all_names(cite_ir, engine)
+  -- 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
+
+  -- util.debug(cite_ir.disam_str)
+
+  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 = #engine.person_names + 1
+      table.insert(engine.person_names, person_name_ir)
+    end
+
+    if not engine.person_names_by_output[name_output] then
+      engine.person_names_by_output[name_output] = {}
+    end
+    engine.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(engine.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 engine.person_names_by_output[pn_ir.name_output] then
+            engine.person_names_by_output[pn_ir.name_output] = {}
+          end
+          engine.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(engine.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, nil)
+  cite_ir.disam_str = disam_str
+  if not engine.cite_irs_by_output[disam_str] then
+    engine.cite_irs_by_output[disam_str] = {}
+  end
+  engine.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(engine.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 Citation:apply_disambiguate_add_givenname_primary_name(cite_ir, engine)
+  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 = #engine.person_names + 1
+    table.insert(engine.person_names, person_name_ir)
+  end
+  if not engine.person_names_by_output[name_output] then
+    engine.person_names_by_output[name_output] = {}
+  end
+  engine.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(engine.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 engine.person_names_by_output[pn_ir.name_output] then
+          engine.person_names_by_output[pn_ir.name_output] = {}
+        end
+        engine.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(engine.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 Citation:apply_disambiguate_add_givenname_by_cite(cite_ir, engine)
+  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(engine.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(engine.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, nil)
+            ir_.disam_str = disam_str
+            if not engine.cite_irs_by_output[disam_str] then
+              engine.cite_irs_by_output[disam_str] = {}
+            end
+            engine.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(engine.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 Citation:apply_disambiguate_add_names(cite_ir, engine)
+  if not self.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(engine.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 = #engine.person_names + 1
+          table.insert(engine.person_names, added_person_name_ir)
+        end
+        local name_output = added_person_name_ir.name_output
+        if not engine.person_names_by_output[name_output] then
+          engine.person_names_by_output[name_output] = {}
+        end
+        engine.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, nil)
+        -- util.debug(disam_str)
+        ir_.disam_str = disam_str
+        if not engine.cite_irs_by_output[disam_str] then
+          engine.cite_irs_by_output[disam_str] = {}
+        end
+        engine.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+      end
+    end
+
+    -- util.debug("disambiguate_add_givenname")
+
+    if self.disambiguate_add_givenname then
+      local gn_disam_rule = self.givenname_disambiguation_rule
+      if gn_disam_rule == "all-names" or gn_disam_rule == "all-names-with-initials" then
+        cite_ir = self:apply_disambiguate_add_givenname_all_names(cite_ir, engine)
+      elseif gn_disam_rule == "by-cite" then
+        cite_ir = self:apply_disambiguate_add_givenname_by_cite(cite_ir, engine)
+      end
+    end
+
+    cite_ir.is_ambiguous = self:check_ambiguity(cite_ir, engine)
+
+    -- for _, ir_ in ipairs(engine.disam_irs) do
+    --   util.debug(ir_.cite_item.id .. ": " .. ir_.disam_str)
+    -- end
+
+  end
+
+
+  return cite_ir
+end
+
+function Citation: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 Citation:apply_disambiguate_conditionals(cite_ir, engine)
+  -- 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(engine.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, nil)
+        -- util.debug("update: " .. ir_.cite_item.id .. ": " .. disam_str)
+        ir_.disam_str = disam_str
+        if not engine.cite_irs_by_output[disam_str] then
+          engine.cite_irs_by_output[disam_str] = {}
+        end
+        engine.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+      end
+    end
+
+    cite_ir.is_ambiguous = self:check_ambiguity(cite_ir, engine)
+    -- util.debug(cite_ir.is_ambiguous)
+    -- for _, ir_ in ipairs(engine.disam_irs) do
+    --   util.debug(ir_.cite_item.id .. ": " .. ir_.disam_str)
+    -- end
+
+  end
+  return cite_ir
+end
+
+function Citation:check_ambiguity(cite_ir, engine)
+  for _, ir_ in pairs(engine.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 Citation:get_same_output_irs(cite_ir, engine)
+  local ambiguous_same_output_irs = {}
+  for _, ir_ in pairs(engine.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 Citation:apply_disambiguate_add_year_suffix(cite_ir, engine)
+  if not cite_ir.is_ambiguous or not self.disambiguate_add_year_suffix then
+    return cite_ir
+  end
+
+  local same_output_irs = self:get_same_output_irs(cite_ir, engine)
+
+  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 = ir_:collect_year_suffix_irs()
+      if #ir_.year_suffix_irs == 0 then
+        -- By default, the year-suffix is appended the first year rendered through cs:date
+        local year_ir = ir_:find_first_year_ir()
+        -- util.debug(year_ir)
+        if year_ir then
+          local year_suffix_ir = YearSuffix:new({}, self)
+          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, nil)
+    -- util.debug("update: " .. ir_.cite_item.id .. ": " .. disam_str)
+    ir_.disam_str = disam_str
+    if not engine.cite_irs_by_output[disam_str] then
+      engine.cite_irs_by_output[disam_str] = {}
+    end
+    engine.cite_irs_by_output[disam_str][ir_.ir_index] = ir_
+
+  end
+
+  cite_ir.is_ambiguous = false
+
+  return cite_ir
+end
+
+function Citation: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
+
+-- Find the first rendering element and it should be produced by and names element
+local function find_first_names_ir(ir)
+  if ir.first_names_ir then
+    return ir.first_names_ir
+  end
+
+  local first_rendering_ir = find_first(ir, function (ir_)
+    return (ir_._element == "text"
+      or ir_._element == "date"
+      or ir_._element == "number"
+      or ir_._element == "names"
+      or ir_._element == "label")
+      and ir_.group_var ~= "missing"
+  end)
+  local first_names_ir
+  if first_rendering_ir and first_rendering_ir._element == "names" then
+    first_names_ir = first_rendering_ir
+  end
+  if first_names_ir then
+    local disam_format = DisamStringFormat:new()
+    local inlines = first_names_ir:flatten(disam_format)
+    first_names_ir.disam_str = disam_format:output(inlines, nil)
+  end
+  ir.first_names_ir = first_names_ir
+  return first_names_ir
+
+end
+
+function Citation:_apply_special_citation_form(irs, properties, output_format, engine)
+  if properties.mode then
+    if properties.mode == "author-only" then
+      for _, ir in ipairs(irs) do
+        self:_apply_citation_mode_author_only(ir)
+      end
+    elseif properties.mode == "suppress-author" then
+      -- suppress-author mode does not work in note style
+      -- discretionary_FirstReferenceNumberWithIntext.txt
+      if engine.style.class ~= "note" then
+        for _, ir in ipairs(irs) do
+          self:_apply_suppress_author(ir)
+        end
+      end
+
+    elseif properties.mode == "composite" then
+      self:_apply_composite(irs[1], output_format, engine)
+
+    end
+
+  else
+    for _, ir in ipairs(irs) do
+      if ir.cite_item["author-only"] then
+        self:_apply_cite_author_only(ir)
+      elseif ir.cite_item["suppress-author"] then
+        self:_apply_suppress_author(ir)
+      end
+    end
+  end
+end
+
+function Citation:_apply_citation_mode_author_only(ir)
+  -- Used in pr
+  local author_ir = find_first_names_ir(ir)
+
+  if author_ir then
+    remove_name_formatting(author_ir)
+    ir.children = {author_ir}
+  else
+    ir.children = {Rendered:new({PlainText:new("[NO_PRINTED_FORM]")}, self)}
+  end
+  return ir
+end
+
+-- Citation flags with makeCitationCluster
+-- In contrast to Citation flags with processCitationCluster, this funciton
+-- looks for the first rendering element instead of names element.
+-- See discretionary_AuthorOnly.txt
+function Citation:_apply_cite_author_only(ir)
+  local author_ir = find_first(ir, function (ir_)
+    return (ir_._element == "text"
+      or ir_._element == "date"
+      or ir_._element == "number"
+      or ir_._element == "names"
+      or ir_._element == "label")
+      and ir_.group_var ~= "missing"
+  end)
+
+  if author_ir then
+    remove_name_formatting(author_ir)
+    ir.children = {author_ir}
+  else
+    ir.children = {Rendered:new({PlainText:new("[NO_PRINTED_FORM]")}, self)}
+  end
+  return ir
+end
+
+function Citation:_apply_suppress_author(ir)
+  local author_ir = find_first_names_ir(ir)
+  if author_ir then
+    -- util.debug(author_ir)
+    author_ir.collapse_suppressed = true
+  end
+  return ir
+end
+
+function Citation:_apply_composite(ir, output_format, engine)
+  -- local first_names_ir = find_first_names_ir(ir)
+
+  local first_names_ir = find_first_names_ir(ir)
+  if first_names_ir then
+    -- util.debug(first_names_ir)
+    first_names_ir.collapse_suppressed = true
+  end
+
+  local author_ir
+  if engine.style.intext then
+    local properties = {mode = "author-only"}
+    author_ir = engine.style.intext:build_fully_disambiguated_ir(ir.cite_item, output_format, engine, properties)
+  elseif first_names_ir then
+    author_ir = first_names_ir
+  end
+
+  if author_ir then
+    remove_name_formatting(author_ir)
+    ir.author_ir = author_ir
+  else
+    ir.author_ir = Rendered:new({PlainText:new("[NO_PRINTED_FORM]")}, self)
+  end
+
+  return ir
+end
+
+function Citation: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" and ir_.group_var ~= "missing"
+      end)
+      if first_names_ir then
+        local inlines = first_names_ir:flatten(disam_format)
+        first_names_ir.disam_str = disam_format:output(inlines, nil)
+      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.cite_group_delimiter
+      end
+      table.insert(grouped, ir)
+    end
+  end
+  return grouped
+end
+
+function Citation:collapse_cites(irs)
+  if self.collapse == "citation-number" then
+    self:collapse_cites_by_citation_number(irs)
+  elseif self.collapse == "year" then
+    self:collapse_cites_by_year(irs)
+  elseif self.collapse == "year-suffix" then
+    self:collapse_cites_by_year_suffix(irs)
+  elseif self.collapse == "year-suffix-ranged" then
+    self:collapse_cites_by_year_suffix_ranged(irs)
+  end
+end
+
+function Citation: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
+      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.after_collapse_delimiter
+    end
+  end
+end
+
+function Citation: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 Citation: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.first_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.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.after_collapse_delimiter
+          elseif self.cite_grouping then
+            if self.sort then
+              cite_ir.own_delimiter = self.cite_group_delimiter
+            else
+              cite_ir.own_delimiter = self.layout.delimiter
+            end
+          else
+            if self.sort then
+              cite_ir.own_delimiter = self.cite_group_delimiter
+            else
+              -- disambiguate_YearCollapseWithInstitution.txt
+              -- disambiguate_InitializeWithButNoDisambiguation.txt ?
+              cite_ir.own_delimiter = self.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 Citation: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.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.cite_group_delimiter
+          else
+            cite_ir.own_delimiter = self.year_suffix_delimiter
+          end
+        elseif i == #cite_group then
+          cite_ir.own_delimiter = self.after_collapse_delimiter
+        end
+      end
+    end
+  end
+end
+
+function Citation: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 Citation: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
+
+
+local InText = Citation:derive("intext", {
+  givenname_disambiguation_rule = "by-cite",
+  cite_group_delimiter = ", ",
+  near_note_distance = 5,
+})
+
+
+citation_module.Citation = Citation
+citation_module.InText = InText
+
+
+return citation_module


Property changes on: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.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-node-date.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -354,9 +354,9 @@
 
   local inlines = {PlainText:new(text)}
   local output_format = context.format
-  if not context.is_english then
-    print(debug.traceback())
-  end
+  -- 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)

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-label.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -70,7 +70,9 @@
     return #variable > 1
   elseif variable_type == "number" then
     if util.startswith(variable, "number-of-") then
-      return tonumber(value) > 1
+      -- Issue #27: "number-of-pages": "91–129"
+      value = string.match(tostring(value), "%d+")
+      return value and tonumber(value) > 1
     else
       value = tostring(value)
       -- label_CollapsedPageNumberPluralDetection.txt

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-locale.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -96,7 +96,9 @@
   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
+      -- if not key then
+      --   print(debug.traceback())
+      -- end
       key = key .. "/form-" .. fallback_form
     end
     local term = self.terms[key]

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-names.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -441,9 +441,6 @@
     ir.group_var = "important"
   end
 
-  ir.formatting = self.formatting
-  ir.affixes = self.affixes
-
   irs = {ir}
 
   if label then
@@ -478,6 +475,10 @@
   local inlines = self:render_person_name(name, is_first, is_latin, is_inverted, context)
   local person_name_ir = PersonNameIr:new(inlines, self)
 
+  -- discretionary_ExampleSeveralAuthorsWithIntext.txt
+  person_name_ir.formatting = self.formatting
+  person_name_ir.affixes = self.affixes
+
   person_name_ir.is_inverted = is_inverted
 
   local output_format = SortStringFormat:new()
@@ -1041,9 +1042,10 @@
   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
+  -- if not output_format then
+  --   print(debug.traceback())
+  --   assert(output_format)
+  -- end
   output_format:apply_text_case(inlines, self.text_case, is_english)
 
   inlines = output_format:with_format(inlines, self.formatting)

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-style.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -33,9 +33,17 @@
 end
 
 function Style:parse(xml_str)
-  local csl_xml = dom.parse(xml_str)
-  if not csl_xml then
-    error("Failed to parse CSL style.")
+  -- The parsing error is not caught by busted in some situcations and thus it's processed here.
+  -- discretionary_CitationNumberAuthorOnlyThenSuppressAuthor.txt
+  local status, csl_xml = pcall(function () return dom.parse(xml_str) end)
+  if not status or not csl_xml then
+    if csl_xml then
+      local error_message = string.match(csl_xml, "^.-: (.*)$")
+      util.error("CSL parsing error: " .. util.rstrip(error_message))
+    else
+      util.error("CSL parsing error")
+    end
+    return nil
   end
   local style_node = csl_xml:get_path("style")[1]
   if not csl_xml then
@@ -44,33 +52,6 @@
   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()
 
@@ -87,7 +68,7 @@
   -- 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)
+  Element.make_name_inheritance(o.name_inheritance, node)
 
   if o.page_range_format == "chicago" then
     if o.version < "1.1" then
@@ -111,6 +92,8 @@
       o.citation = child
     elseif element_name == "bibliography" then
       o.bibliography = child
+    elseif element_name == "intext" then
+      o.intext = child
     elseif element_name == "macro" then
       o.macros[child.name] = child
     elseif element_name == "locale" then
@@ -197,328 +180,8 @@
 end
 
 
-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,
-})
 
-function Citation:from_node(node, style)
-
-  local o = Citation:new()
-  o.children = {}
-
-  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
-
-  -- 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
-
-
-  -- 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
-
-  -- Note Distance
-  o:set_number_attribute(node, "near-note-distance")
-
-  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
-
-  -- update_mode = "plain" or "numeric" or "position" (or "both"?)
-
-  return o
-end
-
-function Citation:build_ir(engine, state, context)
-  if not self.layout then
-    util.error("Missing citation layout.")
-  end
-  return self.layout:build_ir(engine, state, context)
-end
-
-
-local Bibliography = Element:derive("bibliography", {
-  hanging_indent = false,
-  line_spacing = 1,
-  entry_spacing = 1,
-  subsequent_author_substitute_rule = "complete-all"
-})
-
-function Bibliography:from_node(node, style)
-  local o = Bibliography:new()
-  o.children = {}
-
-  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
-
-  -- 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
-
-  if self.subsequent_author_substitute then
-    self:substitute_subsequent_authors(engine, ir)
-  end
-
-  if not ir then
-    ir = Rendered:new(PlainText:new("[CSL STYLE ERROR: reference with no printed form.]"), self)
-  end
-  return ir
-end
-
-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
-
-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
-
-function Bibliography:substitute_subsequent_authors_complete_all(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
-  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
-
-  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
-
-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
-  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
-
-  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
-
-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
-
-function Bibliography:substitute_subsequent_authors_partial_first(engine, ir)
-end
-
-local Macro = Element:derive("macro")
-
-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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -90,10 +90,12 @@
   end
 
   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
+  -- if not engine.opt then
+  --   print(debug.traceback())
+  -- end
+  -- if engine.opt.wrap_url_and_doi and (variable == "URL" or variable == "DOI" or
+  --     variable == "PMID" or variable == "PMID") then
+  if variable == "URL" or variable == "DOI" or variable == "PMID" or variable == "PMID" then
     inlines = self:render_linked(engine, state, context, variable, text)
   else
     inlines = self:render_text_inlines(text, context)
@@ -116,14 +118,13 @@
 
 function Text:render_linked(engine, state, context, variable, text)
   local href
+  local url_prefix = false  -- The prefix is used as part of the URL.
   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 self.affixes and self.affixes.prefix and string.match(self.affixes.prefix, "https?://") then
+    text = self.affixes.prefix .. text
+    href = text
+    url_prefix = true
   elseif variable == "DOI" then
     href = "https://doi.org/" .. text
   elseif variable == "PMID" then
@@ -131,6 +132,7 @@
   elseif variable == "PMCID" then
     href = "https://www.ncbi.nlm.nih.gov/pmc/articles/" .. text
   end
+
   local inlines = {Linked:new(text, href)}
   local output_format = context.format
   local localized_quotes = nil
@@ -139,6 +141,9 @@
   end
   inlines = output_format:with_format(inlines, self.formatting)
   inlines = output_format:affixed_quoted(inlines, self.affixes, localized_quotes)
+  if url_prefix then
+    table.remove(inlines, 1)
+  end
   return output_format:with_display(inlines, self.display)
 end
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-nodes.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-nodes.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-nodes.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -5,6 +5,9 @@
 --
 
 local style  = require("citeproc-node-style")
+local citation  = require("citeproc-node-citation")
+local citation  = require("citeproc-node-citation")
+local bibliography  = require("citeproc-node-bibliography")
 local locale = require("citeproc-node-locale")
 local layout = require("citeproc-node-layout")
 local text   = require("citeproc-node-text")
@@ -18,8 +21,9 @@
 
 local nodes = {
   ["style"]        = style.Style,
-  ["citation"]     = style.Citation,
-  ["bibliography"] = style.Bibliography,
+  ["citation"]     = citation.Citation,
+  ["intext"]       = citation.Intext,
+  ["bibliography"] = bibliography.Bibliography,
   ["locale"]       = locale.Locale,
   ["term"]         = locale.Term,
   ["layout"]       = layout.Layout,

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -110,6 +110,36 @@
 end
 
 
+local Code = InlineElement:derive("Code")
+
+function Code:new(value)
+  local o = InlineElement.new(self)
+  o.value = value
+  setmetatable(o, self)
+  return o
+end
+
+
+local MathML = InlineElement:derive("MathML")
+
+function MathML:new(value)
+  local o = InlineElement.new(self)
+  o.value = value
+  setmetatable(o, self)
+  return o
+end
+
+
+local MathTeX = InlineElement:derive("MathTeX")
+
+function MathTeX:new(value)
+  local o = InlineElement.new(self)
+  o.value = value
+  setmetatable(o, self)
+  return o
+end
+
+
 local NoCase = InlineElement:derive("NoCase")
 
 function NoCase:new(inlines)
@@ -152,11 +182,95 @@
 end
 
 
-function InlineElement:parse(str, context)
+function InlineElement:parse(text, context)
+  local text_type = type(text)
+  local inlines
+  if text_type == "table" then
+    -- CSL rich text
+    inlines = self:parse_csl_rich_text(text)
+  elseif text_type == "string" then
+    -- String with HTML-like formatting tags
+    inlines = self:parse_html_tags(text, context)
+  elseif text_type == "number" then
+    inlines = {PlainText:new(tostring(text))}
+  else
+    util.error("Invalid text type")
+  end
+  return inlines
+end
+
+function InlineElement:parse_csl_rich_text(text)
+  -- Example: [
+  --   "A title with a",
+  --   {
+  --     "quote": "quoted string."
+  --   }
+  -- ]
+  local inlines = {}
+
+  local text_type = type(text)
+  if text_type == "string" then
+    table.insert(inlines, PlainText:new(text))
+  elseif text_type == "table" then
+    for _, subtext in ipairs(text) do
+      local subtext_type = type(subtext)
+      local inline
+      if subtext_type == "string" then
+        inline = PlainText:new(subtext)
+      elseif subtext_type == "table" then
+        local format
+        local content
+        for format_, content_ in pairs(subtext) do
+          format = format_
+          content = content_
+        end
+        if format == "bold" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["font-weight"] = "bold"})
+        elseif format == "code" then
+          if type(content) ~= "string" then
+            util.error("Invalid rich text content.")
+          end
+          inline = Code:new(content)
+        elseif format == "italic" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["font-style"] = "italic"})
+        elseif format == "math-ml" then
+          if type(content) ~= "string" then
+            util.error("Invalid rich text content.")
+          end
+          inline = Code:new(content)
+        elseif format == "math-tex" then
+          if type(content) ~= "string" then
+            util.error("Invalid rich text content.")
+          end
+          inline = Code:new(content)
+        elseif format == "preserve" then
+          inline = NoCase:new(self:parse_csl_rich_text(content))
+        elseif format == "quote" then
+          inline = Quoted:new(self:parse_csl_rich_text(content))
+        elseif format == "sc" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["font-variant"] = "small-caps"})
+        elseif format == "strike" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["strike-through"] = true})
+        elseif format == "sub" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["font-variant"] = "small-caps"})
+        elseif format == "sup" then
+          inline = Formatted:new(self:parse_csl_rich_text(content), {["font-variant"] = "small-caps"})
+        end
+      end
+      table.insert(inlines, inline)
+    end
+  else
+    util.error("Invalid text type")
+  end
+
+  return inlines
+end
+
+function InlineElement:parse_html_tags(str, context)
   -- Return a list of inlines
-  if type(str) ~= "string" then
-    print(debug.traceback())
-  end
+  -- 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
@@ -219,7 +333,12 @@
   local quote_stack = {}
   local text_stack = {{}}
 
-  local localized_quotes = context:get_localized_quotes()
+  local localized_quotes
+  if context then
+    localized_quotes = context:get_localized_quotes()
+  else
+    localized_quotes = LocalizedQuotes:new()
+  end
 
   for _, fragment in ipairs(quote_fragments) do
     local top_text_list = text_stack[#text_stack]
@@ -287,7 +406,7 @@
 
   local elements = text_stack[1]
   if #text_stack > 1 then
-    assert(#text_stack == #quote_stack + 1)
+    -- assert(#text_stack == #quote_stack + 1)
     for i, quote in ipairs(quote_stack) do
       if quote == "'" then
         quote = util.unicode["apostrophe"]
@@ -428,8 +547,10 @@
   -- util.debug(self)
   if self._type == "PlainText" then
     self.value = util.capitalize(self.value)
-  elseif self.inlines[1] then
+  elseif self._type ~= "Code" and self._type ~= "MathML" and self._type ~= "MathTeX" then
+    if self.inlines[1] then
     self.inlines[1]:capitalize_first_term()
+    end
   end
 end
 
@@ -461,9 +582,9 @@
 end
 
 function OutputFormat:flatten_seq_ir(ir)
-  if not ir.children then
-    print(debug.traceback())
-  end
+  -- if not ir.children then
+  --   print(debug.traceback())
+  -- end
   if #ir.children == 0 then
     return {}
   end
@@ -563,6 +684,9 @@
       seen_one = seen_one or string_contains_word(inline.value)
     elseif inline._type == "NoCase" or
            inline._type == "NoDecor" or
+           inline._type == "Code" or
+           inline._type == "MathML" or
+           inline._type == "MathTeX" 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
@@ -813,6 +937,11 @@
         end
       end
 
+    elseif inline._type == "Code" or
+        inline._type == "MathML" or
+        inline._type == "MathTeX" then
+      return
+
     elseif inline.inlines then  -- Div, ...
       self:flip_flop(inline.inlines, state)
     end
@@ -869,6 +998,11 @@
         end
       end
 
+    elseif inline._type == "Code" or
+        inline._type == "MathML" or
+        inline._type == "MathTeX" then
+      return
+
     elseif inline.inlines then  -- Div, ...
       self:flip_flop_micro_inlines(inline.inlines, state)
     end
@@ -876,14 +1010,11 @@
 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
+  elseif inline.inlines and #inline.inlines > 0 and inline._type~="Quoted" then
     return find_left(inline.inlines[1])
   else
     return nil
@@ -1158,6 +1289,15 @@
     elseif inline._type == "Linked" then
       return self:write_link(inline, context)
 
+    elseif inline._type == "Code" then
+      return self:write_code(inline.inlines)
+
+    elseif inline._type == "MathML" then
+      return self:write_mathml(inline.inlines)
+
+    elseif inline._type == "MathTeX" then
+      return self:write_math_tex(inline.inlines)
+
     elseif inline._type == "NoCase" or inline._type == "NoDecor" then
       return self:write_inlines(inline.inlines)
 
@@ -1286,13 +1426,29 @@
 
 function LatexWriter:write_link(inline, context)
   if inline.href == inline.value then
+    -- URL
     return string.format("\\url{%s}", inline.value)
+  elseif context.engine.opt.wrap_url_and_doi then
+    return string.format("\\href{%s}{%s}", inline.href, self:write_escaped(inline.value, context))
   else
-    return string.format("\\href{%s}{%s}", inline.href, inline.value)
+    return self:write_escaped(inline.value, context)
   end
 end
 
+function LatexWriter:write_code(inline, context)
+  return inline.value
+end
 
+function LatexWriter:write_mathml(inline, context)
+  util.error("MathML is not supported in LaTeX output.")
+  return ""
+end
+
+function LatexWriter:write_math_tex(inline, context)
+  return string.format("$%s$", inline.value)
+end
+
+
 local HtmlWriter = Markup:new()
 
 HtmlWriter.markups = {
@@ -1371,10 +1527,28 @@
 end
 
 function HtmlWriter:write_link(inline, context)
-  return string.format('<a href="%s">%s</a>', inline.href, inline.value)
+  local content = self:write_escaped(inline.value, context)
+  if context.engine.opt.wrap_url_and_doi then
+    local href = self:write_escaped(inline.href, context)
+    return string.format('<a href="%s">%s</a>', href, content)
+  else
+    return content
+  end
 end
 
+function HtmlWriter:write_code(inline, context)
+  return string.format("<code>%s</code>", inline.value)
+end
 
+function HtmlWriter:write_mathml(inline, context)
+  return string.format('<math xmlns="http://www.w3.org/1998/Math/MathML">%s</math>', inline.value)
+end
+
+function HtmlWriter:write_math_tex(inline, context)
+  return string.format("<code>$%s$</code>", self:write_escaped(inline.value, context))
+end
+
+
 local PlainTextWriter = Markup:new()
 
 PlainTextWriter.markups = {}
@@ -1432,9 +1606,9 @@
 end
 
 function DisamStringFormat:flatten_seq_ir(ir)
-  if not ir.children then
-    print(debug.traceback())
-  end
+  -- if not ir.children then
+  --   print(debug.traceback())
+  -- end
   if #ir.children == 0 then
     return {}
   end
@@ -1455,6 +1629,18 @@
 end
 
 
+function InlineElement.has_space(inlines)
+  local str = DisamStringFormat:output(inlines)
+  if not str then
+    return false
+  end
+  if string.match(util.strip(str), "%s") then
+    return true
+  else
+    return false
+  end
+end
+
 output_module.move_in_puncts = {
   ["."] = true,
   ["!"] = true,

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-util.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -11,6 +11,21 @@
 
 local util = {}
 
+-- Deep copy
+function util.deep_copy(obj)
+  local res
+  if type(obj) == "table" then
+    res = {}
+    for key, value in pairs(obj) do
+      res[key] = util.deep_copy(value)
+    end
+  else
+    res = obj
+  end
+  return res
+end
+
+-- Shallow copy
 function util.clone(obj)
   if type(obj) == "table" then
     local res = {}
@@ -56,7 +71,7 @@
 end
 
 function util.to_ordinal (n)
-  assert(type(n) == "number")
+  -- assert(type(n) == "number")
   local last_digit = n % 10
   if last_digit == 1 and n ~= 11
     then return tostring(n) .. "st"
@@ -237,9 +252,9 @@
 
 -- Python list.extend()
 function util.extend(first, second)
-  if not second then
-    print(debug.traceback())
-  end
+  -- if not second then
+  --   print(debug.traceback())
+  -- end
   local l = #first
   for i, element in ipairs(second) do
     first[l + i] = element
@@ -262,17 +277,17 @@
 
 function util.lstrip (str)
   if not str then
-    return nil
+    error("Invalid input")
   end
-  local res = string.gsub(str, "^%s+", "")
+  local res = string.gsub(str, "^%s*", "")
   return res
 end
 
 function util.rstrip (str)
   if not str then
-    return nil
+    error("Invalid input")
   end
-  local res = string.gsub(str, "%s+$", "")
+  local res = string.gsub(str, "%s*$", "")
   return res
 end
 
@@ -280,16 +295,16 @@
   return util.lstrip(util.rstrip(str))
 end
 
-function util.startswith (str, prefix)
-  if not str then
-    return false
+function util.startswith(str, prefix)
+  if not str or type(str) ~= "string" then
+    print(debug.traceback())
   end
   return string.sub(str, 1, #prefix) == prefix
 end
 
-function util.endswith (str, suffix)
-  if not str then
-    return false
+function util.endswith(str, suffix)
+  if not str or type(str) ~= "string" then
+    print(debug.traceback())
   end
   return string.sub(str, -#suffix) == suffix
 end
@@ -307,7 +322,7 @@
     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
+      -- Roman number without validation
       return false
     end
   end
@@ -828,7 +843,7 @@
 end
 
 function util.convert_roman (number)
-  assert(type(number) == "number")
+  -- assert(type(number) == "number")
   local output = {}
   for _, tuple in ipairs(util.roman_numerals) do
     local letter, value = table.unpack(tuple)
@@ -979,9 +994,9 @@
 -- File IO
 
 function util.read_file(path)
-  if not path then
-    print(debug.traceback())
-  end
+  -- if not path then
+  --   print(debug.traceback())
+  -- end
   local file = io.open(path, "r")
   if not file then
     -- util.error(string.format('Cannot read file "%s".', path))

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2022-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2022-12-25 21:19:05 UTC (rev 65357)
@@ -9,7 +9,7 @@
 local engine = require("citeproc-engine")
 local util = require("citeproc-util")
 
-citeproc.__VERSION__ = "0.2.2"
+citeproc.__VERSION__ = "0.3.0"
 
 citeproc.new = engine.CiteProc.new
 citeproc.util = util

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-12-25 00:50:07 UTC (rev 65356)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty	2022-12-25 21:19:05 UTC (rev 65357)
@@ -9,7 +9,7 @@
 \RequirePackage{expl3}
 \RequirePackage{xparse}
 
-\ProvidesExplPackage {citation-style-language} {2022-09-23} {v0.2.2}
+\ProvidesExplPackage {citation-style-language} {2022-12-25} {v0.3.0}
   {Citation Style Language for LaTeX}
 
 \RequirePackage { l3keys2e }
@@ -34,6 +34,15 @@
   }
   \__csl_load_check:n
 
+
+% Check l3build regression-test
+\bool_new:N \l__csl_regression_test_bool
+% \if_meaning:w \ASSERT \@undefined
+% \else
+%   \bool_set_true:N \l__csl_regression_test_bool
+% \fi
+
+
 \sys_if_engine_luatex:T
   { \lua_now:n { csl = require("citeproc-latex") } }
 
@@ -47,28 +56,62 @@
 \DeclareDocumentCommand \cite { o o m }
   { \__csl_cite:nnn {#1} {#2} {#3} }
 
+\NewDocumentCommand \parencite { o o m }
+  { \__csl_cite:nnn {#1} {#2} {#3} }
 
+\NewDocumentCommand \citep { o o m }
+  { \__csl_cite:nnn {#1} {#2} {#3} }
+
+\NewDocumentCommand \textcite { o o m }
+  { \__csl_text_cite:nnn {#1} {#2} {#3} }
+
+\NewDocumentCommand \citet { o o m }
+  { \__csl_text_cite:nnn {#1} {#2} {#3} }
+
+
 % \cites[⟨prenote⟩][⟨postnote⟩]{⟨key⟩}...[⟨prenote⟩][⟨postnote⟩]{⟨key⟩}
 \NewDocumentCommand \cites { }
   { \__csl_cites: }
 
+\NewDocumentCommand \citeauthor { o o m }
+  { \__csl_cite_author:nnn {#1} {#2} {#3} }
 
+
 \seq_new:N \l__csl_cite_keys_seq
-\seq_new:N \l__csl_cite_items_seq
+\seq_new:N \l__csl_citation_items_seq
+\prop_new:N \l__csl_citation_properties_prop
+\prop_new:N \l__csl_citation_info_prop
 
 \cs_new:Npn \__csl_cite:nnn #1#2#3
   {
     \seq_clear:N \l__csl_cite_keys_seq
-    \seq_clear:N \l__csl_cite_items_seq
-    \__csl_process_cite_items:nnn {#1} {#2} {#3}
-    \__csl_make_citation:NN \l__csl_cite_keys_seq \l__csl_cite_items_seq
+    \seq_clear:N \l__csl_citation_items_seq
+    \prop_clear:N \l__csl_citation_properties_prop
+    \__csl_process_cite_input:nnn {#1} {#2} {#3}
+    \__csl_process_citation_info:NN \l__csl_cite_keys_seq \l__csl_citation_items_seq
+    \__csl_make_citation:N \l__csl_citation_info_prop
   }
 
 
+\cs_new:Npn \__csl_text_cite:nnn #1#2#3
+  {
+    \seq_clear:N \l__csl_cite_keys_seq
+    \seq_clear:N \l__csl_citation_items_seq
+    \prop_clear:N \l__csl_citation_properties_prop
+    \__csl_process_cite_input:nnn {#1} {#2} {#3}
+    \__csl_process_citation_info:NN \l__csl_cite_keys_seq \l__csl_citation_items_seq
+    \prop_put:Nnn \l__csl_citation_properties_prop { noteIndex } { 0 }
+    \prop_put:Nnn \l__csl_citation_properties_prop { mode } { composite }
+    % \bool_set_false:N \l__csl_note_bool
+    \__csl_make_citation:N \l__csl_citation_info_prop
+  }
+
+
 \cs_new:Npn \__csl_cites:
   {
     \seq_clear:N \l__csl_cite_keys_seq
-    \seq_clear:N \l__csl_cite_items_seq
+    \seq_clear:N \l__csl_citation_items_seq
+    \prop_clear:N \l__csl_citation_properties_prop
     \__csl_next_cites:nnn
   }
 
@@ -75,30 +118,47 @@
 \NewDocumentCommand \__csl_next_cites:nnn { o o g }
   {
     \tl_if_novalue:nTF {#3}
-      { \__csl_make_citation:NN \l__csl_cite_keys_seq \l__csl_cite_items_seq }
       {
-        \__csl_process_cite_items:nnn {#1} {#2} {#3}
+        \__csl_process_citation_info:NN \l__csl_cite_keys_seq \l__csl_citation_items_seq
+        \__csl_make_citation:N \l__csl_citation_info_prop
+      }
+      {
+        \__csl_process_cite_input:nnn {#1} {#2} {#3}
         \__csl_next_cites:nnn
       }
   }
 
 
+\cs_new:Npn \__csl_cite_author:nnn #1#2#3
+  {
+    \seq_clear:N \l__csl_cite_keys_seq
+    \seq_clear:N \l__csl_citation_items_seq
+    \prop_clear:N \l__csl_citation_properties_prop
+    \__csl_process_cite_input:nnn {#1} {#2} {#3}
+    \__csl_process_citation_info:NN \l__csl_cite_keys_seq \l__csl_citation_items_seq
+    \prop_put:Nnn \l__csl_citation_properties_prop { noteIndex } { 0 }
+    \prop_put:Nnn \l__csl_citation_properties_prop { mode } { author-only }
+    \bool_set_false:N \l__csl_note_bool
+    \__csl_make_citation:N \l__csl_citation_info_prop
+  }
+
+
 % Appends the cite key into \l__csl_cite_keys_seq and cite-items into
-% \l__csl_cite_items_seq
+% \l__csl_citation_items_seq
 % #1, #2: prenote/postnote
 % #3: keys
-\cs_new:Npn \__csl_process_cite_items:nnn #1#2#3
+\cs_new:Npn \__csl_process_cite_input:nnn #1#2#3
   {
     \tl_if_novalue:nTF {#2}
       {
         \tl_if_novalue:nTF {#1}
-          { \__csl_process_cite_items_aux:nnn { } { } {#3} }
-          { \__csl_process_cite_items_aux:nnn { } {#1} {#3} }
+          { \__csl_process_cite_input_aux:nnn { } { } {#3} }
+          { \__csl_process_cite_input_aux:nnn { } {#1} {#3} }
       }
-      { \__csl_process_cite_items_aux:nnn {#1} {#2} {#3} }
+      { \__csl_process_cite_input_aux:nnn {#1} {#2} {#3} }
   }
 
-\cs_new:Npn \__csl_process_cite_items_aux:nnn #1#2#3
+\cs_new:Npn \__csl_process_cite_input_aux:nnn #1#2#3
   % #1: prenote, #2: postnote, #3: keys
   % Return: "{id={ITEM-1},{locator=6},...}, {id={ITEM-2},...}, ..."
   {
@@ -120,7 +180,7 @@
 
 \cs_new:Npn \__csl_process_cite_item:nnn #1#2#3
   % #1: prenote, #2: postnote, #3: key
-  % Save "{id={ITEM},locator={42},label={page}}" into \l__csl_cite_items_seq
+  % Save "{id={ITEM},locator={42},label={page}}" into \l__csl_citation_items_seq
   {
     \prop_clear:N \l__csl_cite_item_prop
     \prop_put:Nnn \l__csl_cite_item_prop { id } {#3}
@@ -148,11 +208,10 @@
               }
           }
       }
-    \seq_clear:N \l_tmpa_seq
-    \prop_map_inline:Nn \l__csl_cite_item_prop
-      { \seq_put_right:Nn \l_tmpa_seq { ##1 = {##2} } }
-    \seq_put_right:Nx \l__csl_cite_items_seq
-      { { \seq_use:Nn \l_tmpa_seq { , } } }
+    \__csl_serialize_prop:NN \l__csl_cite_item_prop \l_tmpa_tl
+    \tl_put_left:NV \l_tmpa_tl { \c_left_brace_str }
+    \tl_put_right:NV \l_tmpa_tl { \c_right_brace_str }
+    \seq_put_right:NV \l__csl_citation_items_seq \l_tmpa_tl
   }
 
 \cs_new:Npn \__csl_set_locator:nn #1#2
@@ -170,9 +229,10 @@
     suppress-author .prop_put:N = \l__csl_cite_item_prop,
     author-only     .prop_put:N = \l__csl_cite_item_prop,
     uris            .prop_put:N = \l__csl_cite_item_prop,
+    % Locators.
     act             .code:n = { \__csl_set_locator:nn { act             } {#1} } ,
     appendix        .code:n = { \__csl_set_locator:nn { appendix        } {#1} } ,
-    article-locator .code:n = { \__csl_set_locator:nn { article-locator } {#1} } ,
+    article         .code:n = { \__csl_set_locator:nn { article-locator } {#1} } ,
     book            .code:n = { \__csl_set_locator:nn { book            } {#1} } ,
     canon           .code:n = { \__csl_set_locator:nn { canon           } {#1} } ,
     chapter         .code:n = { \__csl_set_locator:nn { chapter         } {#1} } ,
@@ -195,10 +255,12 @@
     supplement      .code:n = { \__csl_set_locator:nn { supplement      } {#1} } ,
     table           .code:n = { \__csl_set_locator:nn { table           } {#1} } ,
     timestamp       .code:n = { \__csl_set_locator:nn { timestamp       } {#1} } ,
-    title-locator   .code:n = { \__csl_set_locator:nn { title-locator   } {#1} } ,
+    title           .code:n = { \__csl_set_locator:nn { title-locator   } {#1} } ,
     verse           .code:n = { \__csl_set_locator:nn { verse           } {#1} } ,
     version         .code:n = { \__csl_set_locator:nn { version         } {#1} } ,
     volume          .code:n = { \__csl_set_locator:nn { volume          } {#1} } ,
+    % Citation properties
+    infix           .prop_put:N = \l__csl_citation_properties_prop,
   }
 
 
@@ -205,40 +267,62 @@
 \tl_new:N \l__csl_citation_id_tl
 \tl_new:N \l__csl_cite_items_tl
 \tl_new:N \l__csl_note_index_tl
+
+% Load the cite keys and prepare:
+%   - \l__csl_citation_id_tl
+%   - \l__csl_citation_properties_prop
+%
+% #1: \l__csl_cite_keys_seq
+% #2: \l__csl_citation_items_seq
+\cs_new:Npn \__csl_process_citation_info:NN #1#2
+  {
+    \__csl_process_citation_id:NN \l__csl_citation_id_tl #1
+    \__csl_get_note_index:N \l__csl_note_index_tl
+    \prop_put:NnV \l__csl_citation_properties_prop { noteIndex } \l__csl_note_index_tl
+  }
+
+
 \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
+\tl_new:N \l__csl_citation_properties_tl
+
+% Write citation info to aux and print the citation contents.
+% #1: \l__csl_citation_info_prop
+\cs_new:Npn \__csl_make_citation:N #1
   {
-    \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 { , } }
-    \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
-      {
-        \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 } }
-      }
+    \prop_clear:N \l__csl_citation_info_prop
+    % citationID
+    \prop_put:NnV \l__csl_citation_info_prop { citationID } \l__csl_citation_id_tl
+    % citationItems
+    \__csl_serialize_seq:NN \l__csl_citation_items_seq \l__csl_cite_items_tl
+    \prop_put:NnV \l__csl_citation_info_prop { citationItems } \l__csl_cite_items_tl
+    % properties
+    \__csl_serialize_prop:NN \l__csl_citation_properties_prop \l__csl_citation_properties_tl
+    \prop_put:NnV \l__csl_citation_info_prop { properties } \l__csl_citation_properties_tl
+    \__csl_serialize_prop:NN \l__csl_citation_info_prop \l__csl_citation_info_tl
+    % Write to .aux file
+    % \tl_show:N \l__csl_citation_info_tl
     \exp_args:NV \__csl_write_aux_citation:n \l__csl_citation_info_tl
+    \bool_if:NT \l__csl_regression_test_bool
+      { \tl_show:N \l__csl_citation_info_tl }
+    % Print the citation string
     \prop_get:NVNTF \g__csl_citations_prop \l__csl_citation_id_tl
       \l__csl_citation_tl
-      { \exp_args:NV \__csl_print_citation:n \l__csl_citation_tl }
+      { \__csl_print_citation:N \l__csl_citation_tl }
       {
         \bool_if:NTF \l__csl_engine_initialized_bool
           {
-            \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
+            % \tl_show:N \l__csl_citation_info_tl
+            % \tl_set:Nf \l__csl_citation_tl
+            %   { \exp_args:NV \__csl_cite_aux:n \l__csl_citation_info_tl }
+            \group_begin:
+              \char_set_catcode_other:N \%
+              \char_set_catcode_other:N \#
+              \exp_args:NV \__csl_cite_aux:n \l__csl_citation_info_tl
+              \__csl_print_citation:N \l__csl_citation_tl
+            \group_end:
           }
           {
             \exp_args:Nx \__csl_print_undefined_citation:n
@@ -247,7 +331,35 @@
       }
   }
 
+\cs_new:Npn \__csl_cite_aux:n #1
+  { \lua_now:e { csl.cite("\lua_escape:n {#1}") } }
 
+% #1: seq
+% #2: tl
+\cs_new:Npn \__csl_serialize_seq:NN #1#2
+  {
+    \tl_clear:N #2
+    \seq_map_inline:Nn #1
+      {
+        \tl_if_empty:NF #2
+          { \tl_put_right:Nn #2 { , } }
+        \tl_put_right:Nn #2 { ##1 }
+      }
+  }
+
+% #1: prop
+% #2: tl
+\cs_new:Npn \__csl_serialize_prop:NN #1#2
+  {
+    \tl_clear:N #2
+    \prop_map_inline:Nn #1
+      {
+        \tl_if_empty:NF #2
+          { \tl_put_right:Nn #2 { , } }
+        \tl_put_right:Nn #2 { ##1 = { ##2 } }
+      }
+  }
+
 \tl_new:N \l__csl_cite_keys_tl
 \tl_new:N \l__csl_citation_count_tl
 \int_new:N \l__csl_citation_count_int
@@ -260,6 +372,8 @@
   {
     \tl_set:Nx \l__csl_cite_keys_tl
       { \seq_use:Nn #2 { , } }
+    % \prop_show:N \g__csl_citations_count_prop
+    % \tl_show:N \l__csl_cite_keys_tl
     \prop_get:NVNTF \g__csl_citations_count_prop \l__csl_cite_keys_tl
       \l__csl_citation_count_tl
       {
@@ -274,18 +388,31 @@
   }
 
 
+\int_new:N \g__csl_pseudo_note_index_int
+\int_gset:Nn \g__csl_pseudo_note_index_int { 0 }
+
 % 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
   {
-    \bool_if:NTF \l__csl_note_style_bool
+    \bool_if:NTF \l__csl_note_bool
       {
         \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 } }
+      {
+        \tl_if_empty:NTF \l__csl_class_tl
+          {
+            % The style class (in-text/note) is undetermined.
+            \int_set_eq:Nc \l_tmpa_int { c@ \@mpfn }
+            \int_gincr:N \g__csl_pseudo_note_index_int
+            \int_add:Nn \l_tmpa_int { \g__csl_pseudo_note_index_int }
+            \tl_set:Nx #1 { \int_use:N \l_tmpa_int }
+          }
+          { \tl_set:Nx #1 { 0 } }
+      }
   }
 
 
@@ -293,34 +420,19 @@
   % #1: citation info "{<citationID>}{{id=ITEM-1},{id=ITEM-2}}{<noteIndex>}"
   {
     \if at filesw
-      \iow_now:Nx \@auxout
-        { \token_to_str:N \citation { #1 } }
+      \iow_now:Nn \@auxout { \citation {#1} }
     \fi
   }
 
 
-% \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 }
-
-\cs_new:Npn \__csl_print_citation_aux:nn #1#2
-  % #1: citation type, "in-text" or "note"
-  % #2: citation text
+% #1: \l__csl_citation_tl
+\cs_new:Npn \__csl_print_citation:N #1
   {
-    \tl_set:Nn \l__csl_citation_tl {#2}
-    \tl_if_eq:nnTF {#1} { note }
-      { \footnote {#2} }
-      {#2}
+    \bool_if:NT \l__csl_regression_test_bool
+      { \tl_show:N #1 }
+    \bool_if:NTF \l__csl_note_bool
+      { \footnote {#1} }
+      {#1}
   }
 
 
@@ -327,9 +439,11 @@
 \cs_new:Npn \__csl_print_undefined_citation:n #1
   % #1: keys
   {
-    \tl_set:Nn \l__csl_citation_tl { [ \textbf {#1} ] }
     \G at refundefinedtrue
     \msg_warning:nnn { citation-style-language } { citation / undefined } {#1}
+    \tl_set:Nn \l__csl_citation_tl { [ \textbf {#1} ] }
+    \bool_if:NT \l__csl_regression_test_bool
+      { \tl_show:N \l__csl_citation_tl }
     \group_begin:
       \reset at font [ \textbf {#1} ]
     \group_end:
@@ -346,10 +460,10 @@
 \cs_new:Npn \__csl_no_cite:n #1
   {
     \seq_clear:N \l__csl_cite_keys_seq
-    \seq_clear:N \l__csl_cite_items_seq
-    \__csl_process_cite_items:nnn { } { } {#1}
+    \seq_clear:N \l__csl_citation_items_seq
+    \__csl_process_cite_input:nnn { } { } {#1}
     \tl_set:Nx \l__csl_cite_items_tl
-      { \seq_use:Nn \l__csl_cite_items_seq { , } }
+      { \seq_use:Nn \l__csl_citation_items_seq { , } }
     \tl_set:Nx \l__csl_citation_info_tl
       {
         citationID    = { @nocite } ,
@@ -356,9 +470,14 @@
         citationItems = { \l__csl_cite_items_tl } ,
         properties    = { noteIndex = { 0 } }
       }
+    \bool_if:NT \l__csl_regression_test_bool
+      { \tl_show:N \l__csl_citation_info_tl }
     \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") } }
+    \tl_clear:N \l__csl_citation_tl
+    \bool_if:NT \l__csl_regression_test_bool
+      { \tl_show:N \l__csl_citation_tl }
 }
 
 
@@ -369,9 +488,7 @@
         \AtBeginDocument
           { \exp_args:Nx \__csl_write_aux_citation:n { #1 } }
       }
-      {
-        \exp_args:Nx \__csl_write_aux_citation:n { #1 }
-      }
+      { \exp_args:Nx \__csl_write_aux_citation:n { #1 } }
   }
 
 
@@ -396,10 +513,10 @@
 
 \cs_new:Npn \cslcite #1#2
   {
-    \bibcite {#1} {#2}
     % \if at filesw
-    %   \iow_now:Nx \@auxout { \token_to_str:N \bibcite {#1} {#2} }
+    %   \iow_now:Nn \@auxout { \bibcite {#1} {#2} }
     % \fi
+    \bibcite {#1} {#2}
   }
 
 
@@ -411,22 +528,22 @@
 
 \NewDocumentCommand \printbibliography { O { } }
   {
-    \sys_if_engine_luatex:TF
+    \sys_if_engine_luatex:T
       {
-        \bool_if:NTF \l__csl_engine_initialized_bool
+        \bool_if:NT \l__csl_engine_initialized_bool
           {
             \__csl_collect_bibliography:n { \lua_now:n { csl.bibliography() } }
-            % \tl_show:N \g__csl_bibliography_tl
-            \tl_use:N \g__csl_bibliography_tl
           }
-          { \msg_warning:nnn { citation-style-language } { bibliography / empty } }
       }
+    \tl_if_empty:NTF \g__csl_bibliography_tl
+      { \msg_warning:nn { citation-style-language } { bibliography / empty } }
       {
-        \tl_if_empty:NTF \g__csl_bibliography_tl
-          { \msg_warning:nnn { citation-style-language } { bibliography / empty } }
+        \bool_if:NT \l__csl_regression_test_bool
           {
-            \tl_use:N \g__csl_bibliography_tl
+            \seq_set_split:NnV \l__csl_bibliography_seq { \par } \g__csl_bibliography_tl
+            \seq_show:N \l__csl_bibliography_seq
           }
+        \tl_use:N \g__csl_bibliography_tl
       }
   }
 
@@ -435,6 +552,8 @@
 
 
 \tl_new:N \l__csl_style_tl
+\tl_new:N \l__csl_class_tl
+\bool_new:N \l__csl_note_bool
 \clist_new:N \l__csl_bib_resources_clist
 \tl_new:N \l__csl_prefix_separator_tl
 \tl_new:N \l__csl_suffix_separator_tl
@@ -449,7 +568,14 @@
 
 \keys_define:nn { csl }
   {
+    regression-test .bool_set:N = \l__csl_regression_test_bool ,
     style .tl_set:N = \l__csl_style_tl ,
+    class .code:n =
+      {
+        \tl_set:Nn \l__csl_class_tl {#1}
+        \tl_if_eq:NnT \l__csl_class_tl { note }
+          { \bool_set_true:N \l__csl_note_bool }
+      } ,
     prefix-separator .tl_set:N = \l__csl_prefix_separator_tl ,
     suffix-separator .tl_set:N = \l__csl_suffix_separator_tl ,
     locale .tl_set:N = \l__csl_locale_tl ,
@@ -593,6 +719,7 @@
 
 \tl_new:N \g__csl_bibliography_tl
 \tl_new:N \g__csl_bibliography_setup_tl
+\seq_new:N \l__csl_bibliography_seq
 
 
 \AtBeginDocument { \__csl_at_begin_document_hook: }
@@ -602,12 +729,8 @@
   {
     \__csl_write_aux_info:
     \sys_if_engine_luatex:TF
-      {
-        \__csl_initialize_lua_module:
-      }
-      {
-        \__csl_load_bbl:
-      }
+      { \__csl_initialize_lua_module: }
+      { \__csl_load_bbl: }
   }
 
 
@@ -652,16 +775,16 @@
   }
 
 
-\str_new:N \l__csl_style_class_str
+% \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
+% \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 }
+      \tl_set:Nx \l__csl_class_tl { \lua_now:n { csl.get_style_class() } }
+      \tl_if_eq:NnT \l__csl_class_tl { note }
+        { \bool_set_true:N \l__csl_note_bool }
     }
 }
 
@@ -692,17 +815,19 @@
     % write to aux file
     \prop_if_empty:NF \l__csl_options_clist
       {
-        \if at filesw
-          \iow_now:Nx \@auxout
-            {
-              \token_to_str:N \csloptions
-                { \clist_use:Nn \l__csl_options_clist { , } }
-            }
-        \fi
+        \exp_args:Nx \__csl_write_aux_options:n
+          { \clist_use:Nn \l__csl_options_clist { , } }
       }
   }
 
+\cs_new:Npn \__csl_write_aux_options:n #1
+  {
+    \if at filesw
+      \iow_now:Nn \@auxout { \csloptions {#1} }
+    \fi
+  }
 
+
 % Load .bbl at the beginning of document to save one pass of latex.
 % In this procedure, the \cslcite command is processed and the contents
 % of `thebibliography` is stored into \g__csl_bibliography_tl.
@@ -711,10 +836,10 @@
     % The \@input@ prints "No file ....bbl" in the .log file from which
     % the latexmk decides to run $bibtex or not.
     \__csl_collect_bibliography:n { \@input@ { \jobname .bbl } }
-    % \file_if_exist_input:nF { \jobname .bbl }
-    %   {
-    %     \msg_warning:nnx { citation-style-language } { file / non-exist} { \jobname .bbl }
-    %   }
+    % Execute the \cslsetup part to set the style class.
+    % \tl_show:N \g__csl_bibliography_setup_tl
+    \tl_use:N \g__csl_bibliography_setup_tl
+    % \bool_show:N \l__csl_note_bool
   }
 
 % Collection the bibliography (as well as \cslsetup) into \g__csl_bibliography_setup_tl
@@ -721,6 +846,9 @@
 \cs_new:Npn \__csl_collect_bibliography:n #1
   {
     \group_begin:
+      % URLs may contain "%" and "#" characters.
+      \char_set_catcode_other:N \%
+      \char_set_catcode_other:N \#
       \RenewDocumentCommand \cslsetup { m }
         { \tl_gset:Nn \g__csl_bibliography_setup_tl { \cslsetup { ##1 } } }
       \RenewDocumentEnvironment { thebibliography } { m +b }
@@ -780,6 +908,7 @@
 \cs_new:Npn \__csl_write_aux_bibdata:n #1
   {
     \if at filesw
+      % Full expansion for files like \jobname.bib
       \iow_now:Nx \@auxout { \token_to_str:N \csl at data {#1} }
     \fi
   }
@@ -1043,7 +1172,7 @@
   {
     \l__csl_bib_postamble_tl
     \tl_set:Nn \@noitemerr
-      { \msg_warning:nnn { citation-style-language } { bibliography / empty } }
+      { \msg_warning:nn { citation-style-language } { bibliography / empty } }
     \endlist
     \__csl_reset_bib_format:
   }
@@ -1077,9 +1206,8 @@
   {
     \BlockquoteDisable
       {
-        \cs_set_eq:NN \__csl_make_citation:NN \use_none:nn
-        % \sys_if_engine_luatex:T
-        %   { \lua_now:n { csl.preview_mode = true } }
+        \cs_set_eq:NN \__csl_process_citation_info:NN \use_none:nn
+        \cs_set_eq:NN \__csl_make_citation:N \use_none:n
       }
   }
 



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