texlive[72038] Master/texmf-dist: citation-style-language (15aug24)

commits+karl at tug.org commits+karl at tug.org
Thu Aug 15 22:01:24 CEST 2024


Revision: 72038
          https://tug.org/svn/texlive?view=revision&revision=72038
Author:   karl
Date:     2024-08-15 22:01:24 +0200 (Thu, 15 Aug 2024)
Log Message:
-----------
citation-style-language (15aug24)

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.1
    trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.man1.pdf
    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-manager.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
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua
    trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua
    trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty
    trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml
    trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-zh-TW.xml
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl
    trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl

Modified: trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/CHANGELOG.md	2024-08-15 20:01:24 UTC (rev 72038)
@@ -7,14 +7,22 @@
 
 ## [Unreleased]
 
+## [0.6.1] - 2024-08-15
+
+### Fixed
+
+- Fix parsing quotation marks ([#71](https://github.com/zepinglee/citeproc-lua/discussions/71)).
+
 ## [0.6.0] - 2024-07-31
 
+### Added
+
 - Add support for multiple bibliographies (`refsection` environment).
 - Add global `ref-section` option.
 
 ### Fixed
 
-- Fix an error of empty locator in citation (([#70](https://github.com/zepinglee/citeproc-lua/discussions/70)))
+- Fix an error of empty locator in citation ([#70](https://github.com/zepinglee/citeproc-lua/discussions/70)).
 
 ## [0.5.1] - 2024-07-10
 
@@ -212,7 +220,8 @@
 
 - Initial CTAN release.
 
-[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.6.0...HEAD
+[Unreleased]: https://github.com/zepinglee/citeproc-lua/compare/v0.6.1...HEAD
+[0.6.1]: https://github.com/zepinglee/citeproc-lua/compare/v0.6.0...v0.6.1
 [0.6.0]: https://github.com/zepinglee/citeproc-lua/compare/v0.5.1...v0.6.0
 [0.5.1]: https://github.com/zepinglee/citeproc-lua/compare/v0.5.0...v0.5.1
 [0.5.0]: https://github.com/zepinglee/citeproc-lua/compare/v0.4.9...v0.5.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	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/doc/latex/citation-style-language/citation-style-language-doc.tex	2024-08-15 20:01:24 UTC (rev 72038)
@@ -51,7 +51,7 @@
   }%
 }
 
-\date{2024-07-31 v0.6.0}
+\date{2024-08-15 v0.6.1}
 
 \maketitle
 

Modified: trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/doc/man/man1/citeproc-lua.1	2024-08-15 20:01:24 UTC (rev 72038)
@@ -1,4 +1,4 @@
-.TH citeproc-lua 1 "0.6.0"
+.TH citeproc-lua 1 "0.6.1"
 .SH NAME
 citeproc-lua \- make CSL citations and bibliography for LaTeX
 .SH SYNOPSIS

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-context.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-context.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -23,9 +23,11 @@
 
 --- at class Context
 --- at field reference ItemData?
---- at field format any
+--- at field format OutputFormat
 --- at field cite_id string?
+--- at field engine CiteProc
 --- at field style any Style
+--- at field lang LanguageCode
 --- at field locale Locale?
 --- at field name_citation any
 --- at field names_delimiter any
@@ -60,6 +62,7 @@
 
 function Context:new()
   local o = {
+    lang = "en-US",
     in_bibliography = false
   }
   setmetatable(o, self)
@@ -348,6 +351,9 @@
 end
 
 
+--- at class IrState
+--- at field macro_stack string[]
+--- at field suppressed {[string]: boolean}
 local IrState = {}
 
 function IrState:new(style, cite_id, cite, reference)

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-element.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -245,6 +245,9 @@
   return ir
 end
 
+--- at param str string
+--- at param context Context
+--- at return InlineElement[]
 function Element:render_text_inlines(str, context)
   if str == "" then
     return {}

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -101,6 +101,7 @@
 --- at field opt table
 --- at field registry Registry
 --- at field cite_first_note_numbers table<ItemId, NoteIndex>
+--- at field locale_tags_info_dict {[LanguageCode]: table}
 local CiteProc = {}
 
 --- at class CiteProcSys
@@ -179,6 +180,8 @@
   o.person_names = {}
   o.person_names_by_output = {}
 
+  o.locale_tags_info_dict = {}
+
   setmetatable(o, { __index = CiteProc })
   return o
 end
@@ -420,12 +423,21 @@
     cite_item.label = "page"
   end
 
+  local the_context = Context:new()
+  the_context.engine = self
+  the_context.style = self.style
+  the_context.area = self
+  the_context.in_bibliography = false
+  the_context.lang = self.lang
+  the_context.locale = self:get_locale(self.lang)
+  the_context.format = self.output_format
+
   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_inlines = InlineElement:parse(cite_item.prefix, nil, true)
+      cite_item.prefix_inlines = InlineElement:parse(cite_item.prefix, the_context, true)
     end
   end
   if cite_item.suffix then
@@ -432,7 +444,7 @@
     if cite_item.suffix == "" then
       cite_item.suffix = nil
     else
-      cite_item.suffix_inlines = InlineElement:parse(cite_item.suffix, nil, true)
+      cite_item.suffix_inlines = InlineElement:parse(cite_item.suffix, the_context, true)
     end
   end
 
@@ -1065,6 +1077,7 @@
   context.style = self.style
   context.area = self.style.bibliography
   context.in_bibliography = true
+  context.lang = self.lang
   context.locale = self:get_locale(self.lang)
   context.name_inheritance = self.style.bibliography.name_inheritance
   context.format = SortStringFormat:new()
@@ -1177,6 +1190,10 @@
   return o
 end
 
+--- at param engine CiteProc
+--- at param state IrState
+--- at param context Context
+--- at return IrNode?
 function Macro:build_ir(engine, state, context)
   local ir = self:build_group_ir(engine, state, context)
   return ir

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	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-ir-node.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -32,6 +32,8 @@
 --- at field text string?
 --- at field formatting any
 --- at field affixes any
+--- at field display any
+--- at field quotes LocalizedQuotes
 --- at field inlines InlineElement[]?
 --- at field children IrNode[]?
 --- at field delimiter string?

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-manager.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-manager.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-manager.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -292,7 +292,6 @@
 --- at param citeproc_sys CiteProcSys
 function RefSection:_check_dependent_style(citeproc_sys)
   if self.engine:is_dependent_style() then
-    local default_locale = self.engine.style.default_locale;
     local parent_style_link = self.engine:get_independent_parent()
     if not parent_style_link then
       return nil
@@ -304,14 +303,16 @@
       util.error(string.format("Failed to load style '%s.csl'", parent_style_id))
       return nil
     end
+    local default_locale = self.engine.style.default_locale;
     local force_lang = false
     if self.lang and self.lang ~= "" then
       force_lang = true
+    elseif default_locale and default_locale ~= "" then
+      self.lang = default_locale
     else
       self.lang = nil
     end
     self.engine = citeproc.new(citeproc_sys, style, self.lang, force_lang)
-    self.engine.style.default_locale = default_locale
   end
 end
 

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-bibliography.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -118,7 +118,6 @@
     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
@@ -127,6 +126,7 @@
 
     -- CSL-M: `layout` extension
     local active_layout, context_lang = util.get_layout_by_language(self, engine, context.reference)
+    context.lang = context_lang
     context.locale = engine:get_locale(context_lang)
 
     local ir = self:build_ir(engine, state, context, active_layout)

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-citation.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -255,6 +255,7 @@
   context.style = engine.style
   context.area = self
   context.in_bibliography = false
+  context.lang = engine.lang
   context.locale = engine:get_locale(engine.lang)
   context.name_inheritance = self.name_inheritance
   context.format = output_format
@@ -394,6 +395,7 @@
   context.style = engine.style
   context.area = self
   context.in_bibliography = false
+  context.lang = engine.lang
   context.locale = engine:get_locale(engine.lang)
   context.name_inheritance = self.name_inheritance
   context.format = SortStringFormat:new()
@@ -456,7 +458,6 @@
   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
@@ -465,6 +466,7 @@
   context.reference = engine.registry.registry[cite_item.id]
 
   local active_layout, context_lang = util.get_layout_by_language(self, engine, context.reference)
+  context.lang = context_lang
   context.locale = engine:get_locale(context_lang)
 
   local ir

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	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-node-text.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -229,7 +229,12 @@
 end
 
 function Text:build_macro_ir(engine, state, context)
+  --- at type Macro
   local macro = context:get_macro(self.macro)
+  if not macro then
+    util.error(string.format("Macro '%s' not found", self.macro))
+    return nil
+  end
   -- util.debug(string.format('<macro name="%s">', self.macro))
   state:push_macro(self.macro)
   local ir = macro:build_ir(engine, state, context)

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc-output.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -101,12 +101,15 @@
   end
   text = text .. self._type
   if self.formatting then
-    text = text .. "["
+    text = text .. " ["
     for attr, value in pairs(self.formatting) do
       text = text .. attr .. '="' .. value .. '"'
     end
-    text = text .. "]"
+    text = text .. "] "
   end
+  if self.is_inner then
+    text = text .. " [inner quotes]"
+  end
   text = text .. "("
   if self.value then
     text = text .. '"' .. self.value .. '"'
@@ -167,16 +170,17 @@
 
 --- at class Quoted: InlineElement
 --- at field is_inner boolean
---- at field quotes table
+--- at field quotes LocalizedQuotes
 local Quoted = InlineElement:derive("Quoted")
 
 --- at param inlines InlineElement[]
---- at param localized_quotes table?
+--- at param localized_quotes LocalizedQuotes?
+--- at param is_inner boolean?
 --- at return Quoted
-function Quoted:new(inlines, localized_quotes)
+function Quoted:new(inlines, localized_quotes, is_inner)
   local o = InlineElement.new(self)
   o.inlines = inlines
-  o.is_inner = false
+  o.is_inner = is_inner or false
   if localized_quotes then
     o.quotes = localized_quotes
   else
@@ -410,224 +414,457 @@
   return inlines
 end
 
-local function tokens2inlines(tokens)
-  local inlines = {}
-  local i = 1
-  while i <= #tokens do
-    local token = tokens[i]
-    if type(token) == "string" then
-      local text = token
-      local j = i + 1
-      while j <= #tokens and type(tokens[j]) == "string" do
-        text = text .. tokens[j]
-        j = j + 1
-      end
-      i = j
-      table.insert(inlines, PlainText:new(text))
-    else
-      table.insert(inlines, token)
-      i = i + 1
+
+local P = lpeg.P
+local Ct = lpeg.Ct
+local Cp = lpeg.Cp
+
+-- Lua's regex doesn't support groups and thus we have to implement the same
+-- logic with `lpeg`.
+local code_pattern =
+    Ct(Cp() * P("<code>") * Cp()) * ((1 - P("</code>")) ^ 0) *
+    Ct(Cp() * P("</code>") * Cp())
+    + Ct(Cp() * P("<script>") * Cp()) * ((1 - P("</script>")) ^ 0) *
+    Ct(Cp() * P("</script>") * Cp())
+    + Ct(Cp() * P("<pre>") * Cp()) * ((1 - P("</pre>")) ^ 0) *
+    Ct(Cp() * P("</pre>") * Cp())
+    + Ct(Cp() * P("<math>") * Cp()) * ((1 - P("</math>")) ^ 0) * Ct(Cp() * P("</math>") * Cp())
+    + Ct(Cp() * P("<math-tex>") * Cp()) * ((1 - P("</math-tex>")) ^ 0) * Ct(Cp() * P("</math-tex>") * Cp())
+local basic_tag_pattern = P '<span class="nocase">'
+    + P '<span class="nodecor">'
+    + P '<span style="font-variant:small-caps;">'
+    + P '<sc>'
+    + P '<i>'
+    + P '<b>'
+    + P '<sup>'
+    + P '<sub>'
+    + P ' "'
+    + P " '"
+    + P '("'
+    + P "('"
+    + P "“"
+    + P "‘"
+    + P '</span>'
+    + P '</sc>'
+    + P '</i>'
+    + P '</b>'
+    + P '</sup>'
+    + P '</sub>'
+    + P '"'
+    + P "'"
+    + P "”"
+    + P "’"
+
+local default_tag_pattern = Ct((code_pattern + Ct(Cp() * basic_tag_pattern * Cp()) + P(1)) ^ 0)
+
+local default_openers_info = {
+  ['<span class="nocase">'] = {
+    closer = "</span>",
+    quotes = false,
+  },
+  ['<span class="nodecor">'] = {
+    closer = "</span>",
+    quotes = false,
+  },
+  ['<span style="font-variant:small-caps;">'] = {
+    closer = "</span>",
+    quotes = false,
+  },
+  ['<sc>'] = {
+    closer = "</sc>",
+    quotes = false,
+  },
+  ['<i>'] = {
+    closer = "</i>",
+    quotes = false,
+  },
+  ['<b>'] = {
+    closer = "</b>",
+    quotes = false,
+  },
+  ['<sup>'] = {
+    closer = "</sup>",
+    quotes = false,
+  },
+  ['<sub>'] = {
+    closer = "</sub>",
+    quotes = false,
+  },
+  [' "'] = {
+    closer = '"',
+    quotes = true,
+  },
+  [" '"] = {
+    closer = "'",
+    quotes = true,
+  },
+  ["“"] = {
+    closer = "”",
+    quotes = true,
+  },
+  ["‘"] = {
+    closer = "’",
+    quotes = true,
+  },
+  ['<code>'] = {
+    closer = "</code>",
+    quotes = false,
+  },
+  ['<script>'] = {
+    closer = "</script>",
+    quotes = false,
+  },
+  ['<pre>'] = {
+    closer = "</pre>",
+    quotes = false,
+  },
+  ['<math>'] = {
+    closer = "</math>",
+    quotes = false,
+  },
+  ['<math-tex>'] = {
+    closer = "</math-tex>",
+    quotes = false,
+  },
+}
+
+local function _quoted(str)
+  str = string.gsub(str, "'", "\'")
+  return string.format("'%s'", str)
+end
+
+--- at param locale string
+--- at param context Context
+local function make_locale_tag_info(locale, context)
+  if context.engine.locale_tags_info_dict[locale] then
+    return
+  end
+  local localed_quotes = context:get_localized_quotes()
+
+  local tag_pattern = basic_tag_pattern
+  local openers_info = util.deep_copy(default_openers_info)
+
+  if localed_quotes.outer_open and localed_quotes.outer_close then
+    tag_pattern = tag_pattern + P(_quoted(localed_quotes.outer_open))
+    tag_pattern = tag_pattern + P(_quoted(localed_quotes.outer_close))
+
+    openers_info[localed_quotes.outer_open] = {
+      closer = localed_quotes.outer_close,
+      quotes = true,
+      inner = false
+    }
+  end
+
+  if localed_quotes.inner_open and localed_quotes.inner_close then
+    tag_pattern = tag_pattern + P(_quoted(localed_quotes.inner_open))
+    tag_pattern = tag_pattern + P(_quoted(localed_quotes.inner_close))
+
+    openers_info[localed_quotes.inner_open] = {
+      closer = localed_quotes.inner_close,
+      quotes = true,
+      inner = true
+    }
+  end
+
+  context.engine.locale_tags_info_dict[locale] = {
+    tag_pattern = Ct((code_pattern + Ct(Cp() * tag_pattern * Cp()) + P(1)) ^ 0),
+    openers_info = openers_info,
+  }
+end
+
+
+local straight_quotes_flip = {
+  [" '"] = ' "',
+  [' "'] = " '",
+};
+
+--- at param str any
+--- at param context Context?
+--- at return string[]
+--- at return string[]
+local function split_tags_and_strings(str, context)
+  local tags = {}
+  local strings = {}
+
+  str = string.gsub(str, '(<span)%s+(style="font%-variant:)%s*(small%-caps);?"[^>]*(>)', '%1 %2%3;"%4');
+
+  str = string.gsub(str, '(<span)%s+(class="nocase")[^>]*(>)', "%1 %2%3");
+  str = string.gsub(str, '(<span)%s+(class="nodecor")[^>]*(>)', "%1 %2%3");
+
+  local tag_pattern = default_tag_pattern
+  local openers_info = default_openers_info
+  if context and context.lang then
+    tag_pattern = context.engine.locale_tags_info_dict[context.lang].tag_pattern
+  end
+
+  local tag_positions_list = lpeg.match(tag_pattern, str)
+  if not tag_positions_list then
+    error('Pattern not match')
+  end
+  local start = 1
+  local stop = 1
+  local new_stop = 1
+  for _, postion_tuple in ipairs(tag_positions_list) do
+    start, new_stop = table.unpack(postion_tuple)
+    table.insert(strings, string.sub(str, stop, start - 1))
+    table.insert(tags, string.sub(str, start, new_stop - 1))
+    stop = new_stop
+  end
+  table.insert(strings, string.sub(str, stop, -1))
+
+  for i, tag in ipairs(tags) do
+    if string.match(tag, "^.['\"]$") then
+      strings[i] = strings[i] .. string.sub(tag, 1, 1)
+      tags[i] = " " .. string.sub(tag, 2)
+    elseif (tag == "'" or tag == '"') and strings[i] == "" and (i == 1 or openers_info[tags[i - 1]]) then
+      -- See `bugreports_NoCaseEscape.txt`.
+      -- '"PIAAC-Longitudinal (PIAAC-L) 2015"'
+      -- <span class=\"nocase\">\"PIAAC-Longitudinal (PIAAC-Lx) 2015\"</span>
+      tags[i] = " " .. tag
     end
   end
-  return inlines
+
+  return tags, strings
 end
 
-local function get_inline_element_grammar()
-  local P = lpeg.P
-  local C = lpeg.C
-  local Ct = lpeg.Ct
-  local S = lpeg.S
-  local V = lpeg.V
-  local spaces = P(" ")^1
-  local grammar = P{
-    "content";
-    token = V"italic" + V"bold" + V"sup" + V"sub" + V"sc" + V"small_caps" + V"span_nocase" + V"nodecor" + V"math_tex" + V"math_ml" + V"code" + V"script" + C(1),
-    content = Ct((V"token")^0) / tokens2inlines,
-    italic = P"<i>" * Ct((V"token" - P"</i>")^0) * P"</i>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["font-style"] = "italic"})
-    end,
-    bold = P"<b>" * Ct((V"token" - P"</b>")^0) * P"</b>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["font-weight"] = "bold"})
-    end,
-    sup = P"<sup>" * Ct((V"token" - P"</sup>")^0) * P"</sup>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["vertical-align"] = "sup"})
-    end,
-    sub = P"<sub>" * Ct((V"token" - P"</sub>")^0) * P"</sub>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["vertical-align"] = "sub"})
-    end,
-    sc = P"<sc>" * Ct((V"token" - P"</sc>")^0) * P"</sc>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["font-variant"] = "small-caps"})
-    end,
-    quoted = (
-          C(P'"') * Ct((V"token" - P'"')^0) * C(P'"')
-          + C(P"'") * Ct((V"token" - P"'")^0) * C(P"'")
-          + C(P'“') * Ct((V"token" - P'”')^0) * C(P'”')
-          + C(P"‘") * Ct((V"token" - P"’")^0) * C(P"’")
-          + C(P"«") * Ct((V"token" - P"»")^0) * C(P"»")
-        ) / function (open, tokens, close)
-      return Quoted:new(tokens2inlines(tokens), LocalizedQuotes:new(open, close))
-    end,
-    small_caps = P"<span" * spaces * P'style="font-variant:' * spaces^0 * P'small-caps;">' * Ct((V"token" - P"</span>")^0) * P"</span>" / function (tokens)
-      return Formatted:new(tokens2inlines(tokens), {["font-variant"] = "small-caps"})
-    end,
-    -- nocase = P"<nocase>" * Ct((V"token" - P"</nocase>")^0) * P"</nocase>" / function (tokens)
-    --   return NoCase:new(tokens2inlines(tokens))
-    -- end,
-    span_nocase = P"<span" * spaces * P'class="nocase">' * Ct((V"token" - P"</span>")^0) * P"</span>" / function (tokens)
-      return NoCase:new(tokens2inlines(tokens))
-    end,
-    nodecor = P"<span" * spaces * P'class="nodecor">' * Ct((V"token" - P"</span>")^0) * P"</span>" / function (tokens)
-      return NoDecor:new(tokens2inlines(tokens))
-    end,
-    math_tex = P"<math-tex>" * C((1- P"</math-tex>")^0) * P"</math-tex>" / function (text)
-      return MathTeX:new(text)
-    end,
-    math_ml = P"<math>" * C((1- P"</math>")^0) * P"</math>" / function (text)
-      return MathML:new(text)
-    end,
-    code = P"<code>" * C((1 - P"</code>")^0) * P"</code>" / function (text)
-      return Code:new(text)
-    end,
-    script = P"<script>" * C((1- P"</script>")^0) * P"</script>" / function (text)
-      return Code:new(text)
-    end,
-  }
-  return grammar
+
+--- at param tag string
+--- at param str string
+--- at return string?
+local function _apostrophe_force(tag, str)
+  if tag == "'" or tag == "’" then
+    if str ~= "" and string.match(str, "^[^,.?:; ]") then
+      return util.unicode["right single quotation mark"]
+    end
+  elseif (tag == " '" or tag == "’") and str ~= "" and string.match(str, "^%s") then
+    return util.unicode["right single quotation mark"]
+  end
+  return nil
 end
 
-local inline_element_grammar = get_inline_element_grammar()
 
+--- at param quote string
+--- at param openers_info table
+local function set_outer_quote_form(quote, openers_info)
+  openers_info[quote].inner = false;
+  openers_info[straight_quotes_flip[quote]].inner = true;
+end
+
+
+--- at param tag string
+--- at param inlines InlineElement[]
+--- at param openers_info table
+--- at param context Context
+--- at return InlineElement
+local function make_inline_from_tag(tag, inlines, openers_info, context)
+  if tag == '<span class="nocase">' then
+    return NoCase:new(inlines)
+  elseif tag == '<span class="nodecor">' then
+    return NoDecor:new(inlines)
+  elseif tag == '<span style="font-variant:small-caps;">' then
+    return Formatted:new(inlines, {["font-variant"] = "small-caps"})
+  elseif tag == '<sc>' then
+    return Formatted:new(inlines, {["font-variant"] = "small-caps"})
+  elseif tag == '<i>' then
+    return Formatted:new(inlines, {["font-style"] = "italic"})
+  elseif tag == '<b>' then
+    return Formatted:new(inlines, {["font-weight"] = "bold"})
+  elseif tag == '<sup>' then
+    return Formatted:new(inlines, {["vertical-align"] = "sup"})
+  elseif tag == '<sub>' then
+    return Formatted:new(inlines, {["vertical-align"] = "sub"})
+  elseif openers_info[tag] and openers_info[tag].quotes then
+    local localized_quotes = context:get_localized_quotes()
+    return Quoted:new(inlines, localized_quotes, openers_info[tag].inner)
+  elseif tag == "<code>" or tag == "<script>" or tag == "<pre>" then
+    return Code:new(inlines[1].value)
+  elseif tag == "<math>" or tag == "<math-tex>" then
+    return MathTeX:new(inlines[1].value)
+  else
+    error(string.format("Invalid tag '%s'", tag))
+    return PlainText:new("")
+  end
+end
+
+
+--- processTags() in
+--- <https://github.com/Juris-M/citeproc-js/blob/master/src/util_flipflop.js#L474>
 --- at param str string
 --- at param context Context?
 --- at param is_external boolean?
 --- at return InlineElement[]
 function InlineElement:parse_html_tags(str, context, is_external)
-  local inlines = inline_element_grammar:match(str)
-  inlines = InlineElement:parse_quotes(inlines, context, is_external)
-  return inlines
-end
+  if str == "" then
+    return {}
+  end
 
--- TODO: Rewrite with lpeg
---- at param inlines InlineElement[]
---- at param context Context
---- at param is_external boolean?
---- at return table
-function InlineElement:parse_quotes(inlines, context, is_external)
-  local quote_fragments = InlineElement:get_quote_fragments(inlines)
-  -- util.debug(quote_fragments)
+  str = string.gsub(str, "(\u{00ab}) ", "\u{00ab}\u{202f}")
+  str = string.gsub(str, " (\u{00bb})", "\u{202f}%1")
+  str = string.gsub(str, " ([:;?!])", "\u{202f}%1")
+  -- str = " " .. string.gsub(str, util.unicode["right single quotation mark"], "'")
 
-  local quote_stack = {}
-  local text_stack = {{}}
-
-  local localized_quotes
-  if context then
-    localized_quotes = context:get_localized_quotes()
-  else
-    localized_quotes = LocalizedQuotes:new()
+  local openers_info = default_openers_info
+  if context and context.lang then
+    if not context.engine.locale_tags_info_dict[context.lang] then
+      make_locale_tag_info(context.lang, context)
+    end
+    openers_info = context.engine.locale_tags_info_dict[context.lang].openers_info
   end
 
-  for _, fragment in ipairs(quote_fragments) do
-    local top_text_list = text_stack[#text_stack]
+  local tags, strings = split_tags_and_strings(str, context)
 
-    if type(fragment) == "table" then
-      if fragment.inlines then
-        fragment.inlines = self:parse_quotes(fragment.inlines, context, is_external)
-      end
-      table.insert(top_text_list, fragment)
+  -- util.debug(tags)
+  -- util.debug(strings)
 
-    elseif type(fragment) == "string" then
-      local quote = fragment
-      local top_quote = quote_stack[#quote_stack]
+  -- if #tags == 0 then
+  --   return {PlainText:new(str)}
+  -- end
 
-      if quote == "'" then
-        if top_quote == "'" and #top_text_list > 0 then
-          table.remove(quote_stack)
-          local quoted = Quoted:new(top_text_list, localized_quotes)
-          table.remove(text_stack)
-          table.insert(text_stack[#text_stack], quoted)
-        else
-          table.insert(quote_stack, quote)
-          table.insert(text_stack, {})
-        end
+  --- at type {tag: string, closer: string, pos: number}[]
+  local opener_stack = {}
 
-      elseif quote == '"' then
-        if top_quote == '"' then
-          table.remove(quote_stack)
-          local quoted = Quoted:new(top_text_list, localized_quotes)
-          table.remove(text_stack)
-          table.insert(text_stack[#text_stack], quoted)
-        else
-          table.insert(quote_stack, quote)
-          table.insert(text_stack, {})
+  for i, tag in ipairs(tags) do
+    -- util.debug(i)
+    -- util.debug(strings[i])
+    -- util.debug(tag)
+    -- util.debug(opener_stack)
+    local apostrophe = _apostrophe_force(tag, strings[i + 1])
+    if apostrophe then
+      strings[i + 1] = apostrophe .. strings[i + 1]
+      tags[i] = "";
+    else
+      local opener_info = openers_info[tag]
+      local last_opener_info = opener_stack[#opener_stack]
+      if opener_info then
+        if opener_info.quotes and last_opener_info and tag == last_opener_info.tag then
+          local pos = last_opener_info.pos
+          strings[pos + 1] = string.gsub(tag, "^%s", "") .. strings[pos + 1]
+          tags[pos] = "";
+          table.remove(opener_stack)
         end
-
-      elseif quote == util.unicode["left single quotation mark"] or
-             quote == util.unicode["left double quotation mark"] or
-             quote == util.unicode["left-pointing double angle quotation mark"] then
-        table.insert(quote_stack, quote)
-        table.insert(text_stack, {})
-
-      elseif (quote == util.unicode["right single quotation mark"] and
-              top_quote == util.unicode["left single quotation mark"]) or
-             (quote == util.unicode["right double quotation mark"] and
-              top_quote == util.unicode["left double quotation mark"]) or
-             (quote == util.unicode["right-pointing double angle quotation mark"] and
-              top_quote == util.unicode["left-pointing double angle quotation mark"]) then
-          table.remove(quote_stack)
-          if is_external then
-            -- The text is from prefix or suffix of citationItem.
-            -- See flipflop_LeadingMarkupWithApostrophe.txt
-            localized_quotes.outer_open = top_quote
-            localized_quotes.outer_close = quote
-            localized_quotes.inner_open = top_quote
-            localized_quotes.inner_close = quote
+        table.insert(opener_stack, {
+          tag = tag,
+          closer = openers_info[tag].closer,
+          pos = i,
+        })
+      else
+        -- Closer tag
+        if last_opener_info then
+          if last_opener_info.closer == tag then
+            table.remove(opener_stack)
+          else
+            if tag == "'" or tag == "’" then
+              strings[i + 1] = "’" .. strings[i + 1]
+              tags[i] = ""
+            else
+              while #opener_stack > 0 and opener_stack[#opener_stack].closer ~= tag do
+                local pos = opener_stack[#opener_stack].pos
+                strings[pos + 1] = tags[pos] .. strings[pos + 1]
+                tags[pos] = ""
+                table.remove(opener_stack)
+              end
+              if #opener_stack > 0 then
+                table.remove(opener_stack)
+              else
+                strings[i + 1] = tags[i] .. strings[i + 1]
+                tags[i] = ""
+              end
+            end
           end
-          local quoted = Quoted:new(top_text_list, localized_quotes)
-          table.remove(text_stack)
-          table.insert(text_stack[#text_stack], quoted)
-
-      else
-        local last_inline = top_text_list[#top_text_list]
-        if last_inline and last_inline._type == "PlainText" then
-          last_inline.value = last_inline.value .. fragment
         else
-          table.insert(top_text_list, PlainText:new(fragment))
+          if tag == "'" or tag == "’" then
+            strings[i + 1] = "’" .. strings[i + 1]
+            tags[i] = ""
+          else
+            strings[i + 1] = tags[i] .. strings[i + 1]
+            tags[i] = ""
+          end
         end
       end
+    end
+  end
 
+  -- util.debug(opener_stack)
+
+  -- Process remainders in the stack
+  if #opener_stack > 0 then
+    for _, opener_info in ipairs(opener_stack) do
+      local pos = opener_info.pos
+      local orphan = string.gsub(tags[pos], "^%s", "")
+      orphan = string.gsub(orphan, "'", "’")
+      strings[pos + 1] = orphan .. strings[pos + 1]
+      tags[pos] = ""
     end
   end
 
-  local elements = text_stack[1]
-  if #text_stack > 1 then
-    -- assert(#text_stack == #quote_stack + 1)
-    for i, quote in ipairs(quote_stack) do
-      if quote == "'" then
-        quote = util.unicode["apostrophe"]
+  -- Remove empty tag
+  for i = #tags, 1, -1 do
+    local tag = tags[i]
+    if tag == "" then
+      strings[i + 1] = strings[i] .. strings[i + 1]
+      table.remove(strings, i)
+      table.remove(tags, i)
+    end
+  end
+
+  -- The first appearance of straight quote is treated as outer quote.
+  for _, tag in ipairs(tags) do
+    -- util.debug(i)
+    -- util.debug(tag)
+    if straight_quotes_flip[tag] then
+      set_outer_quote_form(tag, openers_info)
+      break
+    end
+  end
+
+  -- util.debug(tags)
+  -- util.debug(strings)
+
+  --- at type InlineElement[]
+  local output = {}
+
+  if strings[1] ~= "" then
+    table.insert(output, PlainText:new(strings[1]))
+  end
+
+  --- at type {tag: string, closer: string, pos: number, output_pos: number}[]
+  opener_stack = {}
+
+  for i, tag in ipairs(tags) do
+    if openers_info[tag] then
+      table.insert(opener_stack, {
+        tag = tag,
+        closer = openers_info[tag].closer,
+        pos = i,
+        output_pos = #output + 1
+      })
+    else
+      assert(#opener_stack > 0)
+      local opener_info = opener_stack[#opener_stack]
+      assert(opener_info.closer == tag)
+      local output_pos = opener_info.output_pos
+      local inlines = util.slice(output, output_pos)
+      for j = #output, output_pos, -1 do
+        table.remove(output, j)
       end
-      local last_inline = elements[#elements]
-      if last_inline and last_inline._type == "PlainText" then
-        last_inline.value = last_inline.value .. quote
-      else
-        table.insert(elements, PlainText:new(quote))
+      table.remove(opener_stack)
+      local inline = make_inline_from_tag(opener_info.tag, inlines, openers_info, context)
+      if inline._type == "Quoted" then
+        --- at cast inline Quoted
+        local quotes = context:get_localized_quotes()
+        inline.punctuation_in_quote = quotes.punctuation_in_quote
       end
+      table.insert(output, inline)
+    end
 
-      for _, inline in ipairs(text_stack[i + 1]) do
-        if inline._type == "PlainText" then
-          local last_inline = elements[#elements]
-          if last_inline and last_inline._type == "PlainText" then
-            last_inline.value = last_inline.value .. inline.value
-          else
-            table.insert(elements, inline)
-          end
-        else
-          table.insert(elements, inline)
-        end
-      end
+    if strings[i + 1] ~= "" then
+      table.insert(output, PlainText:new(strings[i + 1]))
     end
+
   end
 
-  return elements
+  -- util.debug(output)
+  return output
 end
 
 local function merge_fragments_at(fragments, i)
@@ -792,6 +1029,9 @@
   return inlines
 end
 
+--- at param ir IrNode
+--- at param override_delim boolean
+--- at return InlineElement[]
 function OutputFormat:flatten_seq_ir(ir, override_delim)
   -- if not ir.children then
   --   print(debug.traceback())
@@ -1174,10 +1414,14 @@
   return inlines
 end
 
+--- at param inlines InlineElement[]
+--- at param affixes table
+--- at param localized_quotes LocalizedQuotes
+--- at return InlineElement[]
 function OutputFormat:affixed_quoted(inlines, affixes, localized_quotes)
   inlines = util.clone(inlines)
   if localized_quotes then
-    inlines = self:quoted(inlines, localized_quotes)
+    inlines = {Quoted:new(inlines, localized_quotes)}
   end
   if affixes and affixes.prefix and affixes.prefix ~= "" then
     table.insert(inlines, 1, PlainText:new(affixes.prefix))
@@ -1188,10 +1432,6 @@
   return inlines
 end
 
-function OutputFormat:quoted(inlines, localized_quotes)
-  return {Quoted:new(inlines, localized_quotes)}
-end
-
 function OutputFormat:with_display(nodes, display)
   if display then
     return {Div:new(nodes, display)}
@@ -1242,7 +1482,8 @@
     ["font-weight"] = "normal",
     ["text-decoration"] = "none",
     ["vertical-alignment"] = "baseline",
-    in_inner_quotes = false,
+    in_quotes = false,
+    inner_quotes = false,
   }
   self:flip_flop(inlines, flip_flop_state)
 end
@@ -1275,9 +1516,13 @@
       self:flip_flop(inline.inlines, new_state)
 
     elseif inline._type == "Quoted" then
-      inline.is_inner = state.in_inner_quotes
+      --- at cast inline Quoted
+      if state.in_quotes then
+        inline.is_inner = not state.inner_quotes
+      end
       local new_state = util.clone(state)
-      new_state.in_inner_quotes = not new_state.in_inner_quotes
+      new_state.in_quotes = true
+      new_state.inner_quotes = inline.is_inner
       self:flip_flop(inline.inlines, new_state)
 
     elseif inline._type == "NoDecor" then
@@ -1337,9 +1582,13 @@
       self:flip_flop_micro_inlines(inline.inlines, new_state)
 
     elseif inline._type == "Quoted" then
-      inline.is_inner = state.in_inner_quotes
+      --- at cast inline Quoted
+      if state.in_quotes then
+        inline.is_inner = not state.inner_quotes
+      end
       local new_state = util.clone(state)
-      new_state.in_inner_quotes = not new_state.in_inner_quotes
+      new_state.in_quotes = true
+      new_state.inner_quotes = inline.is_inner
       self:flip_flop_micro_inlines(inline.inlines, new_state)
 
     elseif inline._type == "NoDecor" then
@@ -1410,6 +1659,7 @@
 
 --- "'Foo,' bar" => ,
 --- at param inline InlineElement
+--- at return boolean
 local function find_right_quoted(inline)
   if inline._type == "Quoted" and #inline.inlines > 0 then
     --- at cast inline Quoted
@@ -2024,6 +2274,8 @@
 
 
 -- Omit formatting and quotes
+
+--- at class DisamStringFormat: OutputFormat
 local DisamStringFormat = OutputFormat:new()
 
 function DisamStringFormat:output(inlines, context)

Modified: trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/scripts/citation-style-language/citeproc.lua	2024-08-15 20:01:24 UTC (rev 72038)
@@ -16,7 +16,7 @@
   util = require("citeproc.util")
 end
 
-citeproc.__VERSION__ = "0.6.0"
+citeproc.__VERSION__ = "0.6.1"
 
 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	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/citation-style-language.sty	2024-08-15 20:01:24 UTC (rev 72038)
@@ -9,7 +9,7 @@
 \RequirePackage{expl3}
 \RequirePackage{xparse}
 
-\ProvidesExplPackage {citation-style-language} {2024-07-31} {0.6.0}
+\ProvidesExplPackage {citation-style-language} {2024-08-15} {0.6.1}
   {Citation Style Language for LaTeX}
 
 \RequirePackage { l3keys2e }

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-en-US.xml	2024-08-15 20:01:24 UTC (rev 72038)
@@ -17,7 +17,7 @@
       <name>Brenton M. Wiernik</name>
     </translator>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
-    <updated>2024-03-12T12:31:23-04:00</updated>
+    <updated>2024-08-12T16:22:23-04:00</updated>
   </info>
   <style-options punctuation-in-quote="true"/>
   <date form="text">
@@ -685,7 +685,6 @@
     </term>
 
     <!-- VERB ROLE FORMS -->
-    <term name="author" form="verb">by</term>
     <term name="chair" form="verb">chaired by</term>
     <term name="collection-editor" form="verb">edited by</term>
     <term name="compiler" form="verb">compiled by</term>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-zh-TW.xml
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-zh-TW.xml	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/locales/csl-locales-zh-TW.xml	2024-08-15 20:01:24 UTC (rev 72038)
@@ -5,7 +5,7 @@
       <name>sati-bodhi</name>
     </translator>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
-    <updated>2024-03-09T15:20:54-05:00</updated>
+    <updated>2024-08-12T16:22:23-04:00</updated>
   </info>
   <style-options punctuation-in-quote="false"/>
   <date form="text">
@@ -433,7 +433,6 @@
       <single>series creator</single>
       <multiple>series creators</multiple>
     </term>
-    <term name="author">作者</term>     
     <term name="director">導演</term>     
     <term name="editor">編輯</term> 
     <term name="editorial-director">主編</term>     
@@ -485,7 +484,6 @@
       <single>cre.</single>
       <multiple>cres.</multiple>
     </term>
-    <term name="author" form="short">作者</term>     
     <term name="director" form="short">導演</term>     
     <term name="editor" form="short">編輯</term> 
     <term name="editorial-director" form="short">主編</term>     
@@ -511,7 +509,6 @@
     <term name="producer" form="verb">produced by</term>
     <term name="script-writer" form="verb">written by</term>
     <term name="series-creator" form="verb">created by</term>
-    <term name="author" form="verb">著</term>
     <term name="container-author" form="verb">著</term>
     <term name="director" form="verb">指導</term>
     <term name="editor" form="verb">編輯</term>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/american-medical-association.csl	2024-08-15 20:01:24 UTC (rev 72038)
@@ -27,7 +27,7 @@
     <category citation-format="numeric"/>
     <category field="medicine"/>
     <summary>The American Medical Association style as used in JAMA. Version 11 as per November-2019.</summary>
-    <updated>2022-09-28T07:33:04-04:00</updated>
+    <updated>2024-08-04T17:48:30-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -66,7 +66,7 @@
             <group delimiter=". ">
               <choose>
                 <if type="webpage post post-weblog" match="any">
-                  <date variable="issued" prefix="Published " form="text"/>
+                  <date variable="issued" form="text"/>
                 </if>
               </choose>
               <group>
@@ -173,7 +173,7 @@
             <if variable="URL">
               <group delimiter=". " suffix=".">
                 <text variable="URL"/>
-                <group prefix="Published ">
+                <group>
                   <date variable="issued">
                     <date-part name="month" suffix=" "/>
                     <date-part name="day" suffix=", "/>

Modified: trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl
===================================================================
--- trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl	2024-08-15 20:00:44 UTC (rev 72037)
+++ trunk/Master/texmf-dist/tex/latex/citation-style-language/styles/apa.csl	2024-08-15 20:01:24 UTC (rev 72038)
@@ -14,7 +14,7 @@
     <category citation-format="author-date"/>
     <category field="psychology"/>
     <category field="generic-base"/>
-    <updated>2024-07-19T10:52:31-04:00</updated>
+    <updated>2024-08-06T11:26:59-04:00</updated>
     <rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
   </info>
   <locale xml:lang="en">
@@ -139,12 +139,23 @@
           <names variable="author"/>
           <!-- Note: `narrator` only cited in secondary-contributors -->
           <names variable="illustrator"/>
-          <names variable="script-writer director">
-            <!-- Note: Actors/performers and producers [not executive] not cited in APA style. -->
+          <!-- TODO: Replace `delimiter` with `collapse` to combine names variables when that becomes available. -->
+          <choose>
+            <if type="broadcast">
+              <names variable="script-writer director" delimiter=", & ">
+                <!-- Note: Actors/performers and producers [not executive] not cited in APA style. -->
+                <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
+                <label form="long" prefix=" (" suffix=")" text-case="title"/>
+              </names>
+            </if>
+          </choose>
+          <names variable="director">
+            <!-- For non-broadcast items, APA only cites directors and not writers. -->
             <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
             <label form="long" prefix=" (" suffix=")" text-case="title"/>
           </names>
-          <names variable="guest host">
+          <!-- TODO: Replace `delimiter` with `collapse` to combine names variables when that becomes available. -->
+          <names variable="guest host" delimiter=", & ">
             <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
             <label form="long" prefix=" (" suffix=")" text-case="title"/>
           </names>
@@ -174,17 +185,20 @@
               </choose>
             </if>
           </choose>
-          <names variable="series-creator executive-producer">
+          <names variable="executive-producer">
             <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
             <label form="long" prefix=" (" suffix=")" text-case="title"/>
           </names>
-          <names variable="editor-translator" delimiter=", ">
+          <names variable="series-creator">
             <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
+            <label form="long" prefix=" (" suffix=")" text-case="title"/>
+          </names>
+          <names variable="editor-translator">
+            <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
             <label form="short" prefix=" (" suffix=")" text-case="title"/>
           </names>
-          <!-- TODO: Split editor and translator into separate lines once processors begin to automatically create
-               the editor-translator variable. -->
-          <names variable="editor translator" delimiter=", ">
+          <!-- Note: Translator is not cited as a primary creator (only as Ed. & Trans.). -->
+          <names variable="editor">
             <name name-as-sort-order="all" and="symbol" sort-separator=", " initialize-with=". " delimiter=", " delimiter-precedes-last="always"/>
             <label form="short" prefix=" (" suffix=")" text-case="title"/>
           </names>
@@ -279,8 +293,15 @@
           <substitute>
             <names variable="author"/>
             <names variable="illustrator"/>
-            <names variable="script-writer director"/>
-            <names variable="guest host"/>
+            <!-- TODO: Replace `delimiter` with `collapse` to combine names variables when that becomes available. -->
+            <choose>
+              <if type="broadcast">
+                <names variable="script-writer director" delimiter=", & "/>
+              </if>
+            </choose>
+            <names variable="director"/>
+            <!-- TODO: Replace `delimiter` with `collapse` to combine names variables when that becomes available. -->
+            <names variable="guest host" delimiter=", & "/>
             <names variable="producer"/>
             <choose>
               <if variable="container-title">
@@ -291,7 +312,8 @@
                 </choose>
               </if>
             </choose>
-            <names variable="series-creator executive-producer"/>
+            <names variable="executive-producer"/>
+            <names variable="series-creator"/>
             <names variable="editor"/>
             <names variable="editorial-director"/>
             <names variable="compiler"/>
@@ -1172,33 +1194,44 @@
           </names>
         </if>
       </choose>
-      <!-- TODO: When editor-translator becomes available in processors, add a test: 
-                 variable="editor-translator" match="none"; then print translator -->
       <choose>
         <if type="post webpage" match="none">
           <!-- Webpages treat container-title like publisher -->
-          <choose>
-            <if variable="container-title" match="none">
-              <group delimiter="; ">
-                <names variable="container-author">
-                  <label form="verb-short" suffix=" " text-case="title"/>
-                  <name and="symbol" initialize-with=". " delimiter=", "/>
-                </names>
-                <names variable="editor translator" delimiter="; ">
-                  <name and="symbol" initialize-with=". " delimiter=", "/>
-                  <label form="short" prefix=", " text-case="title"/>
-                </names>
-                <names variable="illustrator narrator" delimiter="; ">
-                  <name and="symbol" initialize-with=". " delimiter=", "/>
-                  <label form="short" prefix=", " text-case="title"/>
-                </names>
-                <names variable="compiler chair organizer curator series-creator executive-producer" delimiter="; ">
-                  <name and="symbol" initialize-with=". " delimiter=", "/>
-                  <label form="long" prefix=", " text-case="title"/>
-                </names>
-              </group>
-            </if>
-          </choose>
+          <group delimiter="; ">
+            <names variable="illustrator narrator" delimiter="; ">
+              <name and="symbol" initialize-with=". " delimiter=", "/>
+              <label form="short" prefix=", " text-case="title"/>
+            </names>
+            <choose>
+              <if variable="container-title" match="none">
+                <group delimiter="; ">
+                  <names variable="container-author">
+                    <label form="verb-short" suffix=" " text-case="title"/>
+                    <name and="symbol" initialize-with=". " delimiter=", "/>
+                  </names>
+                  <names variable="editor translator" delimiter="; ">
+                    <name and="symbol" initialize-with=". " delimiter=", "/>
+                    <label form="short" prefix=", " text-case="title"/>
+                  </names>
+                  <names variable="compiler chair organizer curator series-creator executive-producer" delimiter="; ">
+                    <name and="symbol" initialize-with=". " delimiter=", "/>
+                    <label form="long" prefix=", " text-case="title"/>
+                  </names>
+                </group>
+              </if>
+              <else>
+                <choose>
+                  <!-- TODO: Check logic once processors start to automatically populate editor-translator. -->
+                  <if variable="editor-translator" match="none">
+                    <names variable="translator" delimiter="; ">
+                      <name and="symbol" initialize-with=". " delimiter=", "/>
+                      <label form="short" prefix=", " text-case="title"/>
+                    </names>
+                  </if>
+                </choose>
+              </else>
+            </choose>
+          </group>
         </if>
         <else>
           <group delimiter="; ">
@@ -1280,17 +1313,10 @@
     </choose>
   </macro>
   <macro name="version">
-    <choose>
-      <if is-numeric="version">
-        <group delimiter=" ">
-          <label variable="version" text-case="capitalize-first"/>
-          <text variable="version"/>
-        </group>
-      </if>
-      <else>
-        <text variable="version"/>
-      </else>
-    </choose>
+    <group delimiter=" ">
+      <label variable="version" text-case="capitalize-first"/>
+      <text variable="version"/>
+    </group>
   </macro>
   <macro name="edition">
     <choose>
@@ -1539,17 +1565,19 @@
             </else>
           </choose>
           <group delimiter=", ">
-            <names variable="series-creator executive-producer" delimiter=", & ">
+            <names variable="executive-producer">
               <name and="symbol" initialize-with=". " delimiter=", "/>
               <label form="long" text-case="title" prefix=" (" suffix=")"/>
               <substitute>
+                <names variable="series-creator"/>
                 <names variable="editor-translator">
                   <name and="symbol" initialize-with=". " delimiter=", "/>
                   <label form="short" text-case="title" prefix=" (" suffix=")"/>
                 </names>
-                <!-- TODO: Split editor and translator lines once processors start to automatically
-                           create editor-translator variable. -->
-                <names variable="editor translator">
+                <!-- TODO: Translator omitted here on the assumption that editor-translators are uncommon 
+                           for chapter citations. If needed, direct entry or automatic population of
+                           `editor-translator` can produce combined labels. -->
+                <names variable="editor">
                   <name and="symbol" initialize-with=". " delimiter=", "/>
                   <label form="short" text-case="title" prefix=" (" suffix=")"/>
                 </names>



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