rbook 0.1 → 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ require 'rexml/document'
2
+ require 'thread'
3
+
4
+ module RBook
5
+ module Onix
6
+
7
+ # a utility class for the ONIX Stream Reader. See RBook::ONIX::StreamReader for
8
+ # basic usage instructions.
9
+ class Listener
10
+
11
+ def initialize(queue)
12
+ @queue = queue
13
+ @in_product = false
14
+ end
15
+
16
+ def doctype(name, pub_sys, long_name, uri)
17
+ end
18
+
19
+ def tag_start(name, attrs)
20
+ case name
21
+ when "Product"
22
+ # A new product tag has been read, so start
23
+ # building a new product to add to the queue
24
+ @in_product = true
25
+ @product_fragment = "<Product>"
26
+ else
27
+ @product_fragment << "<#{name}>" if @in_product
28
+ end
29
+ end
30
+
31
+ def text(text)
32
+ @product_fragment << text if @in_product
33
+ end
34
+
35
+ def tag_end(name)
36
+ case name
37
+ when "ONIXMessage"
38
+ # the ONIXMessage tag indicates the end of the file, so add
39
+ # nil to the queue to let the iterating thread know reading
40
+ # is finished
41
+ @queue.push(nil)
42
+ when "Product"
43
+ # A product tag is finished, so add it to the queue
44
+ @product_fragment << "</Product>"
45
+ begin
46
+ doc = REXML::Document.new(@product_fragment)
47
+ unless doc.root.nil?
48
+ product = RBook::Onix::Product.load_from_element(doc.root)
49
+ @queue.push(product) unless product.nil?
50
+ end
51
+ rescue
52
+ # error occurred while building the product from an XML fragment
53
+ end
54
+ @in_product = false
55
+ else
56
+ @product_fragment << "</#{name}>" if @in_product
57
+ end
58
+ end
59
+
60
+ def xmldecl(version, encoding, standalone)
61
+ # do nothing
62
+ end
63
+
64
+ def method_missing
65
+ # do nothing
66
+ end
67
+ end
68
+
69
+ # A stream reader for ONIX files. Using a stream reader is preferred for
70
+ # large XML files as the file is processed in stages, removing the need
71
+ # to store the entire thing in memory at once.
72
+ #
73
+ # This class provides forward only iteration over a single ONIX file,
74
+ # returning a RBook::ONIX::Product object for each product encountered.
75
+ #
76
+ # = Basic usage
77
+ # require 'rbook/onix'
78
+ # reader = RBook::ONIX::StreamReader.new("some_onix_file.xml")
79
+ # reader.each do |product|
80
+ # puts product.inspect
81
+ # end
82
+ class StreamReader
83
+
84
+ # creates a new stream reader to read the specified file.
85
+ # file can be specified as a String or File object
86
+ def initialize(input)
87
+ if input.class == String
88
+ @input = File.new(input)
89
+ elsif input.class == File
90
+ @input = input
91
+ else
92
+ throw "Unable to read from path or file"
93
+ end
94
+ # create a sized queue to store each product read from the file
95
+ @queue = SizedQueue.new(100)
96
+
97
+ # launch a reader thread to process the file and store each
98
+ # product in the queue
99
+ Thread.new do
100
+ producer = Listener.new(@queue)
101
+ REXML::Document.parse_stream(@input, producer)
102
+ end
103
+ end
104
+
105
+ # iterate over the file and return a product file to a block.
106
+ #
107
+ # reader = RBook::ONIX::StreamReader.new("some_onix_file.xml")
108
+ # reader.each do |product|
109
+ # puts product.inspect
110
+ # end
111
+ def each
112
+ obj = @queue.pop
113
+ while !obj.nil?
114
+ yield obj
115
+ obj = @queue.pop
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,40 @@
1
+ require 'rexml/document'
2
+
3
+ module RBook
4
+ module Onix
5
+
6
+ class StreamWriter
7
+
8
+ # Default constructor.
9
+ def initialize(output, msg)
10
+ raise ArgumentError, 'msg must be an RBook::Onix::Message object' unless msg.class == RBook::Onix::Message
11
+ @output = output
12
+ @msg = msg
13
+ end
14
+
15
+ def start_document(encoding = nil)
16
+ decl = REXML::XMLDecl.new
17
+ doctype = REXML::DocType.new('ONIXMessage', "SYSTEM \"#{RBook::Onix::Message::ONIX_DTD_URL}\"")
18
+ if encoding
19
+ decl.encoding = encoding
20
+ else
21
+ decl.encoding = "UTF-8"
22
+ end
23
+ @output.write(decl.to_s+"\n")
24
+ @output.write(doctype.to_s+"\n")
25
+ @output.write("<ONIXMessage>\n")
26
+ @output.write(@msg.header.to_s)
27
+ end
28
+
29
+ def << (product)
30
+ @output.write(product.to_s)
31
+ end
32
+
33
+ def end_document
34
+ @output.write("</ONIXMessage>\n")
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -42,7 +42,7 @@ module Onix
42
42
  supply_detail = REXML::Element.new('SupplyDetail')
