XML :: Twig - 管理具有相同标记的字段

时间:2012-08-06 05:44:00

标签: perl xml-twig

我有一个需要解析复杂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;
        }
    }

}

仅仅是一个FYI,实际的XML文件大约有700行,其中也包含编码的PDF。

另一种选择是在散列中设置另一个标记,将标记映射到db字段,并在第一次处理信息时设置它。

由于

PS:很抱歉编辑太多了。我想我现在就明白了。

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

2 个答案:

答案 0 :(得分:2)

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

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

您没有说明<bie1:PdfFile>元素与<PersonsDetails>的关系如何,所以我无法评论这些元素,但看起来没有单个元素包含{{1}和相关的<PersonsDetails>,所以它们只能通过它们在文件中的邻接来捆绑在一起。

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

很容易区分所有<SupportingInformation>元素在特定上下文中遇到的含义 - 在<DateOfBirth>内或在ProcessPersonDetails内作为兄弟列表之一。< / p>

程序只打印示例XML中可用的信息。建立数据库记录并在处理给定人员的最后数据结束时编写数据记录并不会太难。

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

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数据的能力是不必要的,整个文档可以立即加载并处理。

这个程序完全正确,并产生与前一代码相同的输出。无需编写在解析过程中调用的处理程序,代码就更加简洁了

XML::Twig

答案 1 :(得分:1)

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