使用 LibXML 在 Perl 中将 XML 节点替换为字符串

Replace XML node with String in Perl using LibXML

我目前正在使用带有 LibXML 的 perl 脚本来处理给定的 XML 文件。这进行得很好,但如果我有一个节点同时包含子节点和自由文本,我就会开始挣扎。 输入示例为:

<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

预期输出:

<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text HELLOwith a nodeHELLO in between
    </Error>
</Errors>

我试过 replaceChild("HELLO", $testTagNode);用字符串替换节点,然后我可以(如果需要)用简单的搜索替换进一步处理,但我只 运行 进入 "not a blessed reference" 错误。 (我觉得如果它真的那样工作会很脏。)

如果我尝试 运行 像这样直接在父节点上进行简单的搜索替换

$error=~s/\</HELLO/g;

它永远不会触发(无论我是否转义 < 或不转义),因为 LibXML 似乎忽略了我没有特别要求的每个标签;如果我尝试打印出第二个错误,它也会给我

some text with a node in between

对于文件的其余部分,这实际上是一个非常好的功能,但在本例中不是。

不过我可以

$error->removeChild($testTagNode);

这表明它确实被发现了,但对我没有进一步的帮助。理论上我可以删除节点,保存内容,然后将内容插入父节点;问题是它需要在它之前的确切位置。我唯一可能做的就是将整个文件作为字符串读取,让基本搜索替换 运行 在将其输入 LibXML 之前对其进行替换,但这可能会产生相当大的开销这并不是一个很好的解决方案。

我觉得我忽略了一些重要的事情,因为这看起来像是一项非常基本的任务,但我似乎找不到任何东西。也许我只是看错了方向,并且有一种完全不同的方法可用。感谢任何帮助。

XML::XSH2 which is just a wrapper around XML::LibXML 中,以下似乎有效:

for //testTag/text() {
    insert text 'HELLO' prepend . ;
    insert text 'HELLO' append . ;
    move . replace .. ;
}

翻译回 XML::LibXML 留作 reader 的练习。

首先 - 我认为您尝试做的事情不一定特别有用。但是,我会注意到 - 当您处理节点时 - 如果您有第二个示例中的嵌套节点,您实际上会得到 3 'nodes',但其中两个指定为 #PCDATA

所以你可以这样做:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
use Data::Dumper;

my $twig = XML::Twig->new( pretty_print => 'indented_a' )->parse( \*DATA );
foreach my $error ( $twig->get_xpath('//Error') ) {
    my $replace_text;
    foreach my $child ( $error->children ) {
        my $tag = $child->tag;
        print "Child: $tag ", $child->trimmed_text, "\n";
        $tag = '' if $tag eq "#PCDATA";
        $replace_text .= $tag . $child->trimmed_text . $tag;
    }

    $error->set_text($replace_text);
    print $error ->trimmed_text, "\n";
}
print $twig->sprint;

__DATA__
<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

这变成了:

<Errors>
  <Error>this node works fine</Error>
  <Error>some texttestTagwith a nodetestTagin between</Error>
</Errors>

显然,您随后可以将 testTag 重命名为任何您喜欢的名称。

(请耐心等待 - 我将看看如何在 LibXML 中执行此操作 - 不幸的是,它无法轻松安装在我的 Windows 盒子上)。

好的,XML::LibXML:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;


my $xml = XML::LibXML->load_xml( IO => \*DATA );
foreach my $error ( $xml -> findnodes ( '//Error' ) ) {
   my $replace_text; 
   foreach my $child ( $error -> childNodes ) {
      my $tag = $child -> nodeName;
      $tag = '' if $tag eq '#text';
      $replace_text .= $tag . $child -> textContent . $tag; 
      $err -> removeChild($child);
   } 
   $err -> appendTextNode($replace); 
}

print $xml -> toString;

__DATA__
<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

这应该有效

$error='<Errors>
<Error>
    this node works fine
</Error>
<Error>
    some text <testTag>with a node</testTag> in between
</Error>
</Errors>';

$error=~ s/<testTag>/HELLO/gs;
$error=~ s/<\/testTag>/HELLO/gs;

删除 testTag 元素也会删除它的所有子元素,因此在删除 testTag元素。在 XML::LibXML 中,这是按如下方式完成的:(已测试)

for my $node ($doc->findnodes('/Errors/Error//testTag')) {
   my $parent = $node->parentNode();

   for my $child_node (
      XML::LibXML::Text->new("HELLO"),
      $node->childNodes(),
      XML::LibXML::Text->new("HELLO"),
   ) {
      $parent->insertBefore($child_node, $node);
   }

   $node->unbindNode();
}

备注:

  • 处理 testTag 个具有任意数量的文本和元素子元素的元素。
  • 处理 testTag 不是 Error 元素的直接子元素的元素。甚至可以处理嵌套的 testTag 元素。 (如果您只想处理 Error 元素的直接子元素,请使用 /Errors/Error/testTag 而不是 /Errors/Error//testTag。)