sourcepawn/compiler/sci18n.c
author Scott Ehlert <ds@alliedmods.net>
Thu Apr 05 10:55:40 2007 +0000 (2007-04-05)
changeset 669 ce01c2eb020c
parent 365 d830b2b0414f
permissions -rw-r--r--
Various things:
- Fixed inline translations when using %T
- "Improved" caching of server language
- Renamed Translator::GetServerLanguageCode() to GetServerLanguage()
- Added some more comments to sm_globals.h
- Natives folder in project file mysteriously was moved inside Interfaces, so moved it back o_O
- DS hates empty if statements :o
dvander@27
     1
/*  Codepage translation to Unicode, and UTF-8 support
dvander@27
     2
 *
dvander@27
     3
 *  The translation is based on codepage mapping files that are distributed
dvander@27
     4
 *  by the Unicode consortium, see ftp://ftp.unicode.org/Public/MAPPINGS/.
dvander@27
     5
 *
dvander@27
     6
 *  Character sets with a maximum of 256 codes are translated via a lookup
dvander@27
     7
 *  table (these are Single-Byte Character Sets). Character sets like Shift-JIS
dvander@27
     8
 *  with single-byte characters and multi-byte characters (introduced by a
dvander@27
     9
 *  leader byte) are split into two tables: the 256-entry lookup table for
dvander@27
    10
 *  the single-byte characters and an extended table for the multi-byte
dvander@27
    11
 *  characters. The extended table is allocated dynamically; the lookup table
dvander@27
    12
 *  is allocated statically, so loading SBCS tables cannot fail (if the tables
dvander@27
    13
 *  themselves are valid, of course).
dvander@27
    14
 *
dvander@27
    15
 *  Copyright (c) ITB CompuPhase, 2004-2006
dvander@27
    16
 *
dvander@27
    17
 *  This software is provided "as-is", without any express or implied warranty.
dvander@27
    18
 *  In no event will the authors be held liable for any damages arising from
dvander@27
    19
 *  the use of this software.
dvander@27
    20
 *
dvander@27
    21
 *  Permission is granted to anyone to use this software for any purpose,
dvander@27
    22
 *  including commercial applications, and to alter it and redistribute it
dvander@27
    23
 *  freely, subject to the following restrictions:
dvander@27
    24
 *
dvander@27
    25
 *  1.  The origin of this software must not be misrepresented; you must not
dvander@27
    26
 *      claim that you wrote the original software. If you use this software in
dvander@27
    27
 *      a product, an acknowledgment in the product documentation would be
dvander@27
    28
 *      appreciated but is not required.
dvander@27
    29
 *  2.  Altered source versions must be plainly marked as such, and must not be
dvander@27
    30
 *      misrepresented as being the original software.
dvander@27
    31
 *  3.  This notice may not be removed or altered from any source distribution.
dvander@27
    32
 *
dvander@365
    33
 *  Version: $Id$
dvander@27
    34
 */
dvander@27
    35
#include <assert.h>
dvander@27
    36
#include <stdio.h>
dvander@27
    37
#include <stddef.h>
dvander@27
    38
#include <stdlib.h>
dvander@27
    39
#include <string.h>
dvander@27
    40
#include "sc.h"
dvander@27
    41
dvander@27
    42
#if !defined TRUE
dvander@27
    43
  #define FALSE         0
dvander@27
    44
  #define TRUE          1
dvander@27
    45
#endif
dvander@27
    46
#if !defined _MAX_PATH
dvander@27
    47
  #define _MAX_PATH     250
dvander@27
    48
#endif
dvander@27
    49
#if !defined DIRSEP_CHAR
dvander@27
    50
  #if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__
dvander@27
    51
    #define DIRSEP_CHAR '/'
dvander@27
    52
  #elif defined macintosh
dvander@27
    53
    #define DIRSEP_CHAR ':'
dvander@27
    54
  #else
dvander@27
    55
    #define DIRSEP_CHAR '\\'
dvander@27
    56
  #endif
dvander@27
    57
#endif
dvander@27
    58
dvander@27
    59
#if !defined ELEMENTS
dvander@27
    60
  #define ELEMENTS(array)       (sizeof(array) / sizeof(array[0]))
dvander@27
    61
#endif
dvander@27
    62
