bibtex-ruby 1.2.1 → 1.3.0

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.

Files changed (67) hide show
  1. data/Gemfile +6 -1
  2. data/Gemfile.lock +48 -5
  3. data/History.txt +16 -1
  4. data/Manifest +43 -19
  5. data/README.md +178 -167
  6. data/Rakefile +26 -5
  7. data/auto.watchr +6 -0
  8. data/bibtex-ruby.gemspec +8 -5
  9. data/examples/bib2html.rb +28 -18
  10. data/features/bibtex.feature +96 -0
  11. data/features/entries.feature +67 -0
  12. data/features/issues/slash_keys.feature +21 -0
  13. data/features/names.feature +72 -0
  14. data/features/preambles.feature +27 -0
  15. data/features/query.feature +56 -0
  16. data/features/replacement.feature +68 -0
  17. data/features/step_definitions/bibtex_steps.rb +74 -0
  18. data/features/step_definitions/name_steps.rb +13 -0
  19. data/features/strings.feature +52 -0
  20. data/features/support/env.rb +7 -0
  21. data/lib/bibtex.rb +5 -1
  22. data/lib/bibtex/bibliography.rb +218 -95
  23. data/lib/bibtex/bibtex.y +18 -15
  24. data/lib/bibtex/elements.rb +130 -136
  25. data/lib/bibtex/entry.rb +133 -69
  26. data/lib/bibtex/extensions.rb +0 -35
  27. data/lib/bibtex/lexer.rb +9 -9
  28. data/lib/bibtex/name_parser.output +464 -0
  29. data/lib/bibtex/name_parser.rb +490 -0
  30. data/lib/bibtex/names.rb +162 -0
  31. data/lib/bibtex/names.y +196 -0
  32. data/lib/bibtex/parser.output +5 -5
  33. data/lib/bibtex/parser.rb +19 -16
  34. data/lib/bibtex/replaceable.rb +52 -0
  35. data/lib/bibtex/utilities.rb +23 -5
  36. data/lib/bibtex/value.rb +201 -0
  37. data/lib/bibtex/version.rb +1 -1
  38. data/test/benchmark.rb +52 -0
  39. data/test/bibtex/test_bibliography.rb +141 -0
  40. data/test/bibtex/test_elements.rb +40 -0
  41. data/test/bibtex/test_entry.rb +99 -0
  42. data/test/bibtex/test_names.rb +23 -0
  43. data/test/bibtex/test_parser.rb +79 -0
  44. data/test/bibtex/test_string.rb +83 -0
  45. data/test/bibtex/test_utilities.rb +34 -0
  46. data/test/bibtex/test_value.rb +70 -0
  47. data/test/{bib/10_bibdesk.bib → fixtures/bibdesk.bib} +1 -1
  48. data/test/{bib/05_comment.bib → fixtures/comment.bib} +0 -0
  49. data/test/{bib/08_decoret.bib → fixtures/decoret.bib} +0 -0
  50. data/test/{bib/00_empty.bib → fixtures/empty.bib} +0 -0
  51. data/test/{bib/07_entry.bib → fixtures/entry.bib} +0 -0
  52. data/test/{bib/09_errors.bib → fixtures/errors.bib} +0 -0
  53. data/test/{bib/01_no_bibtex.bib → fixtures/no_bibtex.bib} +0 -0
  54. data/test/{bib/06_preamble.bib → fixtures/preamble.bib} +1 -1
  55. data/test/{bib/11_roundtrip.bib → fixtures/roundtrip.bib} +1 -1
  56. data/test/helper.rb +17 -2
  57. data/test/test_bibtex.rb +87 -93
  58. data/test/test_export.rb +18 -22
  59. metadata +85 -30
  60. data/test/bib/02_string.bib +0 -1
  61. data/test/bib/03_string.bib +0 -25
  62. data/test/bib/04_string_replacement.bib +0 -16
  63. data/test/test_comment.rb +0 -21
  64. data/test/test_entry.rb +0 -98
  65. data/test/test_preamble.rb +0 -39
  66. data/test/test_string.rb +0 -97
  67. data/test/test_utilities.rb +0 -36
