15670430创建于 2020年12月28日历史提交
/*-------------------------------------------------------------------------
 *
 * isn.c
 *	  PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
 *
 * Author:	German Mendez Bravo (Kronuz)
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/isn/isn.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "knl/knl_variable.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* aux = NULL;
    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;
    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) == 0) { /* ISBN */
        type = ISBN;
    } else if (strncmp("977", buf, 3) == 0) { /* ISSN */
        type = ISSN;
    } else if (strncmp("9790", buf, 4) == 0) { /* ISMN */
        type = ISMN;
    } else if (strncmp("979", buf, 3) == 0) { /* 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 = NULL;
    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* aux = NULL;
    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;
    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) == 0) { /* ISBN -13 978-range */
        /* 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) == 0) { /* 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) == 0) { /* ISMN */
        /* The string should be in this form: 979-0?000000000-0" */
        type = ISMN;
        TABLE = ISMN_range;
        TABLE_index = ISMN_index;
    } else if (strncmp("979-", result, search) == 0) { /* ISBN-13 979-range */
        /* The string should be in this form: 979-??000000000-0" */
        type = ISBN;
        TABLE = ISBN_range_new;
        TABLE_index = ISBN_index_new;
    } 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) == 0)
                type = ISSN;
            else if (strncmp("978", buf + 3, 3) == 0)
                type = ISBN;
            else if (strncmp("9790", buf + 3, 4) == 0)
                type = ISMN;
            else if (strncmp("979", buf + 3, 3) == 0)
                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 = NULL;
    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 = NULL;
    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);
}