rbook-onix 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,50 +6,45 @@ module Onix
6
6
  class SalesRestriction
7
7
  attr_accessor :type, :detail
8
8
 
9
- # Attempts to create a contributor object using the supplied xml element
10
- def SalesRestriction.load_from_element(element)
11
- raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
12
-
13
- if REXML::XPath.first(element, '//SalesRestriction').nil?
14
- raise LoadError, 'supplied REXML document object does not appear contain a valid SalesRestriction fragment'
15
- end
16
-
9
+ def self.load_from_string(str)
10
+ doc = Hpricot::XML(str)
17
11
  restriction = SalesRestriction.new
18
12
 
19
- tmp = REXML::XPath.first(element, '//SalesRestriction/SalesRestrictionType')
20
- restriction.type = tmp.text if tmp != nil
13
+ tmp = doc.search('//SalesRestriction/SalesRestrictionType')
14
+ restriction.type = tmp.inner_html unless tmp.inner_html.blank?
21
15
 
22
- tmp = REXML::XPath.first(element, '//SalesRestriction/SalesRestrictionDetail')
23
- restriction.detail = tmp.text if tmp != nil
16
+ tmp = doc.search('//SalesRestriction/SalesRestrictionDetail')
17
+ restriction.detail = tmp.inner_html unless tmp.inner_html.blank?
24
18
 
25
19
  return restriction
26
-
27
20
  end
28
-
29
- # Return an xml element representing this contributor
30
- def to_element
31
- raise 'SalesRestriction must have a type to create an element' if self.type.nil?
32
- raise 'Contributor must have a detail to create an element' if self.detail.nil?
21
+
22
+ # Attempts to create a contributor object using the supplied xml element
23
+ def SalesRestriction.load_from_element(element)
24
+ raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
33
25
 
34
- restriction = REXML::Element.new('SalesRestriction')
35
- restriction.add_element('SalesRestrictionType').text = self.type.to_xs
36
- restriction.add_element('SalesRestrictionDetail').text = self.detail.to_xs
26
+ if REXML::XPath.first(element, '//SalesRestriction').nil?
27
+ raise LoadError, 'supplied REXML document object does not appear contain a valid SalesRestriction fragment'
28
+ end
37
29
 
38
- return restriction
30
+ self.load_from_string(element.to_s)
39
31
  end
40
32
 
41
33
  # Return an XML string representing this contributor
42
34
  def to_s
43
- output = ''
44
-
45
- # handle the REXML API change in 1.8.6.110. bah!
46
- if RUBY_VERSION >= "1.8.6" && RUBY_PATCHLEVEL >= 110
47
- formatter = REXML::Formatters::Pretty.new( 2 )
48
- formatter.write( to_element , output )
49
- else
50
- to_element.write(output, 0)
35
+ raise 'SalesRestriction must have a type to create an element' if self.type.nil?
36
+ raise 'Contributor must have a detail to create an element' if self.detail.nil?
37
+
38
+ builder = Builder::XmlMarkup.new(:indent => 2)
39
+
40
+ builder.SalesRestriction do |sr|
41
+ sr.SalesRestrictionType self.type
42
+ sr.SalesRestrictionDetail self.detail
51
43
  end
52
- return output
44
+ end
45
+
46
+ def to_element
47
+ REXML::Document.new(to_s).root
53
48
  end
54
49
  end
55
50
  end
@@ -1,5 +1,6 @@
1
1
  require 'rexml/document'
2
2
  require 'thread'
3
+ require 'timeout'
3
4
 
4
5
  module RBook
5
6
  module Onix
@@ -14,6 +15,11 @@ module RBook
14
15
  end
15
16
 
16
17
  def doctype(name, pub_sys, long_name, uri)
18
+ # do nothing
19
+ end
20
+
21
+ def instruction(name, instruction)
22
+ # do nothing
17
23
  end
18
24
 
19
25
  def tag_start(name, attrs)
@@ -29,7 +35,7 @@ module RBook
29
35
  end
30
36
 
31
37
  def text(text)
32
- @product_fragment << text if @in_product
38
+ @product_fragment << text.to_xs if @in_product
33
39
  end
34
40
 
35
41
  def tag_end(name)