@@ -0,0 +1,68 @@
1
+ Feature: BibTeX String Replacement
2
+ As a hacker who works with bibliographies
3
+ I want to be able to parse BibTeX files with string assignments
4
+ And replace string symbols in other values
5
+ Because that is a cool BibTeX feature
6
+
7
+ @string @replacement
8
+ Scenario: A BibTeX file with string assignments and symbols
9
+ When I parse the following file:
10
+ """
11
+ %%
12
+ %% A valid BibTeX file
13
+ %% String assignment and replacement
14
+ %%
15
+
16
+ @string{ foo = "foo" }
17
+ @string{ bar = "bar" }
18
+ @string{ foobar = foo # "bar" }
19
+ @string{ foobarfoo = foobar # foo }
20
+ @string{ barfoobar = bar # "foo" # bar }
21
+
22
+ @preamble { "foo" # foo # foobarfoo # "bar" }
23
+
24
+ @manual {manual:1,
25
+ title = "foo" # barfoobar
26
+ }
27
+ """
28
+ Then my bibliography should contain these strings:
29
+ | value |
30
+ | foo |
31
+ | bar |
32
+ | foo # "bar" |
33
+ | foobar # foo |
34
+ | bar # "foo" # bar |
35
+ And my bibliography should contain these preambles:
36
+ | content |
37
+ | "foo" # foo # foobarfoo # "bar" |
38
+ And my bibliography should contain these manuals:
39
+ | title |
40
+ | "foo" # barfoobar |
41
+ When I replace all strings in my bibliography
42
+ Then my bibliography should contain these strings:
43
+ | content |
44
+ | foo = "foo" |
45
+ | bar = "bar" |
46
+ | foobar = "foo" # "bar" |
47
+ | foobarfoo = "foo" # "bar" # "foo" |
48
+ | barfoobar = "bar" # "foo" # "bar" |
49
+ And my bibliography should contain these preambles:
50
+ | content |
51
+ | "foo" # "foo" # "foo" # "bar" # "foo" # "bar" |
52
+ And my bibliography should contain these manuals:
53
+ | title |
54
+ | "foo" # "bar" # "foo" # "bar" |
55
+ When I join all strings in my bibliography
56
+ Then my bibliography should contain these strings:
57
+ | content |
58
+ | foo = "foo" |
59
+ | bar = "bar" |
60
+ | foobar = "foobar" |
61
+ | foobarfoo = "foobarfoo" |
62
+ | barfoobar = "barfoobar" |
63
+ And my bibliography should contain these preambles:
64
+ | content |
65
+ | "foofoofoobarfoobar" |
66
+ And my bibliography should contain these manuals:
67
+ | title |
68
+ | foobarfoobar |
@@ -0,0 +1,74 @@
1
+ Given /^the bibliography:$/ do |string|
2
+ @bibliography = BibTeX.parse(string)
3
+ end
4
+
5
+ When /^I parse the following file:$/ do |string|
6
+ @bibliography = BibTeX.parse(string)
7
+ end
8
+
9
+ When /^I search for "([^"]*)"$/ do |query|
10
+ @result = @bibliography.query(query)
11
+ end
12
+
13
+ When /^I search for :(.+)$/ do |query|
14
+ @result = @bibliography.query(query.to_sym)
15
+ end
16
+
17
+ When /^I search for \/(.+)\/$/ do |query|
18
+ @result = @bibliography.query(Regexp.new(query))
19
+ end
20
+
21
+ When /^I (replace|join) all strings(?: in my bibliography)$/ do |method|
22
+ @bibliography.send("#{method}_strings")
23
+ end
24
+
25
+ When /^I replace and join all strings(?: in my bibliography)$/ do
26
+ @bibliography.replace_strings.join_strings
27
+ end
28
+
29
+
30
+
31
+ Then /^my bibliography should contain the following objects:$/ do |table|
32
+ @bibliography.each_with_index do |object, index|
33
+ table.hashes[index].each_pair do |key, value|
34
+ assert_equal value, object.send(key).to_s
35
+ end
36
+ end
37
+ end
38
+
39
+ Then /^my bibliography should contain th(?:ese|is) (\w+):$/ do |type, table|
40
+ @bibliography.q("@#{type.chomp!('s')}").zip(table.hashes).each do |object, expected|
41
+ expected.each_pair do |key, value|
42
+ assert_equal value, object.send(key).to_s
43
+ end
44
+ end
45
+ end
46
+
47
+ Then /^my bibliography should contain the following numbers of elements:$/ do |table|
48
+ counts = table.hashes.first
49
+ counts[[]] = counts.delete('total') if counts.has_key?('total')
50
+ counts.each_pair do |type, length|
51
+ assert_equal length.to_i, @bibliography.find_by_type(type).length
52
+ end
53
+ end
54
+
55
+ Then /^my bibliography should contain an entry with key "([^"]*)"$/ do |key|
56
+ refute_nil @bibliography[key.to_s]
57
+ end
58
+
59
+ Then /^my bibliography should not contain an entry with key "([^"]*)"$/ do |key|
60
+ assert_nil @bibliography[key.to_sym]
61
+ end
62
+
63
+ Then /^there should be exactly (\d+) match(?:es)?$/ do |matches|
64
+ assert_equal matches.to_i, @result.length
65
+ end
66
+
67
+
68
+ Then /^my bibliography should contain (\d+) (\w+) published in (\d+)$/ do |count, type, year|
69
+ assert_equal @bibliography.q("@#{type.chomp!('s')}[year=#{year}]").length, count.to_i
70
+ end
71
+
72
+ Then /^my bibliography should contain an? (\w+) with id "([^"]*)"$/ do |type, id|
73
+ assert_equal @bibliography[id.to_sym].type, type.to_sym
74
+ end
@@ -0,0 +1,13 @@
1
+ When /^I parse the name "(.*)"$/ do |string|
2
+ @name = BibTeX::Name.parse(string)
3
+ end
4
+
5
+ Then /^the parts should be:$/ do |table|
6
+ table.hashes.each do |row|
7
+ assert_equal [row['first'], row['von'], row['last'], row['jr']],
8
+ [@name.first, @name.von, @name.last, @name.jr].map(&:to_s)
9
+ # row.each do |k,v|
10
+ # assert_equal v, @name.send(k).to_s
11
+ # end
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ Feature: BibTeX Strings
2
+ As a hacker who works with bibliographies
3
+ I want to be able to parse BibTeX files containing string assignments
4
+
5
+ Scenario: A BibTeX file with string assignments
6
+ When I parse the following file:
7
+ """
8
+ %%
9
+ %% This is a valid BibTeX file
10
+ %% String assignments Test
11
+ %%
12
+
13
+ @string{foo="bar"}
14
+ @ string{foo="bar"}
15
+ @string {foo="bar"}
16
+ @string{ foo="bar"}
17
+ @string{foo ="bar"}
18
+ @string{foo= "bar"}
19
+ @string{foo="bar" }
20
+ @ string { foo = "bar" }
21
+ @string { foo= "bar"}
22
+ @string{ foo = "bar" }
23
+
24
+ @string{foo="bar"}
25
+ @string{foo="'bar'"}
26
+ @string{foo="{"}bar{"}"}
27
+
28
+ Using some interesting symbols
29
+ @string{foo="@bar@"}
30
+ @string{foo="'bar'"}
31
+ @string{foo="{"}bar{"}"}
32
+ @string{foo="{bar}"}
33
+ """
34
+ Then my bibliography should contain the following objects:
35
+ | type | value |
36
+ | string | bar |
37
+ | string | bar |
38
+ | string | bar |
39
+ | string | bar |
40
+ | string | bar |
41
+ | string | bar |
42
+ | string | bar |
43
+ | string | bar |
44
+ | string | bar |
45
+ | string | bar |
46
+ | string | bar |
47
+ | string | 'bar' |
48
+ | string | {"}bar{"} |
49
+ | string | @bar@ |
50
+ | string | 'bar' |
51
+ | string | {"}bar{"} |
52
+ | string | {bar} |
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
2
+ require 'bibtex'
3
+ require 'minitest/unit'
4
+
5
+ World do
6
+ extend MiniTest::Assertions
7
+ end
@@ -31,7 +31,7 @@ require 'logger'
31
31
  # +Entry+.
