C でのパーサーの作成

始める前に

以下のトピックを読み、理解していることを確認してください。

ロード可能インプリメンテーション・ライブラリー、または LIL、 は C パーサー (またはノード) のインプリメンテーション・モジュールです。 LIL は、ファイル拡張子が .dll ではなく .lil である、UNIX 共有オブジェクトまたは Windows ダイナミック・リンク・ライブラリー (DLL) です。

開発者が作成する必要のあるインプリメンテーション関数は、パーサー・インプリメンテーション関数でリストしています。このプロセスに役立てるために WebSphere Business Integration Message Broker によって提供されるユーティリティー関数は、パーサー・ユーティリティー関数でリストしています。

WebSphere Business Integration Message Broker には、 BipSampPluginParser.c というサンプル・ユーザー定義パーサーのソースが備えられています。これは、単純な擬似 XML パーサーで、現状のまま使用することもできますし、変更を加えることもできます。

パーサーを作成するタスクは、 構文解析されるビット・ストリームの複雑さによってかなり異なります。 ここでは、基本ステップのみを説明します。 これらについて、以下のセクションで説明します。
  1. ブローカーの初期化時のパーサーの定義
  2. パーサーのインスタンスの作成
  3. パーサー機能のインプリメント
  4. ユーザー定義パーサーのインスタンスの削除

input 関数のインプリメント

input 関数 (たとえば、cpiParseBuffer) は、 パーサーが入力メッセージを解析する必要があるときにブローカーによって呼び出されます。パーサーはブローカーに、所有する必要のある入力ビット・ストリーム・バッファーの量を知らせる必要があります。 固定サイズ・ヘッダーの場合、パーサーはヘッダーのサイズを要求します。 パーサーがメッセージ全体を処理する場合、バッファーの残りの部分を要求します。

以下に例を示します。
  1. cpiParseBufferEncoded 関数を呼び出します。
    int cpiParseBufferEncoded(
        CciParser*  parser,
        CciContext* context,
        int            encoding,
        int         ccsid
    ){
        PARSER_CONTEXT_ST* pc = (PARSER_CONTEXT_ST *)context ;
        int rc;
    
  2. cpiBufferPointer 関数を使用して、 メッセージ・バッファーへのポインターを取得し、オフセットを設定します。
        pc->iBuffer = (void *)cpiBufferPointer(&rc, parser);
        pc->iIndex = 0;
  3. バッファーのフォーマットを保管します。
        pc->iEncoding = encoding;
        pc->iCcsid = ccsid;
  4. cpiBufferSize 関数を使用して、 バッファーのサイズを保管します。
        pc->iSize = cpiBufferSize(&rc, parser);
  5. cpiBufferByte 関数を使用して、ストリーム内の先頭バイトの事前準備をします。
        pc->iCurrentCharacter = cpiBufferByte(&rc, parser, pc->iIndex);
  6. cpiRootElement 関数を使用して、 現行エレメントをルート・エレメントに設定します。
        pc->iCurrentElement = cpiRootElement(&rc, parser);
  7. フラグをリセットして、構文解析が正しくリセットされるようにします。
              pc->iInTag = 0;
    
  8. 残りのバッファーの所有権を請求します。
        return(pc->iSize);
    }

parse 関数のインプリメント

汎用解析関数 (たとえば、cpiParseFirstChild) は、 ESQL 式を評価するために構文エレメント・ツリーを作成する必要がある場合に、 ブローカーが呼び出す関数です。たとえば、filter ノードは ESQL 式の ESQL フィールド参照を使用します。 式を評価するには、このフィールド参照を解決しなければなりません。 要求されたエレメントが作成されるか、またはそれが存在しないことをパーサーが認識するまで、 パーサーの汎用解析関数が、おそらく繰り返し呼び出されます。

以下に例を示します。
void cpiParseFirstChild(
    CciParser*  parser,
    CciContext* context,
    CciElement* element
){
    PARSER_CONTEXT_ST* pc = (PARSER_CONTEXT_ST *)context ;
    int rc;

  if ((!cpiElementCompleteNext(&rc, element)) &&
            (cpiElementType(&rc, element) == CCI_ELEMENT_TYPE_NAME)) {

    while ((!cpiElementCompleteNext(&rc, element))     &&
                      (!cpiFirstChild(&rc, element)) &&
                      (pc->iCurrentElement))
    {
          pc->iCurrentElement = parseNextItem(parser, context, pc->iCurrentElement);
    }
  }
    return;
}

output 関数のインプリメント