@@ -43,13 +49,13 @@ module RBook
43
49
  # A product tag is finished, so add it to the queue
44
50
  @product_fragment << "</Product>"
45
51
  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
+ product = RBook::Onix::Product.load_from_string(@product_fragment)
53
+ @queue.push(product) unless product.nil?
54
+ rescue Exception => e
52
55
  # error occurred while building the product from an XML fragment
56
+ # pop the error on the queue so it can be raised by the thread
57
+ # reading items off the queue
58
+ @queue.push(e)
53
59
  end
54
60
  @in_product = false
55
61
  else
@@ -61,7 +67,14 @@ module RBook
61
67
  # do nothing
62
68
  end
63
69
 
64
- def method_missing
70
+ def end_document
71
+ # signal to the thread reading products off the queue that there are none left.
72
+ # this event curently isn't supported by rexml, so if an ONIX file is truncated
73
+ # and is missing </ONIXMessage>, it will hang. Patch submitted upstream
74
+ @queue.push(nil)
75
+ end
76
+
77
+ def method_missing(*args)
65
78
  # do nothing
66
79
  end
67
80
  end
@@ -91,6 +104,7 @@ module RBook
91
104
  else
92
105
  throw "Unable to read from path or file"
93
106
  end
107
+
94
108
  # create a sized queue to store each product read from the file
95
109
  @queue = SizedQueue.new(100)
96
110
 
@@ -109,11 +123,19 @@ module RBook
109
123
  # puts product.inspect
110
124
  # end
111
125
  def each
112
- obj = @queue.pop
126
+ # if the ONIX file we're processing has been truncated (no </ONIXMessage>), then
127
+ # we will block on the next @queue.pop indefinitely, so give it a time limit
128
+ obj = nil
129
+ Timeout::timeout(5) { obj = @queue.pop }
113
130
  while !obj.nil?
131
+ raise obj if obj.kind_of?(Exception)
114
132
  yield obj
115
- obj = @queue.pop
133
+
134
+ Timeout::timeout(5) { obj = @queue.pop }
116
135
  end
136
+ rescue Timeout::Error
137
+ # do nothing, no more items on the queue - possibly the source
138
+ # file wasn't an XML file?
117
139
  end
118
140
  end
119
141
  end
@@ -7,74 +7,70 @@ module Onix
7
7
  class SupplyDetail
8
8
  attr_accessor :supplier_name, :availability_code, :intermediary_availability_code, :price, :price_type_code
9
9
 
10
- # Attempts to create a contributor object using the supplied xml element
11
- def self.load_from_element(element)
12
- raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
13
-
14
- if REXML::XPath.first(element, '//SupplyDetail').nil?
15
- raise LoadError, 'supplied REXML document object does not appear contain a valid SupplyDetail fragment'
16
- end
17
-
10
+ def self.load_from_string(str)
11
+ doc = Hpricot::XML(str)
18
12
  supply_detail = SupplyDetail.new
19
13
 
20
- tmp = REXML::XPath.first(element, '//SupplyDetail/SupplierName')
21
- supply_detail.supplier_name = tmp.text if tmp != nil
14
+ tmp = doc.search('//SupplyDetail/SupplierName')
15
+ supply_detail.supplier_name = tmp.inner_html unless tmp.inner_html.blank?
22
16
 
23
- tmp = REXML::XPath.first(element, '//SupplyDetail/AvailabilityCode')
24
- supply_detail.availability_code = tmp.text if tmp != nil
17
+ tmp = doc.search('//SupplyDetail/AvailabilityCode')
18
+ supply_detail.availability_code = tmp.inner_html unless tmp.inner_html.blank?
25
19
 
26
- tmp = REXML::XPath.first(element, '//SupplyDetail/IntermediaryAvailabilityCode')
27
- supply_detail.intermediary_availability_code = tmp.text if tmp != nil
20
+ tmp = doc.search('//SupplyDetail/IntermediaryAvailabilityCode')
21
+ supply_detail.intermediary_availability_code = tmp.inner_html unless tmp.inner_html.blank?
28
22
 
