summaryrefslogtreecommitdiff
path: root/biblio/citation-style-language/citeproc-context.lua
blob: 08be158bcfee942c74c3d8b0e8736f0df325d248 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
--
-- Copyright (c) 2021-2022 Zeping Lee
-- Released under the MIT license.
-- Repository: https://github.com/zepinglee/citeproc-lua
--

local context = {}

local LocalizedQuotes = require("citeproc-output").LocalizedQuotes

local util = require("citeproc-util")


local Context = {
  reference = nil,
  format = nil,
  cite_id = nil,
  style = nil,
  locale = nil,
  name_citation = nil,
  names_delimiter = nil,

  position = nil,

  disamb_pass = nil,

  cite = nil,
  bib_number = nil,

  in_bibliography = false,
  sort_key = nil,

  year_suffix = nil,
}

function Context:new()
  local o = {
    in_bibliography = false
  }
  setmetatable(o, self)
  self.__index = self
  return o
end

function Context:get_variable(name, form)
  local variable_type = util.variable_types[name]
  if variable_type == "number" then
    return self:get_number(name)
  -- elseif variable_type == "date" then
  --   return self:get_date(name)
  elseif variable_type == "name" then
    return self:get_name(name)
  else
    return self:get_ordinary(name, form)
  end
end

function Context:get_number(name)
  if name == "locator" then
    return self.cite.locator
  elseif name == "citation-number" then
    -- return self.bib_number
    return self.reference["citation-number"]
  elseif name == "first-reference-note-number" then
    return self.cite["first-reference-note-number"]
  elseif name == "page-first" then
    return self.page_first(self.reference.page)
  else
    return self.reference[name]
  end
end

function Context:get_ordinary(name, form)
  local res = nil
  local variable_name = name
  if form and form ~= "long" then
    variable_name = variable_name .. "-" .. form
  end

  if variable_name == "locator" or variable_name == "label" then
    res = self.cite[variable_name]
  else
    res = self.reference[variable_name]
  end
  if res then
    return res
  end

  if variable_name == "container-title-short" then
    res = self.reference["journalAbbreviation"]
    if res then
      return res
    end
  end

  if form then
    res = self.reference[name]
    if res then
      return res
    end
  end

  -- if name == "title-short" or name == "container-title-short" then
  --   variable_name = string.gsub(name, "%-short$", "")
  --   res = self.reference[variable_name]
  -- end

  return res
end

-- TODO: optimize: process only once
-- TODO: organize the name parsing code
function Context:get_name(variable_name)
  local names = self.reference[variable_name]
  if names then
    for _, name in ipairs(names) do
      if name.family == "" then
        name.family = nil
      end
      if name.given == "" then
        name.given = nil
      end
      if name.given and not name.family then
        name.family = name.given
        name.given = nil
      end
      self:parse_name_suffix(name)
      self:split_ndp_family(name)
      -- self:split_given_ndp(name)
      self:split_given_dp(name)
    end
  end
  return names
end