output 関数 (たとえば、cpiWriteBuffer) は、 パーサーが構文エレメント・ツリーを出力ビット・ストリームに直列化する必要があるときにブローカーによって呼び出されます。 たとえば、Compute ノードはご使用のユーザー定義パーサーのドメイン内にツリーを作成したとします。 このツリーが、たとえば MQOutput ノードによる出力でなければならない場合、 パーサーが出力ビット・ストリーム・バッファーを、作成されたツリーを表すデータに付加します。

以下に例を示します。
int cpiWriteBufferEncoded(
    CciParser*  parser,
    CciContext* context,
    int            encoding,
    int         ccsid
){
    PARSER_CONTEXT_ST* pc = (PARSER_CONTEXT_ST *)context ;
    int                initialSize = 0;
    int                rc = 0;
    const void* a;
    CciByte b;

    initialSize = cpiBufferSize(&rc, parser);
    a = cpiBufferPointer(&rc, parser);
    b = cpiBufferByte(&rc, parser, 0);

    cpiAppendToBuffer(&rc, parser, (char *)"Some test data", 14);

    return cpiBufferSize(0, parser) - initialSize;
}

複数のメッセージ形式を持つメッセージ

通常、 着信メッセージ・データは単一メッセージ形式なので、 1 つのパーサーがメッセージの内容全体の構文解析を担当します。 必要なパーサーのクラス名は、入力メッセージの MQMD または MQRFH2 ヘッダーにある、 Format フィールドで定義されます。

しかし、 メッセージは複数のフォーマットで構成される場合もあります。たとえば、 あるフォーマットのヘッダーに続くデータは、別のフォーマットである場合などです。 この場合、最初のパーサーが、 チェーン内の次のフォーマットを構文解析するパーサーのクラス名を識別する必要があり、 これが順に続きます。 ユーザー定義パーサーでは、インプリメンテーション関数 cpiNextParserClassName が、 複数のメッセージ形式から構成されるメッセージ用に、 パーサー・クラスのチェーンをナビゲートする必要がある場合、 そのブローカーによって呼び出されます。

ご使用のユーザー定義パーサーが、 複数のメッセージ形式の一部であるメッセージ形式の構文解析をサポートする場合、 ユーザー定義パーサーは、cpiNextParserClassName 関数をインプリメントする必要があります

以下に例を示します。
  1. cpiNextParserClassName 関数を呼び出します。
    void cpiNextParserClassName(
        CciParser*  parser,
        CciContext* context,
        CciChar*    buffer,
        int         size
    ){
        PARSER_CONTEXT_ST* pc = (PARSER_CONTEXT_ST *)context ;
        int                rc = 0;
    
  2. 次のパーサー・クラスの名前をブローカーにコピーします。
      CciCharNCpy(buffer, pc->iNextParserClassName, size);
    
        return;
    }

ブローカーの初期化時のパーサーの定義

ユーザー定義パーサーの初期化関数は、ブローカーの初期化中に自動的に呼び出されます。 ユーザー定義パーサーの役割は以下のとおりです。
  • ユーザー定義パーサーがインプリメントするメッセージ・ファクトリーの作成および命名。 パーサー・ファクトリーは、関連パーサー・インプリメンテーションのコンテナーです。 パーサー・ファクトリー名は、ブローカー内で固有でなければなりません。
  • サポートされるメッセージ・パーサー・クラス名の定義、 およびユーザー定義パーサー・インプリメンテーション関数へのポインターを含む関数テーブルへのポインターの提供。 パーサー・クラス名は、ブローカー内で固有でなければなりません。

ユーザー定義パーサーをインプリメントするそれぞれの LIL は、 bipGetParserFactory と呼ばれる関数を、その初期化関数としてエクスポートする必要があります。 初期化関数は、ユーザー定義パーサーがサポートするファクトリーの名前、 およびファクトリーがサポートするオブジェクトのクラスまたは共用オブジェクトを定義します。

初期化関数はまた、ファクトリー・オブジェクトを作成し、 LIL がサポートするすべてのパーサーの名前を定義する必要があります。 ファクトリーは、任意の数のオブジェクト・クラス (パーサー) をサポートできます。 パーサーの定義時には、そのパーサー用のインプリメンテーション関数へのポインターのリストが、 ブローカーに渡されます。 同じ名前のパーサーがすでに存在する場合、要求は拒否されます。

