diff --git a/contrib/isn/README.isn b/contrib/isn/README.isn index ef249582ba..22154266f0 100644 --- a/contrib/isn/README.isn +++ b/contrib/isn/README.isn @@ -1,6 +1,6 @@ -EAN13 - UPC - ISBN (books) - ISMN (music) - ISSN (serials) ----------------------------------------------------------- +-- EAN13 - UPC - ISBN (books) - ISMN (music) - ISSN (serials) +------------------------------------------------------------- Copyright Germán Méndez Bravo (Kronuz), 2004 - 2006 This module is released under the same BSD license as the rest of PostgreSQL. @@ -24,7 +24,7 @@ THIS MODULE IS PROVIDED "AS IS" AND WITHOUT ANY WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -Content of the Module +-- Content of the Module ------------------------------------------------- This directory contains definitions for a few PostgreSQL @@ -143,7 +143,7 @@ On success: (on failure, the functions 'ereport' the error) -Testing/Playing Functions +-- Testing/Playing Functions ------------------------------------------------- isn_weak(boolean) - Sets the weak input mode. This function is intended for testing use only! @@ -173,7 +173,7 @@ To work with invalid numbers, you can use two functions: + make_valid(), which validates an invalid number (deleting the invalid flag) + is_valid(), which checks for the invalid flag presence. -Examples of Use +-- Examples of Use ------------------------------------------------- --Using the types directly: select isbn('978-0-393-04002-9'); @@ -181,9 +181,12 @@ Examples of Use select issn('1436-4522'); --Casting types: --- note that you can't cast from ean13 to other type, thus the following --- will NOT work: select upc(ean13('0220356483481')); - select ean13(upc('220356483481')); +-- note that you can only cast from ean13 to other type when the casted +-- number would be valid in the realm of the casted type; +-- thus, the following will NOT work: select isbn(ean13('0220356483481')); +-- but these will: + select upc(ean13('0220356483481')); + select ean13(upc('220356483481')); --Create a table with a single column to hold ISBN numbers: create table test ( id isbn ); @@ -210,7 +213,7 @@ Examples of Use select isbn13(id) from test; -Contact +-- Contact ------------------------------------------------- Please suggestions or bug reports to kronuz at users.sourceforge.net diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c index 4c4aabb9f8..f403dca845 100644 --- a/contrib/isn/isn.c +++ b/contrib/isn/isn.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/contrib/isn/isn.c,v 1.1 2006/09/09 04:07:52 tgl Exp $ + * $PostgreSQL: pgsql/contrib/isn/isn.c,v 1.2 2006/09/10 20:45:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -31,7 +31,7 @@ PG_MODULE_MAGIC; enum isn_type { INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC }; -static const char *isn_names[] = { "ISN", "ISN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC" }; +static const char *isn_names[] = { "EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC" }; static bool g_weak = false; static bool g_initialized = false; @@ -43,11 +43,11 @@ static bool g_initialized = false; /*********************************************************************** ** - ** Routines for ISNs. + ** Routines for EAN13/UPC/ISxNs. ** ** Note: ** In this code, a normalized string is one that is known to be a valid - ** ISN number containing only digits and hyphens and with enough space + ** ISxN number containing only digits and hyphens and with enough space ** to hold the full 13 digits plus the maximum of four hyphens. ***********************************************************************/ @@ -217,7 +217,7 @@ unsigned hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsign } /* - * weight_checkdig -- Receives a buffer with a normalized ISN string number, + * weight_checkdig -- Receives a buffer with a normalized ISxN string number, * and the length to weight. * * Returns the weight of the number (the check digit value, 0-10) @@ -239,7 +239,7 @@ unsigned weight_checkdig(char *isn, unsigned size) /* - * checkdig --- Receives a buffer with a normalized ISN string number, + * checkdig --- Receives a buffer with a normalized ISxN string number, * and the length to check. * * Returns the check digit value (0-9) @@ -267,8 +267,94 @@ unsigned checkdig(char *num, unsigned size) } /* - * ean2isn --- Convert in-place a normalized EAN13 string to the corresponding - * ISN string number. Assumes the input string is normalized. + * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number. + * This doesn't verify for a valid check digit. + * + * If errorOK is false, ereport a useful error message if the ean13 is bad. + * If errorOK is true, just return "false" for bad input. + */ +static +bool ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept) +{ + enum isn_type type = INVALID; + + char buf[MAXEAN13LEN + 1]; + char *firstdig, *aux; + unsigned digval; + unsigned search; + ean13 ret = ean; + + ean >>= 1; + /* verify it's in the EAN13 range */ + if(ean > UINT64CONST(9999999999999)) + goto eantoobig; + + /* convert the number */ + search = 0; + firstdig = aux = buf + 13; + *aux = '\0'; /* terminate string; aux points to last digit */ + do { + digval = (unsigned)(ean % 10); /* get the decimal value */ + ean /= 10; /* get next digit */ + *--aux = (char)(digval + '0'); /* convert to ascii and store */ + } while(ean && search++<12); + while(search++<12) *--aux = '0'; /* fill the remaining EAN13 with '0' */ + + /* find out the data type: */ + if(!strncmp("978", buf, 3)) { /* ISBN */ + type = ISBN; + } else if(!strncmp("977", buf, 3)) { /* ISSN */ + type = ISSN; + } else if(!strncmp("9790", buf, 4)) { /* ISMN */ + type = ISMN; + } else if(!strncmp("979", buf, 3)) { /* ISBN-13 */ + type = ISBN; + } else if(*buf == '0') { /* UPC */ + type = UPC; + } else { + type = EAN13; + } + if(accept != ANY && accept != EAN13 && accept != type) goto eanwrongtype; + + *result = ret; + return true; + +eanwrongtype: + if(!errorOK) { + if(type!=EAN13) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"", + isn_names[type], isn_names[accept], buf))); + } else { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("cannot cast %s to %s for number: \"%s\"", + isn_names[type], isn_names[accept], buf))); + } + } + return false; + +eantoobig: + if(!errorOK) { + char eanbuf[64]; + + /* + * Format the number separately to keep the machine-dependent + * format code out of the translatable message text + */ + snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean); + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for %s type", + eanbuf, isn_names[type]))); + } + return false; +} + +/* + * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding + * UPC/ISxN string number. Assumes the input string is normalized. */ static inline void ean2ISBN(char *isn) @@ -325,7 +411,8 @@ ean13 str2ean(const char *num) { ean13 ean = 0; /* current ean */ while(*num) { - ean = 10 * ean + ((*num++) - '0'); + if(isdigit(*num)) ean = 10 * ean + (*num - '0'); + num++; } return (ean<<1); /* also give room to a flag */ } @@ -336,7 +423,7 @@ ean13 str2ean(const char *num) * the string (maximum MAXEAN13LEN+1 bytes) * This doesn't verify for a valid check digit. * - * If shortType is true, the returned string is in the old ISN short format. + * If shortType is true, the returned string is in the old ISxN short format. * If errorOK is false, ereport a useful error message if the string is bad. * If errorOK is true, just return "false" for bad input. */ @@ -369,8 +456,8 @@ bool ean2string(ean13 ean, bool errorOK, char *result, bool shortType) digval = (unsigned)(ean % 10); /* get the decimal value */ ean /= 10; /* get next digit */ *--aux = (char)(digval + '0'); /* convert to ascii and store */ - if(++search == 1) *--aux = '-'; /* the check digit is always there */ - } while(ean); + if(search == 0) *--aux = '-'; /* the check digit is always there */ + } while(ean && search++<13); while(search++<13) *--aux = '0'; /* fill the remaining EAN13 with '0' */ /* The string should be in this form: ???DDDDDDDDDDDD-D" */ @@ -409,7 +496,7 @@ bool ean2string(ean13 ean, bool errorOK, char *result, bool shortType) TABLE_index = NULL; } - /* verify it's a logically valid EAN13/ISN */ + /* verify it's a logically valid EAN13/UPC/ISxN */ digval = search; search = hyphenate(result+digval, result+digval+2, TABLE, TABLE_index); @@ -452,8 +539,8 @@ eantoobig: snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean); ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value \"%s\" is out of range for ISN type", - eanbuf))); + errmsg("value \"%s\" is out of range for %s type", + eanbuf, isn_names[type]))); } return false; } @@ -483,7 +570,7 @@ bool string2ean(const char *str, bool errorOK, ean13 *result, /* recognize and validate the number: */ while(*aux2 && length <= 13) { last = (*(aux2+1) == '!' || *(aux2+1) == '\0'); /* is the last character */ - digit = isdigit(*aux2); /* is current character a digit? */ + digit = (isdigit(*aux2)!=0); /* is current character a digit? */ if(*aux2=='?' && last) /* automagically calculate check digit if it's '?' */ magic = digit = true; if(length == 0 && (*aux2=='M' || *aux2=='m')) { @@ -583,19 +670,25 @@ bool string2ean(const char *str, bool errorOK, ean13 *result, break; } - if(!valid && !magic) goto eanbadcheck; - + /* fix the check digit: */ for(aux1 = buf; *aux1 && *aux1 <= ' '; aux1++); aux1[12] = checkdig(aux1, 13) + '0'; aux1[13] = '\0'; + if(!valid && !magic) goto eanbadcheck; + *result = str2ean(aux1); *result |= valid?0:1; - return true; eanbadcheck: - if(!g_weak) { + if(g_weak) { /* weak input mode is activated: */ + /* set the "invalid-check-digit-on-input" flag */ + *result = str2ean(aux1); + *result |= 1; + return true; + } + if(!errorOK) { if(rcheck == (unsigned)-1) { ereport(ERROR, @@ -610,30 +703,6 @@ eanbadcheck: } } return false; - } - - if(accept != EAN13 && accept != ANY && type != accept) goto eanwrongtype; - - /* fix the check digit: */ - for(aux1 = buf; *aux1 && *aux1 <= ' '; aux1++); - aux1[12] = checkdig(aux1, 13) + '0'; - aux1[13] = '\0'; - *result = str2ean(aux1); - - /* set the "invalid-check-digit-on-input" flag */ - *result |= 1; - - /* just warn about the error when there was a real check digit error: */ - if(check != rcheck) { - if(rcheck == (unsigned)-1) { - elog(WARNING, "invalid %s number: \"%s\"", - isn_names[accept], str); - } else { - elog(WARNING, "invalid check digit for %s number: \"%s\", should be %c", - isn_names[accept], str, (rcheck==10)?('X'):(rcheck+'0')); - } - } - return true; eaninvalid: if(!errorOK) @@ -647,8 +716,8 @@ eanwrongtype: if(!errorOK) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid %s type for number: \"%s\"", - isn_names[accept], str))); + errmsg("cannot cast %s to %s for number: \"%s\"", + isn_names[type], isn_names[accept], str))); return false; eantoobig: @@ -804,6 +873,55 @@ isn_cast_to_text(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(GET_TEXT(buf)); } +PG_FUNCTION_INFO_V1(isbn_cast_from_ean13); +Datum +isbn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISBN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(ismn_cast_from_ean13); +Datum +ismn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISMN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(issn_cast_from_ean13); +Datum +issn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISSN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(upc_cast_from_ean13); +Datum +upc_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, UPC); + + PG_RETURN_EAN13(result); +} + + PG_FUNCTION_INFO_V1(ean13_cast_from_text); Datum ean13_cast_from_text(PG_FUNCTION_ARGS) diff --git a/contrib/isn/isn.h b/contrib/isn/isn.h index d4369830d9..7581f884d5 100644 --- a/contrib/isn/isn.h +++ b/contrib/isn/isn.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/contrib/isn/isn.h,v 1.1 2006/09/09 04:07:52 tgl Exp $ + * $PostgreSQL: pgsql/contrib/isn/isn.h,v 1.2 2006/09/10 20:45:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,8 +27,8 @@ typedef uint64 ean13; #define EAN13_FORMAT UINT64_FORMAT -#define PG_GETARG_EAN13(n) PG_GETARG_INT64(n) -#define PG_RETURN_EAN13(x) PG_RETURN_INT64(x) +#define PG_GETARG_EAN13(n) PG_GETARG_INT64((int64)n) +#define PG_RETURN_EAN13(x) PG_RETURN_INT64((int64)x) extern Datum isn_out(PG_FUNCTION_ARGS); extern Datum ean13_out(PG_FUNCTION_ARGS); @@ -45,6 +45,10 @@ extern Datum isbn_cast_from_text(PG_FUNCTION_ARGS); extern Datum ismn_cast_from_text(PG_FUNCTION_ARGS); extern Datum issn_cast_from_text(PG_FUNCTION_ARGS); extern Datum upc_cast_from_text(PG_FUNCTION_ARGS); +extern Datum isbn_cast_from_ean13(PG_FUNCTION_ARGS); +extern Datum ismn_cast_from_ean13(PG_FUNCTION_ARGS); +extern Datum issn_cast_from_ean13(PG_FUNCTION_ARGS); +extern Datum upc_cast_from_ean13(PG_FUNCTION_ARGS); extern Datum is_valid(PG_FUNCTION_ARGS); extern Datum make_valid(PG_FUNCTION_ARGS); diff --git a/contrib/isn/isn.sql.in b/contrib/isn/isn.sql.in index d5ac63d135..891e98eeb3 100644 --- a/contrib/isn/isn.sql.in +++ b/contrib/isn/isn.sql.in @@ -2,7 +2,7 @@ -- PostgreSQL code for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) -- Copyright (c) 2004-2006, Germán Méndez Bravo (Kronuz) -- --- $PostgreSQL: pgsql/contrib/isn/isn.sql.in,v 1.1 2006/09/09 04:07:52 tgl Exp $ +-- $PostgreSQL: pgsql/contrib/isn/isn.sql.in,v 1.2 2006/09/10 20:45:17 tgl Exp $ -- -- Example: -- create table test ( id isbn ); @@ -2131,86 +2131,111 @@ CREATE OPERATOR CLASS upc_ops DEFAULT -- Type casts: -- --------------------------------------------------- +CREATE FUNCTION isbn13(ean13) +RETURNS isbn13 +AS 'MODULE_PATHNAME', 'isbn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION ismn13(ean13) +RETURNS ismn13 +AS 'MODULE_PATHNAME', 'ismn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION issn13(ean13) +RETURNS issn13 +AS 'MODULE_PATHNAME', 'issn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION isbn(ean13) +RETURNS isbn +AS 'MODULE_PATHNAME', 'isbn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION ismn(ean13) +RETURNS ismn +AS 'MODULE_PATHNAME', 'ismn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION issn(ean13) +RETURNS issn +AS 'MODULE_PATHNAME', 'issn_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; +CREATE FUNCTION upc(ean13) +RETURNS upc +AS 'MODULE_PATHNAME', 'upc_cast_from_ean13' +LANGUAGE 'C' IMMUTABLE STRICT; + + CREATE FUNCTION ean13(text) RETURNS ean13 AS 'MODULE_PATHNAME', 'ean13_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION isbn13(text) RETURNS isbn13 AS 'MODULE_PATHNAME', 'isbn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION ismn13(text) RETURNS ismn13 AS 'MODULE_PATHNAME', 'ismn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION issn13(text) RETURNS issn13 AS 'MODULE_PATHNAME', 'issn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION isbn(text) RETURNS isbn AS 'MODULE_PATHNAME', 'isbn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION ismn(text) RETURNS ismn AS 'MODULE_PATHNAME', 'ismn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION issn(text) RETURNS issn AS 'MODULE_PATHNAME', 'issn_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION upc(text) RETURNS upc AS 'MODULE_PATHNAME', 'upc_cast_from_text' LANGUAGE 'C' IMMUTABLE STRICT; + CREATE FUNCTION text(ean13) RETURNS text AS 'MODULE_PATHNAME', 'ean13_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(isbn13) RETURNS text AS 'MODULE_PATHNAME', 'ean13_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(ismn13) RETURNS text AS 'MODULE_PATHNAME', 'ean13_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(issn13) RETURNS text AS 'MODULE_PATHNAME', 'ean13_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(isbn) RETURNS text AS 'MODULE_PATHNAME', 'isn_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(ismn) RETURNS text AS 'MODULE_PATHNAME', 'isn_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(issn) RETURNS text AS 'MODULE_PATHNAME', 'isn_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; - CREATE FUNCTION text(upc) RETURNS text AS 'MODULE_PATHNAME', 'isn_cast_to_text' LANGUAGE 'C' IMMUTABLE STRICT; +CREATE CAST (ean13 AS isbn13) WITH FUNCTION isbn13(ean13); +CREATE CAST (ean13 AS isbn) WITH FUNCTION isbn(ean13); +CREATE CAST (ean13 AS ismn13) WITH FUNCTION ismn13(ean13); +CREATE CAST (ean13 AS ismn) WITH FUNCTION ismn(ean13); +CREATE CAST (ean13 AS issn13) WITH FUNCTION issn13(ean13); +CREATE CAST (ean13 AS issn) WITH FUNCTION issn(ean13); +CREATE CAST (ean13 AS upc) WITH FUNCTION upc(ean13); + CREATE CAST (isbn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (isbn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (ismn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; @@ -2218,6 +2243,7 @@ CREATE CAST (ismn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (issn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (issn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (upc AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; + CREATE CAST (isbn AS isbn13) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (isbn13 AS isbn) WITHOUT FUNCTION AS ASSIGNMENT; CREATE CAST (ismn AS ismn13) WITHOUT FUNCTION AS ASSIGNMENT;