HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

ISBNをチェックするユーティリティ

唐突に、いつかどこかで役に立つかなぁ、と思い、持てる力(何)をいろいろ出してユーティリティを書いてみた。
アルゴリズムWikipedia(ISBN - Wikipedia)を参考にした。

Java

多少のブランクはあってもやはり私のプライマリ言語ということでまず最初に。

/**
 * ISBN(ISBN-10、ISBN-13)をチェックするユーティリティ。
 * 
 * @author hhelibex
 * @see http://ja.wikipedia.org/wiki/ISBN
 */
public final class ISBNUtil {

    private ISBNUtil() {
        throw new InternalError("class ISBNUtil cannot be instantiated.");
    }

    /**
     * 正しいISBN-10であるかどうかをチェックする。
     * 
     * @param isbn10 "nnnnnnnnnc"の形式(nは0-9、cは0-9または'X')、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-10である場合はtrue、それ以外の場合はfalse
     */
    public static boolean isValidISBN10(String isbn10) {
        if (isbn10 == null) {
            return false;
        }
        isbn10 = isbn10.replaceAll("[- ]", "");
        if (!isbn10.matches("[0-9]{9}[0-9X]")) {
            return false;
        }
        int val = 11 - (
            (10 * charToInt(isbn10.charAt(0))
            + 9 * charToInt(isbn10.charAt(1))
            + 8 * charToInt(isbn10.charAt(2))
            + 7 * charToInt(isbn10.charAt(3))
            + 6 * charToInt(isbn10.charAt(4))
            + 5 * charToInt(isbn10.charAt(5))
            + 4 * charToInt(isbn10.charAt(6))
            + 3 * charToInt(isbn10.charAt(7))
            + 2 * charToInt(isbn10.charAt(8))) % 11);
        char chk = (val == 10 ? 'X' : (val == 11 ? '0' : intToChar(val)));
        if (isbn10.charAt(9) != chk) {
            return false;
        }
        return true;
    }

    /**
     * 正しいISBN-13であるかどうかをチェックする。
     * 
     * @param isbn13 "nnnnnnnnnnnnn"の形式(nは0-9)、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-13である場合はtrue、それ以外の場合はfalse
     */
    public static boolean isValidISBN13(String isbn13) {
        if (isbn13 == null) {
            return false;
        }
        isbn13 = isbn13.replaceAll("[- ]", "");
        if (!isbn13.matches("[0-9]{13}")) {
            return false;
        }
        int val =
                ( 1 * charToInt(isbn13.charAt(0))
                + 3 * charToInt(isbn13.charAt(1))
                + 1 * charToInt(isbn13.charAt(2))
                + 3 * charToInt(isbn13.charAt(3))
                + 1 * charToInt(isbn13.charAt(4))
                + 3 * charToInt(isbn13.charAt(5))
                + 1 * charToInt(isbn13.charAt(6))
                + 3 * charToInt(isbn13.charAt(7))
                + 1 * charToInt(isbn13.charAt(8))
                + 3 * charToInt(isbn13.charAt(9))
                + 1 * charToInt(isbn13.charAt(10))
                + 3 * charToInt(isbn13.charAt(11))) % 10;
        char chk = (val % 10 == 0 ? intToChar(0) : intToChar(10 - val));
        if (isbn13.charAt(12) != chk) {
            return false;
        }
        return true;
    }

    /**
     * 指定した数字を対応する数値に変換する。
     * 
     * @param ch 数字('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
     * @return 対応する数値(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
     * @throws IllegalArgumentException 指定した文字が数字でない場合
     */
    private static int charToInt(char ch) throws IllegalArgumentException {
        int res = "0123456789".indexOf(ch);
        if (res < 0) {
            throw new IllegalArgumentException("not a digit character");
        } else {
            return res;
        }
    }
    /**
     * 指定した一桁の正の数値を対応する数字に変換する。
     * 
     * @param n 一桁の正の数値(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
     * @return 対応する数字('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
     * @throws IllegalArgumentException 指定した数値が一桁の正数でない場合
     */
    private static char intToChar(int n) throws IllegalArgumentException {
        if (n < 0 || n > 9) {
            throw new IllegalArgumentException("not a one digit of number");
        }
        return "0123456789".charAt(n);
    }
}

JavaScript

中途半端に使っていたので、構文に関する記憶があいまいで、本を眺めながらだったが(謎)。

/**
 * ISBN(ISBN-10、ISBN-13)をチェックするユーティリティ。
 * 
 * @author hhelibex
 * @see http://ja.wikipedia.org/wiki/ISBN
 */