32
32
  #
33
33
  # Author:: {Sylvester Keil}[http://sylvester.keil.or.at]
34
- # Copyright:: Copyright (c) 2010 Sylvester Keil
34
+ # Copyright:: Copyright (c) 2010-2011 Sylvester Keil
35
35
  # License:: GNU GPL 3.0
36
36
  #
37
37
  module BibTeX
@@ -52,6 +52,10 @@ end
52
52
  # Debugger.start
53
53
 
54
54
  require 'bibtex/extensions'
55
+ require 'bibtex/value'
56
+ require 'bibtex/name_parser'
57
+ require 'bibtex/names'
58
+ require 'bibtex/replaceable'
55
59
  require 'bibtex/elements'
56
60
  require 'bibtex/entry'
57
61
  require 'bibtex/error'
@@ -1,6 +1,6 @@
1
1
  #--
2
2
  # BibTeX-Ruby
3
- # Copyright (C) 2010 Sylvester Keil <sylvester.keil.or.at>
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
@@ -16,6 +16,9 @@
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  #++
18
18
 
19
+ require 'forwardable'
20
+ require 'open-uri'
21
+
19
22
  module BibTeX
20
23
 
21
24
  #
@@ -23,94 +26,157 @@ module BibTeX
23
26
  # typically, it corresponds to a `.bib' file.
