javascriptでUTF-8/UTF-16変換

# javascriptでUTF-8/UTF-16変換

javascriptの文字列をUTF-8/UTF-16(LE,BE)へ変換・逆変換する

* 文字列からUTF-8/UTF-16バイト列への変換
  1. スカラー値の取得
  2. 符号化
* UTF-8/UTF-16バイト列から文字列への変換

## 文字列からUTF-8/UTF-16バイト列への変換
###スカラー値の取得

`String.prototype.charCodeAt(i)` でi文字目の文字コードを取得出来る。 ただjsの文字列はUTF-16なのでサロゲートペアを考慮する必要がある。

```js
"use strict";
var Utf = {
	charCodes: function(str) {
		var i, len, arr;
		for(i=0,len=str.length,arr=[]; i<len; i++) {
			arr.push(str.charCodeAt(i));
		}
		return arr;
	},
	scalarValues: function(arr) {
		var scalars = [], i, len, c;
		if (typeof arr === 'string') {arr = Utf.charCodes(arr);}
		for(i=0,len=arr.length; c=arr[i],i<len; i++) {
			if (c >= 0xd800 && c <= 0xdbff) {
				scalars.push((c & 1023) + 64 << 10 | arr[++i] & 1023);
			} else {scalars.push(c);}
		}
		return scalars;
	}
};
```

### 符号化

UTF-8はスカラー値から変換する。 UTF-16は文字コードからバイト列化する(スカラー値からだと2度手間)。

```js
Utf.encodeUTF8 = function(str) {
	var sv = Utf.scalarValues(str), i, len, arr, c;
	for(i=0,len=sv.length,arr=[]; c=sv[i],i<len; i++) {
		if (c <= 0x7f) {arr.push(c);} //1byte
		else if (c <= 0x7ff) {arr.push(0xc0|c>>>6, 0x80|c&0xbf);} //2byte
		else if (c <= 0xffff) { //3byte
			arr.push(0xe0|c>>>12, 0x80|c>>>6&0xbf, 0x80|c&0xbf);
		} else if (c <= 0x10ffff) { //4byte
			arr.push(0xf0|c>>>18, 0x80|c>>>12&0xbf, 0x80|c>>>6&0xbf, 0x80|c&0xbf);
		}
	}
	return arr;
};
Utf.encodeUTF16 = function(str, endian) {
	var codes = Utf.charCodes(str), i, len, arr, c;
	arr = endian ? endian === 'LE' ? [0xff,0xfe] : [0xfe,0xff] : []; //BOM
	for(i=0,len=codes.length; c=codes[i],i<len; i++) {
		if (endian === 'LE') {
			arr.push(c & 0xff, c >>> 8 & 0xff);
		} else {
			arr.push(c >>> 8 & 0xff, c & 0xff);
		}
	}
	return arr;
};
```

またUTF-8への変換にスカラー値を求めず、文字コードからサロゲートペアを考慮しても良い

```js
Utf.encodeUTF8 = function(str) {
	var codes = Utf.charCodes(str), i, len, arr, c;
	for(i=0,len=codes.length,arr=[]; c=codes[i],i<len; i++) {
		if (c <= 0x7f) {arr.push(c);} //1byte
		else if (c <= 0x7ff) {arr.push(0xc0|c>>>6, 0x80|c&0xbf);} //2byte
		else if (c <= 0xdbff && c >= 0xd800) { //4byte
			//utf16  110110wwwwxxxxxx  110111xxxxxxxxxx
			//utf8   11110uuu  10uuxxxx  10xxxxxx  10xxxxxx (uuuuu = wwww+1)
			arr.push(
				0xf0 | (c=(c & 1023) + 64) >>> 8,
				0x80 | c >>> 2 & 63,
				0x80 | (c & 3) << 4 | (c=codes[++i] & 1023) >>> 6,
				0x80 | c & 63
			);
		} else if (c <= 0xffff) { //3byte
			arr.push(0xe0|c>>>12, 0x80|c>>>6&0xbf, 0x80|c&0xbf);
		}
	}
	return arr;
};
```

UTF-8/UTF-16バイト列から文字列への変換

上記と逆の処理を行えばよい

```js
Utf.decodeUTF8 = function(arr) {
	var i, len, c, str, char = String.fromCharCode;
	for(i=0,len=arr.length,str=""; c=arr[i],i<len; i++) {
		if (c <= 0x7f) {str += char(c);}
		else if (c <= 0xdf && c >= 0xc2) {
			str += char((c&31)<<6 | arr[++i]&63);
		} else if (c <= 0xef && c >= 0xe0) {
			str += char((c&15)<<12 | (arr[++i]&63)<<6 | arr[++i]&63);
		} else if (c <= 0xf7 && c >= 0xf0) {
			//utf8   11110uuu  10uuxxxx  10xxxxxx  10xxxxxx
			//utf16  110110wwwwxxxxxx  110111xxxxxxxxxx (wwww = uuuuu-1)
			str += char(
				0xd800 | ((c&7)<<8 | (arr[++i]&63)<<2 | arr[++i]>>>4&3) - 64,
				0xdc00 | (arr[i++]&15)<<6 | arr[i]&63
			);
		}
		else {str += char(0xfffd);}
	}
	return str;
};
Utf.decodeUTF16 = function(arr) {
	var endian = 'BE', i = 0, len, str;
	if (arr[0]*arr[1] === 0xff*0xfe) { // remove BOM
		endian = arr[0] === 0xff ? 'LE' : 'BE';
		i = 2;
	}
	for(len=arr.length,str=""; i<len; i+=2) {
		str += String.fromCharCode(endian === 'LE' ? (arr[i+1]<<8|arr[i]) : (arr[i]<<8|arr[i+1]));
	}
	return str;
};
```