2

我有一个需要解析复杂 XML 数据的项目。我决定继续使用XML::Twig它,它在大多数情况下都非常有效。我遇到了一个问题,即不同的信息具有相同的标签名称,但路径不同。如下所示,其中DateOfBirth用于两个不同的字段。

  <doc:DForm xmlns:doc="urn:xml-gov-au:...">
    <doc:PersonsDetails>
       <doc:GivenName LanguageIdentifier="" LanguageLocaleIdentifier="">
          John
       </doc:GivenName>
       <doc:Surname LanguageIdentifier="" LanguageLocaleIdentifier="">
          Citizen
       </doc:Surname>
       <doc:DateOfBirth LanguageIdentifier="" LanguageLocaleIdentifier="">
          2012-06-14
       </doc:DateOfBirth>
    </doc:PersonsDetails>
    <doc:SupportingInformation>
       <doc:NumberOfSiblings>
       5.00
       </doc:NumberOfSiblings>
       <doc:SiblingsDetails>
         <doc:DateOfBirth LanguageIdentifier="" LanguageLocaleIdentifier="">
         2009-03-18
         </doc:DateOfBirth>
         <doc:Name LanguageIdentifier="" LanguageLocaleIdentifier="">
         James Citizen</doc:Name>
       </doc:SiblingsDetails>
       <doc:SiblingsDetails>
         <doc:DateOfBirth LanguageIdentifier="" LanguageLocaleIdentifier="">
            2006-08-17
         </doc:DateOfBirth>
         <doc:Name LanguageIdentifier="" LanguageLocaleIdentifier="">
            Jane Citizen
         </doc:Name>
       </doc:SiblingsDetails>
       <doc:Address>
           <doc:Street>25 test street<doc:Street>
           <doc:City>Melbourne <doc:City>
           <doc:PostalCode>3000<doc:PostalCode>
       <doc:Address>
    </doc:SupportingInformation>
    </doc:MCCPDForm>

我已经设置了几个处理程序来处理不同的信息,但是由于我们不需要同级详细信息,因此最终基于将字段映射到 XML 元素的 2 级散列对其进行处理。

样本:

my %field = ( 
       "DetDateOfBirth" => {
    "type"    => "Date",
    "value"   => undef,
    "dbfield" => "DetDateOfBirth",
   },
)

所以,当兄弟的 DOB 被处理时,它会使用上面的哈希元素来设置它,但是当这个人的 DOB 被处理时,因为已经有一个值,它会移动到下一个元素。

所以我设置了另一个处理程序并确保之前处理过信息。

现在,问题是,想象有多种情况,其中相同的名称用于多个元素但在不同的路径中。我只是编写更多的处理程序,还是有另一种更好地管理这种情况的方法。

相关的代码

my $namespace = "doc";
my $formname = "DForm";
enter code here
my $twig = XML::Twig->new(
    pretty_print  => 'indented',
    twig_handlers => {
        "$namespace:${formname}/$namespace:PersonsDetails/$namespace:Address" =>
          \&ProcessAddress,
        "$namespace:${formname}/$namespace:SupportingInformation" =>
          \&ProcessSupportingInformation,
        "bie1:PdfFile"           => \&DecodePDF,
        "$namespace:${formname}" => \&ProcessRecord,
    }
);


sub ProcessRecord {
    my $twg    = shift;
    my $record = shift;
    my $fld;
    my $value;
    my $irn;

    my $elt = $record;

    while ( $elt = $elt->next_elt($record) ) {
        $fld = $elt->tag();

        $fld =~ s/^$namespace\://;


        if ( defined $fields{$fld}{"type"} && $elt->text ) {
            if ( $fld =~ /NameOfPlaceInstitution|HospitalNameOfBirth/i ) {
                next if $elt->text =~ /Other location/i;
            }

            if ( !defined $fields{$fld}{"value"} ) {
                $fields{$fld}{"value"} = $elt->text;
            }

        }
    }
}

sub ProcessSupportingInformation {
    my $twg    = shift;
    my $record = shift;
    my $fld;
    my $value;
    my $parent;

    my $elt = $record;

    while ( $elt = $elt->next_elt($record) ) {
        $fld = $elt->tag();
        $fld =~ s/^$namespace\://;

        $parent = $elt->parent();

        next if ( $fld =~ /PCDATA/ );

        if ( defined $fields{$fld}{"type"} && $elt->text ) {
            if ( $fld =~ /PlaceOfDeathHospital/i ) {
                if ( $elt->text =~ /Other location/i ) {
                    next;
                }
            }

                    if ( $fld =~ /StreetAddress/i ) {
                $fields{"StreetAddressOfPerson"} = $elt->text;
            }
            else {
                if ( !defined $fields{$fld}{"value"} ) {
                    $fields{$fld}{"value"} = $elt->text;
                }
            }
        }
        else {
            $record->delete;
        }
    }

}

