RPG で XML 文書を処理する (SAX編 その 1)

V5R4 から、RPG で XML 文書を取り扱う命令がいくつか新規に追加されています。

XML 文書を要素の繰り返しをそのままデータ構造の繰り返しとして読み込むための XML-INTO 命令と、SAX (Simple API for XML) パーサを呼び出して要素の変化に応じて XML 文書を処理するための XML-SAX 命令との二つがあります。

XML-INTO 命令については、このサイトでも「RPG で XML 文書を処理する (その 1)」「RPG で XML 文書を処理する (その 2)」「RPG で XML 文書を処理する (XML 要素の繰り返し件数が事前に決められていない場合)」で使い方を見てきました。

今回は XML-SAX 命令についてです。
"MC Press Online - RPG Has SAX Appeal!"にコーディング例が載っていましたので、それを参考にして使い方の概略を見ていきたいと思います。


制約事項

まず、XML-SAX 命令で使用する SAX パーサについてはいくつか制約があります。

https://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=//rzasc/sc092507227.htm

https://publib.boulder.ibm.com/infocenter/iseries/v7r1m0/topic/rzasc/sc092507217.htm

"MC Press Online - RPG Has SAX Appeal!"に載っていたコードも、日本語環境ではそのままでは動きません。SAX パーサの上記(↑) 制約によるものです。
CHGJOB CCSID(37) とコマンドを実行し、ジョブの CCSID を変更してからだと、ちゃんと動くのですが…

日本語の、たとえば 5035 や 1399 などの CCSID 属性を持ったジョブで実行させる場合は、「ジョブの CCSID が、パーサーが処理しない CCSID のいずれかである場合、文書を UCS-2 で構文解析する必要があります」があてはまります。つまり、XML 文書は UCS-2 で統一し、プログラム内でも読み込み対象の XML 文書は UCS-2 である、と指定することで対応ができます。

XML 文書の CCSID がサポートされているものではなかった場合、以下のようなエラーが出ます。

ちなみにエラーコードは↓に載っています。

http://publib.boulder.ibm.com/infocenter/iadthelp/v7r0/index.jsp?topic=/com.ibm.etools.iseries.pgmgd.doc/c0925076168.htm

XML-SAX を使う場合は、プログラムを呼び出す前に解析対象の XML 文書が UCS-2 で書かれているかどうかの事前チェックが必要になりますね。

今回は話をカンタンにするために、IFS 上で CCSID を指定した変換を行って CCSID 13488 (UCS2) に変換しておいてあります。

処理の概要

このサンプルプログラム、メインルーチンは以下のように XML-SAX 命令ただひとつです。

      /Free

       XML-SAX %Handler(MyHandler: myCommArea )
          %XML( XMLFile : 'doc=file  ccsid=ucs2');

       *InLR = *On;

      /End-free

実際には %Handler で指定されている MyHandler プロシージャの方にロジックがあります。

MyHandler にプロトタイプ定義は↓のようになっています。

     D myHandler       Pr            10I 0
     D   commArea                           Like(myCommArea)
     D   event                       10I 0  VALUE
     D   pstring                       *    VALUE
     D   stringLen                   20I 0  VALUE
     D   exceptionId                 10I 0  VALUE

プロシージャはイベント情報と共に起動されます。
そのイベントによって必要な処理を判断し、必要な場合はそのイベントと共に渡されるポインタをたどってデータを取得する、といった流れになります。

       if eventIndex > 0;

          if event = *XML_EXCEPTION;
             data = '*** Exception offset is ' + %char(stringLen);
             returnCode = -1;     // Unable to continue so terminate parse

          elseif stringLen > 0;
                stringLen = ( stringLen / 2 );
                data = rmvWhiteSpace(%subst(%char(string) : 1 : stringLen));

          else;
             data = '** No Data **';
          endif;

       else;                 // Unknown event

          eventName = '** Unknown event code ** ';
          data = '';

       endif;

rmvWhiteSpace というプロシージャについては、名前から内容の想像はつくとは思いますが、インフォメーションセンターにサンプルコーディングが載っているものをそのまま使用しているようです。

http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=/books_web/c0925086782.htm

修正部分

プログラムは以下の部分を修正してあります。

受け取ったポインタをベースに読み込む文字は UCS2 を想定しているので、文字数や文字タイプがそれに応じたものになります。

     D string          S          16383c    Based(pString)

文字数の計算においても同様に UCS2 であることを考慮したものになります。

                stringLen = ( stringLen / 2 );

その他、いろんなテストに便利なので解析対象の XML ファイル名と CCSID については引数として渡すように変更しています。

     DPRTXMLSAXL       Pr
     D   xmlFile                     30A   const options(*varsize)
     D   ccsid                        5A

↓のように実行するようにしたわけですね。

プログラム

