bibtex-ruby 1.3.12 → 2.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bibtex-ruby might be problematic. Click here for more details.

@@ -28,13 +28,22 @@ module BibTeX
28
28
  attr_reader :bibliography
29
29
 
30
30
  # Returns an array of BibTeX elements.
31
- def self.parse(string, options = {})
32
- elements = BibTeX::Parser.new(options).parse(string).data
33
- elements.each do |e|
34
- e.parse_names unless !e.respond_to?(:parse_names) || options[:parse_names] == false
35
- e.parse_month unless !e.respond_to?(:parse_month) || options[:parse_months] == false
36
- end
37
- elements
31
+ def self.parse(input, options = {})
32
+ case input
33
+ when Element
34
+ [input]
35
+ when Hash
36
+ [Entry.new(input)]
37
+ when Array
38
+ input.inject([]) { |s,a| s.concat(parse(a, options)) }
39
+ when ::String
40
+ Parser.new(options).parse(input).data.each do |e|
41
+ e.parse_names unless !e.respond_to?(:parse_names) || options[:parse_names] == false
42
+ e.parse_month unless !e.respond_to?(:parse_month) || options[:parse_months] == false
43
+ end
44
+ else
45
+ raise BibTeXError, "failed to parse Element from #{input.inspect}"
46
+ end
38
47
  end
39
48
 
40
49
  # Returns a string containing the object's content.
@@ -47,24 +56,26 @@ module BibTeX
47
56
  def join; self; end
48
57
 
49
58
  # Returns the element's id.
50
- def id; @id ||= object_id.to_s.intern; end
59
+ def id; @id ||= object_id.to_s; end
51
60
 
52
61
  # Returns the BibTeX type (if applicable) or the normalized class name.
53
62
  def type
54
63
  self.class.name.split(/::/).last.gsub(/([[:lower:]])([[:upper:]])/) { "#{$1}_#{$2}" }.downcase.intern
55
64
  end
56
65
 
66
+ # Returns a list of names for that Element. All Elements except Entries return an empty list.
67
+ def names
68
+ []
69
+ end
70
+
57
71
  def has_type?(type)
58
72
  self.type == type.intern || defined?(type) == 'constant' && is_a?(type)
59
73
  end
60
74
 
61
75
  [:entry, :book, :article, :collection, :string, :preamble, :comment]. each do |type|
62
- method_id = "#{type}?"
63
- unless method_defined?(method_id)
64
- define_method(method_id) { has_type?(type) }
65
- alias_method("is_#{method_id}", method_id)
66
- end
67
- end
76
+ method_id = "#{type}?"
77
+ define_method(method_id) { has_type?(type) } unless method_defined?(method_id)
78
+ end
68
79
 
69
80
  # Returns true if the element matches the given query.
70
81
  def matches?(query)
@@ -72,7 +83,7 @@ module BibTeX
72
83
 
73
84
  case query
74
85
  when Symbol
75
- query == id
86
+ query.to_s == id.to_s
76
87
  when Element
77
88
  query == self
78
89
  when Regexp
@@ -84,7 +95,7 @@ module BibTeX
84
95
  when /^\/(.+)\/$/
85
96
  to_s.match(Regexp.new($1))
86
97
  else
87
- id == query.to_sym
98
+ id.to_s == query
88
99
  end
89
100
  end
90
101
 
@@ -95,7 +106,7 @@ module BibTeX
95
106
  def meets?(*conditions)
96
107
  conditions.flatten.all? do |condition|
97
108
  property, value = condition.split(/\s*=\s*/)
98
- property.nil? || send(property).to_s == value
109
+ property.nil? || (respond_to?(property) && send(property).to_s == value)
99
110
  end
100
111
  end
101
112
 
@@ -105,7 +116,7 @@ module BibTeX
105
116
 
106
117
  def to_hash(options = {})
107
118
  { type => content }
108
- end
119
+ end
109
120
 
110
121
  def to_yaml(options = {})
111
122
  require 'yaml'
@@ -113,8 +124,7 @@ module BibTeX
113
124
  end
114
125
 
115
126
  def to_json(options = {})
