isbn13 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ Gemfile
2
+ .DS_Store
3
+ *.swp
4
+ *.gem
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # ISBN13
2
+
3
+ Validation and correct formatting of an ISBN13 number.
4
+
5
+ ## Usage
6
+
7
+ require 'isbn13'
8
+
9
+ ISBN13.format('9789873303111') # => '978-987-33-0311-1'
10
+
11
+ ISBN13.valid?('978-987-33-0311-1') # => true
12
+ ISBN13.valid?('9789873303111') # => true
13
+
14
+
15
+ ## Formatting
16
+
17
+ A formatted ISBN13 number is on the form: GS1-GROUP-PUBLISHER-ITEM_NUMBER-CHECKSUM. The problem is, only GS1 and CHECKSUM has fixed lengths. The other components lengths vary according to the ranges assigned by the international/national ISBN agencies.
18
+
19
+ So, how can an ISBN13 be properly hyphenated? I've found RangeMessage.xml at http://www.isbn-international.org/page/ranges containing a list of UCC / Groups prefixes which I've used initially for formatting, but later found some errors hyphenating some isbns from Uruguay.
20
+
21
+ Later I've found a [ISBN10 to ISBN13 converter](http://www.isbn-international.org/ia/isbncvt) which have all the ranges defined as arrays in Javascript. And that's the data I've used to hyphenate the isbns. There's a bin/make_isbn_ranges.rb which downloads/parses the page and creates a new data file to be used by the gem.
22
+
23
+ ## Installation
24
+
25
+ gem install isbn13
26
+
27
+ ## Author
28
+
29
+ [Luis Parravicini](mailto:lparravi@gmail.com)
30
+
31
+ ## Changelog
32
+
33
+ * 2012-01-27 - Initial release
34
+
35
+ ## License
36
+
37
+ Copyright (c) 2012 Luis Parravicini
38
+
39
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
40
+ this software and associated documentation files (the "Software"), to deal in
41
+ the Software without restriction, including without limitation the rights to
42
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
43
+ the Software, and to permit persons to whom the Software is furnished to do so,
44
+ subject to the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be included in all
47
+ copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
51
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
52
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
53
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
54
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs = ['lib']
7
+ t.verbose = false
8
+ t.pattern = 'test/test_*.rb'
9
+ end
10
+
11
+ task :gem do
12
+ %x{gem build isbn13.gemspec}
13
+ end
@@ -0,0 +1,53 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'isbn13'
3
+ require 'mechanize'
4
+
5
+ # reading from a file is only intended for dev/test purposes
6
+ content = if File.exists?('isbncvt')
7
+ puts 'reading ranges from local file'
8
+ IO.read('isbncvt')
9
+ else
10
+ puts 'downloading ranges from isbn-international.org'
11
+ agent = Mechanize.new
12
+ agent.user_agent_alias = 'Mac Safari'
13
+
14
+ doc = agent.get('http://www.isbn-international.org/ia/isbncvt')
15
+
16
+ doc.at('div#wrapper script').inner_html
17
+ end
18
+
19
+
20
+ puts 'parsing'
21
+ ranges = Hash.new
22
+ # ranges are defined in javascript as:
23
+ # ranges['xxx'][y][z] = 'first_value';
24
+ # ranges['xxx'][y][z+1] = 'last_value';
25
+ first = nil
26
+ content.each_line do |line|
27
+ next unless line =~ /^\s*ranges\['(\d+)'\]\[\d+\]\[(\d+)\]*\s*=\s*'(\d+)'/
28
+ data = [$1.to_i, $2, $3]
29
+
30
+ if data[1] == '0'
31
+ raise "first should be nil" unless first.nil?
32
+ first = data.last
33
+ else
34
+ raise "first shouldnt be nil" if first.nil?
35
+
36
+ ranges[data.first] ||= { :ranges => []}
37
+ ranges[data.first][:ranges] << [first, data.last]
38
+ first = nil
39
+ end
40
+ end
41
+
42
+ ranges.values.each do |data|
43
+ data[:min], data[:max] = data[:ranges].flatten.map(&:size).minmax
44
+
45
+ data[:ranges].map! { |x| x.map!(&:to_i); Range.new(x.first, x.last) }
46
+ end
47
+
48
+
49
+ path = File.basename(ISBN13.ranges_path)
50
+ puts "creating " + path
51
+ ISBN13.save(ranges, path)
52
+
53
+ puts "\nyou must replace the file #{ISBN13.ranges_path} (inside the gem) with this new file"
data/isbn13.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ require File.expand_path("../lib/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "isbn13"
5
+ s.version = ISBN13::VERSION
6
+ s.authors = ["Luis Parravicini"]
7
+ s.email = ["lparravi@gmail.com"]
8
+ s.homepage = "http://github.com/luisparravicini/isbn13"
9
+ s.summary = "A library for validating and properly formatting ISBN13 numbers"
10
+ s.files = `git ls-files`.split("\n")
11
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
12
+ s.require_paths = ["lib"]
13
+ s.description = "Provides validation and proper formatting for ISBN13 numbers."
14
+ end
data/lib/isbn13.rb ADDED
@@ -0,0 +1,90 @@
1
+ require 'version'
2
+
3
+ class ISBNError < StandardError
4
+ end
5
+
6
+ module ISBN13
7
+ # based on ruby example on http://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-13
8
+ def self.valid?(isbn)
9
+ return false if isbn.nil?
10
+
11
+ isbn = isbn.gsub(/-/, '')
12
+ return false unless valid_form?(isbn)
13
+
14
+ sum = 0
15
+ 13.times { |i| sum += i.modulo(2)==0 ? isbn[i].to_i : isbn[i].to_i*3 }
16
+ 0 == sum.modulo(10)
17
+ end
18
+
19
+ def self.format(isbn)
20
+ raise ISBNError.new("#{isbn} is not a proper ISBN13 number") unless valid_form?(isbn)
21
+ load
22
+
23
+ breaks = [3]
24
+ found = false
25
+ size = nil
26
+ @ranges.keys.max.to_s.size.downto(3) do |x|
27
+ found = @ranges.has_key?(isbn[0, x].to_i)
28
+ size = x
29
+ break if found
30
+ end
31
+ raise ISBNError.new("group not found") unless found
32
+ breaks << size - breaks.first
33
+
34
+ data = @ranges[isbn[0, size].to_i]
35
+ pub_size = nil
36
+ found = false
37
+ data[:min].upto(isbn.size - breaks.last) do |x|
38
+ found = data[:ranges].any? { |range| range.cover?(isbn[size, x].to_i) }
39
+ pub_size = x
40
+ break if found
41
+ end
42
+ raise ISBNError.new("publisher not found") unless found
43
+ breaks << pub_size
44
+
45
+ breaks << isbn.size - breaks.reduce(&:+) - 1
46
+ breaks << 1
47
+
48
+ result = []
49
+ idx = 0
50
+ breaks.each do |x|
51
+ result << isbn[idx, x]
52
+ idx += x
53
+ end
54
+
55
+ result.join('-')
56
+ end
57
+
58
+ def self.ranges_path
59
+ File.join(File.dirname(__FILE__), 'ranges.bin')
60
+ end
61
+
62
+ private
63
+
64
+ def self.valid_form?(x); x =~ /^\d{13}$/; end
65
+
66
+ def self.save(ranges, path=ranges_path)
67
+ File.open(path, 'w') { |io| io.write Marshal.dump(ranges) }
68
+ end
69
+
70
+ def self.load
71
+ return unless @ranges.nil?
72
+
73
+ if File.exist?(ranges_path)
74
+ @ranges = File.open(ranges_path) { |io| Marshal.restore(io) }
75
+ else
76
+ raise "#{ranges_path} not found, run make_ranges.rb"
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ if $0 == __FILE__
83
+ isbn = ARGV.shift
84
+ if isbn.nil?
85
+ puts "usage: #{$0} <isbn>"
86
+ exit 1
87
+ end
88
+ puts ISBN13.format(isbn)
89
+ end
90
+
data/lib/ranges.bin ADDED
Binary file
data/lib/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ module ISBN13
2
+ VERSION = '1.0'
3
+ end
4
+
5
+
data/test/test_isbn.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'isbn13'
2
+ require 'test/unit'
3
+
4
+ class ISBN13Test < Test::Unit::TestCase
5
+ def test_format_1
6
+ assert_isbn '978-9974-95-536-3'
7
+ end
8
+
9
+ def test_format_2
10
+ assert_isbn '978-9974-95-519-6'
11
+ end
12
+
13
+ def test_format_3
14
+ assert_isbn '978-950-07-3803-3'
15
+ end
16
+
17
+ def test_format_4
18
+ assert_isbn '978-950-644-238-5'
19
+ end
20
+
21
+ def test_format_5
22
+ assert_isbn '978-987-33-0311-1'
23
+ end
24
+
25
+ def test_format_6
26
+ assert_isbn '978-84-323-1145-1'
27
+ end
28
+
29
+ def test_format_7
30
+ assert_isbn '978-9974-1-0026-8'
31
+ end
32
+
33
+ def test_format_8
34
+ assert_isbn '978-9974-32-269-1'
35
+ end
36
+
37
+ def test_invalid_format
38
+ assert_raise(ISBNError) { ISBN13.format('sdlkasjdlka') }
39
+ end
40
+
41
+ def test_valid
42
+ assert_true ISBN13.valid?('9789974100268')
43
+ end
44
+
45
+ def test_valid_hyphens
46
+ assert_true ISBN13.valid?('978-9974-32-269-1')
47
+ end
48
+
49
+ def test_invalid
50
+ assert_false ISBN13.valid?('9999999999999')
51
+ end
52
+
53
+ def test_nil
54
+ assert_false ISBN13.valid?(nil)
55
+ end
56
+
57
+ private
58
+
59
+ def assert_isbn(isbn)
60
+ assert_equal isbn, ISBN13.format(isbn.gsub('-', ''))
61
+ end
62
+
63
+ def assert_true(x)
64
+ assert_equal true, x
65
+ end
66
+
67
+ def assert_false(x)
68
+ assert_equal false, x
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: isbn13
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luis Parravicini
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-27 00:00:00.000000000 -03:00
13
+ default_executable:
14
+ dependencies: []
15
+ description: Provides validation and proper formatting for ISBN13 numbers.
16
+ email:
17
+ - lparravi@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - README.md
24
+ - Rakefile
25
+ - bin/make_isbn_ranges.rb
26
+ - isbn13.gemspec
27
+ - lib/isbn13.rb
28
+ - lib/ranges.bin
29
+ - lib/version.rb
30
+ - test/test_isbn.rb
31
+ has_rdoc: true
32
+ homepage: http://github.com/luisparravicini/isbn13
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 1.6.2
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: A library for validating and properly formatting ISBN13 numbers
56
+ test_files: []