rbook 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.
Files changed (63) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +13 -0
  3. data/README +16 -0
  4. data/Rakefile +206 -0
  5. data/examples/titlepage.rb +14 -0
  6. data/examples/www/find_all.rb +23 -0
  7. data/examples/www/find_cover_from_amazon.rb +12 -0
  8. data/examples/www/find_url_from_rainbow.rb +12 -0
  9. data/examples/www/list.rb +13 -0
  10. data/lib/rbook/bisac.rb +175 -0
  11. data/lib/rbook/errors.rb +7 -0
  12. data/lib/rbook/isbn.rb +249 -0
  13. data/lib/rbook/onix.rb +68 -0
  14. data/lib/rbook/onix/contributor.rb +60 -0
  15. data/lib/rbook/onix/lists.rb +2 -0
  16. data/lib/rbook/onix/lists/contributor_role.rb +10 -0
  17. data/lib/rbook/onix/lists/product_form.rb +100 -0
  18. data/lib/rbook/onix/message.rb +101 -0
  19. data/lib/rbook/onix/product.rb +188 -0
  20. data/lib/rbook/onix/sales_restriction.rb +51 -0
  21. data/lib/rbook/onix/supply_detail.rb +68 -0
  22. data/lib/rbook/onix/xchar.rb +98 -0
  23. data/lib/rbook/titlepage.rb +96 -0
  24. data/lib/rbook/titlepage/TitleQueryClient.rb +62 -0
  25. data/lib/rbook/titlepage/titlepage_driver.rb +134 -0
  26. data/lib/rbook/titlepage/titlepage_utils.rb +374 -0
  27. data/lib/rbook/www.rb +172 -0
  28. data/lib/rbook/www/aau_scraper.rb +76 -0
  29. data/lib/rbook/www/amazon_uk_scraper.rb +44 -0
  30. data/lib/rbook/www/base.rb +87 -0
  31. data/lib/rbook/www/harper_au_scraper.rb +56 -0
  32. data/lib/rbook/www/harper_us_scraper.rb +55 -0
  33. data/lib/rbook/www/hha_scraper.rb +50 -0
  34. data/lib/rbook/www/macmillan_scraper.rb +62 -0
  35. data/lib/rbook/www/orbis_scraper.rb +48 -0
  36. data/lib/rbook/www/oup_scraper.rb +64 -0
  37. data/lib/rbook/www/paulist_scraper.rb +53 -0
  38. data/lib/rbook/www/pearson_au_scraper.rb +52 -0
  39. data/lib/rbook/www/penguin_scraper.rb +45 -0
  40. data/lib/rbook/www/random_au_scraper.rb +90 -0
  41. data/lib/rbook/www/random_us_scraper.rb +59 -0
  42. data/lib/rbook/www/sas_scraper.rb +54 -0
  43. data/lib/rbook/www/unireps_scraper.rb +58 -0
  44. data/lib/rbook/www/wiley_us_scraper.rb +54 -0
  45. data/test/data/abingdon.xml +38931 -0
  46. data/test/data/augsburg.xml +39009 -0
  47. data/test/data/chalice.xml +10851 -0
  48. data/test/data/eerdsman.xml +36942 -0
  49. data/test/data/invalid_no_product.xml +9 -0
  50. data/test/data/not_xml.csv +1 -0
  51. data/test/data/single_product.xml +50 -0
  52. data/test/data/xml_not_onix.xml +7 -0
  53. data/test/mocks/titlepage_driver.rb +107 -0
  54. data/test/unit/bisac_test.rb +57 -0
  55. data/test/unit/isbn_test.rb +149 -0
  56. data/test/unit/onix/contributor_test.rb +50 -0
  57. data/test/unit/onix/message_test.rb +119 -0
  58. data/test/unit/onix/product_test.rb +101 -0
  59. data/test/unit/onix/sales_restriction_test.rb +48 -0
  60. data/test/unit/onix/supply_detail_test.rb +53 -0
  61. data/test/unit/onix/xchar_test.rb +37 -0
  62. data/test/unit/titlepage_test.rb +127 -0
  63. metadata +130 -0