116
- require 'json'
117
- to_hash.to_json
127
+ MultiJson.encode(to_hash(options))
118
128
  end
119
129
 
120
130
  def to_xml(options = {})
@@ -126,6 +136,7 @@ module BibTeX
126
136
 
127
137
  # Called when the element was added to a bibliography.
128
138
  def added_to_bibliography(bibliography)
139
+ # raise BibTeXError, "failed to add element to Bibliography: already registered with another Bibliography" unless @bibliography.nil?
129
140
  @bibliography = bibliography
130
141
  self
131
142
  end
@@ -141,6 +152,10 @@ module BibTeX
141
152
  [type, to_s] <=> [other.type, other.to_s]
142
153
  end
143
154
 
155
+ # Returns the Element as a nicely formatted string.
156
+ def inspect
157
+ "#<#{self.class} #{content.gsub(/\n/, ' ')}>"
158
+ end
144
159
  end
145
160
 
146
161
 
@@ -169,11 +184,11 @@ module BibTeX
169
184
  def key=(key)
170
185
  raise(ArgumentError, "keys must be convertible to Symbol; was: #{type.class.name}.") unless type.respond_to?(:to_sym)
171
186
 
172
- unless @bibliography.nil?
173
- @bibliography.strings.delete(@key)
174
- @bibliography.strings[key.to_sym] = self
175
- end
176
-
187
+ unless bibliography.nil?
188
+ bibliography.strings.delete(@key)
189
+ bibliography.strings[key.to_sym] = self
190
+ end
191
+
177
192
  @key = key.to_sym
178
193
  end
179
194
 
@@ -214,11 +229,15 @@ module BibTeX
214
229
  def to_xml(options = {})
215
230
  require 'rexml/document'
216
231
 
217
- xml = REXML::Element.new(:string)
218
- key = REXML::Element.new(:key)
219
- val = REXML::Element.new(:value)
220
- key.text = @key.to_s
221
- val.text = @value.to_s(:quotes => '"')
232
+ xml = REXML::Element.new(:string)
233
+
234
+ k, v = REXML::Element.new(:key), REXML::Element.new(:value)
235
+ k.text = key.to_s
236
+ v.text = value.to_s(:quotes => '"')
237
+
238
+ xml.add_elements(k)
239
+ xml.add_elements(v)
240
+
222
241
  xml
223
242
  end
224
243
  end
@@ -26,7 +26,7 @@ module BibTeX
26
26
  extend Forwardable
27
27
  include Enumerable
28
28
 
29
- # Hash containing the required fields of the standard entry types
29
+ # Defines the required fields of the standard entry types
30
30
  REQUIRED_FIELDS = Hash.new([]).merge({
31
31
  :article => [:author,:title,:journal,:year],
32
32
  :book => [[:author,:editor],:title,:publisher,:year],
@@ -44,6 +44,13 @@ module BibTeX
44
44
  :unpublished => [:author,:title,:note]
45
45
  }).freeze
46
46
 
47
+ # Defines the default fallbacks for values defined in cross-references
48
+ FIELD_ALIASES = {
49
+ :booktitle => :title,
50
+ # :editor => :author
51
+ }.freeze
52
+
53
+
47
54
  NAME_FIELDS = [:author,:editor,:translator].freeze
48
55
  DATE_FIELDS = [:year,:month].freeze
49
56
 
@@ -102,13 +109,14 @@ module BibTeX
102
109
  }.map(&:intern)]).freeze
103
110
 
104
111
 
105
- attr_reader :fields, :type
112
+ attr_reader :fields, :type
113
+
106
114
  def_delegators :@fields, :empty?, :each, :each_pair
107
115
 
108
116
  # Creates a new instance. If a hash is given, the entry is populated accordingly.
109
117
  def initialize(attributes = {})
110
118
  @fields = {}
111
-
119
+
112
120
  self.type = attributes.delete(:type) if attributes.has_key?(:type)
113
121
  self.key = attributes.delete(:key) if attributes.has_key?(:key)
114
122
 
@@ -125,17 +133,29 @@ module BibTeX
125
133
 
126
134
  add(other.fields)
127
135
  end
