citeproc 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -2,8 +2,12 @@ unless Symbol.is_a?(Comparable)
2
2
  class Symbol
3
3
  include Comparable
4
4
 
5
+ def =~(pattern)
6
+ to_s =~ pattern
7
+ end
8
+
5
9
  def <=>(other)
6
- return nil unless other.respond_to?(:to_s)
10
+ return nil unless other.is_a?(Symbol)
7
11
  to_s <=> other.to_s
8
12
  end
9
13
  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
- class Date < Variable
13
-
14
- include Attributes
15
-
16
- alias attributes value
17
- alias to_hash value
18
-
19
- @parser = Object.const_defined?(:Chronic) ? ::Chronic : ::Date
20
-
21
- class << self
22
-
23
- def parse(date_string)
24
- new(@parser.parse(date_string))
25
- rescue => e
26
- raise ArgumentError.new("failed to parse date from #{date_string.inspect}", e)
27
- end
28
-
29
- def today
30
- new(::Date.today)
31
- end
32
-
33
- alias now today
34
-
35
- end
36
-
37
-
38
- def initialize(attributes = ::Date.today)
39
- super
40
- end
41
-
42
- def initialize_copy(other)
43
- @value = other.value.deep_copy
44
- end
45
-
46
- def parse(attributes)
47
- case
48
- when attributes.is_a?(Date)
49
- @value = attributes.dup
50
-
51
- when attributes.is_a?(Numeric)
52
- @value = { :'date-parts' => [[attributes.to_i]] }
53
-
54
- when attributes.is_a?(Hash)
55
- attributes = attributes.symbolize_keys
56
- if attributes.has_key?(:raw)
57
- @value = Date.parse(attributes.delete(:raw)).value
58
- @value.merge!(attributes)
59
- else
60
- @value = attributes.deep_copy
61
- end
62
- to_i!
63
-
64
- when attributes.respond_to?(:strftime)
65
- @value = { :'date-parts' => [attributes.strftime('%Y-%m-%d').split(/-/).map(&:to_i)] }
66
-
67
- when attributes.is_a?(Array)
68
- @value = { :'date-parts' => attributes[0].is_a?(Array) ? attributes : [attributes] }
69
- to_i!
70
-
71
- when attributes.respond_to?(:to_s)
72
- @value = Date.parse(attributes.to_s).value
73
-
74
- else
75
- raise ArgumentError, "failed to parse date from #{attributes.inspect}"
76
- end
77
- end
78
-
79
- attr_predicates :circa, :season, :literal, :'date-parts'
80
-
81
- def date_parts
82
- @value[:'date-parts'] ||= [[]]
83
- end
84
-
85
- alias parts date_parts
86
- alias parts= date_parts=
87
-
88
- %w{ year month day }.each_with_index do |m,i|
89
- define_method(m) { parts[0][i] }
90
- define_method("#{m}=") { |v| parts[0][i] = v.to_i }
91
- end
92
-
93
- def start_date
94
- ::Date.new(*parts[0])
95
- end
96
-
97
- def start_date=(date)
98
- parts[0] = date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
99
- end
100
-
101
- def end_date=(date)
102
- parts[1] = date.nil? ? [0,0,0] : date.strftime('%Y-%m-%d').split(/-/).map(&:to_i)
103
- end
104
-
105
- # Returns a Ruby date object for this instance, or Range object if this
106
- # instance is closed range
107
- def to_ruby
108
- closed_range? ? start_date ... end_date : start_date
109
- end
110
-
111
- def end_date
112
- closed_range? ? ::Date.new(*parts[1]) : nil
113
- end
114
-
115
- def has_end_date?
116
- parts[1] && !parts[1].empty?
117
- end
118
-
119
- # Returns true if this date is a range
120
- alias range? has_end_date?
121
-
122
- def open_range?
123
- range? && parts[1].uniq == [0]
124
- end
125
-
126
- def closed_range?
127
- range? && !open_range?
128
- end
129
-
130
- alias uncertain? circa?
131
-
132
- # Marks the date as uncertain
133
- def uncertain!; @value[:'circa'] = true; end
134
-
135
- # Marks the date as a certain date
136
- def certain!; @value[:'circa'] = false; end
137
-
138
- def certain?; !uncertain?; end
139
-
140
- def numeric?; false; end
141
-
142
- def bc?; year && year < 0; end
143
- def ad?; !bc? && year < 1000; end
144
-
145
- def to_s
146
- literal? ? literal : @value.inspect
147
- end
148
-
149
- def sort_order
150
- "%04d%02d%02d-%04d%02d%02d" % ((parts[0] + [0,0,0])[0,3] + ((parts[1] || []) + [0,0,0])[0,3])
151
- end
152
-
153
- def <=>(other)
154
- return nil unless other.is_a?(Date)
155
- [year, sort_order] <=> [other.year, other.sort_order]
156
- end
157
-
158
- private
159
-
160
- def to_i!
161
- parts.each { |p| p.map!(&:to_i) }
162
- end
163
-
164
- end
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