1 /* Codepage translation to Unicode, and UTF-8 support
3 * The translation is based on codepage mapping files that are distributed
4 * by the Unicode consortium, see ftp://ftp.unicode.org/Public/MAPPINGS/.
6 * Character sets with a maximum of 256 codes are translated via a lookup
7 * table (these are Single-Byte Character Sets). Character sets like Shift-JIS
8 * with single-byte characters and multi-byte characters (introduced by a
9 * leader byte) are split into two tables: the 256-entry lookup table for
10 * the single-byte characters and an extended table for the multi-byte
11 * characters. The extended table is allocated dynamically; the lookup table
12 * is allocated statically, so loading SBCS tables cannot fail (if the tables
13 * themselves are valid, of course).
15 * Copyright (c) ITB CompuPhase, 2004-2006
17 * This software is provided "as-is", without any express or implied warranty.
18 * In no event will the authors be held liable for any damages arising from
19 * the use of this software.
21 * Permission is granted to anyone to use this software for any purpose,
22 * including commercial applications, and to alter it and redistribute it
23 * freely, subject to the following restrictions:
25 * 1. The origin of this software must not be misrepresented; you must not
26 * claim that you wrote the original software. If you use this software in
27 * a product, an acknowledgment in the product documentation would be
28 * appreciated but is not required.
29 * 2. Altered source versions must be plainly marked as such, and must not be
30 * misrepresented as being the original software.
31 * 3. This notice may not be removed or altered from any source distribution.
33 * Version: $Id: sci18n.c 3612 2006-07-22 09:59:46Z thiadmer $
46 #if !defined _MAX_PATH
49 #if !defined DIRSEP_CHAR
50 #if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__
51 #define DIRSEP_CHAR '/'
52 #elif defined macintosh
53 #define DIRSEP_CHAR ':'
55 #define DIRSEP_CHAR '\\'
60 #define ELEMENTS(array) (sizeof(array) / sizeof(array[0]))
63 #if !defined NO_CODEPAGE
65 #if !defined MAXCODEPAGE
66 #define MAXCODEPAGE 12 /* typically "cp" + 4 digits + ".txt" */
68 #define INVALID 0xffffu /* 0xffff and 0xfffe are invalid Unicode characters */
69 #define LEADBYTE 0xfffeu
75 static char cprootpath[_MAX_PATH] = { DIRSEP_CHAR, '\0' };
76 static wchar_t bytetable[256];
77 static struct wordpair *wordtable = NULL;
78 static unsigned wordtablesize = 0;
79 static unsigned wordtabletop = 0;
82 /* read in a line delimited by '\r' or '\n'; do NOT store the '\r' or '\n' into
83 * the string and ignore empty lines
84 * returns 1 for success and 0 for failure
86 static int cp_readline(FILE *fp,char *string,size_t size)
91 while ((c=fgetc(fp))!=EOF && count<size-1) {
92 if (c=='\r' || c=='\n') {
93 if (count>0) /* '\r' or '\n' ends a string */
95 /* if count==0, the line started with a '\r' or '\n', or perhaps line
96 * ends in the file are '\r\n' and we read and stopped on the '\r' of
100 string[count++]=(char)c;
107 /* cp_path() sets the directory where all codepage files must be found (if
108 * the parameter to cp_set() specifies a full path, that is used instead).
109 * The path is specified into two parts: root and directory; the full path
110 * for the codepage direcory is just the concatenation of the two, with a
111 * directory separator in between. The directory is given in two parts,
112 * because often a program already retrieves its "home" directory and the
113 * codepages are most conveniently stored in a subdirectory of this home
116 SC_FUNC int cp_path(const char *root, const char *directory)
119 int add_slash1,add_slash2;
121 len1= (root!=NULL) ? strlen(root) : 0;
122 add_slash1= (len1==0 || root[len1-1]!=DIRSEP_CHAR);
123 len2= (directory!=NULL) ? strlen(directory) : 0;
124 add_slash2= (len2>0 && root[len2-1]!=DIRSEP_CHAR);
125 if (len1+add_slash1+len2+add_slash2>=(_MAX_PATH-MAXCODEPAGE))
126 return FALSE; /* full filename may not fit */
128 strcpy(cprootpath,root);
130 assert(len1==0 || cprootpath[len1]=='\0');
131 cprootpath[len1]=DIRSEP_CHAR;
132 cprootpath[len1+1]='\0';
135 strcat(cprootpath,directory);
137 assert(cprootpath[len1+add_slash1+len2]=='\0');
138 cprootpath[len1+add_slash1+len2]=DIRSEP_CHAR;
139 cprootpath[len1+add_slash1+len2+1]='\0';
141 cp_set(NULL); /* start with a "linear" table (no translation) */
145 /* cp_set() loads a codepage from a file. The name parameter may be a
146 * filename (including a full path) or it may be a partial codepage name.
147 * If the name parameter is NULL, the codepage is cleared to be a "linear"
148 * table (no translation).
149 * The following files are attempted to open (where <name> specifies the
150 * value of the parameter):
152 * <cprootpath>/<name>
153 * <cprootpath>/<name>.txt
154 * <cprootpath>/cp<name>
155 * <cprootpath>/cp<name>.txt
157 SC_FUNC int cp_set(const char *name)
159 char filename[_MAX_PATH];
163 /* for name==NULL, set up an identity table */
164 if (name==NULL || *name=='\0') {
165 if (wordtable!=NULL) {
171 for (index=0; index<ELEMENTS(bytetable); index++)
172 bytetable[index]=(wchar_t)index;
176 /* try to open the file as-is */
177 if (strchr(name,DIRSEP_CHAR)!=NULL)
180 /* try opening the file in the "root path" for codepages */
181 if (strlen(name)>MAXCODEPAGE)
183 assert(strlen(name)+strlen(cprootpath)<_MAX_PATH);
184 strcpy(filename,cprootpath);
185 strcat(filename,name);
186 fp=fopen(filename,"rt");
189 /* try opening the file in the "root path" for codepages, with a ".txt" extension */
190 if (strlen(name)+4>=MAXCODEPAGE)
192 assert(strlen(filename)+4<_MAX_PATH);
193 strcat(filename,".txt");
194 fp=fopen(filename,"rt");
197 /* try opening the file in the "root path" for codepages, with "cp" prefixed before the name */
198 if (strlen(name)+2>MAXCODEPAGE)
200 assert(2+strlen(name)+strlen(cprootpath)<_MAX_PATH);
201 strcpy(filename,cprootpath);
202 strcat(filename,"cp");
203 strcat(filename,name);
204 fp=fopen(filename,"rt");
207 /* try opening the file in the "root path" for codepages, with "cp" prefixed an ".txt" appended */
208 if (strlen(name)+2+4>MAXCODEPAGE)
210 assert(strlen(filename)+4<_MAX_PATH);
211 strcat(filename,".txt");
212 fp=fopen(filename,"rt");
215 return FALSE; /* all failed */
217 /* clear the tables */
218 for (index=0; index<ELEMENTS(bytetable); index++)
219 bytetable[index]=INVALID; /* special code meaning "not found" */
220 assert(wordtablesize==0 && wordtabletop==0 && wordtable==NULL
221 || wordtablesize>0 && wordtable!=NULL);
222 if (wordtable!=NULL) {
229 /* read in the table */
230 while (cp_readline(fp,filename,sizeof filename)) {
232 if ((ptr=strchr(filename,'#'))!=NULL)
233 *ptr='\0'; /* strip of comment */
234 for (ptr=filename; *ptr>0 && *ptr<' '; ptr++)
235 /* nothing */; /* skip leading whitespace */
237 /* content on line */
238 unsigned code=LEADBYTE;
239 int num=sscanf(ptr,"%i %i",&index,&code);
240 /* if sscanf() returns 1 and the index is in range 0..255, then the
241 * code is a DBCS lead byte; if sscanf() returns 2 and index>=256, this
242 * is a double byte pair (lead byte + follower)
244 if (num>=1 && index<256) {
245 bytetable[index]=(wchar_t)code;
246 } else if (num==2 && index>=256 && index<LEADBYTE) {
247 /* store the DBCS character in wordtable */
248 if (wordtabletop>=wordtablesize) {
251 struct wordpair *newblock;
252 newsize= (wordtablesize==0) ? 128 : 2*wordtablesize;
253 newblock=(struct wordpair *)malloc(newsize*sizeof(*wordtable));
254 if (newblock!=NULL) {
255 memcpy(newblock,wordtable,wordtabletop*sizeof(*wordtable));
258 wordtablesize=newsize;
261 if (wordtabletop<wordtablesize) {
262 /* insert at sorted position */
263 int pos=wordtabletop;
264 assert(wordtable!=NULL);
265 while (pos>0 && (unsigned)wordtable[pos-1].index>index) {
266 wordtable[pos]=wordtable[pos-1];
269 wordtable[pos].index=(unsigned short)index;
270 wordtable[pos].code=(wchar_t)code;
280 SC_FUNC cell cp_translate(const unsigned char *string,const unsigned char **endptr)
284 result=bytetable[*string++];
285 /* check whether this is a leader code */
286 if ((unsigned)result==LEADBYTE && wordtable!=NULL) {
287 /* look up the code via binary search */
289 unsigned short index=(unsigned short)(((*(string-1)) << 8) | *string);
291 assert(wordtabletop>0);
296 assert(low<=mid && mid<high);
297 if (index>wordtable[mid].index)
303 if (wordtable[low].index==index)
304 result=wordtable[low].code;
312 #endif /* NO_CODEPAGE */
315 SC_FUNC cell get_utf8_char(const unsigned char *string,const unsigned char **endptr)
328 if (follow>0 && (ch & 0xc0)==0x80) {
329 /* leader code is active, combine with earlier code */
330 result=(result << 6) | (ch & 0x3f);
332 /* encoding a character in more bytes than is strictly needed,
333 * is not really valid UTF-8; we are strict here to increase
334 * the chance of heuristic dectection of non-UTF-8 text
335 * (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid)
339 /* the code positions 0xd800--0xdfff and 0xfffe & 0xffff do not
340 * exist in UCS-4 (and hence, they do not exist in Unicode)
342 if (result>=0xd800 && result<=0xdfff || result==0xfffe || result==0xffff)
346 } else if (follow==0 && (ch & 0x80)==0x80) {
347 /* UTF-8 leader code */
348 if ((ch & 0xe0)==0xc0) {
349 /* 110xxxxx 10xxxxxx */
353 } else if ((ch & 0xf0)==0xe0) {
354 /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
358 } else if ((ch & 0xf8)==0xf0) {
359 /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
363 } else if ((ch & 0xfc)==0xf8) {
364 /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
368 } else if ((ch & 0xfe)==0xfc) {
369 /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (32 bits) */
374 /* this is invalid UTF-8 */
377 } else if (follow==0 && (ch & 0x80)==0x00) {
378 /* 0xxxxxxx (US-ASCII) */
382 /* this is invalid UTF-8 */
394 SC_FUNC int scan_utf8(FILE *fp,const char *filename)
399 void *resetpos=pc_getpossrc(fp);
401 int firstchar=TRUE,bom_found=FALSE;
402 const unsigned char *ptr;
404 while (utf8 && pc_readsrc(fp,pline,sLINEMAX)!=NULL) {
407 /* check whether the very first character on the very first line
408 * starts with a BYTE order mark
410 cell c=get_utf8_char(ptr,&ptr);
411 bom_found= (c==0xfeff);
415 while (utf8 && *ptr!='\0')
416 utf8= (get_utf8_char(ptr,&ptr)>=0);
418 pc_resetsrc(fp,resetpos);
420 unsigned char bom[3];
422 error(77,filename); /* malformed UTF-8 encoding */
423 pc_readsrc(fp,bom,3);
424 assert(bom[0]==0xef && bom[1]==0xbb && bom[2]==0xbf);