Flutter - 파일 처리 ③: UTF-8·CP949 디코딩, CSV Converter
목차
1: Flutter - 파일 처리 ① 파일 들고 오기(file_picker)
file_picker
패키지를 사용하여 파일 들고 오기
2: Flutter - 파일 처리 ② drag & drop으로 파일 입력 받기(desktop_drop)
desktop_drop
패키지를 사용하여 웹에서 drag & drop으로 파일 입력 받기
3: Flutter - 파일 처리 ③: UTF-8·CP949 디코딩, CSV Converter <현재>현재>
- 읽어 온
UTF-8
또는CP949
로 인코딩된 파일을 디코딩하기
파일 디코딩
utf8
import 'dart:convert' show utf8;
UTF-8
인코딩/디코딩을 위해 convert
에서 utf8
부분을 들고 온다.
cp949
CP949
는 Microsoft Windows의 한글 인코딩 방식으로, EUC-KR
의 확장이다.
cp949 패키지로 CP949
를 인코딩, 디코딩할 수 있다.
cp949: ^1.2.1
dependencies
에 추가
// ignore: import_of_legacy_library_into_null_safe
import 'package:cp949/cp949.dart' as cp949;
조금 아쉽지만 cp949 패키지는 Null safety가 아니더라
빌드할 때는 --no-sound-null-safety
를 추가해야 한다.
안드로이드 스튜디오를 쓰는 경우 Run - Edit Configurations의 Additional arguments에 --no-sound-null-safety
를 추가하도록 하자
파일 디코딩 하기
PlatformFile file;
Uint8List fileBytes = file.bytes!;
XFile file;
Uint8List fileBytes = await file.readAsBytes();
이전 포스트에서 알아봤듯이 각 파일에서 Uint8List
타입으로 파일 바이트를 읽어올 수 있었다.
import 'dart:convert' show utf8;
import 'package:cp949/cp949.dart' as cp949;
String utf8Str = utf8.decode(fileBytes);
String cp949Str = cp949.decode(fileBytes);
해당 파일 바이트를 각 패키지를 통해 디코딩해서 문자열을 얻을 수 있다.
인코딩 방식을 모를 경우
CP949
로 인코딩 된 것을 UTF-8
로 디코딩할 경우 내용이 깨지고 에러가 난다.
static String convertBytesToString(Uint8List fileBytes){
String ret = "";
try{
ret = utf8.decode(fileBytes);
debugPrint("decode: UTF8");
}
catch (e) {
ret = cp949.decode(fileBytes);
debugPrint("decode: CP949");
}
return ret;
}
try-catch
문으로 이런 걸 방지하자
CSV Converter
번외로 CSV Converter를 만들어 보자
csv 패키지가 이미 있으므로 그걸 쓰면 되지만, 나는 엑셀이나 워드에서 복사 붙여넣기로 표를 만들어야 할 때도 있어서 따로 컨버터를 만들어보기로 했다.
내용 보기
CSV는 Comma Separated로, ,
로 값이 나눠져 있으며 다음 행으로는 \n
으로 구분된다.
내용,내용,내용\n
내용,내용,내용\n
내용,내용,내용\n
참고: 엑셀이나 워드에서 표를 복사할 경우 ,
가 아닌 \t
로 구분된다.
문제점
그렇다면 \n
으로 split
하고, ,
로 또 그것들을 split
해주면 될까??
되지 않았다!!
뭔가 싶어 아스키코드로 글자마다 다 출력해보니, csv의 각각 줄의 마지막마다 코드 13
, 즉 CR
이 항상 마지막에 있더라
CR은 Carriage Return, LF는 Line Feed인데,
한 줄을 내릴 때
Windows에서는 CRLF로, 즉 \r\n
,
Unix-Like에서는 그냥 LF, \n
을 사용한다.
csvStr = csvStr.replaceAll('\r\n', '\n');
// or
csvStr = csvStr.replaceAll('\r', '');
로 한 번 CRLF를 LF로 바꿔주자
그러고도 문제가 있다.
csv는 \n
과 ,
으로 각 셀들이 구분이 되어지는데, 셀 안에 이미 이 두 기호가 들어있는 경우에는 어떡할까??
케이스
1. \n
자 이제 이런 경우를 생각해 보자
abc abc |
abc | abc |
---|---|---|
asdd | abc | abcd |
a | b | c |
csv 파일 내에서 두 줄이 띄어진 경우다(엑셀에서는 Alt+Enter로 셀 내에서 줄 구분을 할 수 있다)
이 경우 raw 파일은 다음과 같다.
"abc
abc",abc,abc
a,b,c
a,b,c
보면 문자열 처리되어 있고, "abc\nabc"
로 입력되어 있는 걸 알 수 있다.
따라서 이 경우 그냥 \n
으로 split
을 하면 이상하게 돌아가게 된다.
아하 그럼 뭔가 문자열을 따로 구분할 일이 있을 때 "
를 사용하는구나
예를 들어 셀 안에 ,
이 들어있을 때에도 "
가 사용된다.
셀 abc => "ab,c"
2. ""
그렇다면 이번엔 이런 경우를 한 번 보자
abc abc |
abc” | “abc” |
---|---|---|
a | b | c |
a | b | c |
셀 자체에 이미 "
이 들어있다면 어떻게 될까
이 경우 raw 파일은 다음과 같다.
"abc
abc",abc"","""abc"""
a,b,c
a,b,c
즉 "
는 ""
로 표현하는 것을 알 수 있다.
3. ,
마지막으로
abc, | abc | |
---|---|---|
a | b | c |
a | b | c”c |
셀 안에 ,
가 들어 있는 경우는?
이 경우 raw 파일은
"abc,",,abc
a,b,c
a,b,"c""c"
당연하지만 "
로 감싸지게 된다.
코드
처리 로직은
"
으로 시작하면,
직전의"
찾으면 그게 한 셀- 셀 내에
""
가 있다면"
로 치환
- 셀 내에
"
로 시작하지 않으면- 그냥
,
직전까지가 한 셀- 셀 내에
""
가 있다면"
로 치환
- 셀 내에
- 그냥
보통 문자열 파싱 알고리즘 문제를 풀 때는 스택 자료구조로 풀었는데, 플러터에는 따로 없어서 그냥 구현했다.
static List<List<String>> splitCSV(String csvStr){
csvStr = csvStr.replaceAll('\r\n', '\n'); // convert CRLF to LF
List<List<String>> list = [[]];
int now = 0;
for(int i = 0; i < csvStr.length; i++){
if( csvStr[i] == '"' ){
String s = "";
int j = i+1;
for( ; j < csvStr.length; j++){
if( csvStr[j] == '"' ){
j++;
if( j >= csvStr.length ){
debugPrint("splitCSV error");
return [];
}
bool breakSign = false;
switch (csvStr[j]) {
case '"': // just "
s += '"';
break;
case ',': // cell ending
list[now].add(s);
breakSign = true;
break;
case '\n': // cell ending and line ending
list[now].add(s);
list.add([]);
now++;
breakSign = true;
break;
default:
debugPrint("splitCSV Error");
return [];
}
if( breakSign ) break;
}
else{
s += csvStr[j];
}
}
i = j;
}
else{
String s = "";
int j = i;
for( ; j < csvStr.length; j++){
bool breakSign = false;
switch (csvStr[j]) {
case '"':
j++;
if( j >= csvStr.length ){
debugPrint("splitCSV Error");
return [];
}
if( csvStr[j] == '"' ){ // just "
s += '"';
}
else{
debugPrint("splitCSV Error");
return [];
}
break;
case ',': // cell ending
list[now].add(s);
breakSign = true;
break;
case '\n': // cell ending and line ending
list[now].add(s);
list.add([]);
now++;
breakSign = true;
break;
default:
s += csvStr[j];
}
if( breakSign ) break;
}
i = j;
}
}
if( list.last.isEmpty ) list.removeAt(list.length-1);
return list;
}
cyj893.github.io/markdown_table_generator/에 적용함
댓글남기기