尝试使用 XML::Twig 插入 XML sub-tree 作为新元素时出现问题

Issue when trying to insert an XML sub-tree as new element using XML::Twig

(这是 的 XML 变体)

为我的 object 创建一个 XML 演示文稿,我将 sub-objects 的 XML 的创建推迟到那些。 在初始版本工作后,但创建了一个不必要的深度嵌套结构,我试图将结构稍微扁平化,但是 运行 变成了一个我只能通过一些丑陋的 work-around 解决的问题。

让我们看一下这段代码(在 foreach 循环中执行):

my $child = XML::Twig::Elt->new('sample', { 'name' => $_ });
my $F = $val->{$_}->XML_string($child);
$F->print;
$child = $elt->insert_new_elt(last_child => 'dummy');
$child->replace_with($F);

$F(分配给临时变量用于调试目的)包含 XML sub-tree 用于 object $val->{$_}$val 是当前正在处理的 object 插槽,在本例中它是 sub-objects 的集合(散列)。 所以 $val->{$_} 是要处理的 sub-object)。 sub-tree 添加为额外参数的 children($child,即:sample)。

我想做的是 $elt->insert_new_elt(last_child => $F);,但这从来没有奏效。 所以我在正确的位置插入了一个dummy元素,只是为了之后替换它。

下面是这些行的一些调试输出:

### The parent node where the new 'sample' children should be added
  DB<4> p $elt->print
<perf_data/>
### The new child (it's still too complex...)
  DB<5> p $F->print
<sample name="max"><label>max</label><value>2.48584</value><unit/><warn><range part="end">0.5</range><range part="inverted">0</range><range part="start">0</range></warn><crit><range part="end">1</range><range part="inverted">0</range><range part="start">0</range></crit><min>0</min><max/></sample>
### the dummy child
  DB<6> p $child->print
<dummy/>
### The parent after adding the dummy child
  DB<7> p $elt->print
<perf_data><dummy/></perf_data>
### the parent after having replaced the dummy child with the real one
  DB<8> p $elt->print
<perf_data><sample name="max"><label>max</label><value>2.48584</value><unit/><warn><range part="end">0.5</range><range part="inverted">0</range><range part="start">0</range></warn><crit><range part="end">1</range><range part="inverted">0</range><range part="start">0</range></crit><min>0</min><max/></sample></perf_data>
### more 'sample' elements to follow...

如何避免临时 dummy 元素? 我试图从文档中找到 pout,但失败了。 也许要添加 children in-order,我应该使用 insert_new_elt 以外的东西(也许是 append_new_elt?),但大多数文档都是关于解析现有的 XML, 不构造 XML.

简化替代版本

由于我被要求提供一个简化版本,这里是一个。 然而,它与原始数据结构只有一点点相似。 至少我试图保留问题的本质。

代码如下:

#!/usr/bin/perl
use strict;
use warnings;
use 5.018;

package FOO;

use constant ATTRIBUTES => (
    ['a', 0],
    ['b', 1],
    ['c', 2],
    ['d', 3],
);

sub new($)
{
    my $class = shift;
    my $self = [];

    $#$self = 4;
    bless $self, $class;
    foreach (@$self) {
        $_ = int(rand(10))
    }
    return $self;
}

use XML::Twig;
sub XML_string($;$)
{
    my ($self, $root) = @_;

    $root //= XML::Twig::Elt->new(__PACKAGE__);
    foreach (ATTRIBUTES) {
        my ($name, $i) = @$_[0, 1];

        if (defined(my $val = $self->[$i])) {
            my $elt = $root->insert_new_elt(last_child => $name);

            if ($i == 1 || $i == 2) {
                my $range = $elt->insert_new_elt(last_child => 'range');

                $range->set_att('baz' => $val);
            }
        }       # else leave out
    }
    return $root;
}

package BAR;

use constant ATTRIBUTES => (
    ['e', 0],
    ['f', 1],
    ['g', 2],
    ['h', 3],
);

sub new($)
{
    my $class = shift;
    my $self = [];

    $#$self = 4;
    bless $self, $class;
    foreach (@$self) {
        $_ = int(rand(10))
    }
    return $self;
}

use XML::Twig;
sub XML_string($;$)
{
    my $self = shift;
    my $xml = XML::Twig->new()->set_xml_version('1.0')->set_encoding('utf-8');
    my $b = XML::Twig::Elt->new(__PACKAGE__, {'version' => '1.0'});

    $xml->set_root($b);
    foreach (ATTRIBUTES) {
        my ($name, $i) = @$_[0, 1];

        if (defined(my $val = $self->[$i])) {
            my $elt = $b->insert_new_elt(last_child => $name);

            if ($i == 1) {
                my $e = $elt->insert_new_elt(last_child => 'sample');
                my $F = $val->XML_string($e);

                #print $F->print,"\n";;
                $e = $elt->insert_new_elt(last_child => 'dummy');
                $e->replace_with($F);
            }
        }       # else leave out
    }
    if ($#_ >= 0 && $_[0]) {
        $xml->set_pretty_print('indented');
    }
    return $xml->sprint();
}

package main;
my $f1 = FOO->new();
my $f2 = FOO->new();
my $b = BAR->new();
$b->[1] = $f1;
$b->[2] = $f2;
print $b->XML_string(1), "\n";

示例输出可能如下所示:

  DB<3> x $b
0  BAR=ARRAY(0x1b83860)
   0  1
   1  FOO=ARRAY(0xe2ad40)
      0  2
      1  4
      2  0
      3  4
      4  7
   2  FOO=ARRAY(0x1633788)
      0  3
      1  8
      2  0
      3  1
      4  0
   3  3
   4  1
  DB<4> n
<?xml version="1.0" encoding="utf-8"?>
<BAR version="1.0">
  <e/>
  <f>
    <sample>
      <a/>
      <b>
        <range baz="4"/>
      </b>
      <c>
        <range baz="0"/>
      </c>
      <d/>
    </sample>
  </f>
  <g/>
  <h/>
</BAR>

我希望它与原始问题足够相似。

不要使用新元素的构造函数。 insert_new_elt:

直接使用构造函数参数
my $child = $elt->insert_new_elt(last_child => 'sample', {name => $_});
$val->{$_}->XML_string($child);

或者,使用 paste:

my $child = XML::Twig::Elt->new('sample', {name => $_});
$child->paste(last_child => $elt);