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