(module isbn (normalize-isbn valid-isbn? isbn10->isbn13 isbn13->isbn10 isbn-type) (import chicken scheme) (use srfi-1) (define (recalculate-isbn10-checkdigit isbn) (append (drop-right isbn 1) (list (- 11 (modulo (fold (lambda (x y seed) (+ seed (* x y))) 0 (iota 10 10 -1) (take isbn 9)) 11))))) (define (isbn13->isbn10 isbn-string) (let ((isbn (string->isbn isbn-string))) (when (not (equal? '(9 7 8) (take isbn 3))) (error "Unable to convert this isbn since it is not unambigous with any other prefix than 978")) (isbn->string (recalculate-isbn10-checkdigit (take (drop isbn 3) 10))))) (define (valid-isbn10-checksum? isbn) (= 0 (modulo (fold (lambda (x y seed) (+ seed (* x y))) 0 (iota 10 10 -1) isbn) 11))) (define (valid-isbn10? isbn) (and (= (length isbn) 10) (valid-isbn10-checksum? isbn))) (define (isbn13-checkdigit isbn) (- 10 (modulo (fold (lambda (y x s) (+ s (* x y))) 0 (take (circular-list 1 3) 12) (take isbn 12)) 10))) (define (valid-isbn13-checksum? isbn) (= (last isbn) (isbn13-checkdigit isbn))) (define (valid-isbn13? isbn) (and (= (length isbn) 13) (valid-isbn13-checksum? isbn))) (define (isbn-type isbn-string) (let ((isbn (string->isbn isbn-string))) (cond ((valid-isbn10? isbn) 10) ((valid-isbn13? isbn) 13) (else #f)))) (define (valid-isbn? isbn-string) (not (not (isbn-type isbn-string)))) (define (string->isbn isbn-str) (let ((isbn (fold (lambda (x s) (if x (cons x s) s)) '() (map (lambda (s) (if (or (equal? s "x") ; XXX this is ugly (equal? s "X")) 10 (string->number s))) (map string (string->list isbn-str)))))) (if (and (= (length isbn) 13) (= (car isbn) 0)) (set-car! isbn 10)) (reverse isbn))) (define (isbn->string isbn) (let* ((isbn-orig (reverse isbn)) (isbn (cond ((and (= (length isbn-orig) 13) (= (car isbn-orig) 10)) (cons 0 (cdr isbn-orig))) ((and (= (length isbn-orig) 10) (= (car isbn-orig) 10)) (cons "X" (cdr isbn-orig))) (else isbn-orig)))) (fold string-append "" (map (lambda (x) (if (equal? "X" x) x (number->string x))) isbn)))) (define (isbn10->isbn13 isbn10-string) (and-let* ((isbn10 (string->isbn isbn10-string)) ((valid-isbn10? isbn10)) (isbn10-sans-checkdigit (take isbn10 9)) (isbn13-sans-checkdigit (append '(9 7 8) isbn10-sans-checkdigit)) (isbn13 (append isbn13-sans-checkdigit (list (isbn13-checkdigit isbn13-sans-checkdigit))))) (isbn->string isbn13))) (define (normalize-isbn isbn-string) (when (not (valid-isbn? isbn-string)) (error "invalid ISBN" isbn-string)) (isbn->string (string->isbn isbn-string))))