29
- tmp = REXML::XPath.first(element, '//SupplyDetail/Price/PriceAmount')
30
- supply_detail.price = BigDecimal(tmp.text) if tmp != nil
23
+ tmp = doc.search('//SupplyDetail/Price/PriceAmount')
24
+ supply_detail.price = BigDecimal(tmp.inner_html) unless tmp.inner_html.blank?
31
25
 
32
- tmp = REXML::XPath.first(element, '//SupplyDetail/Price/PriceTypeCode')
33
- supply_detail.price_type_code = tmp.text.to_i if tmp != nil
26
+ tmp = doc.search('//SupplyDetail/Price/PriceTypeCode')
27
+ supply_detail.price_type_code = tmp.inner_html.to_i unless tmp.inner_html.blank?
34
28
 
35
29
  return supply_detail
36
-
37
30
  end
38
-
39
- # Return an xml element representing this contributor
40
- def to_element
41
- raise 'SupplyDetail must have a supplier name to create an element' if self.supplier_name.nil?
42
- raise 'SupplyDetail must have a availability code to create an element' if self.availability_code.nil?
31
+
32
+ # Attempts to create a contributor object using the supplied xml element
33
+ def self.load_from_element(element)
34
+ raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
43
35
 
44
- supply_detail = REXML::Element.new('SupplyDetail')
45
- supply_detail.add_element('SupplierName').text = self.supplier_name.to_xs
46
- supply_detail.add_element('AvailabilityCode').text = self.availability_code.to_xs unless self.availability_code.nil?
47
- supply_detail.add_element('IntermediaryAvailabilityCode').text = self.intermediary_availability_code.to_xs unless self.intermediary_availability_code.nil?
48
- unless self.price.nil?
49
- tmp = REXML::Element.new('Price')
50
- if self.price.kind_of?(BigDecimal)
51
- tmp.add_element('PriceAmount').text = self.price.to_s("F")
52
- else
53
- tmp.add_element('PriceAmount').text = self.price.to_s
54
- end
55
- supply_detail.add_element(tmp)
56
- if self.price_type_code.nil?
57
- tmp.add_element('PriceTypeCode').text = "02"
58
- else
59
- tmp.add_element('PriceTypeCode').text = self.price_type_code
60
- end
36
+ if REXML::XPath.first(element, '//SupplyDetail').nil?
37
+ raise LoadError, 'supplied REXML document object does not appear contain a valid SupplyDetail fragment'
61
38
  end
62
39
 
63
- return supply_detail
40
+ self.load_from_string(element.to_s)
64
41
  end
65
42
 
66
- # Return an XML string representing this contributor
43
+ def to_element
44
+ REXML::Document.new(to_s).root
45
+ end
46
+
47
+ # Return an XML string representing this supply detail
67
48
  def to_s
68
- output = ''
49
+ raise 'SupplyDetail must have a supplier name to create an element' if self.supplier_name.nil?
50
+ raise 'SupplyDetail must have a availability code to create an element' if self.availability_code.nil?
51
+
52
+ builder = Builder::XmlMarkup.new(:indent => 2)
69
53
 
70
- # handle the REXML API change in 1.8.6.110. bah!
71
- if RUBY_VERSION >= "1.8.6" && RUBY_PATCHLEVEL >= 110
72
- formatter = REXML::Formatters::Pretty.new( 2 )
73
- formatter.write( to_element , output )
74
- else
75
- to_element.write(output, 0)
54
+ builder.SupplyDetail do |supply|
55
+ supply.SupplierName self.supplier_name
56
+ supply.AvailabilityCode self.availability_code unless self.availability_code.nil?
57
+ supply.IntermediaryAvailabilityCode self.intermediary_availability_code unless self.intermediary_availability_code.nil?
58
+ unless self.price.nil?
59
+ tmp = REXML::Element.new('Price')
60
+ supply.Price do |p|
61
+ if self.price.kind_of?(BigDecimal)
62
+ p.PriceAmount self.price.to_s("F")
63
+ else
64
+ p.PriceAmount self.price.to_s
65
+ end
66
+ if self.price_type_code.nil?
67
+ p.PriceTypeCode "02"
68
+ else
69
+ p.PriceTypeCode self.price_type_code
70
+ end
71
+ end
72
+ end
76
73
  end
77
- return output
78
74
  end
79
75
  end
80
76
  end
