isbn-tools 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+
2
+ = ISBN-Tools Change Log
3
+
4
+ == ISBN-Tools 0.1.0: 2006-09-28
5
+ * Initial release
6
+
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2006, Thierry Godfroid
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
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 IMPLIED,
16
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,60 @@
1
+ == ISBN-Tools for Ruby
2
+
3
+ This library provides the ability to manipulate ISBN numbers.
4
+ It knows about ISBN-10 and ISBN-13 numbers, has the ability to
5
+ check if they are valid and convert from one format to the other.
6
+ It can, of course, compute the check digit of both formats.
7
+
8
+ Finally, it has the ability to hyphenate ISBN numbers for the
9
+ following group identifiers: 0, 1 and 2. Note that only hyphenation
10
+ methods need to know about ranges, so all others methods (validity,
11
+ checksum computations and number conversions) can be used with ISBN
12
+ numbers from any group.
13
+
14
+ Other ranges could be added on request but I would need samples
15
+ ISBN to check the result.
16
+
17
+ == Usage
18
+
19
+ require 'rubygems'
20
+ require 'isbn/tools'
21
+
22
+ isbn_good = "2800107766"
23
+ isbn_bad = "2810107766"
24
+
25
+ def check_and_hyphenate(isbn)
26
+ if ISBN_Tools.is_valid?(isbn)
27
+ puts ISBN_Tools.hyphenate(isbn)
28
+ else
29
+ cksum = ISBN_Tools.compute_check_digit(isbn)
30
+ puts "Invalid ISBN number [#{isbn}]. Checksum should be #{cksum}"
31
+ end
32
+ end
33
+
34
+ check_and_hyphenate isbn_good # => 2-8001-0776-6
35
+ check_and_hyphenate isbn_bad # => Invalid ISBN number [2810107766]. Checksum should be 9
36
+
37
+ == Copyright
38
+
39
+ Copyright:: 2006, Thierry Godfroid
40
+
41
+ The following sources were used in order to understand ISBN numbers and
42
+ how to manipulate them. No books were harmed in the process.
43
+ - http://www.isbn-international.org
44
+ - "Are You Ready for ISBN-13?" http://www.isbn.org/standards/home/isbn/transition.asp. Note that at the bottom of this page, you can find a link towards a small book "ISBN-13 for Dummies", available as PDF (http://www.bisg.org/isbn-13/ISBN13_For_Dummies.pdf)
45
+ - Structure of an ISBN number http://www.isbn.org/standards/home/isbn/international/html/usm4.htm
46
+
47
+ Ranges information was found at http://www.isbn-international.org/en/identifiers.html.
48
+
49
+ == LICENCE NOTE
50
+
51
+ MIT-Style license. See LICENCE file in this distribution.
52
+
53
+ == Requirements
54
+
55
+ ISBN-Tools requires Ruby 1.8.2 or better.
56
+
57
+ == Known bugs/Limitations
58
+
59
+ - This release code only allows for one-digit groups.
60
+ - See also the TODO file in this distribution
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ == TODO list
2
+ - Handle groups which number of digit is > 1
3
+ - Add all known ranges for all groups
4
+ - Proof read comments
5
+ - Add samples to comments
6
+ - Make sure that library follows Ruby rules (layout, naming ...)
7
+ - Add more tests (there are never enough, I'm told)
8
+
@@ -0,0 +1,3 @@
1
+ 0,00..19,200..699,7000..8499,85000..89999,900000..949999,9500000..9999999
2
+ 1,00..09,100..399,4000..5499,55000..86979,869800..998999
3
+ 2,00..19,200..349,35000..39999,400..699,7000..8399,84000..89999,900000..949999,9500000..9999999
@@ -0,0 +1,173 @@
1
+ #--
2
+ # Copyright 2006, Thierry Godfroid
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # * The name of the author may not be used to endorse or promote products derived
12
+ # from this software without specific prior written permission.
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
18
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
19
+ # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ # This module provides all the methods of the ISBN-Tools library.
26
+ # Methods have no state but the library reads the file data/ranges.dat
27
+ # and fills up the RNG hash when loaded.
28
+ module ISBN_Tools
29
+ # Supported groups and associated ranges. Data is read from data/ranges.dat
30
+ # (provided in gem) at module load.
31
+ RNG = {}
32
+
33
+ File.open(File.join(File.dirname(__FILE__), "../../data/ranges.dat")) do |file|
34
+ file.each do |line|
35
+ line.chomp!
36
+ break if line.empty?
37
+ ar_line = line.split(/,/)
38
+ ndx = ar_line.delete_at(0)
39
+ RNG[ndx] = []
40
+ ar_line.each { |item|
41
+ r = item.split(/\.\./)
42
+ RNG[ndx].push(Range.new(r[0],r[1]))
43
+ }
44
+ end
45
+ end
46
+
47
+ # Clear all useless characters from an ISBN number and upcase the 'X' sign when
48
+ # present. Also does the basic check that 'X' must be the last sign of the number,
49
+ # if present. Returns nil if provided string is nil or X is not at the last position.
50
+ #
51
+ # No length check is done: no matter what string is passed in, all characters that
52
+ # not in the range [0-9xX] are removed.
53
+ def ISBN_Tools.cleanup(isbn_)
54
+ isbn_.gsub(/[^0-9xX]/,'').gsub(/x/,'X') unless isbn_.nil? or isbn_.scan(/([xX])/).length > 1
55
+ end
56
+
57
+ # Same as cleanup but alters the argument.
58
+ def ISBN_Tools.cleanup!(isbn_)
59
+ isbn_.replace(cleanup(isbn_))
60
+ end
61
+
62
+ # Check that the value is a valid ISBN-10 number. Returns true if it is, false otherwise.
63
+ # The method will check that the number is exactly 10 digits long and that the tenth digit is
64
+ # the correct checksum for the number.
65
+ def ISBN_Tools.is_valid_isbn10?(isbn_)
66
+ isbn = cleanup(isbn_)
67
+ return false if isbn.nil? or isbn.match(/^[0-9]{9}[0-9X]$/).nil?
68
+ sum = 0;
69
+ 0.upto(9) { |ndx| sum += (isbn[ndx]!= 88 ? isbn[ndx].chr.to_i : 10) * (10-ndx) } # 88 is ascii of X
70
+ sum % 11 == 0
71
+ end
72
+
73
+ # Check that the value is a valid ISBN-13 number. Returns true if it is, false otherwise.
74
+ # The method will check that the number is exactly 13 digits long and that the thirteenth digit is
75
+ # the correct checksum for the number.
76
+ def ISBN_Tools.is_valid_isbn13?(isbn_)
77
+ isbn = cleanup(isbn_)
78
+ return false if isbn.nil? or isbn.length!=13 or isbn.match(/^97[8|9][0-9]{10}$/).nil?
79
+ sum = 0
80
+ 0.upto(12) { |ndx| sum += isbn[ndx].chr.to_i * (ndx % 2 == 0 ? 1 : 3) }
81
+ sum.remainder(10) == 0
82
+ end
83
+
84
+ # Check that an ISBN is valid or not. Returns true if is, false otherwise. This method will
85
+ # first call is_valid_isbn10() and, on failure, try is_valid_isbn13(). Returns true if it is
86
+ # a valid number, false otherwise.
87
+ # This method is handy if you don't want to be bothered by checking the length of your
88
+ # isbn before checking its validity. It is a bit slower since cleanup will be called twice.
89
+ def ISBN_Tools.is_valid?(isbn_)
90
+ is_valid_isbn10?(isbn_) || is_valid_isbn13?(isbn_)
91
+ end
92
+
93
+ # Computes the check digit of an ISBN-10 number. It will ignore the tenth sign if present
94
+ # and accepts a number with only 9 digits. Returns the checksum digit or nil. Please note
95
+ # that the checksum digit of an ISBN-10 may be the character 'X'.
96
+ def ISBN_Tools.compute_isbn10_check_digit(isbn_)
97
+ isbn = cleanup(isbn_)
98
+ return nil if isbn.nil? or isbn.length > 10 or isbn.length < 9
99
+ sum = 0;
100
+ 0.upto(8) { |ndx| sum += isbn[ndx].chr.to_i * (10-ndx) }
101
+ (11-sum) % 11 == 10 ? "X" : ((11-sum) % 11).to_s
102
+ end
103
+
104
+ # Computes the check digit of an ISBN-13 number. It will ignore the thirteenth sign if present
105
+ # and accepts a number with only 12 digits. Returns the checksum digit or nil. Please note
106
+ # that the checksum digit of an ISBN-13 is always in the range [0-9].
107
+ def ISBN_Tools.compute_isbn13_check_digit(isbn_)
108
+ isbn = cleanup(isbn_)
109
+ return nil if isbn.nil? or isbn.length > 13 or isbn.length < 12
110
+ sum = 0
111
+ 0.upto(11) { |ndx| sum += isbn[ndx].chr.to_i * (ndx % 2 == 0 ? 1 : 3) }
112
+ (10-sum.remainder(10)) == 10 ? "0" : (10-sum.remainder(10)).to_s
113
+ end
114
+
115
+ # Compute the check digit of an ISBN number. Try as an ISBN-10 number
116
+ # first, and if it failed, as an ISBN-13 number. Returns the check digit or
117
+ # nil if a processing error occured.
118
+ # This method is a helper for compute_isbn10_check_digit and
119
+ # compute_isbn13_check_digit.
120
+ def ISBN_Tools.compute_check_digit(isbn_)
121
+ compute_isbn10_check_digit(isbn_) || compute_isbn13_check_digit(isbn_)
122
+ end
123
+
124
+ # Convert an ISBN-10 number to its equivalent ISBN-13 number. Returns the converted number or nil
125
+ # if the provided ISBN-10 number is nil or non valid.
126
+ def ISBN_Tools.isbn10_to_isbn13(isbn_)
127
+ isbn = cleanup(isbn_)
128
+ "978" + isbn[0..8] + compute_isbn13_check_digit("978" + isbn[0..8]) unless isbn.nil? or ! is_valid_isbn10?(isbn)
129
+ end
130
+
131
+ # Convert an ISBN-13 number to its equivalent ISBN-10 number. Returns the converted number or nil
132
+ # if the provided ISBN-13 number is nil or non valid. Please note that only ISBN-13 numbers starting
133
+ # with 978 can be converted.
134
+ def ISBN_Tools.isbn13_to_isbn10(isbn_)
135
+ isbn = cleanup(isbn_)
136
+ isbn[3..11] + compute_isbn10_check_digit(isbn[3..11]) unless isbn.nil? or ! is_valid_isbn13?(isbn) or ! isbn_.match(/^978.*/)
137
+ end
138
+
139
+ # Hyphenate a valid ISBN-10 number. Returns nil if the number is invalid or if the group range is
140
+ # unknown. Works only for groups 0,1 and 2.
141
+ def ISBN_Tools.hyphenate_isbn10(isbn_)
142
+ isbn = cleanup(isbn_)
143
+ group = isbn[0..0]
144
+ if RNG.has_key?(group) and is_valid_isbn10?(isbn)
145
+ RNG[group].each { |r| return isbn.sub(Regexp.new("(.{1})(.{#{r.last.length}})(.{#{8-r.last.length}})(.)"),'\1-\2-\3-\4') if r.member?(isbn[1..r.last.length]) }
146
+ end
147
+ end
148
+
149
+ # Hyphenate a valid ISBN-13 number. Returns nil if the number is invalid or if the group range is
150
+ # unknown. Works only for groups 0,1 and 2.
151
+ def ISBN_Tools.hyphenate_isbn13(isbn_)
152
+ isbn = cleanup(isbn_)
153
+ if is_valid_isbn13?(isbn)
154
+ group = isbn[3..3]
155
+ if RNG.has_key?(group)
156
+ RNG[group].each { |r| return isbn.sub(Regexp.new("(.{3})(.{1})(.{#{r.last.length}})(.{#{8-r.last.length}})(.)"),'\1-\2-\3-\4-\5') if r.member?(isbn[1..r.last.length]) }
157
+ end
158
+ end
159
+ end
160
+
161
+ # This method takes an ISBN then tries to hyphenate it as an ISBN 10 then an ISBN 13. A bit slower
162
+ # than calling the right one directly but saves you the length checking. Returns an hyphenated value
163
+ # or nil.
164
+ def ISBN_Tools.hyphenate(isbn_)
165
+ hyphenate_isbn10(isbn_) || hyphenate_isbn13(isbn_)
166
+ end
167
+
168
+ # Same as hyphenate() but alters the argument.
169
+ def ISBN_Tools.hyphenate!(isbn_)
170
+ isbn_.replace(hyphenate(isbn_))
171
+ end
172
+
173
+ end
@@ -0,0 +1,105 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+ require "isbn/tools"
4
+
5
+ class TC_ISBN_Tools_Tests < Test::Unit::TestCase
6
+ def setup
7
+ end
8
+
9
+ def teardown
10
+ end
11
+
12
+ def testing
13
+ # ISBN 10 inputs
14
+ isbn_orig_ok = "0-8436-1072-7"
15
+ isbn_orig_bad = "0-8436-1072-x"
16
+ # both values taken from: http://www.isbn.org/standards/home/isbn/transition.asp
17
+ isbn_1_10 = "1-56619-909-3"
18
+ isbn_1_13 = "978-1-56619-909-4"
19
+ isbn_1_13b = "978-1-56619-909-x" # wrong check digit
20
+ isbn_2_13 = "979-1-56619-909-3" # same as isbn_1_13 but altered with 979 and valid cksum
21
+ isbn_3_10 = "2-930088-49-4" #unusually small (?) editor: good for hyphen testing!
22
+
23
+ isbn_4_10 = "4413008480" # japanese ISBN (I think, ... At least, it validates.)
24
+
25
+ isbn = ""
26
+
27
+ # check that cleanup works
28
+ assert_equal(true, "0843610727".eql?(ISBN_Tools.cleanup(isbn_orig_ok)))
29
+ # and does not alter provided argument
30
+ assert_equal("0-8436-1072-7",isbn_orig_ok)
31
+ # test that cleanup! does alter the original string
32
+ isbn.replace(isbn_orig_ok)
33
+ ISBN_Tools.cleanup!(isbn)
34
+ assert_equal("0843610727", isbn)
35
+ # grrr .. Nte to self: make sure that isbn was not setup as a simple reference to
36
+ # isbn_orig_ok otherwise it will also be altered. Use replace as above.
37
+ assert_equal("0-8436-1072-7",isbn_orig_ok)
38
+ # test that X is upper cased (even on bad numbers)
39
+ assert_equal(true, "084361072X".eql?(ISBN_Tools.cleanup(isbn_orig_bad)))
40
+
41
+ # test that it is a valid isbn 10 number
42
+ assert_equal(true, ISBN_Tools.is_valid_isbn10?(isbn_orig_ok))
43
+ assert_equal(true, ISBN_Tools.is_valid_isbn10?(isbn_4_10))
44
+ # test that it is NOT a valid isbn 10 number
45
+ assert_equal(false, ISBN_Tools.is_valid_isbn10?(isbn_orig_bad))
46
+ assert_equal(false, ISBN_Tools.is_valid_isbn10?(isbn_1_13))
47
+ # same as the two tests above but via the generic is_valid? method. Must have identical result.
48
+ assert_equal(true, ISBN_Tools.is_valid?(isbn_orig_ok))
49
+ assert_equal(false, ISBN_Tools.is_valid?(isbn_orig_bad))
50
+ # test that it is NOT a valid isbn 13 number
51
+ assert_equal(false, ISBN_Tools.is_valid_isbn13?(isbn_orig_bad))
52
+ # test that it is a valid isbn 13 number
53
+ assert_equal(true, ISBN_Tools.is_valid_isbn13?(isbn_1_13))
54
+ # same as the two tests above but via the generic is_valid? method. Must have identical result.
55
+ assert_equal(true, ISBN_Tools.is_valid?(isbn_1_13))
56
+ assert_equal(false, ISBN_Tools.is_valid?(isbn_1_13b))
57
+ # test that check digit is indeed 7
58
+ assert_equal("7",ISBN_Tools.compute_isbn10_check_digit(isbn_orig_ok))
59
+ # test must also succeed on isbn_orig_bad since only the check digit changes
60
+ # and it must not be considered in the computation
61
+ assert_equal("7",ISBN_Tools.compute_isbn10_check_digit(isbn_orig_bad))
62
+ # test check digit of isbn 13 number. Must be 4
63
+ assert_equal("4",ISBN_Tools.compute_isbn13_check_digit(isbn_1_13))
64
+ # test must also succeed on isbn_orig_bad since only the check digit changes
65
+ # and it must not be considered in the computation
66
+ assert_equal("4",ISBN_Tools.compute_isbn13_check_digit(isbn_1_13b))
67
+ # test the wrapper, once for ISBN10 and once for ISBN13
68
+ assert_equal("7",ISBN_Tools.compute_check_digit(isbn_orig_ok))
69
+ assert_equal("4",ISBN_Tools.compute_check_digit(isbn_1_13))
70
+
71
+ # verify hyphenation from cleanup version
72
+ assert_equal(isbn_orig_ok, ISBN_Tools.hyphenate_isbn10(isbn_orig_ok))
73
+ assert_equal(isbn_1_10, ISBN_Tools.hyphenate_isbn10(isbn_1_10))
74
+ assert_equal(isbn_3_10, ISBN_Tools.hyphenate_isbn10(isbn_3_10))
75
+ assert_equal(isbn_3_10, ISBN_Tools.hyphenate_isbn10(isbn_3_10))
76
+ # fail on hyphenating a non group 0/1/2 ISBN
77
+ assert_equal(nil, ISBN_Tools.hyphenate_isbn10(isbn_4_10))
78
+ # fail on hyphenating an invalid isbn
79
+ assert_equal(nil, ISBN_Tools.hyphenate_isbn10(isbn_orig_bad))
80
+ # on isbn 13
81
+ assert_equal(isbn_1_13,ISBN_Tools.hyphenate_isbn13(isbn_1_13))
82
+ # same result with cleanup up one
83
+ assert_equal(isbn_1_13,ISBN_Tools.hyphenate_isbn13(ISBN_Tools.cleanup(isbn_1_13)))
84
+ # check the generic method
85
+ assert_equal(isbn_1_13,ISBN_Tools.hyphenate(isbn_1_13))
86
+ assert_equal(isbn_1_10, ISBN_Tools.hyphenate(isbn_1_10))
87
+ assert_equal(nil, ISBN_Tools.hyphenate(isbn_4_10))
88
+ # check that hyphenate! alters the argument
89
+ isbn.replace(isbn_1_10)
90
+ ISBN_Tools.cleanup!(isbn)
91
+ assert_equal(isbn_1_10,ISBN_Tools.hyphenate!(isbn))
92
+ assert_equal(isbn,isbn_1_10)
93
+
94
+ # test conversion from 10 to 13
95
+ assert_equal(ISBN_Tools.cleanup(isbn_1_13), ISBN_Tools.isbn10_to_isbn13(isbn_1_10))
96
+ # test conversion from 13 to 10
97
+ assert_equal(ISBN_Tools.cleanup(isbn_1_10), ISBN_Tools.isbn13_to_isbn10(isbn_1_13))
98
+ # test illegal conversion from 13 to 10 (should return nil because of the 979)
99
+ assert_equal(nil, ISBN_Tools.isbn13_to_isbn10(isbn_2_13))
100
+
101
+ end
102
+ end
103
+
104
+ require 'test/unit/ui/console/testrunner'
105
+ Test::Unit::UI::Console::TestRunner.run(TC_ISBN_Tools_Tests)
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: isbn-tools
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-09-28 00:00:00 +02:00
8
+ summary: A series of methods to manipulate ISBN numbers.
9
+ require_paths:
10
+ - lib
11
+ email: ""
12
+ homepage: http://
13
+ rubyforge_project: isbn-tools
14
+ description: ""
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Thierry GODFROID
30
+ files:
31
+ - README
32
+ - LICENCE
33
+ - TODO
34
+ - ChangeLog
35
+ - lib/isbn/tools.rb
36
+ - data/ranges.dat
37
+ test_files:
38
+ - tests/tc_isbn_tools_tests.rb
39
+ rdoc_options:
40
+ - --title
41
+ - ISBN-Tools
42
+ - --main
43
+ - README
44
+ - --line-numbers
45
+ extra_rdoc_files:
46
+ - README
47
+ - ChangeLog
48
+ - LICENCE
49
+ - TODO
50
+ executables: []
51
+
52
+ extensions: []
53
+
54
+ requirements: []
55
+
56
+ dependencies: []
57
+