citeproc-ruby 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +72 -64
- data/lib/citeproc/ruby/engine.rb +11 -15
- data/lib/citeproc/ruby/format.rb +35 -9
- data/lib/citeproc/ruby/formats/default.rb +3 -2
- data/lib/citeproc/ruby/formats/html.rb +42 -2
- data/lib/citeproc/ruby/renderer/date.rb +100 -16
- data/lib/citeproc/ruby/renderer/format.rb +18 -83
- data/lib/citeproc/ruby/renderer/group.rb +18 -6
- data/lib/citeproc/ruby/renderer/label.rb +2 -2
- data/lib/citeproc/ruby/renderer/layout.rb +4 -1
- data/lib/citeproc/ruby/renderer/locale.rb +63 -2
- data/lib/citeproc/ruby/renderer/macro.rb +2 -4
- data/lib/citeproc/ruby/renderer/names.rb +47 -64
- data/lib/citeproc/ruby/renderer/number.rb +2 -2
- data/lib/citeproc/ruby/renderer/observer.rb +10 -4
- data/lib/citeproc/ruby/renderer/state.rb +5 -1
- data/lib/citeproc/ruby/renderer/text.rb +16 -3
- data/lib/citeproc/ruby/renderer.rb +36 -5
- data/lib/citeproc/ruby/version.rb +1 -1
- metadata +12 -22
- data/.document +0 -5
- data/.github/workflows/ci.yml +0 -33
- data/.yardopts +0 -2
- data/AGPL +0 -662
- data/Gemfile +0 -27
- data/Rakefile +0 -47
- data/cucumber.yml +0 -1
|
@@ -16,7 +16,16 @@ module CiteProc
|
|
|
16
16
|
# Applies the current format on the string using the
|
|
17
17
|
# node's formatting options.
|
|
18
18
|
def format!(string, node)
|
|
19
|
-
format.apply(string, node, locale)
|
|
19
|
+
format.apply(string, node, locale, state.language)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Reads the item's language without notifying observers.
|
|
23
|
+
#
|
|
24
|
+
# @param item [CiteProc::Item, nil]
|
|
25
|
+
# @return [String, nil] the item's language
|
|
26
|
+
def language_of(item)
|
|
27
|
+
return unless item.respond_to?(:unobservable_read_attribute)
|
|
28
|
+
item.unobservable_read_attribute(:language).to_s
|
|
20
29
|
end
|
|
21
30
|
|
|
22
31
|
def join(list, delimiter = nil)
|
|
@@ -42,93 +51,19 @@ module CiteProc
|
|
|
42
51
|
end
|
|
43
52
|
|
|
44
53
|
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# the
|
|
49
|
-
# * "expanded": Abbreviated page ranges are expanded to
|
|
50
|
-
# their non-abbreviated form: 42-45, 321-328, 2787-2816.
|
|
51
|
-
# * "minimal": All digits repeated in the second number
|
|
52
|
-
# are left out: 42-45, 321-8, 2787-816.
|
|
53
|
-
#
|
|
54
|
-
# @param [String] pages to be formatted
|
|
55
|
-
# @param [String] format to use for formatting
|
|
54
|
+
# @param pages [String] the pages to format
|
|
55
|
+
# @param format [String, nil] the page range format
|
|
56
|
+
# @return [String, nil] the pages formatted according to format
|
|
57
|
+
# using the localized range delimiter
|
|
56
58
|
def format_page_range(pages, format)
|
|
57
|
-
|
|
58
|
-
format_page_range!(pages.dup, format)
|
|
59
|
+
CiteProc::Number.format_page_range(pages, format, range_delimiter)
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
dash = translate('page-range-delimiter') || '–' # en-dash
|
|
66
|
-
|
|
67
|
-
pages.gsub! PAGE_RANGE_PATTERN do
|
|
68
|
-
affixes, f, t = [$1, $3, $4, $6], $2, $5
|
|
69
|
-
|
|
70
|
-
# When there are affixes or no format was
|
|
71
|
-
# specified we skip this part. As a result,
|
|
72
|
-
# only the delimiter will be replaced!
|
|
73
|
-
|
|
74
|
-
if affixes.all?(&:empty?) && !format.nil?
|
|
75
|
-
|
|
76
|
-
dim = f.length
|
|
77
|
-
delta = dim - t.length
|
|
78
|
-
|
|
79
|
-
if delta >= 0
|
|
80
|
-
t.prepend f[0, delta] unless delta.zero?
|
|
81
|
-
|
|
82
|
-
# TODO
|
|
83
|
-
if format == 'chicago-16' || format == 'chicago-15'
|
|
84
|
-
format = 'chicago'
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
if format == 'chicago'
|
|
88
|
-
changes = dim - f.chars.zip(t.chars).
|
|
89
|
-
take_while { |a,b| a == b }.length if dim == 4
|
|
90
|
-
|
|
91
|
-
format = case
|
|
92
|
-
when dim < 3
|
|
93
|
-
'expanded'
|
|
94
|
-
when dim == 4 && changes > 2
|
|
95
|
-
'expanded'
|
|
96
|
-
when f[-2, 2] == '00'
|
|
97
|
-
'expanded'
|
|
98
|
-
when f[-2] == '0'
|
|
99
|
-
'minimal'
|
|
100
|
-
else
|
|
101
|
-
'minimal-two'
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
case format
|
|
106
|
-
when 'expanded'
|
|
107
|
-
# nothing to do
|
|
108
|
-
when 'minimal'
|
|
109
|
-
t = t.each_char.drop_while.with_index { |c, i| c == f[i] }.join('')
|
|
110
|
-
when 'minimal-two'
|
|
111
|
-
if dim > 2
|
|
112
|
-
t = t.each_char.drop_while.with_index { |c, i|
|
|
113
|
-
c == f[i] && dim - i > 2
|
|
114
|
-
}.join('')
|
|
115
|
-
end
|
|
116
|
-
else
|
|
117
|
-
raise ArgumentError, "unknown page range format: #{format}"
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
affixes.zip([f, dash, t]).flatten.compact.join('')
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
pages
|
|
62
|
+
# @return [String] the localized range delimiter; defaults to an en-dash
|
|
63
|
+
def range_delimiter
|
|
64
|
+
translate('page-range-delimiter') || '–'
|
|
126
65
|
end
|
|
127
66
|
|
|
128
|
-
PAGE_RANGE_PATTERN =
|
|
129
|
-
# ------------ -2- ------------ ------------ -5- ------------
|
|
130
|
-
/\b([[:alpha:]]*)(\d+)([[:alpha:]]*)\s*[–-]+\s*([[:alpha:]]*)(\d+)([[:alpha:]]*)\b/
|
|
131
|
-
|
|
132
67
|
end
|
|
133
68
|
|
|
134
69
|
end
|
|
@@ -7,24 +7,36 @@ module CiteProc
|
|
|
7
7
|
# @param node [CSL::Style::Group]
|
|
8
8
|
# @return [String]
|
|
9
9
|
def render_group(item, node)
|
|
10
|
-
|
|
10
|
+
render_as_group item, node, node.delimiter
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
11
14
|
|
|
15
|
+
# Renders the node's children with group suppression:
|
|
16
|
+
# the result is empty if the children call variables
|
|
17
|
+
# and ALL of them are empty; a non-empty result counts
|
|
18
|
+
# as a non-empty variable for any enclosing groups.
|
|
19
|
+
#
|
|
20
|
+
# @param item [CiteProc::CitationItem]
|
|
21
|
+
# @param node [CSL::Node]
|
|
22
|
+
# @param delimiter [String, nil]
|
|
23
|
+
# @return [String]
|
|
24
|
+
def render_as_group(item, node, delimiter = nil)
|
|
12
25
|
observer = ItemObserver.new(item.data)
|
|
13
26
|
observer.start
|
|
14
27
|
|
|
15
28
|
begin
|
|
16
|
-
rendition = node.each_child.map { |child|
|
|
29
|
+
rendition = join(node.each_child.map { |child|
|
|
17
30
|
render item, child
|
|
18
|
-
}.reject(&:empty?)
|
|
19
|
-
|
|
20
|
-
rendition = join(rendition, node.delimiter)
|
|
21
|
-
|
|
31
|
+
}.reject(&:empty?), delimiter)
|
|
22
32
|
ensure
|
|
23
33
|
observer.stop
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
return '' if observer.skip?
|
|
27
37
|
|
|
38
|
+
item.data.simulate_read_attribute :group, rendition unless rendition.empty?
|
|
39
|
+
|
|
28
40
|
rendition
|
|
29
41
|
end
|
|
30
42
|
|
|
@@ -15,8 +15,6 @@ module CiteProc
|
|
|
15
15
|
when node.page?
|
|
16
16
|
value, name = item.read_attribute(:page) || item.data.unobservable_read_attribute(:page).to_s, :page
|
|
17
17
|
|
|
18
|
-
format_page_range!(value, node.page_range_format)
|
|
19
|
-
|
|
20
18
|
when node.locator?
|
|
21
19
|
|
|
22
20
|
# Subtle: when there is no locator we also look
|
|
@@ -52,6 +50,8 @@ module CiteProc
|
|
|
52
50
|
false
|
|
53
51
|
when node.number_of_pages?, node.number_of_volumes?
|
|
54
52
|
value.to_i > 1
|
|
53
|
+
when node.locator?
|
|
54
|
+
item.plural_locator?(locator_abbreviations)
|
|
55
55
|
when value.respond_to?(:plural?)
|
|
56
56
|
value.plural?
|
|
57
57
|
else
|
|
@@ -5,13 +5,16 @@ module CiteProc
|
|
|
5
5
|
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
+
# Note that the layout's delimiter is used only between
|
|
9
|
+
# cites in a citation, not between the layout's children.
|
|
10
|
+
#
|
|
8
11
|
# @param item [CiteProc::CitationItem]
|
|
9
12
|
# @param node [CSL::Style::Layout]
|
|
10
13
|
# @return [String]
|
|
11
14
|
def render_layout(item, node)
|
|
12
15
|
join node.each_child.map { |child|
|
|
13
16
|
render item, child
|
|
14
|
-
}.reject(&:empty?)
|
|
17
|
+
}.reject(&:empty?)
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
end
|
|
@@ -3,12 +3,16 @@ module CiteProc
|
|
|
3
3
|
|
|
4
4
|
class Renderer
|
|
5
5
|
|
|
6
|
+
# @return [CSL::Locale] the locale including the in-style locale
|
|
7
|
+
# definitions of the style currently being rendered
|
|
6
8
|
def locale
|
|
7
|
-
|
|
9
|
+
localize(base_locale)
|
|
8
10
|
end
|
|
9
11
|
|
|
12
|
+
# @param locale [CSL::Locale, String] a locale or an IETF tag;
|
|
13
|
+
# locales are used as is and never changed by the renderer
|
|
10
14
|
def locale=(locale)
|
|
11
|
-
@locale = CSL::Locale.load(locale)
|
|
15
|
+
@locale = locale.is_a?(CSL::Locale) ? locale : CSL::Locale.load(locale)
|
|
12
16
|
end
|
|
13
17
|
|
|
14
18
|
def translate(name, options = {})
|
|
@@ -20,6 +24,63 @@ module CiteProc
|
|
|
20
24
|
locale.ordinalize(number, options)
|
|
21
25
|
end
|
|
22
26
|
|
|
27
|
+
# @return [Hash<String,String>] the abbreviations of locator labels
|
|
28
|
+
# including the localized short terms
|
|
29
|
+
def locator_abbreviations
|
|
30
|
+
@locator_abbreviations ||= {}.compare_by_identity
|
|
31
|
+
@locator_abbreviations[locale] ||= begin
|
|
32
|
+
terms = {}
|
|
33
|
+
|
|
34
|
+
CiteProc::CitationItem.labels.each do |label|
|
|
35
|
+
[false, true].each do |plural|
|
|
36
|
+
term = translate(label, :form => 'short', :plural => plural)
|
|
37
|
+
terms[term] = label.to_s unless term.to_s.empty?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
CiteProc::CitationItem.locator_abbreviations.merge(terms)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [String] the symbol form of the "and" term
|
|
46
|
+
# if the locale defines it; otherwise "&"
|
|
47
|
+
def ampersand
|
|
48
|
+
term = locale.terms.lookup('and', :form => 'symbol')
|
|
49
|
+
term&.form == 'symbol' ? term.to_s : '&'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def clear_locale_cache!
|
|
55
|
+
@localized, @locales, @locator_abbreviations = nil, nil, nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [CSL::Locale] the locale without in-style locale
|
|
59
|
+
# definitions; defaults to the default locale
|
|
60
|
+
def base_locale
|
|
61
|
+
@locale ||= CSL::Locale.load
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Localized copies are cached per style and locale; the
|
|
65
|
+
# style and the locale themselves are never changed.
|
|
66
|
+
#
|
|
67
|
+
# @param locale [CSL::Locale]
|
|
68
|
+
# @return [CSL::Locale] the locale localized for the current style
|
|
69
|
+
def localize(locale)
|
|
70
|
+
return locale if style.nil?
|
|
71
|
+
|
|
72
|
+
@localized ||= {}.compare_by_identity
|
|
73
|
+
localized = (@localized[style] ||= {}.compare_by_identity)
|
|
74
|
+
localized[locale] ||= style.localize(locale)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @param language [String] an IETF tag
|
|
78
|
+
# @return [CSL::Locale] the (cached) locale for the language
|
|
79
|
+
def locale_for(language)
|
|
80
|
+
@locales ||= {}
|
|
81
|
+
@locales[language] ||= CSL::Locale.load(language)
|
|
82
|
+
end
|
|
83
|
+
|
|
23
84
|
end
|
|
24
85
|
|
|
25
86
|
end
|
|
@@ -6,12 +6,10 @@ module CiteProc
|
|
|
6
6
|
private
|
|
7
7
|
|
|
8
8
|
# @param item [CiteProc::CitationItem]
|
|
9
|
-
# @param node [CSL::Style::
|
|
9
|
+
# @param node [CSL::Style::Macro]
|
|
10
10
|
# @return [String]
|
|
11
11
|
def render_macro(item, node)
|
|
12
|
-
|
|
13
|
-
render item, child
|
|
14
|
-
}.join('')
|
|
12
|
+
render_as_group item, node
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
end
|
|
@@ -25,7 +25,8 @@ module CiteProc
|
|
|
25
25
|
|
|
26
26
|
rendered_names = render_substitute item, node.substitute
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# Substituted output is replaced as a whole for all rules
|
|
29
|
+
if substitute_subsequent_authors? &&
|
|
29
30
|
completely_substitute?(rendered_names)
|
|
30
31
|
|
|
31
32
|
rendered_names = state.node.subsequent_author_substitute
|
|
@@ -146,8 +147,11 @@ module CiteProc
|
|
|
146
147
|
connector = node.connector
|
|
147
148
|
connector = translate('and') if connector == 'text'
|
|
148
149
|
|
|
149
|
-
# Add spaces around connector
|
|
150
|
-
|
|
150
|
+
# Add spaces around connector unless it belongs to a
|
|
151
|
+
# script which does not separate words by spaces
|
|
152
|
+
if CiteProc.romanesque_start?(connector)
|
|
153
|
+
connector = " #{connector} "
|
|
154
|
+
end
|
|
151
155
|
|
|
152
156
|
rendered_names =
|
|
153
157
|
case
|
|
@@ -171,12 +175,14 @@ module CiteProc
|
|
|
171
175
|
], node.ellipsis
|
|
172
176
|
|
|
173
177
|
else
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
translate('et-al')
|
|
178
|
+
term = translate(node.et_al ? node.et_al[:term] : 'et-al')
|
|
179
|
+
others = node.et_al ? format!(term, node.et_al) : term
|
|
177
180
|
|
|
178
|
-
connector =
|
|
179
|
-
|
|
181
|
+
connector = case
|
|
182
|
+
when node.delimiter_precedes_et_al?(truncated) then delimiter
|
|
183
|
+
when CiteProc.romanesque_start?(term) then ' '
|
|
184
|
+
else ''
|
|
185
|
+
end
|
|
180
186
|
|
|
181
187
|
join [
|
|
182
188
|
join(truncated.map.with_index { |name, idx|
|
|
@@ -185,7 +191,7 @@ module CiteProc
|
|
|
185
191
|
|
|
186
192
|
others
|
|
187
193
|
|
|
188
|
-
], connector
|
|
194
|
+
].reject { |part| part.to_s.empty? }, connector
|
|
189
195
|
|
|
190
196
|
end
|
|
191
197
|
|
|
@@ -220,7 +226,7 @@ module CiteProc
|
|
|
220
226
|
rendered_names = state.node.subsequent_author_substitute
|
|
221
227
|
end
|
|
222
228
|
|
|
223
|
-
|
|
229
|
+
affix rendered_names, node
|
|
224
230
|
end
|
|
225
231
|
|
|
226
232
|
# @param names [CiteProc::Name]
|
|
@@ -231,10 +237,6 @@ module CiteProc
|
|
|
231
237
|
if name.personal?
|
|
232
238
|
name = name.dup
|
|
233
239
|
|
|
234
|
-
# TODO move parts of the formatting logic here
|
|
235
|
-
# because name parts may include particles etc.
|
|
236
|
-
|
|
237
|
-
|
|
238
240
|
# Strip away some unusual characters to normalize
|
|
239
241
|
# sort order for names.
|
|
240
242
|
if sort_mode?
|
|
@@ -244,65 +246,26 @@ module CiteProc
|
|
|
244
246
|
name.options.merge! node.name_options
|
|
245
247
|
name.sort_order! node.name_as_sort_order_at?(position)
|
|
246
248
|
|
|
247
|
-
name.initialize_without_hyphen! if
|
|
249
|
+
name.initialize_without_hyphen! if style && style.initialize_without_hyphen?
|
|
248
250
|
|
|
249
251
|
if style && style.demote_particle?
|
|
250
252
|
name.options[:'demote-non-dropping-particle'] = style.demote_particle
|
|
251
253
|
end
|
|
252
254
|
|
|
253
|
-
#
|
|
254
|
-
if sort_mode? && name.
|
|
255
|
-
name.family = name.family.to_s.sub(/^[[:lower:]]+[\s-]/, '')
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
node.name_part.each do |part|
|
|
259
|
-
case part[:name]
|
|
260
|
-
when 'family'
|
|
261
|
-
if !name.particle? || name.demote_particle?
|
|
262
|
-
name.family = format!(name.family, part)
|
|
263
|
-
else
|
|
264
|
-
name.family = format!("#{name.particle} #{name.family}", part)
|
|
265
|
-
name.particle = nil
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Name suffix must be enclosed by family-part
|
|
269
|
-
# suffix in display order!
|
|
270
|
-
if name.has_suffix? && !name.sort_order? && part.attribute?(:suffix)
|
|
271
|
-
comma = name.comma_suffix? ? name.comma : ' '
|
|
272
|
-
suffix = part[:suffix]
|
|
273
|
-
|
|
274
|
-
name.family.chomp! suffix
|
|
275
|
-
name.family.concat "#{comma}#{name.suffix}#{suffix}"
|
|
276
|
-
name.suffix = nil
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
when 'given'
|
|
280
|
-
if name.dropping_particle?
|
|
281
|
-
name.given = format!("#{name.initials} #{name.dropping_particle}", part)
|
|
282
|
-
name.dropping_particle = nil
|
|
283
|
-
else
|
|
284
|
-
name.given = format!(name.initials, part)
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# Demoted particles must be enclosed by
|
|
288
|
-
# given-part affixes in sort order!
|
|
289
|
-
if name.particle? && name.demote_particle? &&
|
|
290
|
-
name.sort_order? && part.attribute?(:suffix)
|
|
255
|
+
# Particles are demoted in sort keys unless they are never demoted
|
|
256
|
+
name.always_demote_particle! if sort_mode? && !name.never_demote_particle?
|
|
291
257
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
name.given.chomp! suffix
|
|
295
|
-
name.given.concat " #{name.particle}#{suffix}"
|
|
258
|
+
end
|
|
296
259
|
|
|
297
|
-
|
|
298
|
-
end
|
|
260
|
+
parts = node.name_part.to_h { |part| [part[:name].to_sym, part] }
|
|
299
261
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
262
|
+
formatted = name.format(
|
|
263
|
+
->(part, text) { format_without_affixes(text, parts[part]) },
|
|
264
|
+
->(part, text) { affix(text, parts[part]) }
|
|
265
|
+
)
|
|
304
266
|
|
|
305
|
-
|
|
267
|
+
# The affixes of the name node enclose the list of names
|
|
268
|
+
format_without_affixes formatted, node
|
|
306
269
|
end
|
|
307
270
|
|
|
308
271
|
# @param item [CiteProc::CitationItem]
|
|
@@ -381,6 +344,23 @@ module CiteProc
|
|
|
381
344
|
name
|
|
382
345
|
end
|
|
383
346
|
|
|
347
|
+
# @return [String, nil] the text with the formatting, but
|
|
348
|
+
# without the affixes, of the node
|
|
349
|
+
def format_without_affixes(text, node)
|
|
350
|
+
return text if node.nil? || text.to_s.empty?
|
|
351
|
+
|
|
352
|
+
formatting = node.deep_copy
|
|
353
|
+
formatting[:prefix] = formatting[:suffix] = nil
|
|
354
|
+
|
|
355
|
+
format! text, formatting
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# @return [String, nil] the text enclosed by the affixes of the node
|
|
359
|
+
def affix(text, node)
|
|
360
|
+
return text if node.nil? || text.to_s.empty?
|
|
361
|
+
"#{node[:prefix]}#{text}#{node[:suffix]}"
|
|
362
|
+
end
|
|
363
|
+
|
|
384
364
|
def resolve_editor_translator_exception!(names)
|
|
385
365
|
|
|
386
366
|
i = names.index { |role, _| role == :translator }
|
|
@@ -391,6 +371,9 @@ module CiteProc
|
|
|
391
371
|
|
|
392
372
|
return names unless names[i][1] == names[j][1]
|
|
393
373
|
|
|
374
|
+
# An empty term disables the special case
|
|
375
|
+
return names if translate('editortranslator').to_s.empty?
|
|
376
|
+
|
|
394
377
|
# rename the first instance and drop the second one
|
|
395
378
|
i, j = j, i if j < i
|
|
396
379
|
|
|
@@ -16,8 +16,7 @@ module CiteProc
|
|
|
16
16
|
|
|
17
17
|
case
|
|
18
18
|
when node.ordinal? || node.long_ordinal?
|
|
19
|
-
options = node.attributes_for
|
|
20
|
-
# TODO lookup term of variable to check gender
|
|
19
|
+
options = node.attributes_for(:form).merge(:noun => node.variable)
|
|
21
20
|
|
|
22
21
|
numbers.map! do |num|
|
|
23
22
|
num =~ /^\d+$/ ? ordinalize(num, options) : num
|
|
@@ -32,6 +31,7 @@ module CiteProc
|
|
|
32
31
|
# nothing
|
|
33
32
|
end
|
|
34
33
|
|
|
34
|
+
numbers.map! { |num| num == '-' ? range_delimiter : num }
|
|
35
35
|
numbers.join('')
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -20,14 +20,14 @@ module CiteProc
|
|
|
20
20
|
self
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# A variable read more than once counts as non-empty
|
|
24
|
+
# if any of the reads was non-empty
|
|
23
25
|
def update(method, key, value)
|
|
24
|
-
history[key] = value if method == :read
|
|
26
|
+
history[key] = value if method == :read && empty?(history[key])
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def skip?
|
|
28
|
-
!history.empty? && history.values.all? { |v|
|
|
29
|
-
v.nil? || v.respond_to?(:empty?) && v.empty?
|
|
30
|
-
}
|
|
30
|
+
!history.empty? && history.values.all? { |v| empty?(v) }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def accessed
|
|
@@ -38,6 +38,12 @@ module CiteProc
|
|
|
38
38
|
history.clear
|
|
39
39
|
self
|
|
40
40
|
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def empty?(value)
|
|
45
|
+
value.nil? || value.respond_to?(:empty?) && value.empty?
|
|
46
|
+
end
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
end
|
|
@@ -30,6 +30,9 @@ module CiteProc
|
|
|
30
30
|
|
|
31
31
|
attr_reader :history, :node, :item, :authors, :substitute
|
|
32
32
|
|
|
33
|
+
# @return [String, nil] the language of the item being rendered
|
|
34
|
+
attr_accessor :language
|
|
35
|
+
|
|
33
36
|
def initialize
|
|
34
37
|
@history, @authors = History.new(self, 3), []
|
|
35
38
|
reset
|
|
@@ -55,7 +58,8 @@ module CiteProc
|
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def reset
|
|
58
|
-
@item, @node, @substitute, @authors, @names
|
|
61
|
+
@item, @node, @substitute, @authors, @names, @language =
|
|
62
|
+
nil, nil, nil, [], nil, nil
|
|
59
63
|
self
|
|
60
64
|
ensure
|
|
61
65
|
changed
|
|
@@ -16,7 +16,12 @@ module CiteProc
|
|
|
16
16
|
# in item.data; there should be no locator there
|
|
17
17
|
# either but the read access will be noticed by
|
|
18
18
|
# observers (if any).
|
|
19
|
-
text = item.locator || item.data[:locator].to_s
|
|
19
|
+
text = (item.locator || item.data[:locator]).to_s
|
|
20
|
+
|
|
21
|
+
elsif node.variable == 'year-suffix'
|
|
22
|
+
# The year-suffix is set by the processor (for disambiguation)
|
|
23
|
+
# and does not count as a variable for group suppression
|
|
24
|
+
text = item.data.unobservable_read_attribute(:'year-suffix').to_s
|
|
20
25
|
|
|
21
26
|
else
|
|
22
27
|
text = item.data.variable(node.variable, node.variable_options).to_s
|
|
@@ -25,7 +30,8 @@ module CiteProc
|
|
|
25
30
|
context, was_short_form = node.variable.split(/-short$/, 2)
|
|
26
31
|
|
|
27
32
|
if !was_short_form.nil? || node[:form] == 'short'
|
|
28
|
-
|
|
33
|
+
# Only the short form falls back to the long form
|
|
34
|
+
if text.empty? && context != node.variable && node[:form] == 'short'
|
|
29
35
|
text = item.data.variable(context, node.variable_options).to_s
|
|
30
36
|
end
|
|
31
37
|
|
|
@@ -36,7 +42,14 @@ module CiteProc
|
|
|
36
42
|
|
|
37
43
|
case
|
|
38
44
|
when node.variable == 'page'
|
|
39
|
-
format_page_range
|
|
45
|
+
text = format_page_range(text, node.page_range_format)
|
|
46
|
+
|
|
47
|
+
when node.variable == 'locator'
|
|
48
|
+
format = node.page_range_format if item.label == 'page'
|
|
49
|
+
text = format_page_range(text, format)
|
|
50
|
+
|
|
51
|
+
# Use the localized ampersand (e.g., in "213 & 235")
|
|
52
|
+
text = text.gsub(/\s*&\s*/, " #{ampersand} ")
|
|
40
53
|
|
|
41
54
|
when node.variable == 'page-first' && text.empty?
|
|
42
55
|
text = item.data[:'page'].to_s[/\d+/].to_s
|
|
@@ -26,6 +26,15 @@ module CiteProc
|
|
|
26
26
|
end
|
|
27
27
|
alias abbrev abbreviate
|
|
28
28
|
|
|
29
|
+
# Clears the renderer's caches. Call this after changing styles or
|
|
30
|
+
# locales in place, or to free memory when using many different styles.
|
|
31
|
+
#
|
|
32
|
+
# @return [self]
|
|
33
|
+
def clear_cache!
|
|
34
|
+
clear_locale_cache!
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
29
38
|
def allow_locale_overrides?
|
|
30
39
|
return false unless engine
|
|
31
40
|
engine.options[:allow_locale_overrides]
|
|
@@ -65,10 +74,14 @@ module CiteProc
|
|
|
65
74
|
# @param node [CSL::Style::Layout]
|
|
66
75
|
# @return [String] the rendered and string
|
|
67
76
|
def render_single_citation(item, node)
|
|
77
|
+
state.language = language_of(item.data)
|
|
78
|
+
|
|
68
79
|
# TODO author_only
|
|
69
80
|
item.suppress! 'author' if item.suppress_author?
|
|
70
81
|
|
|
71
82
|
join [item.prefix, render_layout(item, node), item.suffix].compact
|
|
83
|
+
ensure
|
|
84
|
+
state.language = nil
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
# @param item [CiteProc::CitationItem]
|
|
@@ -76,20 +89,37 @@ module CiteProc
|
|
|
76
89
|
# @return [String] the rendered and formatted string
|
|
77
90
|
def render_bibliography(item, node)
|
|
78
91
|
state.store! item, node
|
|
92
|
+
state.language = language_of(item.data)
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
language = item.language.to_s
|
|
95
|
+
|
|
96
|
+
if allow_locale_overrides? && !language.empty?
|
|
81
97
|
begin
|
|
82
|
-
|
|
98
|
+
item_locale = locale_for(language)
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
original_locale, @locale = @locale,
|
|
100
|
+
if item_locale != base_locale
|
|
101
|
+
original_locale, @locale = @locale, item_locale
|
|
86
102
|
end
|
|
87
103
|
rescue ParseError
|
|
88
104
|
# locale not found
|
|
89
105
|
end
|
|
90
106
|
end
|
|
91
107
|
|
|
92
|
-
|
|
108
|
+
fields = node.layout.each_child.map { |child|
|
|
109
|
+
render item, child
|
|
110
|
+
}.reject(&:empty?)
|
|
111
|
+
|
|
112
|
+
# With second-field-align, the first field is rendered
|
|
113
|
+
# in the left margin and the other fields inline
|
|
114
|
+
# (without trailing whitespace)
|
|
115
|
+
if node.second_field_align? && fields.length > 1
|
|
116
|
+
fields = [
|
|
117
|
+
format!(fields[0], CSL::Style::Group.new(:display => 'left-margin')),
|
|
118
|
+
format!(join(fields[1..]).rstrip, CSL::Style::Group.new(:display => 'right-inline'))
|
|
119
|
+
]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
result = format! join(fields), node.layout
|
|
93
123
|
|
|
94
124
|
ensure
|
|
95
125
|
unless original_locale.nil?
|
|
@@ -101,6 +131,7 @@ module CiteProc
|
|
|
101
131
|
|
|
102
132
|
def render_sort(item, node, key)
|
|
103
133
|
state.store! nil, key
|
|
134
|
+
state.language = language_of(item)
|
|
104
135
|
|
|
105
136
|
original_format = @format
|
|
106
137
|
@format = Formats::Sort.new
|