bibtex-ruby 2.0.1 → 2.0.2
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.lock +2 -2
- data/History.txt +5 -0
- data/lib/bibtex.rb +12 -12
- data/lib/bibtex/bibliography.rb +98 -81
- data/lib/bibtex/elements.rb +191 -191
- data/lib/bibtex/entry.rb +470 -445
- data/lib/bibtex/lexer.rb +5 -5
- data/lib/bibtex/names.rb +2 -1
- data/lib/bibtex/value.rb +40 -43
- data/lib/bibtex/version.rb +1 -1
- data/test/bibtex/test_bibliography.rb +49 -49
- data/test/bibtex/test_entry.rb +214 -208
- data/test/bibtex/test_name_parser.rb +2 -2
- metadata +12 -12
data/lib/bibtex/entry.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#--
|
2
2
|
# BibTeX-Ruby
|
3
|
-
# Copyright (C) 2010-2011
|
3
|
+
# Copyright (C) 2010-2011 Sylvester Keil <sylvester.keil.or.at>
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
6
6
|
# it under the terms of the GNU General Public License as published by
|
@@ -9,48 +9,46 @@
|
|
9
9
|
#
|
10
10
|
# This program is distributed in the hope that it will be useful,
|
11
11
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
13
|
# GNU General Public License for more details.
|
14
14
|
#
|
15
15
|
# You should have received a copy of the GNU General Public License
|
16
|
-
# along with this program.
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
#++
|
18
18
|
|
19
|
-
require 'forwardable'
|
20
|
-
|
21
19
|
module BibTeX
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
20
|
+
#
|
21
|
+
# Represents a regular BibTeX entry.
|
22
|
+
#
|
23
|
+
class Entry < Element
|
24
|
+
extend Forwardable
|
25
|
+
include Enumerable
|
26
|
+
|
27
|
+
# Defines the required fields of the standard entry types
|
28
|
+
REQUIRED_FIELDS = Hash.new([]).merge({
|
29
|
+
:article => [:author,:title,:journal,:year],
|
30
|
+
:book => [[:author,:editor],:title,:publisher,:year],
|
31
|
+
:booklet => [:title],
|
32
|
+
:conference => [:author,:title,:booktitle,:year],
|
33
|
+
:inbook => [[:author,:editor],:title,[:chapter,:pages],:publisher,:year],
|
34
|
+
:incollection => [:author,:title,:booktitle,:publisher,:year],
|
35
|
+
:inproceedings => [:author,:title,:booktitle,:year],
|
36
|
+
:manual => [:title],
|
37
|
+
:mastersthesis => [:author,:title,:school,:year],
|
38
|
+
:misc => [],
|
39
|
+
:phdthesis => [:author,:title,:school,:year],
|
40
|
+
:proceedings => [:title,:year],
|
41
|
+
:techreport => [:author,:title,:institution,:year],
|
42
|
+
:unpublished => [:author,:title,:note]
|
43
|
+
}).freeze
|
44
|
+
|
45
|
+
# Defines the default fallbacks for values defined in cross-references
|
46
|
+
FIELD_ALIASES = {
|
47
|
+
:booktitle => :title,
|
48
|
+
# :editor => :author
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
|
54
52
|
NAME_FIELDS = [:author,:editor,:translator].freeze
|
55
53
|
DATE_FIELDS = [:year,:month].freeze
|
56
54
|
|
@@ -109,22 +107,22 @@ module BibTeX
|
|
109
107
|
article article-journal
|
110
108
|
}.map(&:intern)]).freeze
|
111
109
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def_delegators :@fields, :empty
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
110
|
+
|
111
|
+
attr_reader :fields, :type
|
112
|
+
|
113
|
+
def_delegators :@fields, :empty?
|
114
|
+
|
115
|
+
# Creates a new instance. If a hash is given, the entry is populated accordingly.
|
116
|
+
def initialize(attributes = {})
|
117
|
+
@fields = {}
|
118
|
+
|
119
|
+
self.type = attributes.delete(:type) if attributes.has_key?(:type)
|
120
|
+
self.key = attributes.delete(:key) if attributes.has_key?(:key)
|
121
|
+
|
122
|
+
add(attributes)
|
123
|
+
|
124
|
+
yield self if block_given?
|
125
|
+
end
|
128
126
|
|
129
127
|
def initialize_copy (other)
|
130
128
|
@fields = {}
|
@@ -135,262 +133,287 @@ module BibTeX
|
|
135
133
|
add(other.fields)
|
136
134
|
end
|
137
135
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
136
|
+
# call-seq:
|
137
|
+
# entry.each { |key, value| block } -> entry
|
138
|
+
# entry.each_pair { |key, value| block } -> entry
|
139
|
+
# entry.each -> an_enumerator
|
140
|
+
# entry.each_pair -> an_enumerator
|
141
|
+
#
|
142
|
+
# Calls block once for each key in entry, passing the key-value
|
143
|
+
# pair as parameters.
|
144
|
+
#
|
145
|
+
# If no block is given, an enumerator is returned instead.
|
146
|
+
def each
|
147
|
+
if block_given?
|
148
|
+
fields.each(&Proc.new)
|
149
|
+
self
|
150
|
+
else
|
151
|
+
to_enum
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
alias each_pair each
|
156
|
+
|
157
|
+
|
158
|
+
# Returns the Entry's field name aliases.
|
159
|
+
def aliases
|
160
|
+
@aliases ||= FIELD_ALIASES.dup
|
161
|
+
end
|
162
|
+
|
163
|
+
# Sets the Entry's key. If the Entry is currently registered with a
|
164
|
+
# Bibliography, re-registers the Entry with the new key; note that this
|
165
|
+
# may change the key value if another Entry is already regsitered with
|
166
|
+
# the same key.
|
167
|
+
#
|
168
|
+
# Returns the new key.
|
169
|
+
def key=(key)
|
170
|
+
key = key.to_s
|
171
|
+
|
152
172
|
if registered?
|
153
|
-
|
154
|
-
|
173
|
+
bibliography.entries.delete(@key)
|
174
|
+
key = register(key)
|
155
175
|
end
|
156
176
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
177
|
+
@key = key
|
178
|
+
rescue => e
|
179
|
+
raise BibTeXError, "failed to set key to #{key.inspect}: #{e.message}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def key
|
183
|
+
@key ||= default_key
|
184
|
+
end
|
185
|
+
|
186
|
+
alias id key
|
187
|
+
alias id= key=
|
188
|
+
|
189
|
+
# Sets the type of the entry.
|
190
|
+
def type=(type)
|
191
|
+
@type = type.to_sym
|
192
|
+
end
|
193
|
+
|
194
|
+
def has_type?(type)
|
195
|
+
type.to_s.match(/^entry$/i) || @type == type.to_sym || super
|
196
|
+
end
|
197
|
+
|
198
|
+
alias type? has_type?
|
199
|
+
|
179
200
|
|
180
201
|
def has_field?(name)
|
181
|
-
|
182
|
-
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
202
|
+
name.respond_to?(:to_sym) ? fields.has_key?(name.to_sym) : false
|
203
|
+
end
|
204
|
+
|
205
|
+
alias field? has_field?
|
206
|
+
|
207
|
+
def inherits?(name)
|
208
|
+
!has_field(name) && has_parent? && parent.provides?(name)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns true if the Entry has a field (or alias) for the passed-in name.
|
212
|
+
def provides?(name)
|
213
|
+
return nil unless name.respond_to?(:to_sym)
|
214
|
+
has_field?(name) || has_field?(aliases[name.to_sym])
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns the field value referenced by the passed-in name.
|
218
|
+
# For example, this will return the 'title' value for 'booktitle' if a
|
219
|
+
# corresponding alias is defined.
|
220
|
+
def provide(name)
|
221
|
+
return nil unless name.respond_to?(:to_sym)
|
222
|
+
name = name.to_sym
|
223
|
+
fields[name] || fields[aliases[name]]
|
224
|
+
end
|
225
|
+
|
226
|
+
# If the Entry has a cross-reference, copies all referenced all inherited
|
227
|
+
# values from the parent.
|
228
|
+
#
|
229
|
+
# Returns the Entry.
|
230
|
+
def save_inherited_fields
|
231
|
+
inherited_fields.each do |name|
|
232
|
+
fields[name] = parent.provide(name)
|
233
|
+
end
|
234
|
+
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a sorted list of the Entry's field names. If a +filter+ is passed
|
239
|
+
# as argument, returns all field names that are also defined by the filter.
|
240
|
+
# If the +filter+ is empty, returns all field names.
|
241
|
+
#
|
242
|
+
# If the second optional argument is true (default) and the Entry contains
|
243
|
+
# a cross-reference, the list will include all inherited fields.
|
244
|
+
def field_names(filter = [], include_inherited = true)
|
245
|
+
names = fields.keys
|
246
|
+
|
247
|
+
if include_inherited && has_parent?
|
248
|
+
names.concat(inherited_fields)
|
249
|
+
end
|
250
|
+
|
251
|
+
unless filter.empty?
|
252
|
+
names = names & filter.map(&:to_sym)
|
253
|
+
end
|
254
|
+
|
255
|
+
names.sort!
|
256
|
+
names
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns a sorted list of all field names referenced by this Entry's cross-reference.
|
260
|
+
def inherited_fields
|
261
|
+
return [] unless has_parent?
|
262
|
+
|
263
|
+
names = parent.fields.keys - fields.keys
|
264
|
+
names.concat(parent.aliases.reject { |k,v| !parent.has_field?(v) }.keys)
|
265
|
+
names.sort!
|
266
|
+
|
267
|
+
names
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
def method_missing(name, *args, &block)
|
272
|
+
case
|
273
|
+
when fields.has_key?(name)
|
274
|
+
fields[name]
|
275
|
+
when name.to_s =~ /^(.+)=$/
|
276
|
+
send(:add, $1.to_sym, args[0])
|
277
|
+
when name =~ /^(?:convert|from)_([a-z]+)(!)?$/
|
278
|
+
$2 ? convert!($1, &block) : convert($1, &block)
|
279
|
+
when has_parent? && parent.provides?(name)
|
280
|
+
parent.provide(name)
|
281
|
+
else
|
282
|
+
super
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def respond_to?(method)
|
287
|
+
provides?(method.to_sym) || method.to_s.match(/=$/) ||
|
288
|
+
method =~ /^(?:convert|from)_([a-z]+)(!)?$/ || (has_parent? && parent.respond_to?(method)) || super
|
289
|
+
end
|
290
|
+
|
291
|
+
# Returns a copy of the Entry with all the field names renamed.
|
292
|
+
def rename(*arguments)
|
293
|
+
dup.rename!(*arguments)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Renames the given field names unless a field with the new name already
|
297
|
+
# exists.
|
298
|
+
def rename!(*arguments)
|
299
|
+
Hash[*arguments.flatten].each_pair do |from,to|
|
300
|
+
if fields.has_key?(from) && !fields.has_key?(to)
|
301
|
+
fields[to] = fields[from]
|
302
|
+
fields.delete(from)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
self
|
306
|
+
end
|
307
|
+
|
308
|
+
alias rename_fields rename
|
309
|
+
alias rename_fields! rename!
|
310
|
+
|
311
|
+
# Returns the value of the field with the given name. If the value is not
|
312
|
+
# defined and the entry has cross-reference, returns the cross-referenced
|
313
|
+
# value instead.
|
314
|
+
def [](name)
|
315
|
+
fields[name.to_sym] || parent && parent.provide(name)
|
316
|
+
end
|
317
|
+
|
318
|
+
alias get []
|
319
|
+
|
320
|
+
def fetch(name, default = nil)
|
321
|
+
get(name) || block_given? ? yield(name) : default
|
322
|
+
end
|
323
|
+
|
324
|
+
# Adds a new field (name-value pair) to the entry.
|
325
|
+
# Returns the new value.
|
326
|
+
def []=(name, value)
|
327
|
+
add(name.to_sym, value)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Adds a new field (name-value pair) or multiple fields to the entry.
|
331
|
+
# Returns the entry for chainability.
|
332
|
+
#
|
333
|
+
# call-seq:
|
334
|
+
# add(:author, "Edgar A. Poe")
|
335
|
+
# add(:author, "Edgar A. Poe", :title, "The Raven")
|
336
|
+
# add([:author, "Edgar A. Poe", :title, "The Raven"])
|
337
|
+
# add(:author => "Edgar A. Poe", :title => "The Raven")
|
338
|
+
def add(*arguments)
|
339
|
+
Hash[*arguments.flatten].each_pair do |name, value|
|
340
|
+
fields[name.to_sym] = Value.new(value)
|
341
|
+
end
|
342
|
+
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
alias << add
|
347
|
+
|
348
|
+
# Removes the field with a given name from the entry.
|
349
|
+
# Returns the value of the deleted field; nil if the field was not set.
|
350
|
+
def delete(name)
|
351
|
+
fields.delete(name.to_sym)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns false if the entry is one of the standard entry types and does not have
|
355
|
+
# definitions of all the required fields for that type.
|
356
|
+
def valid?
|
357
|
+
REQUIRED_FIELDS[@type].all? do |f|
|
358
|
+
f.is_a?(Array) ? !(f & fields.keys).empty? : !fields[f].nil?
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
def generate_hash(filter = [])
|
363
|
+
Digest::MD5.hexdigest(field_names(filter).map { |k| [k, fields[k]] }.flatten.join)
|
364
|
+
end
|
365
|
+
|
366
|
+
# Called when the element was added to a bibliography.
|
367
|
+
def added_to_bibliography(bibliography)
|
368
|
+
super
|
369
|
+
|
370
|
+
@key = register(key)
|
371
|
+
|
372
|
+
[:parse_names, :parse_months].each do |parser|
|
373
|
+
send(parser) if bibliography.options[parser]
|
374
|
+
end
|
375
|
+
|
376
|
+
if bibliography.options.has_key?(:filter)
|
377
|
+
convert!(bibliography.options[:filter])
|
378
|
+
end
|
379
|
+
|
380
|
+
self
|
381
|
+
end
|
382
|
+
|
383
|
+
# Called when the element was removed from a bibliography.
|
384
|
+
def removed_from_bibliography(bibliography)
|
385
|
+
super
|
386
|
+
bibliography.entries.delete(key)
|
387
|
+
self
|
388
|
+
end
|
389
|
+
|
390
|
+
# Returns true if the Entry is currently registered with the associated Bibliography.
|
391
|
+
def registered?
|
392
|
+
!!(bibliography && bibliography.entries[key].equal?(self))
|
393
|
+
end
|
394
|
+
|
395
|
+
# Registers this Entry in the associated Bibliographies entries hash.
|
396
|
+
# This method may change the Entry's key, if another entry is already
|
397
|
+
# registered with the current key.
|
398
|
+
#
|
399
|
+
# Returns the key or nil if the Entry is not associated with a Bibliography.
|
400
|
+
def register(key)
|
401
|
+
return nil if bibliography.nil?
|
402
|
+
|
403
|
+
k = key.dup
|
404
|
+
k.succ! while bibliography.has_key?(k)
|
405
|
+
bibliography.entries[k] = self
|
406
|
+
k
|
407
|
+
end
|
408
|
+
|
409
|
+
def replace(*arguments)
|
410
|
+
arguments = bibliography.q('@string') if arguments.empty?
|
411
|
+
fields.values.each { |v| v.replace(*arguments) }
|
412
|
+
self
|
413
|
+
end
|
391
414
|
|
392
415
|
def join
|
393
|
-
|
416
|
+
fields.values.each(&:join)
|
394
417
|
self
|
395
418
|
end
|
396
419
|
|
@@ -399,12 +422,13 @@ module BibTeX
|
|
399
422
|
end
|
400
423
|
|
401
424
|
def parse_month
|
402
|
-
fields[:month] = MONTHS_FILTER[fields[:month]] if
|
425
|
+
fields[:month] = MONTHS_FILTER[fields[:month]] if has_field?(:month)
|
403
426
|
self
|
404
427
|
end
|
405
428
|
|
406
429
|
alias parse_months parse_month
|
407
430
|
|
431
|
+
|
408
432
|
# Parses all name values of the entry. Tries to replace and join the
|
409
433
|
# value prior to parsing.
|
410
434
|
def parse_names
|
@@ -417,154 +441,155 @@ module BibTeX
|
|
417
441
|
end
|
418
442
|
end
|
419
443
|
|
420
|
-
|
421
|
-
end
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
444
|
+
self
|
445
|
+
end
|
446
|
+
|
447
|
+
# Returns a list of all names (authors, editors, translators).
|
448
|
+
def names
|
449
|
+
NAME_FIELDS.map { |k| has_field?(k) ? @fields[k].tokens : nil }.flatten.compact
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
# Returns true if the Entry has a valid cross-reference in the Bibliography.
|
454
|
+
def has_parent?
|
455
|
+
!parent.nil?
|
456
|
+
end
|
457
|
+
|
458
|
+
alias has_cross_reference? has_parent?
|
459
|
+
|
460
|
+
# Returns true if the Entry cross-references an Entry which is not
|
461
|
+
# registered in the current Bibliography.
|
462
|
+
def parent_missing?
|
463
|
+
has_field?(:crossref) && !has_parent?
|
464
|
+
end
|
465
|
+
|
466
|
+
alias cross_reference_missing? parent_missing?
|
467
|
+
|
468
|
+
# Returns the cross-referenced Entry from the Bibliography or nil if this
|
469
|
+
# Entry does define a cross-reference.
|
470
|
+
def parent
|
471
|
+
bibliography && bibliography[fields[:crossref]]
|
472
|
+
end
|
473
|
+
|
474
|
+
alias cross_reference parent
|
475
|
+
|
476
|
+
|
477
|
+
# Returns true if the entry is cross-referenced by another entry in the
|
478
|
+
# Bibliography.
|
479
|
+
def has_children?
|
480
|
+
!children.empty?
|
481
|
+
end
|
482
|
+
|
483
|
+
alias cross_referenced? has_children?
|
484
|
+
|
485
|
+
# Returns a list of all entries in the Bibliography containing a
|
486
|
+
# cross-reference to this entry or [] if there are no references to this
|
487
|
+
# entry.
|
488
|
+
def children
|
489
|
+
bibliography && bibliography.q("@entry[crossref=#{key}]") or []
|
490
|
+
end
|
491
|
+
|
492
|
+
alias cross_referenced_by children
|
493
|
+
|
494
|
+
|
495
|
+
# Returns a string of all the entry's fields.
|
496
|
+
def content(options = {})
|
497
|
+
fields.map { |k,v| "#{k} = #{ fields[k].to_s(options) }" }.join(",\n")
|
498
|
+
end
|
499
|
+
|
500
|
+
# Returns a string representation of the entry.
|
501
|
+
def to_s(options = {})
|
502
|
+
options[:quotes] ||= %w({ })
|
503
|
+
["@#{type}{#{key},", content(options).gsub(/^/,' '), "}\n"].join("\n")
|
504
|
+
end
|
505
|
+
|
506
|
+
def to_hash(options = {})
|
507
|
+
options[:quotes] ||= %w({ })
|
508
|
+
hash = { :key => key, :type => type }
|
509
|
+
each_pair { |k,v| hash[k] = v.to_s(options) }
|
510
|
+
hash
|
511
|
+
end
|
512
|
+
|
513
|
+
def to_citeproc(options = {})
|
514
|
+
options[:quotes] ||= []
|
515
|
+
|
516
|
+
parse_names
|
517
|
+
parse_month
|
518
|
+
|
519
|
+
hash = { 'id' => key.to_s, 'type' => CSL_TYPES[type].to_s }
|
520
|
+
|
497
521
|
each_pair do |k,v|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
522
|
+
hash[CSL_FILTER[k].to_s] = v.to_citeproc(options) unless DATE_FIELDS.include?(k)
|
523
|
+
end
|
524
|
+
|
525
|
+
hash['issued'] = citeproc_date
|
526
|
+
hash
|
527
|
+
end
|
528
|
+
|
529
|
+
def issued
|
530
|
+
m = MONTHS.find_index(fields[:month].to_s.intern)
|
531
|
+
m = m + 1 unless m.nil?
|
532
|
+
|
533
|
+
Hash['date-parts', [[fields[:year],m].compact.map(&:to_i)]]
|
534
|
+
end
|
535
|
+
|
536
|
+
alias citeproc_date issued
|
537
|
+
|
538
|
+
def to_xml(options = {})
|
539
|
+
require 'rexml/document'
|
540
|
+
|
541
|
+
xml = REXML::Element.new('bibtex:entry')
|
542
|
+
xml.attributes['id'] = key
|
543
|
+
|
544
|
+
entry = REXML::Element.new("bibtex:#{type}")
|
520
545
|
|
521
546
|
fields.each do |key, value|
|
522
547
|
field = REXML::Element.new("bibtex:#{key}")
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
548
|
+
|
549
|
+
if options[:extended] && value.name?
|
550
|
+
value.each { |n| entry.add_element(n.to_xml) }
|
551
|
+
else
|
552
|
+
field.text = value.to_s(options)
|
553
|
+
end
|
554
|
+
|
530
555
|
entry.add_element(field)
|
531
556
|
end
|
532
557
|
|
533
558
|
xml.add_element(entry)
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
559
|
+
xml
|
560
|
+
end
|
561
|
+
|
562
|
+
# Returns a duplicate entry with all values converted using the filter.
|
563
|
+
# If an optional block is given, only those values will be converted where
|
564
|
+
# the block returns true (the block will be called with each key-value pair).
|
565
|
+
#
|
566
|
+
# @see #convert!
|
567
|
+
def convert(filter)
|
568
|
+
block_given? ? dup.convert!(filter, &Proc.new) : dup.convert!(filter)
|
569
|
+
end
|
570
|
+
|
571
|
+
# In-place variant of @see #convert
|
572
|
+
def convert!(filter)
|
573
|
+
fields.each_pair { |k,v| !block_given? || yield(k,v) ? v.convert!(filter) : v }
|
574
|
+
self
|
575
|
+
end
|
576
|
+
|
577
|
+
def <=>(other)
|
578
|
+
type != other.type ? type <=> other.type : key != other.key ? key <=> other.key : to_s <=> other.to_s
|
579
|
+
end
|
580
|
+
|
581
|
+
private
|
582
|
+
|
583
|
+
# Returns a default key for this entry.
|
584
|
+
def default_key
|
585
|
+
k = names[0]
|
586
|
+
k = k.respond_to?(:family) ? k.family : k.to_s
|
587
|
+
k = k[/[A-Za-z]+/] || 'unknown'
|
588
|
+
k << (has_field?(:year) ? year : '-')
|
589
|
+
k << 'a'
|
590
|
+
k.downcase!
|
591
|
+
k
|
592
|
+
end
|
593
|
+
|
594
|
+
end
|
570
595
|
end
|