bibtex-ruby 1.2.1 → 1.3.0

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.

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