Perl/문자열인코딩
Perl에서 문자열 인코딩 변환과 관련된 이슈
-
- 1. 인코딩 변환(1)
-
- 2. Encode 모듈과 Text::Iconv 모듈
-
-
- 2.1. 문자열 나누기
-
- 2.2. 파일에 저장해서 hex값 보기
-
- 2.3. 그래서...
-
3. 인코딩 추측
-
- 4. 참고
-
- 5. Comments
-
UseModWiki소스수정에서 사용되는 인코딩 변환 함수는 다음 두 개의 모듈 중 하나를 사용한다.
Encode 모듈이 있으면 그걸 사용하고, 없으면 Text::Iconv 모듈을 사용한다. 그것마저 없으면 변환 못 하는 거고...
sub convert_encode {
my ($str, $from, $to) = @_;
eval { require Encode; };
unless($@) {
$str = Encode::encode($to, Encode::decode($from, $str));
} else {
eval { require Text::Iconv; };
unless($@) {
my $converter = Text::Iconv->new($from, $to);
$str = $converter->convert($str);
}
}
return $str;
}
Encode(4) 모듈을 사용하는 경우에 한해서는 위의 decode()(5)와 encode()(6)를 결합하는 대신에 from_to()(7)를 사용해서 아래와 같이 쓸 수도 있음
Encode::from_to($str, $from, $to);
2. Encode 모듈과 Text::Iconv 모듈
이 두 모듈이... "UCS-2(8)", "UTF-16(9)", "UTF-32(10)" 이 세 가지 인코딩 명칭에 대해 동작이 서로 다르다. Encode(11) 모듈은 이걸 BE(big endian)으로 처리하고, Text::Iconv(12) 모듈은 이걸 LE(little endian)으로 처리한다.
따라서 UTF-8(13) =변환1=> UTF-16(14) =변환2=> UTF-8 순으로 변환(15)하는 경우, 당연히 원래의 바이트 시퀀스가 되어야 하지만, 변환1과 변환2에 쓰이는 모듈이 동일하지 않으면 제대로 되지 않는다.
이와 관련한 글은 [KLDP BBS: perl 로, 인코딩에 관계없이 문자열을 두 개의 문자열로 나누는 코드] 글타래에서 언급되었고, 여기에 몇가지 테스트 결과를 정리하면 다음과 같다.
일단, split_string 에서 중간에 정규표현식을 사용하여 쪼개는 과정을 생략하고, 단지 utf-8 -> 어떤 인코딩 -> utf-8 로 돌리는 것은 웬만하면 다 됩니다. "어떤 인코딩"이 UCS-2(16), UTF-16(17), UTF-32(18), 여기에 각각 BE, LE 붙여가며 해 봤는데, Encode(19) 모듈과 Text::Iconv(20) 모듈 다 스트링이 원래의 것으로 잘 나오더군요.
그런데 정규 표현식을 써서 쪼개기를 하려면, 제대로 쪼개지질 않더군요. 어떤 인코딩을 뭘로 지정해도...
이런 인코딩들의 경우는 정규표현식의 "."이 여전히 "1바이트"를 의미한다고 생각하고, $length 값을 2배로 곱해서 처리하게 했더니 그제서야 제대로 쪼개집니다.
아래의 표는 각각의 인코딩 이름을 두 개의 모듈을 써서, 한글 스트링, 일본어 스트링, 그리고 위키페디아에서 적당히 복사해 온 정체모를 언어 스트링을 split_string($str, 2) 를 거치게 했을 때의 결과입니다. (딱히 언어에 따라 다른 결과가 나오지는 않았습니다)
Encode모듈 Text::Iconv모듈
UCS-2 ok ok
UCS-2BE ok ok
UCS-2LE ok ok
UTF-16 error(주1) error(주5)
UTF-16BE ok ok
UTF-16LE ok ok
UTF-32(주2) error(주3) error(주6)
UTF-32BE ok ok
UTF-32LE ok ok
UTF-8 error(주4) error(주7)
* 주1) UTF-16:Unrecognised BOM b098 at /usr/lib/perl5/5.8.5/i386-linux-thread-multi/Encode.pm line 162
* 주2) $lengh 값을 4배 곱한 후 처리
* 주3) 주1과 같은 에러, 명칭만 UTF-32 로 나옴 (utf-16과 utf-32의 경우는 스트링 앞에 바이트오더를 알려주는
BOM이란 게 붙는다는 설명으로 보아, 스트링을 둘로 쪼갠 후에 뒷쪽 부분에는 그 BOM이 안 붙은 상태라서
이렇게 되는듯
* 주4) 문자들마다 바이트 수가 다르므로, 제대로 쪼개지지 않음.
* 주5) split_string 에 인자로 준 숫자보다 하나 적은 수의 문자를 세어서 쪼갬.(쪼갤 때 BOM도 두 바이트를
차지하기 때문인듯)
* 주6) 주5)와 동일
* 주7) 아예 결과가 ("", "") 으로 나옴
즉 endian 을 지정하지 않은 경우에, BOM이 붙느냐 마느냐, BOM이 붙은 스트링을 쪼갤 때 다시 양쪽에 BOM을 붙여 주느냐 마느냐 등의 문제로 서로 다르게 동작했다.
2.2. 파일에 저장해서 hex값 보기
use Encode;
$str = "\x{ac00}\x{b098}";
open OUT, ">out_unicode.txt"; print OUT "$str\n"; close OUT;
별도의 인코딩 없이 저 유니코드 표현을 그대로 파일에 찍으면 UTF-8로 출력하더군요.
$ hexdump out_unicode.txt
0000000 b0ea eb80 9882 000a <- 가 %EA%B0%80 나 %EB%82%98 그런데 두 바이트씩 끊은 후 little endian 으로 저장한 듯
0000007
그 다음은, 온갖 인코딩을 바꿔가면서 파일에 저장 =.=;;
for $enc qw(EUC-KR UCS-2 UCS-2BE UCS-2LE UTF-16 UTF-16BE UTF-16LE UTF-32BE UTF-32LE UTF-8) {
$str2 = encode($enc, $str);
open OUT, ">out_$enc.txt"; print OUT "$str2\n"; close OUT;
}
$str = "가나";
for $enc qw(EUC-KR UCS-2 UCS-2BE UCS-2LE UTF-16 UTF-16BE UTF-16LE UTF-32BE UTF-32LE UTF-8 UNICODE) {
$converter = Text::Iconv->new("UTF-8", "$enc");
$str2 = $converter->convert($str);
open OUT, ">conv_$enc.txt"; print OUT "$str2\n"; close OUT;
}
Encode()를 사용하여 Text::Iconv()를 사용하여
펄 내부 표현을 각각의 인코딩으로 변환 UTF-8 스트링을 각각의 인코딩으로 변환
--------------------------------------------------------------------
out_EUC-KR.txt conv_EUC-KR.txt
0000000 a1b0 aab3 000a 0000000 a1b0 aab3 000a
0000005 0000005
(가 %B0%A1 나 %B3%AA 인데 두 바이트씩 순서가 바뀌어 저장)
--------------------------------------------------------------------
out_UCS-2.txt conv_UCS-2.txt
0000000 00ac 98b0 000a 0000000 ac00 b098 000a
0000005 0000005
(둘이 다르다! Encode에서는 UCS-2BE로, Text::Iconv는 LE로 저장했음)
(파일이 저장되면서 두 바이트씩 바이트 순서가 바뀌어 저장되는지,
눈으로 봤을때는 좌측이 LE처럼 보임 =.=;)
--------------------------------------------------------------------
out_UCS-2BE.txt conv_UCS-2BE.txt
0000000 00ac 98b0 000a 0000000 00ac 98b0 000a
0000005 0000005
--------------------------------------------------------------------
out_UCS-2LE.txt conv_UCS-2LE.txt
0000000 ac00 b098 000a 0000000 ac00 b098 000a
0000005 0000005
--------------------------------------------------------------------
out_UTF-16.txt conv_UTF-16.txt
0000000 fffe 00ac 98b0 000a 0000000 feff ac00 b098 000a
0000007 0000007
(이것도 다르다! Encode는 UTF-16BE로, Text::Iconv는 LE로 저장했음)
(BOM 마크가 좀 이상하다... Encode는 BE의 BOM인 0xFeFF 를 앞에 붙이고
그게 파일로 저장되면서 순서가 바뀌었다고 볼 수 있는데, Text::Iconv가
붙인 BOM은 해석이 안 됨. BE의 BOM인 0xFeFF가 순서 바뀜 없이 저장된 거라면
그 뒤의 따라오는 "가나"가 16LE.txt와 동일하게 구성된 것과 상충하고,
LE의 BOM은 0xFFeF 이므로 순서를 바꾸면 ef ff 여야 한다. -_-???)
--------------------------------------------------------------------
out_UTF-16BE.txt conv_UTF-16BE.txt
0000000 00ac 98b0 000a 0000000 00ac 98b0 000a
0000005 0000005
--------------------------------------------------------------------
out_UTF-16LE.txt conv_UTF-16LE.txt
0000000 ac00 b098 000a 0000000 ac00 b098 000a
0000005 0000005
--------------------------------------------------------------------
out_UTF-32BE.txt conv_UTF-32BE.txt
0000000 0000 00ac 0000 98b0 000a 0000000 0000 00ac 0000 98b0 000a
0000009 0000009
--------------------------------------------------------------------
out_UTF-32LE.txt conv_UTF-32LE.txt
0000000 ac00 0000 b098 0000 000a 0000000 ac00 0000 b098 0000 000a
0000009 0000009
--------------------------------------------------------------------
out_UTF-8.txt conv_UTF-8.txt
0000000 b0ea eb80 9882 000a 0000000 b0ea eb80 9882 000a
0000007 0000007
--------------------------------------------------------------------
* 이건 좀 특별한 케이스
out_unicode.txt conv_UNICODE.txt
0000000 b0ea eb80 9882 000a 0000000 feff ac00 b098 000a
0000007 0000007
(Encode의 경우는 \x{ac00}\{b098}을 그대로 출력했더니 UTF-8과 동일하게
저장됐고, Text::Iconv 의 경우는 "UTF-8"을 "UNICODE"로 변환시켰더니만
UTF-16과 동일하게 저장됐음)
http://search.cpan.org/~dankogai/Encode-2.18/Unicode/Unicode.pm - perldoc Encode::Unicode
위 문서에 있는 내용에 따르면
- UTF-16(21)이나 UTF-32(22)는 뒤에 "BE"또는 "LE"를 생략하면 =>
- decode의 경우에는 BOM이 있는 지 체크해서 BOM이 있으면 그에 따라 처리하고, 없으면 루틴이 죽는다
- encode의 경우에는 BE로 인코딩한후 제일 앞에 BE에 해당하는 BOM을 붙인다.
- 단, UCS-2(23)의 경우는 예외로, 이건 IANA 등에도 등록되어 있으며 UCS-2BE(24)의 alias이다.
iconv 쪽에서는 어느 문서를 봐야 하는지 알수가 없어 못 찾았지만, 저 설명대로라면 UTF-16(25) 이나 UTF-32(26) 는 Encode(27) 모듈은 제 맘대로 BE취급하고 Text::Iconv(28) 는 LE 취급한다 싶군요. 그건 그러려니 하더라도, UCS-2(29) 만큼은 두 모듈이 공히 BE로 처리해줘야 할 텐데 Iconv 쪽은 LE로 처리하는 것이 의아합니다.
뭐 해결은 못해도... 일단 현재 상황은 정확히 알게 되었으니 만족해야겠고...
인코딩 명칭으로 "UTF-16(30)", "UTF-32(31)", "UCS-2(32)"를 사용할 경우, 두 모듈은 다르게 처리하더라...는 게 결국 힘빠지는 결말.
3. 인코딩 추측
스트링이 뭘로 인코딩되었는지 추측(33)하는 건
Encode::Guess 모듈을 쓴다. (이건 Encode모듈과는 별개) UTF-8이전작업/브라우저GET요청인코딩판별에서 사용된 코드:
if (eval "require Encode; require Encode::Guess;") {
my @suspects = qw(euc-kr utf8);
my $decoder = Encode::Guess::guess_encoding($string, @suspects);
if (ref($decoder)) {
return convert_encode($string, $decoder->name, $HttpCharset);
}
}
코드값 = 0xAC00 + (초성값 * 21 * 28) + (중성값 * 28) + 종성값
초성값 : ㄱ=0, ㄲ=1, ㄴ=2 ... ㅎ=18
중성값 : ㅏ=0, ㅑ=1, ... ㅣ=20
종성값 : 없음 = 0, ㄱ = 1, ㄲ = 2...ㅎ=27
5. Comments
컴퓨터분류
찾아보기:
Encode 2, 4, 11, 19, 27 decode() 5 encode() 6 from_to() 7 | Text::Iconv 3, 12, 20, 28 | 인코딩 UCS-2 8, 16, 23, 29, 32 UCS-2BE 24 UTF-16 9, 14, 17, 21, 25, 30 UTF-32 10, 18, 22, 26, 31 UTF-8 13 변환 1, 15 추측 33 | |