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.
- 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
|