RPGによるXML処理について、第2部
このシリーズの第1部で、RPG の XML-INTO 命令コードの基本的な使用法について紹介しました。そのヒントでは、PSDS で RPG により提供されたカウントのプロビジョンをどのように使用して、繰り返しエレメントがいくつ処理されたのか判断する方法をお見せしました。
しかし、そのときコメントしましたが、これは繰り返し外部エレメントを処理している場合のみ使用することができます。しかし、それら外部エレメントのそれぞれの中に、繰り返しエレメントがあるとしたらどうでしょうか? このシリーズ第 2 部では、そうした状況の対処方法、また特に V6 機能拡張が初期のサポートのギャップをどのように埋めているかについて調べてゆきます。
以下のサンプル XML をご覧ください。
<Customer Id="S987654">
<Name>Smith and Jones Inc. </Name>
<Address> <City>Jonesboro</City>
<State>GA</State>
</Address>
</Customer>
<Customer Id="B012345">
<Name>Brown and Sons</Name>
<Address Type="Shipping">
<City>San Jose</City>
<State>CA</State>
</Address>
<Address Type="Mailing">
<City>San Francisco</City>
<State>CA</State>
</Address>
</Customer>
おわかりのように、第1部で使用した例と非常によく似ています。私は、それを次のレベルに上げただけです。まず、お客様が複数のアドレスを持てるようにしました。また個別の エレメントは、それぞれ Type 属性をそのエレメントを関連付けさせ、郵送先住所、届け先住所のいずれを表すかを、示すことができるようになりました。第 1 部では属性の処理方法を示しましたが、今回は Type を省略できるようにします。この場合、プログラム・ロジックは、デフォルトで「Mailing」になります。以下のデータ構造に行った変更内容がおわかりだと思います。特に、お客様当たり最大 10 件のアドレスを指定可能にした点に注意してください。
d customer ds Dim(99) Qualified
d id 7a
d name 40a
d address LikeDS(address_T)
d Dim(10)
d address_T ds テンプレート
d type 8a
d state 2a
d city 40a
残りのプログラムを変更しない状態にする場合は、問題なくコンパイルしますが、実行しようとすると以下のようなエラーが表示されます。「The XML document does not match the RPG variable; reason code 2.(XML 文書は RPG 変数と一致しません; 理由コード 2)」このメッセージで F1 キーを押すと、最初のお客様のアドレス部分を処理しているときに問題が発生したことがわかります。
では、なぜ問題が発生したのでしょうか? 覚えているでしょうか。第 1 部で、データ受信に使用する変数内のフィールドの名前と階層は、XML 文書のそれらと一致する必要があると述べました。私が指摘しなかったことは、「一致」と見なされているルールは非常に厳格で、かつデフォルトであるということでした。
- XML のフィールドはすべて供給されていなければならず、かつ
- DS のフィールドはすべて XML 文書内に含まれていなければなりません
つまり、XML 文書と DS のフィールドの、ありとあらゆるものが 1 対 1 に正確に対応しなければならないということです。言い換えれば、XML は DS に「home」のないエレメントを含めることができず、かつ DS のあらゆるフィールドのありとあらゆる発生についてデータを供給する XML エレメントがなければなりません。
Type 属性を覚えていますか? XML を調べれば、最初のお客様について、それを省略したことがわかります。これが RPG がないと見なしているデータです。したがって、考えられる一つのソリューションとしては、 XML を変更して 2 番目のお客様のタイプを組み込むことがあります。これで片が付くでしょう? 残念なことに、あせってやってみた人は気が付いたと思いますが、答えはノーです。RPG は、まだデータが欠けていると考えています。どんなデータでしょうか? XML はデフォルトで「ありとあらゆる発生したデータ」を供給すると言ったことを思い出してください。コンパイラーへの指示では、address エレメントが 10 回発生するとなっていますが、テスト・データではそんなに多くはありません。結果、RPG は残りが「ない」と見なします。
コンパイラー達はこの問題を予期しており、我々がこの問題を処理できる処理オプションを提供しています。このキーワードは allowmissing であり、yes という値を指定すると、RPG はこの状況ではエラーをスローしません。サンプル・プログラム XMLINTOB3 では、XML-INTO ステートメントは以下のようになります。
XML-INTO customer %XML( xmlSource:'case=any allowmissing=yes');
このオプションを強制的に使用しなければならず、V5R4 でこれが唯一のオプションである場合、最後まで到達したか判断するため、アドレス配列に空のフィールドがないかテストする必要があります。同様に、空の type フィールドがないかテストすることもでき、フィールドがあればデフォルト値を設定できます。結果生じる XMLINTOB3 のロジックは以下のようになります。
for i = 1 to count;
Dsply ( 'Id ' + customer(i).Id + ':'
+ %TrimR( customer(i).name ));
a = 1;
//アドレスをループ
DoU ( customer(i).address(a).city = *Blanks );
// Mailing のデフォルト・ブランク・アドレス・タイプ
If ( customer(i).address(a).type ) = *Blanks;
customer(i).address(a).type = 'Mailing';
EndIf;
Dsply ( customer(i).address(a).type + ':'
+ %TrimR(customer(i).address(a).city ) );
a += 1;
EndDo;
EndFor;
では、このソリューションで何が悪いのでしょう? 基本的に、細分性がありません。type 属性といくつか address エレメントが欠けていても大丈夫と言いたいのです。しかし、顧客名が欠けていたら、それはエラーに間違いありません。しかし allowmissing=yes と言った途端に、なんでもありになります。XML 文書は効果的かつ完ぺきに空にでき、RPG は「良いように見える」と言うでしょう。
それで満足できないのは明らかで、より良い方法があるはずです。V6 以降のリリースで、IBM は必要な制御ができる代替方法を提供しました。これを制御するオプションは countprefix であり、フィールドの名前を指定して、コンパイラーはロードした繰り返し項目の数を数えることができます。言い換えれば、RPG が PSDS で提供するレベルに加えて、どのレベルでも配列のカウントを指定できます。
これは修正版 XML-INTO で、プログラム XMLINTOB4 で使用されています。
XML-INTO customer %XML( xmlSource:'case=any
countprefix=count_' );
さて、あとは、データ構造を変更して、必要な場合は count フィールドを組み込むだけです。以下のような結果になります。
d customer ds Dim(99) Qualified
d id 7a
d name 40a
d address LikeDS(address_T) Dim(10)
d count_address...
d 5i 0
d address_T ds テンプレート
d type 8a
d count_type 5i 0
d state 2a
d city 40a
ロードした address エレメントの数を数えたいため、我々が選択したプレフィックスをエレメント名に付けることで、 count フィールドを作成します。したがって、この場合フィールドの名前は count_address となり、数えている項目と同じ階層レベルで指定する必要があります。注意して見てみると、フィールド count_type を address_T DS テンプレートに追加したこともわかったと思います。countprefix サポートは、配列にロードされたエレメントの数をカウントできるだけでなく、オプション・エレメントの有無の確認にも使用できます。そのため、これを使用して、空がないかテストするのではなく、type 属性があるかどうか判断できます。
これら 2 つの新しい count フィールドのおかげで、このように処理ロジックを単純化して、利用できるようになりました。
For i = 1 to count;
Dsply ( 'Id ' + customer(i).Id + ':'
+ %TrimR( customer(i).name ));
//アドレスをループ
For a = 1 to customer(i).count_address; //Mailing のデフォルト・アドレス・タイプ未提供
If ( customer(i).address(a).count_type ) = 0;
customer(i).address(a).type = 'Mailing';
EndIf;
Dsply ( customer(i).address(a).type + ':'
+ %TrimR(customer(i).address(a).city ) );
EndFor;
EndFor;
これは、決して RPG の組み込みサポートによる XML の処理の包括的な概要ではありません。ときどき必要になる可能性がある機能がまだまだあり、今後のヒントでそれらを探ってゆきます。とりあえず、XML-INTO について特有の問題がある場合は、お知らせいただければ、今後のヒントで優先的に取り上げたいと思います。