]> granicus.if.org Git - postgresql/blob - contrib/isn/isn.c
Update copyright for 2009.
[postgresql] / contrib / isn / isn.c
1 /*-------------------------------------------------------------------------
2  *
3  * isn.c
4  *        PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
5  *
6  * Copyright (c) 2004-2006, Germán Méndez Bravo (Kronuz)
7  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
8  *
9  * IDENTIFICATION
10  *        $PostgreSQL: pgsql/contrib/isn/isn.c,v 1.10 2009/01/01 17:23:32 momjian Exp $
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
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
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 acctually hyphenated.
164  */
165 static unsigned
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
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
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
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       *firstdig,
345                            *aux;
346         unsigned        digval;
347         unsigned        search;
348         ean13           ret = ean;
349
350         ean >>= 1;
351         /* verify it's in the EAN13 range */
352         if (ean > UINT64CONST(9999999999999))
353                 goto eantoobig;
354
355         /* convert the number */
356         search = 0;
357         firstdig = aux = buf + 13;
358         *aux = '\0';                            /* terminate string; aux points to last digit */
359         do
360         {
361                 digval = (unsigned) (ean % 10); /* get the decimal value */
362                 ean /= 10;                              /* get next digit */
363                 *--aux = (char) (digval + '0'); /* convert to ascii and store */
364         } while (ean && search++ < 12);
365         while (search++ < 12)
366                 *--aux = '0';                   /* fill the remaining EAN13 with '0' */
367
368         /* find out the data type: */
369         if (!strncmp("978", buf, 3))
370         {                                                       /* ISBN */
371                 type = ISBN;
372         }
373         else if (!strncmp("977", buf, 3))
374         {                                                       /* ISSN */
375                 type = ISSN;
376         }
377         else if (!strncmp("9790", buf, 4))
378         {                                                       /* ISMN */
379                 type = ISMN;
380         }
381         else if (!strncmp("979", buf, 3))
382         {                                                       /* ISBN-13 */
383                 type = ISBN;
384         }
385         else if (*buf == '0')
386         {                                                       /* UPC */
387                 type = UPC;
388         }
389         else
390         {
391                 type = EAN13;
392         }
393         if (accept != ANY && accept != EAN13 && accept != type)
394                 goto eanwrongtype;
395
396         *result = ret;
397         return true;
398
399 eanwrongtype:
400         if (!errorOK)
401         {
402                 if (type != EAN13)
403                 {
404                         ereport(ERROR,
405                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
406                                          errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
407                                                         isn_names[type], isn_names[accept], buf)));
408                 }
409                 else
410                 {
411                         ereport(ERROR,
412                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
413                                          errmsg("cannot cast %s to %s for number: \"%s\"",
414                                                         isn_names[type], isn_names[accept], buf)));
415                 }
416         }
417         return false;
418
419 eantoobig:
420         if (!errorOK)
421         {
422                 char            eanbuf[64];
423
424                 /*
425                  * Format the number separately to keep the machine-dependent format
426                  * code out of the translatable message text
427                  */
428                 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
429                 ereport(ERROR,
430                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
431                                  errmsg("value \"%s\" is out of range for %s type",
432                                                 eanbuf, isn_names[type])));
433         }
434         return false;
435 }
436
437 /*
438  * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding
439  *                                      UPC/ISxN string number. Assumes the input string is normalized.
440  */
441 static inline void
442 ean2ISBN(char *isn)
443 {
444         char       *aux;
445         unsigned        check;
446
447         /* the number should come in this format: 978-0-000-00000-0 */
448         /* Strip the first part and calculate the new check digit */
449         hyphenate(isn, isn + 4, NULL, NULL);
450         check = weight_checkdig(isn, 10);
451         aux = strchr(isn, '\0');
452         while (!isdigit((unsigned char) *--aux));
453         if (check == 10)
454                 *aux = 'X';
455         else
456                 *aux = check + '0';
457 }
458
459 static inline void
460 ean2ISMN(char *isn)
461 {
462         /* the number should come in this format: 979-0-000-00000-0 */
463         /* Just strip the first part and change the first digit ('0') to 'M' */
464         hyphenate(isn, isn + 4, NULL, NULL);
465         isn[0] = 'M';
466 }
467
468 static inline void
469 ean2ISSN(char *isn)
470 {
471         unsigned        check;
472
473         /* the number should come in this format: 977-0000-000-00-0 */
474         /* Strip the first part, crop, and calculate the new check digit */
475         hyphenate(isn, isn + 4, NULL, NULL);
476         check = weight_checkdig(isn, 8);
477         if (check == 10)
478                 isn[8] = 'X';
479         else
480                 isn[8] = check + '0';
481         isn[9] = '\0';
482 }
483
484 static inline void
485 ean2UPC(char *isn)
486 {
487         /* the number should come in this format: 000-000000000-0 */
488         /* Strip the first part, crop, and dehyphenate */
489         dehyphenate(isn, isn + 1);
490         isn[12] = '\0';
491 }
492
493 /*
494  * ean2* --- Converts a string of digits into an ean13 number.
495  *                        Assumes the input string is a string with only digits
496  *                        on it, and that it's within the range of ean13.
497  *
498  * Returns the ean13 value of the string.
499  */
500 static ean13
501 str2ean(const char *num)
502 {
503         ean13           ean = 0;                /* current ean */
504
505         while (*num)
506         {
507                 if (isdigit((unsigned char) *num))
508                         ean = 10 * ean + (*num - '0');
509                 num++;
510         }
511         return (ean << 1);                      /* also give room to a flag */
512 }
513
514 /*
515  * ean2string --- Try to convert an ean13 number to an hyphenated string.
516  *                                Assumes there's enough space in result to hold
517  *                                the string (maximum MAXEAN13LEN+1 bytes)
518  *                                This doesn't verify for a valid check digit.
519  *
520  * If shortType is true, the returned string is in the old ISxN short format.
521  * If errorOK is false, ereport a useful error message if the string is bad.
522  * If errorOK is true, just return "false" for bad input.
523  */
524 static bool
525 ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
526 {
527         const char *(*TABLE)[2];
528         const unsigned (*TABLE_index)[2];
529         enum isn_type type = INVALID;
530
531         char       *firstdig,
532                            *aux;
533         unsigned        digval;
534         unsigned        search;
535         char            valid = '\0';   /* was the number initially written with a
536                                                                  * valid check digit? */
537
538         TABLE_index = ISBN_index;
539
540         if ((ean & 1) != 0)
541                 valid = '!';
542         ean >>= 1;
543         /* verify it's in the EAN13 range */
544         if (ean > UINT64CONST(9999999999999))
545                 goto eantoobig;
546
547         /* convert the number */
548         search = 0;
549         firstdig = aux = result + MAXEAN13LEN;
550         *aux = '\0';                            /* terminate string; aux points to last digit */
551         *--aux = valid;                         /* append '!' for numbers with invalid but
552                                                                  * corrected check digit */
553         do
554         {
555                 digval = (unsigned) (ean % 10); /* get the decimal value */
556                 ean /= 10;                              /* get next digit */
557                 *--aux = (char) (digval + '0'); /* convert to ascii and store */
558                 if (search == 0)
559                         *--aux = '-';           /* the check digit is always there */
560         } while (ean && search++ < 13);
561         while (search++ < 13)
562                 *--aux = '0';                   /* fill the remaining EAN13 with '0' */
563
564         /* The string should be in this form: ???DDDDDDDDDDDD-D" */
565         search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
566
567         /* verify it's a logically valid EAN13 */
568         if (search == 0)
569         {
570                 search = hyphenate(result, result + 3, NULL, NULL);
571                 goto okay;
572         }
573
574         /* find out what type of hyphenation is needed: */
575         if (!strncmp("978-", result, search))
576         {                                                       /* ISBN */
577                 /* The string should be in this form: 978-??000000000-0" */
578                 type = ISBN;
579                 TABLE = ISBN_range;
580                 TABLE_index = ISBN_index;
581         }
582         else if (!strncmp("977-", result, search))
583         {                                                       /* ISSN */
584                 /* The string should be in this form: 977-??000000000-0" */
585                 type = ISSN;
586                 TABLE = ISSN_range;
587                 TABLE_index = ISSN_index;
588         }
589         else if (!strncmp("979-0", result, search + 1))
590         {                                                       /* ISMN */
591                 /* The string should be in this form: 979-0?000000000-0" */
592                 type = ISMN;
593                 TABLE = ISMN_range;
594                 TABLE_index = ISMN_index;
595         }
596         else if (*result == '0')
597         {                                                       /* UPC */
598                 /* The string should be in this form: 000-00000000000-0" */
599                 type = UPC;
600                 TABLE = UPC_range;
601                 TABLE_index = UPC_index;
602         }
603         else
604         {
605                 type = EAN13;
606                 TABLE = NULL;
607                 TABLE_index = NULL;
608         }
609
610         /* verify it's a logically valid EAN13/UPC/ISxN */
611         digval = search;
612         search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
613
614         /* verify it's a valid EAN13 */
615         if (search == 0)
616         {
617                 search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
618                 goto okay;
619         }
620
621 okay:
622         /* convert to the old short type: */
623         if (shortType)
624                 switch (type)
625                 {
626                         case ISBN:
627                                 ean2ISBN(result);
628                                 break;
629                         case ISMN:
630                                 ean2ISMN(result);
631                                 break;
632                         case ISSN:
633                                 ean2ISSN(result);
634                                 break;
635                         case UPC:
636                                 ean2UPC(result);
637                                 break;
638                         default:
639                                 break;
640                 }
641         return true;
642
643 eantoobig:
644         if (!errorOK)
645         {
646                 char            eanbuf[64];
647
648                 /*
649                  * Format the number separately to keep the machine-dependent format
650                  * code out of the translatable message text
651                  */
652                 snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
653                 ereport(ERROR,
654                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
655                                  errmsg("value \"%s\" is out of range for %s type",
656                                                 eanbuf, isn_names[type])));
657         }
658         return false;
659 }
660
661 /*
662  * string2ean --- try to parse a string into an ean13.
663  *
664  * If errorOK is false, ereport a useful error message if the string is bad.
665  * If errorOK is true, just return "false" for bad input.
666  *
667  * if the input string ends with '!' it will always be treated as invalid
668  * (even if the check digit is valid)
669  */
670 static bool
671 string2ean(const char *str, bool errorOK, ean13 * result,
672                    enum isn_type accept)
673 {
674         bool            digit,
675                                 last;
676         char            buf[17] = "                ";
677         char       *aux1 = buf + 3; /* leave space for the first part, in case
678                                                                  * it's needed */
679         const char *aux2 = str;
680         enum isn_type type = INVALID;
681         unsigned        check = 0,
682                                 rcheck = (unsigned) -1;
683         unsigned        length = 0;
684         bool            magic = false,
685                                 valid = true;
686
687         /* recognize and validate the number: */
688         while (*aux2 && length <= 13)
689         {
690                 last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0');             /* is the last character */
691                 digit = (isdigit((unsigned char) *aux2) != 0);  /* is current character
692                                                                                                                  * a digit? */
693                 if (*aux2 == '?' && last)               /* automagically calculate check digit
694                                                                                  * if it's '?' */
695                         magic = digit = true;
696                 if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
697                 {
698                         /* only ISMN can be here */
699                         if (type != INVALID)
700                                 goto eaninvalid;
701                         type = ISMN;
702                         *aux1++ = 'M';
703                         length++;
704                 }
705                 else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
706                 {
707                         /* only ISSN can be here */
708                         if (type != INVALID)
709                                 goto eaninvalid;
710                         type = ISSN;
711                         *aux1++ = toupper((unsigned char) *aux2);
712                         length++;
713                 }
714                 else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
715                 {
716                         /* only ISBN and ISMN can be here */
717                         if (type != INVALID && type != ISMN)
718                                 goto eaninvalid;
719                         if (type == INVALID)
720                                 type = ISBN;    /* ISMN must start with 'M' */
721                         *aux1++ = toupper((unsigned char) *aux2);
722                         length++;
723                 }
724                 else if (length == 11 && digit && last)
725                 {
726                         /* only UPC can be here */
727                         if (type != INVALID)
728                                 goto eaninvalid;
729                         type = UPC;
730                         *aux1++ = *aux2;
731                         length++;
732                 }
733                 else if (*aux2 == '-' || *aux2 == ' ')
734                 {
735                         /* skip, we could validate but I think it's worthless */
736                 }
737                 else if (*aux2 == '!' && *(aux2 + 1) == '\0')
738                 {
739                         /* the invalid check digit sufix was found, set it */
740                         if (!magic)
741                                 valid = false;
742                         magic = true;
743                 }
744                 else if (!digit)
745                 {
746                         goto eaninvalid;
747                 }
748                 else
749                 {
750                         *aux1++ = *aux2;
751                         if (++length > 13)
752                                 goto eantoobig;
753                 }
754                 aux2++;
755         }
756         *aux1 = '\0';                           /* terminate the string */
757
758         /* find the current check digit value */
759         if (length == 13)
760         {
761                 /* only EAN13 can be here */
762                 if (type != INVALID)
763                         goto eaninvalid;
764                 type = EAN13;
765                 check = buf[15] - '0';
766         }
767         else if (length == 12)
768         {
769                 /* only UPC can be here */
770                 if (type != UPC)
771                         goto eaninvalid;
772                 check = buf[14] - '0';
773         }
774         else if (length == 10)
775         {
776                 if (type != ISBN && type != ISMN)
777                         goto eaninvalid;
778                 if (buf[12] == 'X')
779                         check = 10;
780                 else
781                         check = buf[12] - '0';
782         }
783         else if (length == 8)
784         {
785                 if (type != INVALID && type != ISSN)
786                         goto eaninvalid;
787                 type = ISSN;
788                 if (buf[10] == 'X')
789                         check = 10;
790                 else
791                         check = buf[10] - '0';
792         }
793         else
794                 goto eaninvalid;
795
796         if (type == INVALID)
797                 goto eaninvalid;
798
799         /* obtain the real check digit value, validate, and convert to ean13: */
800         if (accept == EAN13 && type != accept)
801                 goto eanwrongtype;
802         if (accept != ANY && type != EAN13 && type != accept)
803                 goto eanwrongtype;
804         switch (type)
805         {
806                 case EAN13:
807                         valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
808                         /* now get the subtype of EAN13: */
809                         if (buf[3] == '0')
810                                 type = UPC;
811                         else if (!strncmp("977", buf + 3, 3))
812                                 type = ISSN;
813                         else if (!strncmp("978", buf + 3, 3))
814                                 type = ISBN;
815                         else if (!strncmp("9790", buf + 3, 4))
816                                 type = ISMN;
817                         else if (!strncmp("979", buf + 3, 3))
818                                 type = ISBN;
819                         if (accept != EAN13 && accept != ANY && type != accept)
820                                 goto eanwrongtype;
821                         break;
822                 case ISMN:
823                         strncpy(buf, "9790", 4);        /* this isn't for sure yet, for now
824                                                                                  * ISMN it's only 9790 */
825                         valid = (valid && ((rcheck = checkdig(buf + 3, 10)) == check || magic));
826                         break;
827                 case ISBN:
828                         strncpy(buf, "978", 3);
829                         valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
830                         break;
831                 case ISSN:
832                         strncpy(buf + 10, "00", 2); /* append 00 as the normal issue
833                                                                                  * publication code */
834                         strncpy(buf, "977", 3);
835                         valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
836                         break;
837                 case UPC:
838                         buf[2] = '0';
839                         valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
840                 default:
841                         break;
842         }
843
844         /* fix the check digit: */
845         for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
846         aux1[12] = checkdig(aux1, 13) + '0';
847         aux1[13] = '\0';
848
849         if (!valid && !magic)
850                 goto eanbadcheck;
851
852         *result = str2ean(aux1);
853         *result |= valid ? 0 : 1;
854         return true;
855
856 eanbadcheck:
857         if (g_weak)
858         {                                                       /* weak input mode is activated: */
859                 /* set the "invalid-check-digit-on-input" flag */
860                 *result = str2ean(aux1);
861                 *result |= 1;
862                 return true;
863         }
864
865         if (!errorOK)
866         {
867                 if (rcheck == (unsigned) -1)
868                 {
869                         ereport(ERROR,
870                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
871                                          errmsg("invalid %s number: \"%s\"",
872                                                         isn_names[accept], str)));
873                 }
874                 else
875                 {
876                         ereport(ERROR,
877                                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
878                         errmsg("invalid check digit for %s number: \"%s\", should be %c",
879                                    isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0'))));
880                 }
881         }
882         return false;
883
884 eaninvalid:
885         if (!errorOK)
886                 ereport(ERROR,
887                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
888                                  errmsg("invalid input syntax for %s number: \"%s\"",
889                                                 isn_names[accept], str)));
890         return false;
891
892 eanwrongtype:
893         if (!errorOK)
894                 ereport(ERROR,
895                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
896                                  errmsg("cannot cast %s to %s for number: \"%s\"",
897                                                 isn_names[type], isn_names[accept], str)));
898         return false;
899
900 eantoobig:
901         if (!errorOK)
902                 ereport(ERROR,
903                                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
904                                  errmsg("value \"%s\" is out of range for %s type",
905                                                 str, isn_names[accept])));
906         return false;
907 }
908
909 /*----------------------------------------------------------
910  * Exported routines.
911  *---------------------------------------------------------*/
912
913 void
914 initialize(void)
915 {
916 #ifdef ISN_DEBUG
917         if (!check_table(EAN13, EAN13_index))
918                 elog(LOG, "EAN13 failed check");
919         if (!check_table(ISBN, ISBN_index))
920                 elog(LOG, "ISBN failed check");
921         if (!check_table(ISMN, ISMN_index))
922                 elog(LOG, "ISMN failed check");
923         if (!check_table(ISSN, ISSN_index))
924                 elog(LOG, "ISSN failed check");
925         if (!check_table(UPC, UPC_index))
926                 elog(LOG, "UPC failed check");
927 #endif
928         g_initialized = true;
929 }
930
931 /* isn_out
932  */
933 PG_FUNCTION_INFO_V1(isn_out);
934 Datum
935 isn_out(PG_FUNCTION_ARGS)
936 {
937         ean13           val = PG_GETARG_EAN13(0);
938         char       *result;
939         char            buf[MAXEAN13LEN + 1];
940
941         (void) ean2string(val, false, buf, true);
942
943         result = pstrdup(buf);
944         PG_RETURN_CSTRING(result);
945 }
946
947 /* ean13_out
948  */
949 PG_FUNCTION_INFO_V1(ean13_out);
950 Datum
951 ean13_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, false);
958
959         result = pstrdup(buf);
960         PG_RETURN_CSTRING(result);
961 }
962
963 /* ean13_in
964  */
965 PG_FUNCTION_INFO_V1(ean13_in);
966 Datum
967 ean13_in(PG_FUNCTION_ARGS)
968 {
969         const char *str = PG_GETARG_CSTRING(0);
970         ean13           result;
971
972         (void) string2ean(str, false, &result, EAN13);
973         PG_RETURN_EAN13(result);
974 }
975
976 /* isbn_in
977  */
978 PG_FUNCTION_INFO_V1(isbn_in);
979 Datum
980 isbn_in(PG_FUNCTION_ARGS)
981 {
982         const char *str = PG_GETARG_CSTRING(0);
983         ean13           result;
984
985         (void) string2ean(str, false, &result, ISBN);
986         PG_RETURN_EAN13(result);
987 }
988
989 /* ismn_in
990  */
991 PG_FUNCTION_INFO_V1(ismn_in);
992 Datum
993 ismn_in(PG_FUNCTION_ARGS)
994 {
995         const char *str = PG_GETARG_CSTRING(0);
996         ean13           result;
997
998         (void) string2ean(str, false, &result, ISMN);
999         PG_RETURN_EAN13(result);
1000 }
1001
1002 /* issn_in
1003  */
1004 PG_FUNCTION_INFO_V1(issn_in);
1005 Datum
1006 issn_in(PG_FUNCTION_ARGS)
1007 {
1008         const char *str = PG_GETARG_CSTRING(0);
1009         ean13           result;
1010
1011         (void) string2ean(str, false, &result, ISSN);
1012         PG_RETURN_EAN13(result);
1013 }
1014
1015 /* upc_in
1016  */
1017 PG_FUNCTION_INFO_V1(upc_in);
1018 Datum
1019 upc_in(PG_FUNCTION_ARGS)
1020 {
1021         const char *str = PG_GETARG_CSTRING(0);
1022         ean13           result;
1023
1024         (void) string2ean(str, false, &result, UPC);
1025         PG_RETURN_EAN13(result);
1026 }
1027
1028 /* casting functions
1029 */
1030 PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
1031 Datum
1032 isbn_cast_from_ean13(PG_FUNCTION_ARGS)
1033 {
1034         ean13           val = PG_GETARG_EAN13(0);
1035         ean13           result;
1036
1037         (void) ean2isn(val, false, &result, ISBN);
1038
1039         PG_RETURN_EAN13(result);
1040 }
1041
1042 PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
1043 Datum
1044 ismn_cast_from_ean13(PG_FUNCTION_ARGS)
1045 {
1046         ean13           val = PG_GETARG_EAN13(0);
1047         ean13           result;
1048
1049         (void) ean2isn(val, false, &result, ISMN);
1050
1051         PG_RETURN_EAN13(result);
1052 }
1053
1054 PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
1055 Datum
1056 issn_cast_from_ean13(PG_FUNCTION_ARGS)
1057 {
1058         ean13           val = PG_GETARG_EAN13(0);
1059         ean13           result;
1060
1061         (void) ean2isn(val, false, &result, ISSN);
1062
1063         PG_RETURN_EAN13(result);
1064 }
1065
1066 PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
1067 Datum
1068 upc_cast_from_ean13(PG_FUNCTION_ARGS)
1069 {
1070         ean13           val = PG_GETARG_EAN13(0);
1071         ean13           result;
1072
1073         (void) ean2isn(val, false, &result, UPC);
1074
1075         PG_RETURN_EAN13(result);
1076 }
1077
1078
1079 /* is_valid - returns false if the "invalid-check-digit-on-input" is set
1080  */
1081 PG_FUNCTION_INFO_V1(is_valid);
1082 Datum
1083 is_valid(PG_FUNCTION_ARGS)
1084 {
1085         ean13           val = PG_GETARG_EAN13(0);
1086
1087         PG_RETURN_BOOL((val & 1) == 0);
1088 }
1089
1090 /* make_valid - unsets the "invalid-check-digit-on-input" flag
1091  */
1092 PG_FUNCTION_INFO_V1(make_valid);
1093 Datum
1094 make_valid(PG_FUNCTION_ARGS)
1095 {
1096         ean13           val = PG_GETARG_EAN13(0);
1097
1098         val &= ~((ean13) 1);
1099         PG_RETURN_EAN13(val);
1100 }
1101
1102 /* this function temporarily sets weak input flag
1103  * (to lose the strictness of check digit acceptance)
1104  * It's a helper function, not intended to be used!!
1105  */
1106 PG_FUNCTION_INFO_V1(accept_weak_input);
1107 Datum
1108 accept_weak_input(PG_FUNCTION_ARGS)
1109 {
1110 #ifdef ISN_WEAK_MODE
1111         g_weak = PG_GETARG_BOOL(0);
1112 #else
1113         /* function has no effect */
1114 #endif   /* ISN_WEAK_MODE */
1115         PG_RETURN_BOOL(g_weak);
1116 }
1117
1118 PG_FUNCTION_INFO_V1(weak_input_status);
1119 Datum
1120 weak_input_status(PG_FUNCTION_ARGS)
1121 {
1122         PG_RETURN_BOOL(g_weak);
1123 }