24
27
  #
25
28
  class Bibliography
29
+ extend Forwardable
30
+
31
+ include Enumerable
32
+ include Comparable
33
+
34
+ class << self
35
+
36
+ # Opens and parses the `.bib' file at the given +path+. Returns
37
+ # a new Bibliography instance corresponding to the file, or, if a block
38
+ # is given, yields the instance to the block, ensuring that the file
39
+ # is saved after the block's execution (use the :out option if you want
40
+ # to specify a save path other than the path from where the file is
41
+ # loaded).
42
+ #
43
+ # The options argument is passed on to BibTeX::Parser.new.
44
+ #
45
+ def open(path, options = {})
46
+ b = parse(Kernel.open(path).read, options)
47
+ return b unless block_given?
48
+
49
+ begin
50
+ yield b
51
+ ensure
52
+ b.save_to(options[:out] || path)
53
+ end
54
+ end
26
55
 
56
+ # Parses the given string and returns a corresponding Bibliography instance.
57
+ def parse(bibtex, options = {})
58
+ b = Parser.new(options).parse(bibtex)
59
+ b.parse_names unless options[:parse_names] == false
60
+ b
61
+ end
62
+
63
+ #
64
+ # Defines a new accessor that selects elements by type.
65
+ #
66
+ def attr_by_type(*arguments)
67
+ arguments.each do |type|
68
+ method_id = "#{type}s"
69
+ define_method(method_id) { find_by_type(type) } unless respond_to?(method_id)
70
+ end
71
+ end
72
+ end
73
+
27
74
  attr_accessor :path
28
75
  attr_reader :data, :strings, :entries, :errors
29
76
 
30
- #
31
- # Opens and parses the `.bib' file at the given +path+. Returns
32
- # a new Bibliography instance corresponding to the file.
33
- #
34
- # The options argument is passed on to BibTeX::Parser.new.
35
- #
36
- def self.open(path, options={})
37
- Log.debug('Opening file ' + path.to_s)
38
- BibTeX::Parser.new(options).parse(File.read(path))
39
- end
77
+ attr_by_type :article, :book, :journal, :collection, :preamble, :comment, :meta_content
78
+
79
+ def_delegators :@data, :length, :size, :each, :empty?
80
+
40
81
 
41
82
  #
42
- # Creates a new bibliography; empty if no data attribute is specified, otherwise
43
- # by parsing the file at the given path.
83
+ # Creates a new bibliography; empty if no data attribute is specified.
44
84
  #
45
- def initialize(data=[])
46
- @path = path
85
+ def initialize(data = [])
47
86
  @data = []
48
87
  @strings = {}
49
88
  @entries = {}
50
- @errors = []
51
89
  add(data)
52
90
  end
53
91
 
54
92
  # Adds a new element, or a list of new elements to the bibliography.
