Skip to content
Snippets Groups Projects
Select Git revision
  • benchmark-tools
  • postgres-lambda
  • master default
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
23 results

isn.c

Blame
  • isn.c 24.16 KiB
    /*-------------------------------------------------------------------------
     *
     * isn.c
     *	  PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
     *
     * Author:  German Mendez Bravo (Kronuz)
     * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
     *
     * IDENTIFICATION
     *	  $PostgreSQL: pgsql/contrib/isn/isn.c,v 1.13 2010/02/05 04:34:51 momjian Exp $
     *
     *-------------------------------------------------------------------------
     */
    
    #include "postgres.h"
    
    #include "fmgr.h"
    #include "utils/builtins.h"
    
    #include "isn.h"
    #include "EAN13.h"
    #include "ISBN.h"
    #include "ISMN.h"
    #include "ISSN.h"
    #include "UPC.h"
    
    PG_MODULE_MAGIC;
    
    #define MAXEAN13LEN 18
    
    enum isn_type
    {
    	INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
    };
    
    static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
    
    static bool g_weak = false;
    static bool g_initialized = false;
    
    
    /***********************************************************************
     **
     **		Routines for EAN13/UPC/ISxNs.
     **
     ** Note:
     **  In this code, a normalized string is one that is known to be a valid
     **  ISxN 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((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
    			goto invalidtable;
    		a = *aux1 - '0';
    		b = *aux2 - '0';
    
    		/* must always have the same format and length: */
    		while (*aux1 && *aux2)
    		{
    			if (!(isdigit((unsigned char) *aux1) &&
    				  isdigit((unsigned char) *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((unsigned char) *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((unsigned char) *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 ISxN 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((unsigned char) *isn))
    		{
    			weight += size-- * (*isn - '0');
    		}
    		isn++;
    	}
    	weight = weight % 11;
    	if (weight != 0)
    		weight = 11 - weight;
    	return weight;
    }
    
    
    /*
     * checkdig --- Receives a buffer with a normalized ISxN 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((unsigned char) *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 --- 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)
    {
    	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((unsigned char) *--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)
    	{
    		if (isdigit((unsigned char) *num))
    			ean = 10 * ean + (*num - '0');
    		num++;
    	}
    	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 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.
     */
    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 == 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" */
    	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/UPC/ISxN */
    	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 %s type",
    						eanbuf, isn_names[type])));
    	}
    	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((unsigned char) *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'))
    		{
    			/* 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((unsigned char) *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((unsigned char) *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;
    	}
    
    	/* 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)
    	{							/* 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,
    					(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;
    
    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("cannot cast %s to %s for number: \"%s\"",
    						isn_names[type], 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(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);
    }
    
    
    /* 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);
    }
    
    /* 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)
    {
    #ifdef ISN_WEAK_MODE
    	g_weak = PG_GETARG_BOOL(0);
    #else
    	/* function has no effect */
    #endif   /* ISN_WEAK_MODE */
    	PG_RETURN_BOOL(g_weak);
    }
    
    PG_FUNCTION_INFO_V1(weak_input_status);
    Datum
    weak_input_status(PG_FUNCTION_ARGS)
    {
    	PG_RETURN_BOOL(g_weak);
    }