Project: finder, src: ucs4.c


UCS4 と UTF-8 の相互変換。
#include "ucs4.h"
#include <stdlib.h>

/**
 * 与えれた UTF8 文字列から UCS4 文字列長を推定する
 */
static int ucs4LenFromUtf8(const char *utf8, int len8) {
  unsigned char cc;
  int shift = 0, len4 = 0;
  while (len8 >= 0 && (cc = (unsigned char)(*utf8)) != '\0') {
    if ((cc & 0x80) == 0) {
      len4++;  utf8 += 1; len8 -= 1;
    } else if ((cc & 0xe0) == 0xc0) {
      len4++; utf8 += 2; len8 -= 2;
    } else if ((cc & 0xf0) == 0xe0) {
      len4++; utf8 += 3; len8 -= 3;
    } else if ((cc & 0xf8) == 0xf0) {
      len4++; utf8 += 4; len8 -= 4;
    } else if ((cc & 0xfc) == 0xf8) {
      len4++; utf8 += 5; len8 -= 5;
    } else if ((cc & 0xfe) == 0xfc) {
      len4++; utf8 += 6; len8 -= 6;
    } else {
      --len8;
    }
  }
  return len4;
}

/**
 * 与えられた UCS4 文字列から UTF-8 文字列長を推定する
 */
static int utf8LenFromUcs4(const UCS4 *ucs4, int len) {
  int len8 = 0;
  while (len > 0) {
    UCS4 cc = *(ucs4++);
    --len;
    if (cc < 0x80) {
      len8 += 1;
    } else if (cc < 0x800) {
      len8 += 2;
    } else if (cc < 0x10000) {
      len8 += 3;
    } else if (cc < 0x200000) {
      len8 += 4;
    } else if (cc < 0x4000000) {
      len8 += 5;
    } else {
      len8 += 6;
    }
  }
  return len8;
}

/**
 * UCS4 文字列の長さを返す(キャラクタ単位)
 */
int ucs4Len(const UCS4 *ucs4) {
  int len = 0;
  if (ucs4 == NULL) {
    return -1;
  }
  while (*(ucs4++) != 0) {
    ++len;
  }
  return len;
}

/**
 * UTF8 文字列から UCS4 文字列への変換
 */
UCS4 *ucs4FromUtf8(const char *utf8, int len, int *plen4) {
  int len4_target = ucs4LenFromUtf8(utf8, len);
  UCS4 *ucs4 = (UCS4 *)malloc((len4_target + 1) * sizeof(UCS4));
  UCS4 *p = ucs4;

  while (len > 0) {
    int cc = (int)*(utf8++) & 255;
    --len;
    if ((cc & 0x80) == 0) {
      // 7bit 文字
      *(p++) = cc;
    } else if ((cc & 0xe0) == 0xc0) {
      // 2byte 文字
      if (len <= 1) break;
      int cc2 = (int)*(utf8++);
      --len;
      *(p++) = ((cc & 0x1f) << 6) | (cc2 & 0x3f);
    } else if ((cc & 0xf0) == 0xe0) {
      // 3byte 文字
      int cc2[2];
      if (len < 2) break;
      cc2[0] = (int)*(utf8++) & 255;
      cc2[1] = (int)*(utf8++) & 255;
      len -= 2;
      *(p++) = ((cc & 0x0f) << 12) | ((cc2[0] & 0x3f) << 6) | (cc2[1] & 0x3f);
    } else if ((cc & 0xf8) == 0xf0) {
      // 4byte 文字
      int cc2[3];
      if (len < 3) break;
      cc2[0] = (int)*(utf8++) & 255;
      cc2[1] = (int)*(utf8++) & 255;
      cc2[2] = (int)*(utf8++) & 255;
      len -= 3;
      *(p++) = ((cc & 0x07) << 18) | ((cc2[0] & 0x3f) << 12) | ((cc2[1] & 0x3f) << 6) | (cc2[2] & 0x3f);
    } else if ((cc & 0xfc) == 0xf8) {
      // 5byte 文字
      int cc2[4];
      if (len < 4) break;
      cc2[0] = (int)*(utf8++) & 255;
      cc2[1] = (int)*(utf8++) & 255;
      cc2[2] = (int)*(utf8++) & 255;
      cc2[3] = (int)*(utf8++) & 255;
      len -= 4;
      *(p++) = ((cc & 0x03) << 24) | ((cc2[0] & 0x3f) << 18) | ((cc2[1] & 0x3f) << 12) | ((cc2[2] & 0x3f) << 6) | (cc2[3] & 0x3f);
    } else if ((cc & 0xfe) == 0xfc) {
      int cc2[5];
      if (len < 5) break;
      cc2[0] = (int)*(utf8++) & 255;
      cc2[1] = (int)*(utf8++) & 255;
      cc2[2] = (int)*(utf8++) & 255;
      cc2[3] = (int)*(utf8++) & 255;
      cc2[4] = (int)*(utf8++) & 255;
      len -= 5;
      *(p++) = ((cc & 0x01) << 30) | ((cc2[0] & 0x3f) << 24) | ((cc2[1] & 0x3f) << 18) | ((cc2[2] & 0x3f) << 12) | ((cc2[3] & 0x3f) << 6) | (cc2[4] & 0x3f);
    } else {
      // UTF-8 文字列ではない
      break;
    }
  }
  *p = 0;
  if (plen4) {
    *plen4 = p - ucs4;
  }
  return ucs4;
}

