summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/citation-style-language/citeproc-node-date.lua
blob: e10c1a9ac2538a668b36a3821a8da25fc369c6d5 (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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
--
-- Copyright (c) 2021-2022 Zeping Lee
-- Released under the MIT license.
-- Repository: https://github.com/zepinglee/citeproc-lua
--

local date_module = {}

local element = require("citeproc-element")
local util = require("citeproc-util")


local Date = element.Element:new()

function Date:render (item, context)
  self:debug_info(context)
  context = self:process_context(context)

  if context.sorting then
    return self:render_sort_key(item, context)
  end

  local variable_name = context.options["variable"]

  local is_locale_date
  if variable_name then
    context.variable = variable_name
    is_locale_date = false
  else
    variable_name = context.variable
    is_locale_date = true
  end

  local date = self:get_variable(item, variable_name, context)
  if not date then
    return nil
  end

  local res = nil
  local form = context.options["form"]
  if form and not is_locale_date then
    for _, date_part in ipairs(self:query_selector("date-part")) do
      local name = date_part:get_attribute("name")
      if not context.date_part_attributes then
        context.date_part_attributes = {}
      end
      if not context.date_part_attributes[name] then
        context.date_part_attributes[name] = {}
      end

      for attr, value in pairs(date_part._attr) do
        if attr ~= name then
          context.date_part_attributes[name][attr] = value
        end
      end
    end
    res = self:get_locale_date(context, form):render(item, context)
  else
    if not date["date-parts"] or #date["date-parts"] == 0 then
      local literal = date["literal"]
      if literal then
        res = literal
      else
        local raw = date["raw"]
        if raw then
          res = raw
        end
      end

    else
      if #date["date-parts"] == 1 then
        res = self:_render_single_date(date, context)
      elseif #date["date-parts"] == 2 then
        res = self:_render_date_range(date, context)
      end
    end
  end

  table.insert(context.variable_attempt, res ~= nil)

  res = self:format(res, context)
  res = self:wrap(res, context)
  return res
end

function Date:get_locale_date(context, form)
  local date = nil
  local style = context.style
  local query = string.format("date[form=\"%s\"]", form)
  for _, locale in ipairs(style:get_locales()) do
    date = locale:query_selector(query)[1]
    if date then
      break
    end
  end
  if not date then
    error(string.format("Failed to find '%s'", query))
  end
  return date
end

function Date:render_sort_key (item, context)
  local variable_name = context.options["variable"]
  local date = self:get_variable(item, variable_name, context)
  if not date or not date["date-parts"] then
    return nil
  end
  local show_parts = {
    year = false,
    month = false,
    day = false,
  }
  if self:get_attribute("form") then
    local date_parts = self:get_attribute("date-parts") or "year-month-day"
    for _, dp_name in ipairs(util.split(date_parts, "%-")) do
      show_parts[dp_name] = true
    end
  else
    for _, child in ipairs(self:query_selector("date-part")) do
      show_parts[child:get_attribute("name")] = true
    end
  end
  local res = ""
  for _, date_parts in ipairs(date["date-parts"]) do
    for i, dp_name in ipairs({"year", "month", "day"}) do
      local value = date_parts[i]
      if not value or not show_parts[dp_name] then
        value = 0
      end
      if i == 1 then
        res = res .. string.format("%05d", value + 10000)
      else
        res = res .. string.format("%02d", value)
      end
    end
  end
  return res
end

function Date:_render_single_date (date, context)
  local show_parts = self:_get_show_parts(context)

  local output = {}
  for _, child in ipairs(self:query_selector("date-part")) do
    if show_parts[child:get_attribute("name")] then
      table.insert(output, child:render(date, context))
    end
  end
  return self:concat(output, context)
end

function Date:_render_date_range (date, context)
  local show_parts = self:_get_show_parts(context)
  local part_index = {}

  local largest_diff_part = nil
  for i, name in ipairs({"year", "month", "day"}) do
    part_index[name] = i
    local part_value1 = date["date-parts"][1][i]
    if show_parts[name] and part_value1 then
      if not largest_diff_part then
        largest_diff_part = name
      end
    end
  end

  local date_parts = {}
  for _, date_part in ipairs(self:query_selector("date-part")) do
    if show_parts[date_part:get_attribute("name")] then
      table.insert(date_parts, date_part)
    end
  end

  local diff_begin = 0
  local diff_end = #date_parts
  local range_delimiter = nil

  for i, date_part in ipairs(date_parts) do
    local name = date_part:get_attribute("name")
    if name == largest_diff_part then
      range_delimiter = date_part:get_attribute("range-delimiter")
      if not range_delimiter then
        range_delimiter = util.unicode["en dash"]
      end
    end

    local index = part_index[name]
    local part_value1 = date["date-parts"][1][index]
    local part_value2 = date["date-parts"][2][index]
    if part_value1 and part_value1 ~= part_value2 then
      if diff_begin == 0 then
        diff_begin = i
      end
      diff_end = i
    end
  end

  local same_prefix = {}
  local range_begin = {}
  local range_end = {}
  local same_suffix = {}

  local no_suffix_context = self:process_context(context)
  no_suffix_context.options["suffix"] = nil

  for i, date_part in ipairs(date_parts) do
    local res = nil
    if i == diff_end then
      res = date_part:render(date, no_suffix_context, true)
    else
      res = date_part:render(date, context)
    end
    if i < diff_begin then
      table.insert(same_prefix, res)
    elseif i <= diff_end then
      table.insert(range_begin, res)
      table.insert(range_end, date_part:render(date, context, false, true))
    else
      table.insert(same_suffix, res)
    end
  end

  local prefix_output = self:concat(same_prefix, context) or ""
  local range_begin_output = self:concat(range_begin, context) or ""
  local range_end_output = self:concat(range_end, context) or ""
  local suffix_output = self:concat(same_suffix, context)
  local range_output = range_begin_output .. range_delimiter .. range_end_output

  local res = self:concat({prefix_output, range_output, suffix_output}, context)

  return res
end

function Date:_get_show_parts (context)
  local show_parts = {}
  local date_parts = context.options["date-parts"] or "year-month-day"
  for _, date_part in ipairs(util.split(date_parts, "%-")) do
    show_parts[date_part] = true
  end
  return show_parts
end


local DatePart = element.Element:new()

DatePart.render = function (self, date, context, last_range_begin, range_end)
  self:debug_info(context)
  context = self:process_context(context)
  local name = context.options["name"]
  local range_delimiter = context.options["range-delimiter"] or false

  -- The attributes set on cs:date-part elements of a cs:date with form
  -- attribute override those specified for the localized date formats
  if context.date_part_attributes then
    local context_attributes = context.date_part_attributes[name]
    if context_attributes then
      for attr, value in pairs(context_attributes) do
        context.options[attr] = value
      end
    end
  end

  if last_range_begin then
    context.options["suffix"] = ""
  end

  local date_parts_index = 1
  if range_end then
    date_parts_index = 2
  end

  local res = nil
  if name == "day" then
    local day = date["date-parts"][date_parts_index][3]
    if not day then
      return nil
    end
    day = tonumber(day)
    -- range open
    if day == 0 then
      return nil
    end
    local form = context.options["form"] or "numeric"

    if form == "ordinal" then
      local option = self:get_locale_option("limit-day-ordinals-to-day-1")
      if option and option ~= "false" and day > 1 then
        form = "numeric"
      end
    end
    if form == "numeric" then
      res = tostring(day)
    elseif form == "numeric-leading-zeros" then
      -- TODO: day == nil?
      if not day then
        return nil
      end
      res = string.format("%02d", day)
    elseif form == "ordinal" then
      res = util.to_ordinal(day)
    end

  elseif name == "month" then
    local form = context.options["form"] or "long"

    local month = date["date-parts"][date_parts_index][2]
    if month then
      month = tonumber(month)
      -- range open
      if month == 0 then
        return nil
      end
    end

    if form == "long" or form == "short" then
      local term_name = nil
      if month then
        if month >= 1 and month <= 12 then
          term_name = string.format("month-%02d", month)
        elseif month >= 13 and month <= 24 then
          local season = month % 4
          if season == 0 then
            season = 4
          end
          term_name = string.format("season-%02d", season)
        else
          util.warning("Invalid month value")
          return nil
        end
      else
        local season = date["season"]
        if season then
          season = tonumber(season)
          term_name = string.format("season-%02d", season)
        else
          return nil
        end
      end
      res = self:get_term(term_name, form):render(context)
    elseif form == "numeric" then
      res = tostring(month)
    elseif form == "numeric-leading-zeros" then
      -- TODO: month == nil?
      if not month then
        return nil
      end
      res = string.format("%02d", month)
    end
    res = self:strip_periods(res, context)

  elseif name == "year" then
    local year = date["date-parts"][date_parts_index][1]
    if year then
      year = tonumber(year)
      -- range open
      if year == 0 then
        return nil
      end
      local form = context.options["form"] or "long"
      if form == "long" then
        year = tonumber(year)
        if year < 0 then
          res = tostring(-year) .. self:get_term("bc"):render(context)
        elseif year < 1000 then
          res = tostring(year) .. self:get_term("ad"):render(context)
        else
          res = tostring(year)
        end
      elseif form == "short" then
        res = string.sub(tostring(year), -2)
      end
    end
  end
  res = self:case(res, context)
  res = self:format(res, context)
  res = self:wrap(res, context)
  res = self:display(res, context)
  return res
end


date_module.Date = Date
date_module.DatePart = DatePart

return date_module