Dart で文字コードを変換するときの個人的におすすめなライブラリがこちら。
https://pub.dev/packages/charset_converter
iOS だと iOS SDK, Android だと Java 組み込みの機能をそのまま使って変換しているので幅広い文字コードに対応しており、バグも恐らく少なくそれでいて高速であるため。
逆に、これ以外のプラットフォームでは現状動作しない(macOS とか Windows)ので、現時点であまりいないとは思われますが、デスクトップアプリもサポートしたい場合には使えません。
そういう人向けには、代わりに以下が使えます。
https://pub.dev/packages/euc
ただこのライブラリを使うと、なぜか改行が無視されてしまう・・。(改行ありの euc-jp や shift_jis をデコードすると、改行なしになる)
なぜなのか気になってコードを見てみると、
class JISDecoder extends Converter<List<int>, String> {
@override
convert(input) {
List<int> result = [];
for (int i = 0; i < input.length; i++) {
var c1 = input[i];
if (c1 <= 0x7F) {
// ASCII Compatible
result.addAll(EUC_TABLE[c1] ?? []);
} else if ...
return utf8.decode(result);
}
}
// ASCII Compatible のところでLF(0x0a)もちゃんと入れているように見える・・。なんで動いてないんだろう?と思って EUC_TABLE を見ると、
const EUC_TABLE = {
0x00: [0],
0x10: [16],
0x20: [32],
...
こんな感じで、LF(0x0a)がテーブルに登録されてなかった・・。結果、0x0a は単純に捨てられていた。
EUC-JP だけじゃなくて Shift_JIS も同様にテーブルに登録されていないので改行が変換できていなかった。
結局どうしたかというと、以下のように // ASCII Compatible の処理を微妙に書き換えた新しいクラスを作ってそれの convert を直接呼び出してお茶を濁しました。
import 'package:euc/euc-table.dart';
import 'package:euc/jis-table.dart';
class MyJISDecoder extends Converter<List<int>, String> {
@override
convert(input) {
List<int> result = [];
for (int i = 0; i < input.length; i++) {
var c1 = input[i];
if (c1 <= 0x7F) {
// ASCII Compatible (partially)
result.add(c1);
} else if (c1 >= 0xa1 && c1 <= 0xdf) {
// Half-width Hiragana
result.addAll(JIS_TABLE[c1] ?? []);
} else if (c1 >= 0x81 && c1 <= 0x9f) {
// JIS X 0208
var c2 = input[++i];
result.addAll(JIS_TABLE[(c1 << 8) + c2] ?? []);
} else if (c1 >= 0xe0 && c1 <= 0xef) {
// JIS X 0208
var c2 = input[++i];
result.addAll(JIS_TABLE[(c1 << 8) + c2] ?? []);
} else {
// Unknown
result.addAll([]);
}
}
return utf8.decode(result);
}
}
class MyEucJPDecoder extends Converter<List<int>, String> {
@override
convert(input) {
List<int> result = [];
for (int i = 0; i < input.length; i++) {
var c1 = input[i];
if (c1 <= 0x7E) {
// ASCII Compatible
result.add(c1);
} else if (c1 == 0x8e) {
// Hiragana
var c2 = input[++i];
result.addAll(EUC_TABLE[(c1 << 8) + c2] ?? []);
} else if (c1 == 0x8f) {
// JIS X 0212
var c2 = input[++i];
var c3 = input[++i];
result.addAll(EUC_TABLE[(c1 << 16) + (c2 << 8) + c3] ?? []);
} else {
// JIS X 0208
var c2 = input[++i];
result.addAll(EUC_TABLE[(c1 << 8) + c2] ?? []);
}
}
return utf8.decode(result);
}
}
// iOS/Android では CharsetConverter が使えるのでこれを使います(高速だし高機能だしバグも少ない
// それ以外の環境だと euc パッケージを使います(ただし処理が遅い)
// CharsetConverter は変な文字が入っていると変換に失敗して null を返してくるので、その場合も euc パッケージを利用します(デコードの場合の話)
Future<Uint8List> encodeJIS(String input) async {
if (input == null) {
return null;
}
if (Platform.isIOS || Platform.isAndroid) {
return await CharsetConverter.encode('cp932', input);
} else {
return Uint8List.fromList(ShiftJIS().encode(input));
}
}
Future<Uint8List> encodeEucJP(String input) async {
if (input == null) {
return null;
}
if (Platform.isIOS || Platform.isAndroid) {
return await CharsetConverter.encode('euc-jp', input);
} else {
return Uint8List.fromList(EucJP().encode(input));
}
}
Future<String> decodeJIS(Uint8List input) async {
if (input == null) {
return null;
}
if (Platform.isIOS || Platform.isAndroid) {
var result = await CharsetConverter.decode('cp932', input);
if (result == null) {
return MyJISDecoder().convert(input.toList(growable: false));
} else {
return result;
}
} else {
return MyJISDecoder().convert(input.toList(growable: false));
}
}
Future<String> decodeEucJP(Uint8List input) async {
if (input == null) {
return null;
}
if (Platform.isIOS || Platform.isAndroid) {
var result = await CharsetConverter.decode('euc-jp', input);
if (result == null) {
return MyEucJPDecoder().convert(input.toList(growable: false));
} else {
return result;
}
}else {
return MyEucJPDecoder().convert(input.toList(growable: false));
}
}
ということで、charset_converter がおすすめっていう紹介と、charset_converter が使えない環境では euc パッケージを使わざるを得ないけど改行が無視されるバグがあるので上記の対応が必要、というお話でした。