Header And Logo

PostgreSQL
| The world's most advanced open source database.

isn.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * isn.c
00004  *    PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC)
00005  *
00006  * Author:  German Mendez Bravo (Kronuz)
00007  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00008  *
00009  * IDENTIFICATION
00010  *    contrib/isn/isn.c
00011  *
00012  *-------------------------------------------------------------------------
00013  */
00014 
00015 #include "postgres.h"
00016 
00017 #include "fmgr.h"
00018 #include "utils/builtins.h"
00019 
00020 #include "isn.h"
00021 #include "EAN13.h"
00022 #include "ISBN.h"
00023 #include "ISMN.h"
00024 #include "ISSN.h"
00025 #include "UPC.h"
00026 
00027 PG_MODULE_MAGIC;
00028 
00029 #define MAXEAN13LEN 18
00030 
00031 enum isn_type
00032 {
00033     INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
00034 };
00035 
00036 static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
00037 
00038 static bool g_weak = false;
00039 static bool g_initialized = false;
00040 
00041 
00042 /***********************************************************************
00043  **
00044  **     Routines for EAN13/UPC/ISxNs.
00045  **
00046  ** Note:
00047  **  In this code, a normalized string is one that is known to be a valid
00048  **  ISxN number containing only digits and hyphens and with enough space
00049  **  to hold the full 13 digits plus the maximum of four hyphens.
00050  ***********************************************************************/
00051 
00052 /*----------------------------------------------------------
00053  * Debugging routines.
00054  *---------------------------------------------------------*/
00055 
00056 /*
00057  * Check if the table and its index is correct (just for debugging)
00058  */
00059 #ifdef ISN_DEBUG
00060 static bool
00061 check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
00062 {
00063     const char *aux1,
00064                *aux2;
00065     int         a,
00066                 b,
00067                 x = 0,
00068                 y = -1,
00069                 i = 0,
00070                 j,
00071                 cnt = 0,
00072                 init = 0;
00073 
00074     if (TABLE == NULL || TABLE_index == NULL)
00075         return true;
00076 
00077     while (TABLE[i][0] && TABLE[i][1])
00078     {
00079         aux1 = TABLE[i][0];
00080         aux2 = TABLE[i][1];
00081 
00082         /* must always start with a digit: */
00083         if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2))
00084             goto invalidtable;
00085         a = *aux1 - '0';
00086         b = *aux2 - '0';
00087 
00088         /* must always have the same format and length: */
00089         while (*aux1 && *aux2)
00090         {
00091             if (!(isdigit((unsigned char) *aux1) &&
00092                   isdigit((unsigned char) *aux2)) &&
00093                 (*aux1 != *aux2 || *aux1 != '-'))
00094                 goto invalidtable;
00095             aux1++;
00096             aux2++;
00097         }
00098         if (*aux1 != *aux2)
00099             goto invalidtable;
00100 
00101         /* found a new range */
00102         if (a > y)
00103         {
00104             /* check current range in the index: */
00105             for (j = x; j <= y; j++)
00106             {
00107                 if (TABLE_index[j][0] != init)
00108                     goto invalidindex;
00109                 if (TABLE_index[j][1] != i - init)
00110                     goto invalidindex;
00111             }
00112             init = i;
00113             x = a;
00114         }
00115 
00116         /* Always get the new limit */
00117         y = b;
00118         if (y < x)
00119             goto invalidtable;
00120         i++;
00121     }
00122 
00123     return true;
00124 
00125 invalidtable:
00126     elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)",
00127          TABLE[i][0], TABLE[i][1], i);
00128     return false;
00129 
00130 invalidindex:
00131     elog(DEBUG1, "index %d is invalid", j);
00132     return false;
00133 }
00134 #endif   /* ISN_DEBUG */
00135 
00136 /*----------------------------------------------------------
00137  * Formatting and conversion routines.
00138  *---------------------------------------------------------*/
00139 
00140 static unsigned
00141 dehyphenate(char *bufO, char *bufI)
00142 {
00143     unsigned    ret = 0;
00144 
00145     while (*bufI)
00146     {
00147         if (isdigit((unsigned char) *bufI))
00148         {
00149             *bufO++ = *bufI;
00150             ret++;
00151         }
00152         bufI++;
00153     }
00154     *bufO = '\0';
00155     return ret;
00156 }
00157 
00158 /*
00159  * hyphenate --- Try to hyphenate, in-place, the string starting at bufI
00160  *                into bufO using the given hyphenation range TABLE.
00161  *                Assumes the input string to be used is of only digits.
00162  *
00163  * Returns the number of characters acctually hyphenated.
00164  */
00165 static unsigned
00166 hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2])
00167 {
00168     unsigned    ret = 0;
00169     const char *ean_aux1,
00170                *ean_aux2,
00171                *ean_p;
00172     char       *firstdig,
00173                *aux1,
00174                *aux2;
00175     unsigned    search,
00176                 upper,
00177                 lower,
00178                 step;
00179     bool        ean_in1,
00180                 ean_in2;
00181 
00182     /* just compress the string if no further hyphenation is required */
00183     if (TABLE == NULL || TABLE_index == NULL)
00184     {
00185         while (*bufI)
00186         {
00187             *bufO++ = *bufI++;
00188             ret++;
00189         }
00190         *bufO = '\0';
00191         return (ret + 1);
00192     }
00193 
00194     /* add remaining hyphenations */
00195 
00196     search = *bufI - '0';
00197     upper = lower = TABLE_index[search][0];
00198     upper += TABLE_index[search][1];
00199     lower--;
00200 
00201     step = (upper - lower) / 2;
00202     if (step == 0)
00203         return 0;
00204     search = lower + step;
00205 
00206     firstdig = bufI;
00207     ean_in1 = ean_in2 = false;
00208     ean_aux1 = TABLE[search][0];
00209     ean_aux2 = TABLE[search][1];
00210     do
00211     {
00212         if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2))
00213         {
00214             if (*firstdig > *ean_aux1)
00215                 ean_in1 = true;
00216             if (*firstdig < *ean_aux2)
00217                 ean_in2 = true;
00218             if (ean_in1 && ean_in2)
00219                 break;
00220 
00221             firstdig++, ean_aux1++, ean_aux2++;
00222             if (!(*ean_aux1 && *ean_aux2 && *firstdig))
00223                 break;
00224             if (!isdigit((unsigned char) *ean_aux1))
00225                 ean_aux1++, ean_aux2++;
00226         }
00227         else
00228         {
00229             /*
00230              * check in what direction we should go and move the pointer
00231              * accordingly
00232              */
00233             if (*firstdig < *ean_aux1 && !ean_in1)
00234                 upper = search;
00235             else
00236                 lower = search;
00237 
00238             step = (upper - lower) / 2;
00239             search = lower + step;
00240 
00241             /* Initialize stuff again: */
00242             firstdig = bufI;
00243             ean_in1 = ean_in2 = false;
00244             ean_aux1 = TABLE[search][0];
00245             ean_aux2 = TABLE[search][1];
00246         }
00247     } while (step);
00248 
00249     if (step)
00250     {
00251         aux1 = bufO;
00252         aux2 = bufI;
00253         ean_p = TABLE[search][0];
00254         while (*ean_p && *aux2)
00255         {
00256             if (*ean_p++ != '-')
00257                 *aux1++ = *aux2++;
00258             else
00259                 *aux1++ = '-';
00260             ret++;
00261         }
00262         *aux1++ = '-';
00263         *aux1 = *aux2;          /* add a lookahead char */
00264         return (ret + 1);
00265     }
00266     return ret;
00267 }
00268 
00269 /*
00270  * weight_checkdig -- Receives a buffer with a normalized ISxN string number,
00271  *                     and the length to weight.
00272  *
00273  * Returns the weight of the number (the check digit value, 0-10)
00274  */
00275 static unsigned
00276 weight_checkdig(char *isn, unsigned size)
00277 {
00278     unsigned    weight = 0;
00279 
00280     while (*isn && size > 1)
00281     {
00282         if (isdigit((unsigned char) *isn))
00283         {
00284             weight += size-- * (*isn - '0');
00285         }
00286         isn++;
00287     }
00288     weight = weight % 11;
00289     if (weight != 0)
00290         weight = 11 - weight;
00291     return weight;
00292 }
00293 
00294 
00295 /*
00296  * checkdig --- Receives a buffer with a normalized ISxN string number,
00297  *               and the length to check.
00298  *
00299  * Returns the check digit value (0-9)
00300  */
00301 static unsigned
00302 checkdig(char *num, unsigned size)
00303 {
00304     unsigned    check = 0,
00305                 check3 = 0;
00306     unsigned    pos = 0;
00307 
00308     if (*num == 'M')
00309     {                           /* ISMN start with 'M' */
00310         check3 = 3;
00311         pos = 1;
00312     }
00313     while (*num && size > 1)
00314     {
00315         if (isdigit((unsigned char) *num))
00316         {
00317             if (pos++ % 2)
00318                 check3 += *num - '0';
00319             else
00320                 check += *num - '0';
00321             size--;
00322         }
00323         num++;
00324     }
00325     check = (check + 3 * check3) % 10;
00326     if (check != 0)
00327         check = 10 - check;
00328     return check;
00329 }
00330 
00331 /*
00332  * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number.
00333  *             This doesn't verify for a valid check digit.
00334  *
00335  * If errorOK is false, ereport a useful error message if the ean13 is bad.
00336  * If errorOK is true, just return "false" for bad input.
00337  */
00338 static bool
00339 ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept)
00340 {
00341     enum isn_type type = INVALID;
00342 
00343     char        buf[MAXEAN13LEN + 1];
00344     char       *aux;
00345     unsigned    digval;
00346     unsigned    search;
00347     ean13       ret = ean;
00348 
00349     ean >>= 1;
00350     /* verify it's in the EAN13 range */
00351     if (ean > UINT64CONST(9999999999999))
00352         goto eantoobig;
00353 
00354     /* convert the number */
00355     search = 0;
00356     aux = buf + 13;
00357     *aux = '\0';                /* terminate string; aux points to last digit */
00358     do
00359     {
00360         digval = (unsigned) (ean % 10); /* get the decimal value */
00361         ean /= 10;              /* get next digit */
00362         *--aux = (char) (digval + '0'); /* convert to ascii and store */
00363     } while (ean && search++ < 12);
00364     while (search++ < 12)
00365         *--aux = '0';           /* fill the remaining EAN13 with '0' */
00366 
00367     /* find out the data type: */
00368     if (strncmp("978", buf, 3) == 0)
00369     {                           /* ISBN */
00370         type = ISBN;
00371     }
00372     else if (strncmp("977", buf, 3) == 0)
00373     {                           /* ISSN */
00374         type = ISSN;
00375     }
00376     else if (strncmp("9790", buf, 4) == 0)
00377     {                           /* ISMN */
00378         type = ISMN;
00379     }
00380     else if (strncmp("979", buf, 3) == 0)
00381     {                           /* ISBN-13 */
00382         type = ISBN;
00383     }
00384     else if (*buf == '0')
00385     {                           /* UPC */
00386         type = UPC;
00387     }
00388     else
00389     {
00390         type = EAN13;
00391     }
00392     if (accept != ANY && accept != EAN13 && accept != type)
00393         goto eanwrongtype;
00394 
00395     *result = ret;
00396     return true;
00397 
00398 eanwrongtype:
00399     if (!errorOK)
00400     {
00401         if (type != EAN13)
00402         {
00403             ereport(ERROR,
00404                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00405                      errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"",
00406                             isn_names[type], isn_names[accept], buf)));
00407         }
00408         else
00409         {
00410             ereport(ERROR,
00411                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00412                      errmsg("cannot cast %s to %s for number: \"%s\"",
00413                             isn_names[type], isn_names[accept], buf)));
00414         }
00415     }
00416     return false;
00417 
00418 eantoobig:
00419     if (!errorOK)
00420     {
00421         char        eanbuf[64];
00422 
00423         /*
00424          * Format the number separately to keep the machine-dependent format
00425          * code out of the translatable message text
00426          */
00427         snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
00428         ereport(ERROR,
00429                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
00430                  errmsg("value \"%s\" is out of range for %s type",
00431                         eanbuf, isn_names[type])));
00432     }
00433     return false;
00434 }
00435 
00436 /*
00437  * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding
00438  *                  UPC/ISxN string number. Assumes the input string is normalized.
00439  */
00440 static inline void
00441 ean2ISBN(char *isn)
00442 {
00443     char       *aux;
00444     unsigned    check;
00445 
00446     /* the number should come in this format: 978-0-000-00000-0 */
00447     /* Strip the first part and calculate the new check digit */
00448     hyphenate(isn, isn + 4, NULL, NULL);
00449     check = weight_checkdig(isn, 10);
00450     aux = strchr(isn, '\0');
00451     while (!isdigit((unsigned char) *--aux));
00452     if (check == 10)
00453         *aux = 'X';
00454     else
00455         *aux = check + '0';
00456 }
00457 
00458 static inline void
00459 ean2ISMN(char *isn)
00460 {
00461     /* the number should come in this format: 979-0-000-00000-0 */
00462     /* Just strip the first part and change the first digit ('0') to 'M' */
00463     hyphenate(isn, isn + 4, NULL, NULL);
00464     isn[0] = 'M';
00465 }
00466 
00467 static inline void
00468 ean2ISSN(char *isn)
00469 {
00470     unsigned    check;
00471 
00472     /* the number should come in this format: 977-0000-000-00-0 */
00473     /* Strip the first part, crop, and calculate the new check digit */
00474     hyphenate(isn, isn + 4, NULL, NULL);
00475     check = weight_checkdig(isn, 8);
00476     if (check == 10)
00477         isn[8] = 'X';
00478     else
00479         isn[8] = check + '0';
00480     isn[9] = '\0';
00481 }
00482 
00483 static inline void
00484 ean2UPC(char *isn)
00485 {
00486     /* the number should come in this format: 000-000000000-0 */
00487     /* Strip the first part, crop, and dehyphenate */
00488     dehyphenate(isn, isn + 1);
00489     isn[12] = '\0';
00490 }
00491 
00492 /*
00493  * ean2* --- Converts a string of digits into an ean13 number.
00494  *            Assumes the input string is a string with only digits
00495  *            on it, and that it's within the range of ean13.
00496  *
00497  * Returns the ean13 value of the string.
00498  */
00499 static ean13
00500 str2ean(const char *num)
00501 {
00502     ean13       ean = 0;        /* current ean */
00503 
00504     while (*num)
00505     {
00506         if (isdigit((unsigned char) *num))
00507             ean = 10 * ean + (*num - '0');
00508         num++;
00509     }
00510     return (ean << 1);          /* also give room to a flag */
00511 }
00512 
00513 /*
00514  * ean2string --- Try to convert an ean13 number to an hyphenated string.
00515  *                Assumes there's enough space in result to hold
00516  *                the string (maximum MAXEAN13LEN+1 bytes)
00517  *                This doesn't verify for a valid check digit.
00518  *
00519  * If shortType is true, the returned string is in the old ISxN short format.
00520  * If errorOK is false, ereport a useful error message if the string is bad.
00521  * If errorOK is true, just return "false" for bad input.
00522  */
00523 static bool
00524 ean2string(ean13 ean, bool errorOK, char *result, bool shortType)
00525 {
00526     const char *(*TABLE)[2];
00527     const unsigned (*TABLE_index)[2];
00528     enum isn_type type = INVALID;
00529 
00530     char       *aux;
00531     unsigned    digval;
00532     unsigned    search;
00533     char        valid = '\0';   /* was the number initially written with a
00534                                  * valid check digit? */
00535 
00536     TABLE_index = ISBN_index;
00537 
00538     if ((ean & 1) != 0)
00539         valid = '!';
00540     ean >>= 1;
00541     /* verify it's in the EAN13 range */
00542     if (ean > UINT64CONST(9999999999999))
00543         goto eantoobig;
00544 
00545     /* convert the number */
00546     search = 0;
00547     aux = result + MAXEAN13LEN;
00548     *aux = '\0';                /* terminate string; aux points to last digit */
00549     *--aux = valid;             /* append '!' for numbers with invalid but
00550                                  * corrected check digit */
00551     do
00552     {
00553         digval = (unsigned) (ean % 10); /* get the decimal value */
00554         ean /= 10;              /* get next digit */
00555         *--aux = (char) (digval + '0'); /* convert to ascii and store */
00556         if (search == 0)
00557             *--aux = '-';       /* the check digit is always there */
00558     } while (ean && search++ < 13);
00559     while (search++ < 13)
00560         *--aux = '0';           /* fill the remaining EAN13 with '0' */
00561 
00562     /* The string should be in this form: ???DDDDDDDDDDDD-D" */
00563     search = hyphenate(result, result + 3, EAN13_range, EAN13_index);
00564 
00565     /* verify it's a logically valid EAN13 */
00566     if (search == 0)
00567     {
00568         search = hyphenate(result, result + 3, NULL, NULL);
00569         goto okay;
00570     }
00571 
00572     /* find out what type of hyphenation is needed: */
00573     if (strncmp("978-", result, search) == 0)
00574     {                           /* ISBN -13 978-range */
00575         /* The string should be in this form: 978-??000000000-0" */
00576         type = ISBN;
00577         TABLE = ISBN_range;
00578         TABLE_index = ISBN_index;
00579     }
00580     else if (strncmp("977-", result, search) == 0)
00581     {                           /* ISSN */
00582         /* The string should be in this form: 977-??000000000-0" */
00583         type = ISSN;
00584         TABLE = ISSN_range;
00585         TABLE_index = ISSN_index;
00586     }
00587     else if (strncmp("979-0", result, search + 1) == 0)
00588     {                           /* ISMN */
00589         /* The string should be in this form: 979-0?000000000-0" */
00590         type = ISMN;
00591         TABLE = ISMN_range;
00592         TABLE_index = ISMN_index;
00593     }
00594     else if (strncmp("979-", result, search) == 0)
00595     {                           /* ISBN-13 979-range */
00596         /* The string should be in this form: 979-??000000000-0" */
00597         type = ISBN;
00598         TABLE = ISBN_range_new;
00599         TABLE_index = ISBN_index_new;
00600     }
00601     else if (*result == '0')
00602     {                           /* UPC */
00603         /* The string should be in this form: 000-00000000000-0" */
00604         type = UPC;
00605         TABLE = UPC_range;
00606         TABLE_index = UPC_index;
00607     }
00608     else
00609     {
00610         type = EAN13;
00611         TABLE = NULL;
00612         TABLE_index = NULL;
00613     }
00614 
00615     /* verify it's a logically valid EAN13/UPC/ISxN */
00616     digval = search;
00617     search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index);
00618 
00619     /* verify it's a valid EAN13 */
00620     if (search == 0)
00621     {
00622         search = hyphenate(result + digval, result + digval + 2, NULL, NULL);
00623         goto okay;
00624     }
00625 
00626 okay:
00627     /* convert to the old short type: */
00628     if (shortType)
00629         switch (type)
00630         {
00631             case ISBN:
00632                 ean2ISBN(result);
00633                 break;
00634             case ISMN:
00635                 ean2ISMN(result);
00636                 break;
00637             case ISSN:
00638                 ean2ISSN(result);
00639                 break;
00640             case UPC:
00641                 ean2UPC(result);
00642                 break;
00643             default:
00644                 break;
00645         }
00646     return true;
00647 
00648 eantoobig:
00649     if (!errorOK)
00650     {
00651         char        eanbuf[64];
00652 
00653         /*
00654          * Format the number separately to keep the machine-dependent format
00655          * code out of the translatable message text
00656          */
00657         snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
00658         ereport(ERROR,
00659                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
00660                  errmsg("value \"%s\" is out of range for %s type",
00661                         eanbuf, isn_names[type])));
00662     }
00663     return false;
00664 }
00665 
00666 /*
00667  * string2ean --- try to parse a string into an ean13.
00668  *
00669  * If errorOK is false, ereport a useful error message if the string is bad.
00670  * If errorOK is true, just return "false" for bad input.
00671  *
00672  * if the input string ends with '!' it will always be treated as invalid
00673  * (even if the check digit is valid)
00674  */
00675 static bool
00676 string2ean(const char *str, bool errorOK, ean13 *result,
00677            enum isn_type accept)
00678 {
00679     bool        digit,
00680                 last;
00681     char        buf[17] = "                ";
00682     char       *aux1 = buf + 3; /* leave space for the first part, in case
00683                                  * it's needed */
00684     const char *aux2 = str;
00685     enum isn_type type = INVALID;
00686     unsigned    check = 0,
00687                 rcheck = (unsigned) -1;
00688     unsigned    length = 0;
00689     bool        magic = false,
00690                 valid = true;
00691 
00692     /* recognize and validate the number: */
00693     while (*aux2 && length <= 13)
00694     {
00695         last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0');     /* is the last character */
00696         digit = (isdigit((unsigned char) *aux2) != 0);  /* is current character
00697                                                          * a digit? */
00698         if (*aux2 == '?' && last)       /* automagically calculate check digit
00699                                          * if it's '?' */
00700             magic = digit = true;
00701         if (length == 0 && (*aux2 == 'M' || *aux2 == 'm'))
00702         {
00703             /* only ISMN can be here */
00704             if (type != INVALID)
00705                 goto eaninvalid;
00706             type = ISMN;
00707             *aux1++ = 'M';
00708             length++;
00709         }
00710         else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
00711         {
00712             /* only ISSN can be here */
00713             if (type != INVALID)
00714                 goto eaninvalid;
00715             type = ISSN;
00716             *aux1++ = toupper((unsigned char) *aux2);
00717             length++;
00718         }
00719         else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
00720         {
00721             /* only ISBN and ISMN can be here */
00722             if (type != INVALID && type != ISMN)
00723                 goto eaninvalid;
00724             if (type == INVALID)
00725                 type = ISBN;    /* ISMN must start with 'M' */
00726             *aux1++ = toupper((unsigned char) *aux2);
00727             length++;
00728         }
00729         else if (length == 11 && digit && last)
00730         {
00731             /* only UPC can be here */
00732             if (type != INVALID)
00733                 goto eaninvalid;
00734             type = UPC;
00735             *aux1++ = *aux2;
00736             length++;
00737         }
00738         else if (*aux2 == '-' || *aux2 == ' ')
00739         {
00740             /* skip, we could validate but I think it's worthless */
00741         }
00742         else if (*aux2 == '!' && *(aux2 + 1) == '\0')
00743         {
00744             /* the invalid check digit sufix was found, set it */
00745             if (!magic)
00746                 valid = false;
00747             magic = true;
00748         }
00749         else if (!digit)
00750         {
00751             goto eaninvalid;
00752         }
00753         else
00754         {
00755             *aux1++ = *aux2;
00756             if (++length > 13)
00757                 goto eantoobig;
00758         }
00759         aux2++;
00760     }
00761     *aux1 = '\0';               /* terminate the string */
00762 
00763     /* find the current check digit value */
00764     if (length == 13)
00765     {
00766         /* only EAN13 can be here */
00767         if (type != INVALID)
00768             goto eaninvalid;
00769         type = EAN13;
00770         check = buf[15] - '0';
00771     }
00772     else if (length == 12)
00773     {
00774         /* only UPC can be here */
00775         if (type != UPC)
00776             goto eaninvalid;
00777         check = buf[14] - '0';
00778     }
00779     else if (length == 10)
00780     {
00781         if (type != ISBN && type != ISMN)
00782             goto eaninvalid;
00783         if (buf[12] == 'X')
00784             check = 10;
00785         else
00786             check = buf[12] - '0';
00787     }
00788     else if (length == 8)
00789     {
00790         if (type != INVALID && type != ISSN)
00791             goto eaninvalid;
00792         type = ISSN;
00793         if (buf[10] == 'X')
00794             check = 10;
00795         else
00796             check = buf[10] - '0';
00797     }
00798     else
00799         goto eaninvalid;
00800 
00801     if (type == INVALID)
00802         goto eaninvalid;
00803 
00804     /* obtain the real check digit value, validate, and convert to ean13: */
00805     if (accept == EAN13 && type != accept)
00806         goto eanwrongtype;
00807     if (accept != ANY && type != EAN13 && type != accept)
00808         goto eanwrongtype;
00809     switch (type)
00810     {
00811         case EAN13:
00812             valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic));
00813             /* now get the subtype of EAN13: */
00814             if (buf[3] == '0')
00815                 type = UPC;
00816             else if (strncmp("977", buf + 3, 3) == 0)
00817                 type = ISSN;
00818             else if (strncmp("978", buf + 3, 3) == 0)
00819                 type = ISBN;
00820             else if (strncmp("9790", buf + 3, 4) == 0)
00821                 type = ISMN;
00822             else if (strncmp("979", buf + 3, 3) == 0)
00823                 type = ISBN;
00824             if (accept != EAN13 && accept != ANY && type != accept)
00825                 goto eanwrongtype;
00826             break;
00827         case ISMN:
00828             strncpy(buf, "9790", 4);    /* this isn't for sure yet, for now
00829                                          * ISMN it's only 9790 */
00830             valid = (valid && ((rcheck = checkdig(buf + 3, 10)) == check || magic));
00831             break;
00832         case ISBN:
00833             strncpy(buf, "978", 3);
00834             valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic));
00835             break;
00836         case ISSN:
00837             strncpy(buf + 10, "00", 2); /* append 00 as the normal issue
00838                                          * publication code */
00839             strncpy(buf, "977", 3);
00840             valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic));
00841             break;
00842         case UPC:
00843             buf[2] = '0';
00844             valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic));
00845         default:
00846             break;
00847     }
00848 
00849     /* fix the check digit: */
00850     for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
00851     aux1[12] = checkdig(aux1, 13) + '0';
00852     aux1[13] = '\0';
00853 
00854     if (!valid && !magic)
00855         goto eanbadcheck;
00856 
00857     *result = str2ean(aux1);
00858     *result |= valid ? 0 : 1;
00859     return true;
00860 
00861 eanbadcheck:
00862     if (g_weak)
00863     {                           /* weak input mode is activated: */
00864         /* set the "invalid-check-digit-on-input" flag */
00865         *result = str2ean(aux1);
00866         *result |= 1;
00867         return true;
00868     }
00869 
00870     if (!errorOK)
00871     {
00872         if (rcheck == (unsigned) -1)
00873         {
00874             ereport(ERROR,
00875                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00876                      errmsg("invalid %s number: \"%s\"",
00877                             isn_names[accept], str)));
00878         }
00879         else
00880         {
00881             ereport(ERROR,
00882                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00883             errmsg("invalid check digit for %s number: \"%s\", should be %c",
00884                    isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0'))));
00885         }
00886     }
00887     return false;
00888 
00889 eaninvalid:
00890     if (!errorOK)
00891         ereport(ERROR,
00892                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00893                  errmsg("invalid input syntax for %s number: \"%s\"",
00894                         isn_names[accept], str)));
00895     return false;
00896 
00897 eanwrongtype:
00898     if (!errorOK)
00899         ereport(ERROR,
00900                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
00901                  errmsg("cannot cast %s to %s for number: \"%s\"",
00902                         isn_names[type], isn_names[accept], str)));
00903     return false;
00904 
00905 eantoobig:
00906     if (!errorOK)
00907         ereport(ERROR,
00908                 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
00909                  errmsg("value \"%s\" is out of range for %s type",
00910                         str, isn_names[accept])));
00911     return false;
00912 }
00913 
00914 /*----------------------------------------------------------
00915  * Exported routines.
00916  *---------------------------------------------------------*/
00917 
00918 void
00919 initialize(void)
00920 {
00921 #ifdef ISN_DEBUG
00922     if (!check_table(EAN13, EAN13_index))
00923         elog(LOG, "EAN13 failed check");
00924     if (!check_table(ISBN, ISBN_index))
00925         elog(LOG, "ISBN failed check");
00926     if (!check_table(ISMN, ISMN_index))
00927         elog(LOG, "ISMN failed check");
00928     if (!check_table(ISSN, ISSN_index))
00929         elog(LOG, "ISSN failed check");
00930     if (!check_table(UPC, UPC_index))
00931         elog(LOG, "UPC failed check");
00932 #endif
00933     g_initialized = true;
00934 }
00935 
00936 /* isn_out
00937  */
00938 PG_FUNCTION_INFO_V1(isn_out);
00939 Datum
00940 isn_out(PG_FUNCTION_ARGS)
00941 {
00942     ean13       val = PG_GETARG_EAN13(0);
00943     char       *result;
00944     char        buf[MAXEAN13LEN + 1];
00945 
00946     (void) ean2string(val, false, buf, true);
00947 
00948     result = pstrdup(buf);
00949     PG_RETURN_CSTRING(result);
00950 }
00951 
00952 /* ean13_out
00953  */
00954 PG_FUNCTION_INFO_V1(ean13_out);
00955 Datum
00956 ean13_out(PG_FUNCTION_ARGS)
00957 {
00958     ean13       val = PG_GETARG_EAN13(0);
00959     char       *result;
00960     char        buf[MAXEAN13LEN + 1];
00961 
00962     (void) ean2string(val, false, buf, false);
00963 
00964     result = pstrdup(buf);
00965     PG_RETURN_CSTRING(result);
00966 }
00967 
00968 /* ean13_in
00969  */
00970 PG_FUNCTION_INFO_V1(ean13_in);
00971 Datum
00972 ean13_in(PG_FUNCTION_ARGS)
00973 {
00974     const char *str = PG_GETARG_CSTRING(0);
00975     ean13       result;
00976 
00977     (void) string2ean(str, false, &result, EAN13);
00978     PG_RETURN_EAN13(result);
00979 }
00980 
00981 /* isbn_in
00982  */
00983 PG_FUNCTION_INFO_V1(isbn_in);
00984 Datum
00985 isbn_in(PG_FUNCTION_ARGS)
00986 {
00987     const char *str = PG_GETARG_CSTRING(0);
00988     ean13       result;
00989 
00990     (void) string2ean(str, false, &result, ISBN);
00991     PG_RETURN_EAN13(result);
00992 }
00993 
00994 /* ismn_in
00995  */
00996 PG_FUNCTION_INFO_V1(ismn_in);
00997 Datum
00998 ismn_in(PG_FUNCTION_ARGS)
00999 {
01000     const char *str = PG_GETARG_CSTRING(0);
01001     ean13       result;
01002 
01003     (void) string2ean(str, false, &result, ISMN);
01004     PG_RETURN_EAN13(result);
01005 }
01006 
01007 /* issn_in
01008  */
01009 PG_FUNCTION_INFO_V1(issn_in);
01010 Datum
01011 issn_in(PG_FUNCTION_ARGS)
01012 {
01013     const char *str = PG_GETARG_CSTRING(0);
01014     ean13       result;
01015 
01016     (void) string2ean(str, false, &result, ISSN);
01017     PG_RETURN_EAN13(result);
01018 }
01019 
01020 /* upc_in
01021  */
01022 PG_FUNCTION_INFO_V1(upc_in);
01023 Datum
01024 upc_in(PG_FUNCTION_ARGS)
01025 {
01026     const char *str = PG_GETARG_CSTRING(0);
01027     ean13       result;
01028 
01029     (void) string2ean(str, false, &result, UPC);
01030     PG_RETURN_EAN13(result);
01031 }
01032 
01033 /* casting functions
01034 */
01035 PG_FUNCTION_INFO_V1(isbn_cast_from_ean13);
01036 Datum
01037 isbn_cast_from_ean13(PG_FUNCTION_ARGS)
01038 {
01039     ean13       val = PG_GETARG_EAN13(0);
01040     ean13       result;
01041 
01042     (void) ean2isn(val, false, &result, ISBN);
01043 
01044     PG_RETURN_EAN13(result);
01045 }
01046 
01047 PG_FUNCTION_INFO_V1(ismn_cast_from_ean13);
01048 Datum
01049 ismn_cast_from_ean13(PG_FUNCTION_ARGS)
01050 {
01051     ean13       val = PG_GETARG_EAN13(0);
01052     ean13       result;
01053 
01054     (void) ean2isn(val, false, &result, ISMN);
01055 
01056     PG_RETURN_EAN13(result);
01057 }
01058 
01059 PG_FUNCTION_INFO_V1(issn_cast_from_ean13);
01060 Datum
01061 issn_cast_from_ean13(PG_FUNCTION_ARGS)
01062 {
01063     ean13       val = PG_GETARG_EAN13(0);
01064     ean13       result;
01065 
01066     (void) ean2isn(val, false, &result, ISSN);
01067 
01068     PG_RETURN_EAN13(result);
01069 }
01070 
01071 PG_FUNCTION_INFO_V1(upc_cast_from_ean13);
01072 Datum
01073 upc_cast_from_ean13(PG_FUNCTION_ARGS)
01074 {
01075     ean13       val = PG_GETARG_EAN13(0);
01076     ean13       result;
01077 
01078     (void) ean2isn(val, false, &result, UPC);
01079 
01080     PG_RETURN_EAN13(result);
01081 }
01082 
01083 
01084 /* is_valid - returns false if the "invalid-check-digit-on-input" is set
01085  */
01086 PG_FUNCTION_INFO_V1(is_valid);
01087 Datum
01088 is_valid(PG_FUNCTION_ARGS)
01089 {
01090     ean13       val = PG_GETARG_EAN13(0);
01091 
01092     PG_RETURN_BOOL((val & 1) == 0);
01093 }
01094 
01095 /* make_valid - unsets the "invalid-check-digit-on-input" flag
01096  */
01097 PG_FUNCTION_INFO_V1(make_valid);
01098 Datum
01099 make_valid(PG_FUNCTION_ARGS)
01100 {
01101     ean13       val = PG_GETARG_EAN13(0);
01102 
01103     val &= ~((ean13) 1);
01104     PG_RETURN_EAN13(val);
01105 }
01106 
01107 /* this function temporarily sets weak input flag
01108  * (to lose the strictness of check digit acceptance)
01109  * It's a helper function, not intended to be used!!
01110  */
01111 PG_FUNCTION_INFO_V1(accept_weak_input);
01112 Datum
01113 accept_weak_input(PG_FUNCTION_ARGS)
01114 {
01115 #ifdef ISN_WEAK_MODE
01116     g_weak = PG_GETARG_BOOL(0);
01117 #else
01118     /* function has no effect */
01119 #endif   /* ISN_WEAK_MODE */
01120     PG_RETURN_BOOL(g_weak);
01121 }
01122 
01123 PG_FUNCTION_INFO_V1(weak_input_status);
01124 Datum
01125 weak_input_status(PG_FUNCTION_ARGS)
01126 {
01127     PG_RETURN_BOOL(g_weak);
01128 }