Class: BibTeX::Entry

Inherits:
Element show all
Extended by:
Forwardable
Includes:
Enumerable
Defined in:
lib/bibtex/entry.rb

Overview

Represents a regular BibTeX entry.

Defined Under Namespace

Classes: BibTeXMLConverter, CiteProcConverter, RDFConverter

Constant Summary collapse

REQUIRED_FIELDS =

Defines the required fields of the standard entry types

Hash.new([]).merge({
  :article       => [:author,:title,:journal,:year],
  :book          => [[:author,:editor],:title,:publisher,:year],
  :booklet       => [:title],
  :conference    => [:author,:title,:booktitle,:year],
  :inbook        => [[:author,:editor],:title,[:chapter,:pages],:publisher,:year],
  :incollection  => [:author,:title,:booktitle,:publisher,:year],
  :inproceedings => [:author,:title,:booktitle,:year],
  :manual        => [:title],
  :mastersthesis => [:author,:title,:school,:year],
  :misc          => [],
  :phdthesis     => [:author,:title,:school,:year],
  :proceedings   => [:title,:year],
  :techreport    => [:author,:title,:institution,:year],
  :unpublished   => [:author,:title,:note]
}).freeze
FIELD_ALIASES =

Defines the default fallbacks for values defined in cross-references

{
  :booktitle => :title,
  # :editor => :author
}.freeze
NAME_FIELDS =
[:author,:editor,:translator,:director,:producer,:composer].freeze
DATE_FIELDS =
[:year,:month,:day,:date].freeze
MONTHS =
[:jan,:feb,:mar,:apr,:may,:jun,:jul,:aug,:sep,:oct,:nov,:dec].freeze
MONTHS_FILTER =
Hash.new do |h,k|
  case k.to_s.strip
  when /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i
    h[k] = Value.new(k.to_s[0,3].downcase.to_sym)
  when /^\d\d?$/
    h[k] = Value.new(MONTHS[k.to_i - 1] || k)
  else
    h[k] = Value.new(k)
  end
end

Instance Attribute Summary collapse

Attributes inherited from Element

#bibliography

Instance Method Summary collapse

Methods inherited from Element

#inspect, #matches?, #meets?, #meets_all?, #meets_any?, parse, #to_json, #to_yaml

Constructor Details

#initialize(attributes = {}) {|_self| ... } ⇒ Entry

Creates a new instance. If a hash is given, the entry is populated accordingly.

Yields:

  • (_self)

Yield Parameters:

  • _self (BibTeX::Entry)

    the object that the method was called on


73
74
75
76
77
78
79
80
81
82
83
# File 'lib/bibtex/entry.rb', line 73

def initialize(attributes = {})
  @fields = {}
  @key = nil

  self.type = attributes.delete(:bibtex_type) if attributes.has_key?(:bibtex_type)
  self.key = attributes.delete(:bibtex_key) if attributes.has_key?(:bibtex_key)

  add(attributes)

  yield self if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object


302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/bibtex/entry.rb', line 302

def method_missing(name, *args, &block)
  case
  when fields.has_key?(name)
    fields[name]
  when name.to_s =~ /^(.+)=$/
    send(:add, $1.to_sym, args[0])
  when name =~ /^(?:convert|from)_([a-z]+)(!)?$/
    $2 ? convert!($1, &block) : convert($1, &block)
  when has_parent? && parent.provides?(name)
    parent.provide(name)
  else
    super
  end
end

Instance Attribute Details

#fieldsObject (readonly)

Returns the value of attribute fields


68
69
70
# File 'lib/bibtex/entry.rb', line 68

def fields
  @fields
end

#typeObject

Returns the value of attribute type


68
69
70
# File 'lib/bibtex/entry.rb', line 68

def type
  @type
end

Instance Method Details

#<=>(other) ⇒ Object


627
628
629
# File 'lib/bibtex/entry.rb', line 627