data/lib/rbook/onix.rb ADDED
@@ -0,0 +1,68 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../")
2
+
3
+ require 'rbook/onix/contributor'
4
+ require 'rbook/onix/lists'
5
+ require 'rbook/onix/message'
6
+ require 'rbook/onix/product'
7
+ require 'rbook/onix/sales_restriction'
8
+ require 'rbook/onix/supply_detail'
9
+ require 'rbook/onix/xchar'
10
+ require 'rbook/isbn'
11
+
12
+ module RBook
13
+
14
+ # Ruby classes for working with ONIX files. Currently only supports a very limited
15
+ # subset of the ONIX specification, but the core attributes are there. More will be
16
+ # added over time.
17
+ #
18
+ # = Usage
19
+ #
20
+ # == Loading from an REXML document
21
+ #
22
+ # require 'rubygems'
23
+ # require 'rbook/onix'
24
+ # file = File.open("./onix_message.xml", "r")
25
+ # doc = REXML::Document.new(file)
26
+ # msg = Onix::Message.load_from_xmldoc(doc)
27
+ #
28
+ # puts msg.from_company
29
+ #
30
+ # == Creation
31
+ #
32
+ # require 'rubygems'
33
+ # require 'rbook/onix'
34
+ # msg = RBook::Onix::Message.new
35
+ # msg.from_company = 'XYZ Books'
36
+ # msg.from_name = 'Joe Blogs'
37
+ # msg.message_note = "A sample ONIX file"
38
+ #
39
+ # product = RBook::Onix::Product.new
40
+ # product.product_identifier = "020161622X"
41
+ # product.title = "Pragmatic Programmer"
42
+ # product.subtitle = "From Journeyman to Master"
43
+ # product.form = "BB"
44
+ # product.description = "A book about programming and stuff"
45
+ #
46
+ # contributor1 = RBook::Onix::Contributor.new
47
+ # contributor1.name_inverted = "Hunt, Andrew"
48
+ # contributor1.role = "A01"
49
+ # contributor1.sequence = "01"
50
+ # contributor2 = RBook::Onix::Contributor.new
51
+ # contributor2.name_inverted = "Thomas, David"
52
+ # contributor2.role = "A01"
53
+ # contributor2.sequence = "02"
54
+ # product.add_contributor(contributor1)
55
+ # product.add_contributor(contributor2)
56
+ #
57
+ # supply_detail = RBook::Onix::SupplyDetail.new
58
+ # supply_detail.supplier_name = "Rainbow Book Agencies"
59
+ # supply_detail.price = BigDecimal.new(29.95)
60
+ # supply_detail.availability = "CS"
61
+ # product.add_supply_detail(supply_detail)
62
+ #
63
+ # msg.add_product(product)
64
+ # puts msg.to_s
65
+ module Onix
66
+
67
+ end
68
+ end
@@ -0,0 +1,60 @@
1
+ require 'rexml/document'
2
+ require 'rubygems'
3
+
4
+ module RBook
5
+ module Onix
6
+
7
+ class Contributor
8
+ attr_accessor :sequence_number, :role, :name, :name_inverted
9
+
10
+ # Attempts to create a contributor object using the supplied xml element
11
+ def Contributor.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, '//Contributor').nil?
15
+ raise LoadError, 'supplied REXML document object does not appear contain a valid contributor fragment'
16
+ end
17
+
18
+ contributor = Contributor.new
19
+
20
+ tmp = REXML::XPath.first(element, '//Contributor/SequenceNumber')
21
+ contributor.sequence_number = tmp.text if tmp != nil
22
+
23
+ tmp = REXML::XPath.first(element, '//Contributor/ContributorRole')
24
+ contributor.role = tmp.text if tmp != nil
25
+
26
+ tmp = REXML::XPath.first(element, '//Contributor/PersonName')
27
+ contributor.name = tmp.text if tmp != nil
28
+
29
+ tmp = REXML::XPath.first(element, '//Contributor/PersonNameInverted')
30
+ contributor.name_inverted = tmp.text if tmp != nil
31
+
32
+ return contributor
33
+
34
+ end
35
+
36
+ # Return an xml element representing this contributor
37
+ def to_element
38
+ raise 'Contributor must have a sequence number to create an element' if @sequence_number.nil?
39
+ raise 'Contributor must have a role to create an element' if @role.nil?
40
+ raise '' if @name_inverted.nil? && @name.nil?
41
+
42
+ contributor = REXML::Element.new('Contributor')
43
+ contributor.add_element('SequenceNumber').text = self.sequence_number
44
+ contributor.add_element('ContributorRole').text = self.role.to_xs
45
+ contributor.add_element('PersonName').text = name.to_xs unless self.name.nil?
46
+ contributor.add_element('PersonNameInverted').text = self.name_inverted.to_xs unless self.name_inverted.nil?
47
+
48
+ return contributor
49
+ end
50
+
51
+ # Return an XML string representing this contributor
52
+ def to_s
53
+ tmp = to_element
54
+ output = ''
55
+ tmp.write(output, 0)
56
+ return output
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lists/contributor_role'
2
+ require File.dirname(__FILE__) + '/lists/product_form'
@@ -0,0 +1,10 @@
1
+ module RBook
2
+ module Onix
3
+ module Lists
4
+ CONTRIBUTOR_ROLES = [
5
+ ['Author','A01'],
6
+ ['Illustrator','A02']
7
+ ].freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,100 @@
1
+ module RBook
2
+ module Onix
3
+ module Lists
4
+
5
+ FORM_CODES = [
6
+ ['Undefined', '00'],
7
+ ['Audio', 'AA'],
8
+ ['Audio - Cassette', 'AB'],
9
+ ['Audio - CD', 'AC'],
10
+ ['Audio - DAT', 'AD'],
11
+ ['Audio - Disk', 'AE'],
12
+ ['Audio - Tape', 'AF'],
13
+ ['Audio - Other', 'AZ'],
14
+ ['Book', 'BA'],
15
+ ['Book - Hardback', 'BB'],
16
+ ['Book - Paperback', 'BC'],
17
+ ['Book - Loose-leaf', 'BD'],
18
+ ['Book - Spiral Bound', 'BE'],
19
+ ['Book - Pamphlet', 'BF'],
20
+ ['Book - Leather / Fine Binding', 'BG'],
21
+ ['Book - Board', 'BH'],
22
+ ['Book - Rag', 'BI'],
23
+ ['Book - Bath', 'BJ'],
24
+ ['Book - Other', 'BZ'],
25
+ ['Cartographic - Sheet Map', 'CA'],
26
+ ['Cartographic - Sheet Map, Folded', 'CB'],
27
+ ['Cartographic - Sheet Map, Flat', 'CC'],
28
+ ['Cartographic - Sheet Map, Rolled', 'CD'],
29
+ ['Cartographic - Globe', 'CE'],
30
+ ['Cartographic - Other', 'CZ'],
31
+ ['Digital', 'DA'],
32
+ ['Digital - CDROM', 'DB'],
33
+ ['Digital - CD-Interactive', 'DC'],
34
+ ['Digital - DVD', 'DD'],
35
+ ['Digital - Game Cartridge', 'DE'],
36
+ ['Digital - Diskette', 'DF'],
37
+ ['Digital - Electronic Book Text', 'DG'],
38
+ ['Digital - Online File', 'DH'],
39
+ ['Digital - Other', 'DZ'],
40
+ ['Film or Transparency', 'FA'],
41
+ ['Film', 'FB'],
42
+ ['Film - Slides', 'FC'],
43
+ ['Film - OHP Transparencies', 'FD'],
44
+ ['Film - Other ', 'FZ'],
45
+ ['Microform', 'MA'],
46
+ ['Microform - Microfiche', 'MB'],
47
+ ['Microform - Microfilm', 'MC'],
48
+ ['Microform - Other', 'MZ'],
49
+ ['Print - Misc', 'PA'],
50
+ ['Print - Address Book', 'PB'],
51
+ ['Print - Calendar', 'PC'],
52
+ ['Print - Cards', 'PD'],
53
+ ['Print - Copymasters', 'PE'],
54
+ ['Print - Diary', 'PF'],
55
+ ['Print - Frieze', 'PG'],
56
+ ['Print - Kit', 'PH'],
57
+ ['Print - Sheet Music', 'PI'],
58
+ ['Print - Postcard Book or Pack', 'PJ'],
59
+ ['Print - Poster', 'PK'],
60
+ ['Print - Record Book', 'PL'],
61
+ ['Print - Wallet', 'PM'],
62
+ ['Print - Pictures or Photographs', 'PN'],
63
+ ['Print - Wallchart', 'PO'],
64
+ ['Print - Other', 'PZ'],
65
+ ['Video', 'VA'],
66
+ ['Video - VHS, PAL', 'VB'],
67
+ ['Video - VHA, NTSC', 'VC'],
68
+ ['Video - Betamax, PAL', 'VD'],
69
+ ['Video - Betamax, NTSC', 'VE'],
70
+ ['Video - disk', 'VF'],
71
+ ['Video - VHS, SECAM', 'VG'],
72
+ ['Video - Betamax, SECAM', 'VH'],
73
+ ['Video - Other Format','VZ'],
74
+ ['Multiple - Mixed Media Product', 'WW'],
75
+ ['Misc - Quantity Pack', 'WX'],
76
+ ['Misc - Trade-only Material', 'XA'],
77
+ ['Misc - Dumpbim - Empty', 'XB'],
78
+ ['Misc - Dumpbin - Filled', 'XC'],
79
+ ['Misc - Counterpack - Empty', 'XD'],
80
+ ['Misc - Counterpack - Filled', 'XE'],
81
+ ['Misc - Poster', 'XF'],
82
+ ['Misc - Shelf Strip', 'XG'],
83
+ ['Misc - Window Piece', 'XH'],
84
+ ['Misc - Streamer', 'XI'],
85
+ ['Misc - Spinner', 'XJ'],
86
+ ['Misc - Large Book Display', 'XK'],
87
+ ['Misc - Shrink Wrapped Pack', 'XL'],
88
+ ['Misc - Other Point of Sale', 'XZ'],
89
+ ['General Merchandise', 'ZA'],
90
+ ['Misc - Doll', 'ZB'],
91
+ ['Misc - Soft Toy', 'ZC'],
92
+ ['Misc - Toy', 'ZD'],
93
+ ['Misc - Game', 'ZE'],
94
+ ['Misc - T-shirt', 'ZF'],
95
+ ['Misc - Other Merchandise', 'ZZ'],
96
+ ].freeze
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,101 @@
1
+ require 'rubygems'
2
+ require 'rexml/document'
3
+
4
+ module RBook
5
+ module Onix
6
+
7
+ class Message
8
+ attr_reader :products
9
+ attr_accessor :from_company, :from_person, :from_email, :message_note, :sent_date, :to_person, :to_company
10
+
11
+ VERSION = 0.1
12
+
13
+ def initialize
14
+ @ONIX_DTD_URL = "http://www.editeur.org/onix/2.1/reference/onix-international.dtd"
15
+
16
+ @products = []
17
+ end
18
+
19
+ # Attempts to create a message object using thge supplied xml document
20
+ def Message.load_from_xmldoc(doc)
21
+ raise ArgumentError, 'load_from_xmldoc expects a REXML document object' unless doc.class == REXML::Document
22
+ if REXML::XPath.first(doc, '/ONIXMessage/').nil? ||
23
+ REXML::XPath.first(doc, '/ONIXMessage/Header').nil? ||
24
+ REXML::XPath.first(doc, '/ONIXMessage/Product').nil?
25
+ raise LoadError, 'supplied REXML document object does not appear to be a valid ONIX file'
26
+ end
27
+
28
+ msg = Onix::Message.new
29
+
30
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromCompany')
31
+ msg.from_company = tmpelement.text if tmpelement != nil
32
+
33
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromPerson')
34
+ msg.from_person = tmpelement.text if tmpelement != nil
35
+
36
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromEmail')
37
+ msg.from_email = tmpelement.text if tmpelement != nil
38
+
39
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/SentDate')
40
+ tmpdate = Date.civil(tmpelement.text[0,4].to_i, tmpelement.text[4,2].to_i, tmpelement.text[6,2].to_i) if tmpelement != nil
41
+ msg.sent_date = tmpdate if tmpdate != nil
42
+
43
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/ToCompany')
44
+ msg.to_company = tmpelement.text if tmpelement != nil
45
+
46
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/ToPerson')
47
+ msg.to_person = tmpelement.text if tmpelement != nil
48
+
49
+ products = REXML::XPath.each(doc, '/ONIXMessage/Product') { |product|
50
+ msg.add_product(Onix::Product.load_from_element(product))
51
+ }
52
+
53
+ return msg
54
+ end
55
+
56
+ # Adds a new product to this message
57
+ def add_product(product)
58
+ raise ArgumentError, 'Argument must be a Onix::Product' if product.class != Onix::Product
59
+ @products << product
60
+ end
61
+
62
+ # Returns an XML string representing this message
63
+ def to_s
64
+ doc = to_xml
65
+ output = ''
66
+ doc.write(output, 0)
67
+ return output
68
+ end
69
+
70
+ # Returns an XML document representing this message
71
+ def to_xml
72
+ raise 'from_compnay must be set to create an onix message' if from_company == nil
73
+ raise 'from_person must be set to create an onix message' if from_person == nil
74
+ raise 'There must be at least 1 product to burn' if @products.size < 1
75
+
76
+ doc = REXML::Document.new
77
+ doc << REXML::XMLDecl.new
78
+ doc << REXML::DocType.new('ONIXMessage', "SYSTEM \"#{@ONIX_DTD_URL}\"")
79
+ msg = doc.add_element('ONIXMessage')
80
+ header = msg.add_element('Header')
81
+ header.add_element('FromCompany').text = from_company.to_xs
82
+ header.add_element('FromPerson').text = from_person.to_xs
83
+ header.add_element('FromEmail').text = from_email.to_xs unless from_email.nil?
84
+ header.add_element('MessageNote').text = message_note.to_xs unless message_note.nil?
85
+ header.add_element('ToCompany').text = to_company.to_xs unless to_company.nil?
86
+ header.add_element('ToPerson').text = to_person.to_xs unless to_person.nil?
87
+
88
+ now = Time.now
89
+ header.add_element('SentDate').text = "%.4d%.2d%.2d" % [ now.year, now.month, now.day ]
90
+
91
+ @products.each do |product|
92
+ prod = msg.add_element(product.to_element)
93
+ end
94
+
95
+ return doc
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,188 @@
1
+ require 'rexml/document'
2
+ require 'rubygems'
3
+
4
+ module RBook
5
+ module Onix
6
+ class Product
7
+
8
+ attr_accessor :record_reference, :notification_type, :product_identifier, :title, :subtitle, :pages, :publication_date, :price, :description, :form, :bicmainsubject, :publisher, :supplier, :website, :edition
9
+ attr_reader :contributors, :sales_restrictions, :supply_details
10
+
11
+ def initialize
12
+ @contributors = []
13
+ @supply_details = []
14
+ @sales_restrictions = []
15
+ end
16
+
17
+ # Attempts to create a product object using the supplied xml element
18
+ def Product.load_from_element(element)
19
+ raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
20
+
21
+ if REXML::XPath.first(element, '//Product').nil?
22
+ raise LoadError, 'supplied REXML document object does not appear contain a valid product fragment'
23
+ end
24
+
25
+ product = Product.new
26
+
27
+ tmp = REXML::XPath.first(element, '//Product/RecordReference')
28
+ product.record_reference = tmp.text if tmp != nil
29
+
30
+ tmp = REXML::XPath.first(element, '//Product/NotificationType')
31
+ product.notification_type = tmp.text if tmp != nil
32
+
33
+ # TODO: load ProductIdentifier from the fragment
34
+
35
+ tmp = REXML::XPath.first(element, '//Product/ProductForm')
36
+ product.form = tmp.text if tmp != nil
37
+
38
+ tmp = REXML::XPath.first(element, '//Product/NumberOfPages')
39
+ product.pages = tmp.text if tmp != nil
40
+
41
+ tmp = REXML::XPath.first(element, '//Product/BICMainSubject')
42
+ product.bicmainsubject = tmp.text if tmp != nil
43
+
44
+ tmp = REXML::XPath.first(element, '//Product/DistinctiveTitle')
45
+ if !tmp.nil?
46
+ product.title = tmp.text
47
+ tmp = REXML::XPath.first(element, '//Product/Subtitle') if !tmp.nil?
48
+ product.subtitle = tmp.text
49
+ else
50
+ tmp = REXML::XPath.first(element, '//Product/Title/TitleText')
51
+ product.title = tmp.text unless tmp.nil?
52
+ tmp = REXML::XPath.first(element, '//Product/Title/Subtitle')
53
+ product.subtitle = tmp.text unless tmp.nil?
54
+ end
55
+
56
+ tmp = REXML::XPath.first(element, '//Product/EditionNumber')
57
+ product.edition = tmp.text if tmp != nil
58
+
59
+ tmp = REXML::XPath.first(element, '//Product/PublicationDate')
60
+ tmpdate = Date.civil(tmp.text[0,4].to_i, tmp.text[4,2].to_i, tmp.text[6,2].to_i) if tmp != nil
61
+ product.publication_date = tmpdate if tmpdate != nil
62
+
63
+ tmp = REXML::XPath.first(element, '//Product/Publisher/PublisherName')
64
+ product.publisher = tmp.text if tmp != nil
65
+
66
+ REXML::XPath.each(element, '//Product/Contributor') { |contributor|
67
+ product.add_contributor(Onix::Contributor.load_from_element(contributor))
68
+ }
69
+
70
+ REXML::XPath.each(element, '//Product/SupplyDetail') { |detail|
71
+ product.add_supply_detail(Onix::SupplyDetail.load_from_element(detail))
72
+ }
73
+
74
+ return product
75
+ end
76
+
77
+ # Add a new contributor to this product
78
+ def add_contributor(newAuthor)
79
+ raise ArgumentError, 'Argument to add_contributor must be an ONIX::Contributor' if newAuthor.class != Contributor
80
+ @contributors << newAuthor
81
+ end
82
+
83
+ # Add a new contributor to this product
84
+ def add_sales_restriction(restriction)
85
+ raise ArgumentError, 'Argument to add_sales_restriction must be an ONIX::SalesRestriction' if restriction.class != SalesRestriction
86
+ @sales_restrictions << restriction
87
+ end
88
+
89
+ # Add a new contributor to this product
90
+ def add_supply_detail(detail)
91
+ raise ArgumentError, 'Argument to add_supply_detail must be an ONIX::SupplyDetail' if detail.class != SupplyDetail
92
+ @supply_details << detail
93
+ end
94
+
95
+ # Return an xml element representing this contributor
96
+ def to_element
97
+ raise 'product_identifier must be set' if self.product_identifier.nil?
98
+
99
+ product = REXML::Element.new('Product')
100
+
101
+ if self.record_reference
102
+ product.add_element('RecordReference').text = self.record_reference
103
+ else
104
+ product.add_element('RecordReference').text = self.product_identifier
105
+ end
106
+
107
+ product.add_element('NotificationType').text = '03'
108
+
109
+ product_identifier = REXML::Element.new('ProductIdentifier')
110
+ if ISBN::valid_isbn13?(self.product_identifier)
111
+ product_identifier.add_element('ProductIDType').text = '03'
112
+ elsif ISBN::valid_isbn10?(self.product_identifier)
113
+ product_identifier.add_element('ProductIDType').text = '02'
114
+ else
115
+ product_identifier.add_element('ProductIDType').text = '01'
116
+ end
117
+ product_identifier.add_element('IDValue').text = self.product_identifier.to_xs
118
+ product.add_element(product_identifier)
119
+
120
+ if form != nil
121
+ product.add_element('ProductForm').text = self.form.to_xs
122
+ end
123
+
124
+ product.add_element('NoSeries')
125
+
126
+ title = REXML::Element.new('Title')
127
+ title.add_element('TitleType').text = '01'
128
+ title.add_element('TitleText').text = self.title.to_xs unless self.title.nil?
129
+ title.add_element('Subtitle').text = self.subtitle.to_xs unless self.subtitle.nil?
130
+ product.add_element(title)
131
+
132
+ if website != nil
133
+ product_website = REXML::Element.new('Website')
134
+ product_website.add_element('WebsiteLink').text = self.website.to_xs
135
+ product.add_element(product_website)
136
+ end
137
+
138
+ contributors.each do |contributor|
139
+ product.add_element(contributor.to_element)
140
+ end
141
+
142
+ product.add_element('NoEdition')
143
+
144
+ product.add_element('NumberOfPages').text = self.pages.to_s unless self.pages.nil?
145
+
146
+ if bicmainsubject != nil
147
+ product.add_element('BICMainSubject').text = self.bicmainsubject.to_xs
148
+ end
149
+
150
+ if description != nil
151
+ other_text = REXML::Element.new('OtherText')
152
+ other_text.add_element('TextTypeCode').text = '01'
153
+ other_text.add_element('Text').text = self.description.to_xs
154
+ product.add_element(other_text)
155
+ end
156
+
157
+ if publisher != nil
158
+ publisher = REXML::Element.new('Publisher')
159
+ publisher.add_element('PublishingRole').text = '01'
160
+ publisher.add_element('PublisherName').text = self.publisher.to_xs
161
+ product.add_element(publisher)
162
+ end
163
+
164
+ supply_details.each do |detail|
165
+ product.add_element(detail.to_element)
166
+ end
167
+
168
+ sales_restrictions.each do |restriction|
169
+ product.add_element(restriction.to_element)
170
+ end
171
+
172
+ if publication_date != nil
173
+ product.add_element('PublicationDate').text = "%.4d%.2d%.2d" % [ publication_date.year, publication_date.month, publication_date.day ]
174
+ end
175
+
176
+ return product
177
+ end
178
+
179
+ # return an XML string representing this contributor
180
+ def to_s
181
+ output = ''
182
+ to_element.write(output, 0)
183
+ return output
184
+ end
185
+ end
186
+
187
+ end
188
+ end