多次解析XML文件并将结果合并在一起

问题描述:

我有一个解析2个XML的子例程,一个是原始日志数据,另一个是过滤器。我想从log.xml中删除其中一个筛选器中找不到的所有内容。多次解析XML文件并将结果合并在一起

这是我的日志文件的例子:

<log> 
    <message> 
    <type>warning</type> 
    <from>cody</from> 
    <content>cant use XML::Merge</content> 
    </message> 
    <message> 
    <type>error</type> 
    <from>cody</from> 
    <content>some text here</content> 
    </message> 
    <message> 
    <type>warning</type> 
    <from>charlie</from> 
    <content>ruff</content> 
    </message> 
    <message> 
    <type>error</type> 
    <from>cody</from> 
    <content>an error</content> 
    </message> 
</log> 

与filter.xml看起来像:

<filters> 
    <filter> 
    <type>warning</type> 
    <content>XML::Merge</content> 
    </filter> 
    <filter> 
    <type>error</type> 
    </filter> 
</filters> 

这将导致包含内容的所有警告“XML ::合并“被保留和所有错误。我试图用第一个过滤器进行第一次传递,这导致所有其他消息节点被切断,所以我在得到的XML文件中没有错误。接下来的过滤器会剔除第一个过滤器应该保留的过滤器。这里是我的代码,如果在filter.xml中只有一个过滤器,那么运行良好。

sub include { 
    my $filterParser = XML::LibXML->new->parse_file($filterXML); 
    my $logParser = XML::LibXML->new->parse_file($xml); 

    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) { 
         $msg->parentNode->removeChild($msg); 
        } 
       } 
      } 
     } 
    } 
    } 
    $logParser->toFile($xml); 
} 

我明白为什么它输出与多个过滤器一个空白文档,但需要帮助上获得第一遍的地方保存,然后用原始的XML做出通过用第二过滤器,等等,直到没有剩余的过滤器,然后将所有内容合并成一个XML,而没有重复的消息。

+1

我可能会写你的过滤器的文件转换成执行过滤XSLT文件XSLT文件。 – reinierpost 2015-03-25 11:16:24

+0

@reinierpost是的,我有点希望我走了那条路,但已经沉浸在LibXML中,并不想开始学习别的东西。似乎最好坚持我习惯的方式。 – 2015-03-25 14:38:05

+0

@reinierpost也,是不是增加了一个额外的步骤?将filter.xml转换为XSLT,然后执行过滤器?我认为我提出的解决方案可能不适用于大型log.xml文件(尚未尝试过),但是我认为当节点被标记为保留时,我可以使用更多的标志来加速它以打破循环当然。 – 2015-03-25 14:41:30

我想我很可能将这个问题标题很差,但是希望这个答案和答案有一天能够帮助别人。无论如何,我已经用一些蛮力完成了我的目标......我最终为每个过滤器做了一个pass,并且将我想要保留的节点添加到列表中(我需要一个标志,因为某些过滤器有多个单一标准)。在所有消息的所有过滤器都被处理后,然后循环访问log.xml并查找保存在我的列表中的每个节点。如果log.xml中的节点与列表中的任何节点不匹配,我将它从树中删除。

sub include { 
    my $filterParser = XML::LibXML->new->parse_file($filterXML); 
    my $logParser = XML::LibXML->new->parse_file($xml); 

    my $remove = true; 
    my @nodes; 

    foreach my $msg ($logParser->findnodes('/TdsMainLog/message')) { 
    foreach my $filter ($filterParser->findnodes('/filters/filter')) { 
     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) { 
         #mark for keeping 
         $remove = false; 
        } 
        else { $remove = true; } #else unmark 
       } 
      } 
     } 
     if ($remove eq false) { push (@nodes, $msg); } 
     $remove = true; 
    } 
    } 

    foreach my $msg ($logParser->findnodes('/TdsMainLog/message')) { 
    $remove = true; 
    foreach my $node (@nodes) { 
     if ($msg->isSameNode($node)) { 
      $remove = false; 
     } 
    } 
    if ($remove eq true) { $msg->parentNode->removeChild($msg); } 
    } 
    $logParser->toFile($xml); 
    }