XML::Twig 根据节点存在更改标签或创建元素

XML::Twig change tag or create element based on node existence

我有一个 XML(示例)文件:test.xml

<root>
   <tag1>AAA</tag1>
   <tag2>BBB</tag2>
   <tag3>
      <tag4>DDD</tag4>
   </tag3>
</root>

我想要达到的结果是,设置两个变量(来自输入):即:

my $xpath = '/root/tag3/tag4';   # or '/root/tag2/tag5' or '/root/tag6'
my $xvalue = 'CCC';              # or 'EEE'

脚本会检查 $xpath 变量,如果它存在于 XML 文件中,那么它会更改它的文本。如果它不存在于 XML 文件中,那么它会创建带有 $xpath 和 $xvalue 的元素。

我使用下面的脚本来设置 $xpath 的文本,但是如何修改它以便它根据 $xpath 的存在做正确的事情?非常感谢,

open( my $output, '>', "$ofile") or die "cannot create $ofile: $!";
XML::Twig->new( twig_roots => { "$xpath" =>
                               sub { my $text= $_->text();
                                     $_->set_text($xvalue);
                                     $_->flush;
                                   },
                             },
            twig_print_outside_roots => $output,
            pretty_print => 'indented',
          )
          ->parsefile( "test.xml" );

这是一个使用递归子程序的相当简单的任务

在下面的程序中,每次调用 add_xpath 都会增加 $node 的值并从 $path 参数

中的 XPath 表达式中删除一个步骤
  • 如果路径以斜杠和标签名称开头,则会检查标签名称以确保它与根元素的名称相匹配。然后将当前节点设置为根元素,子程序递归

  • 如果路径立即以标记名称开始,则调用 has_child 以查看该名称的子项是否已存在。如果不是,则 insert_new_elt 为我们添加一个。当前节点设置为新的或预先存在的子节点并且子例程递归

  • 否则路径应该为空,检查确定。然后调用set_text设置当前节点的文本内容,递归终止

输出显示了您在问题中显示的三个操作中的每一个之后的结果 XML 结构

use strict;
use warnings;

use XML::Twig;
use Carp;

my $twig = XML::Twig->new;
$twig->parsefile('test.xml');
$twig->set_pretty_print('indented');
print $twig->sprint, "\n";

add_xpath($twig->root, '/root/tag3/tag4', 'CCC');
print $twig->sprint, "\n";

add_xpath($twig->root, '/root/tag2/tag5', 'EEE');
print $twig->sprint, "\n";

add_xpath($twig->root, '/root/tag6', 'GGG');
print $twig->sprint, "\n";

sub add_xpath {
    my ($node, $path, $value) = @_;

    if ( $path =~ s|^/(\w+)/?|| ) {
        my $tag = ;
        $node = $node->root;
        carp "Root element has wrong tag name" unless $node->tag eq $tag;
    }
    elsif ( $path =~ s|^(\w+)/?|| ) {
        my $tag = ;
        if ( my $child = $node->has_child($tag) ) {
            $node = $child;
        }
        else {
            $node = $node->insert_new_elt('last_child', $tag);
        }
    }
    else {
        carp qq{Invalid path at "$path"} if $path =~ /\S/;
        $node->set_text($value);
        return 1;
    }

    add_xpath($node, $path, $value);
}

产出

<root>
  <tag1>AAA</tag1>
  <tag2>BBB</tag2>
  <tag3>
    <tag4>DDD</tag4>
  </tag3>
</root>

<root>
  <tag1>AAA</tag1>
  <tag2>BBB</tag2>
  <tag3>
    <tag4>CCC</tag4>
  </tag3>
</root>

<root>
  <tag1>AAA</tag1>
  <tag2>BBB<tag5>EEE</tag5></tag2>
  <tag3>
    <tag4>CCC</tag4>
  </tag3>
</root>

<root>
  <tag1>AAA</tag1>
  <tag2>BBB<tag5>EEE</tag5></tag2>
  <tag3>
    <tag4>CCC</tag4>
  </tag3>
  <tag6>GGG</tag6>
</root>