如何使用XML将子元素添加到XMl中的节点:Perl

时间:2015-08-21 05:33:02

标签: xml perl

我正在尝试使用XML将子元素添加到XML中的节点:Twig。 我的XML是

 <Install>
<version >
<number>6.0</number>
<build>1037124</build>
<path>path</path>
<kind>kind</kind>
</version>
<version >
<number>7.0</number>
<build>1037124</build>
<path>path</path>
<kind>kind</kind>
</version>
</Install>

我想要的输出是: -

 <Install>
<version >
<number>6.0</number>
<build>1037124</build>
<path>path/path>
<kind>kind</kind>
<patch>patch</patch>
<patchv>patchv</patchv>  
</version>
<version >
<number>7.0</number>
<build>1037124</build>
<path>path</path>
<kind>kind</kind>
</version>
</Install>

我想将两个新的子节点添加到父版本,其中编号为6.0

我试过这段代码

    my $version = "6.0";    
    my $file_loc = "data.xml";
    my $twig = XML::Twig->new( pretty_print => 'indented_a' )->parsefile( $file_loc );

for my $number ( $twig->findnodes('/Install/version/number') ) {
  $number->parent->last_child->insert(
              patch => {  version =>'patch' },
              patchversion => {  version =>'patchv' },
        ) if $number->trimmed_text eq $version;
}

但我得到的输出不是我想要的。 输出是: -

<Install>
  <version>
    <number>6.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>
      <patch version="patch">
    <patchversion version="patchv">kind</patchversion>
      </patch>
    </kind>
  </version>
     <version >
    <number>7.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
    </version>
    </Install>

我用其他方式尝试了这个也使用粘贴如下: -

    my $version = "6.0";    
    my $file_loc = "data.xml";


        my $myXML = <<"XML";
<patch>
<build></build>
<version></version>
</patch>
XML
my $t = XML::Twig->new(
  twig_handlers => {
    Install => sub { add_version( $myXML, @_ ); }, }
  )->parsefile( $file_loc ) or die $!;

$t->set_pretty_print('indented_c');
open my $xml_fh, '>', "$file_loc" or die $!;
$t->print($xml_fh);
sub add_version {
    my ( $xml, $t, $install ) = @_;
    my $new_elt = XML::Twig::Elt->parse($xml);
    for my $number ( $t->findnodes('/Install/version/number') ) {
    if($number->trimmed_text eq $version )
    {
    $new_elt->paste(  $number->parent->last_child->paste => $install );
    }
    }
}

这里我收到了以下错误

cannot paste an element that belongs to a tree at

我可以使用insert来实现这个,或者我必须使用paste?  在两种情况下如何做到?

1 个答案:

答案 0 :(得分:2)

您第一次尝试时走在正确的轨道上,但您需要使用insert_new_elementinsert替换元素的内容,并使用您选择的语法创建属性。

你可以把它全部放在一个非常简单的twig_handler中。抓取所有<version>元素,检查其<number>的文本节点,然后在修补程序 patchv中添加一个内置文本节点的新元素值。

last_child => ''部分将新元素放在父元素的末尾。它是the $optional_position that is documented with paste

use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig->new(
    pretty_print  => 'indented_a',
    twig_handlers => {
        'version' => sub {
            if ( $_->first_child('number')->text() eq '6.0' ) {
                $_->insert_new_elt( last_child => 'patch' )->set_text('patch');
                $_->insert_new_elt( last_child => 'patchv' )->set_text('patchv');
            }
        }
    }
)->parse( \*DATA );

$twig->print;

<强>输出:

<Install>
  <version>
    <number>6.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
    <patch>patch</patch>
    <patchv>patchv</patchv>
  </version>
  <version>
    <number>7.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
  </version>
</Install>

您的第二次尝试至少有两个原因无效:

# this elem get's pasted (1)
# |               on this positon (2)                            
# |               |                                     in that elem (3)
$new_elt->paste(  $number->parent->last_child->paste => $install );

在(2)中它应该有一个位置,这是一个字符串。这类似于first_childlast_child等等。 They are listed in the docs of paste

因此它会计算$number->parent->last_child->paste的返回值并将其用作位置字符串。但是没有这样的返回值,所以这不起作用。

但它没有那么远。错误消息无法粘贴属于树的元素来自(2)中的paste,因为您在paste()上调用$number->parent->last_child并且已经在树上了。这是不允许的。如果删除代码编译的那部分。

if ( $number->trimmed_text eq $version ) {
  $new_elt->paste( $install );
}

输出是(剪断):

<Install>
  <patch>
    <build></build>
    <version></version>
  </patch>
  <version>
    <number>6.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
  </version>

这仍然不是我们想要的,因为<patch>最终在<Install>内,而不在<version>内。但是我们已经从循环中获得了$number。那是<number>节点内的<version>节点,所以我们可以得到它的父节点并在我们的(3)中使用它。

$new_elt->paste( $number->parent );

现在我们明白了:

<Install>
  <version>
    <patch>
      <build></build>
      <version></version>
    </patch>
    <number>6.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
  </version>

那更好。它是在正确的地方。如果您添加位置last_child,它最终会结束。

$new_elt->paste( last_child => $number->parent );

现在就在这里:

<Install>
  <version>
    <number>6.0</number>
    <build>1037124</build>
    <path>path</path>
    <kind>kind</kind>
    <patch>
      <build></build>
      <version></version>
    </patch>
  </version>

现在它只是我们正在粘贴的错误内容,因为我们想要两个元素<patch><patchv>,两者都带有文本。那是因为你有$myXML中的那些。

你不能有两个顶级元素。这是无效的XML语法。

my $myXML = <<"XML";
<patch>patch</patch>
<patchv>patchv</patchv>
XML

在第2行,第0列,第21行的文档元素之后的垃圾将会失败... 。所以你必须有两个变量并调用paste两次,或者包装它们并获取新根元素的子节点。

但当然这很复杂。上面的第一种方法更清洁。