bibtex-ruby 1.3.12 → 2.0.0pre1
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.
Potentially problematic release.
This version of bibtex-ruby might be problematic. Click here for more details.
- data/Gemfile +7 -0
- data/Gemfile.lock +12 -13
- data/Manifest +1 -1
- data/README.md +114 -22
- data/bibtex-ruby.gemspec +13 -7
- data/features/issues/crossref.feature +62 -0
- data/features/names.feature +1 -1
- data/features/step_definitions/bibtex_steps.rb +2 -2
- data/lib/bibtex.rb +3 -1
- data/lib/bibtex/bibliography.rb +84 -32
- data/lib/bibtex/elements.rb +49 -30
- data/lib/bibtex/entry.rb +243 -72
- data/lib/bibtex/error.rb +11 -2
- data/lib/bibtex/lexer.rb +8 -8
- data/lib/bibtex/names.rb +29 -4
- data/lib/bibtex/value.rb +9 -9
- data/lib/bibtex/version.rb +1 -1
- data/lib/bibtex/version.rbc +2 -2
- data/test/bibtex/test_bibliography.rb +59 -31
- data/test/bibtex/test_elements.rb +31 -7
- data/test/bibtex/test_entry.rb +220 -41
- data/test/bibtex/test_filters.rb +6 -6
- data/test/bibtex/test_lexer.rb +1 -1
- data/test/bibtex/test_name_parser.rb +4 -4
- data/test/bibtex/test_names.rb +5 -5
- data/test/bibtex/test_parser.rb +23 -23
- data/test/bibtex/test_string.rb +6 -6
- data/test/bibtex/test_value.rb +32 -32
- data/test/helper.rb +0 -1
- data/test/test_bibtex.rb +5 -6
- data/test/test_export.rb +2 -3
- metadata +32 -72
- data/profile.png +0 -0
data/lib/bibtex/elements.rb
CHANGED
@@ -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(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
data/lib/bibtex/entry.rb
CHANGED
@@ -26,7 +26,7 @@ module BibTeX
|
|
26
26
|
extend Forwardable
|
27
27
|
include Enumerable
|
28
28
|
|
29
|
-
#
|
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
|
-
#
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
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?(
|
160
|
-
|
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
|
166
|
-
|
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
|
-
|
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
|
190
|
-
|
191
|
-
|
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
|
198
|
-
alias
|
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
|
-
|
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
|
-
|
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
|
-
|
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 &
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
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
|
-
|
397
|
+
fields[:month] = MONTHS_FILTER[month]
|
273
398
|
end
|
274
399
|
|
275
400
|
def parse_month
|
276
|
-
|
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 =
|
288
|
-
name.replace(strings).join
|
289
|
-
|
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
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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
|
-
|
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
|
-
|
366
|
-
|
549
|
+
# Returns a default key for this entry.
|
367
550
|
def default_key
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|