/**
 * UCS4 文字列から UTF8 文字列への変換
 */
char *utf8FromUCS4(const UCS4 *ucs4, int len, int *plen8) {
  int len8 = utf8LenFromUcs4(ucs4, len);
  char *utf8 = (char *)malloc(len8 + 1), *p = utf8;
  while (len > 0) {
    UCS4 cc = *(ucs4++);
    --len;
    if (cc < 0x80) {
      *(p++) = cc;
    } else if (cc < 0x800) {
      *(p++) = 0xc0 | (cc >> 6);
      *(p++) = 0x80 | (cc & 0x3f);
    } else if (cc < 0x10000) {
      *(p++) = 0xe0 | (cc >> 12);
      *(p++) = 0x80 | ((cc >> 6) & 0x3f);
      *(p++) = 0x80 | (cc & 0x3f);
    } else if (cc < 0x200000) {
      *(p++) = 0xf0 | (cc >> 18);
      *(p++) = 0x80 | ((cc >> 12) & 0x3f);
      *(p++) = 0x80 | ((cc >> 6) & 0x3f);
      *(p++) = 0x80 | (cc & 0x3f);
    } else if (cc < 0x4000000) {
      *(p++) = 0xf8 | (cc >> 24);
      *(p++) = 0x80 | ((cc >> 18) & 0x3f);
      *(p++) = 0x80 | ((cc >> 12) & 0x3f);
      *(p++) = 0x80 | ((cc >> 6) & 0x3f);
      *(p++) = 0x80 | (cc & 0x3f);
    } else {
      *(p++) = 0xfc | (cc >> 30);
      *(p++) = 0x80 | ((cc >> 24) & 0x3f);
      *(p++) = 0x80 | ((cc >> 18) & 0x3f);
      *(p++) = 0x80 | ((cc >> 12) & 0x3f);
      *(p++) = 0x80 | ((cc >> 6) & 0x3f);
      *(p++) = 0x80 | (cc & 0x3f);
    }
  }

  if (plen8) {
    *plen8 = len8;
  }
  return utf8;
}

#ifdef DEBUG
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
  UCS4 *ucs4;
  char *utf8;
  int len4, len8;
  if (argc < 2) {
    printf("usage: %s <string to be translated>\n", argv[0]);
    exit(1);
  }

  // UCS4 に変換
  ucs4 = ucs4FromUtf8(argv[1], strlen(argv[1]), &len4);

  // UTF-8 に再変換
  utf8 = utf8FromUCS4(ucs4, len4, &len8);

  printf("re-translation: %.*s\n", len8, utf8);
  free(ucs4);
  free(utf8);
  return 0;
}

#endif // DEBUG