dvander@27
    63
#if !defined NO_CODEPAGE
dvander@27
    64
dvander@27
    65
#if !defined MAXCODEPAGE
dvander@27
    66
  #define MAXCODEPAGE   12      /* typically "cp" + 4 digits + ".txt" */
dvander@27
    67
#endif
dvander@27
    68
#define INVALID         0xffffu /* 0xffff and 0xfffe are invalid Unicode characters */
dvander@27
    69
#define LEADBYTE        0xfffeu
dvander@27
    70
dvander@27
    71
struct wordpair {
dvander@27
    72
  unsigned short index;
dvander@27
    73
  wchar_t code;
dvander@27
    74
};
dvander@27
    75
static char cprootpath[_MAX_PATH] = { DIRSEP_CHAR, '\0' };
dvander@27
    76
static wchar_t bytetable[256];
dvander@27
    77
static struct wordpair *wordtable = NULL;
dvander@27
    78
static unsigned wordtablesize = 0;
dvander@27
    79
static unsigned wordtabletop = 0;
dvander@27
    80
dvander@27
    81
dvander@27
    82
/* read in a line delimited by '\r' or '\n'; do NOT store the '\r' or '\n' into
dvander@27
    83
 * the string and ignore empty lines
dvander@27
    84
 * returns 1 for success and 0 for failure
dvander@27
    85
 */
dvander@27
    86
static int cp_readline(FILE *fp,char *string,size_t size)
dvander@27
    87
{
dvander@27
    88
  size_t count=0;
dvander@27
    89
  int c;
dvander@27
    90
  assert(size>1);
dvander@27
    91
  while ((c=fgetc(fp))!=EOF && count<size-1) {
dvander@27
    92
    if (c=='\r' || c=='\n') {
dvander@27
    93
      if (count>0)  /* '\r' or '\n' ends a string */
dvander@27
    94
        break;
dvander@27
    95
      /* if count==0, the line started with a '\r' or '\n', or perhaps line
dvander@27
    96
       * ends in the file are '\r\n' and we read and stopped on the '\r' of
dvander@27
    97
       * the preceding line
dvander@27
    98
       */
dvander@27
    99
    } else {
dvander@27
   100
      string[count++]=(char)c;
dvander@27
   101
    } /* if */
dvander@27
   102
  } /* while */
dvander@27
   103
  string[count]='\0';
dvander@27
   104
  return count>0;
dvander@27
   105
}
dvander@27
   106
dvander@27
   107
/* cp_path() sets the directory where all codepage files must be found (if
dvander@27
   108
 * the parameter to cp_set() specifies a full path, that is used instead).
dvander@27
   109
 * The path is specified into two parts: root and directory; the full path
dvander@27
   110
 * for the codepage direcory is just the concatenation of the two, with a
dvander@27
   111
 * directory separator in between. The directory is given in two parts,
dvander@27
   112
 * because often a program already retrieves its "home" directory and the
dvander@27
   113
 * codepages are most conveniently stored in a subdirectory of this home
dvander@27
   114
 * directory.
dvander@27
   115
 */
dvander@27
   116
SC_FUNC int cp_path(const char *root, const char *directory)
dvander@27
   117
{
dvander@27
   118
  size_t len1,len2;
dvander@27
   119
  int add_slash1,add_slash2;
dvander@27
   120
dvander@27
   121
  len1= (root!=NULL) ? strlen(root) : 0;
dvander@27
   122
  add_slash1= (len1==0 || root[len1-1]!=DIRSEP_CHAR);
dvander@27
   123
  len2= (directory!=NULL) ? strlen(directory) : 0;
dvander@27
   124
  add_slash2= (len2>0 && root[len2-1]!=DIRSEP_CHAR);
dvander@27
   125
  if (len1+add_slash1+len2+add_slash2>=(_MAX_PATH-MAXCODEPAGE))
dvander@27
   126
    return FALSE;       /* full filename may not fit */
dvander@27
   127
  if (root!=NULL)
dvander@27
   128
    strcpy(cprootpath,root);
dvander@27
   129
  if (add_slash1) {
dvander@27
   130
    assert(len1==0 || cprootpath[len1]=='\0');
dvander@27
   131
    cprootpath[len1]=DIRSEP_CHAR;
dvander@27
   132
    cprootpath[len1+1]='\0';
dvander@27
   133
  } /* if */
dvander@27
   134
  if (directory!=NULL)
dvander@27
   135
    strcat(cprootpath,directory);
dvander@27
   136
  if (add_slash2) {
dvander@27
   137
    assert(cprootpath[len1+add_slash1+len2]=='\0');
dvander@27
   138
    cprootpath[len1+add_slash1+len2]=DIRSEP_CHAR;
dvander@27
   139
    cprootpath[len1+add_slash1+len2+1]='\0';
dvander@27
   140
  } /* if */
dvander@27
   141
  cp_set(NULL);         /* start with a "linear" table (no translation) */
dvander@27
   142
  return TRUE;
dvander@27
   143
}
dvander@27
   144
