rbook-onix 0.5

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 (42) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +13 -0
  3. data/README +0 -0
  4. data/Rakefile +90 -0
  5. data/examples/stream_reader.rb +13 -0
  6. data/lib/rbook/onix.rb +70 -0
  7. data/lib/rbook/onix/contributor.rb +59 -0
  8. data/lib/rbook/onix/lists.rb +2 -0
  9. data/lib/rbook/onix/lists/contributor_role.rb +10 -0
  10. data/lib/rbook/onix/lists/product_form.rb +100 -0
  11. data/lib/rbook/onix/message.rb +121 -0
  12. data/lib/rbook/onix/product.rb +208 -0
  13. data/lib/rbook/onix/sales_restriction.rb +50 -0
  14. data/lib/rbook/onix/stream_reader.rb +120 -0
  15. data/lib/rbook/onix/stream_writer.rb +40 -0
  16. data/lib/rbook/onix/supply_detail.rb +75 -0
  17. data/lib/rbook/onix/xchar.rb +62 -0
  18. data/specs/contributor_class_spec.rb +33 -0
  19. data/specs/contributor_with_data_spec.rb +36 -0
  20. data/specs/data/10_products.xml +469 -0
  21. data/specs/data/2_products_utf16.xml +0 -0
  22. data/specs/data/abingdon.xml +38931 -0
  23. data/specs/data/augsburg.xml +39009 -0
  24. data/specs/data/chalice.xml +10851 -0
  25. data/specs/data/eerdsman.xml +36942 -0
  26. data/specs/data/invalid_no_product.xml +9 -0
  27. data/specs/data/invalid_xml.xml +24 -0
  28. data/specs/data/not_xml.csv +1 -0
  29. data/specs/data/single_product.xml +55 -0
  30. data/specs/data/xml_not_onix.xml +7 -0
  31. data/specs/message_class_spec.rb +110 -0
  32. data/specs/message_with_data_spec.rb +81 -0
  33. data/specs/product_class_spec.rb +42 -0
  34. data/specs/product_with_data_spec.rb +66 -0
  35. data/specs/sales_restriction_class_spec.rb +32 -0
  36. data/specs/sales_restriction_with_data_spec.rb +31 -0
  37. data/specs/stream_reader_spec.rb +31 -0
  38. data/specs/stream_writer_spec.rb +30 -0
  39. data/specs/supply_detail_class_spec.rb +35 -0
  40. data/specs/supply_detail_with_data_spec.rb +42 -0
  41. data/specs/xchar_spec.rb +39 -0
  42. metadata +116 -0