43
43
  supply_detail.add_element('SupplierName').text = self.supplier_name.to_xs
44
44
  supply_detail.add_element('AvailabilityCode').text = self.availability_code.to_xs unless self.availability_code.nil?
45
- supply_detail.add_element('IntermediaryAvailabilityCode').text = self.intermediary_availability_code_to_xs unless self.intermediary_availability_code.nil?
45
+ supply_detail.add_element('IntermediaryAvailabilityCode').text = self.intermediary_availability_code.to_xs unless self.intermediary_availability_code.nil?
46
46
  unless self.price.nil?
47
47
  tmp = REXML::Element.new('Price')
48
48
  if self.price.kind_of?(BigDecimal)
@@ -3,10 +3,11 @@ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../")
3
3
  require 'rbook/isbn'
4
4
  require 'rbook/errors'
5
5
  require 'rbook/titlepage/titlepage_driver'
6
+ require 'rbook/titlepage/client'
6
7
 
7
8
  module RBook
8
9
 
9
- # a convenience class for accessing the SOAP API for http://www.titlepage.com.
10
+ # a convenience module for accessing the SOAP API for http://www.titlepage.com.
10
11
  # Uses boilerplate code generated by soap4r.
11
12
  #
12
13
  # You should be aware of any limits of query volume imposed by the provider - currently a
@@ -17,7 +18,7 @@ module RBook
17
18
  # require 'rbook/titlepage'
18
19
  #
19
20
  # Basic usage:
20
- # tp = Book::TitlePage.new
21
+ # tp = RBook::TitlePage::Client.new
21
22
  # tp.login('someuser','topsecret')
22
23
  # puts tp.find("0091835135").inspect
23
24
  # sleep 3
@@ -25,72 +26,11 @@ module RBook
25
26
  # tp.logout
26
27
  #
27
28
  # Alternative Usage:
28
- # RBook::TitlePage.open("username","password") do |tp|
29
+ # RBook::TitlePage::Client.open("username","password") do |tp|
29
30
  # puts tp.find("0091835135").inspect
30
31
  # sleep 3
31
32
  # puts tp.find("9780672327568").inspect
32
33
  # end
33
- class TitlePage
34
-
35
- # Optional driver parameter allows an alternative SOAP driver to the default to be specified.
36
- # This is primarily for testing purposes and probably isn't useful to anyone in the real world.
37
- def initialize(driver = nil)
38
- @driver = driver || TitleQueryPortType.new
39
- @token = nil
40
- end
41
-
42
- # login to the titlepage API.
43
- def login(username, password)
44
- raise InvalidRubyVersionError, 'Ruby 1.8.3 or higher is required to use this class' unless RUBY_VERSION >= "1.8.3"
45
- logout if @token
46
- @token = @driver.login(username, password)
47
- return true if @token
48
- end
49
-
50
- # logout from the titlepage API
51
- def logout
52
- if @token
53
- @driver.logout(@token)
54
- end
55
- end
56
-
57
- # retrieve information on a specified ISBN
58
- def find(isbn)
59
- return NotLoggedInError, 'You must login to titlepage API before performing a search' unless @token
60
- isbn = ISBN::convert_to_isbn13(isbn)
61
- return nil if isbn.nil?
62
- begin
63
- result = @driver.SearchByEAN(@token, isbn)
64
- if result.Product.nil?
65
- return nil
66
- else
67
- return result
68
- end
69
- rescue
70
- return nil
71
- end
72
-
73
- end
74
-
75
- # a convenience method to make queries to title page a little cleaner. This function
76
- # essentially calls the login and logout functions for you automatically.
77
- #
78
- # RBook::TitlePage.open("username","password") do |tp|
79
- # result = tp.find("9780091835132")
80
- # end
81
- def TitlePage.open(username, password, driver = nil)
82
-
83
- tp = TitlePage.new(driver || TitleQueryPortType.new)
84
-
85
- begin
86
- tp.login(username, password)
87
-
88
- yield(tp)
89
-
90
- ensure
91
- tp.logout
92
- end
93
- end
94
-
34
+ module TitlePage
95
35
  end