dvander@27
   145
/* cp_set() loads a codepage from a file. The name parameter may be a
dvander@27
   146
 * filename (including a full path) or it may be a partial codepage name.
dvander@27
   147
 * If the name parameter is NULL, the codepage is cleared to be a "linear"
dvander@27
   148
 * table (no translation).
dvander@27
   149
 * The following files are attempted to open (where <name> specifies the
dvander@27
   150
 * value of the parameter):
dvander@27
   151
 *    <name>
dvander@27
   152
 *    <cprootpath>/<name>
dvander@27
   153
 *    <cprootpath>/<name>.txt
dvander@27
   154
 *    <cprootpath>/cp<name>
dvander@27
   155
 *    <cprootpath>/cp<name>.txt
dvander@27
   156
 */
dvander@27
   157
SC_FUNC int cp_set(const char *name)
dvander@27
   158
{
dvander@27
   159
  char filename[_MAX_PATH];
dvander@27
   160
  FILE *fp=NULL;
dvander@27
   161
  unsigned index;
dvander@27
   162
dvander@27
   163
  /* for name==NULL, set up an identity table */
dvander@27
   164
  if (name==NULL || *name=='\0') {
dvander@27
   165
    if (wordtable!=NULL) {
dvander@27
   166
      free(wordtable);
dvander@27
   167
      wordtable=NULL;
dvander@27
   168
      wordtablesize=0;
dvander@27
   169
      wordtabletop=0;
dvander@27
   170
    } /* if */
dvander@27
   171
    for (index=0; index<ELEMENTS(bytetable); index++)
dvander@27
   172
      bytetable[index]=(wchar_t)index;
dvander@27
   173
    return TRUE;
dvander@27
   174
  } /* if */
dvander@27
   175
dvander@27
   176
  /* try to open the file as-is */
dvander@27
   177
  if (strchr(name,DIRSEP_CHAR)!=NULL)
dvander@27
   178
    fp=fopen(name,"rt");
dvander@27
   179
  if (fp==NULL) {
dvander@27
   180
    /* try opening the file in the "root path" for codepages */
dvander@27
   181
    if (strlen(name)>MAXCODEPAGE)
dvander@27
   182
      return 0;
dvander@27
   183
    assert(strlen(name)+strlen(cprootpath)<_MAX_PATH);
dvander@27
   184
    strcpy(filename,cprootpath);
dvander@27
   185
    strcat(filename,name);
dvander@27
   186
    fp=fopen(filename,"rt");
dvander@27
   187
  } /* if */
dvander@27
   188
  if (fp==NULL) {
dvander@27
   189
    /* try opening the file in the "root path" for codepages, with a ".txt" extension */
dvander@27
   190
    if (strlen(name)+4>=MAXCODEPAGE)
dvander@27
   191
      return 0;
dvander@27
   192
    assert(strlen(filename)+4<_MAX_PATH);
dvander@27
   193
    strcat(filename,".txt");
dvander@27
   194
    fp=fopen(filename,"rt");
dvander@27
   195
  } /* if */
dvander@27
   196
  if (fp==NULL) {
dvander@27
   197
    /* try opening the file in the "root path" for codepages, with "cp" prefixed before the name */
dvander@27
   198
    if (strlen(name)+2>MAXCODEPAGE)
dvander@27
   199
      return 0;
dvander@27
   200
    assert(2+strlen(name)+strlen(cprootpath)<_MAX_PATH);
dvander@27
   201
    strcpy(filename,cprootpath);
dvander@27
   202
    strcat(filename,"cp");
dvander@27
   203
    strcat(filename,name);
dvander@27
   204
    fp=fopen(filename,"rt");
dvander@27
   205
  } /* if */
dvander@27
   206
  if (fp==NULL) {
dvander@27
   207
    /* try opening the file in the "root path" for codepages, with "cp" prefixed an ".txt" appended */
dvander@27
   208
    if (strlen(name)+2+4>MAXCODEPAGE)
dvander@27
   209
      return 0;
dvander@27
   210
    assert(strlen(filename)+4<_MAX_PATH);
dvander@27
   211
    strcat(filename,".txt");
dvander@27
   212
    fp=fopen(filename,"rt");
dvander@27
   213
  } /* if */
dvander@27
   214
  if (fp==NULL)
dvander@27
   215
    return FALSE;       /* all failed */
dvander@27
   216
dvander@27
   217
  /* clear the tables */
dvander@27
   218
  for (index=0; index<ELEMENTS(bytetable); index++)
dvander@27
   219
    bytetable[index]=INVALID;   /* special code meaning "not found" */
dvander@27
   220
  assert(wordtablesize==0 && wordtabletop==0 && wordtable==NULL
dvander@27
   221
         || wordtablesize>0 && wordtable!=NULL);
dvander@27
   222
  if (wordtable!=NULL) {
dvander@27
   223
    free(wordtable);
dvander@27
   224
    wordtable=NULL;
dvander@27
   225
    wordtablesize=0;
dvander@27
   226
    wordtabletop=0;
dvander@27
   227
  } /* if */
dvander@27
   228
dvander@27
   229
  /* read in the table */
dvander@27
   230
  while (cp_readline(fp,filename,sizeof filename)) {
dvander@27
   231
    char *ptr;
dvander@27
   232
    if ((ptr=strchr(filename,'#'))!=NULL)
dvander@27
   233
      *ptr='\0';                /* strip of comment */
dvander@27
   234
    for (ptr=filename; *ptr>0 && *ptr<' '; ptr++)
dvander@27
   235
      /* nothing */;            /* skip leading whitespace */
dvander@27
   236
    if (*ptr!='\0') {
dvander@27
   237
      /* content on line */
dvander@27
   238
      unsigned code=LEADBYTE;
dvander@27
   239
      int num=sscanf(ptr,"%i %i",&index,&code);
dvander@27
   240
      /* if sscanf() returns 1 and the index is in range 0..255, then the
dvander@27
   241
       * code is a DBCS lead byte; if sscanf() returns 2 and index>=256, this
dvander@27
   242
       * is a double byte pair (lead byte + follower)
dvander@27
   243
       */
dvander@27
   244
      if (num>=1 && index<256) {
dvander@27
   245
        bytetable[index]=(wchar_t)code;
dvander@27
   246
      } else if (num==2 && index>=256 && index<LEADBYTE) {
dvander@27
   247
        /* store the DBCS character in wordtable */
dvander@27
   248
        if (wordtabletop>=wordtablesize) {
dvander@27
   249
          /* grow the list */
dvander@27
   250
          int newsize;
dvander@27
   251
          struct wordpair *newblock;
dvander@27
   252
          newsize= (wordtablesize==0) ? 128 : 2*wordtablesize;
dvander@27
   253
          newblock=(struct wordpair *)malloc(newsize*sizeof(*wordtable));
dvander@27
   254
          if (newblock!=NULL) {
dvander@27
   255
            memcpy(newblock,wordtable,wordtabletop*sizeof(*wordtable));
dvander@27
   256
            free(wordtable);
dvander@27
   257
            wordtable=newblock;
dvander@27
   258
            wordtablesize=newsize;
dvander@27
   259
          } /* if */
dvander@27
   260
        } /* if */
dvander@27
   261
        if (wordtabletop<wordtablesize) {
dvander@27
   262
          /* insert at sorted position */
dvander@27
   263
          int pos=wordtabletop;
dvander@27
   264
          assert(wordtable!=NULL);
dvander@27
   265
          while (pos>0 && (unsigned)wordtable[pos-1].index>index) {
dvander@27
   266
            wordtable[pos]=wordtable[pos-1];
dvander@27
   267
            pos--;
dvander@27
   268
          } /* while */
dvander@27
   269
          wordtable[pos].index=(unsigned short)index;
dvander@27
   270
          wordtable[pos].code=(wchar_t)code;
dvander@27
   271
        } /* if */
dvander@27
   272
      } /* if */
dvander@27
   273
    } /* if */
dvander@27
   274
  } /* while */
dvander@27
   275
dvander@27
   276
  fclose(fp);
dvander@27
   277
  return TRUE;
dvander@27
   278
}
dvander@27
   279
