summaryrefslogtreecommitdiff
path: root/contrib/isn/isn.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2006-09-09 04:07:52 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2006-09-09 04:07:52 +0000
commitdff84dc762bd225dbf69f5baa639560310053a74 (patch)
treeb38d627b5c18a99cd62b85e4d45a4b021177ba50 /contrib/isn/isn.c
parent42c17a6bb07f7400a14dffce687f14cc748fa064 (diff)
downloadpostgresql-dff84dc762bd225dbf69f5baa639560310053a74.tar.gz
Add contrib/isn module for ISBN/ISSN/EAN13/etc product numbers, and
remove the old isbn_issn module which is about to be obsoleted by EAN13. contrib/isn is by Germán Méndez Bravo. Our thanks to Garrett A. Wollman for having written the original isbn_issn module.
Diffstat (limited to 'contrib/isn/isn.c')
-rw-r--r--contrib/isn/isn.c910
1 files changed, 910 insertions, 0 deletions
diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c
new file mode 100644
index 0000000000..4c4aabb9f8
--- /dev/null
+++ b/contrib/isn/isn.c
@@ -0,0 +1,910 @@
+/*-------------------------------------------------------------------------
+ *
+ * isn.c
+ * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
+ *
+ * Copyright (c) 2004-2006, Germán Méndez Bravo (Kronuz)
+ * 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 $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+#include "isn.h"
+
+#include "EAN13.h"
+#include "ISBN.h"
+#include "ISMN.h"
+#include "ISSN.h"
+#include "UPC.h"
+
+#define MAXEAN13LEN 18
+
+enum isn_type { INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC };
+
+static const char *isn_names[] = { "ISN", "ISN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC" };
+
+static bool g_weak = false;
+static bool g_initialized = false;
+
+/* Macros for converting TEXT to and from c-string */
+#define GET_TEXT(cstrp) DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp)))
+#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
+
+
+/***********************************************************************
+ **
+ ** Routines for ISNs.
+ **
+ ** 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
+ ** to hold the full 13 digits plus the maximum of four hyphens.
+ ***********************************************************************/
+
+/*----------------------------------------------------------
+ * Debugging routines.
+ *---------------------------------------------------------*/
+
+/*
+ * Check if the table and its index is correct (just for debugging)
+ */
+#ifdef ISN_DEBUG
+static
+bool check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
+{
+ const char *aux1, *aux2;
+ int a, b, x=0, y=-1, i=0, j, cnt=0, init=0;
+
+ if(TABLE == NULL || TABLE_index == NULL) return true;
+
+ while(TABLE[i][0] && TABLE[i][1]) {
+ aux1 = TABLE[i][0];
+ aux2 = TABLE[i][1];
+
+ /* must always start with a digit: */
+ if(!isdigit(*aux1) || !isdigit(*aux2)) goto invalidtable;
+ a = *aux1 - '0';
+ b = *aux2 - '0';
+
+ /* must always have the same format and length: */
+ while(*aux1 && *aux2) {
+ if(!(isdigit(*aux1) && isdigit(*aux2)) && (*aux1!=*aux2 || *aux1 != '-'))
+ goto invalidtable;
+ aux1++;
+ aux2++;
+ }
+ if(*aux1!=*aux2) goto invalidtable;
+
+ /* found a new range */
+ if(a>y) {
+ /* check current range in the index: */
+ for(j=x;j<=y;j++) {
+ if(TABLE_index[j][0] != init) goto invalidindex;
+ if(TABLE_index[j][1] != i-init) goto invalidindex;
+ }
+ init = i;
+ x = a;
+ }
+
+ /* Always get the new limit */
+ y = b;
+ if(y<x) goto invalidtable;
+ i++;
+ }
+
+ return true;
+
+invalidtable:
+ elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
+ TABLE[i][0], TABLE[i][1], i);
+ return false;
+
+invalidindex:
+ elog(DEBUG1, "index %d is invalid", j);
+ return false;
+}
+#endif /* ISN_DEBUG */
+
+/*----------------------------------------------------------
+ * Formatting and conversion routines.
+ *---------------------------------------------------------*/
+
+static
+unsigned dehyphenate(char *bufO, char *bufI)
+{
+ unsigned ret = 0;
+ while(*bufI) {
+ if(isdigit(*bufI)) {
+ *bufO++ = *bufI;
+ ret++;
+ }
+ bufI++;
+ }
+ *bufO = '\0';
+ return ret;
+}
+
+/*
+ * hyphenate --- Try to hyphenate, in-place, the string starting at bufI
+ * into bufO using the given hyphenation range TABLE.
+ * Assumes the input string to be used is of only digits.
+ *
+ * Returns the number of characters acctually hyphenated.
+ */
+static
+unsigned hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
+{
+ unsigned ret = 0;
+ const char *ean_aux1, *ean_aux2, *ean_p;
+ char *firstdig, *aux1, *aux2;
+ unsigned search, upper, lower, step;
+ bool ean_in1, ean_in2;
+
+ /* just compress the string if no further hyphenation is required */
+ if(TABLE == NULL || TABLE_index == NULL) {
+ while(*bufI) {
+ *bufO++ = *bufI++;
+ ret++;
+ }
+ *bufO = '\0';
+ return (ret+1);
+ }
+
+ /* add remaining hyphenations */
+
+ search = *bufI - '0';
+ upper = lower = TABLE_index[search][0];
+ upper += TABLE_index[search][1];
+ lower--;
+
+ step = (upper - lower) / 2;
+ if(step == 0) return 0;
+ search = lower + step;
+
+ firstdig = bufI;
+ ean_in1 = ean_in2 = false;
+ ean_aux1 = TABLE[search][0];
+ ean_aux2 = TABLE[search][1];
+ do {
+ if((ean_in1 || *firstdig>=*ean_aux1) && (ean_in2 || *firstdig<=*ean_aux2)) {
+ if(*firstdig > *ean_aux1) ean_in1 = true;
+ if(*firstdig < *ean_aux2) ean_in2 = true;
+ if(ean_in1 && ean_in2) break;
+
+ firstdig++, ean_aux1++, ean_aux2++;
+ if(!(*ean_aux1 && *ean_aux2 && *firstdig)) break;
+ if(!isdigit(*ean_aux1)) ean_aux1++, ean_aux2++;
+ } else {
+ /* check in what direction we should go and move the pointer accordingly */
+ if(*firstdig < *ean_aux1 && !ean_in1) upper = search;
+ else lower = search;
+
+ step = (upper - lower) / 2;
+ search = lower + step;
+
+ /* Initialize stuff again: */
+ firstdig = bufI;
+ ean_in1 = ean_in2 = false;
+ ean_aux1 = TABLE[search][0];
+ ean_aux2 = TABLE[search][1];
+ }
+ } while(step);
+
+ if(step) {
+ aux1 = bufO;
+ aux2 = bufI;
+ ean_p = TABLE[search][0];
+ while(*ean_p && *aux2) {
+ if(*ean_p++!='-') *aux1++ = *aux2++;
+ else *aux1++ = '-';
+ ret++;
+ }
+ *aux1++='-';
+ *aux1 = *aux2; /* add a lookahead char */
+ return (ret+1);
+ }
+ return ret;
+}
+
+/*
+ * weight_checkdig -- Receives a buffer with a normalized ISN string number,
+ * and the length to weight.
+ *
+ * Returns the weight of the number (the check digit value, 0-10)
+ */
+static
+unsigned weight_checkdig(char *isn, unsigned size)
+{
+ unsigned weight = 0;
+ while(*isn && size>1) {
+ if(isdigit(*isn)) {
+ weight += size-- * (*isn - '0');
+ }
+ isn++;
+ }
+ weight = weight % 11;
+ if(weight != 0) weight = 11 - weight;
+ return weight;
+}
+
+
+/*
+ * checkdig --- Receives a buffer with a normalized ISN string number,
+ * and the length to check.
+ *
+ * Returns the check digit value (0-9)
+ */
+static
+unsigned checkdig(char *num, unsigned size)
+{
+ unsigned check=0, check3=0;
+ unsigned pos = 0;
+ if(*num == 'M') { /* ISMN start with 'M' */
+ check3 = 3;
+ pos = 1;
+ }
+ while(*num && size>1) {
+ if(isdigit(*num)) {
+ if(pos++%2) check3 += *num - '0';
+ else check += *num - '0';
+ size--;
+ }
+ num++;
+ }
+ check = (check + 3*check3) % 10;
+ if(check != 0) check = 10 - check;
+ return check;
+}
+
+/*
+ * ean2isn --- Convert in-place a normalized EAN13 string to the corresponding
+ * ISN string number. Assumes the input string is normalized.
+ */
+static inline
+void ean2ISBN(char *isn)
+{
+ char *aux;
+ unsigned check;
+ /* the number should come in this format: 978-0-000-00000-0 */
+ /* Strip the first part and calculate the new check digit */
+ hyphenate(isn, isn+4, NULL, NULL);
+ check = weight_checkdig(isn, 10);
+ aux = strchr(isn, '\0');
+ while(!isdigit(*--aux));
+ if(check == 10) *aux = 'X';
+ else *aux = check + '0';
+}
+static inline
+void ean2ISMN(char *isn)
+{
+ /* the number should come in this format: 979-0-000-00000-0 */
+ /* Just strip the first part and change the first digit ('0') to 'M' */
+ hyphenate(isn, isn+4, NULL, NULL);
+ isn[0] = 'M';
+}
+static inline
+void ean2ISSN(char *isn)
+{
+ unsigned check;
+ /* the number should come in this format: 977-0000-000-00-0 */
+ /* Strip the first part, crop, and calculate the new check digit */
+ hyphenate(isn, isn+4, NULL, NULL);
+ check = weight_checkdig(isn, 8);
+ if(check == 10) isn[8] = 'X';
+ else isn[8] = check + '0';
+ isn[9] = '\0';
+}
+static inline
+void ean2UPC(char *isn)
+{
+ /* the number should come in this format: 000-000000000-0 */
+ /* Strip the first part, crop, and dehyphenate */
+ dehyphenate(isn, isn+1);
+ isn[12] = '\0';
+}
+
+/*
+ * ean2* --- Converts a string of digits into an ean13 number.
+ * Assumes the input string is a string with only digits
+ * on it, and that it's within the range of ean13.
+ *
+ * Returns the ean13 value of the string.
+ */
+static
+ean13 str2ean(const char *num)
+{
+ ean13 ean = 0; /* current ean */
+ while(*num) {
+ ean = 10 * ean + ((*num++) - '0');
+ }
+ return (ean<<1); /* also give room to a flag */
+}
+
+/*
+ * ean2string --- Try to convert an ean13 number to an hyphenated string.
+ * Assumes there's enough space in result to hold
+ * 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 errorOK is false, ereport a useful error message if the string is bad.
+ * If errorOK is true, just return "false" for bad input.
+ */
+static
+bool ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
+{
+ const char *(*TABLE)[2];
+ const unsigned (*TABLE_index)[2];
+ enum isn_type type = INVALID;
+
+ char *firstdig, *aux;
+ unsigned digval;
+ unsigned search;
+ char valid = '\0'; /* was the number initially written with a valid check digit? */
+
+ TABLE_index = ISBN_index;
+
+ if((ean & 1)!=0) valid = '!';
+ ean >>= 1;
+ /* verify it's in the EAN13 range */
+ if(ean > UINT64CONST(9999999999999))
+ goto eantoobig;
+
+ /* convert the number */
+ search = 0;
+ firstdig = aux = result + MAXEAN13LEN;
+ *aux = '\0'; /* terminate string; aux points to last digit */
+ *--aux = valid; /* append '!' for numbers with invalid but corrected check digit */
+ do {
+ 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);
+ while(search++<13) *--aux = '0'; /* fill the remaining EAN13 with '0' */
+
+ /* The string should be in this form: ???DDDDDDDDDDDD-D" */
+ search = hyphenate(result, result+3, EAN13_range, EAN13_index);
+
+ /* verify it's a logically valid EAN13 */
+ if(search == 0) {
+ search = hyphenate(result, result+3, NULL, NULL);
+ goto okay;
+ }
+
+ /* find out what type of hyphenation is needed: */
+ if(!strncmp("978-", result, search)) { /* ISBN */
+ /* The string should be in this form: 978-??000000000-0" */
+ type = ISBN;
+ TABLE = ISBN_range;
+ TABLE_index = ISBN_index;
+ } else if(!strncmp("977-", result, search)) { /* ISSN */
+ /* The string should be in this form: 977-??000000000-0" */
+ type = ISSN;
+ TABLE = ISSN_range;
+ TABLE_index = ISSN_index;
+ } else if(!strncmp("979-0", result, search+1)) { /* ISMN */
+ /* The string should be in this form: 979-0?000000000-0" */
+ type = ISMN;
+ TABLE = ISMN_range;
+ TABLE_index = ISMN_index;
+ } else if(*result == '0') { /* UPC */
+ /* The string should be in this form: 000-00000000000-0" */
+ type = UPC;
+ TABLE = UPC_range;
+ TABLE_index = UPC_index;
+ } else {
+ type = EAN13;
+ TABLE = NULL;
+ TABLE_index = NULL;
+ }
+
+ /* verify it's a logically valid EAN13/ISN */
+ digval = search;
+ search = hyphenate(result+digval, result+digval+2, TABLE, TABLE_index);
+
+ /* verify it's a valid EAN13 */
+ if(search == 0) {
+ search = hyphenate(result+digval, result+digval+2, NULL, NULL);
+ goto okay;
+ }
+
+okay:
+ /* convert to the old short type: */
+ if(shortType)
+ switch(type) {
+ case ISBN:
+ ean2ISBN(result);
+ break;
+ case ISMN:
+ ean2ISMN(result);
+ break;
+ case ISSN:
+ ean2ISSN(result);
+ break;
+ case UPC:
+ ean2UPC(result);
+ break;
+ default:
+ break;
+ }
+ return true;
+
+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 ISN type",
+ eanbuf)));
+ }
+ return false;
+}
+
+/*
+ * string2ean --- try to parse a string into an ean13.
+ *
+ * If errorOK is false, ereport a useful error message if the string is bad.
+ * If errorOK is true, just return "false" for bad input.
+ *
+ * if the input string ends with '!' it will always be treated as invalid
+ * (even if the check digit is valid)
+ */
+static
+bool string2ean(const char *str, bool errorOK, ean13 *result,
+ enum isn_type accept)
+{
+ bool digit, last;
+ char buf[17] = " ";
+ char *aux1 = buf + 3; /* leave space for the first part, in case it's needed */
+ const char *aux2 = str;
+ enum isn_type type = INVALID;
+ unsigned check = 0, rcheck = (unsigned)-1;
+ unsigned length = 0;
+ bool magic = false, valid = true;
+
+ /* 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? */
+ if(*aux2=='?' && last) /* automagically calculate check digit if it's '?' */
+ magic = digit = true;
+ if(length == 0 && (*aux2=='M' || *aux2=='m')) {
+ /* only ISMN can be here */
+ if(type != INVALID) goto eaninvalid;
+ type = ISMN;
+ *aux1++ = 'M';
+ length++;
+ } else if(length == 7 && (digit || *aux2=='X' || *aux2=='x') && last) {
+ /* only ISSN can be here */
+ if(type != INVALID) goto eaninvalid;
+ type = ISSN;
+ *aux1++ = toupper(*aux2);
+ length++;
+ } else if(length == 9 && (digit || *aux2=='X' || *aux2=='x') && last) {
+ /* only ISBN and ISMN can be here */
+ if(type != INVALID && type != ISMN) goto eaninvalid;
+ if(type == INVALID) type = ISBN; /* ISMN must start with 'M' */
+ *aux1++ = toupper(*aux2);
+ length++;
+ } else if(length == 11 && digit && last) {
+ /* only UPC can be here */
+ if(type != INVALID) goto eaninvalid;
+ type = UPC;
+ *aux1++ = *aux2;
+ length++;
+ } else if(*aux2 == '-' || *aux2 == ' ') {
+ /* skip, we could validate but I think it's worthless */
+ } else if(*aux2 == '!' && *(aux2+1) == '\0') {
+ /* the invalid check digit sufix was found, set it */
+ if(!magic) valid = false;
+ magic = true;
+ } else if(!digit) {
+ goto eaninvalid;
+ } else {
+ *aux1++ = *aux2;
+ if(++length > 13) goto eantoobig;
+ }
+ aux2++;
+ }
+ *aux1 = '\0'; /* terminate the string */
+
+ /* find the current check digit value */
+ if(length == 13) {
+ /* only EAN13 can be here */
+ if(type != INVALID) goto eaninvalid;
+ type = EAN13;
+ check = buf[15]-'0';
+ } else if(length == 12) {
+ /* only UPC can be here */
+ if(type != UPC) goto eaninvalid;
+ check = buf[14]-'0';
+ } else if(length == 10) {
+ if(type != ISBN && type != ISMN) goto eaninvalid;
+ if(buf[12] == 'X') check = 10;
+ else check = buf[12]-'0';
+ } else if(length == 8) {
+ if(type != INVALID && type != ISSN) goto eaninvalid;
+ type = ISSN;
+ if(buf[10] == 'X') check = 10;
+ else check = buf[10]-'0';
+ } else goto eaninvalid;
+
+ if(type == INVALID) goto eaninvalid;
+
+ /* obtain the real check digit value, validate, and convert to ean13: */
+ if(accept == EAN13 && type != accept) goto eanwrongtype;
+ if(accept != ANY && type != EAN13 && type != accept) goto eanwrongtype;
+ switch(type) {
+ case EAN13:
+ valid = (valid && ((rcheck=checkdig(buf+3, 13)) == check || magic));
+ /* now get the subtype of EAN13: */
+ if(buf[3] == '0') type = UPC;
+ else if(!strncmp("977", buf+3, 3)) type = ISSN;
+ else if(!strncmp("978", buf+3, 3)) type = ISBN;
+ else if(!strncmp("9790", buf+3, 4)) type = ISMN;
+ else if(!strncmp("979", buf+3, 3)) type = ISBN;
+ if(accept != EAN13 && accept != ANY && type != accept) goto eanwrongtype;
+ break;
+ case ISMN:
+ strncpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN it's only 9790 */
+ valid = (valid && ((rcheck=checkdig(buf+3, 10)) == check || magic));
+ break;
+ case ISBN:
+ strncpy(buf, "978", 3);
+ valid = (valid && ((rcheck=weight_checkdig(buf+3, 10)) == check || magic));
+ break;
+ case ISSN:
+ strncpy(buf+10, "00", 2); /* append 00 as the normal issue publication code */
+ strncpy(buf, "977", 3);
+ valid = (valid && ((rcheck=weight_checkdig(buf+3, 8)) == check || magic));
+ break;
+ case UPC:
+ buf[2] = '0';
+ valid = (valid && ((rcheck=checkdig(buf+2, 13)) == check || magic));
+ default:
+ break;
+ }
+
+ if(!valid && !magic) goto eanbadcheck;
+
+ for(aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
+ aux1[12] = checkdig(aux1, 13) + '0';
+ aux1[13] = '\0';
+
+ *result = str2ean(aux1);
+ *result |= valid?0:1;
+
+ return true;
+
+eanbadcheck:
+ if(!g_weak) {
+ if(!errorOK) {
+ if(rcheck == (unsigned)-1) {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid %s number: \"%s\"",
+ isn_names[accept], str)));
+ } else {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid check digit for %s number: \"%s\", should be %c",
+ isn_names[accept], str, (rcheck==10)?('X'):(rcheck+'0'))));
+ }
+ }
+ 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)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for %s number: \"%s\"",
+ isn_names[accept], str)));
+ return false;
+
+eanwrongtype:
+ if(!errorOK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid %s type for number: \"%s\"",
+ isn_names[accept], str)));
+ return false;
+
+eantoobig:
+ if(!errorOK)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for %s type",
+ str, isn_names[accept])));
+ return false;
+}
+
+/*----------------------------------------------------------
+ * Exported routines.
+ *---------------------------------------------------------*/
+
+void initialize(void)
+{
+#ifdef ISN_DEBUG
+ if(!check_table(EAN13, EAN13_index))
+ elog(LOG, "EAN13 failed check");
+ if(!check_table(ISBN, ISBN_index))
+ elog(LOG, "ISBN failed check");
+ if(!check_table(ISMN, ISMN_index))
+ elog(LOG, "ISMN failed check");
+ if(!check_table(ISSN, ISSN_index))
+ elog(LOG, "ISSN failed check");
+ if(!check_table(UPC, UPC_index))
+ elog(LOG, "UPC failed check");
+#endif
+ g_initialized = true;
+}
+
+/* isn_out
+ */
+PG_FUNCTION_INFO_V1(isn_out);
+Datum
+isn_out(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ char *result;
+ char buf[MAXEAN13LEN + 1];
+
+ (void) ean2string(val, false, buf, true);
+
+ result = pstrdup(buf);
+ PG_RETURN_CSTRING(result);
+}
+
+/* ean13_out
+ */
+PG_FUNCTION_INFO_V1(ean13_out);
+Datum
+ean13_out(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ char *result;
+ char buf[MAXEAN13LEN + 1];
+
+ (void) ean2string(val, false, buf, false);
+
+ result = pstrdup(buf);
+ PG_RETURN_CSTRING(result);
+}
+
+/* ean13_in
+ */
+PG_FUNCTION_INFO_V1(ean13_in);
+Datum
+ean13_in(PG_FUNCTION_ARGS)
+{
+ const char *str = PG_GETARG_CSTRING(0);
+ ean13 result;
+
+ (void) string2ean(str, false, &result, EAN13);
+ PG_RETURN_EAN13(result);
+}
+
+/* isbn_in
+ */
+PG_FUNCTION_INFO_V1(isbn_in);
+Datum
+isbn_in(PG_FUNCTION_ARGS)
+{
+ const char *str = PG_GETARG_CSTRING(0);
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISBN);
+ PG_RETURN_EAN13(result);
+}
+
+/* ismn_in
+ */
+PG_FUNCTION_INFO_V1(ismn_in);
+Datum
+ismn_in(PG_FUNCTION_ARGS)
+{
+ const char *str = PG_GETARG_CSTRING(0);
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISMN);
+ PG_RETURN_EAN13(result);
+}
+
+/* issn_in
+ */
+PG_FUNCTION_INFO_V1(issn_in);
+Datum
+issn_in(PG_FUNCTION_ARGS)
+{
+ const char *str = PG_GETARG_CSTRING(0);
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISSN);
+ PG_RETURN_EAN13(result);
+}
+
+/* upc_in
+ */
+PG_FUNCTION_INFO_V1(upc_in);
+Datum
+upc_in(PG_FUNCTION_ARGS)
+{
+ const char *str = PG_GETARG_CSTRING(0);
+ ean13 result;
+
+ (void) string2ean(str, false, &result, UPC);
+ PG_RETURN_EAN13(result);
+}
+
+/* casting functions
+*/
+PG_FUNCTION_INFO_V1(ean13_cast_to_text);
+Datum
+ean13_cast_to_text(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ char buf[MAXEAN13LEN + 1];
+
+ (void) ean2string(val, false, buf, false);
+
+ PG_RETURN_TEXT_P(GET_TEXT(buf));
+}
+
+PG_FUNCTION_INFO_V1(isn_cast_to_text);
+Datum
+isn_cast_to_text(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ char buf[MAXEAN13LEN + 1];
+
+ (void) ean2string(val, false, buf, true);
+
+ PG_RETURN_TEXT_P(GET_TEXT(buf));
+}
+
+PG_FUNCTION_INFO_V1(ean13_cast_from_text);
+Datum
+ean13_cast_from_text(PG_FUNCTION_ARGS)
+{
+ const char *str = GET_STR(PG_GETARG_TEXT_P(0));
+ ean13 result;
+
+ (void) string2ean(str, false, &result, EAN13);
+ PG_RETURN_EAN13(result);
+}
+
+PG_FUNCTION_INFO_V1(isbn_cast_from_text);
+Datum
+isbn_cast_from_text(PG_FUNCTION_ARGS)
+{
+ const char *str = GET_STR(PG_GETARG_TEXT_P(0));
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISBN);
+ PG_RETURN_EAN13(result);
+}
+
+PG_FUNCTION_INFO_V1(ismn_cast_from_text);
+Datum
+ismn_cast_from_text(PG_FUNCTION_ARGS)
+{
+ const char *str = GET_STR(PG_GETARG_TEXT_P(0));
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISMN);
+ PG_RETURN_EAN13(result);
+}
+
+PG_FUNCTION_INFO_V1(issn_cast_from_text);
+Datum
+issn_cast_from_text(PG_FUNCTION_ARGS)
+{
+ const char *str = GET_STR(PG_GETARG_TEXT_P(0));
+ ean13 result;
+
+ (void) string2ean(str, false, &result, ISSN);
+ PG_RETURN_EAN13(result);
+}
+
+PG_FUNCTION_INFO_V1(upc_cast_from_text);
+Datum
+upc_cast_from_text(PG_FUNCTION_ARGS)
+{
+ const char *str = GET_STR(PG_GETARG_TEXT_P(0));
+ ean13 result;
+
+ (void) string2ean(str, false, &result, UPC);
+ PG_RETURN_EAN13(result);
+}
+
+/* is_valid - returns false if the "invalid-check-digit-on-input" is set
+ */
+PG_FUNCTION_INFO_V1(is_valid);
+Datum
+is_valid(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ PG_RETURN_BOOL((val & 1) == 0);
+}
+
+/* make_valid - unsets the "invalid-check-digit-on-input" flag
+ */
+PG_FUNCTION_INFO_V1(make_valid);
+Datum
+make_valid(PG_FUNCTION_ARGS)
+{
+ ean13 val = PG_GETARG_EAN13(0);
+ val &= ~((ean13) 1);
+ PG_RETURN_EAN13(val);
+}
+
+#ifdef ISN_WEAK_MODE
+/* this function temporarily sets weak input flag
+ * (to lose the strictness of check digit acceptance)
+ * It's a helper function, not intended to be used!!
+ */
+PG_FUNCTION_INFO_V1(accept_weak_input);
+Datum
+accept_weak_input(PG_FUNCTION_ARGS)
+{
+ g_weak = PG_GETARG_BOOL(0);
+ PG_RETURN_BOOL(g_weak);
+}
+#else
+PG_FUNCTION_INFO_V1(accept_weak_input);
+Datum
+accept_weak_input(PG_FUNCTION_ARGS)
+{
+ /* function has no effect */
+ PG_RETURN_BOOL(false);
+}
+#endif /* ISN_WEAK_MODE */
+
+PG_FUNCTION_INFO_V1(weak_input_status);
+Datum
+weak_input_status(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(g_weak);
+}