library_stdnums 0.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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/library_stdnums.rb +130 -0
- data/spec/library_stdnums_spec.rb +108 -0
- data/spec/spec_helper.rb +11 -0
- metadata +104 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Bill Dueber
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
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
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= library_stdnums
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Bill Dueber. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "library_stdnums"
|
8
|
+
gem.summary = %Q{Normalize and compute checkdigits for ISBN, ISSN, and LCCN}
|
9
|
+
gem.description = %Q{Normalization and checksum computation for ISBN (10 and 13), ISSN, and LCCN}
|
10
|
+
gem.email = "bill@dueber.com"
|
11
|
+
gem.homepage = "http://github.com/billdueber/library_stdnums"
|
12
|
+
gem.authors = ["Bill Dueber"]
|
13
|
+
gem.add_development_dependency "bacon", ">= 0"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
26
|
+
spec.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |spec|
|
32
|
+
spec.libs << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :spec => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :spec
|
45
|
+
|
46
|
+
begin
|
47
|
+
require 'yard'
|
48
|
+
YARD::Rake::YardocTask.new
|
49
|
+
rescue LoadError
|
50
|
+
task :yardoc do
|
51
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
52
|
+
end
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module StdNum
|
2
|
+
|
3
|
+
STDNUMPAT = /^.*?(\d[\d\-]+[xX]?)/
|
4
|
+
|
5
|
+
# Extract the most likely looking number from the string. This will be the first
|
6
|
+
# string of digits-and-hyphens-and-maybe-a-trailing-X, with the hypens removed
|
7
|
+
def self.extractNumber str
|
8
|
+
match = STDNUMPAT.match str
|
9
|
+
return nil unless match
|
10
|
+
return match[1].gsub(/\-/, '').upcase
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
module ISBN
|
16
|
+
|
17
|
+
# Compute check digits for 10 or 13-digit ISBNs. See algorithm at
|
18
|
+
# http://en.wikipedia.org/wiki/International_Standard_Book_Number
|
19
|
+
# @param [String] isbn The ISBN (we'll try to clean it up if possible)
|
20
|
+
# @return [String] the one-character checkdigit
|
21
|
+
def self.checkdigit isbn
|
22
|
+
isbn = StdNum.extractNumber isbn
|
23
|
+
size = isbn.size
|
24
|
+
return nil unless size == 10 or size == 13
|
25
|
+
checkdigit = 0
|
26
|
+
if size == 10
|
27
|
+
digits = isbn[0..8].split(//).map {|i| i.to_i}
|
28
|
+
(1..9).each do |i|
|
29
|
+
checkdigit += digits[i-1] * i
|
30
|
+
end
|
31
|
+
checkdigit = checkdigit % 11
|
32
|
+
return 'X' if checkdigit == 10
|
33
|
+
return checkdigit.to_s
|
34
|
+
else # size == 13
|
35
|
+
digits = isbn[0..11].split(//).map {|i| i.to_i}
|
36
|
+
6.times do
|
37
|
+
checkdigit += digits.shift
|
38
|
+
checkdigit += digits.shift * 3
|
39
|
+
end
|
40
|
+
return (10 - (checkdigit % 10)).to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check to see if the checkdigit is correct
|
45
|
+
# @param [String] isbn The ISBN (we'll try to clean it up if possible)
|
46
|
+
# @return [Boolean] Whether or not the checkdigit is correct
|
47
|
+
def self.valid? isbn
|
48
|
+
isbn = StdNum.extractNumber isbn
|
49
|
+
size = isbn.size
|
50
|
+
return false unless (size == 10 or size == 13)
|
51
|
+
return isbn[-1..-1] == self.checkdigit(isbn)
|
52
|
+
end
|
53
|
+
|
54
|
+
# To convert to an ISBN13, throw a '978' on the front and
|
55
|
+
# compute the checkdigit
|
56
|
+
# We leave 13-digit numbers alone, figuring they're already ok,
|
57
|
+
# and return nil on anything that's not the right length
|
58
|
+
# @param [String] isbn The ISBN (we'll try to clean it up if possible)
|
59
|
+
# @return [String] The converted 13-character ISBN, nil if something looks wrong, or whatever was passed in if it already looked like a 13-digit ISBN
|
60
|
+
def self.convert_to_13 isbn
|
61
|
+
isbn = StdNum.extractNumber isbn
|
62
|
+
size = isbn.size
|
63
|
+
return isbn if size == 13
|
64
|
+
return nil unless size == 10
|
65
|
+
|
66
|
+
prefix = '978' + isbn[0..8]
|
67
|
+
return prefix + self.checkdigit(prefix + '0')
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Convert to 10 if it's 13 digits and the first three digits are 978.
|
72
|
+
# Pass through anything 10-digits, and return nil for everything else.
|
73
|
+
# @param [String] isbn The ISBN (we'll try to clean it up if possible)
|
74
|
+
# @return [String] The converted 10-character ISBN, nil if something looks wrong, or whatever was passed in if it already looked like a 10-digit ISBN
|
75
|
+
def self.convert_to_10 isbn
|
76
|
+
isbn = StdNum.extractNumber isbn
|
77
|
+
size = isbn.size
|
78
|
+
return isbn if size == 10
|
79
|
+
return nil unless size == 13
|
80
|
+
return nil unless isbn[0..2] == '978'
|
81
|
+
|
82
|
+
prefix = isbn[3..11]
|
83
|
+
return prefix + self.checkdigit(prefix + '0')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module ISSN
|
88
|
+
|
89
|
+
# Compute the checkdigit of an ISSN
|
90
|
+
# @param [String] issn The ISSN (we'll try to clean it up if possible)
|
91
|
+
# @return [String] the one-character checkdigit
|
92
|
+
def self.checkdigit issn
|
93
|
+
issn = StdNum.extractNumber issn
|
94
|
+
return nil unless issn.size == 8
|
95
|
+
digits = issn[0..6].split(//).map {|i| i.to_i}
|
96
|
+
checkdigit = 0
|
97
|
+
(0..6).each do |i|
|
98
|
+
checkdigit += digits[i] * (8 - i)
|
99
|
+
end
|
100
|
+
checkdigit = checkdigit % 11
|
101
|
+
return 0 if checkdigit == 0
|
102
|
+
checkdigit = 11 - checkdigit
|
103
|
+
return 'X' if checkdigit == 10
|
104
|
+
return checkdigit.to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
module LCCN
|
111
|
+
# Normalize based on data at http://www.loc.gov/marc/lccn-namespace.html#syntax
|
112
|
+
# @param [String] str The LCCN to normalize
|
113
|
+
# @return [String] the normalized LCCN, or nil if it looks malformed
|
114
|
+
def self.normalize str
|
115
|
+
str.gsub!(/\s/, '')
|
116
|
+
str.gsub!(/\/.*$/, '')
|
117
|
+
if str =~ /^(.*?)\-(.+)/
|
118
|
+
pre = $1
|
119
|
+
post = $2
|
120
|
+
return nil unless post =~ /^\d+$/ # must be all digits
|
121
|
+
return "%s%06d" % [pre, post]
|
122
|
+
end
|
123
|
+
return str
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Extract" do
|
4
|
+
it "should leave a number alone" do
|
5
|
+
StdNum.extractNumber('123456').should.equal '123456'
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should skip leading and trailing crap" do
|
9
|
+
StdNum.extractNumber(' 12345 (online)').should.equal '12345'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should allow hyphens" do
|
13
|
+
StdNum.extractNumber(' 1-234-5').should.equal '12345'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return nil on a non-match" do
|
17
|
+
StdNum.extractNumber('bill dueber').should.equal nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow a trailing X" do
|
21
|
+
StdNum.extractNumber('1-234-5-X').should.equal '12345X'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should upcase any trailing X" do
|
25
|
+
StdNum.extractNumber('1-234-x').should.equal '1234X'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "only allows a single trailing X" do
|
29
|
+
StdNum.extractNumber('1234-X-X').should.equal '1234X'
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
describe "ISBN" do
|
36
|
+
it "computes 10-digit checksum" do
|
37
|
+
StdNum::ISBN.checkdigit('0-306-40615-X').should.equal '2'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "correctly uses X for checksum" do
|
41
|
+
StdNum::ISBN.checkdigit('061871460X').should.equal 'X'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "finds a zero checkdigit" do
|
45
|
+
StdNum::ISBN.checkdigit('0139381430').should.equal '0'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "computes 13-digit checksum" do
|
49
|
+
StdNum::ISBN.checkdigit('9780306406157').should.equal '7'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "finds a good number valid" do
|
53
|
+
StdNum::ISBN.valid?('9780306406157').should.equal true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "finds a bad number false" do
|
57
|
+
StdNum::ISBN.valid?('9780306406154').should.equal false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns nil when computing checksum for bad ISBN" do
|
61
|
+
StdNum::ISBN.checkdigit('12345').should.equal nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "converts 10 to 13" do
|
65
|
+
StdNum::ISBN.convert_to_13('0-306-40615-2').should.equal '9780306406157'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "passes through 13 digit number instead of converting to 13" do
|
69
|
+
StdNum::ISBN.convert_to_13('9780306406157').should.equal '9780306406157'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "converts 13 to 10" do
|
73
|
+
StdNum::ISBN.convert_to_10('978-0-306-40615-7').should.equal '0306406152'
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
describe 'ISSN' do
|
81
|
+
it "computes checksum" do
|
82
|
+
StdNum::ISSN.checkdigit('0378-5955').should.equal '5'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
describe 'LCCN' do
|
88
|
+
|
89
|
+
# Tests take from http://www.loc.gov/marc/lccn-namespace.html#syntax
|
90
|
+
test = {
|
91
|
+
"n78-890351" => "n78890351",
|
92
|
+
"n78-89035" => "n78089035",
|
93
|
+
"n 78890351 " => "n78890351",
|
94
|
+
" 85000002 " => "85000002",
|
95
|
+
"85-2 " => "85000002",
|
96
|
+
"2001-000002" => "2001000002",
|
97
|
+
"75-425165//r75" => "75425165",
|
98
|
+
" 79139101 /AC/r932" => "79139101"
|
99
|
+
}
|
100
|
+
|
101
|
+
test.each do |k, v|
|
102
|
+
it "normalizes #{k}" do
|
103
|
+
StdNum::LCCN.normalize(k.dup).should.equal v
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bacon'
|
3
|
+
begin
|
4
|
+
require 'greeneggs'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
require 'library_stdnums'
|
10
|
+
|
11
|
+
Bacon.summary_on_exit
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: library_stdnums
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bill Dueber
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-13 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bacon
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: Normalization and checksum computation for ISBN (10 and 13), ISSN, and LCCN
|
50
|
+
email: bill@dueber.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.rdoc
|
58
|
+
files:
|
59
|
+
- .document
|
60
|
+
- .gitignore
|
61
|
+
- LICENSE
|
62
|
+
- README.rdoc
|
63
|
+
- Rakefile
|
64
|
+
- VERSION
|
65
|
+
- lib/library_stdnums.rb
|
66
|
+
- spec/library_stdnums_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://github.com/billdueber/library_stdnums
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.3.7
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Normalize and compute checkdigits for ISBN, ISSN, and LCCN
|
102
|
+
test_files:
|
103
|
+
- spec/library_stdnums_spec.rb
|
104
|
+
- spec/spec_helper.rb
|