Perl:如何将下一个 XML 标签视为前一个标签的子标签?

Perl: How to consider next XML tag as child tag of previous one?

在下面的数据文件中,我想将每个 <Field> 标签视为 <Register> 的子标签,并将每个 <Register> 视为 <Partition> 的子标签。所以,基本上,我试图用相应的 <Register><Field> 提取每个 <Partition> 细节。由于所有这些标签都是独立的,而不是父子关系,我怎样才能得到我想要的输出?

由于文件很大,我不想把它做成父子关系,因为它需要find/replace和人工干预。

<Partition>
    <Name>1</Name>
    <Abstract>2</Abstract>
    <Description>3</Description>
    <ParentName>4</ParentName>

    </Partition>
    <Partition>
    <Name>8</Name>
    <Abstract></Abstract>
    <Description>9</Description>
    <ParentName>10</ParentName>

    </Partition>
    <Register>
    <Name>12</Name>
    <Abstract></Abstract>
    <Description>13</Description>
    <ParentName>14</ParentName>

    <Size>32</Size>
    <AccessMode>15</AccessMode>
    <Type>16</Type>


    </Register>
    <Field>
    <Name>17</Name>
    <Abstract></Abstract>
    <Description></Description>
    <ParentName></ParentName>


    </Field>
    <Field>
    .
    .
    .
    </Field>
    <Register>
    .
    .
    .

    </Register>
    <Field>
    .
    .
    .

    </Field>
    <Field>
    .
    .
    .
    </Field>
    <Partition>
        <Name>88</Name>
        <Abstract></Abstract>
        <Description></Description>
        <ParentName>55</ParentName>

    </Partition>
    <Register>
        .
        .
        .

    </Register>
    <Field>
        .
        .
        .

    </Field>
    <Partition>
        .
        .
        .
    </Partition>
    <Partition>
        .
        .
        .
    </Partition>
    <Partition>
       .
       .
       .
    </Partition>
    <Register>
        .
        .
        .
    </Register>

我正在使用 XML::Twig 包,这是我的代码片段:

foreach my $register ( $twig->get_xpath('//Register') ) # get each <Register>
    {
        #print $register, "\n";
        my $reg_name = $register->first_child('Name')->text;
        my $reg_abstract= $register->first_child('Abstract')->text;
        my $reg_description= $register->first_child('Description')->text;
       .
       .
       .
          foreach my $xml_field ($register->get_xpath('Field'))
          {
            my $reg_field_name= $xml_field->first_child('Name')->text;
            my $reg_field_abstract= $xml_field->first_child('Abstract')->text;
            #print "$reg_field_name \n";
            .
            .
            .

          }
  }

根据你的评论,如果你想用 RegisterField 元素作为 Partition 元素的子元素来重写文件,你可以这样做:

最简单的解决方案,将整个文件加载到内存中:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig;

my $test_file= 'test.xml';

XML::Twig->new( twig_handlers => { 'Register|Field' => \&child,
                                 },
                pretty_print => 'indented',
              )
          ->parsefile( $test_file)
          ->print;

sub child
  { my( $t, $child)= @_;
    $child->move( last_child => $child->prev_sibling( 'Partition'));
  }

由于您提到文件可能非常大,下面是一个稍微复杂的版本,它只在内存中保留 2 Partition 个元素(包括第一个的新子元素)。解析 Partition 时,它使用 flush_up_to 刷新树,直到之前的 Partition:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig;

my $test_file= 'test.xml';

XML::Twig->new( twig_handlers => { 'Partition' => \&parent,
                                   'Register|Field' => \&child,
                                 },
                pretty_print => 'indented',
              )
          ->parsefile( $test_file);

sub child
  { my( $t, $child)= @_;
    $child->move( last_child => $child->prev_sibling( 'Partition'));
  }

sub parent
  { my( $t, $partition)= @_;
    if( my $prev_partition = $partition->prev_sibling( 'Partition'))
      { $t->flush_up_to( $prev_partition); }
  }

请注意,由于使用了 flush_up_to,在解析结束时树的其余部分会自动刷新

如果您需要将 XML 写入特定文件,而不是 STDOUT,您还可以将文件句柄传递给 flush_up_to

顺便说一句,我已经编写了非常基本的代码来将 Field 作为 child 转换为 Register,将 Register 作为 child 转换为 Partition:

use strict;
#use warnings;
use XML::Twig;
use Data::Dumper; 
use Data::Alias;

my $input_xml_file = "gpon.xml";

open (IN_FILE,$input_xml_file);
my @input_file = <IN_FILE>;


for (my $line=0;$line<@input_file;$line++)
{

            if ($input_file[$line] =~ /<\/Partition>/ && $input_file[$line+1] =~ /<Register>/)
            {
                $input_file[$line] = '';

            }
            if ($input_file[$line] =~ /<\/Field>/ && $input_file[$line+1] =~ /<Partition>/)
            {
                $input_file[$line] = "</Field>
</Register>
</Partition>
";

            }
            if ($input_file[$line] =~ /<\/Field>/ && $input_file[$line+1] =~ /<Register>/)
            {
                $input_file[$line] = "</Field>
</Register>
";

            }
            if ($input_file[$line] =~ /<\/Register>/ && $input_file[$line+1] =~ /<Field>/ )
            {
                $input_file[$line] = '';

            }

}
#print OUT_FILE "</Register>";


close(IN_FILE);
open (OUT_FILE,'>gpon_modified.xml');
foreach (@input_file)
{
     print OUT_FILE "$_";
}
print OUT_FILE "</Register>
</Partition>";
close (OUT_FILE);