55
- def add(data)
56
- raise(ArgumentError,'BibTeX::Bibliography.add data expected to be enumerable or of type BibTeX::Element; was: ' + data.class.name) unless data.respond_to?(:each) || data.is_a?(Element)
57
- data.is_a?(Element) ? self << data : data.each { |d| self << d }
93
+ # Returns the Bibliography for chainability.
94
+ def add(*arguments)
95
+ arguments.flatten.each do |element|
96
+ raise(ArgumentError, "Failed to add #{ element.inspect } to Bibliography; instance of BibTeX::Element expected.") unless element.is_a?(Element)
97
+ @data << element.added_to_bibliography(self)
98
+ end
58
99
  self
59
100
  end
60
101
 
102
+ alias :<< :add
103
+ alias :push :add
104
+
61
105
  # Saves the bibliography to the current path.
62
- def save
63
- save_to(@path)
106
+ def save(options = {})
107
+ save_to(@path, options)
64
108
  end
65
109
 
66
- # Saves the bibliography to a file at the given path.
67
- def save_to(path)
68
- File.open(path, "w") do |f|
69
- f.write to_s
70
- end
110
+ # Saves the bibliography to a file at the given path. Returns the bibliography.
111
+ def save_to(path, options = {})
112
+ options[:quotes] ||= %w({ })
113
+ File.open(path, "w") { |f| f.write(to_s(options)) }
114
+ self
71
115
  end
72
116
 
73
- # Add an object to the bibliography. Returns the bibliography.
74
- def <<(obj)
75
- raise(ArgumentError, 'A BibTeX::Bibliography can contain only BibTeX::Elements; was: ' + obj.class.name) unless obj.is_a?(Element)
76
- @data << obj.added_to_bibliography(self)
117
+ def parse_names
118
+ q('@entry') { |e| e.parse_names }
77
119
  self
78
120
  end
79
121
 
80
- # Delete an object from the bibliography. Returns the object, or nil
122
+ #
123
+ # Deletes an object, or a list of objects from the bibliography.
124
+ # If a list of objects is to be deleted, you can either supply the list
125
+ # of objects or use a query or block to define the list.
126
+ #
127
+ # Returns the object (or the list of objects) that were deleted; nil
81
128
  # if the object was not part of the bibliography.
82
- def delete(obj)
83
- @data.delete(obj.removed_from_bibliography(self))
84
- end
85
-
86
- def delete_all
87
- @data.each { |obj| obj.removed_from_bibliography(self) }
88
- @data = []
89
- end
90
-
91
- # Returns all @preamble objects.
92
- def preamble
93
- find_by_type(BibTeX::Preamble)
129
+ #
130
+ def delete(*arguments, &block)
131
+ objects = q(*arguments, &block).map { |o| o.removed_from_bibliography(self) }
132
+ @data = @data - objects
133
+ objects.length == 1 ? objects[0] : objects
94
134
  end
95
135
 
96
- # Returns the first entry with a given key.
97
- def [](key)
98
- @entries[key.to_s]
99
- end
100
-
101
- # Returns all @comment objects.
102
- def comments
103
- find_by_type(BibTeX::Comment)
104
- end
136
+ alias :remove :delete
137
+ alias :rm :delete
138
+
139
+ #
140
+ # Returns an element or a list of elements according to the given index,
141
+ # range, or query. Contrary to the Bibliography#query this method does
142
+ # not yield to a block for additional refinement of the query.
143
+ #
144
+ # call-seq:
145
+ # >> bib[-1]
146
+ # => Returns the last element of the Bibliography or nil
147
+ # >> bib[1,2]
148
+ # => Returns the second and third elements or nil
149
+ # >> bib[1..2]
150
+ # >> Same as above
151
+ # >> bib[:key]
152
+ # => Returns the first entry with key 'key' or nil
153
+ # >> bib['key']
154
+ # => Returns all entries with key 'key' or []
155
+ # >> bib['@article']
156
+ # => Returns all entries of type 'article' or []
157
+ # >> bib['@preamble']
158
+ # => Returns all preamble objects (this is the same as Bibliography#preambles) or []
159
+ # >> bib[/ruby/]
160
+ # => Returns all objects that match 'ruby' anywhere or []
161
+ # >> bib['@book[keywords=ruby]']
162
+ # => Returns all books whose keywords attribute equals 'ruby' or []
163
+ #
164
+ def [](*arguments)
165
+ raise(ArgumentError, "wrong number of arguments (#{arguments.length} for 1..2)") unless arguments.length.between?(1,2)
105
166
 
