citeproc 0.0.2 → 0.0.3
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.
- 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
|