关于在Perl中将混合编码文件转换为UTF8的问题

时间:2011-08-01 11:48:51

标签: perl encoding utf-8 mixed

我正在将我们大学中国研究部古老的基于DOS的图书馆程序生成的文件转换为更有用和可访问的内容。

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

德语变音符号和其他高位ASCII字符在cp1252中编码,我认为是GB18030中的CJK字符。由于“重叠”编码,我不能将整个文件拖到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. 使用字母数字代码替换á, £, ¢, ¡, í,前提是它们不在高位范围\x80-\xFF 中的其他字符之前或之后。 (这些是ß, ú, ó, í和“small nordic o with cross-stroke”的cp1252编码版本,并且同时出现在cp1252和GB18030编码的字符串中。)

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

  4. 将字母数字代码转换回其Unicode等效代码。

  5. 虽然脚本主要起作用,但会出现以下问题:

    • 转换原始的80MB文件后,Notepad ++仍然认为它是一个ANSI文件并显示它。我需要按“编码 - >在UTF-8中编码”才能正确显示。

    我想知道的是:

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

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

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

    4. 转换后,我可能想要调用更多的子程序(例如将整个文件转换为CSV或ODS格式)。我是否需要继续使用codes2char子程序中的开始语句?

    5. 代码由几个子程序组成,最后调用它们:

      !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;
      
      哇,这很长。我希望它不会太复杂

      修改

      这是上面示例字符串的hexdump:

      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。在第一种情况下,它代表德国尖头(ß,“Rußland”=“俄罗斯”),在第二种情况下,它是多字节CJK字符柔和的一部分(读作:“rou”)。

      在库程序中,输入并显示中文字符,附加程序必须首先加载,据我所知,它被挂在低级别的图形驱动程序中,捕获编码的汉字并将它们显示为字符,同时保留其他所有内容。德国的变音符号等由图书馆程序本身处理。

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

      进行转换

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

1 个答案:

答案 0 :(得分:2)

  1. 如果混合编码使得每行/记录/字段/无论是一致编码,您都可以单独读取和转换每一行/记录/字段/任何内容。但这听起来不像这样。
  2. 不会是一个坏主意。
  3. UTF-8通常不使用BOM,但如果你真的想尝试输出字符U + FEFF(UTF-8,那就是3字节ef bb bf)。如果你能弄明白为什么NP ++错误地检测文件会更好。
  4. 读取UTF-8编码文件时,使用UTF-8输入图层打开它是一个好主意。如果您愿意,<:utf8等同于< :encoding(UTF-8)
  5. 至于原始混乱是如何工作的,似乎“附加程序”只是将看起来像中文字符的任何内容转换成中文并留下其他任何东西(标准驱动程序随后使用欧洲编码显示),而“库程序”只输出它收到的任何代码。因此,转换文件的更简单方法可能是镜像:使用:encoding(latin-1)(或其他)读取文件,然后替换中文字符(例如s/\xc8\xe1/柔/)。