;;; isbn-verify.el --- verify ISBN-10 and ISBN-13 -*- lexical-binding: t -*- ;; ;; Author: Emanuel Berg ;; Created: 2020-05-20 ;; Keywords: bib, tex ;; License: GPL3+ ;; Package-Requires: ((cl-lib "1.0")) ;; URL: https://dataswamp.org/~incal/emacs-init/isbn-verify.el ;; Version: 2.3.6 ;; ;;; Commentary: ;; ;; This Elisp package has functions to verify ISBNs by ;; computing their check digits. One can also submit ISBNs ;; that lack the check digits; it will then be computed. ;; ;; Also see: ;; ;; https://dataswamp.org/~incal/books/isbn.txt ;; https://dataswamp.org/~incal/emacs-init/bibtex/bibtex-autokey-insert.el ;; https://dataswamp.org/~incal/emacs-init/issn-verify.el ;; ;; Install: ;; ;; M-x load-file RET RET ;; ;; Usage: ;; ;; Do `isbn-verify-at-point' on the below Biblatex entries. ;; The first one has an ISBN-10, the second one an ISBN-13. ;; Position point at the beginning of an ISBN, then do ;; M-x viap RET ;; ;; @book{russia-and-the-arms-trade, ;; author = {Ian Anthony}, ;; isbn = {0-19-829278-3}, ;; publisher = {Oxford}, ;; title = {Russia and the Arms Trade}, ;; year = 1998 ;; } ;; ;; @book{baa-lo-4, ;; author = {Yukito Kishiro}, ;; isbn = {978-1-61262-294-1}, ;; publisher = {Kodansha}, ;; title = {Battle Angel Alita: The Last Order 4}, ;; year = {2014 (2011)} ;; } ;; ;; With/from Lisp, evaluate these: ;; ;; (isbn-checksum-10 "0-19-829576-6") ; 6 ;; (isbn-checksum-13 "978-1-61262-294-1") ; 1 ;; ;;; Code: (require 'cl-lib) (defun isbn-digits-only-string (str) "Remove all non-digits and Xs from STR." (replace-regexp-in-string "[^[:digit:]X]" "" str) ) ;; (isbn-digits-only-string "91-85668-01-X") ; 918566801X (defun isbn-char-to-int (c) "Convert a char C into the integer it displays, e.g. 9 for ?9." (- c ?0) ) (defun isbn-string-to-integer-list (str) "Make an integer list from STR dropping all non-digits." (let*((digits-only (isbn-digits-only-string str)) (chars (string-to-list digits-only)) (ints (cl-map 'list (lambda (c) (isbn-char-to-int c)) chars) )) ints) ) (defun isbn-convert-to-digit (digit) "If DIGIT is a number between 0 and 9, return it. If DIGIT is a string containing a single number, return that. If DIGIT is \"X\", return 10." (cond ((and (numberp digit) (<= 0 digit) (<= digit 9) ) digit) ((string= digit "X") 10) ((and (string-match-p "[0-9]" digit) (= 1 (length digit)) ) (string-to-number digit) ) (t (error "Incorrect digit, %s" digit)) )) ;; (isbn-convert-to-digit 0 ) ; 0 ;; (isbn-convert-to-digit 1 ) ; 1 ;; (isbn-convert-to-digit "9") ; 9 ;; (isbn-convert-to-digit "X") ; 10 ;; (isbn-convert-to-digit "100") ; Incorrect digit, 100 ;; (isbn-convert-to-digit "e") ; Incorrect digit, e (defun isbn-verify (isbn) "Verify ISBN." (let*((digits-str (isbn-digits-only-string isbn)) (num-digits (length digits-str)) (type (pcase num-digits ((or 9 10) 10) ((or 12 13) 13) (_ (error "An ISBN has 10 or 13 digits")) )) (last-digit-str (substring digits-str -1)) (last-digit-int (isbn-convert-to-digit last-digit-str) ) (check-digit (if (= type 10) (isbn-checksum-10 digits-str) (isbn-checksum-13 digits-str) )) (check-digit-int (isbn-convert-to-digit check-digit)) (pass (= last-digit-int check-digit-int)) ) (if (or (= num-digits 9) (= num-digits 12) ) check-digit (if pass (message "[ISBN-%s OK] %s" type check-digit) (error (format "Incorrect ISBN-%s, correct check digit is %s" type check-digit) ))))) ;; (isbn-verify "978-1-59307-864") ; 5 ;; (isbn-verify "978-1-59307-864-5") ; [ISBN-13 OK] 5 (defun isbn-verify-at-point () "Compute and display the check digit for the ISBN at point." (interactive) (let((isbn-string (thing-at-point 'symbol t))) (isbn-verify isbn-string) )) (defalias 'viap #'isbn-verify-at-point) (defun isbn-checksum-13 (isbn) "Compute the check digit for an ISBN-13. Because the last digit, digit number 13, is the check digit, actually only 12 digits, in the form of a string, is required. If only 12 digits are provided the correct check digit is computed. Hyphens/dashes can be included or omitted. For ISBN-10, see `isbn-checksum-10'. For interactive use, do \\[isbn-verify-at-point] for `isbn-verify-at-point'." (let*((isbn-ints (isbn-string-to-integer-list isbn)) (sum 0)) (cl-loop for e in isbn-ints for i from 0 to 11 do (cl-incf sum (* e (or (and (zerop (mod i 2)) 1) 3))) ) (let ((checksum (- 10 (mod sum 10)))) (if (= 10 checksum) 0 checksum) ))) (defun isbn-checksum-10 (isbn) "Compute the check digit for an ISBN-10. Because the last digit, digit number 10, is the check digit, actually only 9 digits, in the form of a string, is required. If only 9 digits is provided the correct check digit is computed. Hyphens/dashes can be included or omitted. If the check digit is computed to 10 return \"X\" as is the ISBN-10 standard. For ISBN-13, see `isbn-checksum-13'. For interactive use, do \\[isbn-verify-at-point] for `isbn-verify-at-point'." (let*((isbn-ints (isbn-string-to-integer-list isbn)) (sum 0) ) (cl-loop for e in isbn-ints for i downfrom 10 to 2 do (cl-incf sum (* e i)) ) (let ((checksum (mod (- 11 (mod sum 11)) 11))) (if (= 10 checksum) "X" checksum) ))) ;; check for errors: ;; ;; (isbn-verify "0-201-53992-5") ; Incorrect ISBN-10, correct check digit is 6 ;; (isbn-verify "91-518-4657-7") ; Incorrect ISBN-10, correct check digit is 8 ;; (isbn-verify "1-4012-0622-X") ; Incorrect ISBN-10, correct check digit is 0 ;; (isbn-verify "91-85668-01-9") ; Incorrect ISBN-10, correct check digit is X ;; (isbn-verify "978-0-470-56157-0") ; Incorrect ISBN-13, correct check digit is 7 ;; (isbn-verify "978-91-87861-9") ; An ISBN has 10 or 13 digits ;; (isbn-verify "1-4012-062") ; An ISBN has 10 or 13 digits ;; ;; 10 ISBN-10 tests: ;; ;; (isbn-checksum-10 "0-201-53992-6") ; 6 ;; (isbn-checksum-10 "0312168144" ) ; 4 ;; (isbn-checksum-10 "1-4012-0622-0") ; 0 ;; (isbn-checksum-10 "1616558717" ) ; 7 ;; (isbn-checksum-10 "91 7054 940 0") ; 0 ;; (isbn-checksum-10 "91-510-6483-9") ; 9 ;; (isbn-checksum-10 "91-7089-710-7") ; 7 ;; (isbn-checksum-10 "91-85668-01-X") ; X ;; (isbn-checksum-10 "91-88930-23-8") ; 8 ;; (isbn-checksum-10 "9177988515" ) ; 5 ;; ;; 13 ISBN-13 tests: ;; ;; (isbn-checksum-13 "978 91 29 59023 4") ; 4 ;; (isbn-checksum-13 "978-0-470-56157-7") ; 7 ;; (isbn-checksum-13 "978-1-63236-616-0") ; 0 ;; (isbn-checksum-13 "978-91-0-012814-2") ; 2 ;; (isbn-checksum-13 "978-91-7037-681-8") ; 8 ;; (isbn-checksum-13 "978-91-7515-205-9") ; 9 ;; (isbn-checksum-13 "978-91-7515-317-9") ; 9 ;; (isbn-checksum-13 "978-91-86936-31-0") ; 0 ;; (isbn-checksum-13 "978-91-87861-54-3") ; 3 ;; (isbn-checksum-13 "978-91-87861-67-3") ; 3 ;; (isbn-checksum-13 "978-91-87861-99-4") ; 4 ;; (isbn-checksum-13 "9780062802187" ) ; 7 ;; (isbn-checksum-13 "9789188805034" ) ; 4 (provide 'isbn-verify) ;;; isbn-verify.el ends here