onix 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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("&","&amp;").gsub("<","&lt;").gsub(">","&gt;")
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("&amp;","&").gsub("&lt;","<").gsub("&gt;",">") 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)
@@ -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
@@ -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
@@ -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,8 @@
1
+ module ONIX
2
+ class ProductIdentifier
3
+ include ROXML
4
+
5
+ xml_accessor :product_id_type, :twodigit, :from => "ProductIDType"
6
+ xml_accessor :id_value, :from => "IDValue"
7
+ end
8
+ 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
@@ -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