dvander@27
   280
SC_FUNC cell cp_translate(const unsigned char *string,const unsigned char **endptr)
dvander@27
   281
{
dvander@27
   282
  wchar_t result;
dvander@27
   283
dvander@27
   284
  result=bytetable[*string++];
dvander@27
   285
  /* check whether this is a leader code */
dvander@27
   286
  if ((unsigned)result==LEADBYTE && wordtable!=NULL) {
dvander@27
   287
    /* look up the code via binary search */
dvander@27
   288
    int low,high,mid;
dvander@27
   289
    unsigned short index=(unsigned short)(((*(string-1)) << 8) | *string);
dvander@27
   290
    string++;
dvander@27
   291
    assert(wordtabletop>0);
dvander@27
   292
    low=0;
dvander@27
   293
    high=wordtabletop-1;
dvander@27
   294
    while (low<high) {
dvander@27
   295
      mid=(low+high)/2;
dvander@27
   296
      assert(low<=mid && mid<high);
dvander@27
   297
      if (index>wordtable[mid].index)
dvander@27
   298
        low=mid+1;
dvander@27
   299
      else
dvander@27
   300
        high=mid;
dvander@27
   301
    } /* while */
dvander@27
   302
    assert(low==high);
dvander@27
   303
    if (wordtable[low].index==index)
dvander@27
   304
      result=wordtable[low].code;
dvander@27
   305
  } /* if */
dvander@27
   306
dvander@27
   307
  if (endptr!=NULL)
dvander@27
   308
    *endptr=string;
dvander@27
   309
  return (cell)result;
dvander@27
   310
}
dvander@27
   311