def <=>(other)
  type != other.type ? type <=> other.type : key != other.key ? key <=> other.key : to_s <=> other.to_s
end

#[](name) ⇒ Object Also known as: get

Returns the value of the field with the given name. If the value is not defined and the entry has cross-reference, returns the cross-referenced value instead.


346
347
348
# File 'lib/bibtex/entry.rb', line 346

def [](name)
  fields[name.to_sym] || parent && parent.provide(name)
end

#[]=(name, value) ⇒ Object

Adds a new field (name-value pair) to the entry. Returns the new value.


358
359
360
# File 'lib/bibtex/entry.rb', line 358

def []=(name, value)
  add(name.to_sym, value)
end

#add(*arguments) ⇒ Object Also known as: <<

call-seq:

add(:author, "Edgar A. Poe")
add(:author, "Edgar A. Poe", :title, "The Raven")
add([:author, "Edgar A. Poe", :title, "The Raven"])
add(:author => "Edgar A. Poe", :title => "The Raven")
add(:author => Names.new(Name.new(:first => 'Edgar A.', :last => 'Poe')))

Adds a new field (name-value pair) or multiple fields to the entry. Returns the entry for chainability.


371
372
373
374
375
376
377
# File 'lib/bibtex/entry.rb', line 371

def add(*arguments)
  Hash[*arguments.flatten].each_pair do |name, value|
    fields[name.to_sym] = Value.create(value)
  end

  self
end

#added_to_bibliography(bibliography) ⇒ Object

Called when the element was added to a bibliography.


434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/bibtex/entry.rb', line 434

def added_to_bibliography(bibliography)
  super

  @key = register(key)

  [:parse_names, :parse_months].each do |parser|
    send(parser) if bibliography.options[parser]
  end

  if bibliography.options.has_key?(:filter)
    [*bibliography.options[:filter]].each do |filter|
      convert!(filter)
    end
  end

  self
end

#aliasesObject

Returns the Entry's field name aliases.


173
174
175
# File 'lib/bibtex/entry.rb', line 173

def aliases
  @aliases ||= FIELD_ALIASES.dup
end

#childrenObject Also known as: cross_referenced_by

Returns a list of all entries in the Bibliography containing a cross-reference to this entry or [] if there are no references to this entry.


576
577
578
# File 'lib/bibtex/entry.rb', line 576

def children
  bibliography && bibliography.q("@entry[crossref=#{key}]") or []
end

#contained?Boolean

Returns true if this entry is published inside a book, collection or journal

Returns:

  • (Boolean)

595
596
597
598
# File 'lib/bibtex/entry.rb', line 595

def contained?
  has_field?(:container, :journal) ||
    has_field?(:booktitle) && get(:booktitle) != get(:title)
end

#container_titleObject


582
583
584
# File 'lib/bibtex/entry.rb', line 582

def container_title
  get(:booktitle) || get(:journal) || get(:container)
end

#content(options = {}) ⇒ Object

Returns a string of all the entry's fields.


633
634
635
# File 'lib/bibtex/entry.rb', line 633

def content(options = {})
  fields.map { |k,v| "#{k} = #{ fields[k].to_s(options) }" }.join(",\n")
end

#convert(*filters) ⇒ Object

Returns a duplicate entry with all values converted using the filter(s). If an optional block is given, only those values will be converted where the block returns true (the block will be called with each key-value pair).

See Also:


612
613
614
# File 'lib/bibtex/entry.rb', line 612

def convert(*filters)
  block_given? ? dup.convert!(*filters, &Proc.new) : dup.convert!(*filters)
end

#convert!(*filters) ⇒ Object

In-place variant of @see #convert


617
618
619
620
621
622
623
624
625
# File 'lib/bibtex/entry.rb', line 617

def convert!(*filters)
  filters = filters.flatten.map { |f| Filters.resolve!(f) }

  fields.each_pair do |k, v|
    (!block_given? || yield(k, v)) ? v.convert!(*filters) : v
  end

  self
