1 /*-------------------------------------------------------------------------
2 *
3 * isn.c
4 * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
5 *
6 * Author: German Mendez Bravo (Kronuz)
7 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
8 *
9 * IDENTIFICATION
10 * contrib/isn/isn.c
11 *
12 *-------------------------------------------------------------------------
13 */
14
15 #include "postgres.h"
16
17 #include "EAN13.h"
18 #include "ISBN.h"
19 #include "ISMN.h"
20 #include "ISSN.h"
21 #include "UPC.h"
22 #include "fmgr.h"
23 #include "isn.h"
24 #include "utils/builtins.h"
25
26 PG_MODULE_MAGIC;
27
28 #ifdef USE_ASSERT_CHECKING
29 #define ISN_DEBUG 1
30 #else
31 #define ISN_DEBUG 0
32 #endif
33
34 #define MAXEAN13LEN 18
35
36 enum isn_type
37 {
38 INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
39 };
40
41 static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
42
43 static bool g_weak = false;
44
45
46 /***********************************************************************
47 **
48 ** Routines for EAN13/UPC/ISxNs.
49 **
50 ** Note:
51 ** In this code, a normalized string is one that is known to be a valid
52 ** ISxN number containing only digits and hyphens and with enough space
53 ** to hold the full 13 digits plus the maximum of four hyphens.
54 ***********************************************************************/
55
56 /*----------------------------------------------------------
57 * Debugging routines.
58 *---------------------------------------------------------*/
59
60 /*
61 * Check if the table and its index is correct (just for debugging)
62 */
pg_attribute_unused()63 pg_attribute_unused()
64 static bool
65 check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
66 {
67 const char *aux1,
68 *aux2;
69 int a,
70 b,
71 x = 0,
72 y = -1,
73 i = 0,
74 j,
75 init = 0;
76
77 if (TABLE == NULL || TABLE_index == NULL)
78 return true;
79
80 while (TABLE[i][0] && TABLE[i][1])
81 {
82 aux1 = TABLE[i][0];
83 aux2 = TABLE[i][1];
84
85 /* must always start with a digit: */
86 if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
87 goto invalidtable;
88 a = *aux1 - '0';
89 b = *aux2 - '0';
90
91 /* must always have the same format and length: */
92 while (*aux1 && *aux2)
93 {
94 if (!(isdigit((unsigned char) *aux1) &&
95 isdigit((unsigned char) *aux2)) &&
96 (*aux1 != *aux2 || *aux1 != '-'))
97 goto invalidtable;
98 aux1++;
99 aux2++;
100 }
101 if (*aux1 != *aux2)
102 goto invalidtable;
103
104 /* found a new range */
105 if (a > y)
106 {
107 /* check current range in the index: */
108 for (j = x; j <= y; j++)
109 {
110 if (TABLE_index[j][0] != init)
111 goto invalidindex;
112 if (TABLE_index[j][1] != i - init)
113 goto invalidindex;
114 }
115 init = i;
116 x = a;
117 }
118
119 /* Always get the new limit */
120 y = b;
121 if (y < x)
122 goto invalidtable;
123 i++;
124 }
125
126 return true;
127
128 invalidtable:
129 elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
130 TABLE[i][0], TABLE[i][1], i);
131 return false;
132
133 invalidindex:
134 elog(DEBUG1, "index %d is invalid", j);
135 return false;
136 }
137
138 /*----------------------------------------------------------
139 * Formatting and conversion routines.
140 *---------------------------------------------------------*/
141
142 static unsigned
dehyphenate(char * bufO,char * bufI)143 dehyphenate(char *bufO, char *bufI)
144 {
145 unsigned ret = 0;
146
147 while (*bufI)
148 {
149 if (isdigit((unsigned char) *bufI))
150 {
151 *bufO++ = *bufI;
152 ret++;
153 }
154 bufI++;
155 }
156 *bufO = '\0';
157 return ret;
158 }
159
160 /*
161 * hyphenate --- Try to hyphenate, in-place, the string starting at bufI
162 * into bufO using the given hyphenation range TABLE.
163 * Assumes the input string to be used is of only digits.
164 *
165 * Returns the number of characters actually hyphenated.
166 */
167 static unsigned
hyphenate(char * bufO,char * bufI,const char * (* TABLE)[2],const unsigned TABLE_index[10][2])168 hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
169 {
170 unsigned ret = 0;
171 const char *ean_aux1,
172 *ean_aux2,
173 *ean_p;
174 char *firstdig,
175 *aux1,
176 *aux2;
177 unsigned search,
178 upper,
179 lower,
180 step;
181 bool ean_in1,
182 ean_in2;
183
184 /* just compress the string if no further hyphenation is required */
185 if (TABLE == NULL || TABLE_index == NULL)
186 {
187 while (*bufI)
188 {
189 *bufO++ = *bufI++;
190 ret++;
191 }
192 *bufO = '\0';
193 return (ret + 1);
194 }
195
196 /* add remaining hyphenations */
197
198 search = *bufI - '0';
199 upper = lower = TABLE_index[search][0];
200 upper += TABLE_index[search][1];
201 lower--;
202
203 step = (upper - lower) / 2;
204 if (step == 0)
205 return 0;
206 search = lower + step;
207
208 firstdig = bufI;
209 ean_in1 = ean_in2 = false;
210 ean_aux1 = TABLE[search][0];
211 ean_aux2 = TABLE[search][1];
212 do
213 {
214 if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2))
215 {
216 if (*firstdig > *ean_aux1)
217 ean_in1 = true;
218 if (*firstdig < *ean_aux2)
219 ean_in2 = true;
220 if (ean_in1 && ean_in2)
221 break;
222
223 firstdig++, ean_aux1++, ean_aux2++;
224 if (!(*ean_aux1 && *ean_aux2 && *firstdig))
225 break;
226 if (!isdigit((unsigned char) *ean_aux1))
227 ean_aux1++, ean_aux2++;
228 }
229 else
230 {
231 /*
232 * check in what direction we should go and move the pointer
233 * accordingly
234 */
235 if (*firstdig < *ean_aux1 && !ean_in1)
236 upper = search;
237 else
238 lower = search;
239
240 step = (upper - lower) / 2;
241 search = lower + step;
242
243 /* Initialize stuff again: */
244 firstdig = bufI;
245 ean_in1 = ean_in2 = false;
246 ean_aux1 = TABLE[search][0];
247 ean_aux2 = TABLE[search][1];
248 }
249 } while (step);
250
251 if (step)
252 {
253 aux1 = bufO;
254 aux2 = bufI;
255 ean_p = TABLE[search][0];
256 while (*ean_p && *aux2)
257 {
258 if (*ean_p++ != '-')
259 *aux1++ = *aux2++;
260 else
261 *aux1++ = '-';
262 ret++;
263 }
264 *aux1++ = '-';
265 *aux1 = *aux2; /* add a lookahead char */
266 return (ret + 1);
267 }
268 return ret;
269 }
270
271 /*
272 * weight_checkdig -- Receives a buffer with a normalized ISxN string number,
273 * and the length to weight.
274 *
275 * Returns the weight of the number (the check digit value, 0-10)
276 */
277 static unsigned
weight_checkdig(char * isn,unsigned size)278 weight_checkdig(char *isn, unsigned size)
279 {
280 unsigned weight = 0;
281
282 while (*isn && size > 1)
283 {
284 if (isdigit((unsigned char) *isn))
285 {
286 weight += size-- * (*isn - '0');
287 }
288 isn++;
289 }
290 weight = weight % 11;
291 if (weight != 0)
292 weight = 11 - weight;
293 return weight;
294 }
295
296
297 /*
298 * checkdig --- Receives a buffer with a normalized ISxN string number,
299 * and the length to check.
300 *
301 * Returns the check digit value (0-9)
302 */
303 static unsigned
checkdig(char * num,unsigned size)304 checkdig(char *num, unsigned size)
305 {
306 unsigned check = 0,
307 check3 = 0;
308 unsigned pos = 0;
309
310 if (*num == 'M')
311 { /* ISMN start with 'M' */
312 check3 = 3;
313 pos = 1;
314 }
315 while (*num && size > 1)
316 {
317 if (isdigit((unsigned char) *num))
318 {
319 if (pos++ % 2)
320 check3 += *num - '0';
321 else
322 check += *num - '0';
323 size--;
324 }
325 num++;
326 }
327 check = (check + 3 * check3) % 10;
328 if (check != 0)
329 check = 10 - check;
330 return check;
331 }
332
333 /*
334 * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number.
335 * This doesn't verify for a valid check digit.
336 *
337 * If errorOK is false, ereport a useful error message if the ean13 is bad.
338 * If errorOK is true, just return "false" for bad input.
339 */
340 static bool
ean2isn(ean13 ean,bool errorOK,ean13 * result,enum isn_type accept)341 ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
342 {
343 enum isn_type type = INVALID;
344
345 char buf[MAXEAN13LEN + 1];
346 char *aux;
347 unsigned digval;
348 unsigned search;
349 ean13 ret = ean;
350
351 ean >>= 1;
352 /* verify it's in the EAN13 range */
353 if (ean > UINT64CONST(9999999999999))
354 goto eantoobig;
355
356 /* convert the number */
357 search = 0;
358 aux = buf + 13;
359 *aux = '\0'; /* terminate string; aux points to last digit */
360 do
361 {
362 digval = (unsigned) (ean % 10); /* get the decimal value */
363 ean /= 10; /* get next digit */
364 *--aux = (char) (digval + '0'); /* convert to ascii and store */
365 } while (ean && search++ < 12);
366 while (search++ < 12)
367 *--aux = '0'; /* fill the remaining EAN13 with '0' */
368
369 /* find out the data type: */
370 if (strncmp("978", buf, 3) == 0)
371 { /* ISBN */
372 type = ISBN;
373 }
374 else if (strncmp("977", buf, 3) == 0)
375 { /* ISSN */
376 type = ISSN;
377 }
378 else if (strncmp("9790", buf, 4) == 0)
379 { /* ISMN */
380 type = ISMN;
381 }
382 else if (strncmp("979", buf, 3) == 0)
383 { /* ISBN-13 */
384 type = ISBN;
385 }
386 else if (*buf == '0')
387 { /* UPC */
388 type = UPC;
389 }
390 else
391 {
392 type = EAN13;
393 }
394 if (accept != ANY && accept != EAN13 && accept != type)
395 goto eanwrongtype;
396
397 *result = ret;
398 return true;
399
400 eanwrongtype:
401 if (!errorOK)
402 {
403 if (type != EAN13)
404 {
405 ereport(ERROR,
406 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
407 errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
408 isn_names[type], isn_names[accept], buf)));
409 }
410 else
411 {
412 ereport(ERROR,
413 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
414 errmsg("cannot cast %s to %s for number: \"%s\"",
415 isn_names[type], isn_names[accept], buf)));
416 }
417 }
418 return false;
419
420 eantoobig:
421 if (!errorOK)
422 {
423 char eanbuf[64];
424
425 /*
426 * Format the number separately to keep the machine-dependent format
427 * code out of the translatable message text
428 */
429 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
430 ereport(ERROR,
431 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
432 errmsg("value \"%s\" is out of range for %s type",
433 eanbuf, isn_names[type])));
434 }
435 return false;
436 }
437
438 /*
439 * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding
440 * UPC/ISxN string number. Assumes the input string is normalized.
441 */
442 static inline void
ean2ISBN(char * isn)443 ean2ISBN(char *isn)
444 {
445 char *aux;
446 unsigned check;
447
448 /*
449 * The number should come in this format: 978-0-000-00000-0 or may be an
450 * ISBN-13 number, 979-..., which does not have a short representation. Do
451 * the short output version if possible.
452 */
453 if (strncmp("978-", isn, 4) == 0)
454 {
455 /* Strip the first part and calculate the new check digit */
456 hyphenate(isn, isn + 4, NULL, NULL);
457 check = weight_checkdig(isn, 10);
458 aux = strchr(isn, '\0');
459 while (!isdigit((unsigned char) *--aux));
460 if (check == 10)
461 *aux = 'X';
462 else
463 *aux = check + '0';
464 }
465 }
466
467 static inline void
ean2ISMN(char * isn)468 ean2ISMN(char *isn)
469 {
470 /* the number should come in this format: 979-0-000-00000-0 */
471 /* Just strip the first part and change the first digit ('0') to 'M' */
472 hyphenate(isn, isn + 4, NULL, NULL);
473 isn[0] = 'M';
474 }
475
476 static inline void
ean2ISSN(char * isn)477 ean2ISSN(char *isn)
478 {
479 unsigned check;
480
481 /* the number should come in this format: 977-0000-000-00-0 */
482 /* Strip the first part, crop, and calculate the new check digit */
483 hyphenate(isn, isn + 4, NULL, NULL);
484 check = weight_checkdig(isn, 8);
485 if (check == 10)
486 isn[8] = 'X';
487 else
488 isn[8] = check + '0';
489 isn[9] = '\0';
490 }
491
492 static inline void
ean2UPC(char * isn)493 ean2UPC(char *isn)
494 {
495 /* the number should come in this format: 000-000000000-0 */
496 /* Strip the first part, crop, and dehyphenate */
497 dehyphenate(isn, isn + 1);
498 isn[12] = '\0';
499 }
500
501 /*
502 * ean2* --- Converts a string of digits into an ean13 number.
503 * Assumes the input string is a string with only digits
504 * on it, and that it's within the range of ean13.
505 *
506 * Returns the ean13 value of the string.
507 */
508 static ean13
str2ean(const char * num)509 str2ean(const char *num)
510 {
511 ean13 ean = 0; /* current ean */
512
513 while (*num)
514 {
515 if (isdigit((unsigned char) *num))
516 ean = 10 * ean + (*num - '0');
517 num++;
518 }
519 return (ean << 1); /* also give room to a flag */
520 }
521
522 /*
523 * ean2string --- Try to convert an ean13 number to a hyphenated string.
524 * Assumes there's enough space in result to hold
525 * the string (maximum MAXEAN13LEN+1 bytes)
526 * This doesn't verify for a valid check digit.
527 *
528 * If shortType is true, the returned string is in the old ISxN short format.
529 * If errorOK is false, ereport a useful error message if the string is bad.
530 * If errorOK is true, just return "false" for bad input.
531 */
532 static bool
ean2string(ean13 ean,bool errorOK,char * result,bool shortType)533 ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
534 {
535 const char *(*TABLE)[2];
536 const unsigned (*TABLE_index)[2];
537 enum isn_type type = INVALID;
538
539 char *aux;
540 unsigned digval;
541 unsigned search;
542 char valid = '\0'; /* was the number initially written with a
543 * valid check digit? */
544
545 TABLE_index = ISBN_index;
546
547 if ((ean & 1) != 0)
548 valid = '!';
549 ean >>= 1;
550 /* verify it's in the EAN13 range */
551 if (ean > UINT64CONST(9999999999999))
552 goto eantoobig;
553
554 /* convert the number */
555 search = 0;
556 aux = result + MAXEAN13LEN;
557 *aux = '\0'; /* terminate string; aux points to last digit */
558 *--aux = valid; /* append '!' for numbers with invalid but
559 * corrected check digit */
560 do
561 {
562 digval = (unsigned) (ean % 10); /* get the decimal value */
563 ean /= 10; /* get next digit */
564 *--aux = (char) (digval + '0'); /* convert to ascii and store */
565 if (search == 0)
566 *--aux = '-'; /* the check digit is always there */
567 } while (ean && search++ < 13);
568 while (search++ < 13)
569 *--aux = '0'; /* fill the remaining EAN13 with '0' */
570
571 /* The string should be in this form: ???DDDDDDDDDDDD-D" */
572 search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
573
574 /* verify it's a logically valid EAN13 */
575 if (search == 0)
576 {
577 search = hyphenate(result, result + 3, NULL, NULL);
578 goto okay;
579 }
580
581 /* find out what type of hyphenation is needed: */
582 if (strncmp("978-", result, search) == 0)
583 { /* ISBN -13 978-range */
584 /* The string should be in this form: 978-??000000000-0" */
585 type = ISBN;
586 TABLE = ISBN_range;
587 TABLE_index = ISBN_index;
588 }
589 else if (strncmp("977-", result, search) == 0)
590 { /* ISSN */
591 /* The string should be in this form: 977-??000000000-0" */
592 type = ISSN;
593 TABLE = ISSN_range;
594 TABLE_index = ISSN_index;
595 }
596 else if (strncmp("979-0", result, search + 1) == 0)
597 { /* ISMN */
598 /* The string should be in this form: 979-0?000000000-0" */
599 type = ISMN;
600 TABLE = ISMN_range;
601 TABLE_index = ISMN_index;
602 }
603 else if (strncmp("979-", result, search) == 0)
604 { /* ISBN-13 979-range */
605 /* The string should be in this form: 979-??000000000-0" */
606 type = ISBN;
607 TABLE = ISBN_range_new;
608 TABLE_index = ISBN_index_new;
609 }
610 else if (*result == '0')
611 { /* UPC */
612 /* The string should be in this form: 000-00000000000-0" */
613 type = UPC;
614 TABLE = UPC_range;
615 TABLE_index = UPC_index;
616 }
617 else
618 {
619 type = EAN13;
620 TABLE = NULL;
621 TABLE_index = NULL;
622 }
623
624 /* verify it's a logically valid EAN13/UPC/ISxN */
625 digval = search;
626 search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
627
628 /* verify it's a valid EAN13 */
629 if (search == 0)
630 {
631 search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
632 goto okay;
633 }
634
635 okay:
636 /* convert to the old short type: */
637 if (shortType)
638 switch (type)
639 {
640 case ISBN:
641 ean2ISBN(result);
642 break;
643 case ISMN:
644 ean2ISMN(result);
645 break;
646 case ISSN:
647 ean2ISSN(result);
648 break;
649 case UPC:
650 ean2UPC(result);
651 break;
652 default:
653 break;
654 }
655 return true;
656
657 eantoobig:
658 if (!errorOK)
659 {
660 char eanbuf[64];
661
662 /*
663 * Format the number separately to keep the machine-dependent format
664 * code out of the translatable message text
665 */
666 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
667 ereport(ERROR,
668 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
669 errmsg("value \"%s\" is out of range for %s type",
670 eanbuf, isn_names[type])));
671 }
672 return false;
673 }
674
675 /*
676 * string2ean --- try to parse a string into an ean13.
677 *
678 * If errorOK is false, ereport a useful error message if the string is bad.
679 * If errorOK is true, just return "false" for bad input.
680 *
681 * if the input string ends with '!' it will always be treated as invalid
682 * (even if the check digit is valid)
683 */
684 static bool
string2ean(const char * str,bool errorOK,ean13 * result,enum isn_type accept)685 string2ean(const char *str, bool errorOK, ean13 *result,
686 enum isn_type accept)
687 {
688 bool digit,
689 last;
690 char buf[17] = " ";
691 char *aux1 = buf + 3; /* leave space for the first part, in case
692 * it's needed */
693 const char *aux2 = str;
694 enum isn_type type = INVALID;
695 unsigned check = 0,
696 rcheck = (unsigned) -1;
697 unsigned length = 0;
698 bool magic = false,
699 valid = true;
700
701 /* recognize and validate the number: */
702 while (*aux2 && length <= 13)
703 {
704 last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the last character */
705 digit = (isdigit((unsigned char) *aux2) != 0); /* is current character
706 * a digit? */
707 if (*aux2 == '?' && last) /* automagically calculate check digit if
708 * it's '?' */
709 magic = digit = true;
710 if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
711 {
712 /* only ISMN can be here */
713 if (type != INVALID)
714 goto eaninvalid;
715 type = ISMN;
716 *aux1++ = 'M';
717 length++;
718 }
719 else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
720 {
721 /* only ISSN can be here */
722 if (type != INVALID)
723 goto eaninvalid;
724 type = ISSN;
725 *aux1++ = toupper((unsigned char) *aux2);
726 length++;
727 }
728 else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
729 {
730 /* only ISBN and ISMN can be here */
731 if (type != INVALID && type != ISMN)
732 goto eaninvalid;
733 if (type == INVALID)
734 type = ISBN; /* ISMN must start with 'M' */
735 *aux1++ = toupper((unsigned char) *aux2);
736 length++;
737 }
738 else if (length == 11 && digit && last)
739 {
740 /* only UPC can be here */
741 if (type != INVALID)
742 goto eaninvalid;
743 type = UPC;
744 *aux1++ = *aux2;
745 length++;
746 }
747 else if (*aux2 == '-' || *aux2 == ' ')
748 {
749 /* skip, we could validate but I think it's worthless */
750 }
751 else if (*aux2 == '!' && *(aux2 + 1) == '\0')
752 {
753 /* the invalid check digit suffix was found, set it */
754 if (!magic)
755 valid = false;
756 magic = true;
757 }
758 else if (!digit)
759 {
760 goto eaninvalid;
761 }
762 else
763 {
764 *aux1++ = *aux2;
765 if (++length > 13)
766 goto eantoobig;
767 }
768 aux2++;
769 }
770 *aux1 = '\0'; /* terminate the string */
771
772 /* find the current check digit value */
773 if (length == 13)
774 {
775 /* only EAN13 can be here */
776 if (type != INVALID)
777 goto eaninvalid;
778 type = EAN13;
779 check = buf[15] - '0';
780 }
781 else if (length == 12)
782 {
783 /* only UPC can be here */
784 if (type != UPC)
785 goto eaninvalid;
786 check = buf[14] - '0';
787 }
788 else if (length == 10)
789 {
790 if (type != ISBN && type != ISMN)
791 goto eaninvalid;
792 if (buf[12] == 'X')
793 check = 10;
794 else
795 check = buf[12] - '0';
796 }
797 else if (length == 8)
798 {
799 if (type != INVALID && type != ISSN)
800 goto eaninvalid;
801 type = ISSN;
802 if (buf[10] == 'X')
803 check = 10;
804 else
805 check = buf[10] - '0';
806 }
807 else
808 goto eaninvalid;
809
810 if (type == INVALID)
811 goto eaninvalid;
812
813 /* obtain the real check digit value, validate, and convert to ean13: */
814 if (accept == EAN13 && type != accept)
815 goto eanwrongtype;
816 if (accept != ANY && type != EAN13 && type != accept)
817 goto eanwrongtype;
818 switch (type)
819 {
820 case EAN13:
821 valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
822 /* now get the subtype of EAN13: */
823 if (buf[3] == '0')
824 type = UPC;
825 else if (strncmp("977", buf + 3, 3) == 0)
826 type = ISSN;
827 else if (strncmp("978", buf + 3, 3) == 0)
828 type = ISBN;
829 else if (strncmp("9790", buf + 3, 4) == 0)
830 type = ISMN;
831 else if (strncmp("979", buf + 3, 3) == 0)
832 type = ISBN;
833 if (accept != EAN13 && accept != ANY && type != accept)
834 goto eanwrongtype;
835 break;
836 case ISMN:
837 memcpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN
838 * it's only 9790 */
839 valid = (valid && ((rcheck = checkdig(buf, 13)) == check || magic));
840 break;
841 case ISBN:
842 memcpy(buf, "978", 3);
843 valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
844 break;
845 case ISSN:
846 memcpy(buf + 10, "00", 2); /* append 00 as the normal issue
847 * publication code */
848 memcpy(buf, "977", 3);
849 valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
850 break;
851 case UPC:
852 buf[2] = '0';
853 valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
854 default:
855 break;
856 }
857
858 /* fix the check digit: */
859 for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
860 aux1[12] = checkdig(aux1, 13) + '0';
861 aux1[13] = '\0';
862
863 if (!valid && !magic)
864 goto eanbadcheck;
865
866 *result = str2ean(aux1);
867 *result |= valid ? 0 : 1;
868 return true;
869
870 eanbadcheck:
871 if (g_weak)
872 { /* weak input mode is activated: */
873 /* set the "invalid-check-digit-on-input" flag */
874 *result = str2ean(aux1);
875 *result |= 1;
876 return true;
877 }
878
879 if (!errorOK)
880 {
881 if (rcheck == (unsigned) -1)
882 {
883 ereport(ERROR,
884 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
885 errmsg("invalid %s number: \"%s\"",
886 isn_names[accept], str)));
887 }
888 else
889 {
890 ereport(ERROR,
891 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
892 errmsg("invalid check digit for %s number: \"%s\", should be %c",
893 isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0'))));
894 }
895 }
896 return false;
897
898 eaninvalid:
899 if (!errorOK)
900 ereport(ERROR,
901 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
902 errmsg("invalid input syntax for %s number: \"%s\"",
903 isn_names[accept], str)));
904 return false;
905
906 eanwrongtype:
907 if (!errorOK)
908 ereport(ERROR,
909 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
910 errmsg("cannot cast %s to %s for number: \"%s\"",
911 isn_names[type], isn_names[accept], str)));
912 return false;
913
914 eantoobig:
915 if (!errorOK)
916 ereport(ERROR,
917 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
918 errmsg("value \"%s\" is out of range for %s type",
919 str, isn_names[accept])));
920 return false;
921 }
922
923 /*----------------------------------------------------------
924 * Exported routines.
925 *---------------------------------------------------------*/
926
927 void _PG_init(void);
928
929 void
_PG_init(void)930 _PG_init(void)
931 {
932 if (ISN_DEBUG)
933 {
934 if (!check_table(EAN13_range, EAN13_index))
935 elog(ERROR, "EAN13 failed check");
936 if (!check_table(ISBN_range, ISBN_index))
937 elog(ERROR, "ISBN failed check");
938 if (!check_table(ISMN_range, ISMN_index))
939 elog(ERROR, "ISMN failed check");
940 if (!check_table(ISSN_range, ISSN_index))
941 elog(ERROR, "ISSN failed check");
942 if (!check_table(UPC_range, UPC_index))
943 elog(ERROR, "UPC failed check");
944 }
945 }
946
947 /* isn_out
948 */
949 PG_FUNCTION_INFO_V1(isn_out);
950 Datum
isn_out(PG_FUNCTION_ARGS)951 isn_out(PG_FUNCTION_ARGS)
952 {
953 ean13 val = PG_GETARG_EAN13(0);
954 char *result;
955 char buf[MAXEAN13LEN + 1];
956
957 (void) ean2string(val, false, buf, true);
958
959 result = pstrdup(buf);
960 PG_RETURN_CSTRING(result);
961 }
962
963 /* ean13_out
964 */
965 PG_FUNCTION_INFO_V1(ean13_out);
966 Datum
ean13_out(PG_FUNCTION_ARGS)967 ean13_out(PG_FUNCTION_ARGS)
968 {
969 ean13 val = PG_GETARG_EAN13(0);
970 char *result;
971 char buf[MAXEAN13LEN + 1];
972
973 (void) ean2string(val, false, buf, false);
974
975 result = pstrdup(buf);
976 PG_RETURN_CSTRING(result);
977 }
978
979 /* ean13_in
980 */
981 PG_FUNCTION_INFO_V1(ean13_in);
982 Datum
ean13_in(PG_FUNCTION_ARGS)983 ean13_in(PG_FUNCTION_ARGS)
984 {
985 const char *str = PG_GETARG_CSTRING(0);
986 ean13 result;
987
988 (void) string2ean(str, false, &result, EAN13);
989 PG_RETURN_EAN13(result);
990 }
991
992 /* isbn_in
993 */
994 PG_FUNCTION_INFO_V1(isbn_in);
995 Datum
isbn_in(PG_FUNCTION_ARGS)996 isbn_in(PG_FUNCTION_ARGS)
997 {
998 const char *str = PG_GETARG_CSTRING(0);
999 ean13 result;
1000
1001 (void) string2ean(str, false, &result, ISBN);
1002 PG_RETURN_EAN13(result);
1003 }
1004
1005 /* ismn_in
1006 */
1007 PG_FUNCTION_INFO_V1(ismn_in);
1008 Datum
ismn_in(PG_FUNCTION_ARGS)1009 ismn_in(PG_FUNCTION_ARGS)
1010 {
1011 const char *str = PG_GETARG_CSTRING(0);
1012 ean13 result;
1013
1014 (void) string2ean(str, false, &result, ISMN);
1015 PG_RETURN_EAN13(result);
1016 }
1017
1018 /* issn_in
1019 */
1020 PG_FUNCTION_INFO_V1(issn_in);
1021 Datum
issn_in(PG_FUNCTION_ARGS)1022 issn_in(PG_FUNCTION_ARGS)
1023 {
1024 const char *str = PG_GETARG_CSTRING(0);
1025 ean13 result;
1026
1027 (void) string2ean(str, false, &result, ISSN);
1028 PG_RETURN_EAN13(result);
1029 }
1030
1031 /* upc_in
1032 */
1033 PG_FUNCTION_INFO_V1(upc_in);
1034 Datum
upc_in(PG_FUNCTION_ARGS)1035 upc_in(PG_FUNCTION_ARGS)
1036 {
1037 const char *str = PG_GETARG_CSTRING(0);
1038 ean13 result;
1039
1040 (void) string2ean(str, false, &result, UPC);
1041 PG_RETURN_EAN13(result);
1042 }
1043
1044 /* casting functions
1045 */
1046 PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
1047 Datum
isbn_cast_from_ean13(PG_FUNCTION_ARGS)1048 isbn_cast_from_ean13(PG_FUNCTION_ARGS)
1049 {
1050 ean13 val = PG_GETARG_EAN13(0);
1051 ean13 result;
1052
1053 (void) ean2isn(val, false, &result, ISBN);
1054
1055 PG_RETURN_EAN13(result);
1056 }
1057
1058 PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
1059 Datum
ismn_cast_from_ean13(PG_FUNCTION_ARGS)1060 ismn_cast_from_ean13(PG_FUNCTION_ARGS)
1061 {
1062 ean13 val = PG_GETARG_EAN13(0);
1063 ean13 result;
1064
1065 (void) ean2isn(val, false, &result, ISMN);
1066
1067 PG_RETURN_EAN13(result);
1068 }
1069
1070 PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
1071 Datum
issn_cast_from_ean13(PG_FUNCTION_ARGS)1072 issn_cast_from_ean13(PG_FUNCTION_ARGS)
1073 {
1074 ean13 val = PG_GETARG_EAN13(0);
1075 ean13 result;
1076
1077 (void) ean2isn(val, false, &result, ISSN);
1078
1079 PG_RETURN_EAN13(result);
1080 }
1081
1082 PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
1083 Datum
upc_cast_from_ean13(PG_FUNCTION_ARGS)1084 upc_cast_from_ean13(PG_FUNCTION_ARGS)
1085 {
1086 ean13 val = PG_GETARG_EAN13(0);
1087 ean13 result;
1088
1089 (void) ean2isn(val, false, &result, UPC);
1090
1091 PG_RETURN_EAN13(result);
1092 }
1093
1094
1095 /* is_valid - returns false if the "invalid-check-digit-on-input" is set
1096 */
1097 PG_FUNCTION_INFO_V1(is_valid);
1098 Datum
is_valid(PG_FUNCTION_ARGS)1099 is_valid(PG_FUNCTION_ARGS)
1100 {
1101 ean13 val = PG_GETARG_EAN13(0);
1102
1103 PG_RETURN_BOOL((val & 1) == 0);
1104 }
1105
1106 /* make_valid - unsets the "invalid-check-digit-on-input" flag
1107 */
1108 PG_FUNCTION_INFO_V1(make_valid);
1109 Datum
make_valid(PG_FUNCTION_ARGS)1110 make_valid(PG_FUNCTION_ARGS)
1111 {
1112 ean13 val = PG_GETARG_EAN13(0);
1113
1114 val &= ~((ean13) 1);
1115 PG_RETURN_EAN13(val);
1116 }
1117
1118 /* this function temporarily sets weak input flag
1119 * (to lose the strictness of check digit acceptance)
1120 * It's a helper function, not intended to be used!!
1121 */
1122 PG_FUNCTION_INFO_V1(accept_weak_input);
1123 Datum
accept_weak_input(PG_FUNCTION_ARGS)1124 accept_weak_input(PG_FUNCTION_ARGS)
1125 {
1126 #ifdef ISN_WEAK_MODE
1127 g_weak = PG_GETARG_BOOL(0);
1128 #else
1129 /* function has no effect */
1130 #endif /* ISN_WEAK_MODE */
1131 PG_RETURN_BOOL(g_weak);
1132 }
1133
1134 PG_FUNCTION_INFO_V1(weak_input_status);
1135 Datum
weak_input_status(PG_FUNCTION_ARGS)1136 weak_input_status(PG_FUNCTION_ARGS)
1137 {
1138 PG_RETURN_BOOL(g_weak);
1139 }
1140