(function() {
    /*
     * Javaのstaticメソッド呼び出しのように記述するために、
     * まずprivate擬似クラスを作成。
     */
    var ISBNUtilP = function() {};
    /**
     * 正しいISBN-10であるかどうかをチェックする。
     * 
     * @param isbn10 "nnnnnnnnnc"の形式(nは0-9、cは0-9または'X')、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-10である場合はtrue、それ以外の場合はfalse
     */
    ISBNUtilP.prototype.isValidISBN10 = function(isbn10) {
        if (!isbn10) {
            return false;
        }
        isbn10 = isbn10.replace(/[- ]/g, "");
        if (isbn10.length != 10) {
            return false;
        }
        if (!isbn10.match(/^[0-9]+[0-9X]$/)) {
            return false;
        }
        var val = 11 - (
            (10 * isbn10.substring(0, 1)
            + 9 * isbn10.substring(1, 2)
            + 8 * isbn10.substring(2, 3)
            + 7 * isbn10.substring(3, 4)
            + 6 * isbn10.substring(4, 5)
            + 5 * isbn10.substring(5, 6)
            + 4 * isbn10.substring(6, 7)
            + 3 * isbn10.substring(7, 8)
            + 2 * isbn10.substring(8, 9)) % 11);
        var chk = (val == 10 ? 'X' : (val == 11 ? '0' : val + ''));
        if (isbn10.substring(9, 10) != chk) {
            return false;
        }
        return true;
    }
    /**
     * 正しいISBN-13であるかどうかをチェックする。
     * 
     * @param isbn13 "nnnnnnnnnnnnn"の形式(nは0-9)、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-13である場合はtrue、それ以外の場合はfalse
     */
    ISBNUtilP.prototype.isValidISBN13 = function(isbn13) {
        if (!isbn13) {
            return false;
        }
        isbn13 = isbn13.replace(/[- ]/g, "");
        if (isbn13.length != 13) {
            return false;
        }
        if (!isbn13.match(/^[0-9]+$/)) {
            return false;
        }
        var val =
            ( 1 * isbn13.substring( 0,  1)
            + 3 * isbn13.substring( 1,  2)
            + 1 * isbn13.substring( 2,  3)
            + 3 * isbn13.substring( 3,  4)
            + 1 * isbn13.substring( 4,  5)
            + 3 * isbn13.substring( 5,  6)
            + 1 * isbn13.substring( 6,  7)
            + 3 * isbn13.substring( 7,  8)
            + 1 * isbn13.substring( 8,  9)
            + 3 * isbn13.substring( 9, 10)
            + 1 * isbn13.substring(10, 11)
            + 3 * isbn13.substring(11, 12)) % 10;
        var chk = (val % 10 == 0 ? '0' : (10 - val) + '');
        if (isbn13.substring(12, 13) != chk) {
            return false;
        }
        return true;
    }

    /*
     * private擬似クラスのインスタンスを生成する。
     */
    ISBNUtil = new ISBNUtilP();
})();

PHP

実際に使い始めてから約1ヶ月だが、それなりに書けるようになってきたような気がする(謎)。

<?php
/**
 * ISBN(ISBN-10、ISBN-13)をチェックするユーティリティ。
 * 
 * @author hhelibex
 * @see http://ja.wikipedia.org/wiki/ISBN
 */
class ISBNUtil {
    /**
     * 正しいISBN-10であるかどうかをチェックする。
     * 
     * @param isbn10 "nnnnnnnnnc"の形式(nは0-9、cは0-9または'X')、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-10である場合はtrue、それ以外の場合はfalse
     */
    public static function isValidISBN10($isbn10) {
        if (!$isbn10) {
            return false;
        }
        $isbn10 = preg_replace('/[- ]/', '', $isbn10);
        if (strlen($isbn10) != 10) {
            return false;
        }
        if (!preg_match('/^[0-9]+[0-9X]$/', $isbn10)) {
            return false;
        }
        $val = 11 - (
            (10 * substr($isbn10, 0, 1)
            + 9 * substr($isbn10, 1, 1)
            + 8 * substr($isbn10, 2, 1)
            + 7 * substr($isbn10, 3, 1)
            + 6 * substr($isbn10, 4, 1)
            + 5 * substr($isbn10, 5, 1)
            + 4 * substr($isbn10, 6, 1)
            + 3 * substr($isbn10, 7, 1)
            + 2 * substr($isbn10, 8, 1)) % 11);
        $chk = ($val == 10 ? 'X' : ($val == 11 ? '0' : $val + ''));
        if (substr($isbn10, 9, 1) != $chk) {
            return false;
        }
        return true;
    }
    /**
     * 正しいISBN-13であるかどうかをチェックする。
     * 
     * @param isbn13 "nnnnnnnnnnnnn"の形式(nは0-9)、
     *            または、ハイフン(u002d)かスペース(u0020)が入った形式の文字列。
     * @return 正しいISBN-13である場合はtrue、それ以外の場合はfalse
     */
    public static function isValidISBN13($isbn13) {
        if (!$isbn13) {
            return false;
        }
        $isbn13 = preg_replace('/[- ]/', '', $isbn13);
        if (strlen($isbn13) != 13) {
            return false;
        }
        if (!preg_match('/^[0-9]+$/', $isbn13)) {
            return false;
        }
        $val =
            ( 1 * substr($isbn13,  0, 1)
            + 3 * substr($isbn13,  1, 1)
            + 1 * substr($isbn13,  2, 1)
            + 3 * substr($isbn13,  3, 1)
            + 1 * substr($isbn13,  4, 1)
            + 3 * substr($isbn13,  5, 1)
            + 1 * substr($isbn13,  6, 1)
            + 3 * substr($isbn13,  7, 1)
            + 1 * substr($isbn13,  8, 1)
            + 3 * substr($isbn13,  9, 1)
            + 1 * substr($isbn13, 10, 1)
            + 3 * substr($isbn13, 11, 1)) % 10;
        $chk = ($val % 10 == 0 ? '0' : (10 - $val) + '');
        if (substr($isbn13, 12, 13) != $chk) {
            return false;
        }
        return true;
    }
}
?>

