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

在Perl中使用LibXML将XML节点替换为字符串,xml,perl,xml-libxml,Xml,Perl,Xml Libxml,我目前正在使用一个带有LibXML的perl脚本来处理给定的XML文件。这进展得不错,但如果我有一个同时包含子节点和自由文本的节点,我就会开始挣扎。 输入的一个例子是: <Errors> <Error> this node works fine </Error> <Error> some text <testTag>with a node</testTag> in be

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

<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>
对于文件的其余部分,这实际上是一个非常好的功能,但在本例中不是

但我能做到

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

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

在这封信中,以下内容似乎起到了作用:

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

将其翻译回XML::LibXML是留给读者的一个练习。

首先,我不认为您要做的一定特别有用。但是,我要注意的是,在处理节点时,如果像第二个示例中那样有一个嵌套节点,那么实际上会得到3个“节点”,但其中两个指定为
#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>
这应该行得通

$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;
$error=
这个节点工作正常
某些文本之间有一个节点
';
$error=~s//HELLO/gs;
$error=~s//HELLO/gs;

删除
testTag
元素也会删除它的所有子元素,因此在删除
testTag
元素之前,我们必须将每个
testTag
元素的子元素移动到
testTag
元素的父元素中。在XML::LibXML中,这是按如下方式完成的:(已测试)


注:

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

为什么要首先尝试将XML元素转换为纯文本?这感觉像是一个XY问题。我不确定假定
testTag
的childnres只会是文本节点是可以接受的。感谢您提供了不同的方法,但我会选择那些不需要比我已经运行的包更多的包的。嗯,那么有效地创建一个新的
#text
元素来包装这个子节点?比我的方法更简洁。@Sobrique,不。我没有用文本节点包装孩子们。这甚至没有任何意义,因为文本节点不能包含其他节点。OK。我还得再盯着它看一会儿,才能弄清楚到底发生了什么。@Sobrique,删除
testTag
元素也会删除它的所有子元素,所以我们必须先将子元素移出。在数组术语中,我们正在进行
拼接(@$parent,$idx_of_node,1,“HELLO”,@$node,“HELLO”)
。代码将
testTag
元素的子元素移动到
testTag
元素的父元素,将它们定位在
testTag
元素的前面。除了子节点之外,还将在那里创建两个请求的文本节点。最后,现在空的
testTag
被删除了。LibXML::Text和insertBefore的组合正是我想要的,它现在就像一个魔咒。漂亮的小代码。我不确定假定
testTag
的子节点只是文本节点是可以接受的。LibXML解决方案确实有效,但代码中有3点需要更正:$err(两次)必须是$error,在最后一行,$replace必须是$replace\u text。(把这个留给未来的旁观者。)除此之外,它还可以工作。我会接受@ikegami的回答,因为他可以处理嵌套标签。目前,您对testTag节点中只有文本的假设是正确的,但这可能会改变,并且未来安全始终是一件好事。非常感谢您的帮助。是的,转录错误-LibXML不能很好地安装在我的Windows机器上。
for //testTag/text() {
    insert text 'HELLO' prepend . ;
    insert text 'HELLO' append . ;
    move . replace .. ;
}
#!/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>
#!/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;
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();
}