Perl脚本在将Excel文件转换为CSV时生成符号Â

时间:2016-06-15 11:28:12

标签: perl

我们的系统中有一个批处理过程,它将使用Perl将Excel .xlsx文件转换为CSV格式。当它转换CSV文件时,会产生一些像Â这样的符号,所以我没有得到预期的结果。有些人可以帮我转换为CSV时如何使用与Excel文件中相同的值吗?

Excel文件中的值:

Unverifiable License Documentation  NB Only

通过Perl以CSV格式转换的值:

Unverifiable License Documentation – NB Only

我希望在转换为CSV

时保留与Excel中相同的值

注意:我在打开文件时使用了编码(UTF-8),但即使这样它也无效。

我的Perl代码

use Spreadsheet::XLSX;
use File::Basename;
use set_env_cfg;
use Date::Simple (':all');
use Math::Round;

$sts = open( INP, "< ${if}" );
#$sts = open (INP, '<:encoding(UTF-8)', ${if}       );
#$sts = open (INP, '<:encoding(ISO-8859-1)', ${if}       );

if ( $sts == 0 ) {

    print LOG tmstmp() . ": Error opening input file\n";
    close LOG;
    print LOG "$ldlm\n";
    `cp $lf $od`;
    die;
}

print LOG "$ldlm\n";
print LOG tmstmp() . ": Conversion started for $if\n";

$oBook = Spreadsheet::XLSX->new($if);

foreach $WkS ( @{ $oBook->{Worksheet} } ) {

    print LOG tmstmp() . ": Converting worksheet                            -----  " . $WkS->{Name}, "\n";

    $cfgrec = '';    # initialize the configure record

    $sts = open( OUT, ">$od/$WkS->{Name}.txt" );

    if ( $sts == 0 ) {

        print LOG tmstmp() . ": Error opening output file\n";
        close LOG;
        close INP;
        print LOG "$ldlm\n";

        `cp $lf $od`;
        die;
    }

    $WkS->{MaxRow} ||= $WkS->{MinRow};

    foreach $iR ( $WkS->{MinRow} .. $WkS->{MaxRow} ) {

        $WkS->{MaxCol} ||= $WkS->{MinCol};

        print OUT $cfgkey if ( ( $cfgko == 0 ) && ( $iR >= $hdrcnt ) );

        foreach $iC ( $WkS->{MinCol} .. $WkS->{MaxCol} ) {

            $cell = $WkS->{Cells}[$iR][$iC];

            if ($cell) {

                if ( ( $cell->{Type} ) eq "Date" ) {

                    if ( int( $cell->{Val} ) == ( $cell->{Val} ) ) {
                        $tmpval = date("1900-01-01") + ( $cell->{Val} ) - 2;
                    }
                    else {

                        $css = round( ( ( $cell->{Val} ) - int( $cell->{Val} ) ) * 86400 );
                        $cmi = int( $css / 60 );
                        $chr = int( $css / 3600 );

                        $css = $css - $cmi * 60;
                        $cmi = $cmi - $chr * 60;

                        $tmpval = date("1900-01-01") + int( $cell->{Val} ) - 2;
                        $tmpval .= " $chr:$cmi:$css";
                    }
                }
                else {
                    $tmpval = Spreadsheet::XLSX::Utility2007::unescape_HTML( $cell->{Val} );
                }

                print OUT $tmpval;    ###Added double quotes in txt file to handle the comma delimiter value
            }

            if ( ( $iR == ${hdr_seq} - 1 ) ) {

                if ( ( $cell->{Type} ) eq "Date" ) {

                    if ( int( $cell->{Val} ) == ( $cell->{Val} ) ) {

                        $tmpval = date("1900-01-01") + ( $cell->{Val} ) - 2;
                    }
                    else {

                        $css = round( ( ( $cell->{Val} ) - int( $cell->{Val} ) ) * 86400 );
                        $cmi = int( $css / 60 );
                        $chr = int( $css / 3600 );

                        $css = $css - $cmi * 60;
                        $cmi = $cmi - $chr * 60;

                        $tmpval = date("1900-01-01") + int( $cell->{Val} ) - 2;
                        $tmpval .= " $chr:$cmi:$css";
                    }
                }
                else {
                    $tmpval = Spreadsheet::XLSX::Utility2007::unescape_HTML( $cell->{Val} );
                }

                $cfgrec .= $tmpval;
            }

            if ( ( $iC == 0 ) && ( $iR == ${hdr_seq} ) ) {

                $cfgrec = uc($cfgrec);
                $cfgko  = cnt_ocr( $cfgrec, $keyhdr );
                $cfgkey = "*|" x ( $klm - $cfgko );
            }

            print OUT "|" if ( $iC < $WkS->{MaxCol} );

            print OUT $cfgkey if ( ( $cfgko == $iC + 1 ) && ( $iR >= $hdrcnt ) );
        }

        print OUT "\n";

    }

    print LOG tmstmp() . ": Worsheet conversion completed successfully      -----  " . $WkS->{Name}, "\n";

    close OUT;

    push @csv_file_lst, "$WkS->{Name}.txt";

}