仅供参考,实际的 XML 文件大约有 700 行,其中还包括一个编码的 PDF。

另一种选择是在哈希中设置另一个标志,将标签映射到数据库字段,并在第一次处理信息时设置它。

谢谢

PS:抱歉修改太多。我想我现在明白了。

PPS:代码中有一个敏感信息以及我无法显示的 xml,所以我不得不编辑它的一部分......

4

2 回答 2

2

很难理解您的确切情况,因为您已将问题减少到 XML 无效(它以 开头<doc:DForm>但以 结尾<doc:MCCPDForm>)并且 Perl 代码与 XML 数据不对应的程度。

但是我认为您使用XML::Twig错误。“树枝”主要是为了将 XML 文件简化为可以独立处理的一系列记录,而不是作为访问数据中单个元素的基础。

你没有说这些<bie1:PdfFile>元素是如何相关的,<PersonsDetails>所以我不能评论那些,但看起来没有一个元素包含 the<PersonsDetails>和相关的<SupportingInformation>,所以它们只能通过它们在文件中的邻接关系联系在一起.

如果是这种情况,那么我将只在这两个元素上放置一个处理程序,并且代码看起来像下面的程序。

<DateOfBirth>当所有元素在特定上下文中遇到时,很容易区分它们的含义——无论是在同级列表中ProcessPersonDetails还是在ProcessSupportingInformation同级列表中。

该程序仅打印示例 XML 中可用的信息。相反,构建数据库记录并在处理给定人员的最后数据时将其写入不会太难。

purge还要注意从内存中删除已处理信息所必需的调用。没有这个,一次处理数据枝而不是整个文档就没有任何好处

use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new(
    twig_handlers => {
        'doc:PersonsDetails' => \&ProcessPersonsDetails,
        'doc:SupportingInformation' => \&ProcessSupportingInformation
    }
);

$twig->parsefile('DForm.xml');


sub ProcessPersonsDetails {
    my ($twig, $record) = @_;
    print "PersonsDetails\n";
    for (qw/ doc:GivenName doc:Surname doc:DateOfBirth /) {
      print '  ', $record->first_child_trimmed_text($_), "\n";
    }
}

sub ProcessSupportingInformation {
    my ($twig, $record) = @_;
    print "SupportingInformation\n";
    for my $sibling ($record->children('doc:SiblingsDetails')) {
        print "  Sibling\n";
        for (qw/ doc:DateOfBirth doc:Name /) {
          print '    ', $sibling->first_child_trimmed_text($_), "\n";
        }
    }
    $twig->purge;
}

输出

PersonsDetails
  John
  Citizen
  2012-06-14
SupportingInformation
  Sibling
    2009-03-18
    James Citizen
  Sibling
    2006-08-17
    Jane Citizen

更新

如果每个文件只有一条记录,XML::Twig则不需要增量处理 XML 数据的能力,并且可以一次加载并处理整个文档。

这个程序正是这样做的,并产生与前面的代码相同的输出。无需编写在解析过程中调用的处理程序,代码更加简洁

use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new(discard_all_spaces => 1);
my $root = $twig->parsefile('DForm.xml')->root;

print "PersonsDetails\n";
my $details = $root->first_child('doc:PersonsDetails');
for (qw/ GivenName  Surname  DateOfBirth /) {
  my $value = $details->trimmed_field("doc:$_");
  print "  $value\n";
}

print "SupportingInformation\n";
my @siblings = $root->first_child('doc:SupportingInformation')->children;
for my $sib (@siblings) {
  print "  Sibling\n";
  for (qw/ Name  DateOfBirth /) {
    my $value = $sib->trimmed_field("doc:$_");
    print "    $value\n";
  }
}
于 2012-08-06T10:33:20.060 回答
1

在没有看到任何代码的情况下回答您的问题有点困难,但是您是否考虑过在更长的路径上触发处理程序,doc:PersonsDetails/doc:DateOfBirth例如?这将确保仅在正确的上下文中处理日期。

于 2012-08-06T05:52:25.780 回答