128
-
129
- # Sets the key of the entry
136
+
137
+ # Returns the Entry's field name aliases.
138
+ def aliases
139
+ @aliases ||= FIELD_ALIASES.dup
140
+ end
141
+
142
+ # Sets the Entry's key. If the Entry is currently registered with a
143
+ # Bibliography, re-registers the Entry with the new key; note that this
144
+ # may change the key value if another Entry is already regsitered with
145
+ # the same key.
146
+ #
147
+ # Returns the new key.
130
148
  def key=(key)
131
- raise(ArgumentError, "keys must be convertible to Symbol; was: #{type.class.name}.") unless type.respond_to?(:to_sym)
132
-
133
- unless @bibliography.nil?
134
- @bibliography.entries.delete(@key)
135
- @bibliography.entries[key] = self
149
+ key = key.to_s
150
+
151
+ if registered?
152
+ bibliography.entries.delete(@key)
153
+ key = register(key)
136
154
  end
137
155
 
138
- @key = key.to_sym
156
+ @key = key
157
+ rescue => e
158
+ raise BibTeXError, "failed to set key to #{key.inspect}: #{e.message}"
139
159
  end
140
160
 
141
161
  def key
@@ -148,7 +168,7 @@ module BibTeX
148
168
  # TODO we should be more lenient: allow strings as key or don't check at all
149
169
  # Sets the type of the entry.
150
170
  def type=(type)
151
- raise(ArgumentError, "types must be convertible to Symbol; was: #{type.class.name}.") unless type.respond_to?(:to_sym)
171
+ # raise(ArgumentError, "types must be convertible to Symbol; was: #{type.class.name}.") unless type.respond_to?(:to_sym)
152
172
  @type = type.to_sym
153
173
  end
154
174
 
@@ -156,25 +176,92 @@ module BibTeX
156
176
  type.to_s.match(/^entry$/i) || @type == type.to_sym || super
157
177
  end
158
178
 
159
- def has_field?(field)
160
- @fields.has_key?(field)
179
+ def has_field?(name)
180
+ name.respond_to?(:to_sym) ? fields.has_key?(name.to_sym) : false
161
181
  end
182
+
183
+ def inherits?(name)
184
+ !has_field(name) && has_parent? && parent.provides?(name)
185
+ end
186
+
187
+ # Returns true if the Entry has a field (or alias) for the passed-in name.
188
+ def provides?(name)
189
+ return nil unless name.respond_to?(:to_sym)
190
+ has_field?(name) || has_field?(aliases[name.to_sym])
191
+ end
162
192
 
193
+ # Returns the field value referenced by the passed-in name.
194
+ # For example, this will return the 'title' value for 'booktitle' if a
195
+ # corresponding alias is defined.
196
+ def provide(name)
197
+ return nil unless name.respond_to?(:to_sym)
198
+ name = name.to_sym
199
+ fields[name] || fields[aliases[name]]
200
+ end
201
+
202
+ # If the Entry has a cross-reference, copies all referenced all inherited
203
+ # values from the parent.
204
+ #
205
+ # Returns the Entry.
206
+ def save_inherited_fields
207
+ inherited_fields.each do |name|
208
+ fields[name] = parent.provide(name)
209
+ end
210
+
211
+ self
212
+ end
213
+
214
+ # Returns a sorted list of the Entry's field names. If a +filter+ is passed
215
+ # as argument, returns all field names that are also defined by the filter.
216
+ # If the +filter+ is empty, returns all field names.
217
+ #
218
+ # If the second optional argument is true (default) and the Entry contains
219
+ # a cross-reference, the list will include all inherited fields.
220
+ def field_names(filter = [], include_inherited = true)
221
+ names = fields.keys
222
+
223
+ if include_inherited && has_parent?
224
+ names.concat(inherited_fields)
225
+ end
226
+
227
+ unless filter.empty?
228
+ names = names & filter.map(&:to_sym)
229
+ end
230
+
231
+ names.sort!
232
+ names
233
+ end
234
+
235
+ # Returns a sorted list of all field names referenced by this Entry's cross-reference.
236
+ def inherited_fields
237
+ return [] unless has_parent?
238
+
239
+ names = parent.fields.keys - fields.keys
240
+ names.concat(parent.aliases.reject { |k,v| !parent.has_field?(v) }.keys)
241
+ names.sort!
242
+
243
+ names
244
+ end
245
+
246
+
163
247
  def method_missing(name, *args, &block)
