citeproc 0.0.8 → 0.0.9

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.
@@ -1,5 +1,3 @@
1
- require 'forwardable'
2
-
3
1
  module CiteProc
4
2
 
5
3
  class Engine
@@ -9,10 +9,10 @@ module CiteProc
9
9
  end
10
10
  end
11
11
 
12
- class ParseError < Error; end
13
-
14
- class EngineError < Error; end
12
+ ParseError = Class.new(Error)
13
+
14
+ EngineError = Class.new(Error)
15
15
 
16
- class NotImplementedByEngine < EngineError; end
16
+ NotImplementedByEngine = Class.new(Error)
17
17
 
18
18
  end
@@ -14,12 +14,10 @@ module CiteProc
14
14
  end
15
15
  end
16
16
 
17
- module DeepCopy
17
+ module DeepCopy
18
+ # See Matz, Flanagan: 'The Ruby Programming Language', p.83
18
19
  def deep_copy
19
- Hash[*map { |k,v| [
20
- begin k.respond_to?(:deep_copy) ? k.deep_copy : k.dup rescue k end,
21
- begin v.respond_to?(:deep_copy) ? v.deep_copy : v.dup rescue v end
22
- ]}.flatten(1)]
20
+ Marshal.load(Marshal.dump(self))
23
21
  end
24
22
  end
25
23
 
@@ -116,6 +114,9 @@ end
116
114
  class Array
117
115
  include CiteProc::Extensions::CompactJoin
118
116
  # include CiteProc::Extensions::ToSentence unless method_defined?(:to_sentence)
117
+
118
+ warn "citeproc: re-defining Array#deep_copy, this may cause conflicts with other libraries" if method_defined?(:deep_copy)
119
+ include CiteProc::Extensions::DeepCopy
119
120
  end
120
121
 
121
122
  class String
data/lib/citeproc/item.rb CHANGED
@@ -1,27 +1,28 @@
1
1
  module CiteProc
2
2
 
3
-
4
3
  # Items are similar to a Ruby Hash but pose a number of constraints on their
5
4
  # contents: keys are always (implicitly converted to) symbols and values
6
- # are strictly instances of CiteProc::Variable. When Items are constructed
7
- # from (or merged with) JSON objects or Hashes Variable instances are
8
- # automatically created using by passing the variables key as type to
9
- # Variable.create this will create the expected Variable type for all
5
+ # are strictly {Variable Variables}. When Items are constructed
6
+ # from (or merged with) JSON objects or Hashes {Variable} instances are
7
+ # automatically created by passing the variable's key as type to
8
+ # {Variable.create}; this will create the expected {Variable} type for all
10
9
  # fields defined in CSL (for example, the `issued' field will become a
11
- # CiteProc::Date object); unknown types will be converted to simple
12
- # CiteProc::Variable instances, which should be fine for numeric or string
13
- # values but may cause problems for more complex types.
10
+ # {Date} object; unknown types will be converted to simple {Variable}
11
+ # instances, which should be fine for numeric or string values but may
12
+ # cause problems for more complex types.
14
13
  #
15
14
  # Every Item provides accessor methods for all known field names; unknown
16
15
  # fields can still be accessed using array accessor syntax.
17
16
  #
18
17
  # i = Item.new(:edition => 3, :unknown_field => 42)
19
- # i.edition -> #<CiteProc::Number "3">
20
- # i[:edition] -> #<CiteProc::Number "3">
21
- # i[:unknown_field] -> #<CiteProc::Variable "42">
18
+ # i.edition
19
+ # #-> #<CiteProc::Number "3">
20
+ #
21
+ # i[:unknown_field]
22
+ # #-> #<CiteProc::Variable "42">
22
23
  #
23
- # Items can be converted to the CiteProc JSON format via #to_citeproc (or
24
- # #to_json to get a JSON string).
24
+ # Items can be converted to the CiteProc JSON format via {#to_citeproc}
25
+ # and {#to_json}.
25
26
  class Item
26
27
 