@@ -16,12 +16,12 @@ context "A product object with valid data" do
16
16
  contributor.sequence_number = '01'
17
17
  contributor.role = 'A01'
18
18
 
19
- doc = contributor.to_element
19
+ doc = REXML::Document.new(contributor.to_s)
20
20
 
21
- REXML::XPath.first(doc, '/PersonNameInverted').should_not be_nil
22
- REXML::XPath.first(doc, '/PersonNameInverted').text.should eql("Healy, James")
23
- REXML::XPath.first(doc, '/ContributorRole').should_not be_nil
24
- REXML::XPath.first(doc, '/ContributorRole').text.should eql("A01")
21
+ REXML::XPath.first(doc, '//PersonNameInverted').should_not be_nil
22
+ REXML::XPath.first(doc, '//PersonNameInverted').text.should eql("Healy, James")
23
+ REXML::XPath.first(doc, '//ContributorRole').should_not be_nil
24
+ REXML::XPath.first(doc, '//ContributorRole').text.should eql("A01")
25
25
  end
26
26
 
27
27
  specify "should output a valid string" do
@@ -31,6 +31,14 @@ context "A product object with valid data" do
31
31
  contributor.role = 'A01'
32
32
 
33
33
  contributor.to_s.should be_a_kind_of(String)
34
+ end
34
35
 
36
+ specify "should output a valid REXML::Element" do
37
+ contributor = RBook::Onix::Contributor.new
38
+ contributor.name_inverted = 'Healy, James'
39
+ contributor.sequence_number = '01'
40
+ contributor.role = 'A01'
41
+
42
+ contributor.to_element.should be_a_kind_of(REXML::Element)
35
43
  end
