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

时间:2015-08-31 17:29:15

标签: xml perl perl-module xml-twig

我有一个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" );

1 个答案:

答案 0 :(得分:4)

使用递归子程序

这是一项相当简单的任务

在下面的程序中,每次调用add_xpath都会提升$node的值,并从$path参数中的XPath表达式中删除一步

  • 如果路径以斜杠和标记名称开头,则检查标记名称以确保它与根元素的名称匹配。然后将当前节点设置为根元素,子例程将递归

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

  • 否则路径应为空,并检查以确保路径。然后调用set_text来设置currenty节点的文本内容,并且递归终止

输出显示在您在问题中显示的三个操作中的每一个之后生成的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 = $1;
        $node = $node->root;
        carp "Root element has wrong tag name" unless $node->tag eq $tag;
    }
    elsif ( $path =~ s|^(\w+)/?|| ) {
        my $tag = $1;
        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>