在 Perl 中使用 LibXML 根据另一个 XML 过滤一个 XML

Using LibXML to filter one XML based on another XML in Perl

我在想出一个算法时遇到了一些困难,该算法将读取我的 filter.xml,然后从我的 log.xml 中删除所有消息节点。我试图从一个过滤器的小案例开始,而不是一次拍摄多个过滤器,但想法是一条消息必须匹配要删除的过滤器中的所有元素。

这是一个 filter.xml 的示例,它会删除所有包含字符串 "hurts really bad":

的警告消息
<filter>
  <type>warning</type>
  <content>hurts really bad</content>
</filter>

这里是 log.xml 的示例,其中包含 2 条不同的警告消息:

<log>
  <message>
    <type>warning</type>
    <from>cody</from>
    <content>my head hurts really bad right now</content>
  </message>
  <message>
    <type>error</type>
    <from>cody</from>
    <content>i've got too many nested for loops</content>
  </message>
  <message>
    <type>warning</type>
    <from>charlie</from>
    <content>ruff</content>
  </message>
</log>

我在使用 LibXML 加载文件时没有遇到任何问题,但是当我尝试想出开始将过滤器与消息进行比较的算法时,我只是在 for 循环中使用 for 循环,直到我意识到我的意思我正在尝试将不起作用,通常是由于在检查内容字符串之前删除了所有警告。任何人对如何实现这一点有任何伪代码的想法?我希望输出从原始 log.xml 中删除第一条警告消息,因为它既匹配类型又包含 filter.xml 中的内容字符串。这意味着只有错误和第二条警告消息将是新 log.xml 中仅有的 2 项。我知道如何覆盖 xml 以及使用索引比较字符串。只需要把这个算法搞定就行了

我建议您从您的过滤器 XML 构建一个数组散列,该数组将类型作为键,并将该类型的所有内容的数组作为值。像这样

my %filters = (
  warning => [
    'hurts really bad',
    'content 2',
    'content 3',
    ...
  ],
);

然后你就完成了 filter.xml 数据文件。

现在,当您遇到 log.xml 文件中的每个 <message> 元素时,您应该

  • 检查消息类型是否有%filters元素

  • 如果没有则测试失败,即消息未被过滤

  • 如果是,则检查元素值引用的数组中的每个字符串,看它是否是当前消息内容的子字符串

  • 如果没有找到这样的字符串,则测试失败,即消息未被过滤

  • 否则测试成功,消息被过滤掉

假设您正在使用正则表达式来检查过滤器是否与内容匹配,请不要忘记转义(使用 quotemeta)任何正则表达式在 content 字符串。此外,您可能希望将每个散列值转换为已编译的正则表达式以使检查更简单。也就是把上面的结构改成

my %filters = (
  warning => qr/hurts really bad|content 2|content 3|.../,
)

看来我终于解决了。此子将删除 filter.xml

中找到的所有项目
sub exclude {
  my $filterParser = XML::LibXML->new->parse_file($filterXML);
  my $logParser = XML::LibXML->new->parse_file($xml);

  my $remove = false;   

  foreach my $filter ( $filterParser->findnodes('/filters/filter') ) {
    foreach my $msg ( $logParser->findnodes('/log/message') ) {
        foreach my $msgNode ($msg->childNodes) {
            foreach my $filterNode ($filter->childNodes) {
                if ($msgNode->localName eq $filterNode->localName) {
                    my $m = $msgNode->textContent;
                    my $f = $filterNode->textContent;
                    if (index($m, $f) != -1) {
                        $remove = true;
                    }
                    else { $remove = false; }
                }
            }
        }
        if ($remove eq true) {
            $msg->parentNode->removeChild($msg);
            $remove = false;
        }
    }
  }
  $logParser->toFile($xml);
}

如果您按照我的建议将过滤器数据读入内存,那就更简单了。然后你所要做的就是查看日志中的每个 <message> 元素,如果它符合任何条件,则将其删除。

这是它的样子

use strict;
use warnings;

use XML::LibXML;
use List::Util 'any';

my $parser = XML::LibXML->new(no_blanks => 1);

my $filters = $parser->load_xml(location => 'filter.xml');

my %filters;

for my $filter ( $filters ->findnodes('/filters/filter') ) {
  my $type = $filter->findvalue('type');
  my $content = $filter->findvalue('content');
  push @{ $filters{$type} }, $content;
}

my $log = $parser->load_xml(location => 'log.xml');

for my $message ( $log->findnodes('/log/message') ) {
  my $type = $message->findvalue('type');
  my $content = $message->findvalue('content');
  unless ( any { $content =~ /\Q$_/i } @{ $filters{$type} } ) {
    $message->parentNode->removeChild($message);
  }
}

print $log->toString(1);

输出

<?xml version="1.0"?>
<log>
  <message>
    <type>warning</type>
    <from>cody</from>
    <content>my head hurts really bad right now</content>
  </message>
</log>