36
44
  end
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE ONIXMessage SYSTEM "http://www.editeur.org/onix/2.1/02/reference/onix-international.dtd">
3
+ <ONIXMessage>
4
+ <Header>
5
+ <FromCompany>Walker Books Australia</FromCompany>
6
+ <SentDate>20070613</SentDate>
7
+ </Header>
8
+ <Product>
9
+ <RecordReference>9789900036708</RecordReference>
10
+ <NotificationType>03</NotificationType>
11
+ <ProductIdentifier>
12
+ <ProductIDType>03</ProductIDType>
13
+ <IDValue>9789900036708</IDValue>
14
+ </ProductIdentifier>
15
+ <ProductIdentifier>
16
+ <ProductIDType>15</ProductIDType>
17
+ <IDValue>9789900036708</IDValue>
18
+ </ProductIdentifier>
19
+ <ProductForm>WX</ProductForm>
20
+ <NoContributor/>
21
+ <AudienceCode>02</AudienceCode>
22
+ <Imprint>
23
+ <ImprintName>Walker Books</ImprintName>
24
+ </Imprint>
25
+ <Publisher>
26
+ <PublishingRole>01</PublishingRole>
27
+ <PublisherName>Walker Books</PublisherName>
28
+ </Publisher>
29
+ <PublishingStatus>02</PublishingStatus>
30
+ <PublicationDate>20070901</PublicationDate>
31
+ <SupplyDetail>
32
+ <SupplierName>TL Distribution</SupplierName>
33
+ <SupplierRole>02</SupplierRole>
34
+ <ProductAvailability>10</ProductAvailability>
35
+ <Stock>
36
+ <OnHand>NYP</OnHand>
37
+ <OnOrder>No</OnOrder>
38
+ </Stock>
39
+ <Price>
40
+ <PriceTypeCode>02</PriceTypeCode>
41
+ <PriceAmount>508.50</PriceAmount>
42
+ </Price>
43
+ </SupplyDetail>
44
+ <MarketRepresentation>
45
+ <AgentName>Walker Books Australia</AgentName>
46
+ <AgentRole>07</AgentRole>
47
+ <MarketCountry>AU</MarketCountry>
48
+ <MarketPublishingStatus>02</MarketPublishingStatus>
49
+ <MarketDate>
50
+ <MarketDateRole>01</MarketDateRole>
51
+ <Date>20070901</Date>
52
+ </MarketDate>
53
+ </MarketRepresentation>
54
+ </Product>
55
+ </ONIXMessage>
@@ -0,0 +1,70 @@
1
+ <b>ERROR!!!!</b> ORA-00942: table or view does not exist
2
+ <br><b>ERROR!!!!</b> ORA-00942: table or view does not exist
3
+ <br><b>ERROR!!!!</b> ORA-00942: table or view does not exist
4
+ <br><b>ERROR!!!!</b> ORA-00942: table or view does not exist
5
+ <br><b>ERROR!!!!</b> ORA-00942: table or view does not exist
6
+ <br><?xml version="1.0" encoding="utf-8"?>
7
+ <!DOCTYPE ONIXMessage SYSTEM "http://www.editeur.org/onix/2.1/reference/onix-international.dtd">
8
+ <ONIXMessage>
9
+ <Header>
10
+ <FromCompany>TitlePage</FromCompany>
11
+ <FromPerson>TitlePage 02 92819788</FromPerson>
12
+ <FromEmail>titlepage@publishers.asn.au</FromEmail>
13
+ <SentDate>20071104</SentDate>
14
+ <MessageNote>This data is copyright to TitlePage. TitlePage makes no guarantee of the accuracy or the timeliness of production. It is supplied for your exclusive use as our customer and is only to be used for advising on the pricing and availability of publications (Authorised Purpose). You must not charge a fee for this service. TitlePage and its content may not be used for any other purpose. While it may be copied once for the authorised purpose, written permission from TitlePage must be obtained for any other use. If you were not an intended recipient, you must notify the sender and delete all copies.</MessageNote>
15
+ </Header>
16
+ <Product>
17
+ <RecordReference>77-9780140441185</RecordReference>
18
+ <NotificationType>03</NotificationType>
19
+ <ProductForm>BC</ProductForm>
20
+ <ProductFormDetail>B305</ProductFormDetail>
21
+ <Title>
22
+ <TitleType>01</TitleType>
23
+ <TitleText>Thus Spoke Zarathustra</TitleText>
24
+ </Title>
25
+ <Website>
26
+ <WebsiteLink>http://www.penguin.com.au/default.cfm?SBN=9780140441185</WebsiteLink>
27
+ </Website>
28
+ <EditionNumber>1</EditionNumber>
29
+ <NumberOfPages>352</NumberOfPages>
30
+ <BICMainSubject>HPC</BICMainSubject>
31
+ <AudienceCode>01</AudienceCode>
32
+ <MediaFile>
33
+ <MediaFileTypeCode>04</MediaFileTypeCode>
34
+ <MediaFileLinkTypeCode>01</MediaFileLinkTypeCode>
35
+ <MediaFileLink>http://coverimages.titlepage.com.au/77/9780140441185.gif</MediaFileLink>
36
+ </MediaFile>
37
+ <Imprint>
38
+ <ImprintName>Penguin</ImprintName>
39
+ </Imprint>
40
+ <Publisher>
41
+ <PublishingRole>01</PublishingRole>
42
+ <PublisherName>Penguin UK</PublisherName>
43
+ </Publisher>
44
+ <PublishingStatus>04</PublishingStatus>
45
+ <PublicationDate>19640101</PublicationDate>
46
+ <YearFirstPublished>1964</YearFirstPublished>
47
+ <SupplyDetail>
48
+ <SupplierName>United Book Distributors</SupplierName>
49
+ <ProductAvailability>20</ProductAvailability>
50
+ <Stock>
51
+ <OnHand>In Stock</OnHand>
52
+ <OnOrder>No</OnOrder>
53
+ </Stock>
54
+ <PackQuantity>48</PackQuantity>
55
+ <Price>
56
+ <PriceTypeCode>02</PriceTypeCode>
57
+ <PriceAmount>12.95</PriceAmount>
58
+ </Price>
59
+ </SupplyDetail>
60
+ <MarketRepresentation>
61
+ <AgentName>Penguin Group Australia</AgentName>
62
+ <MarketCountry>AU</MarketCountry>
63
+ <MarketPublishingStatus>04</MarketPublishingStatus>
64
+ <MarketDate>
65
+ <MarketDateRole>01</MarketDateRole>
66
+ <Date>19640101</Date>
67
+ </MarketDate>
68
+ </MarketRepresentation>
69
+ </Product>
70
+ </ONIXMessage>