isbnify 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +5 -0
- data/isbnify.gemspec +30 -0
- data/lib/isbnify.rb +25 -0
- data/lib/isbnify/iisbna.rb +168 -0
- data/lib/isbnify/isbn.rb +106 -0
- data/lib/isbnify/version.rb +3 -0
- data/spec/isbnify_spec.rb +51 -0
- data/spec/lib/iisbna_spec.rb +139 -0
- data/spec/lib/isbn_spec.rb +116 -0
- data/spec/spec_helper.rb +32 -0
- data/vendor/lib/RangeMessage.xml +5542 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1ce182ccf75b38d868b634282cbcc0daf145f0f9
|
4
|
+
data.tar.gz: 2db0e7a91ea2e89e09aab45d52a4cf7bae1982f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b4a6877ee5abcfa908d07f7f8c86272d047ed47c00ee0900ded4ecdddbdfc548dd3df47049cad775b8f20dfbe2262fbf09e68fa9e93d04a461b3918d3354acc
|
7
|
+
data.tar.gz: fa04c7d41f0c2f92171582c1a30795fe5c16d5a7da94be3ac3f0624abef598f5e879adb9c75eabc540d13b0356a7f96e03ab9d35fea13d8b7c95c2241293aa38
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Christoph Seydel
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
isbnify
|
2
|
+
=======
|
3
|
+
|
4
|
+
hyphenate, verify and create ISBN13 numbers
|
5
|
+
|
6
|
+
### Functionality
|
7
|
+
* creates valid ISBN13 fake numbers, allready hyphinated
|
8
|
+
* check ISBN13 validity of a given number
|
9
|
+
* hyphinate valid ISBN13 number
|
10
|
+
|
11
|
+
|
12
|
+
### Implementation
|
13
|
+
Include Isbnify into your controller and use the helper actions or call object Isbnify::ISBN class actions.
|
14
|
+
|
15
|
+
|
16
|
+
### Functions
|
17
|
+
|
18
|
+
``create_isbn`` or ``Isbnify::ISBN.create_isbn``
|
19
|
+
|
20
|
+
Returns a random, yet valid fake ISBN allready hyphinated.
|
21
|
+
|
22
|
+
``hyphinate_isbn(String)`` or ``Isbnify::ISBN.hyphinate_isbn(String)``
|
23
|
+
|
24
|
+
Returns hyphinated ISBN or error message as String.
|
25
|
+
|
26
|
+
``valid_isbn?(String)`` or ``Isbnify::ISBN.valid_isbn?(String)``
|
27
|
+
|
28
|
+
Returns true or false, whether a given ISBN is valid or not. It can be passed as hyphinated ISBN or not.
|
29
|
+
|
30
|
+
|
31
|
+
### Useable method calls
|
32
|
+
|
33
|
+
```
|
34
|
+
class ApplicationController < ActionController
|
35
|
+
include Isbnify
|
36
|
+
|
37
|
+
def some_method
|
38
|
+
hyphinate_isbn("9783404166695") # returns "978-3-404-16669-5"
|
39
|
+
Isbnify::ISBN.hyphinate_isbn("9783404166695") # returns "978-3-404-16669-5"
|
40
|
+
end
|
41
|
+
|
42
|
+
def some_other_method
|
43
|
+
valid_isbn?("9783404166695") # returns true
|
44
|
+
Isbnify::ISBN.valid_isbn?("9783404166695") # returns true
|
45
|
+
end
|
46
|
+
|
47
|
+
def some_last_method
|
48
|
+
create_isbn # returns random well-formed isbn number
|
49
|
+
Isbnify::ISBN.create_isbn # returns random well-formed isbn number
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
### Ruby Versions
|
56
|
+
|
57
|
+
This gem was developed and tested with versions 1.9.3 and 2.0.0
|
58
|
+
|
59
|
+
### Contributing
|
60
|
+
|
61
|
+
1. Fork it
|
62
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
63
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
64
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
65
|
+
5. Create new Pull Request
|
66
|
+
|
67
|
+
### Copyright
|
68
|
+
|
69
|
+
Copyright (c) 2013 Christoph Seydel. See LICENSE for further details.
|
data/Rakefile
ADDED
data/isbnify.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'isbnify/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "isbnify"
|
8
|
+
spec.version = Isbnify::VERSION
|
9
|
+
spec.authors = ["cseydel"]
|
10
|
+
spec.email = ["christoph.seydel@me.com"]
|
11
|
+
spec.description = %q{This gem verifies, creates and hyphinates ISBN13 numbers}
|
12
|
+
spec.summary = %q{This gem verifies, creates and hyphinates ISBN13 numbers}
|
13
|
+
spec.homepage = "https://github.com/cseydel/isbnify"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "nokogiri"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", ">= 1.2.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.14.1"
|
26
|
+
spec.add_development_dependency "fakeweb", "~> 1.3.0"
|
27
|
+
spec.add_development_dependency "vcr", "~> 2.6.0"
|
28
|
+
spec.add_development_dependency "simplecov", "~> 0.7.1"
|
29
|
+
spec.add_development_dependency "simplecov-gem-adapter", "~> 1.0.1"
|
30
|
+
end
|
data/lib/isbnify.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "isbnify/version"
|
2
|
+
require "isbnify/isbn"
|
3
|
+
require "isbnify/iisbna"
|
4
|
+
|
5
|
+
module Isbnify
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def hyphinate_isbn(isbn_string = nil)
|
13
|
+
ISBN.hyphinate_isbn(isbn_string)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_isbn?(isbn_string = nil)
|
17
|
+
ISBN.valid_isbn?(isbn_string)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_isbn
|
21
|
+
ISBN.create_isbn
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Isbnify
|
5
|
+
class IISBNA
|
6
|
+
|
7
|
+
attr_accessor :country_group, :group_hash, :isbn, :prefix, :publisher, :range_hash
|
8
|
+
|
9
|
+
def initialize(isbn = "")
|
10
|
+
@isbn = isbn.to_s
|
11
|
+
@range_hash = xml_parse
|
12
|
+
end
|
13
|
+
|
14
|
+
def hyphinate
|
15
|
+
return "invalid international ISBN13 number" unless valid_prefix? && valid_group? && valid_publisher?
|
16
|
+
hyphinate_with_values
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_valid_isbn
|
20
|
+
random_group = range_hash[:"ISBNRangeMessage"][:"RegistrationGroups"][:"Group"].shuffle[0]
|
21
|
+
group = random_group[:Prefix]
|
22
|
+
rule = random_group[:Rules][:Rule].is_a?(Array) ? random_group[:Rules][:Rule].shuffle[0] : random_group[:Rules][:Rule]
|
23
|
+
r_publisher = random_publisher(rule)
|
24
|
+
create_isbn(group, r_publisher)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_isbn(group, r_publisher)
|
31
|
+
product_size = 12 - group.gsub("-","").size - r_publisher.size
|
32
|
+
product = "9999999999"[0..(product_size - 1)]
|
33
|
+
checksum = Isbnify::ISBN.create_isbn_checksum(group.gsub("-","") + r_publisher + product)
|
34
|
+
return "#{group}-#{r_publisher}-#{product}-#{checksum}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def group_prefix_included?(prefix, checkstring)
|
38
|
+
5.times do |n|
|
39
|
+
return true if prefix == "#{@prefix}-#{checkstring[0..(n-1)]}"
|
40
|
+
end
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def hyphinate_with_values
|
45
|
+
product_id = isbn.gsub(/#{@prefix}#{@country_group}#{@publisher}/, "")
|
46
|
+
return "#{@prefix}-#{@country_group}-#{@publisher}-#{product_id[0..-2]}-#{product_id[-1]}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def in_range?(rule, string)
|
50
|
+
truncated_range(rule[:Range], rule[:Length], string[valid_range(rule[:Length])])
|
51
|
+
end
|
52
|
+
|
53
|
+
def open_url_with_file_fallback
|
54
|
+
begin
|
55
|
+
xml_to_hash(open("http://www.isbn-international.org/agency?rmxml=1").read)
|
56
|
+
rescue
|
57
|
+
xml_to_hash(open(File.expand_path("../../../vendor/lib/RangeMessage.xml", __FILE__)).read)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def random_publisher(rule)
|
62
|
+
array = rule[:Range].split("-")
|
63
|
+
new_publisher = (rand(array[1].to_i - array[0].to_i) + array[0].to_i).to_s
|
64
|
+
return new_publisher[valid_range(rule[:Length])]
|
65
|
+
end
|
66
|
+
|
67
|
+
def truncated_range(range, length, needle)
|
68
|
+
array = range.split("-")
|
69
|
+
needle.to_i.between?((array[0][valid_range(length)]).to_i, (array[1][valid_range(length)]).to_i)
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_group?
|
73
|
+
sanitized_string = isbn.gsub(/^#{@prefix}/, "")
|
74
|
+
range_hash[:"ISBNRangeMessage"][:"RegistrationGroups"][:"Group"].each do |group_hash|
|
75
|
+
if group_prefix_included?(group_hash[:Prefix], sanitized_string)
|
76
|
+
@group_hash = group_hash
|
77
|
+
return @country_group = group_hash[:Prefix].gsub(/^#{@prefix}-/, "")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid_prefix?
|
84
|
+
range_hash[:"ISBNRangeMessage"][:"EAN.UCCPrefixes"][:"EAN.UCC"].each do |prefix_hash|
|
85
|
+
return @prefix = prefix_hash[:Prefix] if prefix_hash.has_value?(isbn[0..2])
|
86
|
+
end
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def valid_publisher?
|
91
|
+
sanitized_string = isbn.gsub(/#{@prefix}#{@country_group}/, "")
|
92
|
+
@group_hash[:Rules][:Rule].each do |rule|
|
93
|
+
return @publisher = sanitized_string[valid_range(rule[:Length])] if in_range?(rule, sanitized_string)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def valid_range(length)
|
98
|
+
(0..(length.to_i - 1))
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# XML parser methods
|
103
|
+
# modified from http://stackoverflow.com/questions/1230741/convert-a-nokogiri-document-to-a-ruby-hash/1231297#123129
|
104
|
+
#
|
105
|
+
|
106
|
+
def xml_parse
|
107
|
+
open_url_with_file_fallback.to_hash
|
108
|
+
end
|
109
|
+
|
110
|
+
def xml_to_hash(xml_io)
|
111
|
+
result = Nokogiri::XML(xml_io)
|
112
|
+
return { result.root.name.to_sym => xml_node_to_hash(result.root)}
|
113
|
+
end
|
114
|
+
|
115
|
+
def xml_node_to_hash(node)
|
116
|
+
if node.element?
|
117
|
+
parse_element(node)
|
118
|
+
else
|
119
|
+
return node.content.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_element(node)
|
124
|
+
result_hash = {}
|
125
|
+
attributes = parse_attributes(node)
|
126
|
+
if node.children.size > 0
|
127
|
+
return result_hash = parse_children(node, attributes, result_hash)
|
128
|
+
else
|
129
|
+
return attributes
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_attributes(node)
|
134
|
+
if node.attributes != {}
|
135
|
+
attributes = {}
|
136
|
+
node.attributes.keys.each do |key|
|
137
|
+
attributes[node.attributes[key].name.to_sym] = node.attributes[key].value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
attributes
|
141
|
+
end
|
142
|
+
|
143
|
+
def parse_children(node, attributes, result_hash)
|
144
|
+
node.children.each do |child|
|
145
|
+
result = xml_node_to_hash(child)
|
146
|
+
if child.name == "text"
|
147
|
+
unless child.next_sibling || child.previous_sibling
|
148
|
+
return result unless attributes
|
149
|
+
result_hash[child.name.to_sym] = result
|
150
|
+
end
|
151
|
+
elsif result_hash[child.name.to_sym]
|
152
|
+
if result_hash[child.name.to_sym].is_a?(Object::Array)
|
153
|
+
result_hash[child.name.to_sym] << result
|
154
|
+
else
|
155
|
+
result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << result
|
156
|
+
end
|
157
|
+
else
|
158
|
+
result_hash[child.name.to_sym] = result
|
159
|
+
end
|
160
|
+
end
|
161
|
+
if attributes
|
162
|
+
result_hash = attributes.merge(result_hash)
|
163
|
+
end
|
164
|
+
return result_hash
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
data/lib/isbnify/isbn.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module Isbnify
|
2
|
+
class ISBN
|
3
|
+
|
4
|
+
attr_accessor :param
|
5
|
+
|
6
|
+
def initialize(param = nil)
|
7
|
+
@param = param
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def create_isbn
|
12
|
+
self.new.create_isbn
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_isbn_checksum(number = nil)
|
16
|
+
self.new(number).create_isbn_checksum
|
17
|
+
end
|
18
|
+
|
19
|
+
def hyphinate_isbn(isbn_string = nil)
|
20
|
+
self.new(isbn_string).hyphinate_isbn
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid_isbn?(isbn_string = nil)
|
24
|
+
self.new(isbn_string).valid_isbn?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_isbn
|
29
|
+
Isbnify::IISBNA.new.create_valid_isbn
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_isbn_checksum
|
33
|
+
checksum_argument_validation(param)
|
34
|
+
return calculate_checksum(param)
|
35
|
+
end
|
36
|
+
|
37
|
+
def hyphinate_isbn
|
38
|
+
string_argument_validations(param)
|
39
|
+
return "invalid ISBN given" unless valid_isbn?
|
40
|
+
Isbnify::IISBNA.new(param).hyphinate
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_isbn?
|
44
|
+
string_argument_validations(param)
|
45
|
+
validate_checksum
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def calculate_checksum(string)
|
52
|
+
(10 - (inject_with_index(string_to_array(string)) % 10)) % 10
|
53
|
+
end
|
54
|
+
|
55
|
+
def calculate_sum(sum, number, index)
|
56
|
+
sum + exponate_number_with_index(number, index)
|
57
|
+
end
|
58
|
+
|
59
|
+
def checksum_argument_validation(isbn_string)
|
60
|
+
validate_presence_of_attribute(isbn_string)
|
61
|
+
validate_type_of_attribute(isbn_string, "String")
|
62
|
+
end
|
63
|
+
|
64
|
+
def exponate_number_with_index(number, index)
|
65
|
+
number * (3 ** ((index + 1) % 2))
|
66
|
+
end
|
67
|
+
|
68
|
+
def inject_with_index(array)
|
69
|
+
array.each_index.inject(0){ |sum, index| calculate_sum(sum, array[index].to_i, index + 1) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def sanitize_isbn_string
|
73
|
+
param.gsub(/[^0-9]/, "")
|
74
|
+
end
|
75
|
+
|
76
|
+
def string_argument_validations(isbn_string)
|
77
|
+
validate_presence_of_attribute(isbn_string)
|
78
|
+
validate_type_of_attribute(isbn_string, "String")
|
79
|
+
validates_isbn13_length
|
80
|
+
end
|
81
|
+
|
82
|
+
def string_to_array(string)
|
83
|
+
string[0..-2].split("")
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_checksum
|
87
|
+
validate_with_sanitized_string(sanitize_isbn_string)
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_with_sanitized_string(sanitized_string)
|
91
|
+
sanitized_string[-1].to_i == calculate_checksum(sanitized_string)
|
92
|
+
end
|
93
|
+
|
94
|
+
def validates_isbn13_length
|
95
|
+
raise ArgumentError, "expected argument to include exactly 13 digits" unless sanitize_isbn_string.length == 13
|
96
|
+
end
|
97
|
+
|
98
|
+
def validate_presence_of_attribute(attribute)
|
99
|
+
raise ArgumentError, "expected argument not to be nil" if attribute.nil?
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_type_of_attribute(attribute, type)
|
103
|
+
raise ArgumentError, "expected argument to be #{type}" unless attribute.is_a?(Module.const_get(type))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|