onix 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +30 -0
- data/README.rdoc +30 -0
- data/TODO +14 -0
- data/lib/onix.rb +57 -0
- data/lib/onix/addressee_identifier.rb +9 -0
- data/lib/onix/apa_product.rb +534 -0
- data/lib/onix/contributor.rb +20 -0
- data/lib/onix/date_type.rb +54 -0
- data/lib/onix/etext_type.rb +44 -0
- data/lib/onix/header.rb +31 -0
- data/lib/onix/imprint.rb +10 -0
- data/lib/onix/integer_type.rb +43 -0
- data/lib/onix/lists/product_availability.rb +30 -0
- data/lib/onix/lists/product_form.rb +97 -0
- data/lib/onix/media_file.rb +11 -0
- data/lib/onix/other_text.rb +12 -0
- data/lib/onix/price.rb +16 -0
- data/lib/onix/product.rb +30 -0
- data/lib/onix/product_identifier.rb +8 -0
- data/lib/onix/publisher.rb +11 -0
- data/lib/onix/reader.rb +124 -0
- data/lib/onix/sales_restriction.rb +7 -0
- data/lib/onix/sender_identifier.rb +9 -0
- data/lib/onix/simple_product.rb +41 -0
- data/lib/onix/stock.rb +10 -0
- data/lib/onix/subject.rb +11 -0
- data/lib/onix/supply_detail.rb +21 -0
- data/lib/onix/title.rb +10 -0
- data/lib/onix/two_digit_type.rb +57 -0
- data/lib/onix/website.rb +9 -0
- data/lib/onix/writer.rb +87 -0
- data/spec/header_spec.rb +111 -0
- data/spec/product_spec.rb +75 -0
- data/spec/reader_spec.rb +64 -0
- data/spec/writer_spec.rb +81 -0
- metadata +117 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Contributor
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :sequence_number, :from => "SequenceNumber"
|
6
|
+
xml_accessor :contributor_role, :from => "ContributorRole"
|
7
|
+
xml_accessor :language_code, :from => "LanguageCode"
|
8
|
+
xml_accessor :sequence_number_within_role, :from => "SequenceNumberWithinRole"
|
9
|
+
xml_accessor :person_name, :etext, :from => "PersonName"
|
10
|
+
xml_accessor :person_name_inverted, :etext, :from => "PersonNameInverted"
|
11
|
+
xml_accessor :titles_before_name, :etext, :from => "TitlesBeforeName"
|
12
|
+
xml_accessor :names_before_key, :etext, :from => "NamesBeforeKey"
|
13
|
+
xml_accessor :prefix_to_key, :etext, :from => "PrefixToKey"
|
14
|
+
xml_accessor :key_names, :etext, :from => "KeyNames"
|
15
|
+
xml_accessor :names_after_key, :etext, :from => "NamesArterKey"
|
16
|
+
xml_accessor :suffix_to_key, :etext, :from => "SuffixToKey"
|
17
|
+
xml_accessor :letters_after_names, :etext, :from => "LettersAfterNames"
|
18
|
+
xml_accessor :titles_after_names, :etext, :from => "TitlesAfterNames"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module ONIX
|
4
|
+
|
5
|
+
# Internal class representing XML content date binding
|
6
|
+
#
|
7
|
+
# In context:
|
8
|
+
# <element attribute="XMLAttributeRef">
|
9
|
+
# XMLYYYYMMDDRef
|
10
|
+
# </element>
|
11
|
+
class DateType < ROXML::XMLRef # ::nodoc::
|
12
|
+
attr_reader :cdata, :content
|
13
|
+
|
14
|
+
def initialize(accessor, args, &block)
|
15
|
+
super(accessor, args, &block)
|
16
|
+
@content = args.content?
|
17
|
+
@cdata = args.cdata?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Updates the text in the given _xml_ block to
|
21
|
+
# the _value_ provided.
|
22
|
+
def update_xml(xml, value)
|
23
|
+
parent = wrap(xml)
|
24
|
+
add(parent.child_add(LibXML::XML::Node.new_element(name)), value.strftime("%Y%m%d"))
|
25
|
+
xml
|
26
|
+
end
|
27
|
+
|
28
|
+
def value(xml)
|
29
|
+
begin
|
30
|
+
if content
|
31
|
+
value = Date.parse(xml.content.strip)
|
32
|
+
else
|
33
|
+
child = xml.search(name).first
|
34
|
+
value = Date.parse(child.content.strip) if child
|
35
|
+
end
|
36
|
+
rescue ArgumentError
|
37
|
+
value = nil
|
38
|
+
end
|
39
|
+
block ? block.call(value) : value
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add(dest, value)
|
45
|
+
if cdata
|
46
|
+
dest.child_add(LibXML::XML::Node.new_cdata(value.to_utf))
|
47
|
+
else
|
48
|
+
dest.content = value.to_utf
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
ROXML::TypeRegistry.register(:yyyymmdd, ONIX::DateType)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ONIX
|
2
|
+
|
3
|
+
class ETextType < ROXML::XMLRef # ::nodoc::
|
4
|
+
attr_reader :cdata, :content
|
5
|
+
|
6
|
+
def initialize(accessor, args, &block)
|
7
|
+
super(accessor, args, &block)
|
8
|
+
@content = args.content?
|
9
|
+
@cdata = args.cdata?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Updates the text in the given _xml_ block to
|
13
|
+
# the _value_ provided.
|
14
|
+
def update_xml(xml, value)
|
15
|
+
value = value.to_s.gsub("&","&").gsub("<","<").gsub(">",">")
|
16
|
+
parent = wrap(xml)
|
17
|
+
add(parent.child_add(LibXML::XML::Node.new_element(name)), value)
|
18
|
+
xml
|
19
|
+
end
|
20
|
+
|
21
|
+
def value(xml)
|
22
|
+
if content
|
23
|
+
value = xml.content
|
24
|
+
else
|
25
|
+
child = xml.search(name).first
|
26
|
+
value = child.content if child
|
27
|
+
end
|
28
|
+
value = value.gsub("&","&").gsub("<","<").gsub(">",">") if value
|
29
|
+
block ? block.call(value) : value
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def add(dest, value)
|
35
|
+
if cdata
|
36
|
+
dest.child_add(LibXML::XML::Node.new_cdata(value.to_utf))
|
37
|
+
else
|
38
|
+
dest.content = value.to_utf
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ROXML::TypeRegistry.register(:etext, ONIX::ETextType)
|
data/lib/onix/header.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Header
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_name "Header"
|
6
|
+
|
7
|
+
xml_accessor :from_person, :etext, :from => "FromPerson"
|
8
|
+
xml_accessor :from_ean_number, :etext, :from => "FromEANNumber"
|
9
|
+
xml_accessor :from_san, :etext, :from => "FromSAN"
|
10
|
+
xml_accessor :sender_identifiers, [ONIX::SenderIdentifier], :from => "SenderIdentifier"
|
11
|
+
xml_accessor :from_company, :etext, :from => "FromCompany"
|
12
|
+
xml_accessor :from_email, :etext, :from => "FromEmail"
|
13
|
+
xml_accessor :to_ean_number, :etext, :from => "ToEANNumber"
|
14
|
+
xml_accessor :to_san, :etext, :from => "ToSAN"
|
15
|
+
xml_accessor :addressee_identifier, [ONIX::AddresseeIdentifier], :from => "AddresseeIdentifier"
|
16
|
+
xml_accessor :to_company, :etext, :from => "ToCompany"
|
17
|
+
xml_accessor :to_person, :etext, :from => "ToPerson"
|
18
|
+
xml_accessor :message_number, :etext, :from => "MessageNumber"
|
19
|
+
xml_accessor :message_repeat, :integer, :from => "MessageRepeat"
|
20
|
+
xml_accessor :sent_date, :yyyymmdd, :from => "SentDate"
|
21
|
+
xml_accessor :message_note, :etext, :from => "MessageNote"
|
22
|
+
|
23
|
+
# defaults
|
24
|
+
xml_accessor :default_language_of_text, :from => "DefaultLanguageOfText"
|
25
|
+
xml_accessor :default_price_type_code, :integer, :from => "DefaultPriceTypeCode"
|
26
|
+
xml_accessor :default_currency_code, :from => "DefaultCurrencyCode"
|
27
|
+
xml_reader :default_linear_unit, :from => "DefaultLinearUnit" # deprecated
|
28
|
+
xml_reader :default_weight_unit, :from => "DefaultWeightUnit" # deprecated
|
29
|
+
xml_accessor :default_class_of_trade, :from => "DefaultClassOfTrade"
|
30
|
+
end
|
31
|
+
end
|
data/lib/onix/imprint.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Imprint
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :name_code_type, :twodigit, :from => "NameCodeType"
|
6
|
+
xml_accessor :name_code_type_name, :etext, :from => "NameCodeTypeName"
|
7
|
+
xml_accessor :name_code_value, :etext, :from => "NameCodeValue"
|
8
|
+
xml_accessor :imprint_name, :etext, :from => "ImprintName"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
module ONIX
|
3
|
+
|
4
|
+
class IntegerType < ROXML::XMLRef # ::nodoc::
|
5
|
+
attr_reader :cdata, :content
|
6
|
+
|
7
|
+
def initialize(accessor, args, &block)
|
8
|
+
super(accessor, args, &block)
|
9
|
+
@content = args.content?
|
10
|
+
@cdata = args.cdata?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Updates the text in the given _xml_ block to
|
14
|
+
# the _value_ provided.
|
15
|
+
def update_xml(xml, value)
|
16
|
+
parent = wrap(xml)
|
17
|
+
add(parent.child_add(LibXML::XML::Node.new_element(name)), value)
|
18
|
+
xml
|
19
|
+
end
|
20
|
+
|
21
|
+
def value(xml)
|
22
|
+
if content
|
23
|
+
value = xml.content.to_i
|
24
|
+
else
|
25
|
+
child = xml.search(name).first
|
26
|
+
value = child.content.to_i if child
|
27
|
+
end
|
28
|
+
block ? block.call(value) : value
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def add(dest, value)
|
34
|
+
if cdata
|
35
|
+
dest.child_add(LibXML::XML::Node.new_cdata(value.to_utf))
|
36
|
+
else
|
37
|
+
dest.content = value.to_utf
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ROXML::TypeRegistry.register(:integer, ONIX::IntegerType)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ONIX
|
2
|
+
module Lists
|
3
|
+
# Code list 65
|
4
|
+
PRODUCT_AVAILABILITY = {
|
5
|
+
1 => "Cancelled",
|
6
|
+
10 => "Not Yet Available",
|
7
|
+
11 => "Awaiting Stock",
|
8
|
+
12 => "Not Yet Available, will be POD",
|
9
|
+
20 => "Available",
|
10
|
+
21 => "In Stock",
|
11
|
+
22 => "To Order",
|
12
|
+
23 => "Manufactured on Demand",
|
13
|
+
30 => "Temporarily Unavailable",
|
14
|
+
31 => "Out Of Stock",
|
15
|
+
32 => "Reprinting",
|
16
|
+
33 => "Awaiting Reissue",
|
17
|
+
40 => "Not Available",
|
18
|
+
41 => "Replaced By New Product",
|
19
|
+
42 => "Other Format Available",
|
20
|
+
43 => "No Longer Supplier By Us",
|
21
|
+
44 => "Apply Direct",
|
22
|
+
45 => "Not Sold Seperately",
|
23
|
+
46 => "Withdrawn From Sale",
|
24
|
+
47 => "Remaindered",
|
25
|
+
48 => "Out Of Print, Replaced By POD with Diff ISBN",
|
26
|
+
99 => "Uncertain"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module ONIX
|
2
|
+
module Lists
|
3
|
+
# code list 7
|
4
|
+
PRODUCT_FORM = {
|
5
|
+
"00" => "Undefined",
|
6
|
+
"AA" => "Audio",
|
7
|
+
"AB" => "Audio - Cassette",
|
8
|
+
"AC" => "Audio - CD",
|
9
|
+
"AD" => "Audio - DAT",
|
10
|
+
"AE" => "Audio - Disk",
|
11
|
+
"AF" => "Audio - Tape",
|
12
|
+
"AZ" => "Audio - Other",
|
13
|
+
"BA" => "Book",
|
14
|
+
"BB" => "Book - Hardback",
|
15
|
+
"BC" => "Book - Paperback",
|
16
|
+
"BD" => "Book - Loose-leaf",
|
17
|
+
"BE" => "Book - Spiral Bound",
|
18
|
+
"BF" => "Book - Pamphlet",
|
19
|
+
"BG" => "Book - Leather / Fine Binding",
|
20
|
+
"BH" => "Book - Board",
|
21
|
+
"BI" => "Book - Rag",
|
22
|
+
"BJ" => "Book - Bath",
|
23
|
+
"BZ" => "Book - Other",
|
24
|
+
"CA" => "Cartographic - Sheet Map",
|
25
|
+
"CB" => "Cartographic - Sheet Map Folded",
|
26
|
+
"CC" => "Cartographic - Sheet Map Flat",
|
27
|
+
"CD" => "Cartographic - Sheet Map Rolled",
|
28
|
+
"CE" => "Cartographic - Globe",
|
29
|
+
"CZ" => "Cartographic - Other",
|
30
|
+
"DA" => "Digital",
|
31
|
+
"DB" => "Digital - CDROM",
|
32
|
+
"DC" => "Digital - CD-Interactive",
|
33
|
+
"DD" => "Digital - DVD",
|
34
|
+
"DE" => "Digital - Game Cartridge",
|
35
|
+
"DF" => "Digital - Diskette",
|
36
|
+
"DG" => "Digital - Electronic Book Text",
|
37
|
+
"DH" => "Digital - Online File",
|
38
|
+
"DZ" => "Digital - Other",
|
39
|
+
"FA" => "Film or Transparency",
|
40
|
+
"FB" => "Film",
|
41
|
+
"FC" => "Film - Slides",
|
42
|
+
"FD" => "Film - OHP Transparencies",
|
43
|
+
"FZ" => "Film - Other ",
|
44
|
+
"MA" => "Microform",
|
45
|
+
"MB" => "Microform - Microfiche",
|
46
|
+
"MC" => "Microform - Microfilm",
|
47
|
+
"MZ" => "Microform - Other",
|
48
|
+
"PA" => "Print - Misc",
|
49
|
+
"PB" => "Print - Address Book",
|
50
|
+
"PC" => "Print - Calendar",
|
51
|
+
"PD" => "Print - Cards",
|
52
|
+
"PE" => "Print - Copymasters",
|
53
|
+
"PF" => "Print - Diary",
|
54
|
+
"PG" => "Print - Frieze",
|
55
|
+
"PH" => "Print - Kit",
|
56
|
+
"PI" => "Print - Sheet Music",
|
57
|
+
"PJ" => "Print - Postcard Book or Pack",
|
58
|
+
"PK" => "Print - Poster",
|
59
|
+
"PL" => "Print - Record Book",
|
60
|
+
"PM" => "Print - Wallet",
|
61
|
+
"PN" => "Print - Pictures or Photographs",
|
62
|
+
"PO" => "Print - Wallchart",
|
63
|
+
"PZ" => "Print - Other",
|
64
|
+
"VA" => "Video",
|
65
|
+
"VB" => "Video – VHS PAL",
|
66
|
+
"VC" => "Video – VHS NTSC",
|
67
|
+
"VD" => "Video – Betamax PAL",
|
68
|
+
"VE" => "Video – Betamax NTSC",
|
69
|
+
"VF" => "Video – disk",
|
70
|
+
"VG" => "Video – VHS SECAM",
|
71
|
+
"VH" => "Video – Betamax SECAM",
|
72
|
+
"VZ" => "Video - Other Format",
|
73
|
+
"WW" => "Multiple - Mixed Media Product",
|
74
|
+
"WX" => "Misc - Quantity Pack",
|
75
|
+
"XA" => "Misc - Trade-only Material",
|
76
|
+
"XB" => "Misc - Dumpbim - Empty",
|
77
|
+
"XC" => "Misc - Dumpbin - Filled",
|
78
|
+
"XD" => "Misc - Counterpack - Empty",
|
79
|
+
"XE" => "Misc - Counterpack - Filled",
|
80
|
+
"XF" => "Misc - Poster",
|
81
|
+
"XG" => "Misc - Shelf Strip",
|
82
|
+
"XH" => "Misc - Window Piece",
|
83
|
+
"XI" => "Misc - Streamer",
|
84
|
+
"XJ" => "Misc - Spinner",
|
85
|
+
"XK" => "Misc - Large Book Display",
|
86
|
+
"XL" => "Misc - Shrink Wrapped Pack",
|
87
|
+
"XZ" => "Misc - Other Point of Sale",
|
88
|
+
"ZA" => "General Merchandise",
|
89
|
+
"ZB" => "Misc - Doll",
|
90
|
+
"ZC" => "Misc - Soft Toy",
|
91
|
+
"ZD" => "Misc - Toy",
|
92
|
+
"ZE" => "Misc - Game",
|
93
|
+
"ZF" => "Misc - T-shirt",
|
94
|
+
"ZZ" => "Misc - Other Merchandise"
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ONIX
|
2
|
+
class MediaFile
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :media_file_type_code, :twodigit, :from => "MediaFileTypeCode"
|
6
|
+
xml_accessor :media_file_format_code, :twodigit, :from => "MediaFileFormatCode"
|
7
|
+
xml_accessor :image_resolution, :from => "ImageResolution"
|
8
|
+
xml_accessor :media_file_link_type_code, :twodigit, :from => "MediaFileLinkTypeCode"
|
9
|
+
xml_accessor :media_file_link, :from => "MediaFileLink"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ONIX
|
2
|
+
class OtherText
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :text_type_code, :twodigit, :from => "TextTypeCode"
|
6
|
+
xml_accessor :text_format, :etext, :from => "TextFormat"
|
7
|
+
xml_accessor :text, :etext, :from => "Text"
|
8
|
+
xml_accessor :text_link_type, :twodigit, :from => "TextLinkType"
|
9
|
+
xml_accessor :text_link, :from => "TextLink"
|
10
|
+
xml_accessor :text_author, :etext, :from => "TextAuthor"
|
11
|
+
end
|
12
|
+
end
|
data/lib/onix/price.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Price
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :price_type_code, :twodigit, :from => "PriceTypeCode"
|
6
|
+
xml_accessor :price_type_qualifier, :from => "PriceTypeQualifier"
|
7
|
+
xml_accessor :price_type_description, :from => "PriceTypeDescription"
|
8
|
+
xml_accessor :price_per, :from => "PricePer"
|
9
|
+
xml_accessor :minimum_order_qty, :from => "MinimumOrderQuantity"
|
10
|
+
xml_accessor :class_of_trade, :from => "ClassOfTrade"
|
11
|
+
xml_accessor :bic_discount_group_code, :from => "BICDiscountGroupCode"
|
12
|
+
xml_accessor :price_status, :from => "PriceStatus"
|
13
|
+
xml_accessor :price_amount, :from => "PriceAmount"
|
14
|
+
xml_accessor :currency_code, :from => "CurrencyCode"
|
15
|
+
end
|
16
|
+
end
|
data/lib/onix/product.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Product
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_name "Product"
|
6
|
+
|
7
|
+
xml_accessor :record_reference, :from => "RecordReference"
|
8
|
+
xml_accessor :notification_type, :twodigit, :from => "NotificationType"
|
9
|
+
xml_accessor :product_identifiers, [ONIX::ProductIdentifier], :from => "ProductIdentifier"
|
10
|
+
xml_accessor :product_form, :from => "ProductForm"
|
11
|
+
xml_accessor :series, :from => "Series"
|
12
|
+
xml_accessor :edition_number, :integer, :from => "EditionNumber"
|
13
|
+
xml_accessor :titles, [ONIX::Title], :from => "Title"
|
14
|
+
xml_accessor :websites, [ONIX::Website], :from => "Website"
|
15
|
+
xml_accessor :contributors, [ONIX::Contributor], :from => "Contributor"
|
16
|
+
xml_accessor :number_of_pages, :integer, :from => "NumberOfPages"
|
17
|
+
xml_accessor :bic_main_subject, :from => "BICMainSubject"
|
18
|
+
xml_accessor :subjects, [ONIX::Subject], :from => "Subject"
|
19
|
+
xml_accessor :text, [ONIX::OtherText], :from => "OtherText"
|
20
|
+
xml_accessor :media_files, [ONIX::MediaFile], :from => "MediaFile"
|
21
|
+
xml_accessor :imprints, [ONIX::Imprint], :from => "Imprint"
|
22
|
+
xml_accessor :publishers, [ONIX::Publisher], :from => "Publisher"
|
23
|
+
xml_accessor :publishing_status, :twodigit, :from => "PublishingStatus"
|
24
|
+
xml_accessor :publication_date, :yyyymmdd, :from => "PublicationDate"
|
25
|
+
xml_accessor :year_first_published, :integer, :from => "YearFirstPublished"
|
26
|
+
xml_accessor :sales_restrictions, [ONIX::SalesRestriction], :from => "SalesRestriction"
|
27
|
+
xml_accessor :supply_details, [ONIX::SupplyDetail], :from => "SupplyDetail"
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ONIX
|
2
|
+
class Publisher
|
3
|
+
include ROXML
|
4
|
+
|
5
|
+
xml_accessor :publishing_role, :twodigit, :from => "PublishingRole"
|
6
|
+
xml_accessor :name_code_type, :twodigit, :from => "NameCodeType"
|
7
|
+
xml_accessor :name_code_type_name, :etext, :from => "NameCodeTypeName"
|
8
|
+
xml_accessor :name_code_type_value, :etext, :from => "NameCodeTypeValue"
|
9
|
+
xml_accessor :publisher_name, :etext, :from => "PublisherName"
|
10
|
+
end
|
11
|
+
end
|
data/lib/onix/reader.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module ONIX
|
6
|
+
|
7
|
+
# This is the primary class for reading data from an ONIX file, and there's
|
8
|
+
# really not much to it
|
9
|
+
#
|
10
|
+
# Each file should contain a single header, and 1 or more products:
|
11
|
+
#
|
12
|
+
# reader = ONIX::Reader.new("somefile.xml")
|
13
|
+
#
|
14
|
+
# puts reader.header.inspect
|
15
|
+
#
|
16
|
+
# reader.each do |product|
|
17
|
+
# puts product.inspect
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The header will be returned as an ONIX::Header object, and the product will
|
21
|
+
# be an ONIX::Product.
|
22
|
+
#
|
23
|
+
# The ONIX::Product class can be a bit of a hassle to work with, as data can be
|
24
|
+
# nested in it fairly deeply. To wrap all the products returned by the reader
|
25
|
+
# in a shim that provides simple accessor access to common attributes, pass the
|
26
|
+
# shim class as a second argument
|
27
|
+
#
|
28
|
+
# reader = ONIX::Reader.new("somefile.xml", ONIX::APAProduct)
|
29
|
+
#
|
30
|
+
# puts reader.header.inspect
|
31
|
+
#
|
32
|
+
# reader.each do |product|
|
33
|
+
# puts product.inspect
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# APAProduct stands for Australian Publishers Association and provides simple
|
37
|
+
# access to the ONIX attributes that are commonly used in the Australian market.
|
38
|
+
#
|
39
|
+
# As well as accessing the file header, there are handful of other read only
|
40
|
+
# attributes that might be useful
|
41
|
+
#
|
42
|
+
# reader = ONIX::Reader.new("somefile.xml", ONIX::APAProduct)
|
43
|
+
#
|
44
|
+
# puts reader.version
|
45
|
+
# puts reader.xml_lang
|
46
|
+
# puts reader.xml_version
|
47
|
+
# puts reader.encoding
|
48
|
+
#
|
49
|
+
# The version attribute is particuarly useful. There are multiple revisions of the
|
50
|
+
# ONIX spec, and you may need to handle the file differently based on what
|
51
|
+
# version it is.
|
52
|
+
#
|
53
|
+
class Reader
|
54
|
+
|
55
|
+
attr_reader :header, :version, :xml_lang, :xml_version, :encoding
|
56
|
+
|
57
|
+
# Create a new ONIX::Reader object
|
58
|
+
#
|
59
|
+
def initialize(input, product_klass = ::ONIX::Product)
|
60
|
+
if input.kind_of? String
|
61
|
+
@reader = LibXML::XML::Reader.file(input)
|
62
|
+
elsif input.kind_of?(IO)
|
63
|
+
@reader = LibXML::XML::Reader.io(input)
|
64
|
+
else
|
65
|
+
throw "Unable to read from path or file"
|
66
|
+
end
|
67
|
+
|
68
|
+
@product_klass = product_klass
|
69
|
+
|
70
|
+
# create a sized queue to store each product read from the file
|
71
|
+
@queue = SizedQueue.new(100)
|
72
|
+
|
73
|
+
# launch a reader thread to process the file and store each
|
74
|
+
# product in the queue
|
75
|
+
Thread.abort_on_exception = true
|
76
|
+
Thread.new { read_input }
|
77
|
+
|
78
|
+
# TODO: this is a seriously hacky way to ensure the reading thread
|
79
|
+
# has enough time to read our metadata and header objects from
|
80
|
+
# the input stream. I should be making the constructor block until
|
81
|
+
# it has actively confirmed the data has been read
|
82
|
+
sleep 1
|
83
|
+
end
|
84
|
+
|
85
|
+
# Iterate over all the products in an ONIX file
|
86
|
+
#
|
87
|
+
def each(&block)
|
88
|
+
obj = @queue.pop
|
89
|
+
while !obj.nil?
|
90
|
+
yield obj
|
91
|
+
obj = @queue.pop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Walk the ONIX file, and grab the bits we're interested in.
|
98
|
+
#
|
99
|
+
# High level attributes and the header are stored as attributes of the reader
|
100
|
+
# class. Products are placed in a queue, ready to be popped off when the
|
101
|
+
# user uses the each() method.
|
102
|
+
#
|
103
|
+
def read_input
|
104
|
+
while @reader.read == 1
|
105
|
+
@xml_lang = @reader.xml_lang if @xml_lang.nil?
|
106
|
+
@xml_version = @reader.xml_version.to_f if @xml_version.nil?
|
107
|
+
@encoding = @reader.encoding if @encoding.nil?
|
108
|
+
if @reader.node_type == 10
|
109
|
+
uri = @reader.expand.to_s
|
110
|
+
m, major, minor, rev = *uri.match(/.+(\d)\.(\d)\/(\d*).*/)
|
111
|
+
@version = [major.to_i, minor.to_i, rev.to_i]
|
112
|
+
elsif @reader.name == "Header" && @reader.node_type == 1
|
113
|
+
@header = ONIX::Header.parse(@reader.expand.to_s)
|
114
|
+
@reader.next_sibling
|
115
|
+
elsif @reader.name == "Product" && @reader.node_type == 1
|
116
|
+
node = @reader.expand
|
117
|
+
@queue.push @product_klass.parse(node.to_s)
|
118
|
+
@reader.next_sibling
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@queue.push nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|