oaipmh 0.0.1

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