summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/citation-style-language/citeproc-node-layout.lua
blob: 944ed883a38e9573917ac937d799ad3955f6551a (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
--
-- Copyright (c) 2021-2022 Zeping Lee
-- Released under the MIT license.
-- Repository: https://github.com/zepinglee/citeproc-lua
--

local layout = {}

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


local Layout = element.Element:new()

function Layout:render (items, context)
  self:debug_info(context)

  context.items = items

  -- When used within cs:citation, the delimiter attribute may be used to specify a delimiter for cites within a citation.
  -- Thus the processing of context is put after render_children().
  if context.mode == "citation" then
    if context.options["collapse"] == "citation-number" then
      context.build.item_citation_numbers = {}
      context.build.item_citation_number_text = {}
    end
  elseif context.mode == "bibliography" then
    context.build.longest_label = ""
    context.build.preceding_first_rendered_names = nil
    context = self:process_context(context)
  end

  local output = {}
  local previous_cite = nil
  for _, item in ipairs(items) do

    context.item = item
    context.variable_attempt = {}
    context.suppressed_variables = {}
    context.suppress_subsequent_variables = false
    if context.mode == "bibliography" then
      context.build.first_rendered_names = {}
    end

    if not item.position then
      item.position = self:_get_position(item, previous_cite, context)
    end

    local first = nil
    local second = {}
    local element_index = 0
    for _, child in ipairs(self:get_children()) do
      if child:is_element() then
        element_index = element_index + 1
        local text = child:render(item, context)
        if element_index == 1 then
          first = text
        else
          table.insert(second, text)
        end
      end
    end
    second = self:concat(second, context)

    if context.mode == "bibliography" then
      if first and context.options["prefix"] then
        first = richtext.new(context.options["prefix"]) .. first
      end
      if second and context.options["suffix"] then
        second = second .. richtext.new(context.options["suffix"])
      end
    end

    local res = nil
    if context.options["second-field-align"] == "flush" then
      if first then
        first:add_format("display", "left-margin")
        res = first
      end
      if second then
        second:add_format("display", "right-inline")
        if res then
          res = richtext.concat(res, second)
        else
          res = second
        end
      end
    else
      res = self:concat({first, second}, context)
    end

    if context.mode == "citation" then
      if res and item["prefix"] then
        res = richtext.new(item["prefix"]) .. res
      end
      if res and item["suffix"] then
        res = res .. richtext.new(item["suffix"])
      end
    elseif context.mode == "bibliography" then
      if not res then
        res = richtext.new("[CSL STYLE ERROR: reference with no printed form.]")
      end
      res = self:wrap(res, context)
      -- util.debug(text)
      res = res:render(context.engine.formatter, context)
      res = context.engine.formatter["@bibliography/entry"](res, context)
    end
    table.insert(output, res)
    previous_cite = item
  end

  if context.mode == "citation" then
    if next(output) == nil then
      return "[CSL STYLE ERROR: reference with no printed form.]"
    end

    context = self:process_context(context)
    local res
    if context.options["collapse"] then
      res = self:_collapse_citations(output, context)
    else
      res = self:concat(output, context)
    end
    res = self:wrap(res, context)
    res = self:format(res, context)
    if res then
      -- util.debug(res)
      res = res:render(context.engine.formatter, context)
    end
    return res

  else
    local params = {
      maxoffset = #context.build.longest_label,
    }

    return {params, output}
  end
end

function Layout:_get_position (item, previous_cite, context)
  local engine = context.engine
  if not engine.registry.registry[item.id] then
    return util.position_map["first"]
  end

  local position = util.position_map["subsequent"]
  -- Find the preceding cite referencing the same item
  local preceding_cite = nil
  if previous_cite then
    -- a. the current cite immediately follows on another cite
    if item.id == previous_cite.id then
      preceding_cite = previous_cite
    end
  elseif engine.registry.previous_citation then
    -- b. first cite in the citation and previous citation exists
    for _, cite in ipairs(engine.registry.previous_citation.citationItems) do
      if item.id == cite.id then
        preceding_cite = cite
        break
      end
    end
  end

  if preceding_cite then
    if preceding_cite.locator then
      -- Preceding cite does have a locator
      if item.locator then
        if item.locator == preceding_cite.locator then
          position = util.position_map["ibid"]
        else
          position = util.position_map["ibid-with-locator"]
        end
      else
        -- the current cite lacks a locator
        position = util.position_map["subsequent"]
      end
    else
      -- Preceding cite does not have a locator
      if item.locator then
        position = util.position_map["ibid-with-locator"]
      else
        position = util.position_map["ibid"]
      end
    end
  end
  return position
end


function Layout:_collapse_citations(output, context)
  if context.options["collapse"] == "citation-number" then
    assert(#output == #context.items)
    local citation_numbers = {}
    for i, item in ipairs(context.items) do
      citation_numbers[i] = context.build.item_citation_numbers[item.id] or 0
    end

    local collapsed_output = {}
    local citation_number_range_delimiter = util.unicode["en dash"]
    local index = 1
    while index <= #citation_numbers do
      local stop_index = index + 1
      if output[index] == context.build.item_citation_number_text[index] then
        while stop_index <= #citation_numbers  do
          if output[stop_index] ~= context.build.item_citation_number_text[stop_index] then
            break
          end
          if citation_numbers[stop_index - 1] + 1 ~= citation_numbers[stop_index] then
            break
          end
          stop_index = stop_index + 1
        end
      end

      if stop_index >= index + 3 then
        local range_text = output[index] .. citation_number_range_delimiter .. output[stop_index - 1]
        table.insert(collapsed_output, range_text)
      else
        for i = index, stop_index - 1 do
          table.insert(collapsed_output, output[i])
        end
      end

      index = stop_index
    end

    return self:concat(collapsed_output, context)
  end
  return self:concat(output, context)
end


layout.Layout = Layout

return layout