dvander@27
   312
#endif  /* NO_CODEPAGE */
dvander@27
   313
dvander@27
   314
#if !defined NO_UTF8
dvander@27
   315
SC_FUNC cell get_utf8_char(const unsigned char *string,const unsigned char **endptr)
dvander@27
   316
{
dvander@27
   317
  int follow=0;
dvander@27
   318
  long lowmark=0;
dvander@27
   319
  unsigned char ch;
dvander@27
   320
  cell result=0;
dvander@27
   321
dvander@27
   322
  if (endptr!=NULL)
dvander@27
   323
    *endptr=string;
dvander@27
   324
dvander@27
   325
  for ( ;; ) {
dvander@27
   326
    ch=*string++;
dvander@27
   327
dvander@27
   328
    if (follow>0 && (ch & 0xc0)==0x80) {
dvander@27
   329
      /* leader code is active, combine with earlier code */
dvander@27
   330
      result=(result << 6) | (ch & 0x3f);
dvander@27
   331
      if (--follow==0) {
dvander@27
   332
        /* encoding a character in more bytes than is strictly needed,
dvander@27
   333
         * is not really valid UTF-8; we are strict here to increase
dvander@27
   334
         * the chance of heuristic dectection of non-UTF-8 text
dvander@27
   335
         * (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid)
dvander@27
   336
         */
dvander@27
   337
        if (result<lowmark)
dvander@27
   338
          return -1;
dvander@27
   339
        /* the code positions 0xd800--0xdfff and 0xfffe & 0xffff do not
dvander@27
   340
         * exist in UCS-4 (and hence, they do not exist in Unicode)
dvander@27
   341
         */
dvander@27
   342
        if (result>=0xd800 && result<=0xdfff || result==0xfffe || result==0xffff)
dvander@27
   343
          return -1;
dvander@27
   344
      } /* if */
dvander@27
   345
      break;
dvander@27
   346
    } else if (follow==0 && (ch & 0x80)==0x80) {
dvander@27
   347
      /* UTF-8 leader code */
dvander@27
   348
      if ((ch & 0xe0)==0xc0) {
dvander@27
   349
        /* 110xxxxx 10xxxxxx */
dvander@27
   350
        follow=1;
dvander@27
   351
        lowmark=0x80L;
dvander@27
   352
        result=ch & 0x1f;
dvander@27
   353
      } else if ((ch & 0xf0)==0xe0) {
dvander@27
   354
        /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
dvander@27
   355
        follow=2;
dvander@27
   356
        lowmark=0x800L;
dvander@27
   357
        result=ch & 0x0f;
dvander@27
   358
      } else if ((ch & 0xf8)==0xf0) {
dvander@27
   359
        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
dvander@27
   360
        follow=3;
dvander@27
   361
        lowmark=0x10000L;
dvander@27
   362
        result=ch & 0x07;
dvander@27
   363
      } else if ((ch & 0xfc)==0xf8) {
dvander@27
   364
        /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
dvander@27
   365
        follow=4;
dvander@27
   366
        lowmark=0x200000L;
dvander@27
   367
        result=ch & 0x03;
dvander@27
   368
      } else if ((ch & 0xfe)==0xfc) {
dvander@27
   369
        /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (32 bits) */
dvander@27
   370
        follow=5;
dvander@27
   371
        lowmark=0x4000000L;
dvander@27
   372
        result=ch & 0x01;
dvander@27
   373
      } else {
dvander@27
   374
        /* this is invalid UTF-8 */
dvander@27
   375
        return -1;
dvander@27
   376
      } /* if */
dvander@27
   377
    } else if (follow==0 && (ch & 0x80)==0x00) {
dvander@27
   378
      /* 0xxxxxxx (US-ASCII) */
dvander@27
   379
      result=ch;
dvander@27
   380
      break;
dvander@27
   381
    } else {
dvander@27
   382
      /* this is invalid UTF-8 */
dvander@27
   383
      return -1;
dvander@27
   384
    } /* if */
dvander@27
   385
dvander@27
   386
  } /* for */
dvander@27
   387
dvander@27
   388
  if (endptr!=NULL)
dvander@27
   389
    *endptr=string;
dvander@27
   390
  return result;
dvander@27
   391
}
dvander@27
   392