96
36
  end
@@ -0,0 +1,81 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../")
2
+
3
+ require 'rbook/isbn'
4
+ require 'rbook/errors'
5
+ require 'rbook/titlepage/titlepage_driver'
6
+
7
+ module RBook
8
+ module TitlePage
9
+
10
+ # a convenience class for accessing the SOAP API for http://www.titlepage.com.
11
+ # Uses boilerplate code generated by soap4r.
12
+ #
13
+ # You should be aware of any limits of query volume imposed by the provider - currently a
14
+ # maximum of 30 queries per minute is permitted.
15
+ #
16
+ # For a basic usage overview, check out RBook::TitlePage
17
+ class Client
18
+
19
+ # Optional driver parameter allows an alternative SOAP driver to the default to be specified.
20
+ # This is primarily for testing purposes and probably isn't useful to anyone in the real world.
21
+ def initialize(driver = nil)
22
+ @driver = driver || TitleQueryPortType.new
23
+ @token = nil
24
+ end
25
+
26
+ # login to the titlepage API.
27
+ def login(username, password)
28
+ raise InvalidRubyVersionError, 'Ruby 1.8.3 or higher is required to use this class' unless RUBY_VERSION >= "1.8.3"
29
+ logout if @token
30
+ @token = @driver.login(username, password)
31
+ return true if @token
32
+ end
33
+
34
+ # logout from the titlepage API
35
+ def logout
36
+ if @token
37
+ @driver.logout(@token)
38
+ end
39
+ end
40
+
41
+ # retrieve information on a specified ISBN
42
+ def find(isbn)
43
+ return NotLoggedInError, 'You must login to titlepage API before performing a search' unless @token
44
+ isbn = ISBN::convert_to_isbn13(isbn)
45
+ return nil if isbn.nil?
46
+ begin
47
+ result = @driver.SearchByEAN(@token, isbn)
48
+ if result.Product.nil?
49
+ return nil
50
+ else
51
+ return result
52
+ end
53
+ rescue
54
+ return nil
55
+ end
56
+
57
+ end
58
+
59
+ # a convenience method to make queries to title page a little cleaner. This function
60
+ # essentially calls the login and logout functions for you automatically.
61
+ #
62
+ # RBook::TitlePage.open("username","password") do |tp|
63
+ # result = tp.find("9780091835132")
64
+ # end
65
+ def self.open(username, password, driver = nil)
66
+
67
+ tp = self.new(driver || TitleQueryPortType.new)
68
+
69
+ begin
70
+ tp.login(username, password)
71
+
72
+ yield(tp)
73
+
74
+ ensure
75
+ tp.logout
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -2,133 +2,136 @@ require File.dirname(__FILE__) + '/titlepage_utils.rb'
2
2
 
3
3
  require 'soap/rpc/driver'
4
4
 
5
- class TitleQueryPortType < ::SOAP::RPC::Driver
6
- DefaultEndpointUrl = "http://www.titlepage.com.au/ws/TitleQuery.php"
7
- MappingRegistry = ::SOAP::Mapping::Registry.new
5
+ module RBook
6
+ module TitlePage
7
+ class TitleQueryPortType < ::SOAP::RPC::Driver
8
+ DefaultEndpointUrl = "http://www.titlepage.com.au/ws/TitleQuery.php"
9
+ MappingRegistry = ::SOAP::Mapping::Registry.new
8
10
 
