【问题标题】:XML::Simple returns "Out of memory" error for large XMLsXML::Simple 为大型 XML 返回“内存不足”错误
【发布时间】:2018-06-04 09:02:36
【问题描述】:

这可能需要一些时间来解释,但我有一个文件 (XMLList.txt),其中包含多个 IDOC XML 的路径。 XMLList.txt 的内容如下所示:

/usr/local/sterlingcommerce/data/archive/SFGprdr/SFTPGET/2017/Dec/week_4/AU_DHL_PW_Inbound_Delivery_from_Pfizer_20171220071754.xml /usr/local/sterlingcommerce/data/archive/SFGprdr/SFTPGET/2017/Dec/week_4/AU_DHL_PW_Inbound_Delivery_from_Pfizer_20171220083310.xml /usr/local/sterlingcommerce/data/archive/SFGprdr/SFTPGET/2017/Dec/week_4/CCMastOut_MQ_GLB_1_20171220154826.xml

我正在尝试创建一个 Perl 脚本,该脚本读取每个 XML 并将每个 XML 文件中的标签 DOCNUM、SNDPRN 和 RCVPRN 的值解析为管道分隔文件“report.csv”

另外需要注意的是,我的 XML 文件可能是: 全部在一行 - 示例

 <?xml version="1.0" encoding="UTF-8"?><ZDELVRY073PL><IDOC BEGIN="1">
    <EDI_DC40 SEGMENT="1"><TABNAM>EDI_DC40</TABNAM><MANDT>400</MANDT>
    <DOCNUM>0000000443474886</DOCNUM><DOCREL>731</DOCREL><STATUS>30</STATUS>
    <DIRECT>1</DIRECT><OUTMOD>4</OUTMOD><IDOCTYP>DELVRY07</IDOCTYP>
    <CIMTYP>ZDELVRY073PL</CIMTYP><MESTYP>ZIBDADV</MESTYP><MESCOD>IBG</MESCOD>
    <SNDPOR>SAPQ01</SNDPOR><SNDPRT>LS</SNDPRT><SNDPRN>Q01CLNT400</SNDPRN>
    <RCVPOR>XMLDIST_MT</RCVPOR><RCVPRT>LS</RCVPRT><RCVPFC>LS</RCVPFC>
    <RCVPRN>AU_DHL</RCVPRN>.... </EDI_DC40></IDOC>

或多行 XML:

  <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <INVOIC02>
      <IDOC>
        <EDI_DC40>
      <TABNAM/>
      <DOCNUM>0000000658056255</DOCNUM>
      <DIRECT/>
      <IDOCTYP>INVOIC02</IDOCTYP>
      <MESTYP>INVOIC</MESTYP>
      <SNDPOR>SAPP01</SNDPOR>
      <SNDPRT/>
      <SNDPRN>ALE400</SNDPRN>
      <RCVPOR>XMLINVOICE</RCVPOR>
      <RCVPRT>KU</RCVPRT>
      <RCVPRN>C18BASWARE</RCVPRN>
      <CREDAT>20171220</CREDAT>
      <CRETIM>222323</CRETIM>
    </EDI_DC40>

到目前为止,我使用的脚本似乎适用于小型 XML。但是,某些 > 50 MB 的 XML 会引发此错误:

内存不足!记不清!回调调用退出 /usr/opt/perl5/lib/site_perl/5.10.1/XML/SAX/Base.pm 第 1941 行 (#1) (F) 通过 call_sv() 从外部包调用的子程序 调用exit退出。

内存不足!

所以,这是我正在使用的代码。希望您能帮忙调整一下:

#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
# use module
use XML::Simple;
use Data::Dumper;

# create object
my $xml = new XML::Simple; 

my $file_list = 'XMLList.txt';
open(my $fh_i, '<:encoding(UTF-8)', $file_list)
  or die "Could not open file '$file_list' $!";

my $csv_out = 'report.csv';
open(my $fh_o, '>', $csv_out)
  or die "Could not open file '$csv_out' $!"; 

while (my $row = <$fh_i>) {
  $row =~ s/\R//g;
  my $data = $xml->XMLin($row);
  print $fh_o "$data->{IDOC}->{EDI_DC40}->{DOCNUM}|";
  print $fh_o "$data->{IDOC}->{EDI_DC40}->{SNDPRN}|";
  print $fh_o "$data->{IDOC}->{EDI_DC40}->{RCVPRN}\n";
}

close $fh_o;

【问题讨论】:

  • 如果我需要处理无法放入内存的文档,我会使用 XML::LibXML::Reader(及其 copyCurrentNode(1))或 XML::Twig(使用 @987654325 @)。
  • 当你在同一个对象上多次调用XMLin 时,我不知道XML::Simple 会做什么。但是,在调用XMLin 之前将my $xml = XML::Simple-&gt;new 内部 移动到while 循环中可能会有所收获。

标签: perl xml-parsing xml-simple


【解决方案1】:

我建议人们在遇到问题时停止使用XML::Simple。该模块很好上手,但它并不意味着是一个长期的解决方案。即便如此,请参阅Why is XML::Simple “Discouraged”?

XML::Twig 是我经常用于这些任务的。您可以为标签设置处理程序并获取树的那部分。你处理它并继续前进。这可能就像这样简单,我设置一个子例程来处理我遇到的每个EDI_DC40

use Text::CSV_XS;
use XML::Twig;

my $csv = Text::CSV_XS->new;

my $twig = XML::Twig->new(   
    twig_handlers => { 
        'EDI_DC40' => \&process_EDI_DC40,
        },
    );

$twig->parsefile( $ARGV[0] );

sub process_EDI_DC40 {
    my( $twig, $thingy ) = @_;

    my @values = map { $thingy->first_child( $_ )->text } 
        qw(DOCNUM RCVPRN SNDPRN);

    $csv->say( *STDOUT, \@values );
    }

【讨论】:

    【解决方案2】:

    首先,如果文件包含换行符,

      while (my $row = <$fh_i>){
      $row =~ s/\R//g;
      my $data = $xml->XMLin($row);
    

    将从文件中一次读取一行并尝试仅对该行而不是整个文档进行 XML 转换。我建议您将每个文件放入缓冲区并使用正则表达式在 XMLin 转换之前消除换行符和回车符。此外,如果文件中存在任何 XML 错误,XMLin 会毫不客气地,因此您希望在 eval 块中运行它。

    【讨论】:

      猜你喜欢
      • 2014-05-10
      • 1970-01-01
      • 2011-04-13
      • 2014-06-12
      • 2018-05-11
      • 2019-08-23
      • 1970-01-01
      • 2015-06-18
      • 2011-08-09
      相关资源
      最近更新 更多