@@ -0,0 +1,70 @@
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/onix/stream_reader'
11
+ require 'rbook/onix/stream_writer'
12
+ require 'rbook/isbn'
13
+
14
+ module RBook
15
+
16
+ # Ruby classes for working with ONIX files. Currently only supports a very limited
17
+ # subset of the ONIX specification, but the core attributes are there. More will be
18
+ # added over time.
19
+ #
20
+ # = Usage
21
+ #
22
+ # == Loading from an REXML document
23
+ #
24
+ # require 'rubygems'
25
+ # require 'rbook/onix'
26
+ # file = File.open("./onix_message.xml", "r")
27
+ # doc = REXML::Document.new(file)
28
+ # msg = Onix::Message.load_from_xmldoc(doc)
29
+ #
30
+ # puts msg.from_company
31
+ #
32
+ # == Creation
33
+ #
34
+ # require 'rubygems'
35
+ # require 'rbook/onix'
36
+ # msg = RBook::Onix::Message.new
37
+ # msg.from_company = 'XYZ Books'
38
+ # msg.from_name = 'Joe Blogs'
39
+ # msg.message_note = "A sample ONIX file"
40
+ #
41
+ # product = RBook::Onix::Product.new
42
+ # product.product_identifier = "020161622X"
43
+ # product.title = "Pragmatic Programmer"
44
+ # product.subtitle = "From Journeyman to Master"
45
+ # product.form = "BB"
46
+ # product.description = "A book about programming and stuff"
47
+ #
48
+ # contributor1 = RBook::Onix::Contributor.new
49
+ # contributor1.name_inverted = "Hunt, Andrew"
50
+ # contributor1.role = "A01"
51
+ # contributor1.sequence = "01"
52
+ # contributor2 = RBook::Onix::Contributor.new
53
+ # contributor2.name_inverted = "Thomas, David"
54
+ # contributor2.role = "A01"
55
+ # contributor2.sequence = "02"
56
+ # product.add_contributor(contributor1)
57
+ # product.add_contributor(contributor2)
58
+ #
59
+ # supply_detail = RBook::Onix::SupplyDetail.new
60
+ # supply_detail.supplier_name = "Rainbow Book Agencies"
61
+ # supply_detail.price = BigDecimal.new(29.95)
62
+ # supply_detail.availability = "CS"
63
+ # product.add_supply_detail(supply_detail)
64
+ #
65
+ # msg.add_product(product)
66
+ # puts msg.to_s
67
+ module Onix
68
+ class InvalidRubyVersionError < RuntimeError;end;
69
+ end
70
+ end
@@ -0,0 +1,59 @@
1
+ require 'rexml/document'
2
+
3
+ module RBook
4
+ module Onix
5
+
6
+ class Contributor
7
+ attr_accessor :sequence_number, :role, :name, :name_inverted
8
+
9
+ # Attempts to create a contributor object using the supplied xml element
10
+ def Contributor.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, '//Contributor').nil?
14
+ raise LoadError, 'supplied REXML document object does not appear contain a valid contributor fragment'
15
+ end
16
+
17
+ contributor = Contributor.new
18
+
19
+ tmp = REXML::XPath.first(element, '//Contributor/SequenceNumber')
20
+ contributor.sequence_number = tmp.text if tmp != nil
21
+
22
+ tmp = REXML::XPath.first(element, '//Contributor/ContributorRole')
23
+ contributor.role = tmp.text if tmp != nil
24
+
25
+ tmp = REXML::XPath.first(element, '//Contributor/PersonName')
26
+ contributor.name = tmp.text if tmp != nil
27
+
28
+ tmp = REXML::XPath.first(element, '//Contributor/PersonNameInverted')
29
+ contributor.name_inverted = tmp.text if tmp != nil
30
+
31
+ return contributor
32
+
33
+ end
34
+
35
+ # Return an xml element representing this contributor
36
+ def to_element
37
+ raise 'Contributor must have a sequence number to create an element' if @sequence_number.nil?
38
+ raise 'Contributor must have a role to create an element' if @role.nil?
39
+ raise '' if @name_inverted.nil? && @name.nil?
40
+
41
+ contributor = REXML::Element.new('Contributor')
42
+ contributor.add_element('SequenceNumber').text = self.sequence_number
43
+ contributor.add_element('ContributorRole').text = self.role.to_xs
44
+ contributor.add_element('PersonName').text = name.to_xs unless self.name.nil?
45
+ contributor.add_element('PersonNameInverted').text = self.name_inverted.to_xs unless self.name_inverted.nil?
46
+
47
+ return contributor
48
+ end
49
+
50
+ # Return an XML string representing this contributor
51
+ def to_s
52
+ tmp = to_element
53
+ output = ''
54
+ tmp.write(output, 0)
55
+ return output
56
+ end
57
+ end
58
+ end
59
+ 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,121 @@
1
+ require 'rexml/document'
2
+
3
+ module RBook
4
+ module Onix
5
+
6
+ class Message
7
+ attr_reader :products
8
+ attr_accessor :from_company, :from_person, :from_email, :message_note, :sent_date, :to_person, :to_company
9
+
10
+ VERSION = 0.1
11
+ ONIX_DTD_URL = "http://www.editeur.org/onix/2.1/reference/onix-international.dtd"
12
+
13
+ def initialize
14
+ @products = []
15
+ end
16
+
17
+ def self.check_ruby_version
18
+ raise InvalidRubyVersionError, 'Ruby 1.8.6 or higher is required to use this class' unless RUBY_VERSION >= "1.8.6"
19
+ end
20
+
21
+ # Attempts to create a message object using thge supplied xml string
22
+ def self.load_from_string(str)
23
+ self.check_ruby_version
24
+ begin
25
+ doc = REXML::Document.new(str)
26
+ self.load_from_xmldoc(doc)
27
+ rescue REXML::ParseException => e
28
+ raise LoadError, e.message
29
+ end
30
+ end
31
+
32
+ # Attempts to create a message object using thge supplied xml document
33
+ def self.load_from_xmldoc(doc)
34
+ self.check_ruby_version
35
+ raise ArgumentError, 'load_from_xmldoc expects a REXML document object' unless doc.class == REXML::Document
36
+ if REXML::XPath.first(doc, '/ONIXMessage/').nil? ||
37
+ REXML::XPath.first(doc, '/ONIXMessage/Header').nil? ||
38
+ REXML::XPath.first(doc, '/ONIXMessage/Product').nil?
39
+ raise LoadError, 'supplied REXML document object does not appear to be a valid ONIX file'
40
+ end
41
+
42
+ msg = Onix::Message.new
43
+
44
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromCompany')
45
+ msg.from_company = tmpelement.text if tmpelement != nil
46
+
47
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromPerson')
48
+ msg.from_person = tmpelement.text if tmpelement != nil
49
+
50
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/FromEmail')
51
+ msg.from_email = tmpelement.text if tmpelement != nil
52
+
53
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/SentDate')
54
+ tmpdate = Date.civil(tmpelement.text[0,4].to_i, tmpelement.text[4,2].to_i, tmpelement.text[6,2].to_i) if tmpelement != nil
55
+ msg.sent_date = tmpdate if tmpdate != nil
56
+
57
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/ToCompany')
58
+ msg.to_company = tmpelement.text if tmpelement != nil
59
+
60
+ tmpelement = REXML::XPath.first(doc, '/ONIXMessage/Header/ToPerson')
61
+ msg.to_person = tmpelement.text if tmpelement != nil
62
+
63
+ products = REXML::XPath.each(doc, '/ONIXMessage/Product') { |product|
64
+ msg.add_product(Onix::Product.load_from_element(product))
65
+ }
66
+
67
+ return msg
68
+ end
69
+
70
+ # Adds a new product to this message
71
+ def add_product(product)
72
+ raise ArgumentError, 'Argument must be a Onix::Product' if product.class != Onix::Product
73
+ @products << product
74
+ end
75
+
76
+ # Returns an XML string representing this message
77
+ def to_s
78
+ doc = to_xml
79
+ output = ''
80
+ doc.write(output, 0)
81
+ return output
82
+ end
83
+
84
+ def header
85
+ raise 'from_compnay must be set to create an onix message' if from_company == nil
86
+ raise 'from_person must be set to create an onix message' if from_person == nil
87
+
88
+ header = REXML::Element.new('Header')
89
+ header.add_element('FromCompany').text = from_company.to_xs
90
+ header.add_element('FromPerson').text = from_person.to_xs
91
+ header.add_element('FromEmail').text = from_email.to_xs unless from_email.nil?
92
+ header.add_element('MessageNote').text = message_note.to_xs unless message_note.nil?
93
+ header.add_element('ToCompany').text = to_company.to_xs unless to_company.nil?
94
+ header.add_element('ToPerson').text = to_person.to_xs unless to_person.nil?
95
+
96
+ now = Time.now
97
+ header.add_element('SentDate').text = "%.4d%.2d%.2d" % [ now.year, now.month, now.day ]
98
+ return header
99
+ end
100
+
101
+ # Returns an XML document representing this message
102
+ def to_xml
103
+ raise 'There must be at least 1 product to burn' if @products.size < 1
104
+
105
+ doc = REXML::Document.new
106
+ doc << REXML::XMLDecl.new
107
+ doc << REXML::DocType.new('ONIXMessage', "SYSTEM \"#{ONIX_DTD_URL}\"")
108
+ msg = doc.add_element('ONIXMessage')
109
+ msg.add_element(header)
110
+
111
+ @products.each do |product|
112
+ prod = msg.add_element(product.to_element)
113
+ end
114
+
115
+ return doc
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,208 @@
1
+ require 'rexml/document'
2
+
3
+ module RBook
4
+ module Onix
5
+ class Product
6
+
7
+ attr_accessor :record_reference, :notification_type, :product_identifier
8
+ attr_accessor :title, :subtitle, :pages, :publication_date, :price
9
+ attr_accessor :description, :form, :bicmainsubject, :publisher, :supplier
10
+ attr_accessor :website, :edition, :cover_image
11
+ attr_reader :contributors, :sales_restrictions, :supply_details
12
+
13
+ def initialize
14
+ @contributors = []
15
+ @supply_details = []
16
+ @sales_restrictions = []
17
+ end
18
+
19
+ # Attempts to create a product object using the supplied xml element
20
+ def Product.load_from_element(element)
21
+ raise ArgumentError, 'load_from_element expects a REXML element object' unless element.class == REXML::Element
22
+
23
+ if REXML::XPath.first(element, '//Product').nil?
24
+ raise LoadError, 'supplied REXML document object does not appear contain a valid product fragment'
25
+ end
26
+
27
+ product = Product.new
28
+
29
+ tmp = REXML::XPath.first(element, '//Product/RecordReference')
30
+ product.record_reference = tmp.text if tmp != nil
31
+
32
+ tmp = REXML::XPath.first(element, '//Product/NotificationType')
33
+ product.notification_type = tmp.text if tmp != nil
34
+
35
+ tmp = REXML::XPath.first(element, '//Product/ProductIdentifier/IDValue')
36
+ product.product_identifier = tmp.text if tmp != nil
37
+
38
+ tmp = REXML::XPath.first(element, '//Product/ProductForm')
39
+ product.form = tmp.text if tmp != nil
40
+
41
+ tmp = REXML::XPath.first(element, '//Product/NumberOfPages')
42
+ product.pages = tmp.text if tmp != nil
43
+
44
+ tmp = REXML::XPath.first(element, '//Product/BICMainSubject')
45
+ product.bicmainsubject = tmp.text if tmp != nil
46
+
47
+ REXML::XPath.each(element, '//Product/MediaFile') { |mediafile|
48
+ type = REXML::XPath.first(element, '//Product/MediaFile/MediaFileTypeCode')
49
+ # cover image
50
+ if type.text.eql?("04")
51
+ url = REXML::XPath.first(element, '//Product/MediaFile/MediaFileLink')
52
+ product.cover_image = url.text unless url.nil?
53
+ end
54
+ }
55
+
56
+ tmp = REXML::XPath.first(element, '//Product/DistinctiveTitle')
57
+ if !tmp.nil?
58
+ product.title = tmp.text
59
+ tmp = REXML::XPath.first(element, '//Product/Subtitle') if !tmp.nil?
60
+ product.subtitle = tmp.text
61
+ else
62
+ tmp = REXML::XPath.first(element, '//Product/Title/TitleText')
63
+ product.title = tmp.text unless tmp.nil?
64
+ tmp = REXML::XPath.first(element, '//Product/Title/Subtitle')
65
+ product.subtitle = tmp.text unless tmp.nil?
66
+ end
67
+
68
+ tmp = REXML::XPath.first(element, '//Product/EditionNumber')
69
+ product.edition = tmp.text if tmp != nil
70
+
71
+ tmp = REXML::XPath.first(element, '//Product/PublicationDate')
72
+ tmpdate = Date.civil(tmp.text[0,4].to_i, tmp.text[4,2].to_i, tmp.text[6,2].to_i) if tmp != nil
73
+ product.publication_date = tmpdate if tmpdate != nil
74
+
75
+ tmp = REXML::XPath.first(element, '//Product/Publisher/PublisherName')
76
+ product.publisher = tmp.text if tmp != nil
77
+
78
+ REXML::XPath.each(element, '//Product/Contributor') { |contributor|
79
+ product.add_contributor(Onix::Contributor.load_from_element(contributor))
80
+ }
81
+
82
+ REXML::XPath.each(element, '//Product/SupplyDetail') { |detail|
83
+ product.add_supply_detail(Onix::SupplyDetail.load_from_element(detail))
84
+ }
85
+
86
+ return product
87
+ end
88
+
89
+ # Add a new contributor to this product
90
+ def add_contributor(newAuthor)
91
+ raise ArgumentError, 'Argument to add_contributor must be an ONIX::Contributor' if newAuthor.class != Contributor
92
+ @contributors << newAuthor
93
+ end
94
+
95
+ # Add a new contributor to this product
96
+ def add_sales_restriction(restriction)
97
+ raise ArgumentError, 'Argument to add_sales_restriction must be an ONIX::SalesRestriction' if restriction.class != SalesRestriction
98
+ @sales_restrictions << restriction
99
+ end
100
+
101
+ # Add a new contributor to this product
102
+ def add_supply_detail(detail)
103
+ raise ArgumentError, 'Argument to add_supply_detail must be an ONIX::SupplyDetail' if detail.class != SupplyDetail
104
+ @supply_details << detail
105
+ end
106
+
107
+ # Return an xml element representing this contributor
108
+ def to_element
109
+ raise 'product_identifier must be set' if self.product_identifier.nil?
110
+
111
+ product = REXML::Element.new('Product')
112
+
113
+ if self.record_reference
114
+ product.add_element('RecordReference').text = self.record_reference
115
+ else
116
+ product.add_element('RecordReference').text = self.product_identifier
117
+ end
118
+
119
+ product.add_element('NotificationType').text = '03'
120
+
121
+ product_identifier = REXML::Element.new('ProductIdentifier')
122
+ if ISBN::valid_isbn13?(self.product_identifier)
123
+ product_identifier.add_element('ProductIDType').text = '03'
124
+ elsif ISBN::valid_isbn10?(self.product_identifier)
125
+ product_identifier.add_element('ProductIDType').text = '02'
126
+ else
127
+ product_identifier.add_element('ProductIDType').text = '01'
128
+ end
129
+ product_identifier.add_element('IDValue').text = self.product_identifier.to_xs
130
+ product.add_element(product_identifier)
131
+
132
+ if form != nil
133
+ product.add_element('ProductForm').text = self.form.to_xs
134
+ end
135
+
136
+ product.add_element('NoSeries')
137
+
138
+ title = REXML::Element.new('Title')
139
+ title.add_element('TitleType').text = '01'
140
+ title.add_element('TitleText').text = self.title.to_xs unless self.title.nil?
141
+ title.add_element('Subtitle').text = self.subtitle.to_xs unless self.subtitle.nil?
142
+ product.add_element(title)
143
+
144
+ if website != nil
145
+ product_website = REXML::Element.new('Website')
146
+ product_website.add_element('WebsiteLink').text = self.website.to_xs
147
+ product.add_element(product_website)
148
+ end
149
+
150
+ unless cover_image.nil?
151
+ product_mf = REXML::Element.new('MediaFile')
152
+ product_mf.add_element('MediaFileTypeCode').text = "04"
153
+ product_mf.add_element('MediaFileLinkTypeCode').text = "01"
154
+ product_mf.add_element('MediaFileLink').text = self.cover_image.to_xs
155
+ product.add_element(product_mf)
156
+ end
157
+
158
+ contributors.each do |contributor|
159
+ product.add_element(contributor.to_element)
160
+ end
161
+
162
+ product.add_element('NoEdition')
163
+
164
+ product.add_element('NumberOfPages').text = self.pages.to_s unless self.pages.nil?
165
+
166
+ if bicmainsubject != nil
167
+ product.add_element('BICMainSubject').text = self.bicmainsubject.to_xs
168
+ end
169
+
170
+ if description != nil
171
+ other_text = REXML::Element.new('OtherText')
172
+ other_text.add_element('TextTypeCode').text = '01'
173
+ other_text.add_element('Text').text = self.description.to_xs
174
+ product.add_element(other_text)
175
+ end
176
+
177
+ if publisher != nil
178
+ publisher = REXML::Element.new('Publisher')
179
+ publisher.add_element('PublishingRole').text = '01'
180
+ publisher.add_element('PublisherName').text = self.publisher.to_xs
181
+ product.add_element(publisher)
182
+ end
183
+
184
+ supply_details.each do |detail|
185
+ product.add_element(detail.to_element)
186
+ end
187
+
188
+ sales_restrictions.each do |restriction|
189
+ product.add_element(restriction.to_element)
190
+ end
191
+
192
+ if publication_date != nil
193
+ product.add_element('PublicationDate').text = "%.4d%.2d%.2d" % [ publication_date.year, publication_date.month, publication_date.day ]
194
+ end
195
+
196
+ return product
197
+ end
198
+
199
+ # return an XML string representing this contributor
200
+ def to_s
201
+ output = ''
202
+ to_element.write(output, 0)
203
+ return output
204
+ end
205
+ end
206
+
207
+ end
208
+ end