164
248
  case
165
- when @fields.has_key?(name)
166
- @fields[name]
249
+ when fields.has_key?(name)
250
+ fields[name]
167
251
  when name.to_s =~ /^(.+)=$/
168
252
  send(:add, $1.to_sym, args[0])
169
253
  when name =~ /^(?:convert|from)_([a-z]+)(!)?$/
170
254
  $2 ? convert!($1, &block) : convert($1, &block)
255
+ when has_parent? && parent.provides?(name)
256
+ parent.provide(name)
171
257
  else
172
258
  super
173
259
  end
174
260
  end
175
261
 
176
262
  def respond_to?(method)
177
- @fields.has_key?(method.to_sym) || method.to_s.match(/=$/) || method =~ /^(?:convert|from)_([a-z]+)(!)?$/ || super
263
+ provides?(method.to_sym) || method.to_s.match(/=$/) ||
264
+ method =~ /^(?:convert|from)_([a-z]+)(!)?$/ || (has_parent? && parent.respond_to(method)) || super
178
265
  end
179
266
 
180
267
  # Returns a copy of the Entry with all the field names renamed.
@@ -186,20 +273,28 @@ module BibTeX
186
273
  # exists.
187
274
  def rename!(*arguments)
188
275
  Hash[*arguments.flatten].each_pair do |from,to|
189
- if @fields.has_key?(from) && !@fields.has_key?(to)
190
- @fields[to] = @fields[from]
191
- @fields.delete(from)
276
+ if fields.has_key?(from) && !fields.has_key?(to)
277
+ fields[to] = fields[from]
278
+ fields.delete(from)
192
279
  end
193
280
  end
194
281
  self
195
282
  end
196
283
 
197
- alias :rename_fields :rename
198
- alias :rename_fields! :rename!
284
+ alias rename_fields rename
285
+ alias rename_fields! rename!
199
286
 
200
- # Returns the value of the field with the given name.
287
+ # Returns the value of the field with the given name. If the value is not
288
+ # defined and the entry has cross-reference, returns the cross-referenced
289
+ # value instead.
201
290
  def [](name)
202
- @fields[name.to_sym]
291
+ fields[name.to_sym] || parent && parent.provide(name)
292
+ end
293
+
294
+ alias get []
295
+
296
+ def fetch(name, default = nil)
297
+ get(name) || block_given? ? yield(name) : default
203
298
  end
204
299
 
205
300
  # Adds a new field (name-value pair) to the entry.
@@ -216,11 +311,11 @@ module BibTeX
216
311
  # add(:author, "Edgar A. Poe", :title, "The Raven")
217
312
  # add([:author, "Edgar A. Poe", :title, "The Raven"])
218
313
  # add(:author => "Edgar A. Poe", :title => "The Raven")
219
- #
220
314
  def add(*arguments)
221
315
  Hash[*arguments.flatten].each_pair do |name, value|
222
- @fields[name.to_sym] = Value.new(value)
316
+ fields[name.to_sym] = Value.new(value)
223
317
  end
318
+
224
319
  self
225
320
  end
226
321
 
@@ -229,34 +324,64 @@ module BibTeX
229
324
  # Removes the field with a given name from the entry.
230
325
  # Returns the value of the deleted field; nil if the field was not set.
231
326
  def delete(name)
232
- @fields.delete(name.to_sym)
327
+ fields.delete(name.to_sym)
233
328
  end
234
329
 
235
330
  # Returns false if the entry is one of the standard entry types and does not have
236
331
  # definitions of all the required fields for that type.
237
332
  def valid?
238
333
  REQUIRED_FIELDS[@type].all? do |f|
239
- f.is_a?(Array) ? !(f & @fields.keys).empty? : !@fields[f].nil?
334
+ f.is_a?(Array) ? !(f & fields.keys).empty? : !fields[f].nil?
240
335
  end
241
336
  end
