aboutsummaryrefslogtreecommitdiffstats
path: root/skate/isbn/isbn.go
blob: c50eaaa02b718a7aedf9164f0bc0f1e249cbc24d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2015 Rodrigo Moraes. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package isbn provides functions to validate ISBN strings, calculate ISBN
check digits and convert ISBN-10 to ISBN-13.
*/
package isbn

import (
	"fmt"
	"strconv"
)

// sum10 returns the weighted sum of the provided ISBN-10 string. It is used
// to calculate the ISBN-10 check digit or to validate an ISBN-10.
//
// The provided string must have a length of 9 or 10 and no formatting
// characters (spaces or hyphens).
func sum10(isbn string) (int, error) {
	s := 0
	w := 10
	for k, v := range isbn {
		if k == 9 && v == 88 {
			// Handle "X" as the digit.
			s += 10
		} else {
			n, err := strconv.Atoi(string(v))
			if err != nil {
				return -1, fmt.Errorf("Failed to convert ISBN-10 character to int: %s", string(v))
			}
			s += n * w
		}
		w--
	}
	return s, nil
}

// sum13 returns the weighted sum of the provided ISBN-13 string. It is used
// to calculate the ISBN-13 check digit or to validate an ISBN-13.
//
// The provided string must have a length of 12 or 13 and no formatting
// characters (spaces or hyphens).
func sum13(isbn string) (int, error) {
	s := 0
	w := 1
	for _, v := range isbn {
		n, err := strconv.Atoi(string(v))
		if err != nil {
			return -1, fmt.Errorf("Failed to convert ISBN-13 character to int: %s", string(v))
		}
		s += n * w
		if w == 1 {
			w = 3
		} else {
			w = 1
		}
	}
	return s, nil
}

// CheckDigit10 returns the check digit for an ISBN-10.
//
// The provided string must have a length of 9 or 10 and no formatting
// characters (spaces or hyphens). For a 10-length string, the last character
// (the digit) is ignored since that is what is being (re)calculated.
func CheckDigit10(isbn10 string) (string, error) {
	if len(isbn10) != 9 && len(isbn10) != 10 {
		return "", fmt.Errorf("A string of length 9 or 10 is required to calculate the ISBN-10 check digit. Provided was: %s", isbn10)
	}
	s, err := sum10(isbn10[:9])
	if err != nil {
		return "", err
	}
	d := (11 - (s % 11)) % 11
	if d == 10 {
		return "X", nil
	}
	return strconv.Itoa(d), nil
}

// CheckDigit13 returns the check digit for an ISBN-13.
//
// The provided string must have a length of 12 or 13 and no formatting
// characters (spaces or hyphens). For a 13-length string, the last character
// (the digit) is ignored since that is what is being (re)calculated.
func CheckDigit13(isbn13 string) (string, error) {
	if len(isbn13) != 12 && len(isbn13) != 13 {
		return "", fmt.Errorf("A string of length 12 or 13 is required to calculate the ISBN-13 check digit. Provided was: %s", isbn13)
	}
	s, err := sum13(isbn13[:12])
	if err != nil {
		return "", err
	}
	d := 10 - (s % 10)
	if d == 10 {
		return "0", nil
	}
	return strconv.Itoa(d), nil
}

// Validate returns true if the provided string is a valid ISBN-10 or ISBN-13.
//
// The provided string must have a length of 10 or 13 and no formatting
// characters (spaces or hyphens).
func Validate(isbn string) bool {
	switch len(isbn) {
	case 10:
		return Validate10(isbn)
	case 13:
		return Validate13(isbn)
	}
	return false
}

// Validate10 returns true if the provided string is a valid ISBN-10.
//
// The provided string must have a length of 10 and no formatting
// characters (spaces or hyphens).
func Validate10(isbn10 string) bool {
	if len(isbn10) == 10 {
		s, _ := sum10(isbn10)
		return s%11 == 0
	}
	return false
}

// Validate13 returns true if the provided string is a valid ISBN-13.
//
// The provided string must have a length of 13 and no formatting
// characters (spaces or hyphens).
func Validate13(isbn13 string) bool {
	if len(isbn13) == 13 {
		s, _ := sum13(isbn13)
		return s%10 == 0
	}
	return false
}

// To13 converts an ISBN-10 to an ISBN-13.
//
// The provided string must have a length of 9 or 10 and no formatting
// characters (spaces or hyphens).
func To13(isbn10 string) (string, error) {
	if len(isbn10) != 9 && len(isbn10) != 10 {
		return "", fmt.Errorf("A string of length 9 or 10 is required to convert an ISBN-10 to an ISBN-13. Provided was: %s", isbn10)
	}
	isbn13 := "978" + isbn10[:9]
	d, err := CheckDigit13(isbn13)
	if err != nil {
		return "", err
	}
	return isbn13 + d, nil
}