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
@@ -0,0 +1,208 @@
|
|
1
|
+
module CiteProc
|
2
|
+
|
3
|
+
# CitationItems consititue the main input elements to CiteProc's processing
|
4
|
+
# methods. In order to be processed correctly, an item must contain a valid
|
5
|
+
# id attribute used to retrieve the item's bibliographic data. Additionally,
|
6
|
+
# an item may include the following, optional, attributes:
|
7
|
+
#
|
8
|
+
# * locator: a string identifying a page number or other pinpoint location
|
9
|
+
# or range within the resource;
|
10
|
+
#
|
11
|
+
# * label: a label type, indicating whether the locator is to a page, a
|
12
|
+
# chapter, or other subdivision of the target resource. Valid labels are
|
13
|
+
# defined in CitationItem.labels
|
14
|
+
#
|
15
|
+
# * suppress-author: if true, author names will not be included in the
|
16
|
+
# citation output for this cite;
|
17
|
+
#
|
18
|
+
# * author-only: if true, only the author name will be included in the
|
19
|
+
# citation output for this cite -- this optional parameter provides a
|
20
|
+
# means for certain demanding styles that require the processor output
|
21
|
+
# to be divided between the main text and a footnote.
|
22
|
+
#
|
23
|
+
# * prefix: a string to print before this cite item;
|
24
|
+
#
|
25
|
+
# * suffix: a string to print after this cite item.
|
26
|
+
#
|
27
|
+
class CitationItem
|
28
|
+
|
29
|
+
include Attributes
|
30
|
+
|
31
|
+
@labels = [
|
32
|
+
:book, :chapter, :column, :figure, :folio, :issue, :line, :note, :opus,
|
33
|
+
:page, :paragraph, :part, :section, :'sub-verbo', :verse, :volume
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
class << self
|
37
|
+
attr_reader :labels
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_predicates :id, :locator, :label, :'suppress-author',
|
41
|
+
:'author-only', :prefix, :suffix
|
42
|
+
|
43
|
+
# Added by processor
|
44
|
+
attr_predicates :sortkeys, :postion, :'first-reference-note-number',
|
45
|
+
:'near-note', :unsorted
|
46
|
+
|
47
|
+
attr_accessor :data
|
48
|
+
|
49
|
+
def initialize(attributes = nil)
|
50
|
+
merge(attributes)
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize_copy(other)
|
54
|
+
@attributes = other.attributes.deep_copy
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#<CiteProc::CitationItem #{id.to_s}, #{locator.to_s} >"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
class CitationData
|
67
|
+
|
68
|
+
extend Forwardable
|
69
|
+
include Enumerable
|
70
|
+
|
71
|
+
@defaults = {
|
72
|
+
:footnote => 0
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
@rb2cp = {
|
76
|
+
:id => 'citationID',
|
77
|
+
:items => 'citationItems',
|
78
|
+
:sorted_items => 'sortedItems',
|
79
|
+
:footnote => 'noteIndex',
|
80
|
+
:options => 'properties'
|
81
|
+
}
|
82
|
+
|
83
|
+
@cp2rb = @rb2cp.invert.freeze
|
84
|
+
@rb2cp.freeze
|
85
|
+
|
86
|
+
class << self
|
87
|
+
attr_reader :defaults, :cp2rb, :rb2cp
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_accessor :id
|
91
|
+
|
92
|
+
attr_reader :items, :options, :sorted_items
|
93
|
+
|
94
|
+
alias properties options
|
95
|
+
|
96
|
+
def_delegators :@items, :length, :empty?, :[]
|
97
|
+
|
98
|
+
# Some delegators should return self
|
99
|
+
[:push, :<<, :unshift, :concat].each do |m|
|
100
|
+
define_method(m) do |*arguments|
|
101
|
+
names.send(m, *arguments)
|
102
|
+
self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def each
|
107
|
+
if block_given?
|
108
|
+
items.each(&Proc.new)
|
109
|
+
self
|
110
|
+
else
|
111
|
+
to_enum
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def initialize(attributes = nil, options = {})
|
117
|
+
@options = CitationData.defaults.merge(options)
|
118
|
+
@items, @sorted_items = [], []
|
119
|
+
merge(attributes)
|
120
|
+
end
|
121
|
+
|
122
|
+
def initialize_copy(other)
|
123
|
+
@options = other.options.dup
|
124
|
+
@items = other.items.map(&:dup)
|
125
|
+
@sorted_items = other.items.map(&:dup)
|
126
|
+
@id = other.id.dup if other.processed?
|
127
|
+
end
|
128
|
+
|
129
|
+
def merge(other)
|
130
|
+
return self if other.nil?
|
131
|
+
|
132
|
+
case other
|
133
|
+
when String, /^\s*\{/
|
134
|
+
other = MulitJson.decode(other, :symbolize_keys => true)
|
135
|
+
when Hash
|
136
|
+
# do nothing
|
137
|
+
when Array
|
138
|
+
other = { :items => other }
|
139
|
+
when Attributes
|
140
|
+
other = other.to_hash
|
141
|
+
else
|
142
|
+
raise ParseError, "failed to merge citation data and #{other.inspect}"
|
143
|
+
end
|
144
|
+
|
145
|
+
other = convert_from_citeproc(other)
|
146
|
+
|
147
|
+
items.concat(Array(other.delete(:items)).map { |i| CitationItem.create!(i) })
|
148
|
+
sorted_items.concat(Array(other.delete(:sorted_items)))
|
149
|
+
|
150
|
+
properties = other.delete(:options)
|
151
|
+
options.merge!(convert_from_citeproc(Hash[properties])) unless properties.nil?
|
152
|
+
|
153
|
+
@id = other[:id] if other.has_key?(:id)
|
154
|
+
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
alias update merge
|
159
|
+
|
160
|
+
def processed?
|
161
|
+
!!id
|
162
|
+
end
|
163
|
+
|
164
|
+
def index
|
165
|
+
options[:footnote]
|
166
|
+
end
|
167
|
+
|
168
|
+
def footnote?
|
169
|
+
options[:footnote] > 0
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_citeproc
|
173
|
+
cp = {}
|
174
|
+
|
175
|
+
cp[CitationData.rb2cp[:items]] = items.map(&:to_citeproc)
|
176
|
+
cp[CitationData.rb2cp[:options]] = { CitationData.rb2cp[:footnote] => index }
|
177
|
+
|
178
|
+
cp[CitationData.rb2cp[:id]] = id if processed?
|
179
|
+
|
180
|
+
cp
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_json
|
184
|
+
MultiJson.encode(to_citeproc)
|
185
|
+
end
|
186
|
+
|
187
|
+
alias to_s to_json
|
188
|
+
|
189
|
+
def inspect
|
190
|
+
"#<CiteProc::CitationData items=[#{length}]>"
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def convert_from_citeproc(hash)
|
196
|
+
hash = hash.symbolize_keys
|
197
|
+
|
198
|
+
CitationData.cp2rb.each do |cp, rb|
|
199
|
+
cp = cp.to_sym
|
200
|
+
hash[rb] = hash.delete(cp) if hash.has_key?(cp)
|
201
|
+
end
|
202
|
+
|
203
|
+
hash
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
data/lib/citeproc/date.rb
CHANGED
@@ -1,166 +1,229 @@
|
|
1
1
|
|
2
|
-
require 'date'
|
3
|
-
|
4
|
-
begin
|
5
|
-
require 'chronic'
|
6
|
-
rescue LoadError => e
|
7
|
-
# warn 'failed to load chronic gem'
|
8
|
-
end
|
9
|
-
|
10
2
|
module CiteProc
|
11
3
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
4
|
+
class Date < Variable
|
5
|
+
|
6
|
+
include Attributes
|
7
|
+
|
8
|
+
alias attributes value
|
9
|
+
private :attributes, :value=
|
10
|
+
|
11
|
+
|
12
|
+
# Date parsers (must respond to :parse)
|
13
|
+
@parsers = []
|
14
|
+
|
15
|
+
require 'date'
|
16
|
+
@parsers << ::Date
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'chronic'
|
20
|
+
@parsers << Chronic
|
21
|
+
rescue LoadError
|
22
|
+
# warn 'failed to load chronic gem'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Format string used for sorting dates
|
26
|
+
@sort_order = "%04d%02d%02d-%04d%02d%02d".freeze
|
27
|
+
|
28
|
+
class << self
|
29
|
+
|
30
|
+
attr_reader :parsers, :sort_order
|
31
|
+
|
32
|
+
# Parses the passed-in string with all available date parsers. Creates
|
33
|
+
# a new CiteProc Date from the first valid date returned by a parser;
|
34
|
+
# returns nil if no parser was able to parse the string successfully.
|
35
|
+
#
|
36
|
+
# For an equivalent method that raises an error on invalid input
|
37
|
+
# @see #parse!
|
38
|
+
def parse(date_string)
|
39
|
+
parse!(date_string)
|
40
|
+
rescue ParseError
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Like #parse but raises a ParseError if the input failed to be parsed.
|
45
|
+
def parse!(date_string)
|
46
|
+
@parsers.each do |p|
|
47
|
+
d = p.parse(date_string) rescue nil
|
48
|
+
return new(d) unless d.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# if we get here, all parsers failed
|
52
|
+
raise ParseError, "failed to parse #{date_string.inspect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def today
|
56
|
+
new(::Date.today)
|
57
|
+
end
|
58
|
+
|
59
|
+
alias now today
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_predicates :circa, :season, :literal, :'date-parts'
|
64
|
+
|
65
|
+
# Make Date behave like a regular Ruby Date
|
66
|
+
def_delegators :to_ruby,
|
67
|
+
*::Date.instance_methods(false).reject { |m| m.to_s =~ /^to_s$|^inspect$|start$|^\W/ }
|
68
|
+
|
69
|
+
|
70
|
+
def initialize(value = ::Date.today)
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize_copy(other)
|
75
|
+
@value = other.value.deep_copy
|
76
|
+
end
|
77
|
+
|
78
|
+
def replace(value)
|
79
|
+
case
|
80
|
+
when value.is_a?(CiteProc::Date)
|
81
|
+
initialize_copy(value)
|
82
|
+
|
83
|
+
when value.is_a?(Numeric)
|
84
|
+
@value = { :'date-parts' => [[value.to_i]] }
|
85
|
+
|
86
|
+
when value.is_a?(Hash)
|
87
|
+
attributes = value.symbolize_keys
|
88
|
+
|
89
|
+
if attributes.has_key?(:raw)
|
90
|
+
@value = Date.parse(attributes.delete(:raw)).value
|
91
|
+
@value.merge!(attributes)
|
92
|
+
else
|
93
|
+
@value = attributes.deep_copy
|
94
|
+
end
|
95
|
+
to_i!
|
96
|
+
|
97
|
+
when value.respond_to?(:strftime)
|
98
|
+
@value = { :'date-parts' => [value.strftime('%Y-%m-%d').split(/-/).map(&:to_i)] }
|
99
|
+
|
100
|
+
when value.is_a?(Array)
|
101
|
+
@value = { :'date-parts' => value[0].is_a?(Array) ? value : [value] }
|
102
|
+
to_i!
|
103
|
+
|
104
|
+
when value.respond_to?(:to_s)
|
105
|
+
@value = Date.parse(value.to_s).value
|
106
|
+
|
107
|
+
else
|
108
|
+
raise TypeError, "failed to create date from #{value.inspect}"
|
109
|
+
end
|
110
|
+
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO replace the date parts by two proper dates or structs
|
115
|
+
|
116
|
+
def date_parts
|
117
|
+
@value[:'date-parts'] ||= [[]]
|
118
|
+
end
|
119
|
+
|
120
|
+
alias parts date_parts
|
121
|
+
alias parts= date_parts=
|
122
|
+
|
123
|
+
def empty?
|
124
|
+
parts.flatten.compact.empty?
|
125
|
+
end
|
126
|
+
|
127
|
+
%w{ year month day }.each_with_index do |m,i|
|
128
|
+
define_method(m) { parts[0][i] }
|
129
|
+
define_method("#{m}=") { |v| parts[0][i] = v.to_i }
|
130
|
+
end
|
131
|
+
|
132
|
+
def -@
|
133
|
+
d = dup
|
134
|
+
d.year = -1 * year
|
135
|
+
d
|
136
|
+
end
|
137
|
+
|
138
|
+
def start_date
|
139
|
+
::Date.new(*parts[0])
|
140
|
+
end
|
141
|
+
|
142
|
+
def start_date=(date)
|
143
|
+
parts[0] = date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
|
144
|
+
end
|
145
|
+
|
146
|
+
def end_date=(date)
|
147
|
+
parts[1] = date.nil? ? [0,0,0] : date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a Ruby date object for this instance, or Range object if this
|
151
|
+
# instance is closed range
|
152
|
+
def to_ruby
|
153
|
+
closed_range? ? start_date ... end_date : start_date
|
154
|
+
end
|
155
|
+
|
156
|
+
def end_date
|
157
|
+
closed_range? ? ::Date.new(*parts[1]) : nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def has_end_date?
|
161
|
+
parts[1] && !parts[1].empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns true if this date is a range
|
165
|
+
alias range? has_end_date?
|
166
|
+
|
167
|
+
def open_range?
|
168
|
+
range? && parts[1].uniq == [0]
|
169
|
+
end
|
170
|
+
|
171
|
+
alias open? open_range?
|
172
|
+
|
173
|
+
def closed_range?
|
174
|
+
range? && !open_range?
|
175
|
+
end
|
176
|
+
|
177
|
+
alias closed? closed_range?
|
178
|
+
|
179
|
+
alias uncertain? circa?
|
180
|
+
|
181
|
+
# Marks the date as uncertain
|
182
|
+
def uncertain!
|
183
|
+
@value[:circa] = true
|
184
|
+
end
|
185
|
+
|
186
|
+
# Marks the date as a certain date
|
187
|
+
def certain!
|
188
|
+
@value[:circa] = false
|
189
|
+
end
|
190
|
+
|
191
|
+
def certain?
|
192
|
+
!uncertain?
|
193
|
+
end
|
194
|
+
|
195
|
+
def numeric?
|
196
|
+
false
|
197
|
+
end
|
198
|
+
|
199
|
+
def bc?; year and year < 0; end
|
200
|
+
def ad?; not bc? and year < 1000; end
|
201
|
+
|
202
|
+
def to_citeproc
|
203
|
+
cp = @value.stringify_keys
|
204
|
+
cp.delete('date-parts') if empty?
|
205
|
+
cp
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_s
|
209
|
+
literal? ? literal : to_ruby.to_s
|
210
|
+
end
|
211
|
+
|
212
|
+
def sort_order
|
213
|
+
Date.sort_order % ((parts[0] + [0,0,0])[0,3] + ((parts[1] || []) + [0,0,0])[0,3])
|
214
|
+
end
|
215
|
+
|
216
|
+
def <=>(other)
|
217
|
+
return nil unless other.is_a?(Date)
|
218
|
+
[year, sort_order] <=> [other.year, other.sort_order]
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def to_i!
|
224
|
+
parts.each { |p| p.map!(&:to_i) }
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
166
229
|
end
|