9
- MappingRegistry.set(
10
- SearchResults,
11
- ::SOAP::SOAPStruct,
12
- ::SOAP::Mapping::Registry::TypedStructFactory,
13
- { :type => XSD::QName.new("urn:TitleQuery", "SearchResults") }
14
- )
15
- MappingRegistry.set(
16
- Product,
17
- ::SOAP::SOAPStruct,
18
- ::SOAP::Mapping::Registry::TypedStructFactory,
19
- { :type => XSD::QName.new("urn:TitleQuery", "Product") }
20
- )
21
- MappingRegistry.set(
22
- ArrayOfProductIdentifier,
23
- ::SOAP::SOAPArray,
24
- ::SOAP::Mapping::Registry::TypedArrayFactory,
25
- { :type => XSD::QName.new("urn:TitleQuery", "ProductIdentifier") }
26
- )
27
- MappingRegistry.set(
28
- Title,
29
- ::SOAP::SOAPStruct,
30
- ::SOAP::Mapping::Registry::TypedStructFactory,
31
- { :type => XSD::QName.new("urn:TitleQuery", "Title") }
32
- )
33
- MappingRegistry.set(
34
- ArrayOfContributor,
35
- ::SOAP::SOAPArray,
36
- ::SOAP::Mapping::Registry::TypedArrayFactory,
37
- { :type => XSD::QName.new("urn:TitleQuery", "Contributor") }
38
- )
39
- MappingRegistry.set(
40
- SupplyDetail,
41
- ::SOAP::SOAPStruct,
42
- ::SOAP::Mapping::Registry::TypedStructFactory,
43
- { :type => XSD::QName.new("urn:TitleQuery", "SupplyDetail") }
44
- )
45
- MappingRegistry.set(
46
- Stock,
47
- ::SOAP::SOAPStruct,
48
- ::SOAP::Mapping::Registry::TypedStructFactory,
49
- { :type => XSD::QName.new("urn:TitleQuery", "Stock") }
50
- )
51
- MappingRegistry.set(
52
- Price,
53
- ::SOAP::SOAPStruct,
54
- ::SOAP::Mapping::Registry::TypedStructFactory,
55
- { :type => XSD::QName.new("urn:TitleQuery", "Price") }
56
- )
57
- MappingRegistry.set(
58
- ProductIdentifier,
59
- ::SOAP::SOAPStruct,
60
- ::SOAP::Mapping::Registry::TypedStructFactory,
61
- { :type => XSD::QName.new("urn:TitleQuery", "ProductIdentifier") }
62
- )
63
- MappingRegistry.set(
64
- Contributor,
65
- ::SOAP::SOAPStruct,
66
- ::SOAP::Mapping::Registry::TypedStructFactory,
67
- { :type => XSD::QName.new("urn:TitleQuery", "Contributor") }
68
- )
11
+ MappingRegistry.set(
12
+ SearchResults,
13
+ ::SOAP::SOAPStruct,
14
+ ::SOAP::Mapping::Registry::TypedStructFactory,
15
+ { :type => XSD::QName.new("urn:TitleQuery", "SearchResults") }
16
+ )
17
+ MappingRegistry.set(
18
+ Product,
19
+ ::SOAP::SOAPStruct,
20
+ ::SOAP::Mapping::Registry::TypedStructFactory,
21
+ { :type => XSD::QName.new("urn:TitleQuery", "Product") }
22
+ )
23
+ MappingRegistry.set(
24
+ ArrayOfProductIdentifier,
25
+ ::SOAP::SOAPArray,
26
+ ::SOAP::Mapping::Registry::TypedArrayFactory,
27
+ { :type => XSD::QName.new("urn:TitleQuery", "ProductIdentifier") }
28
+ )
29
+ MappingRegistry.set(
30
+ Title,
31
+ ::SOAP::SOAPStruct,
32
+ ::SOAP::Mapping::Registry::TypedStructFactory,
33
+ { :type => XSD::QName.new("urn:TitleQuery", "Title") }
34
+ )
35
+ MappingRegistry.set(
36
+ ArrayOfContributor,
37
+ ::SOAP::SOAPArray,
38
+ ::SOAP::Mapping::Registry::TypedArrayFactory,
39
+ { :type => XSD::QName.new("urn:TitleQuery", "Contributor") }
40
+ )
41
+ MappingRegistry.set(
42
+ SupplyDetail,
43
+ ::SOAP::SOAPStruct,
44
+ ::SOAP::Mapping::Registry::TypedStructFactory,
45
+ { :type => XSD::QName.new("urn:TitleQuery", "SupplyDetail") }
46
+ )
47
+ MappingRegistry.set(
48
+ Stock,
49
+ ::SOAP::SOAPStruct,
50
+ ::SOAP::Mapping::Registry::TypedStructFactory,
51
+ { :type => XSD::QName.new("urn:TitleQuery", "Stock") }
52
+ )
53
+ MappingRegistry.set(
54
+ Price,
55
+ ::SOAP::SOAPStruct,
56
+ ::SOAP::Mapping::Registry::TypedStructFactory,
57
+ { :type => XSD::QName.new("urn:TitleQuery", "Price") }
58
+ )
59
+ MappingRegistry.set(
60
+ ProductIdentifier,
61
+ ::SOAP::SOAPStruct,
62
+ ::SOAP::Mapping::Registry::TypedStructFactory,
63
+ { :type => XSD::QName.new("urn:TitleQuery", "ProductIdentifier") }
64
+ )
65
+ MappingRegistry.set(
66
+ Contributor,
67
+ ::SOAP::SOAPStruct,
68
+ ::SOAP::Mapping::Registry::TypedStructFactory,
69
+ { :type => XSD::QName.new("urn:TitleQuery", "Contributor") }
70
+ )
69
71
 
