2

複雑なXMLデータの解析が必要なプロジェクトがあります。私は一緒に行くことにしました、XML::Twigそしてそれはほとんどの部分で本当にうまくいきます。異なる情報が同じタグ名を持っているが、パスが異なるという問題に遭遇しました。DateOfBirthが2つの異なるフィールドに使用される以下のようなもの。

  <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も含まれています。

別のオプションは、タグをdbフィールドにマップする別のフラグをハッシュに設定し、情報が最初に処理されるときにそれを設定することです。

ありがとう

PS:編集が多すぎて申し訳ありません。私は今それを手に入れたと思います。

PPS:コードとxmlには表示できない機密情報があるため、その一部を編集する必要がありました...

4

2 に答える 2

2

XMLが無効(最初は)で<doc:DForm>終わり<doc:MCCPDForm>、PerlコードがXMLデータに対応しないところまで問題を切り詰めたため、正確な状況を理解することは困難です。

しかし、私はあなたがXML::Twig間違って使用していると思います。「小枝」は、主にXMLファイルを、データ内の個々の要素にアクセスするための基礎としてではなく、独立して処理できる一連のレコードに縮小することを目的としています。

<bie1:PdfFile>要素がどのように関連しているかはわからないので、コメントすることはできませんが、と関連する<PersonsDetails>要素を含む単一の要素がないように見えるため、ファイル内の隣接関係によってのみ結合できます。 。<PersonsDetails><SupportingInformation>

その場合は、これら2つの要素のみにハンドラーを配置すると、コードは次のプログラムのようになります。

兄弟のリストの1つとして、特定のコンテキスト内または内部で<DateOfBirth>遭遇するすべての要素の意味を簡単に区別できます。ProcessPersonDetailsProcessSupportingInformation

プログラムは、サンプル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

アップデート

ファイルごとにレコードが1つしかない場合は、XML::TwigXMLデータを段階的に処理する機能は不要であり、ドキュメント全体を一度にロードして処理できます。

このプログラムはまさにそれを実行し、前のコードと同じ出力を生成します。解析プロセス中に呼び出されるハンドラーを作成する必要がないため、コードは大幅に簡潔になります

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 に答える