106
- # Returns all meta comments, i.e., all text outside of BibTeX objects.
107
- def meta_comments
108
- find_by_type(BibTeX::MetaComment)
167
+ case
168
+ when !([Range, Numeric] & arguments[0].class.ancestors).empty?
169
+ @data[*arguments]
170
+ when arguments.length == 1 && arguments[0].is_a?(Symbol)
171
+ @entries[arguments[0]]
172
+ else
173
+ query(*arguments)
174
+ end
109
175
  end
110
176
 
111
177
  # Returns all objects which could not be parsed successfully.
112
178
  def errors
113
- @errors
179
+ @errors ||= []
114
180
  end
115
181
 
116
182
  # Returns true if there are object which could not be parsed.
@@ -119,82 +185,139 @@ module BibTeX
119
185
  end
120
186
 
121
187
  # Returns true if the +Bibliography+ contains no errors and only
122
- # valid BibTeX objects (meta comments are ignored).
188
+ # valid BibTeX objects (meta content is ignored).
123
189
  def valid?
124
- !errors? && !@entries.values.map(&:valid?).uniq.include?(false)
190
+ !errors? && @entries.values.all?(&:valid?)
125
191
  end
126
192
 
127
- # Replaces all string constants which are defined in the bibliography.
193
+ # Replaces all string symbols which are defined in the bibliography.
128
194
  #
129
- # By default constants in @string, @preamble and entries are defined; this
130
- # behaviour can be changed using the options argument by setting
131
- # the :include option to a list of types.
195
+ # By default symbols in @string, @preamble and entries are replaced; this
196
+ # behaviour can be changed using the optional query parameter.
132
197
  #
133
198
  # Note that strings are replaced in the order in which they occur in the
134
199
  # bibliography.
135
200
  #
136
201
  # call-seq:
137
- # replace_strings
138
- # replace_strings({ :include => [BibTeX::String,BibTeX::Preamble]})
202
+ # bib.replace #=> replaces all symbols
203
+ # bib.replace('@string, @preamble')
204
+ # #=> replaces only symbols in @string and @preamble objects
139
205
  #
140
- def replace_strings(options={})
141
- options[:include] ||= [BibTeX::String, BibTeX::Preamble, BibTeX::Entry]
142
- find_by_type(options[:include]).each { |e| e.replace!(@strings) if e.respond_to?(:replace!)}
206
+ def replace(filter = '')
207
+ q(filter) { |e| e.replace(@strings.values) }
208
+ self
209
+ end
210
+
211
+ alias :replace_strings :replace
212
+
213
+ def join(filter = '')
214
+ q(filter, &:join)
215
+ self
143
216
  end
217
+
218
+ alias :join_strings :join
144
219
 
145
- def join_strings(options={})
146
- options[:include] ||= [BibTeX::String, BibTeX::Preamble, BibTeX::Entry]
147
- find_by_type(options[:include]).each { |e| e.join! if e.respond_to?(:join!)}
220
+ def rename(*arguments, &block)
221
+ q('@entry') { |e| e.rename(*arguments, &block) }
222
+ self
148
223
  end
149
224
 
150
- # Returns true if the bibliography is currently empty.
151
- def empty?
152
- @data.empty?
225
+ def sort(*arguments, &block)
226
+ @data.sort(*arguments, &block)
227
+ self
153
228
  end
154
229
 
155
- # Returns the number of objects in the bibliography (including meta comments).
156
- def length
157
- @data.length
230
+ # Returns a string representation of the bibliography.
231
+ def to_s(options = {})
232
+ map { |o| o.to_s(options) }.join
158
233
  end
159
234
 
160
- # Returns the bibliography as an array of +BibTeX::Element+
161
- def to_a
162
- @data
235
+ def to_a(options = {})
236
+ map { |o| o.to_hash(options) }
163
237
  end
