citeproc 1.2.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 +15 -4
- data/lib/citeproc/attributes.rb +2 -2
- data/lib/citeproc/bibliography.rb +1 -1
- data/lib/citeproc/citation_data.rb +103 -5
- data/lib/citeproc/date.rb +98 -51
- data/lib/citeproc/item.rb +30 -3
- data/lib/citeproc/names.rb +172 -64
- data/lib/citeproc/number.rb +116 -1
- data/lib/citeproc/selector.rb +5 -4
- data/lib/citeproc/utilities.rb +20 -0
- data/lib/citeproc/variable.rb +23 -6
- 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
|
@@ -86,10 +86,9 @@ You can speed up the XML parsing by installing
|
|
|
86
86
|
[Nokogiri](https://rubygems.org/gems/nokogiri);
|
|
87
87
|
otherwise the REXML from the Ruby standard library will be used.
|
|
88
88
|
|
|
89
|
-
Similarly, you can install
|
|
90
|
-
[EDTF](https://rubygems.org/gems/edtf)
|
|
91
|
-
|
|
92
|
-
to support a wide range of additional inputs for date variables.
|
|
89
|
+
Similarly, you can install the
|
|
90
|
+
[EDTF](https://rubygems.org/gems/edtf)
|
|
91
|
+
gem to support a wide range of additional inputs for date variables.
|
|
93
92
|
|
|
94
93
|
CSL Styles and Locales
|
|
95
94
|
----------------------
|
|
@@ -111,6 +110,18 @@ to install all official CSL styles and locales.
|
|
|
111
110
|
To make the styles and locales available,
|
|
112
111
|
simply `require 'csl/styles'`.
|
|
113
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
|
+
|
|
114
125
|
Credits
|
|
115
126
|
-------
|
|
116
127
|
Thanks to Rintze M. Zelle, Sebastian Karcher, Frank G. Bennett, Jr.,
|
data/lib/citeproc/attributes.rb
CHANGED
|
@@ -37,7 +37,7 @@ module CiteProc
|
|
|
37
37
|
return false if value.nil?
|
|
38
38
|
return false if value.respond_to?(:empty?) && value.empty?
|
|
39
39
|
|
|
40
|
-
value.to_s !~ /^(false|no|never)$/i
|
|
40
|
+
value.to_s !~ /^(false|no|never|0)$/i
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def filter_key(key)
|
|
@@ -108,7 +108,7 @@ module CiteProc
|
|
|
108
108
|
case
|
|
109
109
|
when equal?(other)
|
|
110
110
|
true
|
|
111
|
-
when self.class != other.class, length != other.length
|
|
111
|
+
when self.class != other.class, attributes.length != other.attributes.length
|
|
112
112
|
false
|
|
113
113
|
else
|
|
114
114
|
other.attributes.each_pair do |key, value|
|
|
@@ -11,12 +11,72 @@ module CiteProc
|
|
|
11
11
|
include Comparable
|
|
12
12
|
|
|
13
13
|
@labels = [
|
|
14
|
-
:
|
|
15
|
-
:
|
|
14
|
+
:act,
|
|
15
|
+
:appendix,
|
|
16
|
+
:'article-locator',
|
|
17
|
+
:book,
|
|
18
|
+
:canon,
|
|
19
|
+
:chapter,
|
|
20
|
+
:column,
|
|
21
|
+
:elocation,
|
|
22
|
+
:equation,
|
|
23
|
+
:figure,
|
|
24
|
+
:folio,
|
|
25
|
+
:issue,
|
|
26
|
+
:line,
|
|
27
|
+
:note,
|
|
28
|
+
:opus,
|
|
29
|
+
:page,
|
|
30
|
+
:paragraph,
|
|
31
|
+
:part,
|
|
32
|
+
:rule,
|
|
33
|
+
:scene,
|
|
34
|
+
:section,
|
|
35
|
+
:'sub-verbo',
|
|
36
|
+
:supplement,
|
|
37
|
+
:table,
|
|
38
|
+
:timestamp,
|
|
39
|
+
:'title-locator',
|
|
40
|
+
:verse,
|
|
41
|
+
:version,
|
|
42
|
+
:volume
|
|
16
43
|
].freeze
|
|
17
44
|
|
|
45
|
+
# Abbreviations of locator labels (for extraction)
|
|
46
|
+
@locator_abbreviations = {
|
|
47
|
+
'app.' => 'appendix',
|
|
48
|
+
'art.' => 'article-locator',
|
|
49
|
+
'bk.' => 'book',
|
|
50
|
+
'ch.' => 'chapter',
|
|
51
|
+
'col.' => 'column',
|
|
52
|
+
'fig.' => 'figure',
|
|
53
|
+
'fol.' => 'folio',
|
|
54
|
+
'l.' => 'line',
|
|
55
|
+
'n.' => 'note',
|
|
56
|
+
'no.' => 'issue',
|
|
57
|
+
'op.' => 'opus',
|
|
58
|
+
'p.' => 'page',
|
|
59
|
+
'pp.' => 'page',
|
|
60
|
+
'para.' => 'paragraph',
|
|
61
|
+
'pt.' => 'part',
|
|
62
|
+
'r.' => 'rule',
|
|
63
|
+
'sec.' => 'section',
|
|
64
|
+
'supp.' => 'supplement',
|
|
65
|
+
'sv.' => 'sub-verbo',
|
|
66
|
+
'tbl.' => 'table',
|
|
67
|
+
'tit.' => 'title-locator',
|
|
68
|
+
'vol.' => 'volume',
|
|
69
|
+
'vrs.' => 'verse'
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
18
72
|
class << self
|
|
19
|
-
attr_reader :labels
|
|
73
|
+
attr_reader :labels, :locator_abbreviations
|
|
74
|
+
|
|
75
|
+
# @param abbreviations [Hash<String,String>] label abbreviations
|
|
76
|
+
# @return [Regexp] a pattern matching any of the abbreviations
|
|
77
|
+
def locator_label_pattern(abbreviations = locator_abbreviations)
|
|
78
|
+
Regexp.union(abbreviations.keys.sort_by { |abbr| -abbr.length })
|
|
79
|
+
end
|
|
20
80
|
end
|
|
21
81
|
|
|
22
82
|
# @!attribute id
|
|
@@ -66,6 +126,46 @@ module CiteProc
|
|
|
66
126
|
yield self if block_given?
|
|
67
127
|
end
|
|
68
128
|
|
|
129
|
+
# @return [String, nil] the locator label;
|
|
130
|
+
# defaults to "page" if there is a locator
|
|
131
|
+
def label
|
|
132
|
+
read_attribute(:label) || ('page' if locator?)
|
|
133
|
+
end
|
|
134
|
+
|
|
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".
|
|
149
|
+
#
|
|
150
|
+
# @param abbreviations [Hash<String,String>] label abbreviations
|
|
151
|
+
# @return [self]
|
|
152
|
+
def parse_locator!(abbreviations = CitationItem.locator_abbreviations)
|
|
153
|
+
return self unless locator?
|
|
154
|
+
|
|
155
|
+
self.locator = locator.to_s.strip
|
|
156
|
+
return self unless label.to_s == 'page'
|
|
157
|
+
|
|
158
|
+
pattern = CitationItem.locator_label_pattern(abbreviations)
|
|
159
|
+
match = /\A(#{pattern})\s+(.+)\z/m.match(locator.to_s)
|
|
160
|
+
|
|
161
|
+
unless match.nil?
|
|
162
|
+
self.label = abbreviations[match[1]]
|
|
163
|
+
self.locator = match[2]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
69
169
|
def initialize_copy(other)
|
|
70
170
|
@attributes = other.attributes.deep_copy
|
|
71
171
|
end
|
|
@@ -83,8 +183,6 @@ module CiteProc
|
|
|
83
183
|
end
|
|
84
184
|
|
|
85
185
|
|
|
86
|
-
|
|
87
|
-
|
|
88
186
|
class CitationData
|
|
89
187
|
|
|
90
188
|
extend Forwardable
|
data/lib/citeproc/date.rb
CHANGED
|
@@ -7,8 +7,8 @@ module CiteProc
|
|
|
7
7
|
#
|
|
8
8
|
# {Date Dates} can be constructed from a wide range of input values,
|
|
9
9
|
# including Ruby date objects, integers, date ranges, ISO 8601 and
|
|
10
|
-
# CiteProc JSON strings or hashes, and - provided you have the
|
|
11
|
-
#
|
|
10
|
+
# CiteProc JSON strings or hashes, and - provided you have the EDTF gem
|
|
11
|
+
# installed - EDTF strings.
|
|
12
12
|
#
|
|
13
13
|
# @example Initialization
|
|
14
14
|
# CiteProc::Date.new
|
|
@@ -17,9 +17,6 @@ module CiteProc
|
|
|
17
17
|
# CiteProc::Date.today
|
|
18
18
|
# #-> #<CiteProc::Date "[2012, 6, 10]">
|
|
19
19
|
#
|
|
20
|
-
# CiteProc::Date.new('Yesterday')
|
|
21
|
-
# #-> #<CiteProc::Date "[[2012, 6, 9]]">
|
|
22
|
-
#
|
|
23
20
|
# CiteProc::Date.new(1966)
|
|
24
21
|
# #-> #<CiteProc::Date "[1966]">
|
|
25
22
|
#
|
|
@@ -80,7 +77,7 @@ module CiteProc
|
|
|
80
77
|
d = arguments[0]
|
|
81
78
|
super(d.year, d.month, d.day)
|
|
82
79
|
else
|
|
83
|
-
super(*arguments.map(
|
|
80
|
+
super(*arguments.map { |value| to_part(value) })
|
|
84
81
|
end
|
|
85
82
|
end
|
|
86
83
|
|
|
@@ -98,12 +95,18 @@ module CiteProc
|
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
parts.each_pair do |part, value|
|
|
101
|
-
self[part] = value
|
|
98
|
+
self[part] = to_part(value)
|
|
102
99
|
end
|
|
103
100
|
|
|
104
101
|
self
|
|
105
102
|
end
|
|
106
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
|
+
|
|
107
110
|
# @return [Boolean] whether or not the date parts are unset
|
|
108
111
|
def empty?
|
|
109
112
|
to_citeproc.empty?
|
|
@@ -129,6 +132,19 @@ module CiteProc
|
|
|
129
132
|
!bc? && year < 1000
|
|
130
133
|
end
|
|
131
134
|
|
|
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).
|
|
137
|
+
# @return [Integer, nil] the season (1 to 4) or nil if the
|
|
138
|
+
# month does not encode a season
|
|
139
|
+
def season
|
|
140
|
+
(month - 13) % 4 + 1 if month && month.between?(13, 24)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @return [Boolean] whether or not the month encodes a season
|
|
144
|
+
def season?
|
|
145
|
+
!season.nil?
|
|
146
|
+
end
|
|
147
|
+
|
|
132
148
|
# Formats the date parts according to the passed-in format string.
|
|
133
149
|
# @param format [String] a format string
|
|
134
150
|
# @return [String,nil] the formatted date string; nil if the date
|
|
@@ -148,8 +164,10 @@ module CiteProc
|
|
|
148
164
|
# @return [Fixnum,nil] the result of the comparison (-1, 0, 1 or nil)
|
|
149
165
|
def <=>(other)
|
|
150
166
|
case
|
|
167
|
+
when other.is_a?(DateParts) && season? && other.season?
|
|
168
|
+
[year, season] <=> [other.year, other.season]
|
|
151
169
|
when other.is_a?(DateParts)
|
|
152
|
-
|
|
170
|
+
comparable <=> other.comparable
|
|
153
171
|
when other.respond_to?(:to_date)
|
|
154
172
|
to_date <=> other.to_date
|
|
155
173
|
else
|
|
@@ -192,6 +210,14 @@ module CiteProc
|
|
|
192
210
|
def inspect
|
|
193
211
|
"#<DateParts #{to_s}>"
|
|
194
212
|
end
|
|
213
|
+
|
|
214
|
+
protected
|
|
215
|
+
|
|
216
|
+
# Seasons are compared like dates without month.
|
|
217
|
+
# @return [Array<Fixnum>] the date parts used for comparison
|
|
218
|
+
def comparable
|
|
219
|
+
season? ? to_citeproc.take(1) : to_citeproc
|
|
220
|
+
end
|
|
195
221
|
end
|
|
196
222
|
|
|
197
223
|
#
|
|
@@ -210,13 +236,11 @@ module CiteProc
|
|
|
210
236
|
# List of date parsers (must respond to #parse)
|
|
211
237
|
@parsers = []
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
# warn "failed to load `#{date_parser}' gem"
|
|
219
|
-
end
|
|
239
|
+
begin
|
|
240
|
+
require 'edtf'
|
|
241
|
+
@parsers << ::EDTF
|
|
242
|
+
rescue LoadError
|
|
243
|
+
# warn "failed to load `edtf' gem"
|
|
220
244
|
end
|
|
221
245
|
|
|
222
246
|
@parsers << ::Date
|
|
@@ -229,9 +253,8 @@ module CiteProc
|
|
|
229
253
|
# A list of available date parsers. Each parser must respond to a
|
|
230
254
|
# #parse method that converts a date string into a Ruby date object.
|
|
231
255
|
# By default, the list will include Ruby's date parser from the
|
|
232
|
-
# standard library, as well as the
|
|
233
|
-
#
|
|
234
|
-
# make sure to `gem install chronic edtf`.
|
|
256
|
+
# standard library, as well as the parser of the EDTF gem if it is
|
|
257
|
+
# available; to install it on your system run `gem install edtf`.
|
|
235
258
|
#
|
|
236
259
|
# @return [Array] the available date parsers
|
|
237
260
|
attr_reader :parsers
|
|
@@ -276,12 +299,44 @@ module CiteProc
|
|
|
276
299
|
end
|
|
277
300
|
|
|
278
301
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
302
|
+
def_delegators :to_ruby, :strftime
|
|
303
|
+
|
|
304
|
+
attr_predicates :circa, :literal, :'date-parts'
|
|
305
|
+
|
|
306
|
+
# @return [Boolean] whether or not the date has a season field
|
|
307
|
+
def has_season?
|
|
308
|
+
attribute?(:season)
|
|
309
|
+
end
|
|
283
310
|
|
|
284
|
-
|
|
311
|
+
def season=(season)
|
|
312
|
+
write_attribute :season, season
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Season names recognized in the season field
|
|
316
|
+
SEASONS = {
|
|
317
|
+
'spring' => 1, 'summer' => 2, 'autumn' => 3, 'fall' => 3, 'winter' => 4
|
|
318
|
+
}.freeze
|
|
319
|
+
|
|
320
|
+
# A season takes the place of the month. If the (start) date has a
|
|
321
|
+
# month, the season is encoded by the month (21 to 24); otherwise
|
|
322
|
+
# the season field is used. Season numbers (1 to 4) and names in
|
|
323
|
+
# the season field are converted to integers.
|
|
324
|
+
#
|
|
325
|
+
# @return [Integer, String, nil] the season (1 to 4), the season
|
|
326
|
+
# field as a string if it is not a season number or name, or nil
|
|
327
|
+
def season
|
|
328
|
+
return parts[0].season if month
|
|
329
|
+
|
|
330
|
+
value = read_attribute(:season) or return
|
|
331
|
+
name = value.to_s.strip.downcase
|
|
332
|
+
|
|
333
|
+
SEASONS[name] || (name.match?(/\A[1-4]\z/) ? name.to_i : value.to_s)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# @return [Boolean] whether or not the date has a season (see #season)
|
|
337
|
+
def season?
|
|
338
|
+
!season.nil?
|
|
339
|
+
end
|
|
285
340
|
|
|
286
341
|
def initialize(value = {})
|
|
287
342
|
super
|
|
@@ -321,11 +376,15 @@ module CiteProc
|
|
|
321
376
|
when value.is_a?(Hash)
|
|
322
377
|
attributes = value.symbolize_keys
|
|
323
378
|
|
|
324
|
-
if attributes.has_key?(:raw)
|
|
325
|
-
|
|
379
|
+
if attributes.has_key?(:raw) && Array(attributes[:'date-parts']).empty?
|
|
380
|
+
raw = attributes.delete(:raw)
|
|
381
|
+
attributes.delete(:'date-parts')
|
|
382
|
+
date = Date.parse(raw)
|
|
383
|
+
|
|
384
|
+
@value = date.nil? ? { :literal => raw } : date.value
|
|
326
385
|
@value.merge!(attributes)
|
|
327
386
|
else
|
|
328
|
-
@value = attributes.deep_copy
|
|
387
|
+
@value = attributes.except(:raw).deep_copy
|
|
329
388
|
end
|
|
330
389
|
convert_parts!
|
|
331
390
|
|
|
@@ -397,34 +456,16 @@ module CiteProc
|
|
|
397
456
|
d
|
|
398
457
|
end
|
|
399
458
|
|
|
400
|
-
# @return [::Date,nil] the date
|
|
401
|
-
|
|
402
|
-
d = parts[0] and d.to_date
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
def start_date=(date)
|
|
406
|
-
parts[0] = DateParts.new(date.strftime('%Y-%m-%d').split(/-/))
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
def end_date=(date)
|
|
410
|
-
parts[1] = DateParts.new(date.nil? ? 0 : date.strftime('%Y-%m-%d').split(/-/))
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
# @return [Date,Range] the date as a Ruby date object or as a Range if
|
|
414
|
-
# this instance is closed range
|
|
459
|
+
# @return [::Date, Range, nil] the date as a Ruby date object or as
|
|
460
|
+
# a Range if this instance is closed range
|
|
415
461
|
def to_ruby
|
|
416
462
|
if closed_range?
|
|
417
|
-
|
|
463
|
+
parts[0].to_date..parts[1].to_date
|
|
418
464
|
else
|
|
419
|
-
|
|
465
|
+
d = parts[0] and d.to_date
|
|
420
466
|
end
|
|
421
467
|
end
|
|
422
468
|
|
|
423
|
-
# @return [::Date,nil] the range's end date; or nil
|
|
424
|
-
def end_date
|
|
425
|
-
d = parts[1] and d.to_date
|
|
426
|
-
end
|
|
427
|
-
|
|
428
469
|
# @return [Boolean] whether or not the date-parts contain an end date
|
|
429
470
|
def has_end_date?
|
|
430
471
|
parts[1] && !parts[1].empty?
|
|
@@ -506,7 +547,7 @@ module CiteProc
|
|
|
506
547
|
case
|
|
507
548
|
when literal?
|
|
508
549
|
literal
|
|
509
|
-
when
|
|
550
|
+
when has_season?
|
|
510
551
|
season
|
|
511
552
|
else
|
|
512
553
|
parts.map(&:to_citeproc).inspect
|
|
@@ -516,8 +557,14 @@ module CiteProc
|
|
|
516
557
|
def <=>(other)
|
|
517
558
|
case other
|
|
518
559
|
when CiteProc::Date
|
|
519
|
-
|
|
520
|
-
|
|
560
|
+
result = parts <=> other.parts
|
|
561
|
+
|
|
562
|
+
# Seasons are ordered among themselves only
|
|
563
|
+
if result == 0 && season? && other.season?
|
|
564
|
+
result = season.to_i <=> other.season.to_i
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
result
|
|
521
568
|
when ::Date
|
|
522
569
|
parts <=> [other]
|
|
523
570
|
else
|
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,
|
|
@@ -92,20 +101,19 @@ module CiteProc
|
|
|
92
101
|
end
|
|
93
102
|
|
|
94
103
|
# Names quack sorta like a String
|
|
95
|
-
def_delegators :to_s,
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
def_delegators :to_s,
|
|
105
|
+
:to_str,
|
|
106
|
+
:=~, :===, :match, :match?, :start_with?, :end_with?, :include?,
|
|
107
|
+
:upcase, :downcase, :capitalize, :sub, :gsub, :split, :strip
|
|
98
108
|
|
|
99
|
-
# Delegate
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
p.send(m, *arguments, &block) if p.respond_to?(m)
|
|
106
|
-
end
|
|
107
|
-
self
|
|
109
|
+
# Delegate in-place transformations to each field's value
|
|
110
|
+
[:gsub!, :sub!, :upcase!, :downcase!, :capitalize!, :strip!].each do |m|
|
|
111
|
+
define_method(m) do |*arguments, &block|
|
|
112
|
+
Name.parts.each do |part|
|
|
113
|
+
p = attributes[part]
|
|
114
|
+
p.send(m, *arguments, &block) if p.respond_to?(m)
|
|
108
115
|
end
|
|
116
|
+
self
|
|
109
117
|
end
|
|
110
118
|
end
|
|
111
119
|
|
|
@@ -127,6 +135,30 @@ module CiteProc
|
|
|
127
135
|
end
|
|
128
136
|
|
|
129
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
|
+
|
|
130
162
|
# Resets the object's options to the default settings.
|
|
131
163
|
# @return [self]
|
|
132
164
|
def reset!
|
|
@@ -167,6 +199,12 @@ module CiteProc
|
|
|
167
199
|
static_ordering? || !romanesque?
|
|
168
200
|
end
|
|
169
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
|
+
|
|
170
208
|
# Set the name to use static order for printing, i.e., print the family
|
|
171
209
|
# name before the given name as is customary, for example, in Hungarian
|
|
172
210
|
# and many Asian languages.
|
|
@@ -236,7 +274,7 @@ module CiteProc
|
|
|
236
274
|
|
|
237
275
|
# @return [Boolean] whether or not initials will be used for printing
|
|
238
276
|
def initials?
|
|
239
|
-
!!options[:'initialize-with'] && personal? && romanesque?
|
|
277
|
+
!!options[:'initialize-with'] && personal? && family? && romanesque?
|
|
240
278
|
end
|
|
241
279
|
|
|
242
280
|
def initialize_with
|
|
@@ -244,7 +282,7 @@ module CiteProc
|
|
|
244
282
|
end
|
|
245
283
|
|
|
246
284
|
def initialize_existing_only?
|
|
247
|
-
options[:initialize]
|
|
285
|
+
!CiteProc.boolean(options[:initialize], true)
|
|
248
286
|
end
|
|
249
287
|
|
|
250
288
|
def initialize_without_hyphen?
|
|
@@ -259,16 +297,15 @@ module CiteProc
|
|
|
259
297
|
case
|
|
260
298
|
when !initials?
|
|
261
299
|
given
|
|
262
|
-
when initialize_existing_only?
|
|
263
|
-
existing_initials_of given
|
|
264
300
|
else
|
|
265
301
|
initials_of given
|
|
266
302
|
end
|
|
267
303
|
end
|
|
268
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
|
|
269
307
|
def demote_non_dropping_particle?
|
|
270
|
-
always_demote_non_dropping_particle?
|
|
271
|
-
!!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i)
|
|
308
|
+
always_demote_non_dropping_particle?
|
|
272
309
|
end
|
|
273
310
|
|
|
274
311
|
alias demote_particle? demote_non_dropping_particle?
|
|
@@ -316,30 +353,66 @@ module CiteProc
|
|
|
316
353
|
end
|
|
317
354
|
|
|
318
355
|
# @return [String] the name formatted according to the current options
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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) }
|
|
330
372
|
|
|
331
|
-
|
|
332
|
-
[[particle, family].compact_join(' '), [initials,
|
|
333
|
-
dropping_particle].compact_join(' '), suffix].compact_join(comma)
|
|
373
|
+
return affix_part.(:family, format_part.(:family, literal.to_s)) if literal?
|
|
334
374
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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)
|
|
339
398
|
else
|
|
340
|
-
[
|
|
399
|
+
[affix_part.(:family, family),
|
|
400
|
+
affix_part.(:given, [given, dropping, particle].compact_join(' ')), suffix].compact_join(comma)
|
|
341
401
|
end
|
|
342
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
|
|
343
416
|
alias print format
|
|
344
417
|
|
|
345
418
|
# @return [Array<String>] an ordered array of formatted name parts to be used for sorting
|
|
@@ -348,15 +421,15 @@ module CiteProc
|
|
|
348
421
|
when literal?
|
|
349
422
|
[literal.to_s.sub(sort_prefix, '')]
|
|
350
423
|
when never_demote_particle?
|
|
351
|
-
[
|
|
424
|
+
[particle_family, dropping_particle, given, suffix].map(&:to_s)
|
|
352
425
|
else
|
|
353
|
-
[family, [
|
|
426
|
+
[family, [dropping_particle, particle].compact_join(' '), given, suffix].map(&:to_s)
|
|
354
427
|
end
|
|
355
428
|
end
|
|
356
429
|
|
|
357
430
|
# @return [String] the name as a string stripped off all markup
|
|
358
431
|
def strip_markup
|
|
359
|
-
gsub(Variable.markup, '')
|
|
432
|
+
to_s.gsub(Variable.markup, '')
|
|
360
433
|
end
|
|
361
434
|
|
|
362
435
|
# @return [self] the name with all parts stripped off markup
|
|
@@ -382,37 +455,72 @@ module CiteProc
|
|
|
382
455
|
super key
|
|
383
456
|
end
|
|
384
457
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
388
465
|
|
|
389
|
-
|
|
466
|
+
unless particles.empty?
|
|
467
|
+
self.family = name
|
|
468
|
+
self.particle = particles.match?(/['’] \z/) ? particles.rstrip + ' ' : particles.rstrip
|
|
469
|
+
end
|
|
470
|
+
end
|
|
390
471
|
|
|
391
|
-
|
|
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
|
|
392
480
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
396
486
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
else
|
|
401
|
-
string.gsub!(/\s*-/, '-')
|
|
487
|
+
unless particles.empty?
|
|
488
|
+
self.given = name
|
|
489
|
+
self.dropping_particle = particles.join(' ')
|
|
402
490
|
end
|
|
403
491
|
end
|
|
404
492
|
|
|
405
|
-
def
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
string.gsub!(/([[:upper:]])([[:upper:]])/, '\1 \2')
|
|
410
|
-
string.gsub!(/\b([[:upper:]])\b[^[:alpha:]-]*/, "\\1#{initialize_with}")
|
|
493
|
+
def particle_word?(word)
|
|
494
|
+
word.sub(/\A[-'ʻ’\s]*/, '').match?(/\A\p{Ll}/)
|
|
495
|
+
end
|
|
411
496
|
|
|
412
|
-
|
|
497
|
+
def initials_of(string)
|
|
498
|
+
return unless string
|
|
413
499
|
|
|
414
|
-
|
|
415
|
-
string
|
|
500
|
+
# Split abbreviations like "M.E" into "M. E"
|
|
501
|
+
string = string.gsub(/\.(?=[[:alpha:]])/, '. ')
|
|
502
|
+
string = string.tr('-', ' ') if initialize_without_hyphen?
|
|
503
|
+
|
|
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|
|
|
507
|
+
part = initial_of(part) || " #{part} "
|
|
508
|
+
hyphen ? "#{part.rstrip}-" : part
|
|
509
|
+
}.join.gsub(/-\s+/, '-').squeeze(' ').strip
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# @return [String, nil] the initial for the given part of a name
|
|
513
|
+
# or nil if the part should not be initialized
|
|
514
|
+
def initial_of(part)
|
|
515
|
+
case part
|
|
516
|
+
when /\A([[:upper:]])\z/, /\A(.+)\.\z/
|
|
517
|
+
"#{$1}#{initialize_with}"
|
|
518
|
+
when /\A[[:upper:]]{2}[[:lower:]]/
|
|
519
|
+
# Two letter initials like "Ts" for "TSerendorjiin"
|
|
520
|
+
"#{part[0, 2].capitalize}#{initialize_with}" unless initialize_existing_only?
|
|
521
|
+
when /\A[[:upper:]]/
|
|
522
|
+
"#{part[0]}#{initialize_with}" unless initialize_existing_only?
|
|
523
|
+
end
|
|
416
524
|
end
|
|
417
525
|
end
|
|
418
526
|
|
|
@@ -566,7 +674,7 @@ module CiteProc
|
|
|
566
674
|
when value.is_a?(Name)
|
|
567
675
|
@value << value
|
|
568
676
|
when value.respond_to?(:each_pair), value.respond_to?(:to_hash)
|
|
569
|
-
@value << Name.new(value)
|
|
677
|
+
@value << Name.new(value).parse!
|
|
570
678
|
when value.respond_to?(:to_s)
|
|
571
679
|
begin
|
|
572
680
|
@value.concat Namae.parse!(value.to_s).map { |n| Name.new n }
|
data/lib/citeproc/number.rb
CHANGED
|
@@ -13,9 +13,22 @@ module CiteProc
|
|
|
13
13
|
['i', 1]
|
|
14
14
|
].freeze
|
|
15
15
|
|
|
16
|
+
# Numbers (with optional prefixes and suffixes) or roman numerals
|
|
17
|
+
RANGE_BOUND = /(?:[[:alnum:]]*\d[[:alnum:]]*|[ivxlcdm]+)/i
|
|
18
|
+
|
|
19
|
+
# Ranges between numbers; escaped hyphens are not range delimiters
|
|
20
|
+
RANGE = /(?<![[:alnum:]])#{RANGE_BOUND}\s*[–-]\s*#{RANGE_BOUND}(?![[:alnum:]])/
|
|
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
|
+
|
|
16
29
|
class << self
|
|
17
30
|
def pluralize?(string)
|
|
18
|
-
/\S[\s
|
|
31
|
+
/\S[\s,&]\S|\df/.match?(string) || RANGE.match?(string)
|
|
19
32
|
end
|
|
20
33
|
|
|
21
34
|
# @param number [#to_i] the number to convert
|
|
@@ -31,6 +44,108 @@ module CiteProc
|
|
|
31
44
|
code * count
|
|
32
45
|
}.join
|
|
33
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
|
|
34
149
|
end
|
|
35
150
|
|
|
36
151
|
def <=>(other)
|
data/lib/citeproc/selector.rb
CHANGED
|
@@ -19,11 +19,12 @@ module CiteProc
|
|
|
19
19
|
|
|
20
20
|
attr_reader :type, :conditions, :skip_conditions, :custom_matcher
|
|
21
21
|
|
|
22
|
-
def initialize(attributes = nil)
|
|
22
|
+
def initialize(attributes = nil, &block)
|
|
23
23
|
@conditions, @skip_conditions = {}, {}
|
|
24
24
|
|
|
25
25
|
if block_given?
|
|
26
26
|
@type = :ruby
|
|
27
|
+
@custom_matcher = block
|
|
27
28
|
|
|
28
29
|
else
|
|
29
30
|
unless attributes.nil? || attributes.empty?
|
|
@@ -54,12 +55,12 @@ module CiteProc
|
|
|
54
55
|
@type = other.type
|
|
55
56
|
@conditions = other.conditions.deep_copy
|
|
56
57
|
@skip_conditions = other.skip_conditions.deep_copy
|
|
57
|
-
@custom_matcher = other
|
|
58
|
+
@custom_matcher = other.custom_matcher
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def type=(type)
|
|
61
62
|
raise TypeError, "failed to set selector type to #{type.inspect}" unless
|
|
62
|
-
type.respond_to(:to_sym) && Selector.types.include?(type.to_sym)
|
|
63
|
+
type.respond_to?(:to_sym) && Selector.types.include?(type.to_sym)
|
|
63
64
|
|
|
64
65
|
@type = type.to_sym
|
|
65
66
|
end
|
|
@@ -69,7 +70,7 @@ module CiteProc
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def custom_matcher?
|
|
72
|
-
|
|
73
|
+
!custom_matcher.nil?
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
alias ruby? custom_matcher?
|
data/lib/citeproc/utilities.rb
CHANGED
|
@@ -15,6 +15,26 @@ 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
|
+
|
|
27
|
+
# @param value [String, Boolean, nil] an xsd:boolean value
|
|
28
|
+
# @param default [Boolean] the value if the value is not set
|
|
29
|
+
# @return [Boolean] the boolean value
|
|
30
|
+
def boolean(value, default = false)
|
|
31
|
+
case value.to_s
|
|
32
|
+
when 'true', '1' then true
|
|
33
|
+
when 'false', '0' then false
|
|
34
|
+
else default
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
18
38
|
end
|
|
19
39
|
|
|
20
40
|
end
|
data/lib/citeproc/variable.rb
CHANGED
|
@@ -131,12 +131,15 @@ module CiteProc
|
|
|
131
131
|
# @return [Object] the value wrapped by this variable
|
|
132
132
|
attr_accessor :value
|
|
133
133
|
|
|
134
|
+
# In-place transformations of the value
|
|
134
135
|
def_delegators :@value, :to_s,
|
|
135
|
-
|
|
136
|
+
:gsub!, :sub!, :upcase!, :downcase!, :capitalize!, :strip!
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
# Variables quack sorta like a String
|
|
139
|
+
def_delegators :to_s,
|
|
140
|
+
:to_str, :empty?,
|
|
141
|
+
:=~, :===, :match, :match?, :start_with?, :end_with?, :include?,
|
|
142
|
+
:upcase, :downcase, :capitalize, :sub, :gsub, :split, :strip
|
|
140
143
|
|
|
141
144
|
|
|
142
145
|
# Creates new Variable for the passed-in value
|
|
@@ -226,7 +229,7 @@ module CiteProc
|
|
|
226
229
|
#
|
|
227
230
|
# @return [Boolean] whether or not the variable's value is numeric
|
|
228
231
|
def numeric?
|
|
229
|
-
!!match(/^[\w\.:;]*\d+[\w\.:;]*(\s*[,&-]\s*[\w\.:;]*\d+[\w\.:;]*)*$/i)
|
|
232
|
+
!!to_s.match(/^[\w\.:;]*\d+[\w\.:;]*(\s*[,&-]\s*[\w\.:;]*\d+[\w\.:;]*)*$/i)
|
|
230
233
|
end
|
|
231
234
|
|
|
232
235
|
def date?
|
|
@@ -247,7 +250,7 @@ module CiteProc
|
|
|
247
250
|
|
|
248
251
|
# @return [String] the variable's value stripped of markup
|
|
249
252
|
def strip_markup
|
|
250
|
-
gsub(Variable.markup, '')
|
|
253
|
+
to_s.gsub(Variable.markup, '')
|
|
251
254
|
end
|
|
252
255
|
|
|
253
256
|
# Strips markup off the variable's value.
|
|
@@ -274,6 +277,20 @@ module CiteProc
|
|
|
274
277
|
end
|
|
275
278
|
end
|
|
276
279
|
|
|
280
|
+
# Variables are equal (in the sense of eql?) if they are of
|
|
281
|
+
# the same type and have the same value.
|
|
282
|
+
#
|
|
283
|
+
# @param other [Object] the object used for comparison
|
|
284
|
+
# @return [Boolean]
|
|
285
|
+
def eql?(other)
|
|
286
|
+
other.instance_of?(self.class) && value.eql?(other.value)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# @return [Integer] a hash value based on the type and value
|
|
290
|
+
def hash
|
|
291
|
+
[self.class, value].hash
|
|
292
|
+
end
|
|
293
|
+
|
|
277
294
|
# @!method to_s
|
|
278
295
|
# @return [String] the variable's value as a string
|
|
279
296
|
alias to_citeproc to_s
|
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
|