27
28
  @types = [
@@ -53,34 +54,88 @@ module CiteProc
53
54
  attr_reader :types, :bibtex_types
54
55
  end
55
56
 
57
+ extend Forwardable
58
+
56
59
  include Attributes
57
60
  include Enumerable
58
-
61
+ include Comparable
62
+ include Observable
63
+
64
+
59
65
  attr_predicates :id, :'short-title', :'journal-abbreviation',
60
66
  *Variable.fields[:all]
67
+
68
+ def_delegators :attributes, :values_at, :keys, :values
69
+
70
+ alias fields keys
71
+
72
+ # Hide attributes reader
73
+ protected :attributes
74
+
61
75
 
62
76
  def initialize(attributes = nil)
63
77
  merge(attributes)
78
+ yield self if block_given?
64
79
  end
65
80
 
66
81
  def initialize_copy(other)
67
82
  @attributes = other.attributes.deep_copy
68
83
  end
69
84
 
70
- # Don't expose attributes. Items need to mimic Hash functionality in a controlled way.
71
- private :attributes
72
-
85
+ # Calls a block once for each field in the item, passing the field's
86
+ # name-value pair as parameters.
87
+ #
88
+ # If not block is given, an enumerator is returned instead.
89
+ #
90
+ # item.each { |name, value| block }
91
+ # #-> item
92
+ #
93
+ # item.each
94
+ # #-> an enumerator
95
+ #
96
+ # @yieldparam field [Symbol] the field name
97
+ # @yieldparam value [Variable] the value
98
+ # @return [self,Enumerator] the item or an enumerator if no block is given
73
99
  def each
74
100
  if block_given?
75
- attributes.each(&Proc.new)
101
+ attributes.each_pair(&Proc.new)
76
102
  self
77
103
  else
78
104
  to_enum
79
105
  end
80
106
  end
81
107
 
82
- # Returns a corresponding BibTeX::Entry if the bibtex-ruby gem is installed;
83
- # otherwise returns a BibTeX string.
108
+ alias each_pair each
109
+
110
+ # Calls a block once for each field in the item, passing the field's
111
+ # value as parameters.
112
+ #
113
+ # If not block is given, an enumerator is returned instead.
114
+ #
115
+ # item.each_value { |value| block }
116
+ # #-> item
117
+ #
118
+ # item.each_value
119
+ # #-> an enumerator
120
+ #
121
+ # @yieldparam value [Variable] the value
122
+ # @return [self,Enumerator] the item or an enumerator if no block is given
123
+ def each_value
124
+ if block_given?
125
+ attributes.each_value(&Proc.new)
126
+ self
127
+ else
128
+ enum_for :each_value
129
+ end
130
+ end
131
+
132
+ def <=>(other)
133
+ return nil unless other.is_a?(Attributes)
134
+ eql?(other) ? 0 : length <=> other.length
135
+ end
136
+
137
+ # Returns a corresponding *BibTeX::Entry* if the bibtex-ruby gem is
138
+ # installed; otherwise returns a BibTeX string.
84
139
  def to_bibtex
85
140
  # hash = to_hash
86
141
  #
@@ -103,16 +158,24 @@ module CiteProc
103
158
  raise 'not implemented yet'
104
159
  end
105
160
 
161
+ # @return [Symbol,nil] the item's id
106
162
  def to_sym
107
- id.to_s.to_sym
163
+ if id?
164
+ id.to_s.intern
165
+ else
166
+ nil
167
+ end
108
168
  end
109
169
 
170
+ # @return [String] a string containing a human-readable
171
+ # representation of the item
110
172
  def inspect
111
- "#<CiteProc::Item id=#{id.inspect} attributes={#{attributes.length}}>"
173
+ "#<CiteProc::Item id=#{id.to_s.inspect} attributes={#{attributes.length}}>"
112
174
  end
113
175
 
114
176
  private
115
177
 
178
+ # @private
116
179
  def filter_value(value, key)
117
180
  Variable.create!(value, key)
118
181
  end
@@ -1,556 +1,655 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module CiteProc
2
-
3
- # Names consist of several dependent parts of strings. Simple personal names
4
- # are composed of family and given elements, containing respectively the
5
- # family and given name of the individual.
6
- #
7
- # Name.new(:family => 'Doe', :given => 'Jane')
8
- #
9
- # Institutional and other names that should always be presented literally
10
- # (such as "The Artist Formerly Known as Prince", "Banksy", or "Ramses IV")
11
- # should be delivered as a single :literal element:
12
- #
13
- # Name.new(:literal => 'Banksy')
14
- #
15
- # Name particles, such as the "von" in "Alexander von Humboldt", can be
16
- # delivered separately from the family and given name, as :dropping-particle
17
- # and :non-dropping-particle elements.
18
- #
19
- # Name suffixes such as the "Jr." in "Frank Bennett, Jr." and the "III" in
20
- # "Horatio Ramses III" can be delivered as a suffix element.
21
- #
22
- # Name.new do |n|
23
- # n.family, n.given, n.suffix = 'Ramses', 'Horatio', 'III'
24
- # end
25
- #
26
- # Names not written in the Latin or Cyrillic scripts are always displayed
27
- # with the family name first. Sometimes it might be desired to handle a
28
- # Latin or Cyrillic transliteration as if it were a fixed (non-Byzantine)
29
- # name. This behavior can be prompted by including activating
30
- # static-ordering:
31
- #
32
- # Name.new(:family => 'Muramaki', :given => 'Haruki').to_s
33
- # #=> "Haruki Muramaki"
34
- # Name.new(:family => 'Muramaki', :given => 'Haruki').static_order!.to_s
35
- # #=> "Muramaki Haruki"
36
- #
37
- class Name
38
-
39
- extend Forwardable
40
-
41
- include Attributes
42
- include Comparable
43
-
44
- # Based on the regular expression in Frank G. Bennett's citeproc-js
45
- # https://bitbucket.org/fbennett/citeproc-js/overview
46
- ROMANESQUE =
47
- /^[a-zA-Z\u0080-\u017f\u0400-\u052f\u0386-\u03fb\u1f00-\u1ffe\.,\s\u0027\u02bc\u2019-]*$/
3
+
4
+ # Names consist of several dependent parts of strings. Simple personal names
5
+ # are composed of family and given elements, containing respectively the
6
+ # family and given name of the individual.
7
+ #
8
+ # Name.new(:family => 'Doe', :given => 'Jane')
9
+ #
10
+ # Institutional and other names that should always be presented literally
11
+ # (such as "The Artist Formerly Known as Prince", "Banksy", or "Ramses IV")
12
+ # should be delivered as a single :literal element:
13
+ #
14
+ # Name.new(:literal => 'Banksy')
15
+ #
16
+ # Name particles, such as the "von" in "Alexander von Humboldt", can be
17
+ # delivered separately from the family and given name, as :dropping-particle
18
+ # and :non-dropping-particle elements.
19
+ #
20
+ # Name suffixes such as the "Jr." in "Frank Bennett, Jr." and the "III" in
21
+ # "Horatio Ramses III" can be delivered as a suffix element.
22
+ #
23
+ # Name.new do |n|
24
+ # n.family, n.given, n.suffix = 'Ramses', 'Horatio', 'III'
25
+ # end
26
+ #
27
+ # Names not written in the Latin or Cyrillic scripts are always displayed
28
+ # with the family name first. Sometimes it might be desired to handle a
29
+ # Latin or Cyrillic transliteration as if it were a fixed (non-Byzantine)
30
+ # name (e.g., for Hungarian names). This behavior can be prompted by
31
+ # including activating static-ordering:
32
+ #
33
+ # Name.new(:family => 'Murakami', :given => 'Haruki').to_s
34
+ # #-> "Haruki Murakami"
35
+ #
36
+ # Name.new(:family => 'Murakami', :given => 'Haruki').static_order!.to_s
37
+ # #-> "Murakami Haruki"
38
+ class Name
39
+
40
+ extend Forwardable
41
+
42
+ include Attributes
43
+ include Comparable
48
44
 
49
45
  # Class instance variables
50
46
 
51
- # Default formatting options
52
- @defaults = {
53
- :form => 'long',
54
- :'name-as-sort-order' => false,
55
- :'demote-non-dropping-particle' => 'never',
56
- :'sort-separator' => ', ',
57
- :'initialize-with' => nil
58
- }.freeze
59
-
60
- @parts = [:family, :given,:literal, :suffix, :'dropping-particle',
61
- :'non-dropping-particle'].freeze
62
-
63
- class << self
64
-
65
- attr_reader :defaults, :parts
66
-
67
- def parse(name_string)
68
- parse!(name_string)
69
- rescue ParseError
70
- nil
71
- end
72
-
73
- def parse!(name_string)
74
- fail 'not implemented yet'
75
- end
76
-
77
- end
78
-
79
-
80
-
81
- # Method generators
82
-
83
- attr_reader :options
84
-
85
- attr_predicates :'comma-suffix', :'static-ordering', :multi, *@parts
86
-
87
- # Aliases
88
- [[:last, :family], [:first, :given], [:particle, :'non_dropping_particle']].each do |a, m|
47
+ @romanesque = CiteProc.oniguruma {
48
+ '^[\p{Latin}\p{Greek}\p{Cyrillic}\p{Hebrew}\p{Armenian}\p{Georgian}\p{Common}]*$'
49
+ } || CiteProc.ruby_18 {
50
+ # @todo improve fallback range
51
+ /^[a-zA-Zäöüéè\s[:punct:]]*$/u
52
+ }
53
+
54
+ # Default formatting options
55
+ @defaults = {
56
+ :form => 'long',
57
+ :'name-as-sort-order' => false,
58
+ :'demote-non-dropping-particle' => :never,
59
+ :'sort-separator' => ', ',
60
+ :'initialize-with' => nil
61
+ }.freeze
62
+
63
+ @parts = [:family, :given,:literal, :suffix, :'dropping-particle',
64
+ :'non-dropping-particle'].freeze
65
+
66
+ class << self
67
+ attr_reader :defaults, :parts, :romanesque
68
+ end
69
+
70
+
71
+
72
+ # Method generators
73
+
74
+ # @!attribute [r] options
75
+ # @return the name's formatting options
76
+ attr_reader :options
77
+
78
+ attr_predicates :'comma-suffix', :'static-ordering', :multi, *@parts
79
+
80
+ # Aliases
81
+ [[:particle, :'non_dropping_particle']].each do |a, m|
89
82
  alias_method(a, m) if method_defined?(m)
90
83
 
91
- wa, wm = "#{a}=", "#{m}="
84
+ wa, wm = "#{a}=", "#{m}="
92
85
  alias_method(wa, wm) if method_defined?(wm)
93
86
 
94
- pa, pm = "#{a}?", "#{m}?"
87
+ pa, pm = "#{a}?", "#{m}?"
95
88
  alias_method(pa, pm) if method_defined?(pm)
96
89
 
97
- pa, pm = "has_#{a}?", "has_#{m}?"
90
+ pa, pm = "has_#{a}?", "has_#{m}?"
98
91
  alias_method(pa, pm) if method_defined?(pm)
99
92
  end
100
-
101
- # Names quack sorta like a String
102
- def_delegators :to_s, :=~, :===,
103
- *String.instance_methods(false).reject { |m| m =~ /^\W|!$|to_s|replace|first|last/ }
104
-
105
- # Delegate bang! methods to each field's value
106
- String.instance_methods(false).each do |m|
107
- if m.to_s.end_with?('!')
108
- define_method(m) do |*arguments, &block|
109
- Name.parts.each do |part|
110
- p = attributes[part]
111
- p.send(m, *arguments, &block) if p.respond_to?(m)
112
- end
113
- self
114
- end
115
- end
116
- end
117
-
118
-
119
- # Instance methods
120
-
121
- def initialize(attributes = {}, options = {})
122
- @options = Name.defaults.merge(options)
123
- @sort_prefix = (/^(the|an?|der|die|das|eine?|l[ae])\s+|^l\W/i).freeze
124
-
125
- merge(attributes)
126
-
127
- yield self if block_given?
128
- end
129
-
130
- def initialize_copy(other)
131
- @attributes = other.attributes.deep_copy
132
- @options = other.options.dup
133
- end
134
-
135
-
136
- # Returns true if the Name looks like it belongs to a person.
137
- def personal?
138
- !!family && !literal?
139
- end
140
-
141
- # Returns true if the name contains only romanesque characters. This
142
- # should be the case for the majority of names written in latin or
143
- # greek based script. It will be false, for example, for names written
144
- # in Chinese, Japanese, Arabic or Hebrew.
145
- def romanesque?
146
- !!([given, family].join.gsub(Variable.markup, '') =~ ROMANESQUE)
147
- end
148
-
149
- alias byzantine? romanesque?
150
-
151
- def static_order?
152
- static_ordering? || !romanesque?
153
- end
154
-
155
- def static_order!
156
- self.static_ordering = true
157
- self
158
- end
159
-
160
- alias static_order static_ordering
161
- alias static_order= static_ordering=
162
-
163
- # Returns true if this Name's sort-oder options currently set.
164
- def sort_order?
165
- !!(options[:'name-as-sort-order'].to_s =~ /^(y(es)?|always|t(rue)?)$/i)
166
- end
167
-
168
- def display_order?
169
- !sort_order?
170
- end
171
-
172
- # Sets this name sort-order option to true. Returns the Name instance.
173
- def sort_order!
174
- options[:'name-as-sort-order'] = true
175
- self
176
- end
177
-
178
- # The reverse of @sort_order!
179
- def display_order!
180
- options[:'name-as-sort-order'] = false
181
- self
182
- end
183
-
184
- def sort_separator
185
- options[:'sort-separator']
186
- end
187
-
188
- alias comma sort_separator
189
-
190
- def short_form?
191
- options[:form] == 'short'
192
- end
193
-
194
- def short_form!
195
- options[:form] = 'short'
196
- self
197
- end
198
-
199
- def long_form?
200
- options[:form] == 'long'
201
- end
202
-
203
- def long_form!
204
- options[:form] = 'long'
205
- self
206
- end
207
-
208
- # TODO should be done via mixin to be reused for variables
209
- # def transliterable?
210
- # end
211
- #
212
- # def transliterate(locale)
213
- # end
214
-
215
- def initials?
216
- !!options[:'initialize-with'] && personal? && romanesque?
217
- end
218
-
219
- def demote_non_dropping_particle?
220
- always_demote_non_dropping_particle? ||
221
- !!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i)
222
- end
223
-
224
- alias demote_particle? demote_non_dropping_particle?
225
-
226
- def never_demote_non_dropping_particle?
227
- !!(options[:'demote-non-dropping-particle'] =~ /^never$/i)
228
- end
229
-
230
- def never_demote_non_dropping_particle!
231
- options[:'demote-non-dropping-particle'] = 'never'
232
- self
233
- end
234
-
235
- alias never_demote_particle? never_demote_non_dropping_particle?
236
- alias never_demote_particle! never_demote_non_dropping_particle!
237
-
238
- def always_demote_non_dropping_particle?
239
- !!(options[:'demote-non-dropping-particle'] =~ /^(display-and-sort|always)$/i)
240
- end
241
-
242
- def always_demote_non_dropping_particle!
243
- options[:'demote-non-dropping-particle'] = 'display-and-sort'
244
- self
245
- end
246
-
247
- alias always_demote_particle? always_demote_non_dropping_particle?
248
- alias always_demote_particle! always_demote_non_dropping_particle!
249
-
250
- alias demote_particle! always_demote_non_dropping_particle!
251
-
252
- # Compares two names. The comparison is based on #sort_order_downcase.
253
- def <=>(other)
254
- return nil unless other.respond_to?(:sort_order_downcase)
255
- sort_order_downcase <=> other.sort_order_downcase
256
- end
257
-
258
- # Returns the Name as a String according to the Name's formatting options.
259
- def to_s
260
- case
261
- when literal?
262
- literal.to_s
263
-
264
- when static_order?
265
- [family, given].compact.join(' ')
266
-
267
- when !short_form?
268
- case
269
- when !sort_order?
270
- [[given, dropping_particle, particle, family].compact_join(' '),
271
- suffix].compact_join(comma_suffix? ? comma : ' ')
272
-
273
- when !demote_particle?
274
- [[particle, family].compact_join(' '), [given,
275
- dropping_particle].compact_join(' '), suffix].compact_join(comma)
276
-
277
- else
278
- [family, [given, dropping_particle, particle].compact_join(' '),
279
- suffix].compact_join(comma)
280
- end
281
-
282
- else
283
- [particle, family].compact_join(' ')
284
- end
285
- end
286
-
287
- # Returns an ordered array of formatted name parts to be used for sorting.
288
- def sort_order
289
- case
290
- when literal?
291
- [literal.to_s.sub(sort_prefix, '')]
292
- when never_demote_particle?
293
- [[particle, family].compact_join(' '), dropping_particle, given, suffix].map(&:to_s)
294
- else
295
- [family, [particle, dropping_particle].compact_join(' '), given, suffix].map(&:to_s)
296
- end
297
- end
298
-
299
- def strip_markup
300
- gsub(Variable.markup, '')
301
- end
302
-
303
- def strip_markup!
304
- gsub!(Variable.markup, '')
305
- end
306
-
307
- # Returns the sort order array stripped off markup and downcased.
308
- def sort_order_downcase
309
- sort_order.map { |s| s.downcase.gsub(Variable.markup, '') }
310
- end
311
-
312
- def inspect
313
- "#<CiteProc::Name #{to_s.inspect}>"
314
- end
315
-
316
- private
317
-
318
- attr_reader :sort_prefix
319
-
320
- end
321
-
322
-
323
-
324
-
325
- # Names are a CiteProc Variable containing an ordered list of Name objects.
326
- #
327
- # Names can be formatted using CSL formatting options. The available options
328
- # and their default values are described below.
329
- #
330
- # * and: specifies the delimiter between the penultimate and the last name.
331
- # Defaults to '&'.
332
- #
333
- # * delimiter: Soecifies the text string to seaparate the individual names.
334
- # The default value is ', '.
335
- #
336
- # * delimiter-precedes-last: determines in which cases the delimiter used
337
- # to delimit names is also used to separate the second to last and the
338
- # last name in name lists. The possible values are: 'contextual' (default,
339
- # the delimiter is only included for name lists with three or more names),
340
- # 'always', and 'never'.
341
- #
342
- # * et-al-min and et-al-use-first: Together, these attributes control et-al
343
- # abbreviation. When the number of names in a name variable matches or
344
- # exceeds the number set on et-al-min, the rendered name list is truncated
345
- # at the number of names set on et-al-use-first. If truncation occurs, the
346
- # "et-al" term is appended to the names rendered (see also Et-al). With a
347
- # single name (et-al-use-first="1"), the "et-al" term is preceded by a
348
- # space (e.g. "Doe et al."). With multiple names, the "et-al" term is
349
- # preceded by the name delimiter (e.g. "Doe, Smith, et al.").
350
- #
351
- # * et-al-subsequent-min / et-al-subsequent-use-first: The (optional)
352
- # et-al-min and et-al-use-first attributes take effect for all cites and
353
- # bibliographic entries. With the et-al-subsequent-min and
354
- # et-al-subsequent-use-first attributes divergent et-al abbreviation rules
355
- # can be specified for subsequent cites (cites referencing earlier cited
356
- # items).
357
- #
358
- class Names < Variable
359
-
360
- @defaults = {
361
- :and => '&',
362
- :delimiter => ', ',
363
- :'delimiter-precedes-last' => :contextual,
364
- :'et-al' => 'et al.',
365
- :'et-al-min' => 5,
366
- :'et-al-use-first' => 3,
367
- :'et-al-subsequent-min' => 5,
368
- :'et-al-subsequent-use-first' => 3
369
- }.freeze
370
-
371
- class << self
372
-
373
- attr_reader :defaults
374
-
375
- def parse(names_string)
376
- parse!(names_string)
377
- rescue ParseError
378
- nil
379
- end
380
-
381
- def parse!(names_string)
382
- fail 'not implemented yet'
383
- end
384
-
385
- end
386
-
387
-
388
- include Enumerable
389
-
390
- attr_reader :options
391
-
392
- alias names value
393
-
394
- # Don't expose value/names writer
395
- undef_method :value=
396
-
397
- # Delegate bang! methods to each name
398
- Name.instance_methods(false).each do |m|
399
- if m.to_s.end_with?('!')
400
- define_method(m) do |*arguments, &block|
401
- names.each do |name|
402
- name.send(m, *arguments, &block)
403
- end
404
- self
405
- end
406
- end
407
- end
408
-
409
- # Names quack sorta like an Array
410
- def_delegators :names, :length, :empty?, :[], :join
411
-
412
- # Some delegators should return self
413
- [:push, :<<, :unshift].each do |m|
414
- define_method(m) do |*arguments, &block|
415
- names.send(m, *arguments, &block)
416
- self
417
- end
418
- end
419
-
420
-
421
- def initialize(*arguments)
422
- @options = Names.defaults.dup
423
- super(arguments.flatten(1))
424
- end
425
-
426
- def initialize_copy(other)
427
- @options, @value = other.options.dup, other.value.map(&:dup)
428
- end
429
-
430
- def replace(values)
431
- @value = []
432
-
433
- values.each do |value|
434
- case
435
- when value.is_a?(Name)
436
- @value << value
437
-
438
- when value.is_a?(Hash)
439
- @value << Name.new(value)
440
-
441
- when value.respond_to?(:to_s)
442
- @value << Name.parse!(value.to_s)
443
-
444
- else
445
- raise TypeError, "failed to create names from #{value.inspect}"
446
- end
447
- end
448
-
449
- self
450
- end
451
-
452
- # Returns true if the Names, if printed, will be abbreviated.
453
- def abbreviate?
454
- length >= options[:'et-al-min'].to_i
455
- end
456
-
457
- # Returns true if the Names, if printed on subsequent cites, will be abbreviated.
458
- def abbreviate_subsequent?
459
- length >= options[:'et-al-subsequent-min'].to_i
460
- end
461
-
462
- def delimiter
463
- options[:delimiter]
464
- end
465
-
466
- def last_delimiter
467
- delimiter_precedes_last? ? [delimiter, connector].compact.join : connector
468
- end
469
-
470
- # Returns whether or not the delimiter will be inserted between the penultimate and the last name.
471
- def delimiter_precedes_last?
472
- case
473
- when delimiter_never_precedes_last?
474
- false
475
- when delimiter_always_precedes_last?
476
- true
477
- else
478
- length > 2
479
- end
480
- end
481
-
482
- def delimiter_always_precedes_last?
483
- !!(options[:'delimiter-precedes-last'].to_s =~ /^always$/i)
484
- end
485
-
486
- def delimiter_always_precedes_last!
487
- options[:'delimiter-precedes-last'].to_s = :always
488
- end
489
-
490
- alias delimiter_precedes_last! delimiter_always_precedes_last!
491
-
492
- def delimiter_never_precedes_last?
493
- !!(options[:'delimiter-precedes-last'].to_s =~ /^never$/i)
494
- end
495
-
496
- def delimiter_never_precedes_last!
497
- options[:'delimiter-precedes-last'].to_s = :never
498
- end
499
-
500
- def delimiter_never_precedes_last?
501
- !!(options[:'delimiter-precedes-last'].to_s =~ /^contextual/i)
502
- end
503
-
504
- def delimiter_contextually_precedes_last!
505
- options[:'delimiter-precedes-last'].to_s = :contextual
506
- end
507
-
508
-
509
-
510
- # Returns the string used as connector between the penultimate and the last name.
511
- def connector
512
- options[:and]
513
- end
514
-
515
- # Names are not numeric
516
- def numeric?
517
- false
518
- end
519
-
520
- def each
521
- if block_given?
522
- names.each(&Proc.new)
523
- self
524
- else
525
- to_enum
526
- end
527
- end
528
-
529
- def <=>(other)
530
- return nil unless other.respond_to?(:names)
531
- names <=> other.names
532
- end
533
-
534
- def to_s
535
- if length < 2
536
- names.join(last_delimiter)
537
- else
538
- [names[0...-1].join(delimiter), names[-1]].join(last_delimiter)
539
- end
540
- end
541
-
542
- def to_bibtex
543
- map { |n| n.dup.sort_order! }.join(' and ')
544
- end
545
-
546
- def to_citeproc
547
- map(&:to_citeproc)
548
- end
549
-
550
- def inspect
551
- "#<CiteProc::Names #{to_s}>"
552
- end
553
-
554
- end
555
-
93
+
94
+ # Names quack sorta like a String
95
+ def_delegators :to_s, :=~, :===,
96
+ *String.instance_methods(false).reject { |m| m =~ /^\W|!$|to_s|replace|first|last/ }
97
+
98
+ # Delegate bang! methods to each field's value
99
+ String.instance_methods(false).each do |m|
100
+ if m.to_s.end_with?('!')
101
+ define_method(m) do |*arguments, &block|
102
+ Name.parts.each do |part|
103
+ p = attributes[part]
104
+ p.send(m, *arguments, &block) if p.respond_to?(m)
105
+ end
106
+ self
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ # Instance methods
113
+
114
+ def initialize(attributes = {}, options = {})
115
+ @options = Name.defaults.merge(options)
116
+ @sort_prefix = (/^(the|an?|der|die|das|eine?|l[ae])\s+|^l\W/i).freeze
117
+
118
+ merge(attributes)
119
+
120
+ yield self if block_given?
121
+ end
122
+
123
+ def initialize_copy(other)
124
+ @attributes = other.attributes.deep_copy
125
+ @options = other.options.dup
126
+ end
127
+
128
+
129
+ # @return [Boolean] whether or not the Name looks like it belongs to a person
130
+ def personal?
131
+ !empty? && !literal?
132
+ end
133
+
134
+
135
+ # A name is `romanesque' if it contains only romanesque characters. This
136
+ # should be the case for the majority of names written in latin- or
137
+ # greek-based script. It will be false, for example, for names written
138
+ # in Chinese, Japanese, Arabic or Hebrew.
139
+ #
140
+ # @return [Boolean] whether or not the name is romanesque
141
+ def romanesque?
142
+ !!([given, family].join.gsub(Variable.markup, '') =~ Name.romanesque)
143
+ end
144
+
145
+ alias byzantine? romanesque?
146
+
147
+ # @return [Boolean] whether or not the name should be printed in static order
148
+ def static_order?
149
+ static_ordering? || !romanesque?
150
+ end
151
+
152
+ # Set the name to use static order for printing, i.e., print the family
153
+ # name before the given name as is customary, for example, in Hungarian
154
+ # and many Asian languages.
155
+ #
156
+ # @return [self]
157
+ def static_order!
158
+ self.static_ordering = true
159
+ self
160
+ end
161
+
162
+ alias static_order static_ordering
163
+ alias static_order= static_ordering=
164
+
165
+ # @return [Boolean] whether or not the name will be printed in sort-order
166
+ def sort_order?
167
+ !!(options[:'name-as-sort-order'].to_s =~ /^(y(es)?|always|t(rue)?)$/i)
168
+ end
169
+
170
+ def display_order?
171
+ !sort_order?
172
+ end
173
+
174
+ # Sets the name to use sort-order. The reverse of {#display_order!}.
175
+ # @return [self]
176
+ def sort_order!
177
+ options[:'name-as-sort-order'] = true
178
+ self
179
+ end
180
+
181
+ # Sets the name to use display-order. The reverse of {#sort_order!}.
182
+ # @return [self]
183
+ def display_order!
184
+ options[:'name-as-sort-order'] = false
185
+ self
186
+ end
187
+
188
+ # @return [String] the current sort separator
189
+ def sort_separator
190
+ options[:'sort-separator']
191
+ end
192
+
193
+ alias comma sort_separator
194
+
195
+ # @return [Boolean] whether or not the short form will be used for printing
196
+ def short_form?
197
+ options[:form].to_s =~ /short/i
198
+ end
199
+
200
+ # Use short form for printing the name
201
+ # @return [self]
202
+ def short_form!
203
+ options[:form] = :short
204
+ self
205
+ end
206
+
207
+ # @return [Boolean] whether or not the long form will be used for printing
208
+ def long_form?
209
+ !short_form?
210
+ end
211
+
212
+ # Use long form for printing the name
213
+ # @return [self]
214
+ def long_form!
215
+ options[:form] = :long
216
+ self
217
+ end
218
+
219
+ # @return [Boolean] whether or not initials will be used for printing
220
+ def initials?
221
+ !!options[:'initialize-with'] && personal? && romanesque?
222
+ end
223
+
224
+ def demote_non_dropping_particle?
225
+ always_demote_non_dropping_particle? ||
226
+ !!(sort_order? && options[:'demote-non-dropping-particle'] =~ /^sort(-only)?$/i)
227
+ end
228
+
229
+ alias demote_particle? demote_non_dropping_particle?
230
+
231
+ def never_demote_non_dropping_particle?
232
+ !!(options[:'demote-non-dropping-particle'] =~ /^never$/i)
233
+ end
234
+
235
+ def never_demote_non_dropping_particle!
236
+ options[:'demote-non-dropping-particle'] = 'never'
237
+ self
238
+ end
239
+
240
+ alias never_demote_particle? never_demote_non_dropping_particle?
241
+ alias never_demote_particle! never_demote_non_dropping_particle!
242
+
243
+ def always_demote_non_dropping_particle?
244
+ !!(options[:'demote-non-dropping-particle'] =~ /^(display-and-sort|always)$/i)
245
+ end
246
+
247
+ def always_demote_non_dropping_particle!
248
+ options[:'demote-non-dropping-particle'] = 'display-and-sort'
249
+ self
250
+ end
251
+
252
+ alias always_demote_particle? always_demote_non_dropping_particle?
253
+ alias always_demote_particle! always_demote_non_dropping_particle!
254
+
255
+ alias demote_particle! always_demote_non_dropping_particle!
256
+
257
+ # Compares two names. The comparison is based on #sort_order_downcase
258
+ #
259
+ # @see #sort_order_downcase
260
+ #
261
+ # @param other [#sort_order_downcase] the other name
262
+ # @return [Fixnum,nil] -1, 0, or 1 depending on the result of the
263
+ # comparison; nil if the name cannot be compared to the passed-in object
264
+ def <=>(other)
265
+ return nil unless other.respond_to?(:sort_order_downcase)
266
+ sort_order_downcase <=> other.sort_order_downcase
267
+ end
268
+
269
+ # @return [String] the name formatted according to the current options
270
+ def to_s
271
+ case
272
+ when literal?
273
+ literal.to_s
274
+ when static_order?
275
+ [family, given].compact.join(' ')
276
+ when !short_form?
277
+ case
278
+ when !sort_order?
279
+ [[given, dropping_particle, particle, family].compact_join(' '),
280
+ suffix].compact_join(comma_suffix? ? comma : ' ')
281
+
282
+ when !demote_particle?
283
+ [[particle, family].compact_join(' '), [given,
284
+ dropping_particle].compact_join(' '), suffix].compact_join(comma)
285
+
286
+ else
287
+ [family, [given, dropping_particle, particle].compact_join(' '),
288
+ suffix].compact_join(comma)
289
+ end
290
+ else
291
+ [particle, family].compact_join(' ')
292
+ end
293
+ end
294
+
295
+ # @return [Array<String>] an ordered array of formatted name parts to be used for sorting
296
+ def sort_order
297
+ case
298
+ when literal?
299
+ [literal.to_s.sub(sort_prefix, '')]
300
+ when never_demote_particle?
301
+ [[particle, family].compact_join(' '), dropping_particle, given, suffix].map(&:to_s)
302
+ else
303
+ [family, [particle, dropping_particle].compact_join(' '), given, suffix].map(&:to_s)
304
+ end
305
+ end
306
+
307
+ # @return [String] the name as a string stripped off all markup
308
+ def strip_markup
309
+ gsub(Variable.markup, '')
310
+ end
311
+
312
+ # @return [self] the name with all parts stripped off markup
313
+ def strip_markup!
314
+ gsub!(Variable.markup, '')
315
+ end
316
+
317
+ # @return [Array<String>] the sort order array stripped off markup and downcased
318
+ def sort_order_downcase
319
+ sort_order.map { |s| s.downcase.gsub(Variable.markup, '') }
320
+ end
321
+
322
+ # @return [String] a human-readable representation of the name object
323
+ def inspect
324
+ "#<CiteProc::Name #{to_s.inspect}>"
325
+ end
326
+
327
+ private
328
+
329
+ attr_reader :sort_prefix
330
+
331
+ end
332
+
333
+
334
+
335
+
336
+ # Represents a {Variable} containing an ordered list of {Name}
337
+ # objects. The names can be formatted using CSL formatting options (see
338
+ # {Names.defaults} for details).
339
+ class Names < Variable
340
+
341
+ @defaults = {
342
+ :and => ' & ',
343
+ :delimiter => ', ',
344
+ :'delimiter-precedes-last' => :contextual,
345
+ :'et-al' => 'et al.',
346
+ :'et-al-min' => 5,
347
+ :'et-al-use-first' => 3,
348
+ :'et-al-subsequent-min' => 5,
349
+ :'et-al-subsequent-use-first' => 3
350
+ }.freeze
351
+
352
+ class << self
353
+
354
+ # @!attribute [r] defaults
355
+ # @example
356
+ # {
357
+ # :and => '&',
358
+ # # The delimiter between the penultimate and last name
359
+ #
360
+ # :delimiter => ', ',
361
+ # # The delimiter between the other names
362
+ #
363
+ # :'delimiter-precedes-last' => :contextual,
364
+ # # Determines in which cases the delimiter used to delimit names
365
+ # # is also used to separate the second to last and the last name
366
+ # # in name lists. The possible values are: 'contextual' (default,
367
+ # # the delimiter is only included for name lists with three or
368
+ # # more names), 'always', and 'never'
369
+ #
370
+ # :'et-al' => 'et al.',
371
+ # # The string used for the phrase 'and others'
372
+ #
373
+ # :'et-al-min' => 5,
374
+ # :'et-al-use-first' => 3,
375
+ # # Together, these attributes control et-al abbreviation. When
376
+ # # the number of names in a name variable matches or exceeds
377
+ # # the number set on et-al-min, the rendered name list is truncated
378
+ # # at the number of names set on et-al-use-first. If truncation
379
+ # # occurs, the "et-al" term is appended to the names rendered.
380
+ # # With a single name (et-al-use-first="1"), the "et-al" term is
381
+ # # preceded by a space (e.g. "Doe et al."). With multiple names,
382
+ # # the "et-al" term is preceded by the name delimiter (e.g.
383
+ # # "Doe, Smith, et al.")
384
+ #
385
+ # :'et-al-subsequent-min' => 5,
386
+ # :'et-al-subsequent-use-first' => 3
387
+ # # See above. Abbreviation rules for subsequent cites (cites
388
+ # # referencing earlier cited items)
389
+ # }
390
+ #
391
+ # @return [Hash] the Names' default formatting options
392
+ attr_reader :defaults
393
+
394
+ # Parses the passed-in string and returns a Names object. Behaves like
395
+ # parse but returns nil for bad input without raising an error.
396
+ #
397
+ # @see .parse!
398
+ #
399
+ # @param names [String] the name or names to be parsed
400
+ # @return [Names,nil] the parsed names
401
+ def parse(names)
402
+ parse!(names)
403
+ rescue ParseError
404
+ nil
405
+ end
406
+
407
+ # Parses the passed-in string and returns a Names object.
408
+ #
409
+ # @param names [String] the name or names to be parsed
410
+ # @return [Names] the parsed names
411
+ #
412
+ # @raise [ParseError] if the string cannot be parsed.
413
+ def parse!(names)
414
+ new Namae.parse!(names)
415
+ rescue
416
+ raise ParseError, $!.message
417
+ end
418
+
419
+ end
420
+
421
+ include Enumerable
422
+
423
+ # @!attribute [r] options
424
+ # @return [Hash] the current formatting options
425
+
426
+ attr_reader :options
427
+
428
+ alias names value
429
+
430
+ # Don't expose value/names writer
431
+ undef_method :value=
432
+
433
+ # Delegate bang! methods to each name
434
+ Name.instance_methods(false).each do |m|
435
+ if m.to_s.end_with?('!')
436
+ define_method(m) do |*arguments, &block|
437
+ names.each do |name|
438
+ name.send(m, *arguments, &block)
439
+ end
440
+ self
441
+ end
442
+ end
443
+ end
444
+
445
+ # Names quack sorta like an Array
446
+ def_delegators :names, :length, :empty?, :[], :join
447
+
448
+
449
+ # Some delegators should return self
450
+
451
+ # @!method push(name)
452
+ # Appends the given name to the list of names.
453
+ # @param name [Name] a name
454
+ # @return [self]
455
+ # @!method unshift(name)
456
+ # Inserts the given name at the beginning of the list of names.
457
+ # @param name [Name] a name
458
+ # @return [self]
459
+ [:<<, :push, :unshift].each do |m|
460
+ define_method(m) do |*arguments, &block|
461
+ names.send(m, *arguments, &block)
462
+ self
463
+ end
464
+ end
465
+
466
+ def initialize(*arguments)
467
+ @options = Names.defaults.dup
468
+ super(arguments.flatten(1))
469
+ end
470
+
471
+ def initialize_copy(other)
472
+ @options, @value = other.options.dup, other.value.map(&:dup)
473
+ end
474
+
475
+ def replace(values)
476
+ @value = []
477
+
478
+ [*values].each do |value|
479
+ case
480
+ when value.is_a?(Name)
481
+ @value << value
482
+ when value.respond_to?(:each_pair), value.respond_to?(:to_hash)
483
+ @value << Name.new(value)
484
+ when value.respond_to?(:to_s)
485
+ begin
486
+ @value.concat Namae.parse!(value.to_s)
487
+ rescue
488
+ raise TypeError, $!.message
489
+ end
490
+ else
491
+ raise TypeError, "failed to create names from #{value.inspect}"
492
+ end
493
+ end
494
+
495
+ self
496
+ end
497
+
498
+ # @return [Fixnum] the maximum number of names that should be printed
499
+ def max_names
500
+ [length, options[:'et-al-use-first'].to_i.abs].min
501
+ end
502
+
503
+ # @return [Boolean] whether or not the Names should be truncate
504
+ def truncate?
505
+ length >= options[:'et-al-min'].to_i.abs
506
+ end
507
+
508
+ # @return [Boolean] whether ot not the Names, if printed on subsequent
509
+ # cites, should be truncated
510
+ def truncate_subsequent?
511
+ length >= options[:'et-al-subsequent-min'].to_i
512
+ end
513
+
514
+ # @return [String] the delimiter between names
515
+ def delimiter
516
+ options[:delimiter]
517
+ end
518
+
519
+ # @return [String] the delimiter between the penultimate and last name
520
+ # @see #connector
521
+ # @see #delimiter_precedes_last?
522
+ def last_delimiter
523
+ if delimiter_precedes_last?
524
+ [delimiter, connector].compact.join.squeeze(' ')
525
+ else
526
+ connector
527
+ end
528
+ end
529
+
530
+ # @return [String] the delimiter between the last name printed name and
531
+ # the 'and others' term
532
+ def truncated_delimiter
533
+ max_names > 1 ? delimiter : ' '
534
+ end
535
+
536
+ # @return [Boolean] whether or not the delimiter will be inserted between
537
+ # the penultimate and the last name
538
+ def delimiter_precedes_last?
539
+ case
540
+ when delimiter_never_precedes_last?
541
+ false
542
+ when delimiter_always_precedes_last?
543
+ true
544
+ else
545
+ length > 2
546
+ end
547
+ end
548
+
549
+ # @return [Boolean] whether or not the should always be inserted between
550
+ # the penultimate and the last name
551
+ def delimiter_always_precedes_last?
552
+ !!(options[:'delimiter-precedes-last'].to_s =~ /^always$/i)
553
+ end
554
+
555
+ # Set the :'delimiter-precedes-last' option to :always
556
+ # @return [self] self
557
+ def delimiter_always_precedes_last!
558
+ options[:'delimiter-precedes-last'] = :always
559
+ self
560
+ end
561
+
562
+ alias delimiter_precedes_last! delimiter_always_precedes_last!
563
+
564
+
565
+ # @return [Boolean] whether or not the should never be inserted between
566
+ # the penultimate and the last name
567
+ def delimiter_never_precedes_last?
568
+ !!(options[:'delimiter-precedes-last'].to_s =~ /^never$/i)
569
+ end
570
+
571
+ # Set the :'delimiter-precedes-last' option to :never
572
+ # @return [self] self
573
+ def delimiter_never_precedes_last!
574
+ options[:'delimiter-precedes-last'] = :never
575
+ self
576
+ end
577
+
578
+ # @return [Boolean] whether or not the should be inserted between the
579
+ # penultimate and the last name depending on the number of names
580
+ def delimiter_contextually_precedes_last?
581
+ !!(options[:'delimiter-precedes-last'].to_s =~ /^contextual/i)
582
+ end
583
+
584
+ # Set the :'delimiter-precedes-last' option to :contextual
585
+ # @return [self] self
586
+ def delimiter_contextually_precedes_last!
587
+ options[:'delimiter-precedes-last'] = :contextual
588
+ self
589
+ end
590
+
591
+ # @return [String] the connector between the penultimate and the last name
592
+ def connector
593
+ options[:and]
594
+ end
595
+
596
+ # @return [false] Names are non-numeric Variables
597
+ def numeric?
598
+ false
599
+ end
600
+
601
+ # Calls a block once for each name. If no block is given, an enumerator
602
+ # is returned instead.
603
+ #
604
+ # @yieldparam name [Name] a name in the list
605
+ # @return [self,Enumerator] self or an enumerator if no block is given
606
+ def each
607
+ if block_given?
608
+ names.each(&Proc.new)
609
+ self
610
+ else
611
+ to_enum
612
+ end
613
+ end
614
+
615
+ # Compares two lists of Names.
616
+ # @param other [(Name)] a list of names
617
+ # @return [Fixnum,nil] -1, 0, or 1 depending on the result of the
618
+ # comparison; or nil if the two objects cannot be compared
619
+ def <=>(other)
620
+ return nil unless other.respond_to?(:to_a)
621
+ to_a <=> other.to_a
622
+ end
623
+
624
+ # Converts the list of names into a formatted string depending on the
625
+ # current formatting options.
626
+ # @return [String] the formatted list of names
627
+ def to_s
628
+ case
629
+ when truncate?
630
+ [names[0...max_names].join(delimiter), options[:'et-al']].join(truncated_delimiter)
631
+ when length < 2
632
+ names.join(last_delimiter)
633
+ else
634
+ [names[0...-1].join(delimiter), names[-1]].join(last_delimiter)
635
+ end
636
+ end
637
+
638
+ # @return [String] the names in a BibTeX-compatible format
639
+ def to_bibtex
640
+ map { |n| n.dup.sort_order! }.join(' and ')
641
+ end
642
+
643
+ # @return [Array<Hash>] the list of names converted to hash objects
644
+ def to_citeproc
645
+ map(&:to_citeproc)
646
+ end
647
+
648
+ # @return [String] a human-readable representation of the Names object
649
+ def inspect
650
+ "#<CiteProc::Names #{to_s.inspect}>"
651
+ end
652
+
653
+ end
654
+
556
655
  end