242
337
 
338
+ def generate_hash(filter = [])
339
+ Digest::MD5.hexdigest(field_names(filter).map { |k| [k, fields[k]] }.flatten.join)
340
+ end
341
+
243
342
  # Called when the element was added to a bibliography.
244
343
  def added_to_bibliography(bibliography)
245
344
  super
246
- bibliography.entries[key] = self
247
- parse_names if bibliography.options[:parse_names]
248
- parse_months if bibliography.options[:parse_months]
249
- convert(bibliography.options[:filter]) if bibliography.options[:filter]
345
+
346
+ @key = register(key)
347
+
348
+ [:parse_names, :parse_months].each do |parser|
349
+ send(parser) if bibliography.options[parser]
350
+ end
351
+
352
+ if bibliography.options.has_key?(:filter)
353
+ convert!(bibliography.options[:filter])
354
+ end
355
+
250
356
  self
251
357
  end
252
-
358
+
253
359
  # Called when the element was removed from a bibliography.
254
360
  def removed_from_bibliography(bibliography)
255
361
  super
256
- bibliography.entries[key] = nil
362
+ bibliography.entries.delete(key)
257
363
  self
258
364
  end
259
365
 
366
+ # Returns true if the Entry is currently registered with the associated Bibliography.
367
+ def registered?
368
+ !!(bibliography && bibliography.entries[key].equal?(self))
369
+ end
370
+
371
+ # Registers this Entry in the associated Bibliographies entries hash.
372
+ # This method may change the Entry's key, if another entry is already
373
+ # registered with the current key.
374
+ #
375
+ # Returns the key or nil if the Entry is not associated with a Bibliography.
376
+ def register(key)
377
+ return nil if bibliography.nil?
378
+
379
+ k = key.dup
380
+ k.succ! while bibliography.has_key?(k)
381
+ bibliography.entries[k] = self
382
+ k
383
+ end
384
+
260
385
  def replace(*arguments)
261
386
  arguments = bibliography.q('@string') if arguments.empty?
262
387
  @fields.values.each { |v| v.replace(*arguments) }
@@ -269,11 +394,11 @@ module BibTeX
269
394
  end
270
395
 
271
396
  def month=(month)
272
- @fields[:month] = MONTHS_FILTER[month]
397
+ fields[:month] = MONTHS_FILTER[month]
273
398
  end
274
399
 
275
400
  def parse_month
276
- @fields[:month] = MONTHS_FILTER[@fields[:month]] if @fields.has_key?(:month)
401
+ fields[:month] = MONTHS_FILTER[fields[:month]] if fields.has_key?(:month)
277
402
  self
278
403
  end
279
404
 
@@ -284,18 +409,65 @@ module BibTeX
284
409
  def parse_names
285
410
  strings = bibliography ? bibliography.strings.values : []
286
411
  NAME_FIELDS.each do |key|
287
- if name = @fields[key]
288
- name.replace(strings).join
289
- name = name.to_name
290
- @fields[key] = name
412
+ if name = fields[key]
413
+ name = name.dup.replace(strings).join.to_name
414
+ fields[key] = name unless name.nil?
291
415
  end
292
416
  end
293
417
  self
294
418
  end
295
419
 
420
+ # Returns a list of all names (authors, editors, translators).
421
+ def names
422
+ NAME_FIELDS.map { |k| has_field?(k) ? @fields[k].tokens : nil }.flatten.compact
423
+ end
424
+
425
+
426
+ # Returns true if the Entry has a valid cross-reference in the Bibliography.
427
+ def has_parent?
428
+ !parent.nil?
429
+ end
430
+
431
+ alias has_cross_reference? has_parent?
432
+
433
+ # Returns true if the Entry cross-references an Entry which is not
434
+ # registered in the current Bibliography.
435
+ def parent_missing?
436
+ has_field?(:crossref) && !has_parent?
437
+ end
438
+
439
+ alias cross_reference_missing? parent_missing?
440
+
441
+ # Returns the cross-referenced Entry from the Bibliography or nil if this
442
+ # Entry does define a cross-reference.
443
+ def parent
444
+ bibliography && bibliography[fields[:crossref]]
445
+ end
446
+
447
+ alias cross_reference parent
448
+
449
+
450
+ # Returns true if the entry is cross-referenced by another entry in the
451
+ # Bibliography.
452
+ def has_children?
453
+ !children.empty?
454
+ end
455
+
456
+ alias cross_referenced? has_children?
457
+
458
+ # Returns a list of all entries in the Bibliography containing a
459
+ # cross-reference to this entry or [] if there are no references to this
460
+ # entry.
461
+ def children
462
+ (bibliography && bibliography.q("@entry[crossref=#{key}]")) || []
463
+ end
464
+
465
+ alias cross_referenced_by children
466
+
467
+
296
468
  # Returns a string of all the entry's fields.