end

#dateObject


516
517
518
# File 'lib/bibtex/entry.rb', line 516

def date
  get(:date) || get(:year)
end

#delete(name) ⇒ Object

Removes the field with a given name from the entry. Returns the value of the deleted field; nil if the field was not set.


383
384
385
# File 'lib/bibtex/entry.rb', line 383

def delete(name)
  fields.delete(name.to_sym)
end

#digest(filter = []) ⇒ String

Creates the entry's digest based on the passed-in filters.

The digest contains the type and all key-value pairs based on the passed in filter.

If a block is given, the computed digest will be passed to the block for post-processing (the entry itself will be passed as the second parameter).

Parameters:

  • the (<Symbol>)

    field names to use

Returns:

  • (String)

    the digest string

See Also:


408
409
410
411
412
413
414
415
416
417
418
# File 'lib/bibtex/entry.rb', line 408

def digest(filter = [])
  names = field_names(filter)
  digest = type.to_s

  names.zip(values_at(*names)).each do |key, value|
    digest << "|#{key}:#{value}"
  end

  digest = yield(digest, self) if block_given?
  digest
end

#eachObject Also known as: each_pair

call-seq:

entry.each      { |key, value| block } -> entry
entry.each_pair { |key, value| block } -> entry
entry.each                             -> an_enumerator
entry.each_pair                        -> an_enumerator

Calls block once for each key in entry, passing the key-value pair as parameters.

If no block is given, an enumerator is returned instead.


160
161
162
163
164
165
166
167
# File 'lib/bibtex/entry.rb', line 160

def each
  if block_given?
    fields.each(&Proc.new)
    self
  else
    to_enum
  end
end

#fetch(name, default = nil) ⇒ Object


352
353
354
# File 'lib/bibtex/entry.rb', line 352

def fetch(name, default = nil)
  get(name) || (block_given? ? yield(name) : default)
end

#field_names(filter = [], include_inherited = true) ⇒ Object

Returns a sorted list of the Entry's field names. If a filter is passed as argument, returns all field names that are also defined by the filter. If the filter is empty, returns all field names.

If the second optional argument is true (default) and the Entry contains a cross-reference, the list will include all inherited fields.


275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/bibtex/entry.rb', line 275

def field_names(filter = [], include_inherited = true)
  names = fields.keys

  if include_inherited && has_parent?
    names.concat(inherited_fields)
  end

  unless filter.empty?
    names = names & filter.map(&:to_sym)
  end

  names.sort!
  names
end

#has_children?Boolean Also known as: cross_referenced?

Returns true if the entry is cross-referenced by another entry in the Bibliography.

Returns:

  • (Boolean)

567
568
569
# File 'lib/bibtex/entry.rb', line 567

def has_children?
  !children.empty?
end

#has_field?(*names) ⇒ Boolean Also known as: field?

Returns:

  • (Boolean)

219
220
221
222
223
# File 'lib/bibtex/entry.rb', line 219

def has_field?(*names)
  names.flatten.any? do |name|
    name.respond_to?(:to_sym) ? fields.has_key?(name.to_sym) : false
  end
end

#has_parent?Boolean Also known as: has_cross_reference?

Returns true if the Entry has a valid cross-reference in the Bibliography.

Returns:

  • (Boolean)

542
543
544
# File 'lib/bibtex/entry.rb', line 542

def has_parent?
  !parent.nil?
end

#has_type?(type) ⇒ Boolean Also known as: type?

Returns:

  • (Boolean)

212
213
214
# File 'lib/bibtex/entry.rb', line 212

def has_type?(type)
  type.to_s.match(/^(?:entry|\*)$/i) || @type == type.to_sym || super
end

#identifierObject


420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/bibtex/entry.rb', line 420

