/*------------------------------------------------------------------------- * * isn.c * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) * * Author: German Mendez Bravo (Kronuz) * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * * IDENTIFICATION * contrib/isn/isn.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "EAN13.h" #include "ISBN.h" #include "ISMN.h" #include "ISSN.h" #include "UPC.h" #include "fmgr.h" #include "isn.h" #include "utils/builtins.h" PG_MODULE_MAGIC; #ifdef USE_ASSERT_CHECKING #define ISN_DEBUG 1 #else #define ISN_DEBUG 0 #endif #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; /*********************************************************************** ** ** 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) */ pg_attribute_unused() 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, 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; } /*---------------------------------------------------------- * 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 actually 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; 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; unsigned check; /* * The number should come in this format: 978-0-000-00000-0 or may be an * ISBN-13 number, 979-..., which does not have a short representation. Do * the short output version if possible. */ if (strncmp("978-", isn, 4) == 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 a 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; 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. * * ereturn false with a useful error message if the string is bad. * Otherwise return true. * * 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, struct Node *escontext, 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 suffix 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: memcpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN * it's only 9790 */ valid = (valid && ((rcheck = checkdig(buf, 13)) == check || magic)); break; case ISBN: memcpy(buf, "978", 3); valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic)); break; case ISSN: memcpy(buf + 10, "00", 2); /* append 00 as the normal issue * publication code */ memcpy(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 (rcheck == (unsigned) -1) { ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid %s number: \"%s\"", isn_names[accept], str))); } else { ereturn(escontext, false, (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')))); } eaninvalid: ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for %s number: \"%s\"", isn_names[accept], str))); eanwrongtype: ereturn(escontext, false, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("cannot cast %s to %s for number: \"%s\"", isn_names[type], isn_names[accept], str))); eantoobig: ereturn(escontext, false, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value \"%s\" is out of range for %s type", str, isn_names[accept]))); } /*---------------------------------------------------------- * Exported routines. *---------------------------------------------------------*/ void _PG_init(void) { if (ISN_DEBUG) { if (!check_table(EAN13_range, EAN13_index)) elog(ERROR, "EAN13 failed check"); if (!check_table(ISBN_range, ISBN_index)) elog(ERROR, "ISBN failed check"); if (!check_table(ISMN_range, ISMN_index)) elog(ERROR, "ISMN failed check"); if (!check_table(ISSN_range, ISSN_index)) elog(ERROR, "ISSN failed check"); if (!check_table(UPC_range, UPC_index)) elog(ERROR, "UPC failed check"); } } /* 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; if (!string2ean(str, fcinfo->context, &result, EAN13)) PG_RETURN_NULL(); 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; if (!string2ean(str, fcinfo->context, &result, ISBN)) PG_RETURN_NULL(); 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; if (!string2ean(str, fcinfo->context, &result, ISMN)) PG_RETURN_NULL(); 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; if (!string2ean(str, fcinfo->context, &result, ISSN)) PG_RETURN_NULL(); 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; if (!string2ean(str, fcinfo->context, &result, UPC)) PG_RETURN_NULL(); 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); }