例えば、パーサーを定義するには以下のようにします。
  1. bipGetParserFactory 初期化関数をエクスポートします。
    void LilFactoryExportPrefix * LilFactoryExportSuffix bipGetParserFactory()
    {
  2. 変数を宣言します。
      CciFactory*     factoryObject;
        int rc;
        static CPI_VFT  vftable = {CPI_VFT_DEFAULT};
  3. すべての静的定数を初期化します。
        initParserConstants();
  4. パーサー・インプリメンテーション関数へのポインターが含まれた関数テーブルをセットアップします。
        vftable.iFpCreateContext            = cpiCreateContext;
        vftable.iFpParseBufferEncoded       = cpiParseBufferEncoded;
        vftable.iFpParseFirstChild          = cpiParseFirstChild;
        vftable.iFpParseLastChild           = cpiParseLastChild;
        vftable.iFpParsePreviousSibling     = cpiParsePreviousSibling;
        vftable.iFpParseNextSibling         = cpiParseNextSibling;
        vftable.iFpWriteBufferEncoded       = cpiWriteBufferEncoded;
        vftable.iFpDeleteContext            = cpiDeleteContext;
        vftable.iFpSetElementValue          = cpiSetElementValue;
        vftable.iFpElementValue             = cpiElementValue;
        vftable.iFpNextParserClassName      = cpiNextParserClassName;
        vftable.iFpSetNextParserClassName   = cpiSetNextParserClassName;
        vftable.iFpNextParserEncoding       = cpiNextParserEncoding;
      vftable.iFpNextParserCodedCharSetId = cpiNextParserCodedCharSetId;

初期化関数は、cpiCreateParserFactory を呼び出すことによってパーサー・ファクトリーを作成する必要があります。 ファクトリーがサポートするパーサー・クラスは、cpiDefineParserClass を呼び出すことによって定義されます。ファクトリー・オブジェクトのアドレス (cpiCreateParserFactory によって戻される) は、 初期化関数からの戻り値としてブローカーに戻される必要があります。

以下に例を示します。
  1. cpiCreateParserFactory 関数を使用してパーサー・ファクトリーを作成します。
        factoryObject = cpiCreateParserFactory(&rc, constParserFactory);
      
  2. cpiDefineParserClass 関数を使用して、 ファクトリーがサポートするメッセージのクラスを定義します。
    if (factoryObject) {
          cpiDefineParserClass(&rc, factoryObject, constPXML, &vftable);
      }
    else {
            /* Error: Unable to create parser factory */
      }
  3. このファクトリー・オブジェクトのアドレスをブローカーに戻します。
        return(factoryObject);
    }

パーサーのインスタンスの作成

ユーザー定義パーサー・オブジェクトのインスタンスが作成されるときはいつでも、 コンテキスト作成インプリメンテーション関数 cpiCreateContext が、 メッセージ・ブローカーによって呼び出されます。 これによって、ユーザー定義パーサーは、属性用のデータ域など、 パーサーと関連付けられたインスタンス・データを割り振ることができます。

以下に例を示します。
  1. cpiCreateContext を呼び出します。
    CciContext* cpiCreateContext(
        CciParser* parser
    ){
        PARSER_CONTEXT_ST *p;
    
  2. ローカル・コンテキストにポインターを割り振ります。
        p = (PARSER_CONTEXT_ST *)malloc(sizeof(PARSER_CONTEXT_ST));
    
  3. コンテキスト・エリアを消去します。
        if (p) {
              memset(p, 0, sizeof(PARSER_CONTEXT_ST));
        }
      else {
            /* Error: Unable to allocate memory */
      }
  4. ローカル・コンテキストにポインターを戻します。
        return((CciContext*)p);
    }

パーサー機能のインプリメント

パーサーは、以下のタイプのインプリメンテーション関数をインプリメントする必要があります。
  1. input 関数
  2. parse 関数
  3. output 関数

以下では、各関数タイプについて説明します。

ユーザー定義パーサーのインスタンスの削除

パーサーのインスタンスを削除するには、cpiDeleteContext 関数を使用します。 以下に例を示します。
void cpiDeleteContext(
    CciParser*  parser,
    CciContext* context
){
    PARSER_CONTEXT_ST* pc = (PARSER_CONTEXT_ST *)context ;
    int                rc = 0;

    return;
}

関連概念
ユーザー定義拡張機能の計画
ランタイム環境でのユーザー定義拡張機能
ユーザー定義拡張機能の設計
ユーザー定義のパーサー

関連タスク
ユーザー定義拡張機能の開発
提供されているサンプルのインプリメント
C ユーザー定義拡張機能のコンパイル

関連資料
C ユーザー定義パーサー API