70
- Methods = [
71
- [ XSD::QName.new("http://www.titlepage.com/ws", "Login"),
72
- "http://www.titlepage.com.au/ws/TitleQuery.php/Login",
73
- "login",
74
- [ ["in", "UserName", ["::SOAP::SOAPString"]],
75
- ["in", "Password", ["::SOAP::SOAPString"]],
76
- ["retval", "Token", ["::SOAP::SOAPString"]] ],
77
- { :request_style => :rpc, :request_use => :encoded,
78
- :response_style => :rpc, :response_use => :encoded }
79
- ],
80
- [ XSD::QName.new("http://www.titlepage.com/ws", "SearchByISBN"),
81
- "http://www.titlepage.com.au/ws/TitleQuery.php/SearchByISBN",
82
- "searchByISBN",
83
- [ ["in", "Token", ["::SOAP::SOAPString"]],
84
- ["in", "ISBN", ["::SOAP::SOAPString"]],
85
- ["retval", "SearchResults", ["SearchResults", "urn:TitleQuery", "SearchResults"]] ],
86
- { :request_style => :rpc, :request_use => :encoded,
87
- :response_style => :rpc, :response_use => :encoded }
88
- ],
89
- [ XSD::QName.new("http://www.titlepage.com/ws", "SearchByEAN"),
90
- "http://www.titlepage.com.au/ws/TitleQuery.php/SearchByEAN",
91
- "searchByEAN",
92
- [ ["in", "Token", ["::SOAP::SOAPString"]],
93
- ["in", "EAN", ["::SOAP::SOAPString"]],
94
- ["retval", "SearchResults", ["SearchResults", "urn:TitleQuery", "SearchResults"]] ],
95
- { :request_style => :rpc, :request_use => :encoded,
96
- :response_style => :rpc, :response_use => :encoded }
97
- ],
98
- [ XSD::QName.new("http://www.titlepage.com/ws", "Logout"),
99
- "http://www.titlepage.com.au/ws/TitleQuery.php/Logout",
100
- "logout",
101
- [ ["in", "token", ["::SOAP::SOAPString"]] ],
102
- { :request_style => :rpc, :request_use => :encoded,
103
- :response_style => :rpc, :response_use => :encoded }
104
- ]
105
- ]
72
+ Methods = [
73
+ [ XSD::QName.new("http://www.titlepage.com/ws", "Login"),
74
+ "http://www.titlepage.com.au/ws/TitleQuery.php/Login",
75
+ "login",
76
+ [ ["in", "UserName", ["::SOAP::SOAPString"]],
77
+ ["in", "Password", ["::SOAP::SOAPString"]],
78
+ ["retval", "Token", ["::SOAP::SOAPString"]] ],
79
+ { :request_style => :rpc, :request_use => :encoded,
80
+ :response_style => :rpc, :response_use => :encoded }
81
+ ],
82
+ [ XSD::QName.new("http://www.titlepage.com/ws", "SearchByISBN"),
83
+ "http://www.titlepage.com.au/ws/TitleQuery.php/SearchByISBN",
84
+ "searchByISBN",
85
+ [ ["in", "Token", ["::SOAP::SOAPString"]],
86
+ ["in", "ISBN", ["::SOAP::SOAPString"]],
87
+ ["retval", "SearchResults", ["SearchResults", "urn:TitleQuery", "SearchResults"]] ],
88
+ { :request_style => :rpc, :request_use => :encoded,
89
+ :response_style => :rpc, :response_use => :encoded }
90
+ ],
91
+ [ XSD::QName.new("http://www.titlepage.com/ws", "SearchByEAN"),
92
+ "http://www.titlepage.com.au/ws/TitleQuery.php/SearchByEAN",
93
+ "searchByEAN",
94
+ [ ["in", "Token", ["::SOAP::SOAPString"]],
95
+ ["in", "EAN", ["::SOAP::SOAPString"]],
96
+ ["retval", "SearchResults", ["SearchResults", "urn:TitleQuery", "SearchResults"]] ],
97
+ { :request_style => :rpc, :request_use => :encoded,
98
+ :response_style => :rpc, :response_use => :encoded }
99
+ ],
100
+ [ XSD::QName.new("http://www.titlepage.com/ws", "Logout"),
101
+ "http://www.titlepage.com.au/ws/TitleQuery.php/Logout",
102
+ "logout",
103
+ [ ["in", "token", ["::SOAP::SOAPString"]] ],
104
+ { :request_style => :rpc, :request_use => :encoded,
105
+ :response_style => :rpc, :response_use => :encoded }
106
+ ]
107
+ ]
106
108
 
