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