Age Owner TLA 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-2023, 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 :
5245 tgl 26 CBC 1 : 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
6031 bruce 65 5 : 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 5 : x = 0,
72 5 : y = -1,
73 5 : i = 0,
74 : j,
75 5 : init = 0;
76 :
77 5 : if (TABLE == NULL || TABLE_index == NULL)
6031 bruce 78 UBC 0 : return true;
79 :
6031 bruce 80 CBC 1040 : while (TABLE[i][0] && TABLE[i][1])
81 : {
6056 tgl 82 1035 : aux1 = TABLE[i][0];
83 1035 : aux2 = TABLE[i][1];
84 :
85 : /* must always start with a digit: */
6043 86 1035 : if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
6043 tgl 87 UBC 0 : goto invalidtable;
6056 tgl 88 CBC 1035 : a = *aux1 - '0';
89 1035 : b = *aux2 - '0';
90 :
91 : /* must always have the same format and length: */
6031 bruce 92 8272 : while (*aux1 && *aux2)
93 : {
6043 tgl 94 7237 : if (!(isdigit((unsigned char) *aux1) &&
95 6321 : isdigit((unsigned char) *aux2)) &&
6031 bruce 96 916 : (*aux1 != *aux2 || *aux1 != '-'))
6056 tgl 97 UBC 0 : goto invalidtable;
6056 tgl 98 CBC 7237 : aux1++;
99 7237 : aux2++;
100 : }
6031 bruce 101 1035 : if (*aux1 != *aux2)
6031 bruce 102 UBC 0 : goto invalidtable;
103 :
104 : /* found a new range */
6031 bruce 105 CBC 1035 : if (a > y)
106 : {
107 : /* check current range in the index: */
108 40 : for (j = x; j <= y; j++)
109 : {
110 18 : if (TABLE_index[j][0] != init)
6031 bruce 111 UBC 0 : goto invalidindex;
6031 bruce 112 CBC 18 : if (TABLE_index[j][1] != i - init)
6031 bruce 113 UBC 0 : goto invalidindex;
114 : }
6056 tgl 115 CBC 22 : init = i;
116 22 : x = a;
117 : }
118 :
119 : /* Always get the new limit */
120 1035 : y = b;
6031 bruce 121 1035 : if (y < x)
6031 bruce 122 UBC 0 : goto invalidtable;
6056 tgl 123 CBC 1035 : i++;
124 : }
125 :
126 5 : return true;
127 :
6056 tgl 128 UBC 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
6031 bruce 143 CBC 2 : dehyphenate(char *bufO, char *bufI)
144 : {
145 2 : unsigned ret = 0;
146 :
147 30 : while (*bufI)
148 : {
149 28 : if (isdigit((unsigned char) *bufI))
150 : {
6056 tgl 151 24 : *bufO++ = *bufI;
152 24 : ret++;
153 : }
154 28 : bufI++;
155 : }
156 2 : *bufO = '\0';
157 2 : 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
6031 bruce 168 83 : hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
169 : {
170 83 : 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 83 : if (TABLE == NULL || TABLE_index == NULL)
186 : {
187 260 : while (*bufI)
188 : {
6056 tgl 189 240 : *bufO++ = *bufI++;
190 240 : ret++;
191 : }
192 20 : *bufO = '\0';
6031 bruce 193 20 : return (ret + 1);
194 : }
195 :
196 : /* add remaining hyphenations */
197 :
6056 tgl 198 63 : search = *bufI - '0';
199 63 : upper = lower = TABLE_index[search][0];
200 63 : upper += TABLE_index[search][1];
201 63 : lower--;
202 :
203 63 : step = (upper - lower) / 2;
6031 bruce 204 63 : if (step == 0)
205 3 : return 0;
6056 tgl 206 60 : search = lower + step;
207 :
208 60 : firstdig = bufI;
209 60 : ean_in1 = ean_in2 = false;
210 60 : ean_aux1 = TABLE[search][0];
211 60 : ean_aux2 = TABLE[search][1];
212 : do
213 : {
6031 bruce 214 356 : if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2))
215 : {
216 261 : if (*firstdig > *ean_aux1)
217 34 : ean_in1 = true;
218 261 : if (*firstdig < *ean_aux2)
219 34 : ean_in2 = true;
220 261 : if (ean_in1 && ean_in2)
221 26 : break;
222 :
6056 tgl 223 235 : firstdig++, ean_aux1++, ean_aux2++;
6031 bruce 224 235 : if (!(*ean_aux1 && *ean_aux2 && *firstdig))
225 : break;
226 207 : if (!isdigit((unsigned char) *ean_aux1))
227 40 : 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 95 : if (*firstdig < *ean_aux1 && !ean_in1)
236 32 : upper = search;
237 : else
238 63 : lower = search;
239 :
6056 tgl 240 95 : step = (upper - lower) / 2;
241 95 : search = lower + step;
242 :
243 : /* Initialize stuff again: */
244 95 : firstdig = bufI;
245 95 : ean_in1 = ean_in2 = false;
246 95 : ean_aux1 = TABLE[search][0];
247 95 : ean_aux2 = TABLE[search][1];
248 : }
6031 bruce 249 302 : } while (step);
250 :
251 60 : if (step)
252 : {
6056 tgl 253 54 : aux1 = bufO;
254 54 : aux2 = bufI;
255 54 : ean_p = TABLE[search][0];
6031 bruce 256 284 : while (*ean_p && *aux2)
257 : {
258 230 : if (*ean_p++ != '-')
259 208 : *aux1++ = *aux2++;
260 : else
261 22 : *aux1++ = '-';
6056 tgl 262 230 : ret++;
263 : }
6031 bruce 264 54 : *aux1++ = '-';
265 54 : *aux1 = *aux2; /* add a lookahead char */
266 54 : return (ret + 1);
267 : }
6056 tgl 268 6 : 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
6031 bruce 278 14 : weight_checkdig(char *isn, unsigned size)
279 : {
280 14 : unsigned weight = 0;
281 :
282 138 : while (*isn && size > 1)
283 : {
284 124 : if (isdigit((unsigned char) *isn))
285 : {
6056 tgl 286 114 : weight += size-- * (*isn - '0');
287 : }
288 124 : isn++;
289 : }
290 14 : weight = weight % 11;
6031 bruce 291 14 : if (weight != 0)
292 14 : weight = 11 - weight;
6056 tgl 293 14 : 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
6031 bruce 304 106 : checkdig(char *num, unsigned size)
305 : {
306 106 : unsigned check = 0,
307 106 : check3 = 0;
308 106 : unsigned pos = 0;
309 :
310 106 : if (*num == 'M')
311 : { /* ISMN start with 'M' */
6056 tgl 312 UBC 0 : check3 = 3;
313 0 : pos = 1;
314 : }
6031 bruce 315 CBC 1378 : while (*num && size > 1)
316 : {
317 1272 : if (isdigit((unsigned char) *num))
318 : {
319 1272 : if (pos++ % 2)
320 636 : check3 += *num - '0';
321 : else
322 636 : check += *num - '0';
6056 tgl 323 1272 : size--;
324 : }
325 1272 : num++;
326 : }
6031 bruce 327 106 : check = (check + 3 * check3) % 10;
328 106 : if (check != 0)
329 106 : check = 10 - check;
6056 tgl 330 106 : 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
5050 bruce 341 6 : ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
342 : {
6055 tgl 343 6 : enum isn_type type = INVALID;
344 :
345 : char buf[MAXEAN13LEN + 1];
346 : char *aux;
347 : unsigned digval;
348 : unsigned search;
6031 bruce 349 6 : ean13 ret = ean;
350 :
6055 tgl 351 6 : ean >>= 1;
352 : /* verify it's in the EAN13 range */
6031 bruce 353 6 : if (ean > UINT64CONST(9999999999999))
6055 tgl 354 UBC 0 : goto eantoobig;
355 :
356 : /* convert the number */
6055 tgl 357 CBC 6 : search = 0;
4381 peter_e 358 6 : aux = buf + 13;
6031 bruce 359 6 : *aux = '\0'; /* terminate string; aux points to last digit */
360 : do
361 : {
362 77 : digval = (unsigned) (ean % 10); /* get the decimal value */
363 77 : ean /= 10; /* get next digit */
364 77 : *--aux = (char) (digval + '0'); /* convert to ascii and store */
365 77 : } while (ean && search++ < 12);
366 7 : while (search++ < 12)
367 1 : *--aux = '0'; /* fill the remaining EAN13 with '0' */
368 :
369 : /* find out the data type: */
4121 peter_e 370 6 : if (strncmp("978", buf, 3) == 0)
371 : { /* ISBN */
6055 tgl 372 1 : type = ISBN;
373 : }
4121 peter_e 374 5 : else if (strncmp("977", buf, 3) == 0)
375 : { /* ISSN */
6055 tgl 376 1 : type = ISSN;
377 : }
4121 peter_e 378 4 : else if (strncmp("9790", buf, 4) == 0)
379 : { /* ISMN */
6055 tgl 380 1 : type = ISMN;
381 : }
4121 peter_e 382 3 : else if (strncmp("979", buf, 3) == 0)
383 : { /* ISBN-13 */
6055 tgl 384 2 : type = ISBN;
385 : }
6031 bruce 386 1 : else if (*buf == '0')
387 : { /* UPC */
6055 tgl 388 1 : type = UPC;
389 : }
390 : else
391 : {
6055 tgl 392 UBC 0 : type = EAN13;
393 : }
6031 bruce 394 CBC 6 : if (accept != ANY && accept != EAN13 && accept != type)
6031 bruce 395 UBC 0 : goto eanwrongtype;
396 :
6055 tgl 397 CBC 6 : *result = ret;
398 6 : return true;
399 :
6055 tgl 400 UBC 0 : eanwrongtype:
6031 bruce 401 0 : if (!errorOK)
402 : {
403 0 : if (type != EAN13)
404 : {
6055 tgl 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:
6031 bruce 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 : */
6055 tgl 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
6031 bruce 443 CBC 7 : 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 : */
2807 heikki.linnakangas 453 7 : if (strncmp("978-", isn, 4) == 0)
454 : {
455 : /* Strip the first part and calculate the new check digit */
456 4 : hyphenate(isn, isn + 4, NULL, NULL);
457 4 : check = weight_checkdig(isn, 10);
458 4 : aux = strchr(isn, '\0');
459 4 : while (!isdigit((unsigned char) *--aux));
460 4 : if (check == 10)
461 1 : *aux = 'X';
462 : else
463 3 : *aux = check + '0';
464 : }
6056 tgl 465 7 : }
466 :
467 : static inline void
6031 bruce 468 4 : 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 4 : hyphenate(isn, isn + 4, NULL, NULL);
6056 tgl 473 4 : isn[0] = 'M';
474 4 : }
475 :
476 : static inline void
6031 bruce 477 2 : 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 2 : hyphenate(isn, isn + 4, NULL, NULL);
6056 tgl 484 2 : check = weight_checkdig(isn, 8);
6031 bruce 485 2 : if (check == 10)
6031 bruce 486 UBC 0 : isn[8] = 'X';
487 : else
6031 bruce 488 CBC 2 : isn[8] = check + '0';
6056 tgl 489 2 : isn[9] = '\0';
490 2 : }
491 :
492 : static inline void
6031 bruce 493 2 : ean2UPC(char *isn)
494 : {
495 : /* the number should come in this format: 000-000000000-0 */
496 : /* Strip the first part, crop, and dehyphenate */
497 2 : dehyphenate(isn, isn + 1);
6056 tgl 498 2 : isn[12] = '\0';
499 2 : }
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
6031 bruce 509 40 : str2ean(const char *num)
510 : {
511 40 : ean13 ean = 0; /* current ean */
512 :
513 560 : while (*num)
514 : {
515 520 : if (isdigit((unsigned char) *num))
516 520 : ean = 10 * ean + (*num - '0');
6055 tgl 517 520 : num++;
518 : }
6031 bruce 519 40 : 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 32 : ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
534 : {
535 : const char *(*TABLE)[2];
536 : const unsigned (*TABLE_index)[2];
6056 tgl 537 32 : enum isn_type type = INVALID;
538 :
539 : char *aux;
540 : unsigned digval;
541 : unsigned search;
6031 bruce 542 32 : char valid = '\0'; /* was the number initially written with a
543 : * valid check digit? */
544 :
6056 tgl 545 32 : TABLE_index = ISBN_index;
546 :
6031 bruce 547 32 : if ((ean & 1) != 0)
6031 bruce 548 UBC 0 : valid = '!';
6056 tgl 549 CBC 32 : ean >>= 1;
550 : /* verify it's in the EAN13 range */
6031 bruce 551 32 : if (ean > UINT64CONST(9999999999999))
6056 tgl 552 UBC 0 : goto eantoobig;
553 :
554 : /* convert the number */
6056 tgl 555 CBC 32 : search = 0;
4381 peter_e 556 32 : aux = result + MAXEAN13LEN;
6031 bruce 557 32 : *aux = '\0'; /* terminate string; aux points to last digit */
558 32 : *--aux = valid; /* append '!' for numbers with invalid but
559 : * corrected check digit */
560 : do
561 : {
562 413 : digval = (unsigned) (ean % 10); /* get the decimal value */
563 413 : ean /= 10; /* get next digit */
564 413 : *--aux = (char) (digval + '0'); /* convert to ascii and store */
565 413 : if (search == 0)
566 32 : *--aux = '-'; /* the check digit is always there */
567 413 : } while (ean && search++ < 13);
568 67 : while (search++ < 13)
569 35 : *--aux = '0'; /* fill the remaining EAN13 with '0' */
570 :
571 : /* The string should be in this form: ???DDDDDDDDDDDD-D" */
572 32 : search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
573 :
574 : /* verify it's a logically valid EAN13 */
575 32 : if (search == 0)
576 : {
6031 bruce 577 UBC 0 : search = hyphenate(result, result + 3, NULL, NULL);
6056 tgl 578 0 : goto okay;
579 : }
580 :
581 : /* find out what type of hyphenation is needed: */
4121 peter_e 582 CBC 32 : if (strncmp("978-", result, search) == 0)
583 : { /* ISBN -13 978-range */
584 : /* The string should be in this form: 978-??000000000-0" */
6056 tgl 585 7 : type = ISBN;
586 7 : TABLE = ISBN_range;
587 7 : TABLE_index = ISBN_index;
588 : }
4121 peter_e 589 25 : else if (strncmp("977-", result, search) == 0)
590 : { /* ISSN */
591 : /* The string should be in this form: 977-??000000000-0" */
6056 tgl 592 7 : type = ISSN;
593 7 : TABLE = ISSN_range;
594 7 : TABLE_index = ISSN_index;
595 : }
4121 peter_e 596 18 : else if (strncmp("979-0", result, search + 1) == 0)
597 : { /* ISMN */
598 : /* The string should be in this form: 979-0?000000000-0" */
6056 tgl 599 8 : type = ISMN;
600 8 : TABLE = ISMN_range;
601 8 : TABLE_index = ISMN_index;
602 : }
4121 peter_e 603 10 : else if (strncmp("979-", result, search) == 0)
604 : { /* ISBN-13 979-range */
605 : /* The string should be in this form: 979-??000000000-0" */
4555 rhaas 606 6 : type = ISBN;
607 6 : TABLE = ISBN_range_new;
608 6 : TABLE_index = ISBN_index_new;
609 : }
6031 bruce 610 4 : else if (*result == '0')
611 : { /* UPC */
612 : /* The string should be in this form: 000-00000000000-0" */
6056 tgl 613 3 : type = UPC;
614 3 : TABLE = UPC_range;
615 3 : TABLE_index = UPC_index;
616 : }
617 : else
618 : {
619 1 : type = EAN13;
620 1 : TABLE = NULL;
621 1 : TABLE_index = NULL;
622 : }
623 :
624 : /* verify it's a logically valid EAN13/UPC/ISxN */
625 32 : digval = search;
6031 bruce 626 32 : search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
627 :
628 : /* verify it's a valid EAN13 */
629 32 : if (search == 0)
630 : {
631 9 : search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
6056 tgl 632 9 : goto okay;
633 : }
634 :
635 23 : okay:
636 : /* convert to the old short type: */
6031 bruce 637 32 : if (shortType)
638 15 : switch (type)
639 : {
6056 tgl 640 7 : case ISBN:
641 7 : ean2ISBN(result);
642 7 : break;
643 4 : case ISMN:
644 4 : ean2ISMN(result);
645 4 : break;
646 2 : case ISSN:
647 2 : ean2ISSN(result);
648 2 : break;
649 2 : case UPC:
650 2 : ean2UPC(result);
651 2 : break;
6056 tgl 652 UBC 0 : default:
653 0 : break;
654 : }
6056 tgl 655 CBC 32 : return true;
656 :
6056 tgl 657 UBC 0 : eantoobig:
6031 bruce 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 : */
6056 tgl 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
106 andrew 685 GNC 71 : string2ean(const char *str, struct Node *escontext, ean13 *result,
686 : enum isn_type accept)
687 : {
688 : bool digit,
689 : last;
6031 bruce 690 CBC 71 : char buf[17] = " ";
691 71 : char *aux1 = buf + 3; /* leave space for the first part, in case
692 : * it's needed */
6056 tgl 693 71 : const char *aux2 = str;
694 71 : enum isn_type type = INVALID;
6031 bruce 695 71 : unsigned check = 0,
696 71 : rcheck = (unsigned) -1;
697 71 : unsigned length = 0;
698 71 : bool magic = false,
699 71 : valid = true;
700 :
701 : /* recognize and validate the number: */
702 901 : while (*aux2 && length <= 13)
703 : {
2118 tgl 704 834 : last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the last character */
6031 bruce 705 834 : digit = (isdigit((unsigned char) *aux2) != 0); /* is current character
706 : * a digit? */
2118 tgl 707 834 : if (*aux2 == '?' && last) /* automagically calculate check digit if
708 : * it's '?' */
6056 tgl 709 UBC 0 : magic = digit = true;
6031 bruce 710 CBC 834 : if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
711 : {
712 : /* only ISMN can be here */
713 6 : if (type != INVALID)
6031 bruce 714 UBC 0 : goto eaninvalid;
6056 tgl 715 CBC 6 : type = ISMN;
716 6 : *aux1++ = 'M';
717 6 : length++;
718 : }
6031 bruce 719 828 : else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
720 : {
721 : /* only ISSN can be here */
722 4 : if (type != INVALID)
6031 bruce 723 UBC 0 : goto eaninvalid;
6056 tgl 724 CBC 4 : type = ISSN;
6043 725 4 : *aux1++ = toupper((unsigned char) *aux2);
6056 726 4 : length++;
727 : }
6031 bruce 728 824 : else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
729 : {
730 : /* only ISBN and ISMN can be here */
731 10 : if (type != INVALID && type != ISMN)
6031 bruce 732 UBC 0 : goto eaninvalid;
6031 bruce 733 CBC 10 : if (type == INVALID)
734 4 : type = ISBN; /* ISMN must start with 'M' */
6043 tgl 735 10 : *aux1++ = toupper((unsigned char) *aux2);
6056 736 10 : length++;
737 : }
6031 bruce 738 814 : else if (length == 11 && digit && last)
739 : {
740 : /* only UPC can be here */
6031 bruce 741 UBC 0 : if (type != INVALID)
742 0 : goto eaninvalid;
6056 tgl 743 0 : type = UPC;
744 0 : *aux1++ = *aux2;
745 0 : length++;
746 : }
6031 bruce 747 CBC 814 : else if (*aux2 == '-' || *aux2 == ' ')
748 : {
749 : /* skip, we could validate but I think it's worthless */
750 : }
751 805 : else if (*aux2 == '!' && *(aux2 + 1) == '\0')
752 : {
753 : /* the invalid check digit suffix was found, set it */
6031 bruce 754 UBC 0 : if (!magic)
755 0 : valid = false;
6056 tgl 756 0 : magic = true;
757 : }
6031 bruce 758 CBC 805 : else if (!digit)
759 : {
6056 tgl 760 4 : goto eaninvalid;
761 : }
762 : else
763 : {
764 801 : *aux1++ = *aux2;
6031 bruce 765 801 : if (++length > 13)
6031 bruce 766 UBC 0 : goto eantoobig;
767 : }
6056 tgl 768 CBC 830 : aux2++;
769 : }
6031 bruce 770 67 : *aux1 = '\0'; /* terminate the string */
771 :
772 : /* find the current check digit value */
773 67 : if (length == 13)
774 : {
775 : /* only EAN13 can be here */
776 53 : if (type != INVALID)
6031 bruce 777 UBC 0 : goto eaninvalid;
6056 tgl 778 CBC 53 : type = EAN13;
6031 bruce 779 53 : check = buf[15] - '0';
780 : }
781 14 : else if (length == 12)
782 : {
783 : /* only UPC can be here */
6031 bruce 784 UBC 0 : if (type != UPC)
785 0 : goto eaninvalid;
786 0 : check = buf[14] - '0';
787 : }
6031 bruce 788 CBC 14 : else if (length == 10)
789 : {
790 10 : if (type != ISBN && type != ISMN)
6031 bruce 791 UBC 0 : goto eaninvalid;
6031 bruce 792 CBC 10 : if (buf[12] == 'X')
793 3 : check = 10;
794 : else
795 7 : check = buf[12] - '0';
796 : }
797 4 : else if (length == 8)
798 : {
799 4 : if (type != INVALID && type != ISSN)
6031 bruce 800 UBC 0 : goto eaninvalid;
6056 tgl 801 CBC 4 : type = ISSN;
6031 bruce 802 4 : if (buf[10] == 'X')
6031 bruce 803 UBC 0 : check = 10;
804 : else
6031 bruce 805 CBC 4 : check = buf[10] - '0';
806 : }
807 : else
6031 bruce 808 UBC 0 : goto eaninvalid;
809 :
6031 bruce 810 CBC 67 : if (type == INVALID)
6031 bruce 811 UBC 0 : goto eaninvalid;
812 :
813 : /* obtain the real check digit value, validate, and convert to ean13: */
6031 bruce 814 CBC 67 : if (accept == EAN13 && type != accept)
6031 bruce 815 UBC 0 : goto eanwrongtype;
6031 bruce 816 CBC 67 : if (accept != ANY && type != EAN13 && type != accept)
6031 bruce 817 UBC 0 : goto eanwrongtype;
6031 bruce 818 CBC 67 : switch (type)
819 : {
6056 tgl 820 53 : case EAN13:
6031 bruce 821 53 : valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
822 : /* now get the subtype of EAN13: */
823 53 : if (buf[3] == '0')
824 8 : type = UPC;
4121 peter_e 825 45 : else if (strncmp("977", buf + 3, 3) == 0)
6031 bruce 826 12 : type = ISSN;
4121 peter_e 827 33 : else if (strncmp("978", buf + 3, 3) == 0)
6031 bruce 828 11 : type = ISBN;
4121 peter_e 829 22 : else if (strncmp("9790", buf + 3, 4) == 0)
6031 bruce 830 9 : type = ISMN;
4121 peter_e 831 13 : else if (strncmp("979", buf + 3, 3) == 0)
6031 bruce 832 11 : type = ISBN;
833 53 : if (accept != EAN13 && accept != ANY && type != accept)
834 20 : goto eanwrongtype;
6056 tgl 835 33 : break;
836 6 : case ISMN:
2118 837 6 : memcpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN
838 : * it's only 9790 */
3373 heikki.linnakangas 839 6 : valid = (valid && ((rcheck = checkdig(buf, 13)) == check || magic));
6056 tgl 840 6 : break;
841 4 : case ISBN:
2997 842 4 : memcpy(buf, "978", 3);
6031 bruce 843 4 : valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
6056 tgl 844 4 : break;
845 4 : case ISSN:
2997 846 4 : memcpy(buf + 10, "00", 2); /* append 00 as the normal issue
847 : * publication code */
848 4 : memcpy(buf, "977", 3);
6031 bruce 849 4 : valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
6056 tgl 850 4 : break;
6056 tgl 851 UBC 0 : case UPC:
852 0 : buf[2] = '0';
6031 bruce 853 0 : valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
6056 tgl 854 0 : default:
855 0 : break;
856 : }
857 :
858 : /* fix the check digit: */
6031 bruce 859 CBC 146 : for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
6056 tgl 860 47 : aux1[12] = checkdig(aux1, 13) + '0';
861 47 : aux1[13] = '\0';
862 :
6031 bruce 863 47 : if (!valid && !magic)
864 7 : goto eanbadcheck;
865 :
6056 tgl 866 40 : *result = str2ean(aux1);
6031 bruce 867 40 : *result |= valid ? 0 : 1;
6056 tgl 868 40 : return true;
869 :
6031 bruce 870 7 : eanbadcheck:
871 7 : if (g_weak)
872 : { /* weak input mode is activated: */
873 : /* set the "invalid-check-digit-on-input" flag */
6055 tgl 874 UBC 0 : *result = str2ean(aux1);
875 0 : *result |= 1;
6031 bruce 876 0 : return true;
877 : }
878 :
106 andrew 879 GNC 7 : if (rcheck == (unsigned) -1)
880 : {
106 andrew 881 UNC 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 : {
106 andrew 888 GNC 7 : 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'))));
6031 bruce 892 ECB : }
893 :
6056 tgl 894 GIC 4 : eaninvalid:
106 andrew 895 GNC 4 : ereturn(escontext, false,
896 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
897 : errmsg("invalid input syntax for %s number: \"%s\"",
898 : isn_names[accept], str)));
899 :
6056 tgl 900 GBC 20 : eanwrongtype:
106 andrew 901 GNC 20 : 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 :
6056 tgl 906 UIC 0 : eantoobig:
106 andrew 907 UNC 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 : }
6056 tgl 912 ECB :
6056 tgl 913 EUB : /*----------------------------------------------------------
6056 tgl 914 ECB : * Exported routines.
6056 tgl 915 EUB : *---------------------------------------------------------*/
6056 tgl 916 ECB :
6031 bruce 917 EUB : void
2067 peter_e 918 CBC 1 : _PG_init(void)
6056 tgl 919 EUB : {
920 : if (ISN_DEBUG)
2067 peter_e 921 ECB : {
2067 peter_e 922 GIC 1 : if (!check_table(EAN13_range, EAN13_index))
2067 peter_e 923 UIC 0 : elog(ERROR, "EAN13 failed check");
2067 peter_e 924 GIC 1 : if (!check_table(ISBN_range, ISBN_index))
2067 peter_e 925 LBC 0 : elog(ERROR, "ISBN failed check");
2067 peter_e 926 GIC 1 : if (!check_table(ISMN_range, ISMN_index))
2067 peter_e 927 LBC 0 : elog(ERROR, "ISMN failed check");
2067 peter_e 928 GIC 1 : if (!check_table(ISSN_range, ISSN_index))
2067 peter_e 929 LBC 0 : elog(ERROR, "ISSN failed check");
2067 peter_e 930 GIC 1 : if (!check_table(UPC_range, UPC_index))
2067 peter_e 931 UIC 0 : elog(ERROR, "UPC failed check");
932 : }
6056 tgl 933 CBC 1 : }
934 :
6056 tgl 935 ECB : /* isn_out
936 : */
6056 tgl 937 GIC 8 : PG_FUNCTION_INFO_V1(isn_out);
938 : Datum
939 15 : isn_out(PG_FUNCTION_ARGS)
940 : {
6056 tgl 941 CBC 15 : ean13 val = PG_GETARG_EAN13(0);
942 : char *result;
6056 tgl 943 ECB : char buf[MAXEAN13LEN + 1];
944 :
105 andrew 945 CBC 15 : (void) ean2string(val, false, buf, true);
946 :
6056 tgl 947 GIC 15 : result = pstrdup(buf);
948 15 : PG_RETURN_CSTRING(result);
6056 tgl 949 ECB : }
950 :
951 : /* ean13_out
952 : */
6056 tgl 953 GIC 8 : PG_FUNCTION_INFO_V1(ean13_out);
954 : Datum
955 17 : ean13_out(PG_FUNCTION_ARGS)
956 : {
6056 tgl 957 CBC 17 : ean13 val = PG_GETARG_EAN13(0);
958 : char *result;
6056 tgl 959 ECB : char buf[MAXEAN13LEN + 1];
960 :
105 andrew 961 CBC 17 : (void) ean2string(val, false, buf, false);
962 :
6056 tgl 963 GIC 17 : result = pstrdup(buf);
6056 tgl 964 CBC 17 : PG_RETURN_CSTRING(result);
6056 tgl 965 ECB : }
966 :
967 : /* ean13_in
968 : */
6056 tgl 969 GIC 2 : PG_FUNCTION_INFO_V1(ean13_in);
970 : Datum
6056 tgl 971 CBC 19 : ean13_in(PG_FUNCTION_ARGS)
972 : {
6031 bruce 973 19 : const char *str = PG_GETARG_CSTRING(0);
974 : ean13 result;
6056 tgl 975 ECB :
106 andrew 976 GNC 19 : if (!string2ean(str, fcinfo->context, &result, EAN13))
977 2 : PG_RETURN_NULL();
6056 tgl 978 GIC 15 : PG_RETURN_EAN13(result);
6056 tgl 979 ECB : }
6056 tgl 980 EUB :
6056 tgl 981 ECB : /* isbn_in
982 : */
6056 tgl 983 GIC 4 : PG_FUNCTION_INFO_V1(isbn_in);
984 : Datum
985 19 : isbn_in(PG_FUNCTION_ARGS)
6056 tgl 986 ECB : {
6031 bruce 987 GIC 19 : const char *str = PG_GETARG_CSTRING(0);
6056 tgl 988 ECB : ean13 result;
989 :
106 andrew 990 GNC 19 : if (!string2ean(str, fcinfo->context, &result, ISBN))
106 andrew 991 UNC 0 : PG_RETURN_NULL();
6056 tgl 992 GIC 9 : PG_RETURN_EAN13(result);
993 : }
6056 tgl 994 ECB :
6056 tgl 995 EUB : /* ismn_in
6056 tgl 996 ECB : */
6056 tgl 997 GIC 4 : PG_FUNCTION_INFO_V1(ismn_in);
998 : Datum
999 12 : ismn_in(PG_FUNCTION_ARGS)
1000 : {
6031 bruce 1001 CBC 12 : const char *str = PG_GETARG_CSTRING(0);
1002 : ean13 result;
6056 tgl 1003 ECB :
106 andrew 1004 GNC 12 : if (!string2ean(str, fcinfo->context, &result, ISMN))
106 andrew 1005 UNC 0 : PG_RETURN_NULL();
6056 tgl 1006 CBC 7 : PG_RETURN_EAN13(result);
1007 : }
1008 :
6056 tgl 1009 ECB : /* issn_in
6056 tgl 1010 EUB : */
6056 tgl 1011 CBC 4 : PG_FUNCTION_INFO_V1(issn_in);
1012 : Datum
6056 tgl 1013 GIC 13 : issn_in(PG_FUNCTION_ARGS)
1014 : {
6031 bruce 1015 13 : const char *str = PG_GETARG_CSTRING(0);
6056 tgl 1016 ECB : ean13 result;
1017 :
106 andrew 1018 GNC 13 : if (!string2ean(str, fcinfo->context, &result, ISSN))
106 andrew 1019 UNC 0 : PG_RETURN_NULL();
6056 tgl 1020 GIC 8 : PG_RETURN_EAN13(result);
6056 tgl 1021 ECB : }
1022 :
1023 : /* upc_in
1024 : */
6056 tgl 1025 CBC 2 : PG_FUNCTION_INFO_V1(upc_in);
6056 tgl 1026 ECB : Datum
6056 tgl 1027 GIC 8 : upc_in(PG_FUNCTION_ARGS)
1028 : {
6031 bruce 1029 8 : const char *str = PG_GETARG_CSTRING(0);
1030 : ean13 result;
6056 tgl 1031 ECB :
106 andrew 1032 GNC 8 : if (!string2ean(str, fcinfo->context, &result, UPC))
1033 2 : PG_RETURN_NULL();
6056 tgl 1034 CBC 1 : PG_RETURN_EAN13(result);
1035 : }
6056 tgl 1036 ECB :
1037 : /* casting functions
1038 : */
6055 tgl 1039 CBC 4 : PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
1040 : Datum
1041 3 : isbn_cast_from_ean13(PG_FUNCTION_ARGS)
1042 : {
6055 tgl 1043 GIC 3 : ean13 val = PG_GETARG_EAN13(0);
6055 tgl 1044 ECB : ean13 result;
1045 :
6055 tgl 1046 CBC 3 : (void) ean2isn(val, false, &result, ISBN);
1047 :
1048 3 : PG_RETURN_EAN13(result);
1049 : }
1050 :
1051 3 : PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
1052 : Datum
1053 1 : ismn_cast_from_ean13(PG_FUNCTION_ARGS)
1054 : {
6055 tgl 1055 GIC 1 : ean13 val = PG_GETARG_EAN13(0);
6055 tgl 1056 ECB : ean13 result;
1057 :
6055 tgl 1058 CBC 1 : (void) ean2isn(val, false, &result, ISMN);
1059 :
1060 1 : PG_RETURN_EAN13(result);
1061 : }
1062 :
1063 3 : PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
1064 : Datum
1065 1 : issn_cast_from_ean13(PG_FUNCTION_ARGS)
1066 : {
6055 tgl 1067 GIC 1 : ean13 val = PG_GETARG_EAN13(0);
6055 tgl 1068 ECB : ean13 result;
1069 :
6055 tgl 1070 CBC 1 : (void) ean2isn(val, false, &result, ISSN);
1071 :
1072 1 : PG_RETURN_EAN13(result);
1073 : }
1074 :
1075 2 : PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
1076 : Datum
1077 1 : upc_cast_from_ean13(PG_FUNCTION_ARGS)
1078 : {
6055 tgl 1079 GIC 1 : ean13 val = PG_GETARG_EAN13(0);
1080 : ean13 result;
1081 :
1082 1 : (void) ean2isn(val, false, &result, UPC);
6055 tgl 1083 ECB :
6055 tgl 1084 GIC 1 : PG_RETURN_EAN13(result);
6055 tgl 1085 EUB : }
1086 :
1087 :
1088 : /* is_valid - returns false if the "invalid-check-digit-on-input" is set
6056 1089 : */
6056 tgl 1090 GIC 8 : PG_FUNCTION_INFO_V1(is_valid);
1091 : Datum
6056 tgl 1092 UIC 0 : is_valid(PG_FUNCTION_ARGS)
1093 : {
6031 bruce 1094 LBC 0 : ean13 val = PG_GETARG_EAN13(0);
1095 :
6056 tgl 1096 UBC 0 : PG_RETURN_BOOL((val & 1) == 0);
1097 : }
6056 tgl 1098 EUB :
1099 : /* make_valid - unsets the "invalid-check-digit-on-input" flag
1100 : */
6056 tgl 1101 GBC 8 : PG_FUNCTION_INFO_V1(make_valid);
1102 : Datum
6056 tgl 1103 UIC 0 : make_valid(PG_FUNCTION_ARGS)
1104 : {
6031 bruce 1105 0 : ean13 val = PG_GETARG_EAN13(0);
1106 :
6056 tgl 1107 0 : val &= ~((ean13) 1);
6056 tgl 1108 LBC 0 : PG_RETURN_EAN13(val);
1109 : }
6056 tgl 1110 EUB :
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 : */
6056 tgl 1115 GIC 1 : PG_FUNCTION_INFO_V1(accept_weak_input);
1116 : Datum
6056 tgl 1117 UBC 0 : accept_weak_input(PG_FUNCTION_ARGS)
1118 : {
1119 : #ifdef ISN_WEAK_MODE
6056 tgl 1120 LBC 0 : g_weak = PG_GETARG_BOOL(0);
1121 : #else
6056 tgl 1122 EUB : /* function has no effect */
1123 : #endif /* ISN_WEAK_MODE */
5245 tgl 1124 UBC 0 : PG_RETURN_BOOL(g_weak);
1125 : }
1126 :
6056 tgl 1127 GIC 1 : PG_FUNCTION_INFO_V1(weak_input_status);
1128 : Datum
6056 tgl 1129 UIC 0 : weak_input_status(PG_FUNCTION_ARGS)
1130 : {
1131 0 : PG_RETURN_BOOL(g_weak);
1132 : }
|