oaipmh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/README ADDED
@@ -0,0 +1,3 @@
1
+ README for oaipmh
2
+ =================
3
+
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ include FileUtils
11
+ require File.join(File.dirname(__FILE__), 'lib', 'oaipmh', 'version')
12
+
13
+ AUTHOR = "will"
14
+ EMAIL = "your contact email for bug fixes and info"
15
+ DESCRIPTION = "description of gem"
16
+ RUBYFORGE_PROJECT = "oaipmh"
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+ BIN_FILES = %w( )
19
+
20
+
21
+ NAME = "oaipmh"
22
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
23
+ VERS = ENV['VERSION'] || (Oaipmh::VERSION::STRING + (REV ? ".#{REV}" : ""))
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = ['--quiet', '--title', "oaipmh documentation",
26
+ "--opname", "index.html",
27
+ "--line-numbers",
28
+ "--main", "README",
29
+ "--inline-source"]
30
+
31
+ desc "Packages up oaipmh gem."
32
+ task :default => [:test]
33
+ task :package => [:clean]
34
+
35
+ Rake::TestTask.new("test") { |t|
36
+ t.libs << "test"
37
+ t.pattern = "test/**/*_test.rb"
38
+ t.verbose = true
39
+ }
40
+
41
+ spec =
42
+ Gem::Specification.new do |s|
43
+ s.name = NAME
44
+ s.version = VERS
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = ["README", "CHANGELOG"]
48
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
49
+ s.summary = DESCRIPTION
50
+ s.description = DESCRIPTION
51
+ s.author = AUTHOR
52
+ s.email = EMAIL
53
+ s.homepage = HOMEPATH
54
+ s.executables = BIN_FILES
55
+ s.rubyforge_project = RUBYFORGE_PROJECT
56
+ s.bindir = "bin"
57
+ s.require_path = "lib"
58
+ s.autorequire = "oaipmh"
59
+
60
+ #s.add_dependency('activesupport', '>=1.3.1')
61
+ #s.required_ruby_version = '>= 1.8.2'
62
+
63
+ s.files = %w(README CHANGELOG Rakefile) +
64
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
65
+ Dir.glob("ext/**/*.{h,c,rb}") +
66
+ Dir.glob("examples/**/*.rb") +
67
+ Dir.glob("tools/*.rb")
68
+
69
+ # s.extensions = FileList["ext/**/extconf.rb"].to_a
70
+ end
71
+
72
+ Rake::GemPackageTask.new(spec) do |p|
73
+ p.need_tar = true
74
+ p.gem_spec = spec
75
+ end
76
+
77
+ task :install do
78
+ name = "#{NAME}-#{VERS}.gem"
79
+ sh %{rake package}
80
+ sh %{sudo gem install pkg/#{name}}
81
+ end
82
+
83
+ task :uninstall => [:clean] do
84
+ sh %{sudo gem uninstall #{NAME}}
85
+ end
@@ -0,0 +1,9 @@
1
+ # Copyright (C) 2006 William Groppe
2
+ #
3
+ # Will Groppe mailto:wfg@artstor.org
4
+ #
5
+ #
6
+
7
+ Dir[File.join(File.dirname(__FILE__), 'oaipmh/**/*.rb')].sort.each do |lib|
8
+ require lib
9
+ end
@@ -0,0 +1,31 @@
1
+ module OaiPmh
2
+
3
+ module Const
4
+
5
+ # OAI defines six verbs with various allowable options.
6
+ VERBS = {
7
+ 'Identify' => [],
8
+ 'ListMetadataFormats' => [],
9
+ 'ListSets' => [:token],
10
+ 'GetRecord' => [:id, :from, :until, :set, :prefix, :token],
11
+ 'ListIdentifiers' => [:from, :until, :set, :prefix, :token],
12
+ 'ListRecords' => [:from, :until, :set, :prefix, :token]
13
+ }.freeze
14
+
15
+ # Common to many data source, and sadly also a method on object.
16
+ RESERVED_WORDS = %{type}.freeze
17
+
18
+ # Default configuration of a repository
19
+ DEFAULTS = {
20
+ :name => 'Open Archives Initiative Data Provider',
21
+ :url => 'unknown',
22
+ :prefix => 'oai:localhost',
23
+ :email => 'nobody@localhost',
24
+ :deletes => 'no',
25
+ :granularity => 'YYYY-MM-DDThh:mm:ssZ',
26
+ :formats => OaiPmh::METADATA
27
+ }.freeze
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,72 @@
1
+ module OaiPmh
2
+
3
+ # Standard error responses for problems serving OAI content. These
4
+ # messages will be wrapped in an XML response to the client.
5
+
6
+ class OAIException < RuntimeError
7
+ attr_reader :code
8
+
9
+ def initialize(code, message)
10
+ super(message)
11
+ @code = code
12
+ end
13
+ end
14
+
15
+ class ArgumentException < OAIException
16
+ def initialize()
17
+ super('badArgument', 'The request includes ' \
18
+ 'illegal arguments, is missing required arguments, includes a ' \
19
+ 'repeated argument, or values for arguments have an illegal syntax.')
20
+ end
21
+ end
22
+
23
+ class VerbException < OAIException
24
+ def initialize()
25
+ super('badVerb', 'Value of the verb argument is not a legal OAI-PMH '\
26
+ 'verb, the verb argument is missing, or the verb argument is repeated.')
27
+ end
28
+ end
29
+
30
+ class FormatException < OAIException
31
+ def initialize()
32
+ super('cannotDisseminateFormat', 'The metadata format identified by '\
33
+ 'the value given for the metadataPrefix argument is not supported '\
34
+ 'by the item or by the repository.')
35
+ end
36
+ end
37
+
38
+ class IdException < OAIException
39
+ def initialize()
40
+ super('idDoesNotExist', 'The value of the identifier argument is '\
41
+ 'unknown or illegal in this repository.')
42
+ end
43
+ end
44
+
45
+ class NoMatchException < OAIException
46
+ def initialize()
47
+ super('noRecordsMatch', 'The combination of the values of the from, '\
48
+ 'until, set and metadataPrefix arguments results in an empty list.')
49
+ end
50
+ end
51
+
52
+ class MetadataFormatException
53
+ def initialize()
54
+ super('noMetadataFormats', 'There are no metadata formats available '\
55
+ 'for the specified item.')
56
+ end
57
+ end
58
+
59
+ class SetException < OAIException
60
+ def initialize()
61
+ super('noSetHierarchy', 'This repository does not support sets.')
62
+ end
63
+ end
64
+
65
+ class ResumptionTokenException < OAIException
66
+ def initialize()
67
+ super('badResumptionToken', 'The value of the resumptionToken argument '\
68
+ 'is invalid or expired.')
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,22 @@
1
+ require 'oaipmh'
2
+
3
+ module OaiPmh
4
+ module Extensions
5
+ module Camped
6
+
7
+ def self.included(mod)
8
+ instance_eval(%{module #{mod}::Controllers
9
+ class Oai
10
+ def get
11
+ @headers['Content-Type'] = 'text/xml'
12
+ provider = OaiPmh::Provider.new
13
+ provider.process_verb(@input.delete('verb'), @input.merge(:url => "http:"+URL(Oai).to_s))
14
+ end
15
+ end
16
+ end
17
+ })
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ module OaiPmh
2
+ module Helpers
3
+
4
+ # Output the OAI-PMH header
5
+ def header
6
+ @xml = Builder::XmlMarkup.new
7
+ @xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
8
+ @xml.tag!('OAI-PMH',
9
+ 'xmlns' => "http://www.openarchives.org/OAI/2.0/",
10
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
11
+ 'xsi:schemaLocation' => %{http://www.openarchives.org/OAI/2.0/
12
+ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd}) do
13
+ @xml.responseDate Time.now.utc.xmlschema
14
+ yield
15
+ end
16
+ end
17
+
18
+ # Echo the request parameters back to the client. See spec.
19
+ def echo_params(verb, opts)
20
+ @xml.request(@url, {:verb => verb}.merge(opts))
21
+ end
22
+
23
+ def build_scope_hash
24
+ params = {}
25
+ params[:from] = parse_date(@opts[:from]) if @opts[:from]
26
+ params[:until] = parse_date(@opts[:until]) if @opts[:until]
27
+ params[:set] = @opts[:set] if @opts[:set]
28
+ params
29
+ end
30
+
31
+ # Use of Chronic here is mostly for human interactions. It's
32
+ # nice to be able to say '?verb=ListRecords&from=October&until=November'
33
+ def parse_date(dt_string)
34
+ # Oddly Chronic doesn't parse an UTC encoded datetime.
35
+ # Luckily Time does
36
+ dt = Chronic.parse(dt_string) || Time.parse(dt_string)
37
+ dt.strftime("%Y-%m-%d %H:%M:%S")
38
+ end
39
+
40
+ # Massage the options until they are bit more palatable.
41
+ def ensure_valid(verb, opts)
42
+ return {} unless opts
43
+ popts = {}
44
+ opts.keys.each do |k|
45
+ # Ensure they are all lowercase symbols
46
+ nk = k.to_s.downcase.intern
47
+ popts[nk] = opts[k]
48
+ end
49
+ # shorten the big ugly long ones
50
+ popts[:id] = popts.delete(:identifier) if popts[:identifier]
51
+ popts[:prefix] = popts.delete(:metadataprefix) if popts[:metadataprefix]
52
+ popts[:token] = popts.delete(:resumptiontoken) if popts[:resumptiontoken]
53
+
54
+ raise ArgumentException.new unless popts.empty? ||
55
+ (popts.keys - OaiPmh::Const::VERBS[verb]).empty?
56
+ popts
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ require 'oaipmh/metadata/oai_dc'
2
+ module OaiPmh
3
+
4
+ METADATA = {}
5
+
6
+ def OaiPmh.register_metadata_class(metadata_class)
7
+ METADATA[metadata_class.to_s] = metadata_class
8
+ end
9
+
10
+ OaiPmh.register_metadata_class(Metadata::OaiDc)
11
+
12
+ end
13
+
14
+
@@ -0,0 +1,84 @@
1
+ # = OaiPmh::Metadata::OaiDc
2
+ #
3
+ # Copyright (C) 2006 William Groppe
4
+ #
5
+ # Will Groppe mailto:wfg@artstor.org
6
+ #
7
+ # Only one form of metadata is supported out of the box. Dublin Core is the
8
+ # most basic form of metadata, and the one recommended for support in all
9
+ # OAI-PMH repositories.
10
+ #
11
+ # To add additional metadata types it's easiest just to subclass
12
+ # OaiPmh::Metadata::OaiDc. Subclasses should override header(xml) to ouput a
13
+ # valid metadata header. They should also set defaults for prefix, schema,
14
+ # namespace, element_ns, and fields.
15
+ #
16
+ # === Example
17
+ # class CdwaLite < OaiPmh::Metadata::OaiDc
18
+ # prefix = 'cdwalite'
19
+ # schema = 'http://www.getty.edu/CDWA/CDWALite/CDWALite-xsd-draft-009c2.xsd'
20
+ # namespace = 'http://www.getty.edu/CDWA/CDWALite'
21
+ # element_ns = 'cdwalite'
22
+ # fields = [] # using to_cdwalite in model
23
+ #
24
+ # def self.header(xml)
25
+ # xml.tag!('cdwalite:cdwalite',
26
+ # 'xmlns:cdwalite' => "http://www.getty.edu/CDWA/CDWALite",
27
+ # 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
28
+ # 'xsi:schemaLocation' =>
29
+ # %{http://www.getty.edu/CDWA/CDWALite
30
+ # http://www.getty.edu/CDWA/CDWALite/CDWALite-xsd-draft-009c2.xsd}) do
31
+ # yield xml
32
+ # end
33
+ # end
34
+ # end
35
+ #
36
+ # # Now register the new metadata class
37
+ # OaiPmh.register_metadata_class(CdwaLite)
38
+ #
39
+ module OaiPmh::Metadata
40
+
41
+ class OaiDc
42
+ # Defaults
43
+ DEFAULTS = {:prefix => 'oai_dc',
44
+ :schema => 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
45
+ :namespace => 'http://www.language-archives.org/OLAC/0.2/',
46
+ :element_ns => 'dc',
47
+ :fields => %w(title creator subject description publisher
48
+ contributor date type format identifier
49
+ source language relation coverage rights)
50
+ }
51
+
52
+ # Create accessors.
53
+ DEFAULTS.each_key do |proc|
54
+ class_eval %{ def self.#{proc}; DEFAULTS[:#{proc}]; end }
55
+ class_eval %{ def self.#{proc}=(value); DEFAULTS[:#{proc}]=value; end }
56
+ end
57
+
58
+
59
+ class << self
60
+ def header(xml)
61
+ xml.tag!('oai_dc:dc',
62
+ 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
63
+ 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
64
+ 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
65
+ 'xsi:schemaLocation' =>
66
+ %{http://www.openarchives.org/OAI/2.0/oai_dc/
67
+ http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
68
+ yield xml
69
+ end
70
+ end
71
+
72
+ def to_s
73
+ DEFAULTS[:prefix]
74
+ end
75
+
76
+ def validate(document)
77
+ raise RuntimeError, "Validation not yet implemented."
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+
84
+ end
@@ -0,0 +1,34 @@
1
+ # = model.rb
2
+ #
3
+ # Copyright (C) 2006 William Groppe
4
+ #
5
+ # Will Groppe mailto: wfg@artstor.org
6
+ #
7
+ #
8
+ # Implementing a model from scratch requires overridding three methods from
9
+ # OaiPmh::Model
10
+ #
11
+ # * oai_earliest - should provide the earliest possible timestamp
12
+ # * oai_sets - if you want to support sets
13
+ # * oai_find(selector, opts) - selector can be either a record id, or :all for
14
+ # finding all matches. opts is a hash of query parameters. Valid parameters
15
+ # include :from, :until, :set, :token, and :prefix. Any errors in the
16
+ # parameters should raise a OaiPmh::ArgumentException.
17
+ #
18
+ module OaiPmh
19
+ module Model
20
+
21
+ def self.oai_earliest
22
+ Time.now.utc
23
+ end
24
+
25
+ def self.oai_sets
26
+ nil
27
+ end
28
+
29
+ def self.oai_find(selector, opts={})
30
+ []
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,421 @@
1
+ # = provider.rb
2
+ #
3
+ # Copyright (C) 2006 William Groppe
4
+ #
5
+ # Will Groppe mailto:wfg@artstor.org
6
+ #
7
+ # Open Archives Initiative - Protocol for Metadata Harvesting see
8
+ # http://www.openarchives.org/
9
+ #
10
+ # === Features
11
+ # * Easily setup a simple repository
12
+ # * Simple integration with ActiveRecord
13
+ # * Dublin Core metadata format included
14
+ # * Easily add addition metadata formats
15
+ # * Adaptable to any data source
16
+ #
17
+ #
18
+ # === Current shortcomings
19
+ # * No resumption tokens
20
+ # * Doesn't validate metadata
21
+ # * No deletion support
22
+ # * Many others I can't think of right now. :-)
23
+ #
24
+ #
25
+ # === ActiveRecord integration
26
+ #
27
+ # To successfully use ActiveRecord as a OAI PMH datasource the database table
28
+ # should include an updated_at column so that updates to the table are
29
+ # tracked by ActiveRecord. This provides much of the base functionality for
30
+ # selecting update periods.
31
+ #
32
+ # To understand how the data is extracted from the AR model it's best to just
33
+ # go thru the logic:
34
+ #
35
+ # Does the model respond to 'to_{prefix}'? Where prefix is the
36
+ # metadata prefix. If it does then just include the response from
37
+ # the model. So if you want to provide custom or complex metadata you can
38
+ # simply define a 'to_{prefix}' method on your model.
39
+ #
40
+ # Example:
41
+ #
42
+ # class Record < ActiveRecord::Base
43
+ #
44
+ # def to_oai_dc
45
+ # xml = Builder::XmlMarkup.new
46
+ # xml.tag!('oai_dc:dc',
47
+ # 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
48
+ # 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
49
+ # 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
50
+ # 'xsi:schemaLocation' =>
51
+ # %{http://www.openarchives.org/OAI/2.0/oai_dc/
52
+ # http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
53
+ #
54
+ # xml.oai_dc :title, title
55
+ # xml.oai_dc :subject, subject
56
+ # end
57
+ # xml.to_s
58
+ # end
59
+ #
60
+ # end
61
+ #
62
+ # If the model doesn't define a 'to_{prefix}' then start iterating thru
63
+ # the defined metadata fields.
64
+ #
65
+ # Grab a mapping if one exists by trying to call 'map_{prefix}'.
66
+ #
67
+ # Now do the iteration and try calling methods on the model that match
68
+ # the field names, or the mapped field names.
69
+ #
70
+ # So with Dublin Core we end up with the following:
71
+ #
72
+ # 1. Check for 'title' mapped to a different method.
73
+ # 2. Call model.titles - try plural
74
+ # 3. Call model.title - try singular last
75
+ #
76
+ # Extremely contrived Blog example:
77
+ #
78
+ # class Post < ActiveRecord::Base
79
+ # def map_oai_dc
80
+ # {:subject => :tags,
81
+ # :description => :text,
82
+ # :creator => :user,
83
+ # :contibutor => :comments}
84
+ # end
85
+ # end
86
+ #
87
+ # === Supporting custom metadata
88
+ #
89
+ # See OaiPmh::Metadata for details.
90
+ #
91
+ # == Examples
92
+ #
93
+ # === Sub classing a provider
94
+ #
95
+ # class MyProvider < OaiPmh::Provider
96
+ # name 'My little OAI provider'
97
+ # url 'http://localhost/provider'
98
+ # prefix 'oai:localhost'
99
+ # email 'root@localhost' # String or Array
100
+ # deletes 'no' # future versions will support deletes
101
+ # granularity 'YYYY-MM-DDThh:mm:ssZ' # update resolution
102
+ # model MyModel # Class to get data from
103
+ # end
104
+ #
105
+ # # Now use it
106
+ #
107
+ # provider = MyProvider.new
108
+ # provider.identify
109
+ # provider.list_sets
110
+ # provider.list_metadata_formats
111
+ # # these verbs require a working model
112
+ # provider.list_identifiers
113
+ # provider.list_records
114
+ # provider.get_record('oai:localhost/1')
115
+ #
116
+ #
117
+ # === Configuring the default provider
118
+ #
119
+ # class OaiPmh::Provider
120
+ # name 'My little OAI Provider'
121
+ # url 'http://localhost/provider'
122
+ # prefix 'oai:localhost'
123
+ # email 'root@localhost' # String or Array
124
+ # deletes 'no' # future versions will support deletes
125
+ # granularity 'YYYY-MM-DDThh:mm:ssZ' # update resolution
126
+ # model MyModel # Class to get data from
127
+ # end
128
+ #
129
+ #
130
+ module OaiPmh
131
+
132
+ class Provider
133
+ include Helpers
134
+
135
+ @@options = {}
136
+
137
+ AVAILABLE_FORMATS = {}
138
+
139
+ class << self
140
+
141
+ OaiPmh::Const::DEFAULTS.keys.each do |field|
142
+ class_eval %{
143
+ def #{field}(value)
144
+ @@options[:#{field}] = value
145
+ end
146
+ }
147
+ end
148
+
149
+ def model(value)
150
+ @@options[:model] = value
151
+ end
152
+
153
+ end
154
+
155
+ def initialize
156
+ @config = OaiPmh::Const::DEFAULTS.merge(@@options)
157
+ end
158
+
159
+ def identify
160
+ process_verb 'Identify'
161
+ end
162
+
163
+ def list_metadata_formats
164
+ process_verb 'ListMetadataFormats'
165
+ end
166
+
167
+ def list_sets(opts = {})
168
+ process_verb 'ListSets', opts
169
+ end
170
+
171
+ def get_record(id, opts = {})
172
+ process_verb 'GetRecord', opts.merge(:id => id)
173
+ end
174
+
175
+ def list_identifiers(opts = {})
176
+ process_verb 'ListIdentifiers', opts
177
+ end
178
+
179
+ def list_records(opts = {})
180
+ process_verb 'ListRecords', opts
181
+ end
182
+
183
+ # xml_response = process_verb('ListRecords', :from => 'October', :until => 'November') # thanks Chronic!
184
+ #
185
+ # If you are implementing a web interface using process_verb is the
186
+ # preferred way. See extensions/camping.rb
187
+ def process_verb(verb = nil, opts = {})
188
+ header do
189
+ begin
190
+ raise VerbException.new unless verb &&
191
+ OaiPmh::Const::VERBS.keys.include?(verb)
192
+
193
+ # Allow the request to pass in a
194
+ @url = opts['url'] ? opts.delete('url') : @config[:url]
195
+
196
+ echo_params(verb, opts)
197
+
198
+ @opts = ensure_valid(verb, opts)
199
+
200
+ @model = @config[:model]
201
+
202
+ # Pull out the requested metadata format. Important for GetRecord,
203
+ # ListRecords, ListIdentifiers? Default to 'oai_dc'
204
+ @format = @opts[:prefix] ? @opts[:prefix] : "oai_dc"
205
+
206
+ # Rubify the verb for calling method
207
+ call = verb.gsub(/[A-Z]/) {|m| "_#{m.downcase}"}.sub(/^\_/,'')
208
+ send("#{call}_response")
209
+
210
+ rescue
211
+ if $!.respond_to?(:code)
212
+ @xml.error $!.to_s, :code => $!.code
213
+ else
214
+ puts $!.message
215
+ puts $!.backtrace.join("\n")
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ private
222
+
223
+ def identify_response
224
+ @xml.Identify do
225
+ @xml.repositoryName @config[:name]
226
+ @xml.baseURL @url
227
+ @xml.protocolVersion 2.0
228
+ @config[:email].to_a.each do |email|
229
+ @xml.adminEmail email
230
+ end
231
+ @xml.earliestDatestamp earliest
232
+ @xml.deleteRecord @config[:delete]
233
+ @xml.granularity @config[:granularity]
234
+ end
235
+ end
236
+
237
+ def list_sets_response
238
+ raise SetException.new unless sets
239
+ @xml.ListSets do |ls|
240
+ oai_sets.each do |ms|
241
+ @xml.set do |set|
242
+ @xml.setSpec ms.spec
243
+ @xml.setName ms.name
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ def list_metadata_formats_response
250
+ @xml.ListMetadataFormats do
251
+ @config[:formats].each_pair do |key, format|
252
+ @xml.metadataFormat do
253
+ @xml.metadataPrefix instance_eval("#{format}.prefix")
254
+ @xml.schema instance_eval("#{format}.schema")
255
+ @xml.metadataNamespace instance_eval("#{format}.namespace")
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ def list_identifiers_response
262
+ raise FORMAT_ERROR unless @config[:formats].include? @format
263
+ records = find :all
264
+
265
+ raise RECORDS_ERROR if records.nil? || records.empty?
266
+
267
+ @xml.ListIdentifiers do
268
+ records.each do |record|
269
+ metadata_header record
270
+ end
271
+ end
272
+ end
273
+
274
+ def get_record_response
275
+ raise FormatException.new unless @config[:formats].include? @format
276
+
277
+ rec = @opts[:id].gsub("#{@config[:prefix]}/", "")
278
+
279
+ record = find rec
280
+
281
+ raise IdException.new unless record
282
+
283
+ @xml.GetRecord do
284
+ @xml.record do
285
+ metadata_header record
286
+ metadata record
287
+ end
288
+ end
289
+ end
290
+
291
+ def list_records_response
292
+ raise FormatException.new unless @config[:formats].include? @format
293
+
294
+ records = find :all
295
+
296
+ raise NoMatchException.new if records.nil? || records.empty?
297
+
298
+ @xml.ListRecords do
299
+ records.each do |record|
300
+ @xml.record do
301
+ metadata_header record
302
+ metadata record
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ def find(selector)
309
+ return nil unless @model
310
+
311
+ # Try oai finder methods first
312
+ begin
313
+ return @model.oai_find(selector, @opts)
314
+ rescue NoMethodError
315
+ begin
316
+ # Try an ActiveRecord finder call
317
+ return @model.find(selector, build_scope_hash)
318
+ rescue
319
+ end
320
+ end
321
+ nil
322
+ end
323
+
324
+ def earliest
325
+ return DateTime.new unless @model
326
+
327
+ # Try oai finder methods first
328
+ begin
329
+ return @model.oai_earliest
330
+ rescue NoMethodError
331
+ begin
332
+ # Try an ActiveRecord finder call
333
+ return @model.find(:first, :order => "updated_at asc").updated_at
334
+ rescue
335
+ end
336
+ end
337
+ nil
338
+ end
339
+
340
+ def sets
341
+ return nil unless @model
342
+
343
+ # Try oai finder methods first
344
+ begin
345
+ return @model.oai_sets
346
+ rescue NoMethodError
347
+ end
348
+ nil
349
+ end
350
+
351
+ # emit record header
352
+ def metadata_header(record)
353
+ @xml.header do |h|
354
+ h.identifier "#{@config[:prefix]}/#{record.id}"
355
+ h.datestamp record.updated_at.utc.xmlschema
356
+ h.set @opts[:set] if @opts[:set]
357
+ end
358
+ end
359
+
360
+
361
+ # metadata - core routine for delivering metadata records
362
+ #
363
+ def metadata(record)
364
+ if record.respond_to?("to_#{@format}")
365
+ @xml.metadata do
366
+ str = record.send("to_#{@format}")
367
+ # Strip off the xml header if we got one.
368
+ str.sub!(/<\?xml.*?\?>/, '')
369
+ @xml << str
370
+ end
371
+ else
372
+ map = record.respond_to?("map_#{@format}") ?
373
+ instance_eval("record.map_#{@format}") : {}
374
+
375
+ mdformat = @config[:formats][@format]
376
+ @xml.metadata do
377
+ mdformat.header(@xml) do
378
+ mdformat.fields.each do |field|
379
+ set = value_for(field, record, map)
380
+ set.each do |mdv|
381
+ instance_eval("@xml.#{mdformat.element_ns} :#{field}, %{#{mdv}}")
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
388
+
389
+ # We try a bunch of different methods to get the data from the model.
390
+ #
391
+ # 1) See if the model will hand us the entire record in the requested
392
+ # format. Example: if the model defines 'to_oai_dc' we call that
393
+ # method and append the result to the xml stream.
394
+ # 2) Check if the model defines a field mapping for the field of
395
+ # interest.
396
+ # 3) Try calling the pluralized name method on the model.
397
+ # 4) Try calling the singular name method on the model, if it's not a
398
+ # reserved word.
399
+ def value_for(field, record, map)
400
+ if map.keys.include?(field)
401
+ return map[field].nil? ? [] :
402
+ record.send("#{map[field]}").to_a
403
+ end
404
+
405
+ begin
406
+ return record.send("#{field.pluralize}").to_a
407
+ rescue
408
+ unless OaiPmh::Const::RESERVED_WORDS.include?(field)
409
+ begin
410
+ return record.send("#{field}").to_a
411
+ rescue
412
+ return []
413
+ end
414
+ end
415
+ end
416
+ []
417
+ end
418
+
419
+ end
420
+
421
+ end
@@ -0,0 +1,9 @@
1
+ module Oaipmh #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class OaipmhTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_truth
9
+ assert true
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/oaipmh'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: oaipmh
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-11-02 00:00:00 -05:00
8
+ summary: description of gem
9
+ require_paths:
10
+ - lib
11
+ email: your contact email for bug fixes and info
12
+ homepage: http://oaipmh.rubyforge.org
13
+ rubyforge_project: oaipmh
14
+ description: description of gem
15
+ autorequire: oaipmh
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - will
31
+ files:
32
+ - README
33
+ - CHANGELOG
34
+ - Rakefile
35
+ - test/oaipmh_test.rb
36
+ - test/test_helper.rb
37
+ - lib/oaipmh
38
+ - lib/oaipmh.rb
39
+ - lib/oaipmh/constants.rb
40
+ - lib/oaipmh/exceptions.rb
41
+ - lib/oaipmh/extensions
42
+ - lib/oaipmh/helpers.rb
43
+ - lib/oaipmh/metadata
44
+ - lib/oaipmh/metadata.rb
45
+ - lib/oaipmh/model.rb
46
+ - lib/oaipmh/provider.rb
47
+ - lib/oaipmh/version.rb
48
+ - lib/oaipmh/extensions/camping.rb
49
+ - lib/oaipmh/metadata/oai_dc.rb
50
+ test_files: []
51
+
52
+ rdoc_options:
53
+ - --quiet
54
+ - --title
55
+ - oaipmh documentation
56
+ - --opname
57
+ - index.html
58
+ - --line-numbers
59
+ - --main
60
+ - README
61
+ - --inline-source
62
+ - --exclude
63
+ - ^(examples|extras)/
64
+ extra_rdoc_files:
65
+ - README
66
+ - CHANGELOG
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ requirements: []
72
+
73
+ dependencies: []
74
+