home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / charsets / utf8.c < prev    next >
C/C++ Source or Header  |  2020-01-01  |  12KB  |  429 lines

  1. /*
  2.    U T F 8 . C
  3.  
  4.    Dumps a selected portion of Unicode Plane 0 in UTF8 to standard output.
  5.  
  6.    Output is one line per character:
  7.      [c]  xxxx  name
  8.  
  9.    where:
  10.      c is the character in UTF-8,
  11.      xxxx is the 4-digit hex code,
  12.      name is the character's name from the Unicode database.
  13.  
  14.    Usage:
  15.      utf8                Dump all of BMP except controls.
  16.      utf8 hex            Dump <hex> through FFFF.
  17.      utf8 hex1 hex2      Dump <hex1> through <hex2>.
  18.      utf8 -w [hex [hex]] As above but suitable for the Web.
  19.      utf8 -p directory   Include this to specify directory for database files.
  20.  
  21.    Default location for Unicode database files is:
  22.      /pub/ftp/kermit/charsets/
  23.      /www/data/ftp/kermit/charsets/
  24.  
  25.    Obtain up-to-date copies of database files from:
  26.      http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
  27.      http://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt
  28.  
  29.    If the Unicode database can't be found, the characters are dumped
  30.    without names.
  31.  
  32.    Assumes Unicode database is in ascending order with one record
  33.    per line: first field is hex code, second field is name; field
  34.    separator is semicolon (;).  The DerivedBidiClass file need not be
  35.    in code order.
  36.  
  37.    ANSI C required.
  38.  
  39.    F. da Cruz, Columbia University, May 2000.
  40.  
  41.    Updated 10 June 2003: new -w option makes a Web version that:
  42.     - Puts < for <, > for >, & for &.
  43.     - Puts a space before each combining mark
  44.     - Puts U+200E (LTRM) after each RTL character.
  45.     - Automatically substitutes space for control/formatting characters.
  46.  
  47.    Updated 18 Jun 2003:
  48.     - new -p option to specify path for database files.
  49.     - Reads DerivedBidiClass.txt to get BIDI class for undefined code points.
  50.     - getfields() strips leading and trailing blanks from each field.
  51.  
  52.    Updated 25 Jun 2003:
  53.     - Show hex code as U+xxxx to avoid having the digits in certain entries
  54.       turned into Hindi digits (don't ask!)
  55.  
  56.    Updated 15 Aug 2004:
  57.     - Show decimal NCRs.
  58. */
  59. #include <stdio.h>
  60.  
  61. #define USHORT unsigned short
  62. #define ULONG unsigned long
  63. #define CONST const
  64. #define CHAR unsigned char
  65. #ifndef MAXPATHLEN
  66. #define MAXPATHLEN 1024
  67. #endif /* MAXPATHLEN */
  68.  
  69. CHAR firstByteMark[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
  70.  
  71. /* Default directories for Unicode database files */
  72.  
  73. char * ucdata = "/pub/ftp/kermit/charsets/";
  74. char * ucdata2 = "/www/data/ftp/kermit/charsets/";
  75.  
  76. char * argv0;                /* My name */
  77. char ltrm[4];                /* Left To Right Mark */
  78. char line[1024];            /* Database line buffer */
  79. char * field[16];            /* Fields within line */
  80.  
  81. struct lohi {                /* Struct for ranges */
  82.     int lo;
  83.     int hi;
  84. };
  85.  
  86. struct lohi bidi[256];            /* Default RTL blocks */
  87. int b = 0;                /* Number of RTL blocks */
  88.  
  89. int
  90. ucs2_to_utf8(USHORT ucs2, CHAR ** utf8) { /* Convert UCS-2 to UTF-8 */
  91.  
  92.     static CHAR utf8return[8]={0,0,0,0,0,0,0,0};
  93.     register CONST ULONG byteMask = 0xBF;
  94.     register CONST ULONG byteMark = 0x80;
  95.     int utf8len = 0;
  96.     int i = 0;
  97.  
  98.     if (ucs2 < 0x80) {
  99.         utf8len = 1;
  100.     } else if (ucs2 < 0x800) {
  101.         utf8len = 2;
  102.     } else
  103. #ifdef DO_UCS4
  104.       /* This is always true for UCS-2 but would be needed for UCS-4*/
  105.       /* When ucs2 is USHORT this gives compiler warnings. */
  106.       if (ucs2 <= 0xffff)
  107. #endif /* DO_UCS4 */
  108.     {
  109.         utf8len = 3;
  110.     }
  111. #ifdef DO_UCS4
  112. /* The following would be for UCS-4 */
  113.     else if (ucs2 < 0x200000) {
  114.         utf8len = 4;
  115.     } else if (ucs2 < 0x4000000) {
  116.         utf8len = 5;
  117.     } else if (ucs2 <= 0x7FFFFFFFUL) {    /* 31 bits = max for UCS4 */
  118.         utf8len = 6;
  119.     } else {
  120.         utf8len = 2;
  121.         ucs2 = 0xFFFD;                  /* Replacement for invalid char */
  122.     }
  123. #endif /* DO_UCS4 */
  124.     i = utf8len;                        /* index into utf8return */
  125.     utf8return[i--] = 0;                /* Null terminate the string */
  126.  
  127.     switch (utf8len) {                    /* code falls through cases! */
  128.       case 6: utf8return[i--] = (ucs2 | byteMark) & byteMask; ucs2 >>= 6;
  129.       case 5: utf8return[i--] = (ucs2 | byteMark) & byteMask; ucs2 >>= 6;
  130.       case 4: utf8return[i--] = (ucs2 | byteMark) & byteMask; ucs2 >>= 6;
  131.       case 3: utf8return[i--] = (ucs2 | byteMark) & byteMask; ucs2 >>= 6;
  132.       case 2: utf8return[i--] = (ucs2 | byteMark) & byteMask; ucs2 >>= 6;
  133.       case 1: utf8return[i--] =  ucs2 | firstByteMark[utf8len];
  134.     }
  135.     *utf8 = utf8return;
  136.     return(utf8len);
  137. }
  138.  
  139. usage(s) char * s; {
  140.     fprintf(stderr,"Usage: %s [-w] [hex [hex]]\n",s);
  141.     exit(1);
  142. }
  143.  
  144. int
  145. hextoint(s) char * s; {            /* Convert hex string to integer */
  146.     unsigned int x = 0;
  147.     int d;
  148.     char c;
  149.  
  150.     while (c = *s++) {
  151.     if (c >= 'A' && c <= 'F')
  152.       d = c - 'A' + 10;
  153.     else if (c >= 'a' && c <= 'f')
  154.       d = c - 'a' + 10;
  155.     else if (c >= '0' && c <= '9')
  156.       d = c - '0';
  157.     else
  158.       usage(argv0);
  159.     x = (x << 4) + d;
  160.     }
  161.     return(x);
  162. }
  163.  
  164. void
  165. clearfields() {                /* Clear database fields */
  166.     int i;
  167.     for (i = 0; i < 16; i++)
  168.       field[i] = (char *)0;
  169. }
  170.  
  171. void
  172. getfields(s) char *s; {            /* Get fields from database entry */
  173.     int i = 0;
  174.     char * p = s, * q = s, * r = s, * t;
  175.  
  176.     while (*p && i < 16) {
  177.     if (*p == ';' || *p == '#') {    /* Field separator */
  178.         t = q;            /* Beginning of this field */
  179.         while (*t == ' ' || *t == '\t') /* Trim leading whitespace */
  180.           t++;
  181.         field[i++] = t;
  182.         *p = '\0';            /* End of this field */
  183.         t = p - 1;            /* Trim trailing whitespace */
  184.         while (t > r && *t && (*t == ' ' || *t == '\t')) {
  185.         *t = '\0';
  186.         t--;
  187.         }
  188.         if (*p == '#')        /* Comment introducer terminates */
  189.           return;
  190.         q = p+1;            /* Advance to next field */
  191.     }
  192.     p++;
  193.     }
  194. }
  195.  
  196. FILE *
  197. fileopen(char * path, char * name) {
  198.     char filename[MAXPATHLEN+2];    /* Buffer for filespec */
  199.     int i, n;
  200.     if (!path || !name)
  201.       return((FILE *)0);
  202.     n = (int) strlen(path);
  203.     if (n + (int)strlen(name) > MAXPATHLEN)
  204.       return((FILE *)0);
  205.     if (n > 0) {
  206.     strncpy(filename,path,MAXPATHLEN);
  207.     if (path[n-1] != '/') {
  208.         filename[n++] = '/';
  209.     }
  210.     }
  211.     strncpy(&filename[n],name,MAXPATHLEN-n+1);
  212.     return(fopen(filename,"r"));
  213. }
  214.  
  215. /* Given s == hex "XXXX" or "XXXX..XXXX" constructs lo,hi pair */
  216.  
  217. struct lohi
  218. splitpair(char * s) {
  219.     struct lohi x;
  220.     char * p, * q;
  221.     p = s;
  222.     for (q = p; *q; q++)
  223.       if (*q == '.')
  224.     break;
  225.     if (*q == '.') {
  226.     while (*q == '.')
  227.       *q++ = '\0';
  228.     }        
  229.     x.lo = hextoint(p);
  230.     x.hi = *q ? hextoint(q) : x.lo;
  231.     return(x);
  232. }
  233.  
  234. static char ncrbuf[32];
  235.  
  236. int
  237. main(argc,argv) int argc; char *argv[]; {
  238.  
  239.     FILE * fp;                /* Unicode database file pointer */
  240.     USHORT x;                /* Unicode values */
  241.     CHAR * buf = NULL;            /* UTF-8 buffer pointer */
  242.     char c, * s, * p, * bp;        /* Workers... */
  243.     char * argv1 = (char *)0, * argv2 = (char *)0;
  244.     int i, m, current = -1, all = 0, web = 0;
  245.     int rtl, combining;
  246.     unsigned int xx, from = 0, to = 0;
  247.     struct lohi z;
  248.  
  249.     argv0 = argv[0];            /* My name */
  250.  
  251.     all = 1;
  252.     for (i = 1; i < argc; i++) {    /* Parse command-line args */
  253.     if (*argv[i] == '-') {    
  254.         if (!strcmp(argv[i],"-w")) {
  255.         web++;
  256.         continue;
  257.         } else if (!strcmp(argv[i],"-p")) {
  258.         i++;
  259.         ucdata = argv[i];
  260.         ucdata2 = (char *)0;
  261.         } else {
  262.         usage(argv0);    
  263.         }
  264.     } else if (!argv1) {
  265.         argv1 = argv[i];
  266.         all = 0;
  267.     } else if (!argv2) { 
  268.         argv2 = argv[i];
  269.     } else 
  270.       usage(argv0); 
  271.     }
  272.     if (!argv1) argv1 = "20";        /* Supply defaults */
  273.     if (!argv2) argv2 = "FFFF";
  274.  
  275.     bp = (char *)buf;
  276.  
  277.     x = hextoint("200E");        /* UTF-8 for LTRM */
  278.     m = ucs2_to_utf8(x,&buf);
  279.     if (m > 3) m = 3;
  280.     for (i = 0; i < m; i++)
  281.       ltrm[i] = buf[i];
  282.     ltrm[3] = '\0';
  283.     
  284.     from = hextoint(argv1);        /* Get range as ints */
  285.     if (from < 32)            /* Check range and sanity */
  286.       usage(argv0);
  287.     to = hextoint(argv2);
  288.     if (to > 0xffff)
  289.       usage(argv0);
  290.  
  291. /* Load table of default BIDI class for character blocks */
  292.  
  293.     fp = fileopen(ucdata,"DerivedBidiClass.txt");
  294.     if (!fp && ucdata2)
  295.       fp = fileopen(ucdata2,"DerivedBidiClass.txt");
  296.     while (fp) {            /* Get entry for code x */
  297.     if ((bp = fgets(line,1023,fp))) { /* Read a line */
  298.         clearfields();
  299.         if (line[0] == '#' || !line[0])
  300.           continue;
  301.         getfields(line);        /* Separate fields */
  302.         if (!field[0]) continue;    /* Comment or blank line */
  303.         if (!field[1]) continue;    /* No properties */
  304.         z = splitpair(field[0]);
  305.         if (*(field[1]) == 'R' || !strcmp(field[1],"AL")) {
  306.         bidi[b] = z;        /* (might not be portable)*/
  307.         b++;
  308.         if (b > 254) {
  309.             fprintf(stderr,"Too many BIDI blocks (255 max)\n");
  310.             exit(1);
  311.         }
  312.         }
  313.     } else
  314.       break;
  315.     }
  316.     if (fp) {                /* Close file */
  317.     fclose(fp);
  318.     fp = (FILE *)0;
  319.     }
  320.     if (b > 1) {            /* Have to sort these */
  321.     int i, j;            /* Bubble sort is fine */
  322.     struct lohi t;            /* it's a small array */
  323.     for (i = 0; i < b-1; i++) {
  324.         for (j = i+1; j < b; j++) {
  325.         if (bidi[i].lo > bidi[j].lo) {
  326.             t = bidi[i];    /* warning: struct assignment */
  327.             bidi[i] = bidi[j];    /* might not be portable */
  328.             bidi[j] = t;
  329.         }
  330.         }
  331.     }
  332.     }
  333.  
  334. /* Open Unicode Character Database file */
  335.  
  336.     fp = fileopen(ucdata,"UnicodeData.txt"); /* Unicode Data file */
  337.     if (!fp && ucdata2)
  338.       fp = fileopen(ucdata2,"UnicodeData.txt");
  339.  
  340. /* Main loop... */
  341.  
  342.     clearfields;            /* Initialize database fields */
  343.     for (xx = from; xx <= to; xx++) {    /* Loop through range */
  344.     x = xx;                /* Convert index to unsigned short */
  345.     if (all && (x == 0x7F || (x >= 0x80 && x < 0xA0))) /* Skip controls */
  346.       continue;
  347.     if (web) {            /* If making a Web table */
  348.         /* Including Han crashes all known browsers (2003) */
  349.         if (x >= 0x2b0e && x <= 0x303f || /* Skip CJK */
  350.         x >= 0x3130 && x <= 0x319f || /* but keep kana and bopomofu */
  351.         x >= 0x3200 && x <= 0xfff8) {
  352.         if (x == 0x2b0e || x == 0x3130 || x == 0x3200)
  353.           printf("...\n");
  354.         continue;
  355.         }
  356.     }
  357.     p = (char *)0;            /* Initialize name */
  358.     rtl = combining = 0;        /* and attributes */
  359.     while (fp && current < x) {    /* Get entry for code x */
  360.         if ((bp = fgets(line,1023,fp))) { /* Read a record */
  361.         if (line[0] == '#' || !line[0])
  362.           continue;
  363.         getfields(line);    /* Separate the fields */
  364.         current = (unsigned) hextoint(field[0]); /* Get code */
  365.         } else {            /* Read failed */
  366.         fclose(fp);        /* Close the database */
  367.         fp = NULL;        /* and don't try reading it again */
  368.         break;
  369.         }
  370.     }
  371.     if (current == x) {        /* If it's the desired record */
  372.         p = field[1];        /* get name of this character */
  373.         if (web) {            /* and if making a web table */
  374.         if (field[3])        /* get character properties */
  375.           combining = atoi(field[3]);
  376.         if (field[4])
  377.           rtl = (*(field[4]) == 'R' || !strcmp(field[4],"AL"));
  378.         }
  379.     } else {            /* This char is undefined */
  380.         int i;            /* Get its default bidi category */
  381.         for (i = 0; i < b; i++) {
  382.         if (bidi[i].lo > x)    /* (Table is sorted) */
  383.           break;
  384.         if (x >= bidi[i].lo && x <= bidi[i].hi) {
  385.             rtl = 1;
  386.             break;
  387.         }
  388.         }
  389.     }
  390.     if (!p) p = "(unknown)";    /* Supply this if name not known */
  391.     putchar('[');            /* Print UTF-8 character in brackets */
  392.     buf = (CHAR *)0;        /* Initialize value */
  393.     if (web) {            /* Sensitive HTML characters */
  394.         switch(x) {
  395.           case '<': buf = (CHAR *)"<" ; m = 4; break;
  396.           case '&': buf = (CHAR *)"&"; m = 5; break;
  397.           case '>': buf = (CHAR *)">" ; m = 4; break;
  398.           default:
  399.         if (x == current && field[2]) {    /* Print Controls as Space */
  400.             char * t = field[2];
  401.             if (*t == 'C') {
  402.             buf = (CHAR *)" ";
  403.             m = 1;
  404.             } else if (*t == 'Z' && *(t+1) != 's') { /* LS and PS */
  405.             buf = (CHAR *)" ";
  406.             m = 1;
  407.             }
  408.         }
  409.         }
  410.     }
  411.     if (!buf)            /* Anything but ">&<" or Control */
  412.       m = ucs2_to_utf8(x,&buf);    /* convert to UTF-8 */
  413.     if (combining > 0)        /* If combining */
  414.       putchar(' ');            /* put a space to combine with. */
  415.     for (i = 0; i < m; i++)        /* Copy UTF-8 bytes */
  416.       putchar(buf[i]);
  417.     if (combining == 233 || combining == 234) /* Combining double */
  418.       putchar(' ');                      /* Another space after */
  419.     if (rtl)            /* If RTL put LTR Mark after */
  420.       printf("%s",ltrm);
  421.  
  422.     putchar(']');            /* Closing bracket */
  423.     sprintf(ncrbuf,"&#%d;",x);
  424.     printf("  U+%04X  %12s  %s\n",x,ncrbuf,p); /* Print codes and name */
  425.     if (current == x)        /* Clear data */
  426.       clearfields();
  427.     }
  428. }
  429.