im_onix 1.0.2 → 1.1.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.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/Gemfile +8 -0
- data/LICENSE.md +7 -0
- data/README.md +3 -3
- data/Rakefile +10 -0
- data/bin/html_codelist_to_yml.rb +14 -15
- data/bin/onix_bench.rb +1 -0
- data/bin/onix_pp.rb +4 -8
- data/bin/onix_serialize.rb +27 -0
- data/doc-src/handlers.rb +154 -0
- data/im_onix.gemspec +32 -0
- data/lib/im_onix.rb +0 -1
- data/lib/onix/addressee.rb +10 -0
- data/lib/onix/code.rb +108 -282
- data/lib/onix/collateral_detail.rb +24 -17
- data/lib/onix/collection.rb +38 -0
- data/lib/onix/collection_sequence.rb +7 -0
- data/lib/onix/contributor.rb +40 -39
- data/lib/onix/date.rb +73 -109
- data/lib/onix/descriptive_detail.rb +90 -417
- data/lib/onix/discount_coded.rb +3 -16
- data/lib/onix/entity.rb +28 -62
- data/lib/onix/epub_usage_constraint.rb +7 -0
- data/lib/onix/epub_usage_limit.rb +6 -0
- data/lib/onix/extent.rb +39 -0
- data/lib/onix/helper.rb +25 -25
- data/lib/onix/identifier.rb +13 -54
- data/lib/onix/language.rb +8 -0
- data/lib/onix/market.rb +5 -0
- data/lib/onix/market_publishing_detail.rb +20 -0
- data/lib/onix/onix21.rb +76 -139
- data/lib/onix/onix_message.rb +87 -100
- data/lib/onix/price.rb +19 -39
- data/lib/onix/product.rb +141 -637
- data/lib/onix/product_form_feature.rb +7 -0
- data/lib/onix/product_part.rb +89 -0
- data/lib/onix/product_supplies_extractor.rb +275 -0
- data/lib/onix/product_supply.rb +17 -58
- data/lib/onix/publishing_detail.rb +16 -32
- data/lib/onix/related_material.rb +4 -3
- data/lib/onix/related_product.rb +9 -29
- data/lib/onix/related_work.rb +3 -17
- data/lib/onix/sales_outlet.rb +2 -10
- data/lib/onix/sales_restriction.rb +8 -21
- data/lib/onix/sales_rights.rb +1 -5
- data/lib/onix/sender.rb +12 -0
- data/lib/onix/serializer.rb +156 -0
- data/lib/onix/subject.rb +9 -30
- data/lib/onix/subset.rb +88 -78
- data/lib/onix/supply_detail.rb +42 -0
- data/lib/onix/supporting_resource.rb +29 -86
- data/lib/onix/tax.rb +9 -18
- data/lib/onix/territory.rb +23 -17
- data/lib/onix/title_detail.rb +22 -0
- data/lib/onix/title_element.rb +32 -0
- data/lib/onix/website.rb +3 -16
- metadata +53 -34
@@ -0,0 +1,7 @@
|
|
1
|
+
module ONIX
|
2
|
+
class ProductFormFeature < SubsetDSL
|
3
|
+
element "ProductFormFeatureType", :subset, :shortcut => :type
|
4
|
+
element "ProductFormFeatureValue", :text, :shortcut => :value
|
5
|
+
elements "ProductFormFeatureDescription", :text, :shortcut => :descriptions
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ONIX
|
2
|
+
# product part use full Product to provide file protection and file size
|
3
|
+
class ProductPart < SubsetDSL
|
4
|
+
include EanMethods
|
5
|
+
include ProprietaryIdMethods
|
6
|
+
|
7
|
+
elements "ProductIdentifier", :subset, :shortcut => :identifiers
|
8
|
+
element "ProductForm", :subset, :shortcut => :form
|
9
|
+
element "ProductFormDescription", :text, :shortcut => :file_description
|
10
|
+
elements "ProductFormDetail", :subset, :shortcut => :form_details
|
11
|
+
elements "ProductContentType", :subset, :shortcut => :content_types
|
12
|
+
element "NumberOfCopies", :integer
|
13
|
+
|
14
|
+
def file_formats
|
15
|
+
@product_form_details.select { |fd| fd.code =~ /^E1.*/ }
|
16
|
+
end
|
17
|
+
|
18
|
+
# full Product if referenced in ONIXMessage
|
19
|
+
attr_accessor :product
|
20
|
+
|
21
|
+
# this ProductPart is part of Product
|
22
|
+
attr_accessor :part_of
|
23
|
+
|
24
|
+
# @!group High level
|
25
|
+
|
26
|
+
# digital file format string (Epub,Pdf,AmazonKindle)
|
27
|
+
# @return [String]
|
28
|
+
def file_format
|
29
|
+
file_formats.first.human if file_formats.first
|
30
|
+
end
|
31
|
+
|
32
|
+
# digital file format mimetype
|
33
|
+
# @return [String]
|
34
|
+
def file_mimetype
|
35
|
+
if file_formats.first
|
36
|
+
file_formats.first.mimetype
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# is digital file reflowable ?
|
41
|
+
# @return [Boolean]
|
42
|
+
def reflowable?
|
43
|
+
return true if @product_form_details.select { |fd| fd.code == "E200" }.length > 0
|
44
|
+
return false if @product_form_details.select { |fd| fd.code == "E201" }.length > 0
|
45
|
+
end
|
46
|
+
|
47
|
+
# raw part file description string without HTML
|
48
|
+
# @return [String]
|
49
|
+
def raw_file_description
|
50
|
+
if @product_form_description
|
51
|
+
Helper.strip_html(@product_form_description).gsub(/\s+/, " ").strip
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Protection type string (None, Watermarking, DRM, AdobeDRM)
|
56
|
+
# @return [String]
|
57
|
+
def protection_type
|
58
|
+
if product
|
59
|
+
product.protection_type
|
60
|
+
else
|
61
|
+
if part_of
|
62
|
+
part_of.protection_type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# List of protections type string (None, Watermarking, DRM, AdobeDRM)
|
68
|
+
# @return [Array<String>]
|
69
|
+
def protections
|
70
|
+
if product
|
71
|
+
product.protections
|
72
|
+
else
|
73
|
+
if part_of
|
74
|
+
part_of.protections
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# digital file filesize in bytes
|
80
|
+
# @return [Integer]
|
81
|
+
def filesize
|
82
|
+
if product
|
83
|
+
product.filesize
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @!endgroup
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
module ONIX
|
2
|
+
# flattened supplies extractor
|
3
|
+
module ProductSuppliesExtractor
|
4
|
+
# class must define a product_supplies returning an Array of objects responding to :
|
5
|
+
# - availability_date (Date)
|
6
|
+
# - countries (country code Array)
|
7
|
+
|
8
|
+
# @!group High level
|
9
|
+
|
10
|
+
# flattened supplies with prices
|
11
|
+
#
|
12
|
+
# supplies is a hash symbol array in the form :
|
13
|
+
# [{:available=>bool,
|
14
|
+
# :availability_date=>date,
|
15
|
+
# :including_tax=>bool,
|
16
|
+
# :currency=>string,
|
17
|
+
# :territory=>string,
|
18
|
+
# :suppliers=>[Supplier,...],
|
19
|
+
# :prices=>[{:amount=>int,
|
20
|
+
# :from_date=>date,
|
21
|
+
# :until_date=>date,
|
22
|
+
# :tax=>{:amount=>int, :rate_percent=>float}}]}]
|
23
|
+
def supplies(keep_all_prices_dates = false)
|
24
|
+
supplies = []
|
25
|
+
|
26
|
+
# add territories if missing
|
27
|
+
if self.product_supplies
|
28
|
+
self.product_supplies.each do |ps|
|
29
|
+
ps.supply_details.each do |sd|
|
30
|
+
sd.prices.each do |p|
|
31
|
+
supply = {}
|
32
|
+
supply[:suppliers] = sd.suppliers
|
33
|
+
supply[:available] = sd.available?
|
34
|
+
supply[:availability_date] = sd.availability_date
|
35
|
+
|
36
|
+
unless supply[:availability_date]
|
37
|
+
if ps.availability_date
|
38
|
+
supply[:availability_date] = ps.market_publishing_detail.availability_date
|
39
|
+
end
|
40
|
+
end
|
41
|
+
supply[:price] = p.amount
|
42
|
+
supply[:qualifier] = p.qualifier.human if p.qualifier
|
43
|
+
supply[:including_tax] = p.including_tax?
|
44
|
+
if !p.territory or p.territory.countries.length == 0
|
45
|
+
supply[:territory] = []
|
46
|
+
supply[:territory] = ps.countries
|
47
|
+
|
48
|
+
if supply[:territory].length == 0
|
49
|
+
if @publishing_detail
|
50
|
+
supply[:territory] = self.countries_rights
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
supply[:territory] = p.territory.countries
|
55
|
+
end
|
56
|
+
supply[:from_date] = p.from_date
|
57
|
+
supply[:until_date] = p.until_date
|
58
|
+
supply[:currency] = p.currency
|
59
|
+
supply[:tax] = p.tax
|
60
|
+
|
61
|
+
unless supply[:availability_date]
|
62
|
+
if @publishing_detail
|
63
|
+
supply[:availability_date] = @publishing_detail.publication_date
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
supplies << supply
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
grouped_supplies = {}
|
74
|
+
supplies.each do |supply|
|
75
|
+
supply[:territory].each do |territory|
|
76
|
+
pr_key = "#{supply[:available]}_#{supply[:including_tax]}_#{supply[:currency]}_#{territory}"
|
77
|
+
grouped_supplies[pr_key] ||= []
|
78
|
+
grouped_supplies[pr_key] << supply
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
nb_suppliers = supplies.map { |s| s[:suppliers][0].name }.uniq.length
|
83
|
+
# render prices sequentially with dates
|
84
|
+
grouped_supplies.each do |ksup, supply|
|
85
|
+
if supply.length > 1
|
86
|
+
global_price = supply.select { |p| not p[:from_date] and not p[:until_date] }
|
87
|
+
global_price = global_price.first
|
88
|
+
|
89
|
+
if global_price
|
90
|
+
if nb_suppliers > 1
|
91
|
+
grouped_supplies[ksup] += self.prices_with_periods(supply, global_price)
|
92
|
+
else
|
93
|
+
grouped_supplies[ksup] = self.prices_with_periods(supply, global_price)
|
94
|
+
end
|
95
|
+
grouped_supplies[ksup].uniq!
|
96
|
+
else
|
97
|
+
# remove explicit from date
|
98
|
+
explicit_from = supply.select { |p| p[:from_date] and not supply.select { |sp| sp[:until_date] and sp[:until_date] <= p[:from_date] }.first }.first
|
99
|
+
if explicit_from
|
100
|
+
explicit_from[:from_date] = nil unless keep_all_prices_dates
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
supply.each do |s|
|
105
|
+
if s[:from_date] and s[:availability_date] and s[:from_date] >= s[:availability_date]
|
106
|
+
s[:availability_date] = s[:from_date]
|
107
|
+
end
|
108
|
+
s[:from_date] = nil unless keep_all_prices_dates
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# merge by territories
|
114
|
+
grouped_territories_supplies = {}
|
115
|
+
grouped_supplies.each do |ksup, supply|
|
116
|
+
fsupply = supply.first
|
117
|
+
pr_key = "#{fsupply[:available]}_#{fsupply[:including_tax]}_#{fsupply[:currency]}"
|
118
|
+
supply.each do |s|
|
119
|
+
pr_key += "_#{s[:price]}_#{s[:from_date]}_#{s[:until_date]}"
|
120
|
+
end
|
121
|
+
grouped_territories_supplies[pr_key] ||= []
|
122
|
+
grouped_territories_supplies[pr_key] << supply
|
123
|
+
end
|
124
|
+
|
125
|
+
supplies = []
|
126
|
+
|
127
|
+
grouped_territories_supplies.each do |ksup, supply|
|
128
|
+
fsupply = supply.first.first
|
129
|
+
supplies << {:including_tax => fsupply[:including_tax], :currency => fsupply[:currency],
|
130
|
+
:territory => supply.map { |fs| fs.map { |s| s[:territory] } }.flatten.uniq,
|
131
|
+
:available => fsupply[:available],
|
132
|
+
:availability_date => fsupply[:availability_date],
|
133
|
+
:suppliers => fsupply[:suppliers],
|
134
|
+
:prices => supply.first.map { |s|
|
135
|
+
|
136
|
+
s[:amount] = s[:price]
|
137
|
+
s.delete(:price)
|
138
|
+
s.delete(:available)
|
139
|
+
s.delete(:currency)
|
140
|
+
s.delete(:availability_date)
|
141
|
+
s.delete(:including_tax)
|
142
|
+
s.delete(:territory)
|
143
|
+
s
|
144
|
+
}}
|
145
|
+
end
|
146
|
+
|
147
|
+
supplies
|
148
|
+
end
|
149
|
+
|
150
|
+
# add missing periods when they can be guessed
|
151
|
+
def prices_with_periods(supplies, global_supply)
|
152
|
+
complete_supplies = supplies.select { |supply| supply[:from_date] && supply[:until_date] }.sort_by { |supply| supply[:from_date] }
|
153
|
+
missing_start_period_supplies = supplies.select { |supply| supply[:from_date] && !supply[:until_date] }.sort_by { |supply| supply[:from_date] }
|
154
|
+
missing_end_period_supplies = supplies.select { |supply| !supply[:from_date] && supply[:until_date] }.sort_by { |supply| supply[:until_date] }
|
155
|
+
|
156
|
+
return [global_supply] if [complete_supplies, missing_start_period_supplies, missing_end_period_supplies].all? { |supply| supply.empty? }
|
157
|
+
|
158
|
+
return self.add_missing_periods(complete_supplies, global_supply) unless complete_supplies.empty?
|
159
|
+
|
160
|
+
without_start = missing_start_period_supplies.length == 1 && complete_supplies.empty? && missing_end_period_supplies.empty?
|
161
|
+
without_end = missing_end_period_supplies.length == 1 && complete_supplies.empty? && missing_start_period_supplies.empty?
|
162
|
+
|
163
|
+
return self.add_starting_period(missing_start_period_supplies.first, global_supply) if without_start
|
164
|
+
return self.add_ending_period(missing_end_period_supplies.first, global_supply) if without_end
|
165
|
+
|
166
|
+
[global_supply]
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_missing_periods(supplies, global_supply)
|
170
|
+
new_supplies = []
|
171
|
+
|
172
|
+
supplies.each.with_index do |supply, index|
|
173
|
+
new_supplies << global_supply.dup.tap { |start_sup| start_sup[:until_date] = supply[:from_date] - 1 } if index == 0
|
174
|
+
|
175
|
+
if index > 0 && index != supplies.length
|
176
|
+
new_supplies << global_supply.dup.tap do |missing_supply|
|
177
|
+
missing_supply[:from_date] = supplies[index - 1][:until_date] + 1
|
178
|
+
missing_supply[:until_date] = supply[:from_date] - 1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
new_supplies << supply
|
183
|
+
|
184
|
+
new_supplies << global_supply.dup.tap { |end_sup| end_sup[:from_date] = supply[:until_date] + 1 } if index == supplies.length - 1
|
185
|
+
end
|
186
|
+
|
187
|
+
new_supplies
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_starting_period(supply, global_supply)
|
191
|
+
missing_supply = global_supply.dup
|
192
|
+
missing_supply[:until_date] = supply[:from_date] - 1
|
193
|
+
|
194
|
+
[missing_supply, supply]
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_ending_period(supply, global_supply)
|
198
|
+
missing_supply = global_supply.dup
|
199
|
+
missing_supply[:from_date] = supply[:until_date] + 1
|
200
|
+
|
201
|
+
[supply, missing_supply]
|
202
|
+
end
|
203
|
+
|
204
|
+
# flattened supplies only including taxes
|
205
|
+
def supplies_including_tax
|
206
|
+
self.supplies.select { |p| p[:including_tax] }
|
207
|
+
end
|
208
|
+
|
209
|
+
# flattened supplies only excluding taxes
|
210
|
+
def supplies_excluding_tax
|
211
|
+
self.supplies.select { |p| not p[:including_tax] }
|
212
|
+
end
|
213
|
+
|
214
|
+
# flattened supplies with default tax (excluding tax for US and CA, including otherwise)
|
215
|
+
def supplies_with_default_tax
|
216
|
+
self.supplies_including_tax + self.supplies_excluding_tax.select { |s| ["CAD", "USD"].include?(s[:currency]) }
|
217
|
+
end
|
218
|
+
|
219
|
+
# flattened supplies for country
|
220
|
+
def supplies_for_country(country, currency = nil)
|
221
|
+
country_supplies = self.supplies
|
222
|
+
if currency
|
223
|
+
country_supplies = country_supplies.select { |s| s[:currency] == currency }
|
224
|
+
end
|
225
|
+
country_supplies.select { |s|
|
226
|
+
if s[:territory].include?(country)
|
227
|
+
true
|
228
|
+
else
|
229
|
+
false
|
230
|
+
end
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
# price amount for given +currency+ and country at time
|
235
|
+
def at_time_price_amount_for(time, currency, country = nil)
|
236
|
+
sups = self.supplies_with_default_tax.select { |p| p[:currency] == currency }
|
237
|
+
if country
|
238
|
+
sups = sups.select { |p| p[:territory].include?(country) }
|
239
|
+
end
|
240
|
+
if sups.length > 0
|
241
|
+
# exclusive
|
242
|
+
sup = sups.first[:prices].select { |p|
|
243
|
+
(!p[:from_date] or p[:from_date].to_date <= time.to_date) and
|
244
|
+
(!p[:until_date] or p[:until_date].to_date > time.to_date)
|
245
|
+
}.first
|
246
|
+
|
247
|
+
if sup
|
248
|
+
sup[:amount]
|
249
|
+
else
|
250
|
+
# or inclusive
|
251
|
+
sup = sups.first[:prices].select { |p|
|
252
|
+
(!p[:from_date] or p[:from_date].to_date <= time.to_date) and
|
253
|
+
(!p[:until_date] or p[:until_date].to_date >= time.to_date)
|
254
|
+
}.first
|
255
|
+
|
256
|
+
if sup
|
257
|
+
sup[:amount]
|
258
|
+
else
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
else
|
264
|
+
nil
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# current price amount for given +currency+ and country
|
269
|
+
def current_price_amount_for(currency, country = nil)
|
270
|
+
at_time_price_amount_for(Time.now, currency, country)
|
271
|
+
end
|
272
|
+
|
273
|
+
# @!endgroup
|
274
|
+
end
|
275
|
+
end
|
data/lib/onix/product_supply.rb
CHANGED
@@ -1,85 +1,44 @@
|
|
1
|
-
require 'onix/price'
|
2
1
|
require 'onix/date'
|
2
|
+
require 'onix/market'
|
3
|
+
require 'onix/market_publishing_detail'
|
4
|
+
require 'onix/supply_detail'
|
3
5
|
|
4
6
|
module ONIX
|
5
|
-
|
6
|
-
class Market < SubsetDSL
|
7
|
-
element "Territory", :subset
|
8
|
-
end
|
9
|
-
|
10
|
-
class MarketPublishingDetail < SubsetDSL
|
11
|
-
elements "PublisherRepresentative", :subset, {:klass=>"Agent"}
|
12
|
-
element "MarketPublishingStatus", :subset
|
13
|
-
elements "MarketDate", :subset
|
14
|
-
|
15
|
-
def availability_date
|
16
|
-
av=@market_dates.availability.first
|
17
|
-
if av
|
18
|
-
av.date
|
19
|
-
else
|
20
|
-
nil
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class SupplyDetail < SubsetDSL
|
26
|
-
elements "Supplier", :subset
|
27
|
-
element "ProductAvailability", :subset
|
28
|
-
elements "SupplyDate", :subset
|
29
|
-
elements "Price", :subset
|
30
|
-
element "UnpricedItemType", :subset
|
31
|
-
|
32
|
-
def availability
|
33
|
-
@product_availability
|
34
|
-
end
|
35
|
-
|
36
|
-
def distributors
|
37
|
-
@suppliers.select{|s| s.role.human=~/Distributor/}.uniq
|
38
|
-
end
|
39
|
-
|
40
|
-
def available?
|
41
|
-
["Available","NotYetAvailable","InStock","ToOrder","Pod"].include?(@product_availability.human)
|
42
|
-
end
|
43
|
-
|
44
|
-
def sold_separately?
|
45
|
-
@product_availability.human!="NotSoldSeparately"
|
46
|
-
end
|
47
|
-
|
48
|
-
def availability_date
|
49
|
-
av=@supply_dates.availability.first
|
50
|
-
if av
|
51
|
-
av.date
|
52
|
-
else
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
7
|
class ProductSupply < SubsetDSL
|
59
8
|
elements "Market", :subset
|
60
9
|
element "MarketPublishingDetail", :subset
|
61
10
|
elements "SupplyDetail", :subset
|
62
11
|
|
12
|
+
# availability date from market
|
13
|
+
# @return [Date]
|
63
14
|
def availability_date
|
64
15
|
if @market_publishing_detail
|
65
16
|
@market_publishing_detail.availability_date
|
66
17
|
end
|
67
18
|
end
|
68
19
|
|
20
|
+
# countries string array
|
21
|
+
# @return [Array<String>]
|
69
22
|
def countries
|
70
|
-
@markets.map{|
|
23
|
+
@markets.map { |market| market.territory.countries }.flatten.uniq
|
71
24
|
end
|
72
25
|
|
26
|
+
# distributors string array
|
27
|
+
# @return [Array<String>]
|
73
28
|
def distributors
|
74
|
-
@supply_details.map{|
|
29
|
+
@supply_details.map { |supply_detail| supply_detail.distributors }.flatten.uniq { |distributor| distributor.name }
|
75
30
|
end
|
76
31
|
|
32
|
+
# available supply details
|
33
|
+
# @return [Array<SupplyDetail>]
|
77
34
|
def available_supply_details
|
78
|
-
@supply_details.select{|
|
35
|
+
@supply_details.select { |supply_detail| supply_detail.available? }
|
79
36
|
end
|
80
37
|
|
38
|
+
# unavailable supply details
|
39
|
+
# @return [Array<SupplyDetail>]
|
81
40
|
def unavailable_supply_details
|
82
|
-
@supply_details.delete_if{|
|
41
|
+
@supply_details.delete_if { |supply_detail| supply_detail.available? }
|
83
42
|
end
|
84
43
|
|
85
44
|
def available?
|