【发布时间】:2014-12-23 06:21:21
【问题描述】:
是否可以使用 XML::SAX::Expat 的处理程序类暂停和恢复解析?
文件非常大,我们正在将节点读入内存以渲染表格。我们只想一次渲染一个部分,否则内存不足。所以我们需要停止解析文件,在程序的其他部分做一些事情,然后在下一页继续。
我可以想到几种方法来实现这一点(如下),但它们都感觉像是 hack。有什么我可以使用的原生的吗?
可能的选择:
- 跟踪 $current_node 计数器并将其传递给处理程序构造函数,每个 我们在解析文件时会在存储数据之前跳过这么多节点。
- 与上述类似,但使用 tell/seek 跳过每个新调用
- 预处理将文件拆分为多个大小正确的文件。
前两个效率低下,最后一个杂乱无章。有更好的选择吗?
编辑以解释有关文件结构的更多信息以及替代方法不起作用的原因。
除了一些其他数据外,大部分结构如下。
<DETAILS>
<DETAIL>
<ITEM1>...</ITEM1>
<ITEM2>...</ITEM2>
...
</DETAIL>
<DETAIL>
<ITEM1>...</ITEM1>
<ITEM2>...</ITEM2>
...
</DETAIL>
...
</DETAILS>
对于有问题的文件,每个<DETAIL> 节点的大小大约为 240 字节,虽然不多,但我们有超过 180,000 个(这是无法处理的较小文件之一)。 LibXML 在遇到此结构时会失败,因为它会尝试将其全部解析到内存中(我们仅限于 32 位系统,并且 Perl 的内存中还有其他重要的结构)。
更新到最新版本和一些代码调整后,XML::Twig 将解析文档,但我仍然有同样的问题 - 是否可以稍后暂停和恢复?
我不控制整个逻辑流程,所以当主应用程序准备好进入下一页时,它会调用我的对象来获取它。我需要能够输出一大块数据并等待下一个请求。这可能由fork 处理,但我不确定是否需要这样做。
显示程序流程的示例。
这是一种简化(尤其是 while 循环)。实际程序具有复杂的文档页面嵌套结构,其中包含表示页面元素的多个对象。它是通过使用 Web 服务调用来定义的,并且也是数据驱动的,因此我们不能为此硬编码任何假设。
我看不出如何在其中添加回调 - 处理必须在表格之后恢复以发出剩余的页面元素,开始一个新页面,并在恢复表格之前发出该新页面的前几个页面元素。
use strict;
use warnings;
use XML::Twig;
my $table = Table->new('details.xml');
my $table_finished = 0;
while (!$table_finished) {
# emit some data e.g. page header
# ...
# emit the table - 2 data rows per page, for testing
$table_finished = $table->partial_emit(2);
# emit some data e.g. page footer
# ...
}
exit;
package Table;
sub new {
my ($class, $filename) = @_;
my $self = {
'_file' => $filename,
};
bless ($self, $class);
my $sub_ref = $self->can('process_table_row');
$self->{'_twig'} = XML::Twig->new(
twig_handlers => {
'DETAIL' => sub {
$sub_ref->($self, @_),
},
});
return $self;
}
sub partial_emit {
my ($this, $rows) = @_;
$this->{'_rows_emitted'} = 0;
$this->{'_limit'} = $rows;
$this->{'_finished'} = 1;
# we want this to return after parsing part of the file if it is large
$this->{'_twig'}->parsefile($this->{'_file'});
# should be zero if we returned early
return $this->{'_finished'};
}
sub process_table_row {
my ($this, $twig, $elt) = @_;
# increase row count
$this->{'_rows_emitted'}++;
# handle data - doesn't matter what it does here
print $elt->text, "\n";
# we've done as many as we want - how to stop processing and return to main loop?
if ($this->{'_rows_emitted'} >= $this->{'_limit'}) {
print "Limit reached\n";
# Ideally we'd set this, tell Twig to stop for a while, and carry on, but in my test script this causes an infinite loop
#$this->{'_finished'} = 0;
}
}
1;
还有另一个编辑......似乎在调整了我的搜索之后,我偶然发现了我一直想要的东西。 XML::SAX::Expat::Incremental 有一个 parse_more 例程,它完全符合我的需要。我需要等待几天才能测试完整的数据集,但下面的简短测试有效。
Table 类可以这样做:
$self->{'_parser'} = XML::SAX::Expat::Incremental->new( Handler => MyHandler->new($self) );
其中MyHandler 是一个简单的XML::SAX 样式处理程序,现在可以访问Table。
调用Table::partial_emit 将执行以下操作:
my $buf;
my $bytes_to_read = 50; # small for testing
while (read($this->{'_fh'}, $buf, $bytes_to_read)) {
$this->{'_parser'}->parse_more($buf);
# MyHandler will increment this based on the number of rows (DETAIL nodes) encountered
if ($this->{'_rows_emitted'} >= $rows) {
$this->{'_finished'} = 0;
last;
}
}
以上可能在极端情况下存在一些错误,但它适用于我的测试。稍后我需要对其进行适当的压力测试,看看它是否可以投入生产。
【问题讨论】:
-
请澄清“是否可以稍后暂停和恢复?”。
XML::Twig将等待您指定的回调返回。如果 later 意味着几微秒,那么没有问题,但如果可能是几年,那么你有一个不同的设计问题。这同样适用于您的 BIG BROTHER 主应用程序。您没有描述任何阻止您等待一两年再回复IT的事情。 -
@Borodin pause 是不正确的词,我不应该使用它。产量控制会更准确。我们希望 twig 在特定数量的节点后返回,恢复正常处理,然后稍后再次调用拥有 twig 的对象,它应该从中断的地方继续读取。稍后我将尝试添加一个简化的示例。
标签: xml windows perl xml-parsing sax