(module isbn (normalize-isbn valid-isbn? isbn10->isbn13 isbn-type) (import chicken scheme) (use srfi-1) (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 (isbn13->string isbn) (string-append (number->string (list-ref isbn 0)) (number->string (list-ref isbn 1)) (number->string (list-ref isbn 2)) "-" (number->string (list-ref isbn 3)) "-" (number->string (list-ref isbn 4)) (number->string (list-ref isbn 5)) (number->string (list-ref isbn 6)) (number->string (list-ref isbn 7)) (number->string (list-ref isbn 8)) "-" (number->string (list-ref isbn 9)) (number->string (list-ref isbn 10)) (number->string (list-ref isbn 11)) "-" (number->string (if (= (list-ref isbn 12) 10) 0 (list-ref isbn 12))))) (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))))) (isbn13->string isbn13))) (define (normalize-isbn isbn) (let* ((isbn-orig (reverse (string->isbn 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 (error "Not a valid length for an isbn" isbn))))) (fold string-append "" (map (lambda (x) (if (equal? "X" x) x (number->string x))) isbn)))))