print LOG tmstmp() . ": Conversion completed successfully for $if\n";

1 个答案:

答案 0 :(得分:5)

我的猜测是你的Excel文件包含使用Windows-1252代码页编码的数据,该代码页在没有首先被解码的情况下被重新编码为UTF-8

Excel文件中的此字符串

Unverifiable License Documentation – NB Only

包含EN DASH,在Windows-1252中表示为"\x96"。如果将其再次编码为UTF-8,则结果为两个字节"\xC2\x96"。使用Windows-1252对此进行解释会产生两个字符:LATIN CAPITAL LETTER A WITH CIRCUMFLEX,然后是EN DASH,这就是您所看到的

据我所知,唯一需要做的改变是用Windows-1252解码打开你的文件,就像这样

open my $fh, '<:encoding(Windows-1252)', $excel_file or die $!


更新

您修改后的问题会显示您的Perl代码,但已从您显示的Excel数据中删除了基本信息。这个字符串

Unverifiable License Documentation  NB Only

现在DocumentationNB之间只有两个空格,省略了"0x96" n-dash

注意 - 我已经恢复了原始数据并整理了您的代码。

您打开输入文件的各种尝试都在这里

$sts=open (INP, "< ${if}"       );
#$sts=open (INP, '<:encoding(UTF-8)', ${if}       );
#$sts=open (INP, '<:encoding(ISO-8859-1)', ${if}       );

并且你与ISO-8859-1非常接近,但是Microsft在他们的智慧中重复使用了ISO-8859-1编码在0x7F和0x9F之间的差距来表示Windows-1252中的可打印字符。 0x96处的n-dash字符在此范围内,因此将输入解码为ISO-8859-1将无法正确呈现

据我所知,你只需要写

$sts = open (INP, '<:encoding(Windows-1252)', ${if} );

,您的输入数据将被正确读取

您还应指定输出文件的编码,以避免Wide character in print警告和格式错误的数据。我不知道你是想要复制Excel文件的编码,使用UTF-8还是其他东西,但你应该改变这个

$sts = open( OUT, ">$od/$WkS->{Name}.txt" );

$sts = open OUT, '>:encoding(Windows-1252)', "$od/$WkS->{Name}.txt";

或     $ sts = open OUT,'&gt;:encoding(UTF-8)',“$ od / $ WkS-&gt; {Name} .txt”;

酌情

另请注意,最佳做法是始终使用open的三参数形式,最好使用词汇文件名而不是全局名称。但这不是代码审查,所以我忽略了这些要点

我希望这能为您强调建立输入数据的编码并正确解码是至关重要。猜猜真的不是一个选择


更新

道歉。我忽略了open模块忽略了初始Spreadsheet::XLSX,它传递了文件名,而不是文件句柄

这个模块很尴尬,因为它完全隐藏了所有字符解码,并依赖于[Text::Iconv] [Text :: Iconv]来进行它支持的小转换:Perl自己更好地支持这一点[Encode] [编码]模块

我建议您对open调用的更改是错误的,因为.xlsx文件似乎是压缩文件。但是你从来没有读过INP因此它没有任何区别。您应该在打开它之后立即close INP,因为它是一个浪费的资源

如果不使用其他模块,我建议的最好的事情是你破解Spreadsheet::XLSX->new

返回的数据

此块将纠正错误的重新编码。我已经在foreach $iR(...)`循环

之前添加了它

您需要添加

use Encode qw/ decode :fallbacks /;

到代码顶部

请告诉我你是怎么过的。现在我真的必须去!

{
    my $columns = $WkS->{Cells};

    for my $row ( @$columns ) {

        next unless $row;

        for my $cell ( @$row) {

            next unless $cell and $cell->type eq 'Text';

            for ( $cell->{_Value} ) {

                $_ = decode('UTF-8', $_, FB_CROAK);
                $_ = decode('Windows-1252', $_, FB_CROAK);
            }
        }
    }
}