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