こちらが修正済みで、日本語環境 (CCSID 5035/1399) で稼動を確認したプログラムのソース全文になります。

     h Option(*SrcStmt : *NoDebugIO) DftActGrp(*No)
     h Debug(*xmlsax)

     FQPRINT    O    F  132        PRINTER

     DPRTXMLSAXL       Pr
     D   xmlFile                     30A   const options(*varsize)
     D   ccsid                        5A
     DPRTXMLSAXL       Pi
     D   xmlFile                     30A   const options(*varsize)
     D   ccsid                        5A

     D myHandler       Pr            10I 0
     D   commArea                           Like(myCommArea)
     D   event                       10I 0  VALUE
     D   pstring                       *    VALUE
     D   stringLen                   20I 0  VALUE
     D   exceptionId                 10I 0  VALUE

     D rmvWhiteSpace   pr         65535a   varying
     D   input                    65535a   varying const

     d i               s             10i 0

     d myCommArea      s              1a

     d eventCodeData   DS
     d                               10i 0 Inz(*XML_START_DOCUMENT)
     d                               10i 0 Inz(*XML_VERSION_INFO)
     d                               10i 0 Inz(*XML_ENCODING_DECL)
     d                               10i 0 Inz(*XML_STANDALONE_DECL)
     d                               10i 0 Inz(*XML_DOCTYPE_DECL)
     d                               10i 0 Inz(*XML_START_ELEMENT)
     d                               10i 0 Inz(*XML_CHARS)
     d                               10i 0 Inz(*XML_PREDEF_REF)
     d                               10i 0 Inz(*XML_UCS2_REF)
     d                               10i 0 Inz(*XML_UNKNOWN_REF)
     d                               10i 0 Inz(*XML_END_ELEMENT)
     d                               10i 0 Inz(*XML_ATTR_NAME)
     d                               10i 0 Inz(*XML_ATTR_CHARS)
     d                               10i 0 Inz(*XML_ATTR_PREDEF_REF)
     d                               10i 0 Inz(*XML_ATTR_UCS2_REF)
     d                               10i 0 Inz(*XML_UNKNOWN_ATTR_REF)
     d                               10i 0 Inz(*XML_END_ATTR)
     d                               10i 0 Inz(*XML_PI_TARGET)
     d                               10i 0 Inz(*XML_PI_DATA)
     d                               10i 0 Inz(*XML_START_CDATA)
     d                               10i 0 Inz(*XML_CHARS)
     d                               10i 0 Inz(*XML_END_CDATA)
     d                               10i 0 Inz(*XML_COMMENT)
     d                               10i 0 Inz(*XML_EXCEPTION)
     d                               10i 0 Inz(*XML_END_DOCUMENT)

     d eventCodes                    10i 0 Dim(25) Overlay(eventCodeData)

     d eventNameData   DS
     d                               21a   Inz('*XML_START_DOCUMENT')
     d                               21a   Inz('*XML_VERSION_INFO')
     d                               21a   Inz('*XML_ENCODING_DECL')
     d                               21a   Inz('*XML_STANDALONE_DECL')
     d                               21a   Inz('*XML_DOCTYPE_DECL')
     d                               21a   Inz('*XML_START_ELEMENT')
     d                               21a   Inz('*XML_CHARS')
     d                               21a   Inz('*XML_PREDEF_REF')
     d                               21a   Inz('*XML_UCS2_REF')
     d                               21a   Inz('*XML_UNKNOWN_REF')
     d                               21a   Inz('*XML_END_ELEMENT')
     d                               21a   Inz('*XML_ATTR_NAME')
     d                               21a   Inz('*XML_ATTR_CHARS')
     d                               21a   Inz('*XML_ATTR_PREDEF_REF')
     d                               21a   Inz('*XML_ATTR_UCS2_REF')
     d                               21a   Inz('*XML_UNKNOWN_ATTR_REF')
     d                               21a   Inz('*XML_END_ATTR')
     d                               21a   Inz('*XML_PI_TARGET')
     d                               21a   Inz('*XML_PI_DATA')
     d                               21a   Inz('*XML_START_CDATA')
     d                               21a   Inz('*XML_CHARS')
     d                               21a   Inz('*XML_END_CDATA')
     d                               21a   Inz('*XML_COMMENT')
     d                               21a   Inz('*XML_EXCEPTION')
     d                               21a   Inz('*XML_END_DOCUMENT')

     d eventNames                    21a   Dim(25) Overlay(eventNameData)

      // Print field definitions
     d data                          90a
     d dataLength                     5p 0
     d eventCode                      5p 0
     d eventName                     21a

     d option          s             25a

      /Free

       if ccsid = *blanks;
          ccsid = 'ucs2' ;
       endif;

       option = %trimR('doc=file  ccsid=' + ccsid) ;
       XML-SAX %Handler(MyHandler: myCommArea )
          %XML( XMLFile : option );
        //%XML( XMLFile : 'doc=file  ccsid=ucs2');

       *InLR = *On;

      /End-free

     OQPRINT    D
     O          E            PrintEvent
     O                       eventCode     J     10
     O                       eventName           35
     O                       dataLength    J     40
     O                       data               132

       // SAX handler myHandler
     P myHandler       B
     D                 PI            10I 0
     D   commArea                           Like(myCommArea)
     D   event                       10I 0  VALUE
     D   pString                       *    VALUE
     D   stringLen                   20I 0  VALUE
     D   exceptionId                 10I 0  VALUE

     D string          S          16383c    Based(pString)
     D returnCode      S             10I 0 INZ(0)
     D eventIndex      s              5i 0

      /free

       eventCode = event;         // Set up event code
       dataLength = stringLen;    //    and string length for printing

       eventIndex = %lookup(event : eventCodes );   // identify event
       if eventIndex > 0;
          eventName = eventNames( eventIndex );

          if event = *XML_EXCEPTION;
             // When an exception occurs, length identifies error position
             data = '*** Exception offset is ' + %char(stringLen);
             returnCode = -1;     // Unable to continue so terminate parse

          elseif stringLen > 0;
                stringLen = ( stringLen / 2 );
                data = rmvWhiteSpace(%subst(%char(string) : 1 : stringLen));

          else;
             data = '** No Data **';
          endif;
       else;                 // Unknown event
          eventName = '** Unknown event code ** ';
          data = '';
       endif;

       // Surround data with quotes before printing - a field containing
       //   nothing but blanks will show as ""

       data = '"' + %TrimR(data) + '"';

       // dsply  ('Event: ' + eventName );
       // dsply  ('Data: ' + %subst(data:1:40) );
       Except PrintEvent;

       return returnCode;

      /end-free
     P                 E

      // Following procedure courtesy of Barbara Morris
      //  - Returns a string that is the same as the input string
      //    except strings of whitespace are converted to a single blank.

     P rmvWhiteSpace   b
     D                 pi         65535a   varying
     D   input                    65535a   varying const

     D output          s                   like(input) inz('')

      * x'15'=newline  x'05'=tab     x'0D'=carriage-return
      * x'25'=linefeed x'40'=blank
     D whitespaceChr   C                   x'15050D2540'
     D c               s              1a
     D i               s             10i 0
     D inWhitespace    s               n   Inz(*Off)

      /free
           // copy all non-whitespace characters to the return value
           for i = 1 to %len(input);
              c = %subst(input : i : 1);
              if %scan(c : whitespaceChr) > 0;
                 // If this is a new set of whitespace, add one blank
                 if inWhitespace = *OFF;
                    inWhitespace = *ON;
                    output += ' ';
                 endif;
              else;
                 // Not handling whitespace now.  Add character to output
                 inWhitespace = *OFF;
                 output += c;
              endif;
           endfor;

           return output;

      /end-free
     P rmvWhiteSpace   e

