我正在将我们大学中文系古老的基于DOS的图书馆程序生成的文件转换为更有用和易于访问的内容。
我正在处理的问题之一是导出的文本文件(约80MB)采用混合编码。我使用的是Windows操作系统。
德语umlauts和其他高ASCII字符采用cp1252编码,CJK字符采用GB18030编码。由于“重叠”编码的原因,我不能只是将整个文件拖到Word或其他软件中进行转换,否则会得到类似以下内容:
原始:
+Autor:
-Yan, Lianke / ÑÖÁ¬¿Æ # encoded Chinese characters
+Co-Autor:
-Min, Jie / (šbers.) # encoded German U-umlaut (Ü)
结果:
+Autor:
-Yan, Lianke / 阎连科 # good
+Co-Autor:
-Min, Jie / (歜ers.) # bad... (should be: "Übers.")
我写了一个脚本,包含多个子程序,可以将非ASCII字符转换为Unicode字符。它执行以下操作(还有其他操作):
用字母数字代码替换某些高阶ASCII字符(如š、á等)。例如:
-Min, Jie / (šbers.)
->-Min, Jie / (uumlautgrossbers.)
。注意:我手动制作了“转换表”,因此只考虑了文档中实际出现的特殊字符。因此,转换不完全,但在我的情况下可以产生足够的结果,因为我们的书大多是德语、英语和中文,只有极少数是意大利语、西班牙语、法语等语言,几乎没有捷克语等。仅当它们不是前面或后面的另一个高ASCII范围内的字符
\x80-\xFF
时,将á、£、¢、¡、í
替换为字母数字代码(这些是cp1252编码的ß、ú、ó、í
和“带交叉线的小北欧o”版本,它们都出现在cp1252和GB18030编码的字符串中)。读入整个文件,并将其从GB18030转换为UTF8,从而将编码的中文字符转换为真正的中文字符。
将字母数字代码转换回其Unicode等效项。
虽然脚本基本上可以工作,但存在以下问题:
- 在转换原始80MB文件后,Notepad++仍然认为它是ANSI文件,并将其显示为ANSI。我需要按“编码->以UTF-8编码”才能正确显示。
我想知道的是:
通常,是否有更好的方法将混合编码文件转换为UTF-8?
如果没有,我应该使用
use utf8
,以便直接输入字符而不是它们在codes2char
子程序中的十六进制表示形式吗?文件开头加上BOM能否解决NP++最初将其显示为ANSI文件的问题?如果可以,我应该如何修改我的脚本,使输出文件具有BOM?
在转换之后,我可能需要调用一些其他子程序(例如,将整个文件转换为CSV或ODS格式)。我需要继续使用
codes2char
子程序的开头语句吗?
代码由多个子程序组成,这些子程序在结尾处被调用:
!perl -w
use strict;
use warnings;
use Encode qw(decode encode);
use Encode::HanExtra;
our $input = "export.txt";
our $output = "export2.txt";
sub switch_var { # switch Input and Output file between steps
($input, $output) = ($output, $input);
}
sub specialchars2codes {
open our $in, "<$input" or die "$!\n";
open our $out, ">$output" or die "$!\n";
while( <$in> ) {
## replace higher ASCII characters such as a-umlaut etc. with codes.
s#\x94#oumlautklein#g;
s#\x84#aumlautklein#g;
s#\x81#uumlautklein#g;
## ... and some more. (ö, Ö, ä, Ä, Ü, ü, ê, è, é, É, â, á, à, ì, î,
## û, ù, ô, ò, ç, ï, a°, e-umlaut and ñ in total.)
## replace problematic special characters (ß, ú, ó, í, ø, ') with codes.
s#(?<![\x80-\xFF])\xE1(?![\x80-\xFF])#eszett#g;
s#(?<![\x80-\xFF])\xA3(?![\x80-\xFF])#uaccentaiguklein#g;
s#(?<![\x80-\xFF])\xA2(?![\x80-\xFF])#oaccentaiguklein#g;
s#(?<![\x80-\xFF])\xA1(?![\x80-\xFF])#iaccentaiguklein#g;
s#(?<![\x80-\xFF])\xED(?![\x80-\xFF])#nordischesoklein#g;
print $out $_;
}
close $out;
close $in;
}
sub convert2unicode {
open(our $in, "< :encoding(GB18030)", $input) or die "$!\n";
open(our $out, "> :encoding(UTF-8)", $output) or die "$!\n";
print "Convert ASCII to UTF-8\n\n";
while (<$in>) {
print $out $_;
}
close $in;
close $out;
}
sub codes2char {
open(our $in, "< :encoding(UTF-8)", $input) or die "$!\n";
open(our $out, "> :encoding(UTF-8)", $output) or die "$!\n";
print "replace Codes with original characters.\n";
while (<$in>) {
s#lidosoumlautklein#\xF6#g;
s#lidosaumlautklein#\xE4#g;
s#lidosuumlautklein#\xFC#g;
## ... and some more.
s#eszett#\xDF#g;
s#uaccentaiguklein#\xFA#g;
s#oaccentaiguklein#\xF3#g;
s#iaccentaiguklein#\xED#g;
s#nordischesoklein#\xF8#g;
print $out $_;
}
close($in) or die "can't close $input: $!";
close($out) or die "can't close $output: $!";
}
##################
## Main program ##
##################
&specialchars2codes;
&switch_var;
&convert2unicode;
&switch_var;
&codes2char;
哇,这很长。希望不会太复杂。
编辑:
这是上面示例字符串的十六进制转储:
01A36596 2B 41 +A
01A365A9 75 74 6F 72 3A 0D 0A 2D 59 61 6E 2C 20 4C 69 61 6E 6B 65 utor: -Yan, Lianke
01A365BC 20 2F 20 D1 D6 C1 AC BF C6 0D 0A 2B 43 6F 2D 41 75 74 6F / ÑÖÁ¬¿Æ +Co-Auto
01A365CF 72 3A 0D 0A 2D 4D 69 6E 2C 20 4A 69 65 20 2F 20 28 9A 62 r: -Min, Jie / (šb
01A365E2 65 72 73 2E 29 0D 0A ers.)
and another two to illustrate:
1.
000036B3 2D 52 75 -Ru
000036C6 E1 6C 61 6E 64 0D 0A áland
2.
015FE030 2B 54 69 74 65 6C 3A 0D 0A 2D 57 65 6E 72 6F 75 +Titel: -Wenrou
015FE043 64 75 6E 68 6F 75 20 20 CE C2 C8 E1 B6 D8 BA F1 20 28 47 dunhou ÎÂÈá¶Øºñ (G
015FE056 65 6E 74 6C 65 6E 65 73 73 20 61 6E 64 20 4B 69 6E 64 6E entleness and Kindn
015FE069 65 73 73 29 2E 0D 0A ess).
在这两种情况下,都有十六进制值为E1。在第一种情况下,它代表一个德语sharp-s字母(ß,“Rußland”=“Russia”),而在第二种情况下,它是多字节CJK字符柔 (读作:"rou")的一部分。
在库程序中,中文字符是通过另一个必须首先加载的附加程序输入和显示的。据我所知,该程序与图形驱动程序相连,以低级方式捕获编码的中文字符并将其显示为字符,同时保留其他所有内容。德语umlauts等则由库程序本身处理。
我不完全理解这是如何工作的,即程序如何知道HexE1是要被视为单个字符á,因此根据codepage X进行转换,还是它是多字节字符的一部分,因此根据codepage Y进行转换。
我找到的最接近的近似值是,如果在特殊字符之前或之后有其他特殊字符,则特殊字符很可能是中文字符串的一部分。(例如:
ÎÂÈá¶Øºñ
)
š
是CP850编码,视为Windows-1252。ÑÖÁ¬¿Æ
是GB18030编码,视为Windows-1252。 - daxim