2  * SPDX-License-Identifier: GPL-2.0-only
 
   4  * This file is part of Nominatim. (https://nominatim.org)
 
   6  * Copyright (C) 2022 by the Nominatim developer community.
 
   7  * For a full list of authors see the git log.
 
  11 #include "mb/pg_wchar.h"
 
  12 #include <utfasciitable.h>
 
  14 #ifdef PG_MODULE_MAGIC
 
  18 Datum transliteration( PG_FUNCTION_ARGS );
 
  19 Datum gettokenstring( PG_FUNCTION_ARGS );
 
  20 void str_replace(char* buffer, int* len, int* changes, char* from, int fromlen, char* to, int tolen, int);
 
  21 void str_dupspaces(char* buffer);
 
  23 PG_FUNCTION_INFO_V1( transliteration );
 
  25 transliteration( PG_FUNCTION_ARGS )
 
  27         static char * ascii = UTFASCII;
 
  28         static uint16 asciilookup[65536] = UTFASCIILOOKUP;
 
  32         unsigned char *sourcedata;
 
  35         unsigned int c1,c2,c3,c4;
 
  36         unsigned int * wchardata;
 
  37         unsigned int * wchardatastart;
 
  40         unsigned char *resultdata;
 
  44         if (GetDatabaseEncoding() != PG_UTF8) 
 
  47                                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 
  48                                          errmsg("requires UTF8 database encoding")));
 
  56         // The original string
 
  57         source = PG_GETARG_TEXT_P(0);
 
  58         sourcedata = (unsigned char *)VARDATA(source);
 
  59         sourcedatalength = VARSIZE(source) - VARHDRSZ;
 
  61         // Intermediate wchar version of string
 
  62         wchardatastart = wchardata = (unsigned int *)palloc((sourcedatalength+1)*sizeof(int));
 
  64         // Based on pg_utf2wchar_with_len from wchar.c
 
  65         // Postgresql strings are not zero terminalted
 
  66         while (sourcedatalength > 0)
 
  68                 if ((*sourcedata & 0x80) == 0)
 
  70                         *wchardata = *sourcedata++;
 
  74                 else if ((*sourcedata & 0xe0) == 0xc0)
 
  76                         if (sourcedatalength < 2) break;
 
  77                         c1 = *sourcedata++ & 0x1f;
 
  78                         c2 = *sourcedata++ & 0x3f;
 
  79                         *wchardata = (c1 << 6) | c2;
 
  80                         if (*wchardata < 65536) wchardata++;
 
  81                         sourcedatalength -= 2;
 
  83                 else if ((*sourcedata & 0xf0) == 0xe0)
 
  85                         if (sourcedatalength < 3) break;
 
  86                         c1 = *sourcedata++ & 0x0f;
 
  87                         c2 = *sourcedata++ & 0x3f;
 
  88                         c3 = *sourcedata++ & 0x3f;
 
  89                         *wchardata = (c1 << 12) | (c2 << 6) | c3;
 
  90                         if (*wchardata < 65536) wchardata++;
 
  91                         sourcedatalength -= 3;
 
  93                 else if ((*sourcedata & 0xf8) == 0xf0)
 
  95                         if (sourcedatalength < 4) break;
 
  96                         c1 = *sourcedata++ & 0x07;
 
  97                         c2 = *sourcedata++ & 0x3f;
 
  98                         c3 = *sourcedata++ & 0x3f;
 
  99                         c4 = *sourcedata++ & 0x3f;
 
 100                         *wchardata = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
 
 101                         if (*wchardata < 65536) wchardata++;
 
 102                         sourcedatalength -= 4;
 
 104                 else if ((*sourcedata & 0xfc) == 0xf8)
 
 106                         // table does not extend beyond 4 char long, just skip
 
 107                         if (sourcedatalength < 5) break;
 
 108                         sourcedatalength -= 5;
 
 111                 else if ((*sourcedata & 0xfe) == 0xfc)
 
 113                         // table does not extend beyond 4 char long, just skip
 
 114                         if (sourcedatalength < 6) break;
 
 115                         sourcedatalength -= 6;
 
 120                         // assume lenngth 1, silently drop bogus characters
 
 127         // calc the length of transliteration string
 
 128         resultdatalength = 0;
 
 129         wchardata = wchardatastart;
 
 132                 if (*(asciilookup + *wchardata) > 0) resultdatalength += *(ascii + *(asciilookup + *wchardata));
 
 136         // allocate & create the result
 
 137         result = (text *)palloc(resultdatalength + VARHDRSZ);
 
 138         SET_VARSIZE(result, resultdatalength + VARHDRSZ);
 
 139         resultdata = (unsigned char *)VARDATA(result);
 
 141         wchardata = wchardatastart;
 
 144                 if (*(asciilookup + *wchardata) > 0)
 
 146                         asciipos = ascii + *(asciilookup + *wchardata);
 
 147                         for(iLen = *asciipos; iLen > 0; iLen--)
 
 150                                 *resultdata = *asciipos;
 
 156                         ereport( WARNING, ( errcode( ERRCODE_SUCCESSFUL_COMPLETION ),
 
 157                               errmsg( "missing char: %i\n", *wchardata )));
 
 163         pfree(wchardatastart);
 
 165         PG_RETURN_TEXT_P(result);
 
 168 // Set isspace=1 if the replacement _only_ adds a space before the search string.  I.e. to == " " + from
 
 169 void str_replace(char* buffer, int* len, int* changes, char* from, int fromlen, char* to, int tolen, int isspace)
 
 173         // Search string is too long to be present
 
 174         if (fromlen > *len) return;
 
 176         p = strstr(buffer, from);
 
 179                 if (!isspace || (p > buffer && *(p-1) != ' '))
 
 182                         if (tolen != fromlen) memmove(p+tolen, p+fromlen, *len-(p-buffer)+1);
 
 183                         memcpy(p, to, tolen);
 
 184                         *len += tolen - fromlen;
 
 186                 p = strstr(p+1, from);
 
 190 void str_dupspaces(char* buffer)
 
 199                 if (wasspace && *buffer != ' ') wasspace = 0;
 
 204                         wasspace = (*buffer == ' ');
 
 211 PG_FUNCTION_INFO_V1( gettokenstring );
 
 213 gettokenstring( PG_FUNCTION_ARGS )
 
 216         unsigned char *sourcedata;
 
 217         int sourcedatalength;
 
 225         if (GetDatabaseEncoding() != PG_UTF8) 
 
 228                                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 
 229                                          errmsg("requires UTF8 database encoding")));
 
 237         // The original string
 
 238         source = PG_GETARG_TEXT_P(0);
 
 239         sourcedata = (unsigned char *)VARDATA(source);
 
 240         sourcedatalength = VARSIZE(source) - VARHDRSZ;
 
 242         // Buffer for doing the replace in - string could get slightly longer (double is massive overkill)
 
 243         buffer = (char *)palloc((sourcedatalength*2)*sizeof(char));
 
 244         memcpy(buffer+1, sourcedata, sourcedatalength);
 
 246         buffer[sourcedatalength+1] = 32;
 
 247         buffer[sourcedatalength+2] = 0;
 
 248         len = sourcedatalength+3;
 
 251         str_dupspaces(buffer);
 
 255                 #include <tokenstringreplacements.inc>
 
 256                 str_dupspaces(buffer);
 
 259         // 'and' in various languages
 
 260         str_replace(buffer, &len, &changes, " and ", 5, " ", 1, 0);
 
 261         str_replace(buffer, &len, &changes, " und ", 5, " ", 1, 0);
 
 262         str_replace(buffer, &len, &changes, " en ", 4, " ", 1, 0);
 
 263         str_replace(buffer, &len, &changes, " et ", 4, " ", 1, 0);
 
 264         str_replace(buffer, &len, &changes, " y ", 3, " ", 1, 0);
 
 266         // 'the' (and similar)
 
 267         str_replace(buffer, &len, &changes, " the ", 5, " ", 1, 0);
 
 268         str_replace(buffer, &len, &changes, " der ", 5, " ", 1, 0);
 
 269         str_replace(buffer, &len, &changes, " den ", 5, " ", 1, 0);
 
 270         str_replace(buffer, &len, &changes, " die ", 5, " ", 1, 0);
 
 271         str_replace(buffer, &len, &changes, " das ", 5, " ", 1, 0);
 
 272         str_replace(buffer, &len, &changes, " la ", 4, " ", 1, 0);
 
 273         str_replace(buffer, &len, &changes, " le ", 4, " ", 1, 0);
 
 274         str_replace(buffer, &len, &changes, " el ", 4, " ", 1, 0);
 
 275         str_replace(buffer, &len, &changes, " il ", 4, " ", 1, 0);
 
 278         str_replace(buffer, &len, &changes, "ae", 2, "a", 1, 0);
 
 279         str_replace(buffer, &len, &changes, "oe", 2, "o", 1, 0);
 
 280         str_replace(buffer, &len, &changes, "ue", 2, "u", 1, 0);
 
 281         str_replace(buffer, &len, &changes, "sss", 3, "ss", 2, 0);
 
 282         str_replace(buffer, &len, &changes, "ih", 2, "i", 1, 0);
 
 283         str_replace(buffer, &len, &changes, "eh", 2, "e", 1, 0);
 
 286         str_replace(buffer, &len, &changes, "ie", 2, "i", 1, 0);
 
 287         str_replace(buffer, &len, &changes, "yi", 2, "i", 1, 0);
 
 289         // allocate & create the result
 
 290         len--;// Drop the terminating zero
 
 291         result = (text *)palloc(len + VARHDRSZ);
 
 292         SET_VARSIZE(result, len + VARHDRSZ);
 
 293         memcpy(VARDATA(result), buffer, len);
 
 297         PG_RETURN_TEXT_P(result);