164
238
 
165
- # Returns a string representation of the bibliography.
166
- def to_s
167
- @data.map(&:to_s).join
239
+ # Returns a Ruby hash representation of the bibliography.
240
+ def to_hash(options = {})
241
+ { :bibliography => map { |o| o.to_hash(options) } }
242
+ end
243
+
244
+ # Returns a YAML representation of the bibliography.
245
+ def to_yaml(options = {})
246
+ to_a(options).to_yaml
168
247
  end
169
248
 
170
- # Returns a YAML representation of the bibliography. Only BibTeX entries are exported.
171
- def to_yaml
172
- @entries.values.map(&:to_hash).to_yaml
249
+ # Returns a JSON representation of the bibliography.
250
+ def to_json(options = {})
251
+ to_a(options).to_json
173
252
  end
174
253
 
175
- # Returns a JSON representation of the bibliography. Only BibTeX entries are exported.
176
- def to_json
177
- @entries.values.map(&:to_hash).to_json
254
+ # Returns a CiteProc JSON representation of the bibliography. Only BibTeX enrties are exported.
255
+ def to_citeproc(options = {})
256
+ q('@entry').map { |o| o.to_citeproc(options) }
178
257
  end
179
258
 
180
259
  # Returns an XML representation of the bibliography. Only BibTeX entries are exported.
181
260
  def to_xml
261
+ require 'rexml/document'
262
+
182
263
  xml = REXML::Document.new
183
264
  xml << REXML::XMLDecl.new('1.0','UTF-8')
184
265
  root = REXML::Element.new('bibliography')
185
- @entries.values.each { |e| root.add_element(e.to_xml) }
266
+ each { |e| root.add_element(e.to_xml) }
186
267
  xml << root
187
268
  xml
188
269
  end
189
-
190
- private
191
270
 
192
- def find_by_type(type)
193
- @data.find_all { |x| type.respond_to?(:inject) ? type.inject(false) { |s,n| s || x.is_a?(n) } : x.is_a?(type) }
271
+ # Returns objects in the Bibliography which match the given selector and,
272
+ # optionally, the conditions specified in the given block.
273
+ #
274
+ # call-seq:
275
+ # bib.query() #=> returns all objects
276
+ # bib.query(:all) #=> returns all objects
277
+ # bib.query(:first) #=> returns the first object
278
+ # bib.query('@book') #=> returns all books
279
+ # bib.query(:first, '@book, @article')
280
+ # #=> returns the first book or article
281
+ # bib.query('@book[year=2011], @article)
282
+ # #=> returns all books published in 2011 and all articles
283
+ # bib.query('@book, @article) { |o| o.year == '2011' }
284
+ # #=> returns all books and articles published in 2011
285
+ # bib.query('@book[year=2011], @article[year=2011])
286
+ # #=> same as above without using a block
287
+ #
288
+ def query(*arguments, &block)
289
+ raise(ArgumentError, "wrong number of arguments (#{arguments.length} for 0..2)") unless arguments.length.between?(0,2)
290
+
291
+ q, selector = arguments.reverse
292
+ filter = block ? Proc.new { |e| e.match?(q) && block.call(e) } : Proc.new { |e| e.match?(q) }
293
+
294
+ send(query_handler(selector), &filter)
295
+ end
296
+
297
+ alias :q :query
298
+
299
+ def find_by_type(*types, &block)
300
+ q(types.flatten.compact.map { |t| "@#{t}" }.join(', '), &block)
194
301
  end
302
+
303
+ alias :find_by_types :find_by_type
195
304
 
196
- def find_entry(key)
197
- entries.find { |e| e.key == key.to_s }
305
+ def <=>(other)
306
+ other.respond_to?(:to_a) ? to_a <=> other.to_a : nil
198
307
  end
308
+
309
+ private
310
+
311
+ def query_handler(selector)
312
+ case selector
313
+ when /first|distinct|detect/i
314
+ :detect
315
+ when /none|reject|not/i
316
+ :reject
317
+ else
318
+ :select
319
+ end
320
+ end
321
+
199
322
  end
200
323
  end