107
- def initialize(endpoint_url = nil)
108
- endpoint_url ||= DefaultEndpointUrl
109
- super(endpoint_url, nil)
110
- self.mapping_registry = MappingRegistry
111
- init_methods
112
- end
109
+ def initialize(endpoint_url = nil)
110
+ endpoint_url ||= DefaultEndpointUrl
111
+ super(endpoint_url, nil)
112
+ self.mapping_registry = MappingRegistry
113
+ init_methods
114
+ end
113
115
 
114
- private
116
+ private
115
117
 
116
- def init_methods
117
- Methods.each do |definitions|
118
- opt = definitions.last
119
- if opt[:request_style] == :document
120
- add_document_operation(*definitions)
121
- else
122
- add_rpc_operation(*definitions)
123
- qname = definitions[0]
124
- name = definitions[2]
125
- if qname.name != name and qname.name.capitalize == name.capitalize
126
- ::SOAP::Mapping.define_singleton_method(self, qname.name) do |*arg|
127
- __send__(name, *arg)
118
+ def init_methods
119
+ Methods.each do |definitions|
120
+ opt = definitions.last
121
+ if opt[:request_style] == :document
122
+ add_document_operation(*definitions)
123
+ else
124
+ add_rpc_operation(*definitions)
125
+ qname = definitions[0]
126
+ name = definitions[2]
127
+ if qname.name != name and qname.name.capitalize == name.capitalize
128
+ ::SOAP::Mapping.define_singleton_method(self, qname.name) do |*arg|
129
+ __send__(name, *arg)
130
+ end
131
+ end
128
132
  end
129
133
  end
130
134
  end
131
135
  end
132
136
  end
133
137
  end
134
-