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