テスト用 の XML ファイル

テストに使用している XML ファイルは以下のものになっていますが、上の方でも述べたように、UCS2 のテキストファイルとして IFS に転送して使用しています。

<Products>
  <Category Code="02">
    <CatDescr>Toasters</CatDescr>
    <Product Code="1234">
      <Description type="short">Two slot chrome</Description>
      <Description type="long">This beautiful chrome finished toaster is the 
      perfect complement to any modern kitchen</Description>
      <MSRP>22.95</MSRP>
      <SellPrice>15.95</SellPrice>
      <QtyOnHand>24</QtyOnHand>
    </Product>
    <Product Code="2345">
      <Description type="short">Four slot matt black</Description>
      <MSRP>35.75</MSRP>
      <SellPrice>23.95</SellPrice>
      <QtyOnHand>247</QtyOnHand>
    </Product>
    <Product Code="3456">
      <Description type="short">Four slot chrome finish</Description>
      <MSRP>42.95</MSRP>
      <SellPrice>29.95</SellPrice>
      <QtyOnHand>47</QtyOnHand>
    </Product>  
  </Category> 
  <Category Code="04">
    <CatDescr>Coffee Makers</CatDescr>
    <Product Code="9234">
      <Description type="short">8 Cup Basic Model</Description>
      <MSRP>27.95</MSRP>
      <SellPrice>18.95</SellPrice>
      <QtyOnHand>27</QtyOnHand>
    </Product>
    <Product Code="9345">
      <Description type="short">One more product</Description>
      <MSRP>44.55</MSRP>
      <SellPrice>32.5</SellPrice>
      <QtyOnHand>95</QtyOnHand>
    </Product>
    <Product Code="9347">
      <Description type="short">Yet another product</Description>
      <MSRP>44.55</MSRP>
      <SellPrice>32.5</SellPrice>
      <QtyOnHand>900</QtyOnHand>
    </Product>
  </Category>
</Products>

実行結果

↓が実行結果です。

スプールに受け取ったイベントとデータを書き出すようになっています。

[Top Pageに戻る]

Ads by TOK2