#endif
dvander@27
   393
dvander@27
   394
SC_FUNC int scan_utf8(FILE *fp,const char *filename)
dvander@27
   395
{
dvander@27
   396
  #if defined NO_UTF8
dvander@27
   397
    return 0;
dvander@27
   398
  #else
dvander@27
   399
    void *resetpos=pc_getpossrc(fp);
dvander@27
   400
    int utf8=TRUE;
dvander@27
   401
    int firstchar=TRUE,bom_found=FALSE;
dvander@27
   402
    const unsigned char *ptr;
dvander@27
   403
dvander@27
   404
    while (utf8 && pc_readsrc(fp,pline,sLINEMAX)!=NULL) {
dvander@27
   405
      ptr=pline;
dvander@27
   406
      if (firstchar) {
dvander@27
   407
        /* check whether the very first character on the very first line
dvander@27
   408
         * starts with a BYTE order mark
dvander@27
   409
         */
dvander@27
   410
        cell c=get_utf8_char(ptr,&ptr);
dvander@27
   411
        bom_found= (c==0xfeff);
dvander@27
   412
        utf8= (c>=0);
dvander@27
   413
        firstchar=FALSE;
dvander@27
   414
      } /* if */
dvander@27
   415
      while (utf8 && *ptr!='\0')
dvander@27
   416
        utf8= (get_utf8_char(ptr,&ptr)>=0);
dvander@27
   417
    } /* while */
dvander@27
   418
    pc_resetsrc(fp,resetpos);
dvander@27
   419
    if (bom_found) {
dvander@27
   420
      unsigned char bom[3];
dvander@27
   421
      if (!utf8)
dvander@27
   422
        error(77,filename);     /* malformed UTF-8 encoding */
dvander@27
   423
      pc_readsrc(fp,bom,3);
dvander@27
   424
      assert(bom[0]==0xef && bom[1]==0xbb && bom[2]==0xbf);
dvander@27
   425
    } /* if */
dvander@27
   426
    return utf8;
dvander@27
   427
  #endif  /* NO_UTF8 */
dvander@27
   428
}