def identifier
  case
  when provides?(:doi)
    "info:doi/#{get(:doi)}"
  when provides?(:isbn)
    "urn:isbn:#{get(:isbn)}"
  when provides?(:issn)
    "urn:issn:#{get(:issn)}"
  else
    "urn:bibtex:#{key}"
  end
end

#inherited_fieldsObject

Returns a sorted list of all field names referenced by this Entry's cross-reference.


291
292
293
294
295
296
297
298
299
# File 'lib/bibtex/entry.rb', line 291

def inherited_fields
  return [] unless has_parent?

  names = parent.fields.keys - fields.keys
  names.concat(parent.aliases.reject { |k,v| !parent.has_field?(v) }.keys)
  names.sort!

  names
end

#inherits?(*names) ⇒ Boolean

Returns:

  • (Boolean)

227
228
229
230
231
# File 'lib/bibtex/entry.rb', line 227

def inherits?(*names)
  names.flatten.any? do |name|
    !has_field(name) && has_parent? && parent.provides?(name)
  end
end

#initialize_copy(other) ⇒ Object


85
86
87
88
89
90
91
92
# File 'lib/bibtex/entry.rb', line 85

def initialize_copy(other)
  @fields = {}

  self.type = other.type
  self.key = other.key

  add(other.fields)
end

#joinObject


484
485
486
487
# File 'lib/bibtex/entry.rb', line 484

def join
  fields.values.each(&:join)
  self
end

#keyObject Also known as: id


196
197
198
199
200
201
202
# File 'lib/bibtex/entry.rb', line 196

def key
  if @key.nil? || @key.empty?
    @key = default_key
  else
    @key
  end
end

#key=(key) ⇒ Object Also known as: id=

Sets the Entry's key. If the Entry is currently registered with a Bibliography, re-registers the Entry with the new key; note that this may change the key value if another Entry is already regsitered with the same key.

Returns the new key.


183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/bibtex/entry.rb', line 183

def key=(key)
  key = key.to_s

  if registered?
    bibliography.entries.delete(@key)
    key = register(key)
  end

  @key = key
rescue => e
  raise BibTeXError, "failed to set key to #{key.inspect}: #{e.message}"
end

#merge(other, filter = field_names) ⇒ Object


94
95
96
# File 'lib/bibtex/entry.rb', line 94

def merge(other, filter = field_names)
  dup.merge!(other, filter)
end

#merge!(other, filter = field_names) ⇒ Object

Raises:

  • (InvalidArgument)

98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/bibtex/entry.rb', line 98

def merge!(other, filter = field_names)
  raise InvalidArgument, "failed to merge entries: type mismatch: #{type} #{other.type}" unless
    type == other.type

  other.each do |name, value|
    if has_field?(name)
      get(name).merge!(value) if filter.include?(name)
    else
      add name, value.dup
    end
  end

  self
end

#month=(month) ⇒ Object


489
490
491
492
493
# File 'lib/bibtex/entry.rb', line 489

def month=(month)
  fields[:month] = month
ensure
  parse_month
end

#month_numericObject


495
496
497
498
499
500
# File 'lib/bibtex/entry.rb', line 495

def month_numeric
  return unless has_field?(:month)
  return unless (num = MONTHS.index fields[:month].to_sym)

  num.succ
end

#namesObject

Returns a list of all names (authors, editors, translators).


536
537
538
# File 'lib/bibtex/entry.rb', line 536

def names
  NAME_FIELDS.map { |k| has_field?(k) ? @fields[k].tokens : nil }.flatten.compact
end

#pages_fromObject


586
587
588
# File 'lib/bibtex/entry.rb', line 586

def pages_from
  fetch(:pages, '').split(/\D+/)[0]
end

#pages_toObject


590
591
592
# File 'lib/bibtex/entry.rb', line 590

def pages_to
  fetch(:pages, '').split(/\D+/)[-1]
end

#parentObject Also known as: cross_reference

Returns the cross-referenced Entry from the Bibliography or nil if this Entry does define a cross-reference.


558
559
560
# File 'lib/bibtex/entry.rb', line 558

