isbn13 1.0

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.
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: []