SQL(DB2)版

とりあえず、テーブルの中にISBNが入っているものとして、それをチェックするためのSQL文。
SELECT文で書こうとすると、なかなか手続き的に書けなくて力尽きたので、DB2版のみ。

テーブル構成は次のものを想定。

CREATE TABLE isbn10s(
      id INTEGER GENERATED ALWAYS AS IDENTITY
    , isbn10 VARCHAR(32) NOT NULL) ;
CREATE TABLE isbn13s(
      id INTEGER GENERATED ALWAYS AS IDENTITY
    , isbn13 VARCHAR(32) NOT NULL) ;
-- ISBN-10
SELECT id, isbn10,
        CASE WHEN (CASE WHEN val = 10 THEN 'X' WHEN val = 11 THEN '0' ELSE CHAR(val) END) = check
                THEN 'valid' ELSE 'invalid' END AS isValid
    FROM TABLE(
        SELECT id, isbn10,
                CASE WHEN LENGTH(body) = 9
                        AND LENGTH(TRIM(TRANSLATE(body, '', '0123456789'))) = 0
                    THEN
                        11 - MOD(10 * INTEGER(SUBSTR(body, 1, 1))
                        + 9 * INTEGER(SUBSTR(body, 2, 1))
                        + 8 * INTEGER(SUBSTR(body, 3, 1))
                        + 7 * INTEGER(SUBSTR(body, 4, 1))
                        + 6 * INTEGER(SUBSTR(body, 5, 1))
                        + 5 * INTEGER(SUBSTR(body, 6, 1))
                        + 4 * INTEGER(SUBSTR(body, 7, 1))
                        + 3 * INTEGER(SUBSTR(body, 8, 1))
                        + 2 * INTEGER(SUBSTR(body, 9, 1)), 11)
                    ELSE -1 END AS val,
                CASE WHEN LENGTH(check) = 1
                        AND check IN ('0','1','2','3','4','5','6','7','8','9','X')
                    THEN check ELSE '' END AS check
            FROM TABLE(
                SELECT id, isbn10,
                        TRIM(SUBSTR(REPLACE(REPLACE(isbn10, '-', ''), ' ', ''), 1, 9)) AS body,
                        TRIM(SUBSTR(REPLACE(REPLACE(isbn10, '-', ''), ' ', ''), 10)) AS check
                    FROM isbn10s
            ) AS T1
    ) AS T2
    ORDER BY id
;
-- ISBN-13
SELECT id, isbn13,
        CASE WHEN (CASE WHEN MOD(val, 10) = 0 THEN '0' ELSE CHAR(10 - val) END) = check
                THEN 'valid' ELSE 'invalid' END AS isValid
    FROM TABLE(
        SELECT id, isbn13,
                CASE WHEN LENGTH(body) = 12
                        AND LENGTH(TRIM(TRANSLATE(body, '', '0123456789'))) = 0
                    THEN
                        MOD(
                          1 * INTEGER(SUBSTR(body,  1, 1))
                        + 3 * INTEGER(SUBSTR(body,  2, 1))
                        + 1 * INTEGER(SUBSTR(body,  3, 1))
                        + 3 * INTEGER(SUBSTR(body,  4, 1))
                        + 1 * INTEGER(SUBSTR(body,  5, 1))
                        + 3 * INTEGER(SUBSTR(body,  6, 1))
                        + 1 * INTEGER(SUBSTR(body,  7, 1))
                        + 3 * INTEGER(SUBSTR(body,  8, 1))
                        + 1 * INTEGER(SUBSTR(body,  9, 1))
                        + 3 * INTEGER(SUBSTR(body, 10, 1))
                        + 1 * INTEGER(SUBSTR(body, 11, 1))
                        + 3 * INTEGER(SUBSTR(body, 12, 1)), 10)
                    ELSE -1 END AS val,
                CASE WHEN LENGTH(check) = 1
                        AND check IN ('0','1','2','3','4','5','6','7','8','9')
                    THEN check ELSE '' END AS check
            FROM TABLE(
                SELECT id, isbn13,
                        TRIM(SUBSTR(REPLACE(REPLACE(isbn13, '-', ''), ' ', ''), 1, 12)) AS body,
                        TRIM(SUBSTR(REPLACE(REPLACE(isbn13, '-', ''), ' ', ''), 13)) AS check
                    FROM isbn13s
            ) AS T1
    ) AS T2
    ORDER BY id
;


続きはまたいつか(何時)。