def parent
  bibliography && bibliography[fields[:crossref]]
end

#parent_missing?Boolean Also known as: cross_reference_missing?

Returns true if the Entry cross-references an Entry which is not registered in the current Bibliography.

Returns:

  • (Boolean)

550
551
552
# File 'lib/bibtex/entry.rb', line 550

def parent_missing?
  has_field?(:crossref) && !has_parent?
end

#parse_monthObject Also known as: parse_months


502
503
504
505
506
507
508
509
510
511
512
# File 'lib/bibtex/entry.rb', line 502

def parse_month
  fields.delete(:month_numeric)
  return unless has_field?(:month)

  fields[:month] = MONTHS_FILTER[fields[:month]]

  numeric = MONTHS.index(fields[:month].to_sym)
  fields[:month_numeric] = Value.new(numeric.succ) if numeric

  self
end

#parse_namesObject

Parses all name values of the entry. Tries to replace and join the value prior to parsing.


522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/bibtex/entry.rb', line 522

def parse_names
  strings = bibliography ? bibliography.strings.values : []

  NAME_FIELDS.each do |key|
    if name = fields[key]
      name = name.dup.replace(strings).join.to_name
      fields[key] = name unless name.nil?
    end
  end

  self
end

#provide(name) ⇒ Object

Returns the field value referenced by the passed-in name. For example, this will return the 'title' value for 'booktitle' if a corresponding alias is defined.


251
252
253
254
255
# File 'lib/bibtex/entry.rb', line 251

def provide(name)
  return nil unless name.respond_to?(:to_sym)
  name = name.to_sym
  fields[name] || fields[aliases[name]]
end

#provides?(*names) ⇒ Boolean

Returns true if the Entry has a field (or alias) for any of the passed-in names.

Returns:

  • (Boolean)

234
235
236
237
238
239
240
241
242
# File 'lib/bibtex/entry.rb', line 234

def provides?(*names)
  names.flatten.any? do |name|
    if name.respond_to?(:to_sym)
      has_field?(name) || has_field?(aliases[name.to_sym])
    else
      false
    end
  end
end

#provides_or_inherits?(*names) ⇒ Boolean

Returns:

  • (Boolean)

244
245
246
# File 'lib/bibtex/entry.rb', line 244

def provides_or_inherits?(*names)
  provides?(names) || inherits?(names)
end

#register(key) ⇒ Object

Registers this Entry in the associated Bibliographies entries hash. This method may change the Entry's key, if another entry is already registered with the current key.

Returns the key or nil if the Entry is not associated with a Bibliography.


469
470
471
472
473
474
475
476
# File 'lib/bibtex/entry.rb', line 469

def register(key)
  return nil if bibliography.nil?

  k = key.dup
  k.succ! while bibliography.has_key?(k)
  bibliography.entries[k] = self
  k
end

#registered?Boolean

Returns true if the Entry is currently registered with the associated Bibliography.

Returns:

  • (Boolean)

460
461
462
# File 'lib/bibtex/entry.rb', line 460

def registered?
  !!(bibliography && bibliography.entries[key].equal?(self))
end

#removed_from_bibliography(bibliography) ⇒ Object

Called when the element was removed from a bibliography.


453
454
455
456
457
# File 'lib/bibtex/entry.rb', line 453

def removed_from_bibliography(bibliography)
  super
  bibliography.entries.delete(key)
  self
end

#rename(*arguments) ⇒ Object Also known as: rename_fields

Returns a copy of the Entry with all the field names renamed.


324
325
326
# File 'lib/bibtex/entry.rb', line 324

def rename(*arguments)
  dup.rename!(*arguments)
end

#rename!(*arguments) ⇒ Object Also known as: rename_fields!

Renames the given field names unless a field with the new name already exists.


330
331
332
333
334
335
336
337
338
# File 'lib/bibtex/entry.rb', line 330