297
469
  def content(options = {})
298
- @fields.map { |k,v| "#{k} = #{ @fields[k].to_s(options) }" }.join(",\n")
470
+ fields.map { |k,v| "#{k} = #{ fields[k].to_s(options) }" }.join(",\n")
299
471
  end
300
472
 
301
473
  # Returns a string representation of the entry.
@@ -332,27 +504,37 @@ module BibTeX
332
504
  def to_xml(options = {})
333
505
  require 'rexml/document'
334
506
 
335
- xml = REXML::Element.new(type)
336
- xml.attributes['key'] = key
337
- @fields.each do |k,v|
338
- e = REXML::Element.new(k.to_s)
339
- e.text = v.to_s(options)
340
- xml.add_element(e)
507
+ xml = REXML::Element.new('bibtex:entry')
508
+ xml.attributes['id'] = key
509
+
510
+ entry = REXML::Element.new("bibtex:#{type}")
511
+
512
+ fields.each do |key, value|
513
+ field = REXML::Element.new("bibtex:#{key}")
514
+
515
+ if options[:extended] && value.name?
516
+ value.each { |n| entry.add_element(n.to_xml) }
517
+ else
518
+ field.text = value.to_s(options)
519
+ end
520
+
521
+ entry.add_element(field)
341
522
  end
342
- xml
523
+
524
+ xml.add_element(entry)
525
+ xml
343
526
  end
344
527
 
345
528
  # Returns a duplicate entry with all values converted using the filter.
346
529
  # If an optional block is given, only those values will be converted where
347
530
  # the block returns true (the block will be called with each key-value pair).
348
531
  #
349
- # @see convert!
350
- #
532
+ # @see #convert!
351
533
  def convert (filter)
352
534
  block_given? ? dup.convert!(filter, &Proc.new) : dup.convert!(filter)
353
535
  end
354
536
 
355
- # In-place variant of @see convert
537
+ # In-place variant of @see #convert
356
538
  def convert! (filter)
357
539
  @fields.each_pair { |k,v| !block_given? || yield(k,v) ? v.convert!(filter) : v }
358
540
  self
@@ -361,29 +543,18 @@ module BibTeX
361
543
  def <=>(other)
362
544
  type != other.type ? type <=> other.type : key != other.key ? key <=> other.key : to_s <=> other.to_s
363
545
  end
546
+
547
+ private
364
548
 
365
- protected
366
-
549
+ # Returns a default key for this entry.
367
550
  def default_key
368
- a = case fields[:author]
369
- when Names
370
- author[0].last
371
- when Value
372
- author.to_s[/\w+/]
373
- else
374
- nil
375
- end
376
-
377
- case
378
- when a && has_field?(:year) && has_field?(:title)
379
- [a,year,title.to_s[/\w{4,}/]].join.downcase.to_sym
380
- when a && has_field?(:year)
381
- [a,year].join.downcase.to_sym
382
- when has_field?(:year) && has_field?(:title)
383
- [year,title.to_s[/\w{4,}/]].join.downcase.to_sym
384
- else
385
- object_id.to_s
386
- end
551
+ k = names[0]
552
+ k = k.respond_to?(:family) ? k.family : k.to_s
553
+ k = k[/[A-Za-z]+/] || 'unknown'
554
+ k << (has_field?(:year) ? year : '-')
555
+ k << 'a'
556
+ k.downcase!
557
+ k
387
558
  end
388
559
 
389
560
  end