1 /*-------------------------------------------------------------------------
4 * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
6 * Author: German Mendez Bravo (Kronuz)
7 * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
12 *-------------------------------------------------------------------------
18 #include "utils/builtins.h"
29 #define MAXEAN13LEN 18
33 INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
36 static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
38 static bool g_weak = false;
39 static bool g_initialized = false;
42 /***********************************************************************
44 ** Routines for EAN13/UPC/ISxNs.
47 ** In this code, a normalized string is one that is known to be a valid
48 ** ISxN number containing only digits and hyphens and with enough space
49 ** to hold the full 13 digits plus the maximum of four hyphens.
50 ***********************************************************************/
52 /*----------------------------------------------------------
54 *---------------------------------------------------------*/
57 * Check if the table and its index is correct (just for debugging)
61 check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
74 if (TABLE == NULL || TABLE_index == NULL)
77 while (TABLE[i][0] && TABLE[i][1])
82 /* must always start with a digit: */
83 if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
88 /* must always have the same format and length: */
89 while (*aux1 && *aux2)
91 if (!(isdigit((unsigned char) *aux1) &&
92 isdigit((unsigned char) *aux2)) &&
93 (*aux1 != *aux2 || *aux1 != '-'))
101 /* found a new range */
104 /* check current range in the index: */
105 for (j = x; j <= y; j++)
107 if (TABLE_index[j][0] != init)
109 if (TABLE_index[j][1] != i - init)
116 /* Always get the new limit */
126 elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
127 TABLE[i][0], TABLE[i][1], i);
131 elog(DEBUG1, "index %d is invalid", j);
134 #endif /* ISN_DEBUG */
136 /*----------------------------------------------------------
137 * Formatting and conversion routines.
138 *---------------------------------------------------------*/
141 dehyphenate(char *bufO, char *bufI)
147 if (isdigit((unsigned char) *bufI))
159 * hyphenate --- Try to hyphenate, in-place, the string starting at bufI
160 * into bufO using the given hyphenation range TABLE.
161 * Assumes the input string to be used is of only digits.
163 * Returns the number of characters acctually hyphenated.
166 hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
169 const char *ean_aux1,
182 /* just compress the string if no further hyphenation is required */
183 if (TABLE == NULL || TABLE_index == NULL)
194 /* add remaining hyphenations */
196 search = *bufI - '0';
197 upper = lower = TABLE_index[search][0];
198 upper += TABLE_index[search][1];
201 step = (upper - lower) / 2;
204 search = lower + step;
207 ean_in1 = ean_in2 = false;
208 ean_aux1 = TABLE[search][0];
209 ean_aux2 = TABLE[search][1];
212 if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2))
214 if (*firstdig > *ean_aux1)
216 if (*firstdig < *ean_aux2)
218 if (ean_in1 && ean_in2)
221 firstdig++, ean_aux1++, ean_aux2++;
222 if (!(*ean_aux1 && *ean_aux2 && *firstdig))
224 if (!isdigit((unsigned char) *ean_aux1))
225 ean_aux1++, ean_aux2++;
230 * check in what direction we should go and move the pointer
233 if (*firstdig < *ean_aux1 && !ean_in1)
238 step = (upper - lower) / 2;
239 search = lower + step;
241 /* Initialize stuff again: */
243 ean_in1 = ean_in2 = false;
244 ean_aux1 = TABLE[search][0];
245 ean_aux2 = TABLE[search][1];
253 ean_p = TABLE[search][0];
254 while (*ean_p && *aux2)
263 *aux1 = *aux2; /* add a lookahead char */
270 * weight_checkdig -- Receives a buffer with a normalized ISxN string number,
271 * and the length to weight.
273 * Returns the weight of the number (the check digit value, 0-10)
276 weight_checkdig(char *isn, unsigned size)
280 while (*isn && size > 1)
282 if (isdigit((unsigned char) *isn))
284 weight += size-- * (*isn - '0');
288 weight = weight % 11;
290 weight = 11 - weight;
296 * checkdig --- Receives a buffer with a normalized ISxN string number,
297 * and the length to check.
299 * Returns the check digit value (0-9)
302 checkdig(char *num, unsigned size)
309 { /* ISMN start with 'M' */
313 while (*num && size > 1)
315 if (isdigit((unsigned char) *num))
318 check3 += *num - '0';
325 check = (check + 3 * check3) % 10;
332 * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number.
333 * This doesn't verify for a valid check digit.
335 * If errorOK is false, ereport a useful error message if the ean13 is bad.
336 * If errorOK is true, just return "false" for bad input.
339 ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
341 enum isn_type type = INVALID;
343 char buf[MAXEAN13LEN + 1];
350 /* verify it's in the EAN13 range */
351 if (ean > UINT64CONST(9999999999999))
354 /* convert the number */
357 *aux = '\0'; /* terminate string; aux points to last digit */
360 digval = (unsigned) (ean % 10); /* get the decimal value */
361 ean /= 10; /* get next digit */
362 *--aux = (char) (digval + '0'); /* convert to ascii and store */
363 } while (ean && search++ < 12);
364 while (search++ < 12)
365 *--aux = '0'; /* fill the remaining EAN13 with '0' */
367 /* find out the data type: */
368 if (!strncmp("978", buf, 3))
372 else if (!strncmp("977", buf, 3))
376 else if (!strncmp("9790", buf, 4))
380 else if (!strncmp("979", buf, 3))
384 else if (*buf == '0')
392 if (accept != ANY && accept != EAN13 && accept != type)
404 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
405 errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
406 isn_names[type], isn_names[accept], buf)));
411 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
412 errmsg("cannot cast %s to %s for number: \"%s\"",
413 isn_names[type], isn_names[accept], buf)));
424 * Format the number separately to keep the machine-dependent format
425 * code out of the translatable message text
427 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
429 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
430 errmsg("value \"%s\" is out of range for %s type",
431 eanbuf, isn_names[type])));
437 * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding
438 * UPC/ISxN string number. Assumes the input string is normalized.
446 /* the number should come in this format: 978-0-000-00000-0 */
447 /* Strip the first part and calculate the new check digit */
448 hyphenate(isn, isn + 4, NULL, NULL);
449 check = weight_checkdig(isn, 10);
450 aux = strchr(isn, '\0');
451 while (!isdigit((unsigned char) *--aux));
461 /* the number should come in this format: 979-0-000-00000-0 */
462 /* Just strip the first part and change the first digit ('0') to 'M' */
463 hyphenate(isn, isn + 4, NULL, NULL);
472 /* the number should come in this format: 977-0000-000-00-0 */
473 /* Strip the first part, crop, and calculate the new check digit */
474 hyphenate(isn, isn + 4, NULL, NULL);
475 check = weight_checkdig(isn, 8);
479 isn[8] = check + '0';
486 /* the number should come in this format: 000-000000000-0 */
487 /* Strip the first part, crop, and dehyphenate */
488 dehyphenate(isn, isn + 1);
493 * ean2* --- Converts a string of digits into an ean13 number.
494 * Assumes the input string is a string with only digits
495 * on it, and that it's within the range of ean13.
497 * Returns the ean13 value of the string.
500 str2ean(const char *num)
502 ean13 ean = 0; /* current ean */
506 if (isdigit((unsigned char) *num))
507 ean = 10 * ean + (*num - '0');
510 return (ean << 1); /* also give room to a flag */
514 * ean2string --- Try to convert an ean13 number to an hyphenated string.
515 * Assumes there's enough space in result to hold
516 * the string (maximum MAXEAN13LEN+1 bytes)
517 * This doesn't verify for a valid check digit.
519 * If shortType is true, the returned string is in the old ISxN short format.
520 * If errorOK is false, ereport a useful error message if the string is bad.
521 * If errorOK is true, just return "false" for bad input.
524 ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
526 const char *(*TABLE)[2];
527 const unsigned (*TABLE_index)[2];
528 enum isn_type type = INVALID;
533 char valid = '\0'; /* was the number initially written with a
534 * valid check digit? */
536 TABLE_index = ISBN_index;
541 /* verify it's in the EAN13 range */
542 if (ean > UINT64CONST(9999999999999))
545 /* convert the number */
547 aux = result + MAXEAN13LEN;
548 *aux = '\0'; /* terminate string; aux points to last digit */
549 *--aux = valid; /* append '!' for numbers with invalid but
550 * corrected check digit */
553 digval = (unsigned) (ean % 10); /* get the decimal value */
554 ean /= 10; /* get next digit */
555 *--aux = (char) (digval + '0'); /* convert to ascii and store */
557 *--aux = '-'; /* the check digit is always there */
558 } while (ean && search++ < 13);
559 while (search++ < 13)
560 *--aux = '0'; /* fill the remaining EAN13 with '0' */
562 /* The string should be in this form: ???DDDDDDDDDDDD-D" */
563 search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
565 /* verify it's a logically valid EAN13 */
568 search = hyphenate(result, result + 3, NULL, NULL);
572 /* find out what type of hyphenation is needed: */
573 if (!strncmp("978-", result, search))
574 { /* ISBN -13 978-range */
575 /* The string should be in this form: 978-??000000000-0" */
578 TABLE_index = ISBN_index;
580 else if (!strncmp("977-", result, search))
582 /* The string should be in this form: 977-??000000000-0" */
585 TABLE_index = ISSN_index;
587 else if (!strncmp("979-0", result, search + 1))
589 /* The string should be in this form: 979-0?000000000-0" */
592 TABLE_index = ISMN_index;
594 else if (!strncmp("979-", result, search))
595 { /* ISBN-13 979-range */
596 /* The string should be in this form: 979-??000000000-0" */
598 TABLE = ISBN_range_new;
599 TABLE_index = ISBN_index_new;
601 else if (*result == '0')
603 /* The string should be in this form: 000-00000000000-0" */
606 TABLE_index = UPC_index;
615 /* verify it's a logically valid EAN13/UPC/ISxN */
617 search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
619 /* verify it's a valid EAN13 */
622 search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
627 /* convert to the old short type: */
654 * Format the number separately to keep the machine-dependent format
655 * code out of the translatable message text
657 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
659 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
660 errmsg("value \"%s\" is out of range for %s type",
661 eanbuf, isn_names[type])));
667 * string2ean --- try to parse a string into an ean13.
669 * If errorOK is false, ereport a useful error message if the string is bad.
670 * If errorOK is true, just return "false" for bad input.
672 * if the input string ends with '!' it will always be treated as invalid
673 * (even if the check digit is valid)
676 string2ean(const char *str, bool errorOK, ean13 *result,
677 enum isn_type accept)
682 char *aux1 = buf + 3; /* leave space for the first part, in case
684 const char *aux2 = str;
685 enum isn_type type = INVALID;
687 rcheck = (unsigned) -1;
692 /* recognize and validate the number: */
693 while (*aux2 && length <= 13)
695 last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the last character */
696 digit = (isdigit((unsigned char) *aux2) != 0); /* is current character
698 if (*aux2 == '?' && last) /* automagically calculate check digit
700 magic = digit = true;
701 if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
703 /* only ISMN can be here */
710 else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
712 /* only ISSN can be here */
716 *aux1++ = toupper((unsigned char) *aux2);
719 else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
721 /* only ISBN and ISMN can be here */
722 if (type != INVALID && type != ISMN)
725 type = ISBN; /* ISMN must start with 'M' */
726 *aux1++ = toupper((unsigned char) *aux2);
729 else if (length == 11 && digit && last)
731 /* only UPC can be here */
738 else if (*aux2 == '-' || *aux2 == ' ')
740 /* skip, we could validate but I think it's worthless */
742 else if (*aux2 == '!' && *(aux2 + 1) == '\0')
744 /* the invalid check digit sufix was found, set it */
761 *aux1 = '\0'; /* terminate the string */
763 /* find the current check digit value */
766 /* only EAN13 can be here */
770 check = buf[15] - '0';
772 else if (length == 12)
774 /* only UPC can be here */
777 check = buf[14] - '0';
779 else if (length == 10)
781 if (type != ISBN && type != ISMN)
786 check = buf[12] - '0';
788 else if (length == 8)
790 if (type != INVALID && type != ISSN)
796 check = buf[10] - '0';
804 /* obtain the real check digit value, validate, and convert to ean13: */
805 if (accept == EAN13 && type != accept)
807 if (accept != ANY && type != EAN13 && type != accept)
812 valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
813 /* now get the subtype of EAN13: */
816 else if (!strncmp("977", buf + 3, 3))
818 else if (!strncmp("978", buf + 3, 3))
820 else if (!strncmp("9790", buf + 3, 4))
822 else if (!strncmp("979", buf + 3, 3))
824 if (accept != EAN13 && accept != ANY && type != accept)
828 strncpy(buf, "9790", 4); /* this isn't for sure yet, for now
829 * ISMN it's only 9790 */
830 valid = (valid && ((rcheck = checkdig(buf + 3, 10)) == check || magic));
833 strncpy(buf, "978", 3);
834 valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
837 strncpy(buf + 10, "00", 2); /* append 00 as the normal issue
838 * publication code */
839 strncpy(buf, "977", 3);
840 valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
844 valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
849 /* fix the check digit: */
850 for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
851 aux1[12] = checkdig(aux1, 13) + '0';
854 if (!valid && !magic)
857 *result = str2ean(aux1);
858 *result |= valid ? 0 : 1;
863 { /* weak input mode is activated: */
864 /* set the "invalid-check-digit-on-input" flag */
865 *result = str2ean(aux1);
872 if (rcheck == (unsigned) -1)
875 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
876 errmsg("invalid %s number: \"%s\"",
877 isn_names[accept], str)));
882 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
883 errmsg("invalid check digit for %s number: \"%s\", should be %c",
884 isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0'))));
892 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
893 errmsg("invalid input syntax for %s number: \"%s\"",
894 isn_names[accept], str)));
900 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
901 errmsg("cannot cast %s to %s for number: \"%s\"",
902 isn_names[type], isn_names[accept], str)));
908 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
909 errmsg("value \"%s\" is out of range for %s type",
910 str, isn_names[accept])));
914 /*----------------------------------------------------------
916 *---------------------------------------------------------*/
922 if (!check_table(EAN13, EAN13_index))
923 elog(LOG, "EAN13 failed check");
924 if (!check_table(ISBN, ISBN_index))
925 elog(LOG, "ISBN failed check");
926 if (!check_table(ISMN, ISMN_index))
927 elog(LOG, "ISMN failed check");
928 if (!check_table(ISSN, ISSN_index))
929 elog(LOG, "ISSN failed check");
930 if (!check_table(UPC, UPC_index))
931 elog(LOG, "UPC failed check");
933 g_initialized = true;
938 PG_FUNCTION_INFO_V1(isn_out);
940 isn_out(PG_FUNCTION_ARGS)
942 ean13 val = PG_GETARG_EAN13(0);
944 char buf[MAXEAN13LEN + 1];
946 (void) ean2string(val, false, buf, true);
948 result = pstrdup(buf);
949 PG_RETURN_CSTRING(result);
954 PG_FUNCTION_INFO_V1(ean13_out);
956 ean13_out(PG_FUNCTION_ARGS)
958 ean13 val = PG_GETARG_EAN13(0);
960 char buf[MAXEAN13LEN + 1];
962 (void) ean2string(val, false, buf, false);
964 result = pstrdup(buf);
965 PG_RETURN_CSTRING(result);
970 PG_FUNCTION_INFO_V1(ean13_in);
972 ean13_in(PG_FUNCTION_ARGS)
974 const char *str = PG_GETARG_CSTRING(0);
977 (void) string2ean(str, false, &result, EAN13);
978 PG_RETURN_EAN13(result);
983 PG_FUNCTION_INFO_V1(isbn_in);
985 isbn_in(PG_FUNCTION_ARGS)
987 const char *str = PG_GETARG_CSTRING(0);
990 (void) string2ean(str, false, &result, ISBN);
991 PG_RETURN_EAN13(result);
996 PG_FUNCTION_INFO_V1(ismn_in);
998 ismn_in(PG_FUNCTION_ARGS)
1000 const char *str = PG_GETARG_CSTRING(0);
1003 (void) string2ean(str, false, &result, ISMN);
1004 PG_RETURN_EAN13(result);
1009 PG_FUNCTION_INFO_V1(issn_in);
1011 issn_in(PG_FUNCTION_ARGS)
1013 const char *str = PG_GETARG_CSTRING(0);
1016 (void) string2ean(str, false, &result, ISSN);
1017 PG_RETURN_EAN13(result);
1022 PG_FUNCTION_INFO_V1(upc_in);
1024 upc_in(PG_FUNCTION_ARGS)
1026 const char *str = PG_GETARG_CSTRING(0);
1029 (void) string2ean(str, false, &result, UPC);
1030 PG_RETURN_EAN13(result);
1033 /* casting functions
1035 PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
1037 isbn_cast_from_ean13(PG_FUNCTION_ARGS)
1039 ean13 val = PG_GETARG_EAN13(0);
1042 (void) ean2isn(val, false, &result, ISBN);
1044 PG_RETURN_EAN13(result);
1047 PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
1049 ismn_cast_from_ean13(PG_FUNCTION_ARGS)
1051 ean13 val = PG_GETARG_EAN13(0);
1054 (void) ean2isn(val, false, &result, ISMN);
1056 PG_RETURN_EAN13(result);
1059 PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
1061 issn_cast_from_ean13(PG_FUNCTION_ARGS)
1063 ean13 val = PG_GETARG_EAN13(0);
1066 (void) ean2isn(val, false, &result, ISSN);
1068 PG_RETURN_EAN13(result);
1071 PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
1073 upc_cast_from_ean13(PG_FUNCTION_ARGS)
1075 ean13 val = PG_GETARG_EAN13(0);
1078 (void) ean2isn(val, false, &result, UPC);
1080 PG_RETURN_EAN13(result);
1084 /* is_valid - returns false if the "invalid-check-digit-on-input" is set
1086 PG_FUNCTION_INFO_V1(is_valid);
1088 is_valid(PG_FUNCTION_ARGS)
1090 ean13 val = PG_GETARG_EAN13(0);
1092 PG_RETURN_BOOL((val & 1) == 0);
1095 /* make_valid - unsets the "invalid-check-digit-on-input" flag
1097 PG_FUNCTION_INFO_V1(make_valid);
1099 make_valid(PG_FUNCTION_ARGS)
1101 ean13 val = PG_GETARG_EAN13(0);
1103 val &= ~((ean13) 1);
1104 PG_RETURN_EAN13(val);
1107 /* this function temporarily sets weak input flag
1108 * (to lose the strictness of check digit acceptance)
1109 * It's a helper function, not intended to be used!!
1111 PG_FUNCTION_INFO_V1(accept_weak_input);
1113 accept_weak_input(PG_FUNCTION_ARGS)
1115 #ifdef ISN_WEAK_MODE
1116 g_weak = PG_GETARG_BOOL(0);
1118 /* function has no effect */
1119 #endif /* ISN_WEAK_MODE */
1120 PG_RETURN_BOOL(g_weak);
1123 PG_FUNCTION_INFO_V1(weak_input_status);
1125 weak_input_status(PG_FUNCTION_ARGS)
1127 PG_RETURN_BOOL(g_weak);