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