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