function Context:parse_name_suffix(name)
  if not name.suffix and name.family and string.match(name.family, ",") then
    local words = util.split(name.family, ",%s*")
    name.suffix = words[#words]
    name.family = table.concat(util.slice(words, 1, -2), ", ")
  end
  if not name.suffix and name.given and string.match(name.given, ",") then
    -- Split name suffix: magic_NameSuffixNoComma.txt
    -- "John, III" => given: "John", suffix: "III"
    local words = util.split(name.given, ",%s*")
    name.suffix = words[#words]
    name.given = table.concat(util.slice(words, 1, -2), ", ")
  end
end

function Context:split_ndp_family(name)
  if name["non-dropping-particle"] or not name.family then
    return
  end
  if util.startswith(name.family, '"') and util.endswith(name.family, '"') then
    -- Stop parsing family name if surrounded by quotation marks
    -- bugreports_parseName.txt
    name.family = string.gsub(name.family, '"', "")
    return
  end
  local ndp_parts = {}
  local family_parts = {}
  local parts = util.split(name.family)
  for i, part in ipairs(parts) do
    local ndp, family
    -- d'Aubignac
    ndp, family = string.match(part, "^(%l')(.+)$")
    if ndp and family then
      table.insert(ndp_parts, ndp)
      parts[i] = family
    else
      ndp, family = string.match(part, "^(%l’)(.+)$")
      if ndp and family then
        table.insert(ndp_parts, ndp)
        parts[i] = family
      else
        -- al-Aswānī
        ndp, family = string.match(part, "^(%l+%-)(.+)$")
        if ndp and family then
          table.insert(ndp_parts, ndp)
          parts[i] = family
        elseif i < #parts and util.is_lower(part) then
          table.insert(ndp_parts, part)
        end
      end
    end
    if ndp or i == #parts then
      for j = i, #parts do
        table.insert(family_parts, parts[j])
      end
      break
    end
    if not util.is_lower(part) then
      for j = i, #parts do
        table.insert(family_parts, parts[j])
      end
      break
    end
  end
  if #ndp_parts > 0 then
    name["non-dropping-particle"] = table.concat(ndp_parts, " ")
    name.family = table.concat(family_parts, " ")
  end
end

function Context:split_given_dp(name)
  if name["dropping-particle"] or not name.given then
    return
  end
  local dp_parts = {}
  local given_parts = {}
  local parts = util.split(name.given)
  for i = #parts, 1, -1 do
    local part = parts[i]
    if i == 1 or not util.is_lower(part) then
      for j = 1, i do
        table.insert(given_parts, parts[j])
      end
      break
    end
  -- name_ParsedDroppingParticleWithApostrophe.txt
  -- given: "François Hédelin d'" =>
  -- given: "François Hédelin", dropping-particle: "d'"
    if string.match(part, "^%l+'?$") or string.match(part, "^%l+’$") then
      table.insert(dp_parts, 1, part)
    end
  end
  if #dp_parts > 0 then
    name["dropping-particle"] = table.concat(dp_parts, " ")
    name.given = table.concat(given_parts, " ")
  end
end

-- function Context:split_given_ndp(name)
--   if name["non-dropping-particle"] or not name.given then
--     return
--   end

--   if not (string.match(name.given, "%l'$") or string.match(name.given, "%l’$")) then
--     return
--   end

--   local words = util.split(name.given)
--   if #words < 2 then
--     return
--   end
--   local last_word = words[#words]
--   if util.endswith(last_word, "'") or util.endswith(last_word, util.unicode["apostrophe"]) then
--     name["non-dropping-particle"] = last_word
--     name.given = table.concat(util.slice(words, 1, -2), " ")
--   end
--   util.debug(name)
-- end

function Context:get_localized_date(form)
  return self.locale.dates[form]
end

function Context:get_macro(name)
  local res = self.style.macros[name]
  if not res then
    util.error(string.format('Undefined macro "%s"', name))
  end
  return res
end

function Context:get_simple_term(name, form, plural)
  assert(self.locale)
  return self.locale:get_simple_term(name, form, plural)
end

function Context:get_localized_quotes()
  return LocalizedQuotes:new(
    self:get_simple_term("open-quote"),
    self:get_simple_term("close-quote"),
    self:get_simple_term("open-inner-quote"),
    self:get_simple_term("close-inner-quote"),
    self.locale.style_options.punctuation_in_quote
  )
end

function Context.page_first(page)
  local page_first = util.split(page, "%s*[&,-]%s*")[1]
  return util.split(page_first, util.unicode["en dash"])[1]
end

-- https://docs.citationstyles.org/en/stable/specification.html#non-english-items
function Context:is_english()
  local language = self:get_variable("language")
  if util.startswith(self.engine.lang, "en") then
    if not language or util.startswith(language, "en") then
      return true
    else
      return false
    end
  else
    if language and util.startswith(language, "en") then
      return true
    else
      return false
    end
  end
end


local IrState = {}

function IrState:new(style, cite_id, cite, reference)
  local o = {
    macro_stack = {},
    suppressed = {},
  }
  setmetatable(o, self)
  self.__index = self
  return o
end

function IrState:push_macro(macro_name)
  for _, name in ipairs(macro_name) do
    if name == macro_name then
      util.error(string.format('Recursive macro "%s".', macro_name))
    end
    table.insert(self.macro_stack, macro_name)
  end
end

function IrState:pop_macro(macro_name)
  table.remove(self.macro_stack)
end


context.Context = Context
context.IrState = IrState

return context