7

我正在将我们大学汉学系基于 DOS 的古老图书馆程序生成的文件转换为更有用和更易于访问的文件。

我正在处理的问题之一是导出的文本文件(大小约为 80MB)采用混合编码。我在 Windows 上。

德语变音符号和其他更高的 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 字符。它执行以下操作(除其他外):

  1. 用字母数字代码替换一些高阶 ASCII 字符(š、á 等)(不太可能自然地出现在文件中的其他任何位置)。例如:-Min, Jie / (šbers.)->-Min, Jie / (uumlautgrossbers.)
    注意:我手工制作了“转换表”,所以我只考虑了文档中实际出现的特殊字符。因此,转换并未完全完成,但在我的情况下产生了足够的结果,因为我们的书大多是德文、英文和中文的,只有很少的语言,如意大利语、西班牙语、法语等,几乎没有捷克语等。

  2. 仅当它们前面或后面没有高 ASCII 范围中的另一个字符时,才替换á, £, ¢, ¡, í为字母数字代码。(这些是和“ ”的 cp1252 编码版本,出现在 cp1252 和 GB18030 编码的字符串中。)\x80-\xFFß, ú, ó, ísmall nordic o with cross-stroke

  3. 读入整个文件并将其从GB18030转换为UTF8,从而将编码的汉字转换为真正的汉字。

  4. 将字母数字代码转换回它们的 Unicode 等价物。

虽然脚本大部分都可以工作,但会出现以下问题:

  • 将原来的 80MB 文件转换后,Notepad++ 仍然认为它是一个 ANSI 文件并按原样显示。我需要按“Encoding->Encode in UTF-8”才能正确显示。

我想知道的是:

  1. 一般来说,有没有更好的方法将混合编码文件转换为 UTF-8?

  2. 如果不是,我应该使用use utf8以便我可以在codes2char子例程中直接输入字符而不是它们的十六进制表示吗?

  3. 文件开头的 BOM 是否可以解决 NP++ 最初将其显示为 ANSI 文件的问题?如果是这样,我应该如何修改我的脚本以使输出文件具有 BOM?

  4. 转换后我可能想调用更多的子程序(例如将整个文件转换为 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.)  

还有两个来说明:

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”)。

在库程序中,汉字是通过一个附加程序输入和显示的,该程序必须首先加载,据我所知,它在底层连接到图形驱动程序,捕获编码的汉字并显示它们作为角色,同时不理会其他一切。德语变音等由图书馆程序本身处理。

我不完全理解这是如何工作的,即程序如何知道 HexE1 是否被视为单个字符á并因此根据codepage X以及何时转换为多字节字符的一部分并因此根据codepage Y

我发现的最接近的近似值是,如果特殊字符之前或之后有其他特殊字符,则它很可能是中文字符串的一部分。(例如ÎÂÈá¶Øºñ

4

1 回答 1

2
  1. 如果混合编码使得每行/记录/字段/任何内容都采用一致的编码,则您可以单独读取和转换每行/记录/字段/任何内容。但这听起来不像这里的情况。
  2. 不会是个坏主意。
  3. UTF-8 通常不使用 BOM,但如果您真的想尝试输出字符 U+FEFF(在 UTF-8 中,即 3 bytes ef bb bf)。如果你能弄清楚为什么 NP++ 会错误地检测到文件,那就更好了。
  4. 读取 UTF-8 编码文件时,使用 UTF-8 输入层打开它是个好主意。如果你愿意,<:utf8是一个更短的等价于< :encoding(UTF-8).

至于原来的混乱是如何工作的,似乎“附加程序”只是将任何看起来像汉字的东西转换成中文,而剩下任何东西(标准驱动程序然后使用欧洲编码显示),而“库程序”只输出它收到的任何代码。因此,转换文件的更直接的方法可能是镜像:使用:encoding(latin-1)(或其他)读取文件,然后替换中文字符(例如s/\xc8\xe1/柔/)。

于 2011-08-01T15:36:17.327 回答