def rename!(*arguments)
  Hash[*arguments.flatten].each_pair do |from,to|
    if fields.has_key?(from) && !fields.has_key?(to)
      fields[to] = fields[from]
      fields.delete(from)
    end
  end
  self
end

#replace(*arguments) ⇒ Object


478
479
480
481
482
# File 'lib/bibtex/entry.rb', line 478

def replace(*arguments)
  arguments = bibliography.q('@string') if arguments.empty?
  fields.values.each { |v| v.replace(*arguments) }
  self
end

#respond_to?(method, include_all = false) ⇒ Boolean

Returns:

  • (Boolean)

317
318
319
320
321
# File 'lib/bibtex/entry.rb', line 317

def respond_to?(method, include_all=false)
  provides?(method.to_sym) || method.to_s.match(/=$/) ||
    method =~ /^(?:convert|from)_([a-z]+)(!)?$/ ||
    (has_parent? && parent.respond_to?(method, include_all)) || super
end

#save_inherited_fieldsObject

If the Entry has a cross-reference, copies all referenced all inherited values from the parent.

Returns the Entry.


261
262
263
264
265
266
267
# File 'lib/bibtex/entry.rb', line 261

def save_inherited_fields
  inherited_fields.each do |name|
    fields[name] = parent.provide(name)
  end

  self
end

#to_citeproc(options = {}) ⇒ Object


650
651
652
# File 'lib/bibtex/entry.rb', line 650

def to_citeproc(options = {})
  CiteProcConverter.convert(self, options)
end

#to_hash(options = {}) ⇒ Object


643
644
645
646
647
648
# File 'lib/bibtex/entry.rb', line 643

def to_hash(options = {})
  options[:quotes] ||= %w({ })
  hash = { :bibtex_key => key, :bibtex_type => type }
  each_pair { |k,v| hash[k] = v.to_s(options) }
  hash
end

#to_rdf(options = {}) ⇒ Object Also known as: to_bibo

Returns a RDF::Graph representation of the entry using the BIBO ontology.


659
660
661
662
663
664
665
# File 'lib/bibtex/entry.rb', line 659

def to_rdf(options = {})
  if defined?(::RDF)
    RDFConverter.convert(self)
  else
    BibTeX.log.error 'Please `gem install rdf` for RDF support.'
  end
end

#to_s(options = {}) ⇒ Object

Returns a string representation of the entry.


638
639
640
641
# File 'lib/bibtex/entry.rb', line 638

def to_s(options = {})
  options[:quotes] ||= %w({ })
  ["@#{type}{#{key},", content(options).gsub(/^/,'  '), "}\n"].join("\n")
end

#to_xml(options = {}) ⇒ Object


654
655
656
# File 'lib/bibtex/entry.rb', line 654

def to_xml(options = {})
  BibTeXMLConverter.convert(self, options)
end

#update(fields) ⇒ Object


113
114
115
116
117
118
119
# File 'lib/bibtex/entry.rb', line 113

def update(fields)
  fields.each do |name, value|
    add name, value
  end

  self
end

#valid?Boolean

Returns false if the entry is one of the standard entry types and does not have definitions of all the required fields for that type.

Returns:

  • (Boolean)

389
390
391
392
393
# File 'lib/bibtex/entry.rb', line 389

def valid?
  REQUIRED_FIELDS[type].all? do |f|
    f.is_a?(Array) ? !(f & fields.keys).empty? : !fields[f].nil?
  end
end

#values_at(*arguments) ⇒ Object

Returns an array containing the values associated with the given keys.


601
602
603
604
605
# File 'lib/bibtex/entry.rb', line 601

def values_at(*arguments)
  arguments.map do |key|
    get key
  end
end

#yearObject


669
670
671
672
673
674
# File 'lib/bibtex/entry.rb', line 669

def year
  return fields[:year] if has_field?(:year)
  return unless has_field?(:date)

  fields[:date].to_s[/\d{4}/]
end