citeproc 1.3.0 → 2.0.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 +12 -0
- data/lib/citeproc/citation_data.rb +18 -4
- data/lib/citeproc/date.rb +11 -4
- data/lib/citeproc/item.rb +30 -3
- data/lib/citeproc/names.rb +143 -26
- data/lib/citeproc/number.rb +109 -0
- data/lib/citeproc/utilities.rb +9 -0
- data/lib/citeproc/version.rb +1 -1
- metadata +32 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 21838f9d914cb947a14c36bf1b65271170de7a12d40eb069921e3a2ab3fe8a2a
|
|
4
|
+
data.tar.gz: bf916ee190ab7850caacde982b7510ffa045d2d5e276b30e9cd3a112ef644e7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e4d56b5e112d3a3b94b931f648a75947efce1613f33ce147991ff9d8e2497ba33c002a547f403c9977ff65f6375ce2ebf7edbbe958ec3ea5b8daebfc819ba4c
|
|
7
|
+
data.tar.gz: 3be138495514a7f85ab8df3e22215c181069d0417aa8a863173592618fab37741e5b5691a3b52770deefcd7fb2bfed95077172c3f994b558e54daa0df11605db
|
data/README.md
CHANGED
|
@@ -110,6 +110,18 @@ to install all official CSL styles and locales.
|
|
|
110
110
|
To make the styles and locales available,
|
|
111
111
|
simply `require 'csl/styles'`.
|
|
112
112
|
|
|
113
|
+
Development
|
|
114
|
+
-----------
|
|
115
|
+
To get started, install the development dependencies and run all tests:
|
|
116
|
+
|
|
117
|
+
$ bundle install
|
|
118
|
+
$ bundle exec rake
|
|
119
|
+
|
|
120
|
+
The [CSL test-suite](https://github.com/citation-style-language/test-suite)
|
|
121
|
+
runs as Cucumber features using CiteProc-Ruby:
|
|
122
|
+
|
|
123
|
+
$ bundle exec cucumber
|
|
124
|
+
|
|
113
125
|
Credits
|
|
114
126
|
-------
|
|
115
127
|
Thanks to Rintze M. Zelle, Sebastian Karcher, Frank G. Bennett, Jr.,
|
|
@@ -132,14 +132,28 @@ module CiteProc
|
|
|
132
132
|
read_attribute(:label) || ('page' if locator?)
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
135
|
+
# Only the locator up to the first embedded label (e.g., "fol."
|
|
136
|
+
# in "1, fol. 186") determines whether or not it is plural.
|
|
137
|
+
#
|
|
138
|
+
# @param abbreviations [Hash<String,String>] label abbreviations
|
|
139
|
+
# @return [Boolean] whether or not the locator is plural
|
|
140
|
+
def plural_locator?(abbreviations = CitationItem.locator_abbreviations)
|
|
141
|
+
pattern = CitationItem.locator_label_pattern(abbreviations)
|
|
142
|
+
Number.pluralize?(locator.to_s.split(/\s(?:#{pattern})\s/, 2)[0])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Removes whitespace around the locator and moves a label at the
|
|
146
|
+
# start of the locator into the label (e.g., "vol. 1" becomes "1"
|
|
147
|
+
# with the label "volume") unless the item has a label other than
|
|
148
|
+
# "page".
|
|
138
149
|
#
|
|
139
150
|
# @param abbreviations [Hash<String,String>] label abbreviations
|
|
140
151
|
# @return [self]
|
|
141
152
|
def parse_locator!(abbreviations = CitationItem.locator_abbreviations)
|
|
142
|
-
return self unless locator?
|
|
153
|
+
return self unless locator?
|
|
154
|
+
|
|
155
|
+
self.locator = locator.to_s.strip
|
|
156
|
+
return self unless label.to_s == 'page'
|
|
143
157
|
|
|
144
158
|
pattern = CitationItem.locator_label_pattern(abbreviations)
|
|
145
159
|
match = /\A(#{pattern})\s+(.+)\z/m.match(locator.to_s)
|
data/lib/citeproc/date.rb
CHANGED
|
@@ -77,7 +77,7 @@ module CiteProc
|
|
|
77
77
|
d = arguments[0]
|
|
78
78
|
super(d.year, d.month, d.day)
|
|
79
79
|
else
|
|
80
|
-
super(*arguments.map(
|
|
80
|
+
super(*arguments.map { |value| to_part(value) })
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -95,12 +95,18 @@ module CiteProc
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
parts.each_pair do |part, value|
|
|
98
|
-
self[part] = value
|
|
98
|
+
self[part] = to_part(value)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
self
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# @return [Integer, nil] the value as a date part; empty values are unset
|
|
105
|
+
def to_part(value)
|
|
106
|
+
value.to_i unless value.to_s.strip.empty?
|
|
107
|
+
end
|
|
108
|
+
private :to_part
|
|
109
|
+
|
|
104
110
|
# @return [Boolean] whether or not the date parts are unset
|
|
105
111
|
def empty?
|
|
106
112
|
to_citeproc.empty?
|
|
@@ -126,11 +132,12 @@ module CiteProc
|
|
|
126
132
|
!bc? && year < 1000
|
|
127
133
|
end
|
|
128
134
|
|
|
129
|
-
# Seasons may be encoded as months 21 to 24 (Spring to Winter)
|
|
135
|
+
# Seasons may be encoded as months 21 to 24 (Spring to Winter)
|
|
136
|
+
# and, as citeproc-js does, 13 to 20 (Spring to Winter, twice).
|
|
130
137
|
# @return [Integer, nil] the season (1 to 4) or nil if the
|
|
131
138
|
# month does not encode a season
|
|
132
139
|
def season
|
|
133
|
-
month -
|
|
140
|
+
(month - 13) % 4 + 1 if month && month.between?(13, 24)
|
|
134
141
|
end
|
|
135
142
|
|
|
136
143
|
# @return [Boolean] whether or not the month encodes a season
|
data/lib/citeproc/item.rb
CHANGED
|
@@ -74,11 +74,28 @@ module CiteProc
|
|
|
74
74
|
protected :attributes
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
# Legacy names of variables in CSL-JSON
|
|
78
|
+
LEGACY_NAMES = {
|
|
79
|
+
:journalAbbreviation => :'container-title-short',
|
|
80
|
+
:shortTitle => :'title-short'
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
77
83
|
def initialize(attributes = nil)
|
|
78
84
|
merge(attributes)
|
|
79
85
|
yield self if block_given?
|
|
80
86
|
end
|
|
81
87
|
|
|
88
|
+
def merge(other)
|
|
89
|
+
super
|
|
90
|
+
|
|
91
|
+
LEGACY_NAMES.each do |legacy, name|
|
|
92
|
+
value = attributes.delete(legacy)
|
|
93
|
+
write_attribute(name, value) unless value.nil? || attribute?(name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
82
99
|
def initialize_copy(other)
|
|
83
100
|
@attributes = other.attributes.deep_copy
|
|
84
101
|
end
|
|
@@ -100,14 +117,24 @@ module CiteProc
|
|
|
100
117
|
end
|
|
101
118
|
|
|
102
119
|
def observable_read_attribute(key)
|
|
103
|
-
value = original_read_attribute(key)
|
|
104
|
-
return if suppressed?(key)
|
|
105
|
-
value
|
|
120
|
+
value = original_read_attribute(key) unless suppressed?(key)
|
|
106
121
|
ensure
|
|
107
122
|
changed
|
|
108
123
|
notify_observers :read, key, value
|
|
109
124
|
end
|
|
110
125
|
|
|
126
|
+
# Reads the variable without notifying observers and yields it;
|
|
127
|
+
# observers are notified of the block's result instead
|
|
128
|
+
# (e.g., what a renderer made of the variable).
|
|
129
|
+
# @return the block's result
|
|
130
|
+
def deferred_read_attribute(key)
|
|
131
|
+
value = original_read_attribute(key) unless suppressed?(key)
|
|
132
|
+
result = yield value
|
|
133
|
+
ensure
|
|
134
|
+
changed
|
|
135
|
+
notify_observers :read, key, result
|
|
136
|
+
end
|
|
137
|
+
|
|
111
138
|
def simulate_read_attribute(key, value)
|
|
112
139
|
changed
|
|
113
140
|
notify_observers :read, key, value
|
data/lib/citeproc/names.rb
CHANGED
|
@@ -42,6 +42,15 @@ module CiteProc
|
|
|
42
42
|
include Attributes
|
|
43
43
|
include Comparable
|
|
44
44
|
|
|
45
|
+
# Leading particle of a family name: "van der Vlist", "d'Alembert", "al-Hassan"
|
|
46
|
+
FAMILY_PARTICLE = /\A(?<particle>\S+[-ʻ’' ] *)(?<name>.+)\z/
|
|
47
|
+
|
|
48
|
+
# Trailing particle of a given name: "Alexander von"
|
|
49
|
+
GIVEN_PARTICLE = /\A(?<name>.+)\s+(?<particle>\S+)\z/
|
|
50
|
+
|
|
51
|
+
# Suffix after a comma in a given name: "John, III" or "John,! Jr."
|
|
52
|
+
SUFFIX = /\A(?<given>.*?)\s*,(?<comma>!?)\s*(?<suffix>.+)\z/
|
|
53
|
+
|
|
45
54
|
# Class instance variables
|
|
46
55
|
|
|
47
56
|
@romanesque =
|
|
@@ -51,7 +60,7 @@ module CiteProc
|
|
|
51
60
|
@defaults = {
|
|
52
61
|
:form => 'long',
|
|
53
62
|
:'name-as-sort-order' => false,
|
|
54
|
-
:'demote-non-dropping-particle' =>
|
|
63
|
+
:'demote-non-dropping-particle' => 'display-and-sort',
|
|
55
64
|
:'sort-separator' => ', ',
|
|
56
65
|
:initialize => true,
|
|
57
66
|
:'initialize-with-hyphen' => true,
|
|
@@ -126,6 +135,30 @@ module CiteProc
|
|
|
126
135
|
end
|
|
127
136
|
|
|
128
137
|
|
|
138
|
+
# Parses name particles and suffixes out of the family and given names
|
|
139
|
+
# and sets parse-names to false, so the name is not parsed again.
|
|
140
|
+
# Names with parse-names set to false are not parsed.
|
|
141
|
+
# Quotes around the family name are removed
|
|
142
|
+
# and the family name is not parsed.
|
|
143
|
+
# Names without family or given name, or with any particle or suffix set,
|
|
144
|
+
# are not parsed.
|
|
145
|
+
#
|
|
146
|
+
# @return [self]
|
|
147
|
+
def parse!
|
|
148
|
+
return self unless CiteProc.boolean(read_attribute(:'parse-names'), true)
|
|
149
|
+
|
|
150
|
+
quoted = family? && family.match?(/\A".+"\z/)
|
|
151
|
+
self.family = family[1...-1] if quoted
|
|
152
|
+
|
|
153
|
+
if family? && given? && !(particle? || dropping_particle? || suffix?)
|
|
154
|
+
parse_family! unless quoted
|
|
155
|
+
parse_given!
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
write_attribute(:'parse-names', false)
|
|
159
|
+
self
|
|
160
|
+
end
|
|
161
|
+
|
|
129
162
|
# Resets the object's options to the default settings.
|
|
130
163
|
# @return [self]
|
|
131
164
|
def reset!
|
|
@@ -166,6 +199,12 @@ module CiteProc
|
|
|
166
199
|
static_ordering? || !romanesque?
|
|
167
200
|
end
|
|
168
201
|
|
|
202
|
+
# @return [Boolean] whether or not the name can be printed in sort
|
|
203
|
+
# order (literal names and names in static order cannot)
|
|
204
|
+
def invertible?
|
|
205
|
+
personal? && !static_order?
|
|
206
|
+
end
|
|
207
|
+
|
|
169
208
|
# Set the name to use static order for printing, i.e., print the family
|
|
170
209
|
# name before the given name as is customary, for example, in Hungarian
|
|
171
210
|
# and many Asian languages.
|
|
@@ -263,9 +302,10 @@ module CiteProc
|
|
|
263
302
|
end
|
|
264
303
|
end
|
|
265
304
|
|
|
305
|
+
# @return [Boolean] whether or not the non-dropping particle is demoted
|
|
306
|
+
# when the name is printed; "sort-only" demotes it only in sort keys
|
|
266
307
|
def demote_non_dropping_particle?
|
|
267
|
-
always_demote_non_dropping_particle?
|
|
268
|
-
!!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i)
|
|
308
|
+
always_demote_non_dropping_particle?
|
|
269
309
|
end
|
|
270
310
|
|
|
271
311
|
alias demote_particle? demote_non_dropping_particle?
|
|
@@ -313,30 +353,66 @@ module CiteProc
|
|
|
313
353
|
end
|
|
314
354
|
|
|
315
355
|
# @return [String] the name formatted according to the current options
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
356
|
+
# Formats the name according to the formatting options. The name
|
|
357
|
+
# parts may be formatted and enclosed in affixes: the formatter is
|
|
358
|
+
# called with the name part (:given or :family) and its text, and the
|
|
359
|
+
# affixer with the name part and the text it encloses. The given
|
|
360
|
+
# formatting applies to the given name and the dropping particle, the
|
|
361
|
+
# family formatting to the family name and the non-dropping particle.
|
|
362
|
+
# The family affixes enclose all preceding particles and, for names in
|
|
363
|
+
# display order, the suffix; the given affixes enclose the particles
|
|
364
|
+
# following the given name in sort order.
|
|
365
|
+
#
|
|
366
|
+
# @param formatter [#call] formats the text of a name part
|
|
367
|
+
# @param affixer [#call] encloses the text of a name part in affixes
|
|
368
|
+
# @return [String] the formatted name
|
|
369
|
+
def format(formatter = nil, affixer = nil)
|
|
370
|
+
format_part = ->(part, text) { formatter.nil? || text.to_s.empty? ? text : formatter.(part, text) }
|
|
371
|
+
affix_part = ->(part, text) { affixer.nil? || text.to_s.empty? ? text : affixer.(part, text) }
|
|
327
372
|
|
|
328
|
-
|
|
329
|
-
[[particle, family].compact_join(' '), [initials,
|
|
330
|
-
dropping_particle].compact_join(' '), suffix].compact_join(comma)
|
|
373
|
+
return affix_part.(:family, format_part.(:family, literal.to_s)) if literal?
|
|
331
374
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
375
|
+
given = format_part.(:given, initials)
|
|
376
|
+
dropping = format_part.(:given, dropping_particle)
|
|
377
|
+
particle = format_part.(:family, self.particle)
|
|
378
|
+
family = format_part.(:family, self.family)
|
|
379
|
+
particle_family = [particle, family].compact_join(particle_separator)
|
|
380
|
+
|
|
381
|
+
case
|
|
382
|
+
when static_order?
|
|
383
|
+
[affix_part.(:family, family), affix_part.(:given, given)]
|
|
384
|
+
.compact_join(romanesque? ? ' ' : '')
|
|
385
|
+
when short_form?
|
|
386
|
+
affix_part.(:family, particle_family)
|
|
387
|
+
when !sort_order?
|
|
388
|
+
given = affix_part.(:given, given)
|
|
389
|
+
family = [[dropping, particle_family].compact_join(particle_separator(dropping_particle)),
|
|
390
|
+
suffix].compact_join(comma_suffix? ? comma : ' ')
|
|
391
|
+
|
|
392
|
+
# Affixes ending in a space (e.g., a no-break space) replace the space
|
|
393
|
+
[given, affix_part.(:family, family)]
|
|
394
|
+
.compact_join(given.to_s.match?(/[[:space:]]\z/) ? '' : ' ')
|
|
395
|
+
when !demote_particle?
|
|
396
|
+
[affix_part.(:family, particle_family),
|
|
397
|
+
affix_part.(:given, [given, dropping].compact_join(' ')), suffix].compact_join(comma)
|
|
336
398
|
else
|
|
337
|
-
[
|
|
399
|
+
[affix_part.(:family, family),
|
|
400
|
+
affix_part.(:given, [given, dropping, particle].compact_join(' ')), suffix].compact_join(comma)
|
|
338
401
|
end
|
|
339
402
|
end
|
|
403
|
+
|
|
404
|
+
# @return [String] the family name preceded by the non-dropping particle
|
|
405
|
+
def particle_family
|
|
406
|
+
[particle, family].compact_join(particle_separator)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# @param particle [String] the dropping or non-dropping particle
|
|
410
|
+
# @return [String] the separator following the particle; particles
|
|
411
|
+
# ending in a hyphen or apostrophe (e.g., "al-" or "d'") are joined
|
|
412
|
+
# without a space
|
|
413
|
+
def particle_separator(particle = self.particle)
|
|
414
|
+
particle.to_s.match?(/[-'’ʻ\s]\z/) ? '' : ' '
|
|
415
|
+
end
|
|
340
416
|
alias print format
|
|
341
417
|
|
|
342
418
|
# @return [Array<String>] an ordered array of formatted name parts to be used for sorting
|
|
@@ -345,9 +421,9 @@ module CiteProc
|
|
|
345
421
|
when literal?
|
|
346
422
|
[literal.to_s.sub(sort_prefix, '')]
|
|
347
423
|
when never_demote_particle?
|
|
348
|
-
[
|
|
424
|
+
[particle_family, dropping_particle, given, suffix].map(&:to_s)
|
|
349
425
|
else
|
|
350
|
-
[family, [
|
|
426
|
+
[family, [dropping_particle, particle].compact_join(' '), given, suffix].map(&:to_s)
|
|
351
427
|
end
|
|
352
428
|
end
|
|
353
429
|
|
|
@@ -379,6 +455,45 @@ module CiteProc
|
|
|
379
455
|
super key
|
|
380
456
|
end
|
|
381
457
|
|
|
458
|
+
# Leading lowercase words of the family name become the non-dropping particle;
|
|
459
|
+
# particles may be joined by hyphens or apostrophes ("d'Alembert", "al-Hassan").
|
|
460
|
+
def parse_family!
|
|
461
|
+
particles, name = '', family.to_s
|
|
462
|
+
while (m = FAMILY_PARTICLE.match(name)) && particle_word?(m[:particle])
|
|
463
|
+
particles, name = particles + m[:particle], m[:name]
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
unless particles.empty?
|
|
467
|
+
self.family = name
|
|
468
|
+
self.particle = particles.match?(/['’] \z/) ? particles.rstrip + ' ' : particles.rstrip
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Text after a comma in the given name becomes the suffix
|
|
473
|
+
# ("John, III"; with "John,! Jr." the suffix keeps its comma)
|
|
474
|
+
# and trailing lowercase words the dropping particle.
|
|
475
|
+
def parse_given!
|
|
476
|
+
if (m = SUFFIX.match(given.to_s))
|
|
477
|
+
self.given, self.suffix = m[:given], m[:suffix]
|
|
478
|
+
self.comma_suffix = true unless m[:comma].empty?
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
particles, name = [], given.to_s
|
|
482
|
+
while (m = GIVEN_PARTICLE.match(name)) && particle_word?(m[:particle])
|
|
483
|
+
particles.unshift(m[:particle])
|
|
484
|
+
name = m[:name]
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
unless particles.empty?
|
|
488
|
+
self.given = name
|
|
489
|
+
self.dropping_particle = particles.join(' ')
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def particle_word?(word)
|
|
494
|
+
word.sub(/\A[-'ʻ’\s]*/, '').match?(/\A\p{Ll}/)
|
|
495
|
+
end
|
|
496
|
+
|
|
382
497
|
def initials_of(string)
|
|
383
498
|
return unless string
|
|
384
499
|
|
|
@@ -386,7 +501,9 @@ module CiteProc
|
|
|
386
501
|
string = string.gsub(/\.(?=[[:alpha:]])/, '. ')
|
|
387
502
|
string = string.tr('-', ' ') if initialize_without_hyphen?
|
|
388
503
|
|
|
389
|
-
|
|
504
|
+
# Hyphens followed by a lowercase letter (e.g., "Guo-ping") do not
|
|
505
|
+
# separate parts of the name
|
|
506
|
+
string.scan(/((?:[^\s-]|-(?=\p{Ll}))+)\s*(-)?\s*/).map { |part, hyphen|
|
|
390
507
|
part = initial_of(part) || " #{part} "
|
|
391
508
|
hyphen ? "#{part.rstrip}-" : part
|
|
392
509
|
}.join.gsub(/-\s+/, '-').squeeze(' ').strip
|
|
@@ -557,7 +674,7 @@ module CiteProc
|
|
|
557
674
|
when value.is_a?(Name)
|
|
558
675
|
@value << value
|
|
559
676
|
when value.respond_to?(:each_pair), value.respond_to?(:to_hash)
|
|
560
|
-
@value << Name.new(value)
|
|
677
|
+
@value << Name.new(value).parse!
|
|
561
678
|
when value.respond_to?(:to_s)
|
|
562
679
|
begin
|
|
563
680
|
@value.concat Namae.parse!(value.to_s).map { |n| Name.new n }
|
data/lib/citeproc/number.rb
CHANGED
|
@@ -19,6 +19,13 @@ module CiteProc
|
|
|
19
19
|
# Ranges between numbers; escaped hyphens are not range delimiters
|
|
20
20
|
RANGE = /(?<![[:alnum:]])#{RANGE_BOUND}\s*[–-]\s*#{RANGE_BOUND}(?![[:alnum:]])/
|
|
21
21
|
|
|
22
|
+
PAGE_RANGE = /([[:alnum:]]+)\s*[–-]+\s*([[:alnum:]]+)/
|
|
23
|
+
|
|
24
|
+
# A page number with optional prefix and suffix
|
|
25
|
+
PAGE = /\A(\d*[[:alpha:]]+)?(\d+)([[:alpha:]]*)\z/
|
|
26
|
+
|
|
27
|
+
ROMAN = /\A[ivxlcdm]+\z/i
|
|
28
|
+
|
|
22
29
|
class << self
|
|
23
30
|
def pluralize?(string)
|
|
24
31
|
/\S[\s,&]\S|\df/.match?(string) || RANGE.match?(string)
|
|
@@ -37,6 +44,108 @@ module CiteProc
|
|
|
37
44
|
code * count
|
|
38
45
|
}.join
|
|
39
46
|
end
|
|
47
|
+
|
|
48
|
+
# Formats the page ranges in pages according to format:
|
|
49
|
+
#
|
|
50
|
+
# * "chicago-15" (or "chicago") and "chicago-16": page ranges are
|
|
51
|
+
# abbreviated according to the Chicago Manual of Style rules.
|
|
52
|
+
# * "expanded": Abbreviated page ranges are expanded to
|
|
53
|
+
# their non-abbreviated form: 42-45, 321-328, 2787-2816.
|
|
54
|
+
# * "minimal": All digits repeated in the second number
|
|
55
|
+
# are left out: 42-45, 321-8, 2787-816.
|
|
56
|
+
# * "minimal-two": As "minimal", but at least two digits
|
|
57
|
+
# are kept in the second number: 42-45, 321-28, 2787-816.
|
|
58
|
+
#
|
|
59
|
+
# Without a format only the range delimiter is replaced. Ranges
|
|
60
|
+
# with different prefixes (e.g., "N110-5") keep a plain hyphen;
|
|
61
|
+
# escaped hyphens ("\-") are not range delimiters.
|
|
62
|
+
#
|
|
63
|
+
# @param pages [String] the pages to format
|
|
64
|
+
# @param format [String, nil] the page range format
|
|
65
|
+
# @param delimiter [String] the range delimiter
|
|
66
|
+
# @return [String, nil] the formatted pages
|
|
67
|
+
def format_page_range(pages, format = nil, delimiter = '–')
|
|
68
|
+
return if pages.nil?
|
|
69
|
+
|
|
70
|
+
pages.to_s
|
|
71
|
+
.gsub(PAGE_RANGE) { format_page_bounds($1, $2, format, delimiter) || "#{$1}-#{$2}" }
|
|
72
|
+
.gsub('\\-', '-')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# @return [String, nil] the formatted page range or nil if
|
|
78
|
+
# the bounds do not form a page range
|
|
79
|
+
def format_page_bounds(from, to, format, delimiter)
|
|
80
|
+
if ROMAN.match?(from) && ROMAN.match?(to)
|
|
81
|
+
return "#{from}#{delimiter}#{to}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
f, t = PAGE.match(from), PAGE.match(to)
|
|
85
|
+
|
|
86
|
+
# Ranges must have the same prefix on both sides
|
|
87
|
+
return unless f && t && f[1] == t[1]
|
|
88
|
+
|
|
89
|
+
# When there are suffixes or no format was
|
|
90
|
+
# specified we only replace the delimiter
|
|
91
|
+
if format.nil? || !f[3].empty? || !t[3].empty?
|
|
92
|
+
return "#{from}#{delimiter}#{to}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
prefix = f[1]
|
|
96
|
+
last = format_page_number(f[2], t[2].dup, format)
|
|
97
|
+
|
|
98
|
+
# The prefix is repeated only for expanded ranges
|
|
99
|
+
"#{prefix}#{f[2]}#{delimiter}#{prefix if format == 'expanded'}#{last}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def format_page_number(f, t, format)
|
|
103
|
+
dim = f.length
|
|
104
|
+
delta = dim - t.length
|
|
105
|
+
|
|
106
|
+
if delta >= 0
|
|
107
|
+
t.prepend f[0, delta] unless delta.zero?
|
|
108
|
+
|
|
109
|
+
format = 'chicago-15' if format == 'chicago'
|
|
110
|
+
|
|
111
|
+
if format == 'chicago-15' || format == 'chicago-16'
|
|
112
|
+
# Only the 15th edition expands four digit
|
|
113
|
+
# numbers when three or more digits change
|
|
114
|
+
changes = dim - f.chars.zip(t.chars).
|
|
115
|
+
take_while { |a,b| a == b }.length if dim == 4
|
|
116
|
+
|
|
117
|
+
format = case
|
|
118
|
+
when dim < 3
|
|
119
|
+
'expanded'
|
|
120
|
+
when dim == 4 && format == 'chicago-15' && changes > 2
|
|
121
|
+
'expanded'
|
|
122
|
+
when f[-2, 2] == '00'
|
|
123
|
+
'expanded'
|
|
124
|
+
when f[-2] == '0'
|
|
125
|
+
'minimal'
|
|
126
|
+
else
|
|
127
|
+
'minimal-two'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
case format
|
|
132
|
+
when 'expanded'
|
|
133
|
+
# nothing to do
|
|
134
|
+
when 'minimal'
|
|
135
|
+
t = t.each_char.drop_while.with_index { |c, i| c == f[i] }.join('')
|
|
136
|
+
when 'minimal-two'
|
|
137
|
+
if dim > 2
|
|
138
|
+
t = t.each_char.drop_while.with_index { |c, i|
|
|
139
|
+
c == f[i] && dim - i > 2
|
|
140
|
+
}.join('')
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
raise ArgumentError, "unknown page range format: #{format}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
t
|
|
148
|
+
end
|
|
40
149
|
end
|
|
41
150
|
|
|
42
151
|
def <=>(other)
|
data/lib/citeproc/utilities.rb
CHANGED
|
@@ -15,6 +15,15 @@ module CiteProc
|
|
|
15
15
|
process(:bibliography, items, options)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# @param string [String, nil]
|
|
19
|
+
# @return [Boolean] whether or not the string starts with a letter of
|
|
20
|
+
# a script which separates words by spaces (as decided by citeproc-js);
|
|
21
|
+
# the Hebrew conjunction "ו" is excluded because it is written as a
|
|
22
|
+
# prefix of the following word
|
|
23
|
+
def romanesque_start?(string)
|
|
24
|
+
/\A(?!ו)[&\p{Latin}\p{Greek}\p{Cyrillic}\p{Hebrew}\p{Arabic}\p{Thai}]/.match?(string.to_s)
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
# @param value [String, Boolean, nil] an xsd:boolean value
|
|
19
28
|
# @param default [Boolean] the value if the value is not set
|
|
20
29
|
# @return [Boolean] the boolean value
|
data/lib/citeproc/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: citeproc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sylvester Keil
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: namae
|
|
@@ -15,56 +15,56 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.
|
|
25
|
+
version: '1.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: date
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
32
|
+
version: '3.0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
39
|
+
version: '3.0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: forwardable
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '1.3'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '1.3'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: json
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0'
|
|
60
|
+
version: '2.0'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0'
|
|
67
|
+
version: '2.0'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: observer
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,6 +93,20 @@ dependencies:
|
|
|
93
93
|
- - "<"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '1.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: citeproc-ruby
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.0'
|
|
96
110
|
description: A cite processor interface for Citation Style Language (CSL) styles.
|
|
97
111
|
email:
|
|
98
112
|
- sylvester@keil.or.at
|
|
@@ -124,7 +138,10 @@ files:
|
|
|
124
138
|
homepage: https://github.com/inukshuk/citeproc
|
|
125
139
|
licenses:
|
|
126
140
|
- BSD-2-Clause
|
|
127
|
-
metadata:
|
|
141
|
+
metadata:
|
|
142
|
+
source_code_uri: https://github.com/inukshuk/citeproc
|
|
143
|
+
bug_tracker_uri: https://github.com/inukshuk/citeproc/issues
|
|
144
|
+
rubygems_mfa_required: 'true'
|
|
128
145
|
rdoc_options: []
|
|
129
146
|
require_paths:
|
|
130
147
|
- lib
|