【问题标题】:Can I use Text::CSV_XS to parse a csv-format string without writing it to disk?我可以使用 Text::CSV_XS 解析 csv 格式的字符串而不将其写入磁盘吗?
【发布时间】:2020-07-04 05:35:56
【问题描述】:

我从供应商处获得了一个“csv 文件”(使用他们的 API),但他们所做的只是将整个内容都注入到他们的响应中。这不会是一个大问题,当然,除了一些讨厌的人输入数据并放入“特征”,如换行符。我现在正在做的是为原始数据创建一个文件,然后重新打开它来读取数据:

open RAW, ">", "$rawfile" or die "ERROR: Could not open $rawfile for write: $! \n";
print RAW $response->content;
close RAW;

my $csv = Text::CSV_XS->new({ binary=>1,always_quote=>1,eol=>$/ });
open my $fh, "<", "$rawfile" or die "ERROR: Could not open $rawfile for read: $! \n";

while ( $line = $csv->getline ($fh) ) { ...

不知怎的,这似乎……不雅。看来我应该能够从 $response->content (多行字符串)中读取数据,就好像它是一个文件一样。但我对如何做到这一点完全空白。 一个指针将不胜感激。 谢谢, 保罗

【问题讨论】:

    标签: csv perl filehandle


    【解决方案1】:

    你可以使用字符串文件句柄:

    my $data = $response->content;
    open my $fh, "<", \$data or croak "unable to open string filehandle : $!";
    my $csv = Text::CSV_XS->new({ binary=>1,always_quote=>1,eol=>$/ });
    while ( $line = $csv->getline ($fh) ) { ... }
    

    【讨论】:

    • 这是我在 Perl 中最喜欢的技巧之一,我在 Effective Perl Programming 中写了很多关于它的内容。将许多事物视为文件句柄意味着您拥有一个更简单且熟悉的界面。反过来也一样;您可以写入文件句柄,但将其显示在字符串中。
    • 是的,很好,我也使用它——只是不要忘记它不是一个合适的文件句柄,所以不要遇到麻烦;例如,请参阅this post
    • 好的,谢谢!这正是我一直在寻找但没有得到的。我已经记不清我尝试过哪些组合了,但我显然很接近,但语法不太正确。
    【解决方案2】:

    是的,您可以通过函数接口在字符串上使用Text::CSV_XS

    use warnings;
    use strict;
    use feature 'say';
    
    use Text::CSV_XS qw(csv);  # must use _XS version
    
    my $csv = qq(a,line\nand,another);
    
    my $aoa = csv(in => \$csv) 
        or die Text::CSV->error_diag; 
    
    say "@$_" for @aoa;    
    

    请注意,这确实需要Text::CSV_XS(通常Text::CSV 有效,但不适用于此)。

    我不知道为什么这在 OO 界面中不可用(或者可能是但没有记录)。


    虽然上面直接按要求解析字符串,但也可以通过在获取文件时直接将内容写入文件来减少示例中的“不优雅”方面,大多数库都支持LWP::UserAgent::get 中的:content_file 选项方法。

    我还要注意,大多数时候您希望库对内容进行解码,因此LWP::UA 使用decoded_content(请参阅HTTP::Response)。

    【讨论】:

      【解决方案3】:

      我用Mojo::UserAgent 编写了这个示例。对于 CSV 输入,我使用了来自 NYC Open Data 的各种数据集。这也将出现在Mojo Web Clients 的下一次更新中。

      我在没有立即发出请求的情况下构建请求,这给了我事务对象$tx。然后我可以替换read 事件,这样我就可以立即将这些行发送到Text::CSV_XS

      #!perl
      
      use v5.10;
      use Mojo::UserAgent;
      
      my $ua = Mojo::UserAgent->new;
      
      my $url = ...;
      my $tx = $ua->build_tx( GET => $url );
      
      $tx->res->content->unsubscribe('read')->on(read => sub {
          state $csv = do {
              require Text::CSV_XS;
              Text::CSV_XS->new;
              };
          state $buffer;
          state $reader = do {
              open my $r, '<:encoding(UTF-8)', \$buffer;
              $r;
              };
      
          my ($content, $bytes) = @_;
          $buffer .= $bytes;
          while (my $row = $csv->getline($reader) ) {
              say join ':', $row->@[2,4];
              }
          });
      
      $tx = $ua->start($tx);
      

      这并不像我希望的那样好,因为所有数据仍显示在缓冲区中。这有点吸引人,但在我在 cmets 中注意到的方式中它很脆弱。我现在懒得让它变得更好,因为当你知道什么时候有足够的数据来处理记录时,它会很快变得毛茸茸。我的特定代码并不像您可以随心所欲地做任何事情的想法那么重要,因为交易者会读取数据并将其传递给内容处理程序:

      use v5.10;
      use strict;
      use warnings;
      use feature qw(signatures);
      no warnings qw(experimental::signatures);
      
      use Mojo::UserAgent;
      
      my $ua = Mojo::UserAgent->new;
      
      my $url = ...;
      my $tx = $ua->build_tx( GET => $url );
      
      $tx->res->content
          ->unsubscribe('read')
          ->on( read => process_bytes_factory() );
      
      $tx = $ua->start($tx);
      
      sub process_bytes_factory {
          return sub ( $content, $bytes ) {
              state $csv = do {
                  require Text::CSV_XS;
                  Text::CSV_XS->new( { decode_utf8 => 1 } );
                  };
              state $buffer = '';
              state $line_no = 0;
      
              $buffer .= $bytes;
              # fragile if the entire content does not end in a
              # newline (or whatever the line ending is)
              my $last_line_incomplete = $buffer !~ /\n\z/;
      
              # will not work if the format allows embedded newlines
              my @lines = split /\n/, $buffer;
              $buffer = pop @lines if $last_line_incomplete;
      
              foreach my $line ( @lines ) {
                  my $status = $csv->parse($line);
                  my @row = $csv->fields;
                  say join ':', $line_no++, @row[2,4];
                  }
              };
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-14
        • 2013-05-17
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        相关资源
        最近更新 更多