| 订阅 | 在线投稿
分享
 
 
 

用Perl来分析并生成中文Excel文件

2008-05-30 23:00:59 编辑來源:互联网 国际版 评论
 
 
  最近实验室作为自学考试的考场,需要在服务器上面为每个学生创建FTP帐号,我计划用Perl来实现的批处理创建。考虑到获取的考场学生名单是存储在Excel文件里面的,因此还需要让Perl去分析Excel文件。通过google找到用Spreadsheet::ParseExcel以及Spreadsheet::WriteExcel来读写Excel。在www.cpan.org上下载了相应的Module并看了文档、范例后,终于写出了一个程序可以读考场学生名单,并生成密码清单存到另一个Excel文件中。

  这还只是第一步,刚写出来的程序读Excel文件中的中文,也无法将中文写入Excel文件:单元格(Cell) 和工作簿(Worksheet) 中的汉字。

  在找相应的帮助,得知可以用Spreadsheet::ParseExcel::FmtUnicode来处理Excel文件中的Unicode字符,其使用方法如下:

  use Spreadsheet::ParseExcel::FmtUnicode;

  my $oFmtJ = Spreadsheet::ParseExcel::FmtUnicode->new(Unicode_Map => CODE);

  my $oBook = Spreadsheet::ParseExcel::Workbook->Parse($ARGV[0], $oFmtJ);

  知道了实现的方法,但是这个CODE的值应该为多少还不知道。刚开始我猜测是'GB2312',可是不知道是哪里其他什么地方错了导致不成功;后来看到Manual里提到'GB2312-80',也试了一下,还是不行。最后只好google,发现别人用的是'CP936',这次就成功了。当成功了以后再把CODE改回'GB2312'居然也可以了。

  现在读Excel文件已经没有问题了,可是尽管这些中文读出来了,可是在写Excel文件的时候并无法写入中文。

  解决方案就只有两种了:网上搜索答案;看ParseExcel的原文件逆向处理。

  首先通过看WriteExcel的Manual得知它是支持写Unicode字符的,其中就有一个Example说明了通过write_unicode()函数来向单元格写入日文Unicode字符。可是Example里面提供的日文字符串是通过pack来生成的,本身已经是Unicode格式的了,而我们通常使用的GB2312的字符不属于Unicode字符串,所以没法直接写入。那么如何转换呢?

  通过分析Spreadsheet::ParseExcel.pm和Spreadsheet::ParseExcel::FmtUnicode.pm发现:所有通过ParseExcel从Excel文件中分析出来的字符都是经过函数TextFmt()格式化过的,这个函数的定义在FmtUnicode.pm中。而TextFmt()核心是通过Unicode::Map的from_unicode()函数来将一个unicode字符串转换为非unicode的字符串,当然在转换之前还做了一个处理:s/(.)/\x00$1/sg。

  根据这个思路,就在WriteExcel之前,创建一个Unicode::Map对象,然后调用对象里的to_unicode函数进行字符串格式转换,最后调用write_unicode函数将中文写入单元格(Cell) 中。下面给出一个简单的Example:

  use Unicode::Map();

  my $Map = new Unicode::Map("GB2312");

  $worksheet->write_unicode($iR, 2, $Map->to_unicode("考生姓名"))

  单元格中的中文可以正常显示了,可是在写工作簿名称的时候这个方法就不那么管用了,像$worksheet = $workbook->add_worksheet($Map->to_unicode($name)这样进行的话,就会产生工作簿名称非法的错误而退出。同样的方法在set_header()是也不管用,尽管不会出错可是显示的却是乱码。在处理单元格的时候有分unicode的方法和非unicode的方法,为什么add_worksheet的时候没有呢?莫非要自己去写个函数或者加个参数来扩展?

  单元格中的中文可以正常显示了,可是在写工作簿名称的时候这个方法就不那么管用了,像$worksheet = $workbook->add_worksheet($Map->to_unicode($name)这样进行的话,就会产生工作簿名称非法的错误而退出。同样的方法在set_header()是也不管用,尽管不会出错可是显示的却是乱码。在处理单元格的时候有分unicode的方法和非unicode的方法,为什么add_worksheet的时候没有呢?莫非要自己去写个函数或者加个参数来扩展?

  再次进入源代码Spreadsheet::WriteExcel::Workbook.pm,发现原来add_worksheet()函数还可以传递一个$encoding的参数的,可是这个参数仅用于判断输入的unicode字符是否符合长度要求,编码转换哪里去了?如果说要自己去补齐的话该加什么代码呢?比较Spreadsheet::WriteExcel::Worksheet.pm中的write()(实际上最后调用的是write_string)和write_unicode()发现,后者比前者多了相应的这么一段代码>(说相应是由于一些变量名的差异,将此代码直接添加到前者是不能工作的):

  # Check for a valid 2-byte char string.

  croak "Uneven number of bytes in Unicode string" if $num_bytes % 2;

  # Change from UTF16 big-endian to little endian

  $str = pack "v*", unpack "n*", $str

  那么也就是说将这段代码加入到add_worksheet()适当的位置就可以喽?答案是令人沮丧的。为了查找原因再次回到Spreadsheet::ParseExcel.pm,从调入Excel文件分析Excel文件开始,看看工作簿名称是如何得到的。

  分析代码发现,TextFmt()处理工作簿名称时是这样的:$sWsName = $oBook->{FmtClass}->TextFmt($sWsName, 'ucs2')。TextFmt()函数还有一部分是针对Excel文件个别类型的字符串(如header,footer,工作簿名称等)不做上面提到的处理(s/(.)/\x00$1/sg)。可是这个不是关键问题,不能解释为什么直接装换为unicode的字符不能写入。

  进一步分析发现,相对于单元格的字符其他的特殊的字符再进行TextFmt()格式化之前都有进行类似_SwapForUnicode(\$sWsName)的调用,也就是说还有特殊处理:

  sub _SwapForUnicode(\$)

  {

  my($sObj) = @_;

  #for(my $i = 0; $i for(my $i = 0; $i<(int (length($$sObj) / 2) * 2); $i+=2) {

  my $sIt = substr($$sObj, $i, 1)

  substr($$sObj, $i, 1) = substr($$sObj, $i+1, 1);

  substr($$sObj, $i+1, 1) = $sIt

  }

  }

  根据以上所有的分析,最后得出了一个解决方案:

  my $sWsName = $Map->to_unicode($sWsName);

  &SwapForUnicode(\$name);

  my $worksheet = $workbook->add_worksheet($name, 1);

  再经历两天的失败了以后,成功意外的降临了,上面的代码是可行的。第一行,将非Unicode的字符转换为Unicode的;第二行,变更其存储格式使之符合Excel文件的要求;第三行,通过带参数$encoding的调用,执行了相当于write_unicode()中写入unicode字符的代码(事实上这部分代码所说的自行添加的部分,NOTE:修改了Module的源文件):$name = pack "v*", unpack "n*", $name;

  最后是与标题无关的总结。

  Spreadsheet这两个模块处理Excel的能力太过独立,二者很难结合的很好。两个模块要么只能读,要么只能写,必须要一个中间的数据存储。

  虽然说ParseExcel使用WriteExcel模块写了一个SaveParser,可本质上还是通过用SaveAS方法来新建了一个Excel对象并把数据复制过去,并没有真正意义上的“Save”。

  而且SaveParser还有一个严重的问题:它内部同时使用了ParseExcel和WriteExcel的Workbook对象,可是却无法将二者统一起来:两个对象执行同一功能的函数名不同,如AddFormat()和add_format(),让人很难确定什么时候改用什么;甚至很多功能函数没有继承下来,如keep_leading_zeros(),这给我写“000946”带来了很大的麻烦。

  希望以后能够出一个Module将这二者很好的结合起来新Module。
 
 
最近实验室作为自学考试的考场,需要在服务器上面为每个学生创建FTP帐号,我计划用Perl来实现的批处理创建。考虑到获取的考场学生名单是存储在Excel文件里面的,因此还需要让Perl去分析Excel文件。通过google找到用Spreadsheet::ParseExcel以及Spreadsheet::WriteExcel来读写Excel。在www.cpan.org上下载了相应的Module并看了文档、范例后,终于写出了一个程序可以读考场学生名单,并生成密码清单存到另一个Excel文件中。 这还只是第一步,刚写出来的程序读Excel文件中的中文,也无法将中文写入Excel文件:单元格(Cell) 和工作簿(Worksheet) 中的汉字。 在找相应的帮助,得知可以用Spreadsheet::ParseExcel::FmtUnicode来处理Excel文件中的Unicode字符,其使用方法如下: use Spreadsheet::ParseExcel::FmtUnicode; my $oFmtJ = Spreadsheet::ParseExcel::FmtUnicode->new(Unicode_Map => CODE); my $oBook = Spreadsheet::ParseExcel::Workbook->Parse($ARGV[0], $oFmtJ); 知道了实现的方法,但是这个CODE的值应该为多少还不知道。刚开始我猜测是'GB2312',可是不知道是哪里其他什么地方错了导致不成功;后来看到Manual里提到'GB2312-80',也试了一下,还是不行。最后只好google,发现别人用的是'CP936',这次就成功了。当成功了以后再把CODE改回'GB2312'居然也可以了。 现在读Excel文件已经没有问题了,可是尽管这些中文读出来了,可是在写Excel文件的时候并无法写入中文。 解决方案就只有两种了:网上搜索答案;看ParseExcel的原文件逆向处理。 首先通过看WriteExcel的Manual得知它是支持写Unicode字符的,其中就有一个Example说明了通过write_unicode()函数来向单元格写入日文Unicode字符。可是Example里面提供的日文字符串是通过pack来生成的,本身已经是Unicode格式的了,而我们通常使用的GB2312的字符不属于Unicode字符串,所以没法直接写入。那么如何转换呢? 通过分析Spreadsheet::ParseExcel.pm和Spreadsheet::ParseExcel::FmtUnicode.pm发现:所有通过ParseExcel从Excel文件中分析出来的字符都是经过函数TextFmt()格式化过的,这个函数的定义在FmtUnicode.pm中。而TextFmt()核心是通过Unicode::Map的from_unicode()函数来将一个unicode字符串转换为非unicode的字符串,当然在转换之前还做了一个处理:s/(.)/\x00$1/sg。 根据这个思路,就在WriteExcel之前,创建一个Unicode::Map对象,然后调用对象里的to_unicode函数进行字符串格式转换,最后调用write_unicode函数将中文写入单元格(Cell) 中。下面给出一个简单的Example: use Unicode::Map(); my $Map = new Unicode::Map("GB2312"); $worksheet->write_unicode($iR, 2, $Map->to_unicode("考生姓名")) 单元格中的中文可以正常显示了,可是在写工作簿名称的时候这个方法就不那么管用了,像$worksheet = $workbook->add_worksheet($Map->to_unicode($name)这样进行的话,就会产生工作簿名称非法的错误而退出。同样的方法在set_header()是也不管用,尽管不会出错可是显示的却是乱码。在处理单元格的时候有分unicode的方法和非unicode的方法,为什么add_worksheet的时候没有呢?莫非要自己去写个函数或者加个参数来扩展? 单元格中的中文可以正常显示了,可是在写工作簿名称的时候这个方法就不那么管用了,像$worksheet = $workbook->add_worksheet($Map->to_unicode($name)这样进行的话,就会产生工作簿名称非法的错误而退出。同样的方法在set_header()是也不管用,尽管不会出错可是显示的却是乱码。在处理单元格的时候有分unicode的方法和非unicode的方法,为什么add_worksheet的时候没有呢?莫非要自己去写个函数或者加个参数来扩展? 再次进入源代码Spreadsheet::WriteExcel::Workbook.pm,发现原来add_worksheet()函数还可以传递一个$encoding的参数的,可是这个参数仅用于判断输入的unicode字符是否符合长度要求,编码转换哪里去了?如果说要自己去补齐的话该加什么代码呢?比较Spreadsheet::WriteExcel::Worksheet.pm中的write()(实际上最后调用的是write_string)和write_unicode()发现,后者比前者多了相应的这么一段代码>(说相应是由于一些变量名的差异,将此代码直接添加到前者是不能工作的): # Check for a valid 2-byte char string. croak "Uneven number of bytes in Unicode string" if $num_bytes % 2; # Change from UTF16 big-endian to little endian $str = pack "v*", unpack "n*", $str 那么也就是说将这段代码加入到add_worksheet()适当的位置就可以喽?答案是令人沮丧的。为了查找原因再次回到Spreadsheet::ParseExcel.pm,从调入Excel文件分析Excel文件开始,看看工作簿名称是如何得到的。 分析代码发现,TextFmt()处理工作簿名称时是这样的:$sWsName = $oBook->{FmtClass}->TextFmt($sWsName, 'ucs2')。TextFmt()函数还有一部分是针对Excel文件个别类型的字符串(如header,footer,工作簿名称等)不做上面提到的处理(s/(.)/\x00$1/sg)。可是这个不是关键问题,不能解释为什么直接装换为unicode的字符不能写入。 进一步分析发现,相对于单元格的字符其他的特殊的字符再进行TextFmt()格式化之前都有进行类似_SwapForUnicode(\$sWsName)的调用,也就是说还有特殊处理: sub _SwapForUnicode(\$) { my($sObj) = @_; #for(my $i = 0; $i for(my $i = 0; $i<(int (length($$sObj) / 2) * 2); $i+=2) { my $sIt = substr($$sObj, $i, 1) substr($$sObj, $i, 1) = substr($$sObj, $i+1, 1); substr($$sObj, $i+1, 1) = $sIt } } 根据以上所有的分析,最后得出了一个解决方案: my $sWsName = $Map->to_unicode($sWsName); &SwapForUnicode(\$name); my $worksheet = $workbook->add_worksheet($name, 1); 再经历两天的失败了以后,成功意外的降临了,上面的代码是可行的。第一行,将非Unicode的字符转换为Unicode的;第二行,变更其存储格式使之符合Excel文件的要求;第三行,通过带参数$encoding的调用,执行了相当于write_unicode()中写入unicode字符的代码(事实上这部分代码所说的自行添加的部分,NOTE:修改了Module的源文件):$name = pack "v*", unpack "n*", $name; 最后是与标题无关的总结。 Spreadsheet这两个模块处理Excel的能力太过独立,二者很难结合的很好。两个模块要么只能读,要么只能写,必须要一个中间的数据存储。 虽然说ParseExcel使用WriteExcel模块写了一个SaveParser,可本质上还是通过用SaveAS方法来新建了一个Excel对象并把数据复制过去,并没有真正意义上的“Save”。 而且SaveParser还有一个严重的问题:它内部同时使用了ParseExcel和WriteExcel的Workbook对象,可是却无法将二者统一起来:两个对象执行同一功能的函数名不同,如AddFormat()和add_format(),让人很难确定什么时候改用什么;甚至很多功能函数没有继承下来,如keep_leading_zeros(),这给我写“000946”带来了很大的麻烦。 希望以后能够出一个Module将这二者很好的结合起来新Module。
󰈣󰈤
日版宠物情人插曲《Winding Road》歌词

日版宠物情人2017的插曲,很带节奏感,日语的,女生唱的。 最后听见是在第8集的时候女主手割伤了,然后男主用嘴帮她吸了一下,插曲就出来了。 歌手:Def...

兄弟共妻,我成了他们夜里的美食

老钟家的两个儿子很特别,就是跟其他的人不太一样,魔一般的执着。兄弟俩都到了要结婚的年龄了,不管自家老爹怎么磨破嘴皮子,兄弟俩说不娶就不娶,老父母为兄弟两操碎了心...

网络安全治理:国家安全保障的主要方向是打击犯罪,而不是处置和惩罚受害者

来源:中国青年报 新的攻击方法不断涌现,黑客几乎永远占据网络攻击的上风,我们不可能通过技术手段杜绝网络攻击。国家安全保障的主要方向是打击犯罪,而不是处置和惩罚...

 
 
 
>>返回首页<<
 为你推荐
 
 
 
 转载本文
 UBB代码 HTML代码
复制到剪贴板...
 
 
 热帖排行
 
美人鱼靓丽摄影(3)
美人鱼靓丽摄影(2)
熊耳山徒步归来
年月的桂林-NO
 
 
王朝网络微信公众号
微信扫码关注本站公众号wangchaonetcn
 
  免责声明:本文仅代表作者个人观点,与王朝网络无关。王朝网络登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
©2005- 王朝网络 版权所有