citeproc 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/README.md +2 -0
- data/auto.watchr +2 -0
- data/lib/citeproc.rb +16 -0
- data/lib/citeproc/assets.rb +66 -0
- data/lib/citeproc/attributes.rb +92 -16
- data/lib/citeproc/bibliography.rb +180 -0
- data/lib/citeproc/citation_data.rb +208 -0
- data/lib/citeproc/compatibility.rb +5 -1
- data/lib/citeproc/date.rb +225 -162
- data/lib/citeproc/engine.rb +6 -11
- data/lib/citeproc/errors.rb +5 -4
- data/lib/citeproc/extensions.rb +71 -3
- data/lib/citeproc/item.rb +113 -0
- data/lib/citeproc/names.rb +556 -0
- data/lib/citeproc/processor.rb +30 -4
- data/lib/citeproc/selector.rb +128 -0
- data/lib/citeproc/variable.rb +85 -45
- data/lib/citeproc/version.rb +1 -1
- data/spec/citeproc/assets_spec.rb +60 -0
- data/spec/citeproc/attributes_spec.rb +23 -10
- data/spec/citeproc/bibliography_spec.rb +72 -0
- data/spec/citeproc/citation_data_spec.rb +94 -0
- data/spec/citeproc/date_spec.rb +65 -9
- data/spec/citeproc/extensions_spec.rb +33 -0
- data/spec/citeproc/item_spec.rb +52 -0
- data/spec/citeproc/names_spec.rb +492 -0
- data/spec/citeproc/processor_spec.rb +39 -0
- data/spec/citeproc/selector_spec.rb +81 -0
- data/spec/citeproc/variable_spec.rb +9 -3
- metadata +133 -121
data/lib/citeproc/engine.rb
CHANGED
@@ -43,7 +43,7 @@ module CiteProc
|
|
43
43
|
|
44
44
|
# Returns the best available engine class or nil.
|
45
45
|
def autodetect(options = {})
|
46
|
-
|
46
|
+
subclasses.detect { |e|
|
47
47
|
!options.has_key?(:engine) || e.name == options[:engine] and
|
48
48
|
!options.has_key?(:name) || e.name == options[:name]
|
49
49
|
} || subclasses.first
|
@@ -52,7 +52,7 @@ module CiteProc
|
|
52
52
|
# Loads the engine by requiring the engine name.
|
53
53
|
def load(name)
|
54
54
|
require name.gsub(/-/,'/')
|
55
|
-
rescue LoadError
|
55
|
+
rescue LoadError
|
56
56
|
warn "failed to load #{name} engine: try to gem install #{name}"
|
57
57
|
end
|
58
58
|
|
@@ -70,14 +70,14 @@ module CiteProc
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
attr_accessor :processor, :locales, :style
|
74
|
-
|
75
|
-
def_delegators :@processor, :items, :options
|
76
|
-
|
73
|
+
attr_accessor :processor, :locales, :style, :items
|
77
74
|
|
78
75
|
def initialize(attributes = {})
|
79
76
|
@processor = attributes[:processor]
|
77
|
+
|
78
|
+
@items = attributes[:items] || {}
|
80
79
|
@abbreviations = attributes[:abbreviations] || { :default => {} }
|
80
|
+
|
81
81
|
yield self if block_given?
|
82
82
|
end
|
83
83
|
|
@@ -113,11 +113,6 @@ module CiteProc
|
|
113
113
|
|
114
114
|
alias append_citation_cluster append
|
115
115
|
|
116
|
-
def preview
|
117
|
-
raise NotImplementedByEngine
|
118
|
-
end
|
119
|
-
|
120
|
-
alias preview_citation_cluster preview
|
121
116
|
|
122
117
|
def bibliography
|
123
118
|
raise NotImplementedByEngine
|
data/lib/citeproc/errors.rb
CHANGED
@@ -3,15 +3,16 @@ module CiteProc
|
|
3
3
|
class Error < StandardError
|
4
4
|
attr_reader :original
|
5
5
|
|
6
|
-
def initialize(message, original =
|
7
|
-
@original = original
|
6
|
+
def initialize(message, original = $!)
|
7
|
+
@original = original
|
8
|
+
super(message)
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
12
|
+
class ParseError < Error; end
|
13
|
+
|
11
14
|
class EngineError < Error; end
|
12
15
|
|
13
16
|
class NotImplementedByEngine < EngineError; end
|
14
17
|
|
15
|
-
class ArgumentError < Error; end
|
16
|
-
|
17
18
|
end
|
data/lib/citeproc/extensions.rb
CHANGED
@@ -2,11 +2,23 @@
|
|
2
2
|
module CiteProc
|
3
3
|
module Extensions
|
4
4
|
|
5
|
+
module Underscore
|
6
|
+
def underscore(word)
|
7
|
+
word = word.to_s.dup
|
8
|
+
word.gsub!(/::/, '/')
|
9
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
10
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
11
|
+
word.tr!('-', '_')
|
12
|
+
word.downcase!
|
13
|
+
word
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
5
17
|
module DeepCopy
|
6
18
|
def deep_copy
|
7
19
|
Hash[*map { |k,v| [
|
8
|
-
|
9
|
-
|
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
|
10
22
|
]}.flatten(1)]
|
11
23
|
end
|
12
24
|
end
|
@@ -35,8 +47,50 @@ module CiteProc
|
|
35
47
|
def symbolize_keys!
|
36
48
|
replace(symbolize_keys)
|
37
49
|
end
|
50
|
+
|
38
51
|
end
|
52
|
+
|
53
|
+
module StringifyKeys
|
54
|
+
def stringify_keys
|
55
|
+
inject({}) do |options, (key, value)|
|
56
|
+
options[(key.to_s rescue key) || key] = value
|
57
|
+
options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def stringify_keys!
|
62
|
+
replace(symbolize_keys)
|
63
|
+
end
|
64
|
+
end
|
39
65
|
|
66
|
+
module CompactJoin
|
67
|
+
def compact_join(delimiter = ' ')
|
68
|
+
reject { |t| t.nil? || (t.respond_to?(:empty?) && t.empty?) }.join(delimiter)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# based and compatible to the active support version
|
73
|
+
# module ToSentence
|
74
|
+
# def to_sentence(options = {})
|
75
|
+
# options = {
|
76
|
+
# :words_connector => ", ",
|
77
|
+
# :two_words_connector => " and ",
|
78
|
+
# :last_word_connector => ", and "
|
79
|
+
# }.merge!(options)
|
80
|
+
#
|
81
|
+
# case length
|
82
|
+
# when 0
|
83
|
+
# ""
|
84
|
+
# when 1
|
85
|
+
# self[0].to_s.dup
|
86
|
+
# when 2
|
87
|
+
# "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
|
88
|
+
# else
|
89
|
+
# "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
|
40
94
|
module AliasMethods
|
41
95
|
private
|
42
96
|
def alias_methods(*arguments)
|
@@ -49,9 +103,23 @@ module CiteProc
|
|
49
103
|
end
|
50
104
|
|
51
105
|
class Hash
|
106
|
+
warn "citeproc: re-defining Hash#deep_copy, this may cause conflicts with other libraries" if method_defined?(:deep_copy)
|
52
107
|
include CiteProc::Extensions::DeepCopy
|
108
|
+
|
109
|
+
warn "citeproc: re-defining Hash#deep_copy, this may cause conflicts with other libraries" if method_defined?(:deep_fetch)
|
53
110
|
include CiteProc::Extensions::DeepFetch
|
54
|
-
|
111
|
+
|
112
|
+
include CiteProc::Extensions::SymbolizeKeys unless method_defined?(:symbolize_keys)
|
113
|
+
include CiteProc::Extensions::StringifyKeys unless method_defined?(:stringify_keys)
|
114
|
+
end
|
115
|
+
|
116
|
+
class Array
|
117
|
+
include CiteProc::Extensions::CompactJoin
|
118
|
+
# include CiteProc::Extensions::ToSentence unless method_defined?(:to_sentence)
|
119
|
+
end
|
120
|
+
|
121
|
+
class String
|
122
|
+
include CiteProc::Extensions::Underscore unless method_defined?(:underscore)
|
55
123
|
end
|
56
124
|
|
57
125
|
# module Kernel
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module CiteProc
|
2
|
+
|
3
|
+
# Items are similar to a Ruby Hash but pose a number of constraints on their
|
4
|
+
# contents: keys are always (implicitly converted to) symbols and values
|
5
|
+
# are strictly instances of CiteProc::Variable. When Items are constructed
|
6
|
+
# from (or merged with) JSON objects or Hashes Variable instances are
|
7
|
+
# automatically created using by passing the variables key as type to
|
8
|
+
# Variable.create – this will create the expected Variable type for all
|
9
|
+
# fields defined in CSL (for example, the `issued' field will become a
|
10
|
+
# CiteProc::Date object); unknown types will be converted to simple
|
11
|
+
# CiteProc::Variable instances, which should be fine for numeric or string
|
12
|
+
# values but may cause problems for more complex types.
|
13
|
+
#
|
14
|
+
# Every Item provides accessor methods for all known field names; unknown
|
15
|
+
# fields can still be accessed using array accessor syntax.
|
16
|
+
#
|
17
|
+
# i = Item.new(:edition => 3, :unknown_field => 42)
|
18
|
+
# i.edition -> #<CiteProc::Number "3">
|
19
|
+
# i[:edition] -> #<CiteProc::Number "3">
|
20
|
+
# i[:unknown_field] -> #<CiteProc::Variable "42">
|
21
|
+
#
|
22
|
+
# Items can be converted to the CiteProc JSON format via #to_citeproc (or
|
23
|
+
# #to_json to get a JSON string).
|
24
|
+
class Item
|
25
|
+
|
26
|
+
@types = [
|
27
|
+
:article, :'article-journal', :'article-magazine', :'article-newspaper',
|
28
|
+
:bill, :book, :broadcast, :chapter, :entry, :'entry-dictionary',
|
29
|
+
:'entry-encyclopedia', :figure, :graphic, :interview, :legal_case,
|
30
|
+
:legislation, :manuscript, :map, :motion_picture, :musical_score,
|
31
|
+
:pamphlet, :'paper-conference', :patent, :personal_communication, :post,
|
32
|
+
:'post-weblog', :report, :review, :'review-book', :song, :speech,
|
33
|
+
:thesis, :treaty, :webpage].freeze
|
34
|
+
|
35
|
+
@bibtex_types = Hash.new { |h,k| :misc }.merge(Hash[*%w{
|
36
|
+
pamphlet booklet
|
37
|
+
paper-conference conference
|
38
|
+
chapter inbook
|
39
|
+
chapter incollection
|
40
|
+
paper-conference inproceedings
|
41
|
+
book manual
|
42
|
+
thesis phdthesis
|
43
|
+
paper-conference proceedings
|
44
|
+
report techreport
|
45
|
+
manuscript unpublished
|
46
|
+
article article
|
47
|
+
article-journal article
|
48
|
+
article-magazine article
|
49
|
+
}.map(&:intern)]).freeze
|
50
|
+
|
51
|
+
class << self
|
52
|
+
attr_reader :types, :bibtex_types
|
53
|
+
end
|
54
|
+
|
55
|
+
include Attributes
|
56
|
+
include Enumerable
|
57
|
+
|
58
|
+
attr_predicates :id, :'short-title', :'journal-abbreviation',
|
59
|
+
*Variable.fields[:all]
|
60
|
+
|
61
|
+
def initialize(attributes = nil)
|
62
|
+
merge(attributes)
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize_copy(other)
|
66
|
+
@attributes = other.attributes.deep_copy
|
67
|
+
end
|
68
|
+
|
69
|
+
# Don't expose attributes. Items need to mimic Hash functionality in a controlled way.
|
70
|
+
private :attributes
|
71
|
+
|
72
|
+
def each
|
73
|
+
if block_given?
|
74
|
+
attributes.each(&Proc.new)
|
75
|
+
self
|
76
|
+
else
|
77
|
+
to_enum
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns a corresponding BibTeX::Entry if the bibtex-ruby gem is installed;
|
82
|
+
# otherwise returns a BibTeX string.
|
83
|
+
def to_bibtex
|
84
|
+
# hash = to_hash
|
85
|
+
#
|
86
|
+
# hash[:type] = Item.bibtex_types[hash[:type]]
|
87
|
+
#
|
88
|
+
# if hash.has_key?(:issued)
|
89
|
+
# date = hash.delete(:issued)
|
90
|
+
# hash[:year] = date.year
|
91
|
+
# hash[:month] = date.month
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Variable.fields[:date].each do |field|
|
95
|
+
# hash[field] = hash[field].to_s if hash.has_key?(field)
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# Variable.fields[:names].each do |field|
|
99
|
+
# hash[field] = hash[field].map(&:sort_order!).join(' and ')
|
100
|
+
# end
|
101
|
+
|
102
|
+
raise 'not implemented yet'
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def filter_value(value, key)
|
108
|
+
Variable.create!(value, key)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,556 @@
|
|
1
|
+
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-]*$/
|
48
|
+
|
49
|
+
# Class instance variables
|
50
|
+
|
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|
|
89
|
+
alias_method(a, m) if method_defined?(m)
|
90
|
+
|
91
|
+
wa, wm = "#{a}=", "#{m}="
|
92
|
+
alias_method(wa, wm) if method_defined?(wm)
|
93
|
+
|
94
|
+
pa, pm = "#{a}?", "#{m}?"
|
95
|
+
alias_method(pa, pm) if method_defined?(pm)
|
96
|
+
|
97
|
+
pa, pm = "has_#{a}?", "has_#{m}?"
|
98
|
+
alias_method(pa, pm) if method_defined?(pm)
|
99
|
+
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
|
+
|
556
|
+
end
|