Archive for the ‘article’ Category

TestRunner自動生成 (for CppUnit 1.6.2)

Thursday, April 6th, 2006
単体テストフレームワーク(UnitTestFramework): xUnit の解説書:
「eXtreme Programming テスト技法」
翔泳社 ISBN4-7981-0128-1

では、C++版xUnitのひとつ CppUnit-xを利用したUnitTestの手順について解説しています。

C++のテストをCppUnitで書くとき、最も面倒なのはテストスイートの構築です。 この本にはCppUnit-x によるテストケースを記述したヘッダを基にテストスイートを生成し、 そして実行するC++コードを吐くruby スクリプト ‘TestRunnerFactory.rb’ が紹介されています。

CppUnit-x 用のTestRunnerFactory.rb を本家 CppUnit 1.6.2で使えるように書き換えたものを以下に示します。

TestRunnerFactory.rb (for CppUnit 1.6.2)
class TestSuiteFactory

  def initialize(classname)
    @classname = classname
    @testMethods = []
  end

  def makeAddSuite
    "  suite.addTest(" + @classname + "_suite());\n"
  end

  def makeSuiteMethod(file)
    file.puts "#include \"" + @classname + ".h\""
    file.puts "CppUnit::Test* " + @classname + "_suite() {"
    file.puts "  typedef CppUnit::TestCaller<" + @classname + "> caller_type;"
    file.puts "  CppUnit::TestSuite* suite = new CppUnit::TestSuite(\"" + @classname + "\");\n"
    @testMethods.each{ |method|
       file.print "  suite->addTest(new caller_type(\"" + method + "\", "
       file.print @classname + "::" + method + "));\n"
    }
    file.puts "  return suite;"
    file.puts "}"
  end

  def parse
    lex = /.*void (test.*)\(\).*/
    File.readlines(@classname + ".h").each do |f|
      if lex =~ f then
        @testMethods.push($1)
      end
    end
  end

end

class TestRunnerFactory

  def initialize
    @tests = []
  end

  def getTestList
    dir = Dir.open(Dir.pwd)
    reg = /(.*Test)\.h/
    dir.each { |file|
      if reg =~ file then
        @tests.push TestSuiteFactory.new($1)
      end
    }
    dir.close
  end

  def makeRunner
    tester = File.new("Tester.cpp", "w")
    tester.puts "#include <iostream>"
    tester.puts "#include <cppunit/TestCaller.h>"
    tester.puts "#include <cppunit/TestSuite.h>"
    tester.puts "#include <cppunit/TextTestResult.h>"
    @tests.each { |aClass|
      aClass.parse
      aClass.makeSuiteMethod(tester)
    }
    tester.puts "int main() {"
    tester.puts "  CppUnit::TestSuite suite;"
    tester.puts "  CppUnit::TextTestResult result;"
    @tests.each { |aClass|
      tester.puts aClass.makeAddSuite
    }
    tester.puts "  suite.run(&result);"
    tester.puts "  std::cout << result << std::endl;"
    tester.puts "  return 0;"
    tester.puts "}"
    tester.close
  end

end

factory = TestRunnerFactory.new
factory.getTestList
factory.makeRunner

これを使えばテスト効率がぐんと向上します。が、TestRunnerFactory.rbはrubyスクリプトですから、もちろんrubyなしには実行できません。

そこで、このスクリプトが行う処理をそのままC++で実現しました。Microsoft Visual C++ 6.0 でコンパイル/実行を確認しました。

なお、このアプリケーションの実装には Boost 1.26.0 の正規表現ライブラリ ‘Regex++’を利用しました。

TestRunnerFactory.cpp
/*
 * TestRunnerFactory.cpp
 * compiled & tested on Visual C++ 6.0 & Boost 1.26.0 Regex++
 *
 * [build]
 * cl -GX -MD -I[BOOST_ROOT] TestRunnerFactory.cpp setargv.obj
 *    -link -libpath:[BOOST_ROOT]/libs/regex/build/vc6
 *
 * [usage]
 * TestRunnerFactory <file...>
 *   - parse only *Test.h
 *   - generated code goes to 'stdout'
 */

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <boost/regex.hpp>

using namespace std;
using namespace boost;

int main(int argc, char* argv[]) {

  vector<string> classnames;
  vector<string>::iterator classname;

  reg_expression<char> regex;
  match_results<const char*> results;

  /* argv[] から "xxxTest.h" を探し、classnames に追加 */
  regex = "(.*Test)\\.h";
  for ( int i= 1; i < argc; ++i ) {
    if ( regex_match(argv[i], results, regex) ) {
      classnames.push_back(results.str(1));
    }
  }

  /* お決まりの #include ... */
  cout << "#include <iostream>\n\n"
          "#include <cppunit/TestCaller.h>\n"
          "#include <cppunit/TestSuite.h>\n"
          "#include <cppunit/TextTestResult.h>\n\n";

  /* 抽出した "xxxTest.h" を #include */
  for ( classname = classnames.begin(); classname != classnames.end(); ++classname ) {
    cout << "#include \"" << *classname << ".h\"\n";
  }

  /* int main() 冒頭部 */
  cout << "\nint main() {\n"
          "  CppUnit::TestSuite suite;\n"
          "  CppUnit::TestSuite* group;\n"
          "  CppUnit::TextTestResult result;\n\n";

  /* "xxxTest.h" から "void testXXXX()" を探し、TestSuiteに追加 */
  regex = "[ \t]void[ \t]+(test.*)\\(\\)";
  for ( classname = classnames.begin(); classname != classnames.end(); ++classname ) {
    cout << "  group = new CppUnit::TestSuite(\"" << *classname << "\");\n";
    ifstream file((*classname + ".h").c_str());
    string line;
    while ( getline(file, line) ) {
      if ( regex_search(line.c_str(), results, regex) ) {
        cout << "  group->addTest(new CppUnit::TestCaller<" << *classname << ">(\""
             << results.str(1) << "\", " << *classname
             << "::" << results.str(1) << "));\n";
      }
    }
    cout << "  suite.addTest(group);\n\n";
  }

  /* 実行と結果の出力 */
  cout << "  suite.run(&result);\n"
          "  result.print(std::cout);\n\n"
          "  return 0;\n"
          "}" << endl;
  return 0;
}

早速使ってみましょう。

非常に簡単なクラスCounter、およびCounterのテストコードを用意しました。

テストされるオブジェクト Counter

Counter.h
#ifndef __COUNTER_H__
#define __COUNTER_H__

class Counter {
public:
  Counter();
 ~Counter();
  int value() const;
  void incr();
  void decr();
  void clear();

private:
  int value_;
};
#endif
Counter.cpp
#include "Counter.h"

Counter::Counter() : value_(0) {
}

Counter::~Counter() {}

int Counter::value() const {
  return value_;
}

void Counter::incr() {
  ++value_;
}

void Counter::decr() {
  --value_;
}

void Counter::clear() {
  value_ = 0;
}

Counterのテストコード

CounterTest.h
#ifndef __COUNTERTEST_H__
#define __COUNTERTEST_H__

#include <cppunit/TestCase.h>
#include "Counter.h"

class CounterTest : public CppUnit::TestCase {
public:
  void test_init();
  void test_incr();
  void test_decr();
  void test_clear();

  virtual void setUp();
  virtual void tearDown();

};

#endif
CounterTest.cpp
#include "CounterTest.h"

void CounterTest::setUp() {
  // 前準備
}

void CounterTest::tearDown() {
  // 後始末
}

////////////// TEST CASES //////////////////

void CounterTest::test_init() {
  Counter c;
  CPPUNIT_ASSERT_EQUAL( 0, c.value() );
}

void CounterTest::test_incr() {
  Counter c;
  CPPUNIT_ASSERT_EQUAL( 0, c.value() );
  c.incr();
  CPPUNIT_ASSERT_EQUAL( 1, c.value() );
  c.incr();
  CPPUNIT_ASSERT_EQUAL( 2, c.value() );
}

void CounterTest::test_decr() {
  Counter c;
  CPPUNIT_ASSERT_EQUAL( 0, c.value() );
  c.decr();
  CPPUNIT_ASSERT_EQUAL( -1, c.value() );
  c.decr();
  CPPUNIT_ASSERT_EQUAL( -2, c.value() );
}

void CounterTest::test_clear() {
  Counter c;
  CPPUNIT_ASSERT_EQUAL( 0, c.value() );
  c.incr();
  CPPUNIT_ASSERT_EQUAL( 1, c.value() );
  c.clear();
  CPPUNIT_ASSERT_EQUAL( 0, c.value() );
}

TestRunnerFactory CounterTest.hによって、以下のようなコードが生成されます。

TestRunnerFactoryが生成したコード

Tester.cpp
#include <iostream>

#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TextTestResult.h>

#include "CounterTest.h"

int main() {
  CppUnit::TestSuite suite;
  CppUnit::TestSuite* group;
  CppUnit::TextTestResult result;

  group = new CppUnit::TestSuite("CounterTest");
  group->addTest(new CppUnit::TestCaller<CounterTest>("test_init", CounterTest::test_init));
  group->addTest(new CppUnit::TestCaller<CounterTest>("test_incr", CounterTest::test_incr));
  group->addTest(new CppUnit::TestCaller<CounterTest>("test_decr", CounterTest::test_decr));
  group->addTest(new CppUnit::TestCaller<CounterTest>("test_clear", CounterTest::test_clear));
  suite.addTest(group);

  suite.run(&result);
  result.print(std::cout);

  return 0;
}

生成された Tester.cpp、テストコード CounterTest.cpp、テスト対象Counter.cpp をコンパイルし、 CppUnitライブラリをリンクすればテストモジュールの完成です。

% TestRunnerFactory CounterTest.h > Tester.cpp

% cl -GX -MD -Id:/cppunit-1.6.2/include Tester.cpp Counter.cpp CounterTest.cpp cppunit.lib
     -link -libpath:d:/cppunit-1.6.2/lib

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

Tester.cpp
Counter.cpp
CounterTest.cpp
コードを生成中...
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/out:Tester.exe
-libpath:d:/cppunit-1.6.2/lib
Tester.obj
Counter.obj
CounterTest.obj
cppunit.lib

% Tester
....
OK (4 tests)

ICU 2.x : UnicodeString による文字コード変換

Thursday, April 6th, 2006

ICU 2.x ではUNICODEによる文字列クラス UnicodeString が導入されました。

UnicodeString はマルチバイト文字列からコンストラクトでき、さらにメソッド
extract でマルチバイト文字列に変換できます。

このふたつを用いて任意の文字コード間の変換を行なってみましょう。

マルチバイト文字列 → UnicodeString

マルチバイト文字列からUnicodeStringすなわちUNICODEに変換するには:

  • UConverter を生成し、UnicodeStringのコンストラクタに与える
  • codepage文字列を UnicodeStringのコンストラクタに与える

の2通りの方法があります。後者の方が実装が簡単ですが、これはUnicodeStringのコンストラクタ内でUConverterを生成/変換後に破棄していますから、パフォーマンスは若干劣ります。

  • UnicodeString::UnicodeString(const char* codepageData, const char* codepage =0);

    codepageで表現されたマルチバイト文字列codepageDataからUnicodeStringを生成します。[2]

  • UnicodeString::UnicodeString(const char* src, int32_t srcLength, UConverter* cnv, UErrorCode& errorCode);

    cnvを用いてsrcから始まるsrcLengthバイトのマルチバイト文字列からUnicodeStringを生成します。[3]

    ※ 変換で発生したエラーはerrorCodeに設定されます。

UnicodeString → マルチバイト文字列

UnicodeStringからマルチバイト文字列に変換するには:

  • UConverter を生成し、メソッド extractに与える
  • codepage文字列をメソッド extract に与える

の2通りの方法があります。後者の方が実装が簡単ですが、コンストラクタと同様、メソッド内でUConverterを生成/変換後に破棄していますから、パフォーマンスは若干劣ります。

  • int32_t UnicodeString::extract(int32_t start, int32_t startLength, char* target, const char* codepage =0) const;

    startから始まるstartLength文字をcodepageで表現されたマルチバイト文字列に変換しtargetに格納します。[5]

  • int32_t UnicodeString::extract(char* dest, int32_t destCapacity, UConverter* cnv, UErrorCode& errorCode) const;

    cnvを用いてUNICODE文字列全体をマルチバイト文字列に変換し、destから始まるdestCapacityバイトの領域に格納します。[7]

    ※ 変換で発生したエラーはerrorCodeに設定されます。

いずれのメソッドも、変換後のバイト数を返します。 格納先を 0にしておけば、実際の変換を行なわず、変換結果の格納に必要な領域の大きさを返します(ただし末尾の ‘\0′を含まない)。[4] [6]

std::string のコンストラクタであらかじめ必要な大きさを与えることにより[8] [10]、char[]バッファを明示的に確保せず、std::stringをUNICODE→マルチバイト文字列変換の格納先に直接指定することもできます[9][11]

このとき、変換結果を格納するのに必要な容量をあらかじめ求めておかなければならないため、メソッドextractを2度呼ばなければなりません。

が、ある程度の余裕をもって十分な領域を確保しておくことができる場合もあります。

たとえば shift_jis に変換する場合、UNICODEでの1文字は shift_jisでは1もしくは2バイトに変換されますから、UNICODE文字列の文字数の2倍のバイト数の領域を確保しておけば十分です。

UnicodeString input;
  std::string result(input.length()*2, '\0'); // 十分な長さの '\0' で埋まった文字列
  input.extract(0, input.length(), &result[0], "shift_jis");
  result.erase(result.find('\0')); // 末尾の余分な '\0' を削除

CppUnitによるサンプル

#include <unicode/unistr.h> // UnicodeString
#include <unicode/ucnv.h>   // UConverter

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestAssert.h>

/* CppUnitのための
 * UnicodeString の比較と文字列化 */
template<>
struct CppUnit::assertion_traits<icu::UnicodeString> {
  static bool equal(const icu::UnicodeString& x, const icu::UnicodeString& y) {
    return x == y ? true : false;
  }
  static std::string toString(const icu::UnicodeString& x) {
    std::string result(x.extract(0, x.length(), 0, "shift_jis"), '\0');
    x.extract(0, x.length(), &result[0], "shift_jis");
    return result;
  }
};

class UnicodeStringTest : public CppUnit::TestFixture {
private:

  UConverter* converter;
  UErrorCode  error;

  static std::wstring uinput;
  static std::string  sinput; 

public:

  virtual void setUp() {
    error = U_ZERO_ERROR;
    converter = ucnv_open("shift_jis", &error);
    CPPUNIT_ASSERT( U_SUCCESS(error) );
  }

  virtual void tearDown() {
    ucnv_close(converter);
  }

  /* from UNICODE
     UnicodeString::UnicodeString(const UChar* text);
   */
  void testCtor0() {
    icu::UnicodeString result(uinput.c_str()); // [1]
    CPPUNIT_ASSERT_EQUAL(6L, result.length());
    for ( int i = 0; i < result.length(); ++i) {
      CPPUNIT_ASSERT( uinput[i] == result[i]);
    }
  }

  /* from shift_jis using codepage
     UnicodeString::UnicodeString(const char* codepageData,
                                  const char* codepage =0);
   */
  void testCtor1() {
    icu::UnicodeString expected(uinput.c_str());
    icu::UnicodeString result(sinput.c_str(), "shift_jis"); // [2]
    CPPUNIT_ASSERT_EQUAL( expected, result);
  }

  /* from shift_jis using UConverter
     UnicodeString::UnicodeString(const char* src,
                                  int32_t srcLength,
                                  UConverter* cnv,
                                  UErrorCode& errorCode);
   */
  void testCtor2() {
    icu::UnicodeString expected(uinput.c_str());
    icu::UnicodeString result(sinput.c_str(), sinput.size(),
                              converter, error = U_ZERO_ERROR); // [3]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, result);
  }

  /* to shift_jis using codepage
     int32_t UnicodeString::extract(int32_t start,
                                    int32_t startLength,
                                    char* target,
                                    const char* codepage =0) const;
   */
  void testConvert0() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    int32_t convertedLength = input.extract(0, input.length(), 0, "shift_jis"); // [4]
    char* result = new char[convertedLength + 1];
    input.extract(0, input.length(), result, "shift_jis"); // [5]
    CPPUNIT_ASSERT_EQUAL( expected, std::string(result) );
    delete[] result;
  }

  /* to shift_jis using converter
     int32_t UnicodeString::extract(char* dest,
                                    int32_t destCapacity,
                                    UConverter* cnv,
                                    UErrorCode& errorCode) const;
   */
  void testConvert1() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    int32_t convertedLength = input.extract(0, 0, converter, error = U_ZERO_ERROR); // [6]
    char* result = new char[convertedLength+1];
    input.extract(result, convertedLength+1, converter, error = U_ZERO_ERROR); // [7]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, std::string(result) );
    delete[] result;
  }

  /* to shift_jis no-using buffer
   */
  void testConvert2() {
    const std::string  expected = sinput;
    icu::UnicodeString input(uinput.c_str());
    { // using codepage
    std::string result(input.extract(0, input.length(), 0, "shift_jis"), '\0'); // [8]
    input.extract(0, input.length(), &result[0], "shift_jis"); // [9]
    CPPUNIT_ASSERT_EQUAL( expected, result);
    }
    { // using UConverter
    std::string result(input.extract(0, 0,
                       converter, error = U_ZERO_ERROR), '\0'); // [10]
    input.extract(&result[0], result.size(),
                       converter, error = U_ZERO_ERROR); // [11]
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    CPPUNIT_ASSERT_EQUAL( expected, result);
    }
  }

  /* euc-jp to shift_jis
   */
  void testConvert3() {
    std::string euc_jp = "\xC6\xFC\xCB\xDC";
    std::string shift_jis = "日本";
    UConverter* fromConverter = ucnv_open("euc-jp", &error);
    UConverter* toConverter   = ucnv_open("shift_jis", &error);
    CPPUNIT_ASSERT( U_SUCCESS(error) );
    icu::UnicodeString ustr(euc_jp.c_str(), euc_jp.size(),
                            fromConverter, error = U_ZERO_ERROR); // [12]
    std::string result(ustr.extract(0, 0, toConverter, error = U_ZERO_ERROR), '\0');
    ustr.extract(&result[0], result.size(), toConverter, error = U_ZERO_ERROR);
    ucnv_close(fromConverter);
    ucnv_close(toConverter);
    CPPUNIT_ASSERT_EQUAL( shift_jis, result );
  }

  CPPUNIT_TEST_SUITE(UnicodeStringTest);
  CPPUNIT_TEST(testCtor0);
  CPPUNIT_TEST(testCtor1);
  CPPUNIT_TEST(testCtor2);
  CPPUNIT_TEST(testConvert0);
  CPPUNIT_TEST(testConvert1);
  CPPUNIT_TEST(testConvert2);
  CPPUNIT_TEST(testConvert3);
  CPPUNIT_TEST_SUITE_END();
};

std::wstring UnicodeStringTest::uinput = L"ガンバレ日本";
std::string  UnicodeStringTest::sinput =  "ガンバレ日本";

CPPUNIT_TEST_SUITE_REGISTRATION(UnicodeStringTest);

ICU 2.4 の正規表現

Thursday, April 6th, 2006

IBMのOpenSource: ICU はUnicodeを扱うライブラリとして高い評価を得ています。ICUは何度かのバージョンアップを繰り返しながら、その度にライブラリとしての完成度を上げてきました。

ICU 2.4では、新たにUnicode文字列を対象とした正規表現(regular Expression)をサポートします。正規表現を扱うアプリケーションの構築にはBoost Regex++などのライブラリを使用していましたが、ICUが正規表現をサポートしたことで、選択の幅がより広がったことになります。

ICUの正規表現を利用する場合は、少なくともライブラリ: icuucicuin をリンクしてください。

なお、以下のサンプルは Microsoft Visual C++ 6.0 および 7.0 でコンパイル/実行を確認しました。

ICUが定義した型UCharwchar_tが等価であることを前提としており、そうでない場合はUnicodeString str = L"..."; のような代入ができません。

RegexPatternとRegexMatcher

ICUの正規表現は、<unicode/regex.h>に定義された二つのクラス:RegexPatternRegexMatcherによってサポートされます。RegexPatternは正規表現文字列を内部形式にコンパイルしたもの、RegexMatcherRegexPatternに基づいた正規表現パターンが文字列のどの位置に現れるかを検索します。この二つはJava2 1.4で導入された java.util.regex パッケージの Pattern, Matcher とコンパチブルなインタフェースを提供しています。

パターン生成

RegexPatternのインスタンスはstaticメソッド:

RegexPattern* RegexPattern::compile(const UnicodeString& regex, UParseError& pe, UErrorCode& status);

によって生成します。regexに正規表現文字列を与えます。

コンパイルに失敗したときはstatusにエラーコード、peにエラーの詳細情報が格納されます。

UnicodeString regex = L"(.)や"; // 正規表現
  UParseError   pe;
  UErrorCode    status = U_ZERO_ERROR;
  RegexPattern* pattern = RegexPattern::compile(regex, pe, status);
  assert( U_SUCCESS(status) );

生成されたパターンからRegexMatcherを生成するのがRegexPatternのメソッド:

RegexMatcher* RegexPattern::matcher(const UnicodeString& input, UErrorCode& status);

です。inputに検索対象文字列を与えます。

UnicodeString input = L"松島やああ竹島や梅島や"; // 入力(検索対象)
  RegexMatcher* matcher = pattern->matcher(input, status);
  assert( U_SUCCESS(status) );

RegexPatternは、正規表現にマッチする部分を単語の区切りと見なしてトークン分割する

int32_t RegexPattern::split(const UnicodeString& input, UnicodeString dest[], UErrorCode& status);

を持っています。inputを単語毎に切り出し、dest[destCapacity]に格納し、単語数を返します。

RegexPatternによるトークン分割

#include <iostream>
#include <locale>
#include <cassert>

#include <unicode/regex.h>

int main() {
  std::locale::global(std::locale("ja"));

  UParseError error;
  UErrorCode  status = U_ZERO_ERROR;

  UnicodeString regex = L"[とやも]";
  RegexPattern* pattern = RegexPattern::compile(regex, error, status);
  assert( U_SUCCESS(status) );

  UnicodeString fruits[10];
  UnicodeString input = L"リンゴとミカンやナシとモモも";
  int32_t splits = pattern->split(input, fruits, 10, status);
  assert( U_SUCCESS(status) );

  for ( int32_t i = 0; i < splits; ++i ) {
    std::wcout << fruits[i].getTerminatedBuffer() << std::endl;
  }

  delete pattern;
  return 0;
}

実行結果

リンゴ
ミカン
ナシ
モモ

検索

RegexMatcher::find()を呼び出す毎に、 inputからregexとマッチする部分文字列を順に検索します(マッチしなかったとき、0を返します)。

find() の結果は:

int32_t RegexMatcher::start(int group, UErrorCode& status),

int32_t RegexMatcher::end(int group, UErrorCode& status)

で得られます。

それぞれ、マッチ個所の先頭/末尾の位置を返します。 groupには正規表現に現れるグルーピング(括弧で囲まれた部分)を与えることができ、0あるいは省略時には正規表現全体を意味します。

また、UnicodeString RegexMatcher::group(int groupNum, UErrorCode& status) は、マッチする部分文字列を返します。

matcher->reset();
  while ( matcher->find() ) {
    for ( int32_t i = 0; i <= matcher->groupCount(); ++i ) {
      std::wcout << L"[" << matcher->start(i,status)
                 << L"," << matcher->end(i,status)
                 << L")" << matcher->group(i,status).getTerminatedBuffer()
                 << std::endl;
      assert( U_SUCCESS(status) );
    }
    std::wcout << std::endl;
  }

置換

UnicodeString RegexMatcher::replaceFirst(const UnicdeString& replacement, UErrorCode& status)

によって、最初に現れたマッチ個所をreplacementに置き換えた文字列を返します。

マッチ個所すべてを置換するときは:

UnicodeString RegexMatcher::replaceAll(const UnicdeString& replacement, UErrorCode& status)

を用います。replaceAllは以下のコードと等価です:

UnicodeString result;
  matcher->reset();
  while ( matcher->find() ) {
    matcher->appendReplacement(result, replacement, status);
    assert( U_SUCCESS(status) );
  }
  matcher->appendTail(result);

残念ながら、グルーピングたとえば正規表現 "(.)や" の "(.)" に対応する部分(部分マッチ)を置換するメソッドを提供していないようです。

部分マッチの置換は、検索とUnicodeString::replaceを用いた以下のような実装となるでしょう。

#include <iostream>
#include <locale>
#include <cassert>

#include <vector>
#include <utility>

#include <unicode/regex.h>

int main() {
  std::locale::global(std::locale("ja"));

  UParseError error;
  UErrorCode  status = U_ZERO_ERROR;

  UnicodeString regex = L"(.)島や";
  RegexPattern* pattern = RegexPattern::compile(regex, error, status);
  assert( U_SUCCESS(status) );

  UnicodeString input = L"松島やああ竹島や梅島や";
  RegexMatcher* matcher = pattern->matcher(input, status);
  assert( U_SUCCESS(status) );

  /*
   * 部分マッチ"(.)"の置換
   */
  typedef std::pair<int32_t,int32_t> range_type;
  std::vector<range_type> matches;
  matcher->reset();
  while ( matcher->find() ) {
    // 第一グループを保存する
    matches.push_back(range_type(matcher->start(1,status),
                                 matcher->end(1,status)));
      assert( U_SUCCESS(status) );
  }
  UnicodeString replacement = L"松";
  UnicodeString result = input;
  // 末尾から置換
  while ( !matches.empty() ) {
    range_type range = matches.back();
    matches.pop_back();
    result.replace(range.first, range.second - range.first,
                   replacement);
  }
  std::wcout << result.getTerminatedBuffer() << std::endl;

  delete matcher;
  delete pattern;

  return 0;
}

サンプル

#include <iostream>
#include <locale>
#include <cassert>

#include <unicode/regex.h>

int main() {
  std::locale::global(std::locale("ja"));

  UParseError error;
  UErrorCode  status = U_ZERO_ERROR;

  UnicodeString regex = L"(.)島や";
  RegexPattern* pattern = RegexPattern::compile(regex, error, status);
  assert( U_SUCCESS(status) );

  UnicodeString input = L"松島やああ竹島や梅島や";
  RegexMatcher* matcher = pattern->matcher(input, status);
  assert( U_SUCCESS(status) );

  /*
   * 検索
   */
  matcher->reset();
  while ( matcher->find() ) {
    for ( int32_t i = 0; i <= matcher->groupCount(); ++i ) {
      std::wcout << L"[" << matcher->start(i,status)
                 << L"," << matcher->end(i,status)
                 << L")" << matcher->group(i,status).getTerminatedBuffer()
                 << std::endl;
      assert( U_SUCCESS(status) );
    }
    std::wcout << std::endl;
  }

  UnicodeString replacement = L"松島や";
  UnicodeString result;
  /*
   * 置換 : replaceFirst
   */
  result = matcher->replaceFirst(replacement, status);
  assert( U_SUCCESS(status) );
  std::wcout << result.getTerminatedBuffer() << std::endl;

  /*
   * 置換 : replaceAll
   */
  result = matcher->replaceAll(replacement, status);
  assert( U_SUCCESS(status) );
  std::wcout << result.getTerminatedBuffer() << std::endl;

  /*
   * 置換 : appendReplacement/appendAll
   */
  result = L"";
  matcher->reset();
  while ( matcher->find() ) {
    matcher->appendReplacement(result, replacement, status);
    assert( U_SUCCESS(status) );
  }
  matcher->appendTail(result);
  std::wcout << result.getTerminatedBuffer() << std::endl;

  delete matcher;
  delete pattern;

  return 0;
}

実行結果

[0,3)松島や
[0,1)松

[5,8)竹島や
[5,6)竹

[8,11)梅島や
[8,9)梅

松島やああ竹島や梅島や
松島やああ松島や松島や
松島やああ松島や松島や

※ コンパイラ/OSによっては std::wcoutが漢字やかなを正常に出力できないものがあります。

そのような場合は、以下のコードを付加し、std::cout << narrow(...); してください。

このとき、コード冒頭の#include <locale> および std::locale::global(std::locale("ja");は不要です。

UnicodeString -> std::string 変換

#include <iostream>
#include <string>
#include <unicode/unistr.h>

std::string narrow(const UnicodeString& str) {
  // マルチバイト文字は最大2-charと仮定する。
  // shift_jis/euc-jp ならこれで大丈夫。
  int32_t length = str.length();
  char* buffer = new char[length*2+1];
  str.extract(0, length, buffer, "shift_jis");
  std::string result(buffer);
  delete[] buffer;
  return result;
}

/*
 * お試し
 */
int main() {
  UnicodeString input = L"こんにちは、世界";
  std::cout << narrow(input) << std::endl;
  return 0;
}

実行結果

こんにちは、世界

‘置換’はどうやればいいのですか?

Thursday, April 6th, 2006

そうですよね、’検索’ができたならその次は’置換’でしょう。置換すなわち文字列のある部分を他の文字列で置き換えるのは、単なる’検索’より少しばかり複雑です。

charwchar_t配列を文字列として用いている場合、’置換’はひどく面倒な処理が必要となります。’置換’によって文字列の長さが変わらないのなら何の問題もないのですが、文字列の一部をより短い文字列で置き換える場合、置き換える箇所の後ろに続く部分を短くなった分だけ前に詰めなくてはなりません。また、より長い文字列で置き換える場合、置き換える箇所の後ろに続く部分をより後ろにズラさなくてはなりません。困ったことにcharwchar_t配列の長さを知ることができませんから、置換による領域あふれの危険が常につきまといます。

それに対し標準C++ライブラリが提供する文字列クラスstd::basic_string(string/wstring)は長さをそれ自身が管理していますから、置換は思いのままです。std::basic_stringには部分文字列の置換を行うメソッドが用意されています。

std::string str;
str.replace(pos,len,rep);

によって、strposからlen文字をrepに置き換えます。

std::string str = "Hello, World";
str.replace(7,5,"C++"); // str = "Hello, C++"

boost::match_resultsのメソッドposition(n)およびlength(n)はマッチした部分の位置と長さを返してくれますから、これを使えば置換は簡単です:

std::string str = "Hello, World";
boost::reg_expression<char> regex = "World";
boost::match_results<std::string::iterator> results;
if ( boost::regex_search(str, regex, results) ) {
  str.replace(results.position(0), results.length(0), "C++");
}

このコードはstr中の最初に現れる”World”を”C++”に置き換えます。

では最初に現れるものだけでなく、文字列中のすべての”World”を”C++”に置き換えるにはどうすればよいのでしょうか。

boost::regex_grep()は第1引数にcallback関数オブジェクトを与えることで、与えた文字列から正規表現にマッチする箇所が見つかる度にcallbackされ、callbackがfalseを返した時点で検索を終了します。

#include <iostream>
#include <boost/regex.hpp>

sturut callback {
  bool operator()(const boost::match_results<std::string::iterator>& mr) {
  std::cout << "position= " << mr.position(0) << " length= " << mr.length(0) << std::endl;
  return true;
  }
};

int main() {
  std::string str = "Hello, World. Good bye World.";
  boost::reg_expression<char> regex = "World";
  boost::regex_grep(callback(), str.begin(), str.end(), regex);
  return 0;
}

したがって、callback内で位置と長さを適当なコンテナに記憶させることができます:

#include <iostream>
#include <vector>
#include <utility>
#include <boost/regex.hpp>

std::vector< std::pair<int,int> > container;

sturut callback {
  bool operator()(const boost::match_results<std::string::iterator>& mr) {
    container.push_back(std::pair<int,int>(mr.position(0), mr.length(0)));
    return true;
  }
};

int main() {
  std::string str = "Hello, World. Good bye World.";
  boost::reg_expression<char> regex = "World";
  boost::regex_grep(callback(), str.begin(), str.end(), regex);
...

これでstr中に現れる”World”の位置と長さがcontainerに格納されました。この情報を元にstr.replace()によって”C++”に置き換えることができます。このとき、見つかったのとは’逆順’で置き換えを行わないと、正しく置換されないので注意してください:

...
  while ( !container.empty() ) {
    str.replace(container.back().first, container.back().second, "C++");
    container.pop_back();
  }
  std::cout << str << std::cout;
  return 0;
}

以上のようなアルゴリズムで正規表現のマッチと置換が実現できます。が、これではあまりに複雑/煩雑です。そこで、この一連の処理を関数テンプレートregex_replace()を実装しました。

regex/replace.h

#ifndef __REGEX_REPLACE_H__
#define __REGEX_REPLACE_H__

#include <boost/regex.hpp>

#include <vector>  // std::vector
#include <utility> // std::pair

namespace s34 {

  /*
   * replace helper (internal)
   */
  namespace detail {
    template<class Iterator>
    class pushback_submatch {
    public:
      typedef std::vector< std::pair<int,int> > container;
      explicit pushback_submatch(container& v, int s, bool a)
        : vec_(&v), sub_(s), all_(a) {}
      bool operator()(const boost::match_results<Iterator>& mr) {
        vec_->push_back(container::value_type(mr.position(sub_),mr.length(sub_)));
        return all_;
      }
    private:
      container* vec_;
      int sub_;
      bool all_;
    };
  }

  /*
   * size_t
   * regex_replace(std::basic_string<charT, ST, SA>& str,
   *             const boost::reg_expression<charT, traits, Allocator>& e,
   *             const std::basic_string<charT, ST, SA>& rep,
   *             int      sub =0,
   *             bool     all =false,
   *             unsigned flags = boost::match_default)
   *
   *   str から e にマッチする部分文字列を探し、
   *   そのsub番目のマッチ部分をrepで置き換える。
   *   all==trueのときは、この置換を連続して行う。
   *
   */
  template<class ST, class SA, class Allocator, class charT, class traits>
  size_t
  regex_replace(std::basic_string<charT, ST, SA>& str,
                const boost::reg_expression<charT, traits, Allocator>& e,
                const std::basic_string<charT, ST, SA>& rep,
                int      sub =0,
                bool     all =false,
                unsigned flags = boost::match_default) {
    typedef detail::pushback_submatch<std::basic_string<charT,ST,SA>::iterator> helper;
    helper::container sub_stack;
    size_t n = boost::regex_grep(helper(sub_stack, sub, all),
                                 str.begin(), str.end(),
                                 e, flags);
    while ( !sub_stack.empty() ) {
      str.replace(sub_stack.back().first, sub_stack.back().second, rep);
      sub_stack.pop_back();
    }
    return n;
  }

}

#endif

テスト・コード

#include <iostream>
#include <regex/replace.h>

using std::wcout;
using std::endl;

void s34_replace_trial() {
  std::wstring                   source   = L"僕は海が好きで山が好きで川も好き";
  boost::reg_expression<wchar_t> regex    = L"(山|川)(.)好き";
  std::wstring                   replace  = L"だって";
  int                            submatch = 2;

  wcout << L"¥nreplace strings that matches the 2'nd braces of '"
        << regex.str() << L"' with '"
        << replace << L"'" << endl;
  wcout << L"before replace [" << source << L"]" << endl;
  s34::regex_replace(source, regex, replace, submatch, true);
  wcout << L"after  replace [" << source << L"]" << endl;
}

実行結果

replace strings that matches the 2'nd braces of '(山|川)(.)好き' with 'だって'
before replace [僕は海が好きで山が好きで川も好き]
after  replace [僕は海が好きで山だって好きで川だって好き]

とてもシンプルに実装できているのですが、少しばかり問題があります。検索された文字列の位置と長さを保持しなくてはならないためにメモリを食うんです。

たとえば “aaaaaaaaaaaa….”(N個の’a') から “a”を検索すると、位置と長さの組をN個貯めておかなければならないわけです。(それに加えて、文字列の置換を繰り返すためにそれほど速くはないでしょう。)

そこで、最初に長さ0の文字列を用意しておき、マッチするたびに置換と共に連結していくアルゴリズムを考えました。少々複雑ですが速くてコンパクトです。

regex/replace.h

#ifndef __REGEX_REPLACE_H__
#define __REGEX_REPLACE_H__

#include <boost/regex.hpp>

#include <vector>  // std::vector
#include <utility> // std::pair

namespace s34 {

  /*
   * size_t
   * regex_replace(std::basic_string<charT, ST, SA>& str,
   *             const boost::reg_expression<charT, traits, Allocator>& e,
   *             const std::basic_string<charT, ST, SA>& rep,
   *             int      sub =0,
   *             bool     all =false,
   *             unsigned flags = boost::match_default)
   *
   *   str から e にマッチする部分文字列を探し、
   *   そのsub番目のマッチ部分をrepで置き換える。
   *   all==trueのときは、この置換を連続して行う。
   *
   */
  template<class ST, class SA, class Allocator, class charT, class traits>
  size_t
  regex_replace(std::basic_string<charT, ST, SA>& str,
                const boost::reg_expression<charT, traits, Allocator>& e,
                const std::basic_string<charT, ST, SA>& rep,
                int      sub =0,
                bool     all =false,
                unsigned flags = boost::match_default) {
    std::basic_string<charT, ST, SA> result;
    boost::match_results<const charT*> results;
    bool   matched;
    size_t count = 0;
    size_t start = 0;
    size_t length = str.length();
    do {
      matched = boost::regex_search(str.data() + start, results, e, flags);
      if ( matched ) {
        ++count;
        result.append(str, start, results.position(sub));
        result.append(rep);
        size_t tmp = start;
        start += results.position(0) + results.length(0);
        if ( !results.length(0) ) ++start;
        size_t trail = tmp + results.position(sub) + results.length(sub);
        result.append(str, trail, start - trail);
      }
    } while ( matched && all &&  start < length );
    result.append(str, start, str.length() - start);
    str = result;
    return count;
  }

}

#endif

※ おまけ

正規表現なんて気の利いたものは要らない。単に文字列の一部を他の文字列で置換したいんだけど…

了解。簡単です。

単純な文字列の置換

#include <iostream>
#include <string>

template<class E, class T, class A>
std::basic_string<E,T,A>
replace_all(
  const std::basic_string<E,T,A>& source,   // source中にある
  const std::basic_string<E,T,A>& pattern,  // patternを
  const std::basic_string<E,T,A>& placement // placementに置き換える
  ) {
  std::basic_string<E,T,A> result(source);
  for ( std::string::size_type pos = 0 ;
        std::string::npos != (pos = result.find(pattern,pos));
        pos += placement.size() )
    result.replace(pos, pattern.size(), placement);
  return result;
}

int main() {
  std::string source    = "すうどんもうどんもうどんのうち" ;
  std::string pattern   = "うどん" ;
  std::string placement = "もも" ;
  // 結果 :               "すもももももももものうち"
  std::cout << replace_all(source,pattern,placement) << std::endl;

  return 0;
}

非常に簡単な実装ですが、replaceが頻繁に行われるのであまり速くありません。そこで…

単純な文字列の置換 スピードアップ版

#include <iostream>
#include <string>

template<class E, class T, class A>
std::basic_string<E,T,A>
replace_all(
  const std::basic_string<E,T,A>& source,
  const std::basic_string<E,T,A>& pattern,
  const std::basic_string<E,T,A>& placement
  ) {
  std::basic_string<E,T,A> result;
  std::basic_string<E,T,A>::size_type pos_before = 0;
  std::basic_string<E,T,A>::size_type pos = 0;
  std::basic_string<E,T,A>::size_type len = pattern.size();
  while( ( pos = source.find( pattern, pos ) ) != std::string::npos ) {
    result.append(source, pos_before, pos - pos_before);
    result.append(placement);
    pos += len ;
    pos_before = pos ;
  }
  result.append(source, pos_before, source.size() - pos_before) ;
  return result;
}

int main() {
  std::string source    = "すうどんもうどんもうどんのうち" ;
  std::string pattern   = "うどん" ;
  std::string placement = "もも" ;
  // 結果 :               "すもももももももものうち"
  std::cout << replace_all(source,pattern,placement) << std::endl;

  return 0;
}

※ 上記の二つのreplace_allはマルチバイト文字を考慮していません。

 

Xercesが変わった!?

Thursday, April 6th, 2006

はい、Xerces-C 2.x から、かなり大きく変わりました。少なくとも従来のコードを’そのまま’コンパイルできなくなっています。 それ以外にも細かな相異がいくつかあります。

とりあえず昔のままで…

インタフェースが大きく変わってしまったのですが、幸いにも旧いインタフェースは残されています。従来のinclude:

#include <xercesc/dom/DOM.hpp>

は、

#include <xercesc/dom/deprecated/DOM.hpp>

のように .../deprecatedに置かれていますから、#include
の修正だけで’とりあえず’はコンパイルできます。

とはいえ、deprecated には違いないので早めに新たなインタフェースで書き直すことをお薦めします

ICUを使わせるには?

Xerces-CにICUを使わせるには?と全く同じ手順で使えるようになります。すなわち:

ICU
  1. ICU 2.2 を適当なディレクトリに展開し、そのディレクトリ・パスを環境変数 ICU_ROOT に設定しておく。
  2. $(ICU_ROOT)/source/allinone/allinone.dsw で build

    $(ICU_ROOT)/bin に DLL ができるから、PATHを通す。

Xerces-C 2.x
  1. Xerces-C 2.0.0 のソース版を適当なディレクトリに展開する。

  2. (VC++の場合) IDEから Projects/Win32/VC6/xerces-all/xerces-all.dsw を開け、XercesLibをbuild-targetとする。
  3. プロジェクト設定を以下のとおり変更:

    • include-path に $(ICU_ROOT)¥include を追加
    • マクロ XML_USE_WIN32_TRANSCODER 改め XML_USE_ICU_TRANSCODER に
    • library-path に $(ICU_ROOT)¥lib を追加。
    • ライブラリ icuuc.lib (Debug版では icuucd.lib) を追加。
  4. FileView: XercesLib/util/Transcoders にある Win32TransService.(hpp|cpp) を引っこ抜いて、跡地にsrc/xercesc/util/Transcoders/ICU/ICUTransService.(hpp|cpp) を投げ込む。

build!
  1. Build/Win32/VC6/(Release|Debug) に lib/dll ができあがる。

文字コード変換がICUに置き換わったことを確認するには、例えばiso-2022-jp(JIS)で書かれたXMLをパースしてください。

後述する”で、どう変わったのですか?”にあるコードがそれです。

標準C++ライブラリに依存しますか?

はい、依存します。そのために、標準C++ライブラリをたとえばSTLPortなどに取り替えるとなれば、Xerces-C自体も同じ環境で再構築しなければなりません。

Xerces-Cのソースコードを眺めてみたところ、標準C++依存部はたった一箇所でした。

以下に示すのは Xerces-C 2.1.0 の …/src/xercesc/framework/StdOutFormatTarget.cppです:

#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <iostream.h>
#include <stdio.h>

StdOutFormatTarget::StdOutFormatTarget()
{}

StdOutFormatTarget::‾StdOutFormatTarget()
{}

void StdOutFormatTarget::writeChars(const XMLByte* const  toWrite
                                  , const unsigned int    count
                                  , XMLFormatter* const   formatter)
{
        // Surprisingly, Solaris was the only platform on which
        // required the char* cast to print out the string correctly.
        // Without the cast, it was printing the pointer value in hex.
        // Quite annoying, considering every other platform printed
        // the string with the explicit cast to char* below.
    cout.write((char *) toWrite, (int) count);
}

これを以下のように修正し、再構築してください。標準C++依存部分がなくなります:

#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <stdio.h>

StdOutFormatTarget::StdOutFormatTarget()
{}

StdOutFormatTarget::‾StdOutFormatTarget()
{}

void StdOutFormatTarget::writeChars(const XMLByte* const  toWrite
                                  , const unsigned int    count
                                  , XMLFormatter* const   formatter)
{
        // Surprisingly, Solaris was the only platform on which
        // required the char* cast to print out the string correctly.
        // Without the cast, it was printing the pointer value in hex.
        // Quite annoying, considering every other platform printed
        // the string with the explicit cast to char* below.
    fwrite(toWrite, 1, count, stdout);
}

v1.xで文字コードの変換が思わしくないのですが…

どうやらそのようです。v1.5〜v1.7あたりでは、UNICODEからnativeへの変換:DOMString::transcode()の挙動に不審な点が見受けられます。

かわりに XMLString::transcodeをお薦めします。

/*
 * DOMString output を native に変換し、プリントする
 */

  DOMString    output; // UNICODE文字列
  XMLCh wbuffer[32]; // 十分な大きさのバッファ(変換先)
  XMLString::copyNString(wbuffer, output.rawBuffer(), output.length());
  wbuffer[output.length()] = L'¥0';
  char* buffer = XMLString::transcode(wbuffer);
  std::cout << buffer << std::endl;
  delete[] buffer;

v2.xではDOMString自体deprecateされたので、いずれにせよXMLStringを用いたコード変換が主流となるでしょう

で、どう変わったのですか?

従来(v1.x)のDOM_NodeはHandle/Bodyパターンを駆使することによってポインタを排除し、Java-likeなインテフェースを提供していました。しかしこのJava-likeなインタフェースはその実現のためパフォーマンスを落としていました。

v2.xからはこのJava-likeなインタフェースを一掃(deprecate)し、ポインタを’素’で見せる実装に改められました。

加えて従来の文字列 DOMStringもdeprecateされ、UNICODE文字列へのポインタ XMLCh*に改められました。

以下は簡単なXMLドキュメントをパースし、出力するコードです(v1.7.0)

#include <string>
#include <iostream>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/DOMParser.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/util/PlatformUtils.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  {
                      "<country>¥x1b¥x24¥x42¥x46¥x7c¥x4b¥x5c¥x1b¥x28¥x42</country>";

  MemBufInputSource source((const XMLByte*)input.data(), input.size(), "memory_buffer", false);

  DOMParser parser;
  parser.parse(source);

  DOM_Document document = parser.getDocument();
  DOM_Element  element  = document.getDocumentElement();
  DOM_Text     text     = static_cast<DOM_Text&>(element.getFirstChild());

  DOMString    output   = text.getData();
  XMLCh wbuffer[32];
  XMLString::copyNString(wbuffer, output.rawBuffer(), output.length());
  wbuffer[output.length()] = L'¥0';
  char* buffer = XMLString::transcode(wbuffer);
  std::cout << buffer << std::endl;
  delete[] buffer;
  }
  XMLPlatformUtils::Terminate();
  return 0;
}

同じものを v2.1.0 の新 API で書き直すと以下のようになります。

#include <string>
#include <iostream>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/util/PlatformUtils.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  {
                      "<country>¥x1b¥x24¥x42¥x46¥x7c¥x4b¥x5c¥x1b¥x28¥x42</country>";

  MemBufInputSource source((const XMLByte*)input.data(), input.size(), "memory_buffer", false);

  XercesDOMParser parser;
  parser.parse(source);

  DOMDocument* document = parser.getDocument();
  DOMElement*  element  = document->getDocumentElement();
  DOMText*     text     = static_cast<DOMText*>(element->getFirstChild());

  char         buffer[32];
  XMLString::transcode(text->getData(), buffer, 32);
  std::cout << buffer << std::endl;
  }
  XMLPlatformUtils::Terminate();
  return 0;
}

ポインタはdeleteしなくていいのですか?

新APIで定義されたDOMNodeにはメソッドrelease()が定義されており、これを使ってメモリの解放を行ないます

DOMNode* node;
node->release();

このメソッドはノードの子ノードを再帰的にreleaseします

ただし、パーサからgetDocument()で取り出したドキュメント、すなわちDOMNodeは、パーサのデストラクトと同時にreleaseされます。DOMNodeを温存したままパーサだけを先に廃棄するときは、パーサのメソッドadoptDocumentでドキュメントを取り出してください。

  DOMDocument* document;
  {
    XercesDOMParser parser;
    parser.parse(...);
    document = parser.adoptDocument();
  }
  ...
  document->release();

DOMNodeをファイルに出力するには?

DOMWriter が装備されました。使用例を以下に示します。

#include <iostream>
#include <locale>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMWriter.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/util/XMLUni.hpp>

int main() {

  XMLPlatformUtils::Initialize();
  std::locale::global(std::locale("japanese"));

  DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(L"LS");
  DOMDocument* doc = impl->createDocument(0, L"会社",0);
  DOMElement* rootElem = doc->getDocumentElement();
  DOMElement*  prodElem = doc->createElement(L"製品");
  rootElem->appendChild(prodElem);
  DOMText*    prodDataVal = doc->createTextNode(L"Xerces-C");
  prodElem->appendChild(prodDataVal);
  DOMElement*  catElem = doc->createElement(L"品種");
  rootElem->appendChild(catElem);
  catElem->setAttribute(L"アイデア", L"お見事");
  DOMText*    catDataVal = doc->createTextNode(L"XMLパーサ");
  catElem->appendChild(catDataVal);
  DOMElement*  devByElem = doc->createElement(L"作ったひと");
  rootElem->appendChild(devByElem);
  DOMText*    devByDataVal = doc->createTextNode(L"あぱっち");
  devByElem->appendChild(devByDataVal);

  DOMWriter* writer = ((DOMImplementationLS*)impl)->createDOMWriter();
  writer->setEncoding(L"shift_jis");
  StdOutFormatTarget target;
  writer->writeNode(&target, *doc);
  delete writer;

  doc->release();
  XMLPlatformUtils::Terminate();
  return 0;
}

この例では標準出力に出力しています。

ファイルに出力するには、StdOutFormatTargetLocalFileFormatTargetに置き換えてください。

ICUの文字コード変換を使いたいのですが…

Thursday, April 6th, 2006

文字コードは日本に生を受けたプログラマの悩みのタネです。UNIX-serverとWindows-clientとの間でメッセージのやりとりを行うとき、そのメッセージがアルファベット(ASCII)だけなら何の問題もないのですが、漢字や仮名を含んでいる場合は片方が使っている文字コードを他方が使っている文字コードに変換してあげないと’文字化け’が生じます。また、SMTP/POP3などを介した電子メールなどではJISコード(iso-2022-jp)との相互変換が必要となります。さらにはXMLではUnicodeが使われますから…

IBMのOpenSourceプロジェクト’ICU’は世界中で使われている数多くの文字コードとUnicodeとの相互変換を提供するライブラリです。’ICU’による文字コード変換を、現在最も広く用いられている2つのencoding:euc-jpとshift_jisとの文字コードの変換を例に解説します。

※ICUの文字コード変換にはC/C++の双方に対応したAPIが用意されています。C++-APIはC-APIにかぶせた薄いwrapperですから、ここではC-APIについて解説します。

※ ここでは文字コード変換に必要な最小限のAPIの解説にとどめます。詳細はICUのドキュメントを参照してください。

前準備

ucnv.h : 文字コード変換APIのヘッダ

文字コード変換に必要なAPIを使うのに#includeしなければならないヘッダはucnv.hただひとつです。ソースコードの先頭付近に:

#include <unicode/ucnv.h>

を書きましょう。’ucnv_’で始まる文字コード変換APIのプロトタイプはすべてこの ucnv.h で宣言されています。

UErrorCode : エラーコード

文字コード変換APIはほとんどがその引数に UErrorCodeへのポインタを与えます。処理中に発生したエラーはここに書き込まれます。

//  例:
UErrorCode error = U_ZERO_ERROR; // [1]
UConverter* conv = ucnv_open("shift_jis", &error);
if ( U_FAILURE(error) ) { // [2]
  cout >> u_errorName(error) >> endl; // [3]
  return;
}
  • [1]各関数の呼び出しに先だって必ずエラーコードを U_ZERO_ERROR に設定しておいてください。
  • [2]エラーの有無はマクロ U_SUCCESS / U_FAILURE で検出できます。
  • [3]エラーコードの文字列表現は関数 u_errorName() で得られます。

UConverter の生成と解放

文字コード変換はUConverterucnv_から始まる関数の組み合わせによって行います。

UConverter の生成と使用後の解放には、それぞれ ucnv_open() / ucnv_close() を呼び出します。

ucnv_open :UConverterの生成

UConverter* ucnv_open(const char* converterName, UErrorCode* err)

converterName : 変換の対象となる文字コード名、たとえば”shift_jis”,”euc-jp”,”iso-2022-jp”を指定します。

大文字/小文字は区別されません。また’-',’_',’ ‘(ダッシュ,アンダースコア,スペース)も無視されます。

*err : エラーコードが設定されます。

戻り値 : Unicodeコンバータ(エラー時には0)

ucnv_close :UConverterの解放

void ucnv_close(UConverter* converter)

converter : 解放するUnicodeコンバータ

対Unicode変換

ICUの文字コード変換は任意の文字コードで表現されたマルチバイト文字列とUnicodeとの相互変換を提供しています。

マルチバイト文字列 X から Y への相互変換は:

  1. X から Unicodeへ(ucnv_toUnicode)
  2. Unicode から Yへ(ucnv_fromUnicode)

の2つのステップを踏むことになります。

※ マルチバイト文字列相互の変換API ucnv_convertがありますが、パフォーマンスが若干落ちるのでお薦めしません。

ucnv_toUnicode : マルチバイト文字列をUnicode文字列へ変換
void ucnv_toUnicode(UConverter*  converter,
                      UChar**      target,
                      const UChar* targetLimit,
                      const char** source,
                      const char*  sourceLimit,
                      int32_t*     offsets,
                      UBool        flush,
                      UErrorCode*  err)

converter : Unicodeコンバータ

*target : 出力バッファの先頭

targetLimit : 出力バッファの上限

*source : 入力バッファの先頭

sourceLimit : 入力バッファの上限

offsets : 常に0

flush : 入力シーケンスの終端であればTRUE

*err : エラーコード

ucnv_fromUnicode : Unicode文字列をマルチバイト文字列へ変換
void ucnv_fromUnicode(UConverter*  converter,
                        UChar**      target,
                        const UChar* targetLimit,
                        const char** source,
                        const char*  sourceLimit,
                        int32_t*     offsets,
                        UBool        flush,
                        UErrorCode*  err)

converter : Unicodeコンバータ

*target : 出力バッファの先頭

targetLimit : 出力バッファの上限

*source : 入力バッファの先頭

sourceLimit : 入力バッファの上限

offsets : 常に0

flush : 入力シーケンスの終端であればTRUE

*err : エラーコード

ucnv_toUnicode/ucnv_fromUnicode いずれの場合も入力/出力バッファの先頭位置の与え方が特殊なので注意してください。

入力/出力バッファの先頭位置のポインタを与えます。ここで与えた引数は入力/出力バッファの位置を与えるだけでなく、変換後の位置すなわち入力バッファをどこまで読んだか/出力バッファにどこまで書き込んだかを返すのに用いられるからです。

たとえば:

UConverter* converter;
char        in[IN];
wchar_t     out[OUT];
const char* source = in;
const char* sourceLimit = in + IN;
wchar_t *   target = out;
wchar_t *   targetLimit = out + OUT;
UErrorCode  err = U_ZERO_ERROR;
ucnv_toUnicode(converter, &target, targetLimit, &source, sourceLimit, 0, TRUE, &err);

上記のコードを実行すると、ucnv_toUnicode実行後、inからsource直前までのマルチバイト文字がoutからtarget直前までのUnicode文字に変換されたことになります。

引数flushは、一連の文字列を一気に変換できない場合にFALSEを設定します。flushFALSEであるとき、入力末尾の変換状態がUConverter内に保持され、後に続く変換時に使われます。この機能によって、入力マルチバイト文字列が2バイト文字の途中で途切れていた場合でも正しく変換できます。

バッファの大きさ

ucnv_toUnicode /ucnv_fromUnicode でマルチバイト文字列/Unicode相互の変換を行う時、変換結果を格納するバッファの大きさは、以下のように求めることができます。

マルチバイトからUnicode

関数 ucnv_getMinCharSize(UConverter* converter) が、converterに指定された文字コードにおいて、1文字を表現するのに必要な最小のバイト数を返します。

したがって、入力されるマルチバイト文字列のバイト数をucnv_getMinCharSize(converter)で割れば、出力Unicodeバッファに必要な文字数が得られます。

Unicodeからマルチバイト

関数 ucnv_getMaxCharSize(UConverter* converter) が、converterに指定された文字コードにおいて、1文字を表現するのに必要な最大のバイト数を返します。

したがって、入力されるUnicode文字列の文字数にucnv_getMaxCharSize(converter)を掛ければ、出力マルチバイト文字列バッファに必要な文字数が得られます。

サンプル

では、ICUの文字コード変換APIでeuc-jpで書かれた”日本”をshift_jisに変換してみましょう。Windows環境下であれば、以下のコードをコンパイル/実行すれば “日本” と出力されます。

/*
 * IBM ICU 1.8.1 による euc-jp から shift_jis への変換
 */

#include <unicode/ucnv.h>
#include <iostream>

using namespace std;

int main() {

  UErrorCode error = U_ZERO_ERROR;

  UConverter* sourceConverter = ucnv_open("euc-jp",&error); // UConverterの生成
  if ( U_FAILURE(error) ) {
    cerr << u_errorName(error);
    return 1;
  }

  const char  euc_jp[]     = "¥xC6¥xFC¥xCB¥xDC"; // "日本"のeuc-jp表現
  const char* source       = euc_jp; // 入力の先頭位置
  size_t      sourceLength = 4;      // 入力の長さ(バイト数)

  size_t      targetLength ;

  // 出力(Unicode)に必要な文字数
  targetLength = sourceLength / ucnv_getMinCharSize(sourceConverter);
  wchar_t* wbuffer = new wchar_t[targetLength]; // 出力領域の確保
  wchar_t* wtarget = wbuffer;

  // マルチバイト文字列(euc-jp) から Unicode へ変換
  ucnv_toUnicode(sourceConverter,
                 &wtarget, wtarget + targetLength,
                 &source,  source  + sourceLength,
                 0, TRUE, &error);
  if ( U_FAILURE(error) ) {
    delete[] wbuffer;
    cerr << u_errorName(error);
    return 1;
  }
  ucnv_close(sourceConverter); // UConverterの開放

  char shift_jis[10]; // 変換結果をここに出力

  UConverter* targetConverter = ucnv_open("shift_jis",&error); // UConverterの生成
  if ( U_FAILURE(error) ) {
    cerr << u_errorName(error);
    return 1;
  }

  // 出力(shift-jis)に必要なバイト数
  targetLength = (wtarget - wbuffer) * ucnv_getMaxCharSize(targetConverter);
  if ( targetLength >= 10 ) {
    cerr << "not enougth room to convert!" << endl;
    return 1;
  }

  char*          buffer  = shift_jis;
  const wchar_t* wsource = wbuffer;   // 入力(Unicode)の先頭位置
  char*          target  = buffer;    // 出力(shift_jis)の先頭位置

  // Unicode から マルチバイト文字列(shift_jis) へ変換
  ucnv_fromUnicode(targetConverter,
                   &target,  target + targetLength,
                   &wsource, wtarget,
                   0, TRUE, &error);
  delete[] wbuffer; // 使用済みバッファの開放
  if ( U_FAILURE(error) ) {
    cerr << u_errorName(error);
    return 1;
  }
  ucnv_close(targetConverter); // UConverterの開放

  *target = '¥0';
  cout << shift_jis << endl;
  return 0;

}

もっと簡単に…

文字コード変換API ucnv_...()は極めてプリミティブなAPIであるためその挙動を細かく制御することができますが、それだけ複雑な呼び出しとなっています。文字コード変換をもっと簡単に行うために、出力バッファの確保や引数の設定を行う’薄いラッパ’の実装を試みました。これによって:

  • widen : マルチバイト -> Unicode
    • const char* -> std::wstring
    • std::string -> std::wstring
  • narrow : Unicode -> マルチバイト
    • const wchar_t* -> std::string
    • std::wstring -> std::string
  • convert : マルチバイト -> マルチバイト
    • const char_t* -> std::string
    • std::string -> std::string

の各変換が非常にシンプルな呼び出しで利用できます。使い方(インタフェース)はヘッダ converter.h を参照してください。

converter.h

#ifndef __CONVERTER_H__
#define __CONVERTER_H__

#include <exception>
#include <string>

struct UConverter;
enum   UErrorCode;

namespace s34 {

  /*
   * 'icu_error' exception
   */
  class icu_error : public std::exception {
    UErrorCode error_;
  public:
    explicit icu_error(UErrorCode error) : error_(error) {}
    virtual const char* what() const;
    UErrorCode code() const { return error_; }
  };

  /*
   * 'open_converter'
   */
  UConverter* open_converter(const char* name, UErrorCode* error =0);

  /*
   * 'close_converter'
   */
  void        close_converter(UConverter*);

  /*
   * 'widen' : multibyte -> doublebyte
   */
  std::wstring
  widen(const char* source,
        size_t      sourceLength,
        UConverter* converter,
        bool             flush =true,
        UErrorCode* error = 0
       );

  inline
  std::wstring
  widen(const char* source,
        UConverter* converter,
        bool             flush =true,
        UErrorCode* error =0
       )
    { return widen(source, std::char_traits<char>::length(source),
                   converter, flush, error); } 

  inline
  std::wstring
  widen(const char* source,
        const char* sourceLimit,
        UConverter* converter,
        bool        flush =true,
        UErrorCode* error =0
       )
    { return widen(source, sourceLimit - source,
                   converter, flush, error); } 

  inline
  std::wstring
  widen(const std::string& source,
        UConverter*        converter,
        bool               flush =true,
        UErrorCode*        error =0
       )
    { return widen(source.data(), source.length(),
                   converter, flush, error); }

  /*
   * 'narrow' : doublebyte -> multibyte
   */
  std::string
  narrow(const wchar_t* source,
         size_t         sourceLength,
         UConverter*    converter,
         bool           flush =true,
         UErrorCode*    error =0
        );

  inline
  std::string
  narrow(const wchar_t* source,
         UConverter*    converter,
         bool           flush =true,
         UErrorCode*    error =0
        )
    { return narrow(source, std::char_traits<wchar_t>::length(source),
                    converter, flush, error); }

  inline
  std::string
  narrow(const wchar_t* source,
         const wchar_t* sourceLimit,
         UConverter*    converter,
         bool           flush =true,
         UErrorCode*    error =0
        )
    { return narrow(source, sourceLimit - source,
                    converter, flush, error); }

  inline
  std::string
  narrow(const std::wstring& source,
         UConverter*         converter,
         bool                flush =true,
         UErrorCode*         error =0
        )
    { return narrow(source.data(), source.length(),
                    converter, flush, error); }

  /*
   * 'convert' : multibyte -> multibyte
   */
  std::string
  convert(const char* source,
          size_t      sourceLength,
          UConverter* sourceConverter,
          UConverter* targetConverter,
          bool        flush =true,
          UErrorCode* error =0
         );

  inline
  std::string
  convert(const char* source,
          UConverter* sourceConverter,
          UConverter* targetConverter,
          bool        flush =true,
          UErrorCode* error =0
         )
    { return convert(source, std::char_traits<char>::length(source),
                     sourceConverter, targetConverter, flush, error); }

  inline
  std::string
  convert(const char* source,
          const char* sourceLimit,
          UConverter* sourceConverter,
          UConverter* targetConverter,
          bool        flush =true,
          UErrorCode* error =0
         )
    { return convert(source, sourceLimit - source,
                     sourceConverter, targetConverter, flush, error); }

  inline
  std::string
  convert(std::string& source,
          UConverter*  sourceConverter,
          UConverter*  targetConverter,
          bool         flush =true,
          UErrorCode*  error =0
         )
    { return convert(source.data(), source.length(),
                     sourceConverter, targetConverter, flush, error); }

}

#endif

converter.cpp

#include <unicode/ucnv.h>
#include "converter.h"

namespace s34 {

  const char* icu_error::what() const { return u_errorName(error_); }

  /*
   * make_converter
   */
  UConverter*
  open_converter(const char* name, UErrorCode* error) {
    UErrorCode  innerError = U_ZERO_ERROR;
    UErrorCode* perror = error ? error : &innerError;
    UConverter* converter = ucnv_open(name, perror);
    if ( U_FAILURE(*perror) && !error )
      throw icu_error(innerError);
    return converter;
  }

  void
  close_converter(UConverter* converter) {
    ucnv_close(converter);
  }

  /*
   * widen : multibyte --> wide
   */
  std::wstring
  widen(const char* source,
        size_t      sourceLength,
        UConverter* converter,
        bool        flush,
        UErrorCode* error
       ) {

    const size_t poolLength = 256;
    wchar_t      pool[poolLength];
    size_t       targetLength = sourceLength / ucnv_getMinCharSize(converter);
    wchar_t*     buffer = ( targetLength > poolLength ) ? new wchar_t[targetLength] : pool;
    wchar_t*     target = buffer;
    UErrorCode   innerError = U_ZERO_ERROR;
    UErrorCode*  perror = error ? error : &innerError;
    ucnv_toUnicode(converter,
                   &target, target + targetLength,
                   &source, source + sourceLength,
                   0, flush, perror);
    if ( U_FAILURE(*perror) && !error ) {
      if ( buffer != pool ) delete[] buffer;
      throw icu_error(innerError);
    }
    std::wstring result(buffer,target);
    if ( buffer != pool ) delete[] buffer;
    return result;
  }

  /*
   * narrow : wide --> multibyte
   */
  std::string
  narrow(const wchar_t* source,
         size_t         sourceLength,
         UConverter*    converter,
         bool           flush,
         UErrorCode*    error
        ) {

    const size_t   poolLength = 256;
    char           pool[poolLength];
    size_t         targetLength = sourceLength * ucnv_getMaxCharSize(converter);
    char*          buffer = ( targetLength > poolLength ) ? new char[targetLength] : pool;
    char*          target = buffer;
    UErrorCode   innerError = U_ZERO_ERROR;
    UErrorCode*  perror = error ? error : &innerError;
    ucnv_fromUnicode(converter,
                     &target, target + targetLength,
                     &source, source + sourceLength,
                     0, flush, perror);
    if ( U_FAILURE(*perror) && !error ) {
      if ( buffer != pool ) delete[] buffer;
      throw icu_error(innerError);
    }
    std::string result(buffer, target);
    if ( buffer != pool ) delete[] buffer;
    return result;
  }

  /*
   * convert : multibyte --> multibyte
   */
  std::string
  convert(const char* source,
          size_t      sourceLength,
          UConverter* sourceConverter,
          UConverter* targetConverter,
          bool        flush,
          UErrorCode* error
         ) {

    const size_t poolLength = 256;
    size_t       targetLength;

    targetLength = sourceLength / ucnv_getMinCharSize(sourceConverter);
    wchar_t  wpool[poolLength];
    wchar_t* wbuffer = ( targetLength > poolLength ) ? new wchar_t[targetLength] : wpool;
    wchar_t* wtarget = wbuffer;
    UErrorCode   innerError = U_ZERO_ERROR;
    UErrorCode*  perror = error ? error : &innerError;
    ucnv_toUnicode(sourceConverter,
                   &wtarget, wtarget + targetLength,
                   &source,  source  + sourceLength,
                   0, flush, perror);
    if ( U_FAILURE(*perror) ) {
      if ( wbuffer != wpool ) delete[] wbuffer;
      if ( !error ) throw icu_error(innerError);
      return std::string();
    }

    targetLength = (wtarget - wbuffer) * ucnv_getMaxCharSize(targetConverter);
    char     pool[poolLength];
    char*    buffer = ( targetLength > poolLength ) ? new char[targetLength] : pool;
    const wchar_t* wsource = wbuffer;
    char*    target = buffer;
    ucnv_fromUnicode(targetConverter,
                     &target,  target + targetLength,
                     &wsource, wtarget,
                     0, flush, perror);
    if ( U_FAILURE(*perror) && !error ) {
      if ( wbuffer != wpool ) delete[] wbuffer;
      if ( buffer != pool ) delete[] buffer;
      throw icu_error(innerError);
    }
    std::string result(buffer, target);

    if ( wbuffer != wpool ) delete[] wbuffer;
    if ( buffer != pool ) delete[] buffer;
    return result;

  }

}

サンプル (tiral.cpp)

#include <iostream>
#include "converter.h"

int main() {
  std::string euc_jp = "¥xC6¥xFC¥xCB¥xDC";
  try {
    UConverter* sourceConverter = s34::open_converter("euc-jp");
    UConverter* targetConverter = s34::open_converter("shift_jis");
    std::cout << s34::convert(euc_jp, sourceConverter, targetConverter) << std::endl;
    s34::close_converter(sourceConverter);
    s34::close_converter(targetConverter);
  } catch ( s34::icu_error& ex ) {
    std::cerr << ex.what() << std::endl;
  }
  return 0;
}

実行結果

日本

Xercesではやれませんか?

Thursday, April 6th, 2006

iPEXはインフォテリアが開発した国産XMLパーサで、開発バージョンは無償で提供されています。が、iPEXを使ったアプリケーションの再販ライセンスは有償で、それもかなりお高い…

フリーのXMLパーサ:Xerces C++を提供するApache(http://xml.apache.org/)はXMLパーサのC++実装では定評があり、Xerces C++との相性抜群なXSLプロセッサXalan C++ 1.2を使って書き直してみました。

/*
 * read_schema
 *
 *  by the use of:
 *    - Rogue Wave : SourcePro DB 4.2
 *    - Apache     : Xerces 1.5.1 & Xalan 1.2
 *
 *  build type : 'm'  -- native-StdLib / multithread / shared / release
 *               'md' -- native-StdLib / multithread / shared / debug
 */

#ifdef _MSC_VER
#pragma warning( disable: 4251 4786 )
#endif

// stdlib
#include <iostream>
#include <locale>
#include <sstream>

// Rogue Wave SourcePro DB
#define RW_DISABLE_DEPRECATED
#include <rw/db/dbmgr.h>  // RWDBManager
#include <rw/db/dbase.h>  // RWDBDatabase
#include <rw/db/schema.h> // RWDBSchema
#include <rw/db/table.h>  // RWDBTable
#include <rw/db/reader.h> // RWDBReader
#include <rw/db/row.h>    // RWDBRow

// Xerces
#include <util/PlatformUtils.hpp>
#include <dom/DOM.hpp>
#include <parsers/DOMParser.hpp>

// Xalan
#include <XalanTransformer/XalanTransformer.hpp>
#include <XSLT/XSLTResultTarget.hpp>

/*
 * access-lib の設定。(ここでは MS SQL Server)
 */
#ifdef RWDEBUG
#define SERVER "msq42-md.dll"
#else
#define SERVER "msq42-m.dll"
#endif

// DOMString(Unicode) -> RWCString(Multibyte)
inline RWCString narrow(const DOMString& str) {
  return RWWString(str.rawBuffer(), str.length()).toMultiByte();
}

/*
 * main routine
 */
int main(int argc, char* argv[]) {

  // localeを'日本語'に設定
  std::locale::global(std::locale("japanese"));

  // Xerces / Xalan の初期化
  XMLPlatformUtils::Initialize();
  XalanTransformer::initialize();

  // XMLをパース
  DOMParser parser;
  parser.parse(argv[1]);

  // ルート・エレメント(schema)を取得
  DOM_Element schema = parser.getDocument().getDocumentElement();

  // データベースに接続
  RWCString dsn = narrow(schema.getAttribute("dsn"));
  RWCString usr = narrow(schema.getAttribute("usr"));
  RWCString pwd = narrow(schema.getAttribute("pwd"));
  RWCString db  = narrow(schema.getAttribute("db"));
  RWDBDatabase database = RWDBManager::database(SERVER, dsn, usr, pwd, db);
  if ( database.isValid() ) {
    std::cerr << database.version() << std::endl;
  } else {
    std::cerr << "can't connect to a database...¥n";
    return 1;
  }

  const char* valueType [] = {
    "NoType",       "Char",    "UnsignedChar",  "Tiny",
    "UnsignedTiny", "Short",   "UnsignedShort", "Int",
    "UnsignedInt",  "Long",    "UnsignedLong",  "Float",
    "Double",       "Decimal", "Date",          "DateTime",
    "Duration",     "String",  "Blob",          "WString"
    "MBString"
  };

  RWDBConnection connection = database.connection();

  // データベース内の全テーブルを取得
  RWDBTable  dbtables = database.dbTables(connection, RWDBDatabase::UserTable);

  std::stringstream xml;
  xml.imbue(std::locale::classic());

  // <schema dsn=... ></schema>
  xml << "<schema dsn='" << dsn << "' usr='" << usr << "' pwd='" << pwd << "' db='" << db << "'>¥n";

  // 各テーブルに対し...
  for ( RWDBReader dbreader = dbtables.reader(); dbreader(); ) {
    RWDBRow dbrow;
    dbreader >> dbrow;

    // スキーマの読み出し
    RWDBTable dbtable = database.table(dbrow[0].asString());
    dbtable.fetchSchema(connection);
    RWDBSchema dbschema = dbtable.schema();

    // <table name="テーブル名">...</table>
    xml << "  <table name='" << dbrow[0].asString() << "'>¥n";

    // スキーマの各カラムに対し...
    for ( size_t i = 0; i < dbschema.entries(); ++i ) {
      RWDBColumn dbcolumn = dbschema.column(i);

      // <column name="カラム名" 属性名="属性値" ... />
      xml << "    <column name='" << dbcolumn.name() << "' ";

      if ( dbcolumn.type() != RWDBValue::NoType )      // type
        xml << "type='" << valueType[dbcolumn.type()] << "' ";
      if ( dbcolumn.storageLength() != RWDB_NO_TRAIT ) // size
        xml << "size='" << dbcolumn.storageLength() << "' ";
      if ( dbcolumn.precision() != RWDB_NO_TRAIT )     // precision
        xml << "precision='" << dbcolumn.precision() << "' ";
      if ( dbcolumn.scale() != RWDB_NO_TRAIT )         // scale
        xml << "scale ='" << dbcolumn.scale() << "' ";
      xml << "/>¥n";
    }
    xml << "  </table>¥n";
  }
  xml << "</schema>¥n";

  if ( argc > 2 ) {
    // スタイルシート(XSL)
    XSLTInputSource xsl(argv[2]);
    // 標準出力を出力先に設定
    XSLTResultTarget out(std::cout);
    // XSLによるtransform
    XalanTransformer transformer;
    transformer.transform(&xml,xsl,out);
  } else {
    std::cout << xml.str() << std::flush;
  }
  // 後始末
  XalanTransformer::terminate();
  XMLPlatformUtils::Terminate();

  return 0;
}

iPEX2.1だとコンパイル・エラーになります

Thursday, April 6th, 2006

そうなんですよ、iPEXはバージョン2.1でインタフェースが変更されました。

以前の版との大きな違いのひとつは、名前空間が細分化されたことです。iPEXが提供するクラスは名前空間’iPEX’に置かれていました。v2.1では名前空間’iPEX’のさらにその中に機能ごとの名前空間、たとえば’DOM’や’XSLT’を配置しています。たとえば従来のiPEX::Nodeはv2.1ではiPEX::DOM::Nodeに変更されています。

※ その他にもv2.1での変更点がかなりあります。

詳細は iPEX Version 2.1 プログラマーズガイド 8. 過去のバージョンとの互換性 を参照してください。

iPEX v2.1で実装した版を以下に示します:

/*
 * read_schema
 *
 *  by the use of:
 *    - Rogue Wave : SourcePro DB
 *    - Infoteria  : iPEX 2.1
 *
 *  build type : 'm'  -- native-StdLib / multithread / shared / release
 *               'md' -- native-StdLib / multithread / shared / debug
 */

#ifdef _MSC_VER
#pragma warning( disable: 4251 4786 )
#endif

// stdlib
#include <iostream>
#include <string>
#include <locale>

// Rogue Wave SourcePro DB
#define RW_DISABLE_DEPRECATED
#include <rw/db/dbmgr.h>  // RWDBManager
#include <rw/db/dbase.h>  // RWDBDatabase
#include <rw/db/schema.h> // RWDBSchema
#include <rw/db/table.h>  // RWDBTable
#include <rw/db/reader.h> // RWDBReader
#include <rw/db/row.h>    // RWDBRow

// Infoteria iPEX
#include <ipexdom.h> // iPEX::DOM
#include <ipexslt.h> // iPEX::XSLT

/*
 * access-lib の設定。(ここでは MS SQL Server)
 */
#ifdef RWDEBUG
#define SERVER "msq42-md.dll"
#else
#define SERVER "msq42-m.dll"
#endif

/*
 * string conversions
 */
// UNIXの場合 L"euc-jp"
const std::wstring native = L"shift_jis";

// string(native) -> wstring(unicode)
inline std::wstring wide(const std::string& str) {
  return iPEX::Character::toWCString(str.c_str(), native);
}

// wstring(unicode) -> string(native)
inline std::string narrow(const std::wstring& str) {
  return iPEX::Character::toMBString(str.c_str(), native);
}

/*
 * ErrorHandler : reports erross/warnings
 */
class ErrorHandler : public iPEX::DOM::DOMErrorHandler {
  void report(Code code, const iPEX::DOM::Node* node, unsigned int line, const std::wstring& hint) {
    std::wcerr << getURI() << L"(" << line << L"): Error code="
               << code << L" hint=¥"" << hint << L"¥"" << std::endl;
  }
public:
  void fatalError(Code code, const iPEX::DOM::Node* node, unsigned int line, const std::wstring& hint)
    { report(code, node, line, hint) ; }
  void error(Code code, const iPEX::DOM::Node* node, unsigned int line, const std::wstring& hint)
    { report(code, node, line, hint) ; }
  void warning(Code code, const iPEX::DOM::Node* node, unsigned int line, const std::wstring& hint)
    { report(code, node, line, hint) ; }
};

/*
 * main routine
 */
int main(int argc, char* argv[]) {

  // iPEX初期化
  iPEX::Init init;
  iPEX::StringBufferManager sbm;

  // shift_jis/iso-2022-jp/euc-jp の各encodingをサポート
  static iPEX::CP932ConverterFactory     f_SJIS(L"shift_jis");
  static iPEX::ISO2022JPConverterFactory f_JIS(L"iso-2022-jp");
  static iPEX::EUCJPConverterFactory     f_EUC(L"euc-jp");

  // localeを'日本語'に設定
  std::locale::global(std::locale("japanese"));

  // parse-error を出力するハンドラ
  ErrorHandler error_handler;

  // argv[1]で与えられたURLに基づいて読み込み
  iPEX::Stream::InputStreamByURL in_strm(wide(argv[1]));
  iPEX::DOM::XMLReader reader(&in_strm, iPEX::DOM::Reader::NORMALIZE, &error_handler);

  // XMLをパース
  iPEX::DOM::Document* document = iPEX::DOM::IPEXDocument::createDocumentObject(sbm);
  if ( !reader.read(document) ) {
    return 1;
  }

  // ルート・エレメントを取得
  iPEX::DOM::Element* schema = document->getDocumentElement();

  // データベースに接続
  RWDBDatabase database = RWDBManager::database(SERVER,
                            narrow(schema->getAttribute(L"dsn")).c_str(),
                            narrow(schema->getAttribute(L"usr")).c_str(),
                            narrow(schema->getAttribute(L"pwd")).c_str(),
                            narrow(schema->getAttribute(L"db")).c_str());
  if ( database.isValid() ) {
    std::cerr << database.version() << std::endl;
  } else {
    std::cerr << "can't connect to a database...¥n";
    return 1;
  }

  const wchar_t* valueType [] = {
    L"NoType",       L"Char",    L"UnsignedChar",  L"Tiny",
    L"UnsignedTiny", L"Short",   L"UnsignedShort", L"Int",
    L"UnsignedInt",  L"Long",    L"UnsignedLong",  L"Float",
    L"Double",       L"Decimal", L"Date",          L"DateTime",
    L"Duration",     L"String",  L"Blob",          L"WString"
    L"MBString"
  };

  RWDBConnection connection = database.connection();

  // データベース内の全テーブルを取得
  RWDBTable  dbtables = database.dbTables(connection, RWDBDatabase::UserTable);

  // 各テーブルに対し...
  for ( RWDBReader dbreader = dbtables.reader(); dbreader(); ) {
    RWDBRow dbrow;
    dbreader >> dbrow;

    // スキーマの読み出し
    RWDBTable dbtable = database.table(dbrow[0].asString());
    dbtable.fetchSchema(connection);
    RWDBSchema dbschema = dbtable.schema();

    // <table name="テーブル名">...</table>
    iPEX::DOM::Element* table = document->createElement(L"table");
    table->setAttribute(L"name", dbrow[0].asWString().data());

    // <table> を <schema> に追加
    schema->appendChild(table);

    // スキーマの各カラムに対し...
    for ( size_t i = 0; i < dbschema.entries(); ++i ) {
      RWDBColumn dbcolumn = dbschema.column(i);
      wchar_t number[16];

      // <column name="カラム名" 属性名="属性値" ... />
      iPEX::DOM::Element* column = document->createElement(L"column");
      column->setAttribute(L"name", wide(dbcolumn.name().data()));

      if ( dbcolumn.type() != RWDBValue::NoType )      // type
       column->setAttribute(L"type", valueType[dbcolumn.type()]);
      if ( dbcolumn.storageLength() != RWDB_NO_TRAIT ) // size
        column->setAttribute(L"size", _itow(dbcolumn.storageLength(), number, 10));
      if ( dbcolumn.precision() != RWDB_NO_TRAIT )     // precision
        column->setAttribute(L"precision", _itow(dbcolumn.precision(), number, 10));
      if ( dbcolumn.scale() != RWDB_NO_TRAIT )         // scale
        column->setAttribute(L"scale", _itow(dbcolumn.scale(), number, 10));
      // <column> を <table> に追加
      table->appendChild(column);
    }
  }

  // 標準出力を出力先に設定
  iPEX::Stream::OutputStreamByFile out_strm(iPEX::Stream::OutputStreamByFile::STDOUT, native);

  if ( argc < 3 ) {
    // XMLをプリント
    iPEX::DOM::XMLWriter writer(&out_strm,L"",true);
    writer.write(document);
  } else {
    // XSLによるtransform
    iPEX::Stream::InputStreamByURL in_strm(wide(argv[2]));
    iPEX::XSLT::StreamCreator creator(sbm);
    iPEX::XSLT::Processor* processor = creator.createProcessor(&in_strm);
    iPEX::XSLT::StreamResultHandler* result = iPEX::XSLT::StreamResultHandler::create(out_strm);
    processor->process(document, *result);
    delete result;
    delete processor;
  }

  delete document;

  return 0;
}

アクションの結果に応じて遷移先を変えたいのです (改訂版)

Thursday, April 6th, 2006

たとえば”ファイルを開く”イベントが起こったらそのアクションとして’fopen()する’んだけど、ファイルのオープンに成功したら’読み込み中’、失敗したら’初期状態’に遷移したい、なんてやつです。ありますよねぇ、そんなこと。

‘門番のいるゲート’の例では’Locked’状態で’Coin’イベントが発生したとき、投入されたコインが偽コインだったらゲートを開けずに’Locked’状態に戻りたい、という場合です。

残念ながら現FSMで記述できる状態遷移は、ある状態下において:

  • 受理しうるイベント
  • その時のアクション
  • 新たな状態(遷移先)

を指定できますが、アクションを実行してみないことには遷移先を決定できない場合の記述ができません。

現FSMの文法を大幅に変更することなく”アクションが遷移先を決定する”ことができないか、検討しました。

…アクションによる遷移先の変更に関する解決法ですが、実装してはみたもののどうにも釈然としないものを感じていました。

FSMの文法を変更せずに解決するため、ありもしない仮想的な状態を定義し、さらにコンテキストが仮想的なイベントを発行しなければならないからです。もっと’素直’な解決法はなかろうかとしばし考えました。

さんざん考えた挙句、

FSM文法に手を加えない限り、素直な表現は無理だろう…

という結論に至りました。

ある状態/イベントの組に対するアクションと遷移先を:

  <State name="Locked" enter="EnterLocked" exit="ExitLocked">
    <Event name="Coin" action="Check" transition="Confirm" />
    <Event name="Pass" action="Alarm" />
  </State>

のようにEvent要素のaction, transition 属性で指定しているのが現在のFSM文法です。

以下のように記述できたら’アクションによる遷移先の変更’が実現できますよね:

  <State name="Locked" enter="EnterLocked" exit="ExitLocked">
    <Event name="Coin">
      <!-- Check の結果が true ならば Unlocked に遷移する -->
      <transit state="Unlocked" if="Check"/>
    </Event>
    <Event name="Pass">
      <!-- Alarm アクションを行う -->
      <execute action="Alarm"/>
    </Event>
  </State>

すなわち、Event の子要素として executetransit を並べることができるようにしようというわけです。

早速この戦術にしたがってFSM文法に変更を加えました。

変更を加えたFSM文法 ’smc.dtd’

<!-- DTD for 'smc' -->

<!ELEMENT fsm (state)* >
<!ATTLIST fsm name    NMTOKEN #REQUIRED>
<!ATTLIST fsm initial NMTOKEN #REQUIRED>

<!ELEMENT state (event)*>
<!ATTLIST state name  NMTOKEN #REQUIRED>
<!ATTLIST state enter NMTOKEN #IMPLIED>
<!ATTLIST state exit  NMTOKEN #IMPLIED>

<!ELEMENT event (execute | transit)* >
<!ATTLIST event name NMTOKEN #REQUIRED>

<!ELEMENT execute EMPTY>
<!ATTLIST execute action NMTOKEN #REQUIRED>

<!ELEMENT transit EMPTY>
<!ATTLIST transit state NMTOKEN #REQUIRED>
<!ATTLIST transit if    NMTOKEN #IMPLIED>

この文法にしたがって’偽コイン検出版ゲート’を記述すると:

新たな文法に基づく ‘gate.xml’

<!DOCTYPE fsm SYSTEM 'smc.dtd'>

<fsm name='Gate' initial='Locked'>

  <state name='Locked' enter='Lock'>
    <event name='Coin'>
      <transit state='Unlocked' if='Confirm'/>
    </event>
    <event name='Pass'>
      <execute action='Alarm'/>
    </event>
  </state>

  <state name='Unlocked' enter='Unlock'>
    <event name='Coin'>
      <execute action='ThankYou'/>
    </event>
    <event name='Pass'>
      <transit state='Locked'/>
    </event>
  </state>

</fsm>

この文法を解釈する’新smc’に’gate.xml’を食わせると、以下のようなヘッダ’gate.h’を吐きます:

生成されたヘッダ ‘gate.h’

#ifndef Gate_fsm_h
#define Gate_fsm_h

namespace Gate {

enum StateCode {
  Locked_state = 1,
  Unlocked_state = 2,
};

enum EventCode {
  Coin_event = 1,
  Pass_event = 2,
};

class Context {
public:
  virtual ‾Context() {}
  virtual void error(StateCode,EventCode) =0;
  virtual bool Confirm() = 0;
  virtual void Alarm() = 0;
  virtual void Lock() = 0;
  virtual void ThankYou() = 0;
  virtual void Unlock() = 0;
};
  ... 省略 ...
}

#endif

transit要素のif属性に指定したConfirmアクションの戻り値がboolになっています。

Contextの導出クラスでtrueあるいはfalseを返してください。

お試しに書いたユーザコードを示します:

お試しコード ‘gate.cpp’

#include <iostream>
#include <string>
#include "gate.h"

using namespace std;

class GateContext : public Gate::Context {
public:
  virtual void error(Gate::StateCode, Gate::EventCode) {}
  virtual bool Confirm();
  virtual void Alarm()         { cout << "金払え!!" << endl; }
  virtual void ThankYou()      { cout << "ありがとうございます" << endl; }
  virtual void Lock()          { cout << "ゲートを閉めます" << endl; }
  virtual void Unlock()        { cout << "ゲートを開けます" << endl; }
};

bool GateContext::Confirm() {
  cout << "コインは本物? [y/n] " << flush;
  string answer;
  cin >> answer;
  return answer[0] == 'y';
}

int main() {

  GateContext context;
  Gate::FiniteStateMachine fsm;
  fsm.setContext(&context);

  while ( true ) {
    cout << "  enter an event Coin / Pass / exit :" << flush;
    string event_name;
    cin >> event_name;
    if ( event_name == "exit" ) break;
    else
    if ( event_name == "Coin" ) fsm.Coin();
    else
    if ( event_name == "Pass" ) fsm.Pass();
    else continue;
  }
  return 0;
}

いかがでしょう、これでずいぶんとスッキリしました。

新版 ’smc.cpp’

/*
 * smc : State Map Compiler
 */

#include <iostream>
#include <iterator>
#include <algorithm>
#include <set>
#include <string>

#include <util/PlatformUtils.hpp>
#include <sax/ErrorHandler.hpp>
#include <sax/SAXParseException.hpp>

#include <dom/DOM.hpp>
#include <parsers/DOMParser.hpp>

/* for ( ... ) 内で宣言した変数を閉じ込めるマクロ */
#define for if ( false ) ; else for

using namespace std;

inline string transcode(const DOMString& domstr) {
  char* cstr = domstr.transcode();
  string result = cstr;
  delete[] cstr;
  return result;
}

/*
 * elementからattribute_nameなるアトリビュート値を取り出す
 */
inline string attr(DOM_Element element, const char* attribute_name) {
  return transcode(element.getAttribute(attribute_name));
}

void generate(ostream& stream, DOM_Document document) {

  set<string>::iterator iter;
  set<string> state_names;
  set<string> event_names;
  set<string> action_names;
  set<string> pred_names;

  DOM_NodeList nodes;
  DOM_Element  element;
  string       value;

  /*
   * ルートエレメント(fsm)を取得する
   */
  DOM_Element fsm = document.getDocumentElement();

  /*
   * 全stateを取り出す
   */
  nodes = fsm.getElementsByTagName("state");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    element = static_cast<DOM_Element&>(nodes.item(i));
    state_names.insert(attr(element,"name"));
  }

  /*
   * 全eventを取り出す
   */
  nodes = fsm.getElementsByTagName("event");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    element = static_cast<DOM_Element&>(nodes.item(i));
    event_names.insert(attr(element,"name"));
  }

  /*
   * 全predicateを取り出す
   */
  nodes = fsm.getElementsByTagName("transit");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    element = static_cast<DOM_Element&>(nodes.item(i));
    value = attr(element,"if");
    if ( !value.empty() ) {
      pred_names.insert(value);
    }
  }

  /*
   * 全actionを取り出す
   */
  nodes = fsm.getElementsByTagName("state");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    element = static_cast<DOM_Element&>(nodes.item(i));
    value = attr(element,"enter");
    if ( !value.empty() && pred_names.find(value) == pred_names.end() ) {
      action_names.insert(value);
    }
    value = attr(element,"exit");
    if ( !value.empty() && pred_names.find(value) == pred_names.end() ) {
      action_names.insert(value);
    }
  }
  nodes = fsm.getElementsByTagName("execute");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    element = static_cast<DOM_Element&>(nodes.item(i));
    value = attr(element,"action");
    if ( !value.empty() && pred_names.find(value) == pred_names.end() ) {
      action_names.insert(value);
    }
  }

  /*
   * 生成!!!
   */
  stream << "#ifndef " << attr(fsm,"name") << "_fsm_h¥n"
            "#define " << attr(fsm,"name") << "_fsm_h¥n¥n"
            "namespace " << attr(fsm,"name") << " {¥n" << endl;

  stream << "enum StateCode {¥n";
  int count = 0;
  for ( iter = state_names.begin(); iter != state_names.end(); ++iter ) {
    stream << "  " << *iter << "_state = " << ++count << ",¥n";
  }
  stream << "};¥n" << endl;

  stream << "enum EventCode {¥n";
  count = 0;
  for ( iter = event_names.begin(); iter != event_names.end(); ++iter ) {
    stream << "  " << *iter << "_event = " << ++count << ",¥n";
  }
  stream << "};¥n" << endl;

  stream << "class Context {¥n"
            "public:¥n"
            "  virtual ‾Context() {}¥n"
            "  virtual void error(StateCode,EventCode) =0;¥n";
  for ( iter = pred_names.begin(); iter != pred_names.end(); ++iter ) {
    stream << "  virtual bool " << *iter << "() = 0;¥n";
  }
  for ( iter = action_names.begin(); iter != action_names.end(); ++iter ) {
    stream << "  virtual void " << *iter << "() = 0;¥n";
  }
  stream << "};¥n¥n";

  stream << "class State {¥n"
            "  friend class FiniteStateMachine;¥n"
            "protected:¥n";
  for ( iter = state_names.begin(); iter != state_names.end(); ++iter ) {
    stream << "  static State* " << *iter << "_();¥n";
  }
  stream << "public:¥n"
            "  virtual void _enter_(Context*) {}¥n"
            "  virtual void _exit_(Context*) {}¥n"
            "  virtual StateCode _code_() =0;¥n";
  for ( iter = event_names.begin(); iter != event_names.end(); ++iter ) {
    stream << "  virtual State* " << *iter << "(Context* ctx) "
              "{ ctx->error(_code_()," << *iter << "_event); return this; }¥n";
  }
  stream << "};¥n¥n";

  nodes = fsm.getElementsByTagName("state");
  for ( int i = 0; i < nodes.getLength(); ++i ) {
    DOM_Element state = static_cast<DOM_Element&>(nodes.item(i));
    stream << "class " << attr(state,"name") << " : public State {¥n"
              "public:¥n";
    value = attr(state,"enter");
    if ( !value.empty() ) {
      stream << "  virtual void _enter_(Context* ctx) { ctx->" << value << "(); }¥n";
    }
    value = attr(state,"exit");
    if ( !value.empty() ) {
      stream << "  virtual void _exit_(Context* ctx) { ctx->" << value << "(); }¥n";
    }
    stream << "  virtual StateCode _code_() { return " << attr(state,"name") << "_state; }¥n";
    for ( DOM_Node node = state.getFirstChild(); node != 0; node = node.getNextSibling() ) {
      DOM_Element event = static_cast<DOM_Element&>(node);
      stream << "  virtual State* " << attr(event,"name") << "(Context* context) {¥n";
      for ( DOM_Node subnode = event.getFirstChild(); subnode != 0; subnode = subnode.getNextSibling() ) {
        DOM_Element action = static_cast<DOM_Element&>(subnode);
        string tag_name = transcode(action.getTagName());
        string condition = attr(action,"if");
        if ( tag_name == "execute" ) {
          stream <<   "    context->" << attr(action,"action") << "();¥n";
        } else
        if ( tag_name == "transit" ) {
          if ( !condition.empty() ) {
            stream << "    if ( context->" << condition << "() ) {¥n";
          } else {
            stream << "    {¥n";
          }
          stream << "      _exit_(context);¥n"
                    "      " << attr(action,"state") << "_()->_enter_(context);¥n"
                    "      return " << attr(action,"state") << "_();¥n"
                    "    }¥n";
        }
      }
      stream << "    return this;¥n"
                "  }¥n";
          }
    stream <<  "};¥n¥n";

    stream << "State* State::" << attr(state,"name") << "_() {¥n"
              "  static " << attr(state,"name") << " state;¥n"
              "  return &state;¥n"
              "}¥n¥n";
  }

  stream << "class FiniteStateMachine {¥n"
            "  Context* context_;¥n"
            "  State*   state_;¥n"
            "public:¥n"
            "  FiniteStateMachine() : context_(0) "
            "{ state_ = State::" << attr(fsm,"initial") << "_(); }¥n"
            "  void setContext(Context* ctx) { context_ = ctx; }¥n"
            "  Context* context() { return context_; }¥n"
            "  StateCode state() { return state_->_code_(); }¥n";
  for ( iter = event_names.begin(); iter != event_names.end(); ++iter ) {
    stream << "  void " << *iter << "() { state_ = state_->" << *iter << "(context()); }¥n";
  }
  stream << "};¥n¥n";

  stream << "}¥n¥n"
            "#endif" << endl;
}

/*
 * Parse中に発生したエラーを出力する
 */
class ErrorReporter : public ErrorHandler {
  bool ok_;
public:
  ErrorReporter() : ok_(true) {}
  ‾ErrorReporter() {}

  void warning(const SAXParseException& toCatch) {}
  void error(const SAXParseException& toCatch) {
    ok_ = false;
    std::cerr << "Error at file ¥"" << transcode(toCatch.getSystemId())
              << "¥", line " << toCatch.getLineNumber()
              << ", column " << toCatch.getColumnNumber()
              << "¥n   Message: " << transcode(toCatch.getMessage())
              << "¥n¥n";
  }

  void fatalError(const SAXParseException& toCatch) {
    ok_ = false;
    std::cerr << "Fatal Error at file ¥"" << transcode(toCatch.getSystemId())
              << "¥", line " << toCatch.getLineNumber()
              << ", column " << toCatch.getColumnNumber()
              << "¥n   Message: " << transcode(toCatch.getMessage())
              << "¥n¥n";
  }

  void resetErrors() {}

  bool ok() const { return ok_; }
};

/*
 * ----- main -----
 */
int main(int argc, char* argv[]) {

  if ( argc != 2 ) {
    cerr << "smc <URL for FSM>" << endl;
    return 1;
  }

  /*
   * XML Parser の生成と初期化
   */
  try {
    XMLPlatformUtils::Initialize();
  } catch ( XMLException& ex ) {
    cerr << transcode(ex.getMessage()) << endl;
    return 1;
  }
  DOMParser parser;
  ErrorReporter error;
  parser.setDoValidation(true);  // 検証を行う
  parser.setErrorHandler(&error); // エラーハンドラを設定
  parser.setIncludeIgnorableWhitespace(false);

  /*
   * 引数に与えられたURLで示されたXMLをロードする
   */
  try {
    parser.parse(argv[1]);
    if ( !error.ok() ) {
      return 1;
    }
  } catch ( XMLException& ex ) {
    cerr << transcode(ex.getMessage()) << endl;
    return 1;
  }

  /*
   * ドキュメントを取得する
   */
  DOM_Document document = parser.getDocument();

  generate(cout, document);

  XMLPlatformUtils::Terminate();

  return 0;
}

アクションの結果に応じて遷移先を変えたいのです

Thursday, April 6th, 2006

たとえば”ファイルを開く”イベントが起こったらそのアクションとして’fopen()するんだけど、ファイルのオープンに成功したら’読み込み中’、失敗したら’初期状態’に遷移したい、なんてやつです。ありますよねぇ、そんなこと。

‘門番のいるゲート’の例では’Locked’状態で’Coin’イベントが発生したとき、投入されたコインが偽コインだったらゲートを開けずに’Locked’状態に戻りたい、という場合です。

残念ながら現FSMで記述できる状態遷移は、ある状態下において:

  • 受理しうるイベント
  • その時のアクション
  • 新たな状態(遷移先)

を指定できますが、アクションを実行してみないことには遷移先を決定できない場合の記述ができません。

現FSMの文法を大幅に変更することなく”アクションが遷移先を決定する”ことができないか、検討しました。

こんな解決法はいかがでしょう。

  • ‘Locked’状態で
    • ‘Coin’イベントが発生したら’判定(Check)’アクションののち’確認(Confirm)’状態に遷移する。
  • ‘確認(Confirm)’状態で
    • ‘本物(realCoin)’イベントが発生したら’ゲートを開ける’アクションののち’Unlocked’状態に遷移する。
    • ‘偽物(fakeCoin)’イベントが発生したら(アクション無しで)’Locked’状態に遷移する。

上記の各状態遷移は、現FSMの文法で記述できます。

Gate.xml

<FSM name="Gate" initial="Locked" error="error">

  <State name="Locked" enter="EnterLocked" exit="ExitLocked">
    <Event name="Coin" action="Check" transition="Confirm" />
    <Event name="Pass" action="Alarm" />
  </State>

  <State name="Confirm">
    <Event name="realCoin" action="Unlock" transition="Unlocked" />
    <Event name="fakeCoin" action="Lock"   transition="Locked"/>
  </State>

  <State name="Unlocked" enter="EnterUnlocked" exit="ExitUnlocked">
    <Event name="Coin" action="ThankYou" />
    <Event name="Pass" action="Lock" transition="Locked" />
  </State>

</FSM>

アクションの中からイベントを発行し、そのイベントが次の状態に遷移した途端に受理されるなら、’判定(Check)’アクションで偽コインか否かを判定し、その結果に従って’本物(realCoin)’あるいは’偽物(fakeCoin)’イベントを発行すればいいことになります。

このように、

  • アクションからイベントを発行できること
  • ある状態に遷移した瞬間、直前のアクションでイベントが発行されていたらそれを受理すること

を盛り込めばいいわけです。

新しいsmcはC++コードを吐きます(Javaコードは…ごめん)。

たとえば smc gate.xml すると、

  • Gate_state.h : enum Gate::state;
  • Gate_event.h : enum Gate::event;
  • Gate_context.h : class Gate::Context;
  • Gate_fsm.h : class Gate::FSM;
  • Gate_impl.cpp : Gate::Context / Gate::FSM の実装

が生成されます。コンテキストをGate::Contextから導出し、仮想関数を再定義して、FSMにsetContextします。

Gate.xml

<?xml-stylesheet type="text/xsl" href="fsm.xsl" media="msie 5.0" ?>

<!DOCTYPE FSM SYSTEM "fsm.dtd">

<FSM name="Gate" initial="Locked" error="error">
  <Note>門番のいるゲート</Note>
  <State name="Locked" enter="EnterLocked" exit="ExitLocked">
    <Note>ゲートが閉じている</Note>
    <Event name="Coin" action="Check" transition="Confirm">
      <Note>不正なコインでないことを確認する</Note>
    </Event>
    <Event name="Pass" action="Alarm">
      <Note>不法侵入</Note>
    </Event>
  </State>

  <State name="Confirm">
    <Event name="realCoin" action="Unlock" transition="Unlocked">
      <Note>ゲートを開ける</Note>
    </Event>
    <Event name="fakeCoin" action="Lock"   transition="Locked"/>
  </State>

  <State name="Unlocked" enter="EnterUnlocked" exit="ExitUnlocked">
    <Note>ゲートが開いている</Note>
    <Event name="Coin" action="ThankYou">
      <Note>チップとして受け取る</Note>
    </Event>
    <Event name="Pass" action="Lock" transition="Locked">
      <Note>ゲートを閉じる</Note>
    </Event>
  </State>

</FSM>

Gate.html (Gate.xml を fsm.xsl で変換したもの)

Gate の状態遷移

門番のいるゲート

初期状態 Locked
異常時のアクション error
  1. 状態 : Locked

    ゲートが閉じている

    • 入場動作 : EnterLocked
    • 退場動作 : ExitLocked
    イベント アクション 遷移先 説明
    Coin Check Confirm 不正なコインでないことを確認する
    Pass Alarm 不法侵入
  2. 状態 : Confirm
    • 入場動作 :
    • 退場動作 :
    イベント アクション 遷移先 説明
    realCoin Unlock Unlocked ゲートを開ける
    fakeCoin Lock Locked
  3. 状態 : Unlocked

    ゲートが開いている

    • 入場動作 : EnterUnlocked
    • 退場動作 : ExitUnlocked
    イベント アクション 遷移先 説明
    Coin ThankYou チップとして受け取る
    Pass Lock Locked ゲートを閉じる

※ 遷移先が空欄である場合、状態の変化がなく、入場/退場動作も行わないことを意味します

コンテキスト

class GateContext : public Gate::Context {
public:
  virtual void error();
  virtual void Alarm();
  virtual void EnterLocked();
  virtual void EnterUnlocked();
  virtual void ExitLocked();
  virtual void ExitUnlocked();
  virtual void Lock();
  virtual void ThankYou();
  virtual void Unlock();
  virtual void Check();
};

// 本物/偽物を判定する
void GateContext::Check() {
  string prompt;
  cout << "コインは本物? [y/n] ";
  cin >> prompt;
  if ( prompt[0] == 'y' ) {
    realCoin(); // '本物'イベント発行
  } else {
    fakeCoin(); // '偽物'イベント発行
  }
}

...

GateContext context;
Gate::FSM fsm;
fsm.setContext(&context);
...

実行結果

Locked
  enter an event(coin/pass or exit) [c/p/e]:c
Check: state=Locked event=Coin
コインは本物? [y/n] y
ExitLocked: state=Locked event=(none)
閉じた状態を終わります
Unlock: state=Confirm event=realCoin
ゲートを開けます
EnterUnlocked: state=Unlocked event=(none)
ゲートが開きました
Unlocked
  enter an event(coin/pass or exit) [c/p/e]:p
Lock: state=Unlocked event=Pass
ゲートを閉めます
ExitUnlocked: state=Unlocked event=(none)
開いた状態を終わります
EnterLocked: state=Locked event=(none)
ゲートが閉まりました
Locked
  enter an event(coin/pass or exit) [c/p/e]:c
Check: state=Locked event=Coin
コインは本物? [y/n] n
ExitLocked: state=Locked event=(none)
閉じた状態を終わります
Lock: state=Confirm event=fakeCoin
ゲートを閉めます
EnterLocked: state=Locked event=(none)
ゲートが閉まりました
Locked
  enter an event(coin/pass or exit) [c/p/e]:e

上記戦略を盛り込んだのが以下のコードです。コンテキスト内部からイベントを発行することができ、次の状態に遷移したら直ちにイベントを受理します。

smc.cpp

/*
 * Finite State Machine code generator
 *  using IBM alphaWorks XML4C
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <set>
#include <string>
#include <algorithm>

#include <util/PlatformUtils.hpp>    // PlatformUtils
#include <parsers/SAXParser.hpp>     // SAXParser
#include <sax/HandlerBase.hpp>       // HandlerBase
#include <sax/SaxParseException.hpp> // SaxParseException

#define LN "¥n"

using namespace std;

/*
 * UNICODEからstringへの変換
 */
inline string transcode(const XMLCh* xmlch) {
  string result;
  if ( xmlch ) {
    char* p = XMLString::transcode(xmlch);
    result = p;
    delete[] p;
  }
  return result;
}

typedef set<string> strings;

void insert_strings(strings& ss, const string& s) {
  istringstream strm(s);
  string token;
  while ( strm >> token ) {
    ss.insert(token);
  }
}

/*
 * Event
 */
struct Event {
  std::string name;        // 名前
  strings     actions;     // アクション
  std::string transition;  // 次の状態
};

inline bool operator<(const Event& x, const Event& y) {
  return x.name < y.name;
}

typedef std::set<Event> Events;

/*
 * State
 */
struct State {
  std::string name;   // 名前
  strings     enter;  // 入場動作
  strings     exit;   // 退場動作
  Events      events; // Eventの集合
  State() {}
};

inline bool operator<(const State& x, const State& y) {
  return x.name < y.name;
}

typedef std::set<State> States;

/*
 * FSM
 */
struct FSM {
  std::string name;     // 名前
  std::string initial;  // 初期状態
  strings     error;    // エラーアクション
  States      states;   // Stateの集合
  strings     state_names;
  strings     event_names;
  strings     action_names;
  strings     transition_names;
};

/*
 * エラーメッセージの出力
 */
ostream& operator<<(ostream& stream, const SAXParseException& exception) {
  return stream << "public-id=" << transcode(exception.getPublicId())
                << " system-id=" << transcode(exception.getSystemId())
                << " line=" << exception.getLineNumber()
                << " column=" << exception.getColumnNumber()
                << " : " << transcode(exception.getMessage());
}

/*
 * XMLからFSMを構築するためのハンドラ
 */
class Handler : public HandlerBase {
  State current_state;
  FSM&  fsm;
public:

  Handler(FSM& f) : fsm(f) {}

  // DocumentHandler

  // エレメント(タグ)の開始
  virtual void startElement(const XMLCh* const tag_name, AttributeList& attrs) {
    std::string tag = transcode(tag_name);
    std::string tmp;
    // <FSM ...>
    if ( tag == "FSM" ) {
      fsm.name     = transcode(attrs.getValue(L"name"));
      fsm.initial  = transcode(attrs.getValue(L"initial"));
      tmp = transcode(attrs.getValue(L"error"));
      insert_strings(fsm.error, tmp);
      insert_strings(fsm.action_names, tmp);
    } else
    // <State ...>
    if ( tag == "State" ) {
      current_state.events.clear();
      current_state.enter.clear();
      current_state.exit.clear();
      current_state.name  = tmp = transcode(attrs.getValue(L"name"));
      if ( !tmp.empty() ) fsm.state_names.insert(tmp);
      tmp = transcode(attrs.getValue(L"enter"));
      if ( !tmp.empty() ) {
        insert_strings(fsm.action_names, tmp);
        insert_strings(current_state.enter, tmp);
      }
      tmp = transcode(attrs.getValue(L"exit"));
      if ( !tmp.empty() ) {
        insert_strings(fsm.action_names, tmp);
        insert_strings(current_state.exit, tmp);
      }
    } else
    // <Event ...>
    if ( tag == "Event" ) {
      Event event;
      event.name = tmp = transcode(attrs.getValue(L"name"));
      if ( !tmp.empty() ) fsm.event_names.insert(tmp);
      tmp = transcode(attrs.getValue(L"action"));
      if ( !tmp.empty() ) {
        insert_strings(event.actions, tmp);
        insert_strings(fsm.action_names, tmp);
      }
      event.transition = tmp = transcode(attrs.getValue(L"transition"));
      if ( !tmp.empty() ) fsm.transition_names.insert(tmp);
      current_state.events.insert(event);
    }
  }

  // エレメント(タグ)の終了
  virtual void endElement(const XMLCh* const tag_name) {
    std::string tag = transcode(tag_name);
    // </State>
    if ( tag == "State" ) {
      fsm.states.insert(current_state);
    }
  }

  // ErrorHandler

  virtual void warning(const SAXParseException& exception)
    { cout << "warning: " << exception << endl; }

  virtual void error(const SAXParseException& exception)
    { cout << "error:   " << exception << endl; }

  virtual void fatalError(const SAXParseException& exception)
    { cout << "fatal:   " << exception << endl; }

};

/*
 * ----- main -----
 */
int main(int argc, char* argv[]) {

  cerr << "smc - State Map Compiler for C++ " __DATE__ << endl
       << "      using IBM alphaWorks XML Parser for C++ (XML4C)¥n" << endl;
  if ( argc != 2 ) {
    cerr << "usage: smc <xml-script>" << endl;
    return 0;
  }

  FSM fsm;
  Events::const_iterator  eit;
  States::const_iterator  sit;
  strings::const_iterator nit;

  /*
   * 初期化とFSMの構築
   */
  try {
    XMLPlatformUtils::Initialize();
    SAXParser parser;
    Handler   handler(fsm);

    parser.setDoValidation(true);        // DTDによる検証を行う
    parser.setDocumentHandler(&handler); // ハンドラ設定
    parser.setErrorHandler(&handler);    // 同上(エラー用)

    parser.parse(argv[1]);

  } catch ( const XMLException& er ) {
    cout << er.getMessage() << endl;
  }

  /*
   * 初期状態は定義されているか?
   */
  if ( fsm.state_names.find(fsm.initial) == fsm.state_names.end() ) {
    cerr << "invalid initial : " << fsm.initial << endl;
    return 1;
  }

  /*
   * 遷移先は定義されているか?
   */
  for ( nit = fsm.transition_names.begin(); nit != fsm.transition_names.end(); ++nit ) {
    if ( fsm.state_names.find(*nit) == fsm.state_names.end() ) {
      cerr << "invalid transition : " << *nit << endl;
      return 1;
    }
  }

  ofstream fout;
  string   fname;
  /*
   * C++ コードの生成
   */

  ///// STATE

  fname = fsm.name + "_state.h";
  fout.open(fname.c_str());
  cout << fname << endl;

  fout << "#ifndef __" << fsm.name << "_state_h__" LN
       << "#define __" << fsm.name << "_state_h__" LN LN;

  fout << "namespace " << fsm.name << " {" LN LN;

  fout << "enum state { ";
  for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) {
    fout << *nit << ", ";
  }
  fout << "};" LN LN
          "}" LN
          "#endif" LN;
  fout.close();

  ///// EVENT

  fname = fsm.name + "_event.h";
  fout.open(fname.c_str());
  cout << fname << endl;

  fout << "#ifndef __" << fsm.name << "_event_h__" LN
       << "#define __" << fsm.name << "_event_h__" LN LN;

  fout << "namespace " << fsm.name << " {" LN LN;

  fout << "enum event { ";
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << *nit << ", ";
  }
  fout << "none };" LN LN
          "}" LN
          "#endif" LN;
  fout.close();

  ///// CONTEXT

  fname = fsm.name + "_context.h";
  fout.open(fname.c_str());
  cout << fname << endl;

  /*
   * 宣言部 (ヘッダ)
   */
  fout << "#ifndef __" << fsm.name << "_context_h__" LN
          "#define __" << fsm.name << "_context_h__" LN LN
          "#include ¥"" << fsm.name << "_state.h¥"" LN
          "#include ¥"" << fsm.name << "_event.h¥"" LN LN
          "namespace " << fsm.name << " {" LN LN;

  /*
   * Context 宣言
   */
  fout << "class FSM;" LN LN
          "class Context {" LN "public:" LN;
  for ( nit = fsm.action_names.begin(); nit != fsm.action_names.end(); ++nit ) {
    fout << "  virtual void " << *nit << "() =0;" LN;
  }
  fout << LN "private:" LN "  FSM* fsm_;" LN "  friend class FSM;" LN;
  fout << LN "protected:" LN
          "  state getState() const;" LN
          "  event getEvent() const;" LN
          "  const char* getStateName() const;" LN
          "  const char* getEventName() const;" LN;
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "  void " << *nit << "();" LN;
  }
  fout << "};" LN LN
          "}" LN
          "#endif" LN LN;

  fout << "/* prototype of Context's derivative */" LN
          "/*" LN LN
          "#include ¥"context.h¥"" LN LN
          "class ContextDerivative : public " << fsm.name << "::Context {" LN
          "public:" LN;
  for ( nit = fsm.action_names.begin(); nit != fsm.action_names.end(); ++nit ) {
    fout << "  virtual void " << *nit << "();" LN;
  }
  fout << "};" LN LN
          "*/" LN;

  fout.close();

  ///// FSM

  fname = fsm.name + "_fsm.h";
  fout.open(fname.c_str());
  cout << fname << endl;

  fout << "#ifndef __" << fsm.name << "_fsm_h__" LN
          "#define __" << fsm.name << "_fsm_h__" LN LN
          "#include ¥"" << fsm.name << "_state.h¥"" LN
          "#include ¥"" << fsm.name << "_event.h¥"" LN LN
          "namespace " << fsm.name << " {" LN LN
          "class Context;" LN
          "class State;" LN LN;

  /*
   * FSM 宣言
   */
  fout << "class FSM  {" LN
          "  Context* context_;" LN
          "  State*   state_;" LN
          "  event    post_event_;" LN
          "  event    event_;" LN
          "  void     prologue();" LN
          "  static State* state_v[];" LN
          "  static const char* event_v[];" LN LN
          "  friend class Context;" LN;
  for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) {
    fout << "  friend class " << *nit << "State;" LN;
  }
  fout << "public:" LN
          "  FSM() : state_(state_v[" << fsm.initial << "]), context_(0), post_event_(none), event_(none) {}" LN;
  fout << "  void        setContext(Context* context);" LN
          "  Context*    getContext() const { return context_; }" LN
          "  state       getState() const;" LN
          "  const char* getStateName() const;" LN
          "  event       getEvent() const;" LN
          "  const char* getEventName() const;" LN LN;
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "  void " << *nit << "();" LN;
  }
  fout << "};" LN LN
          "}" LN LN
          "#endif" LN;
  fout.close();

  ///// IMPLEMENTATION

  fname = fsm.name + "_impl.cpp";
  fout.open(fname.c_str());
  cout << fname << endl;

  fout << "#include ¥"" << fsm.name << "_context.h¥"" LN
          "#include ¥"" << fsm.name << "_fsm.h¥"" LN LN;

  fout << "namespace " << fsm.name << " {" LN LN;

  // Context

  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "void Context::" << *nit << "() { fsm_->post_event_ = " << fsm.name << "::" << *nit << "; }" LN;
  }
  fout << "state Context::getState() const { return fsm_->getState(); }" LN
          "const char* Context::getStateName() const { return fsm_->getStateName(); }" LN
          "event Context::getEvent() const { return fsm_->getEvent(); }" LN
          "const char* Context::getEventName() const { return fsm_->getEventName(); }" LN LN;

  // State

  fout << "class State {" LN "public:" LN
          "  virtual const char* name() const =0;" LN
          "  virtual state id() const =0;" LN
          "  virtual void enter(FSM* fsm) {}" LN
          "  virtual void exit(FSM* fsm) {}" LN
          "  void error(FSM* fsm);" LN;
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "  virtual void " << *nit << "(FSM* fsm) { error(fsm); }" LN;
  }
  fout << "};" LN LN;

  fout << "void State::error(FSM* fsm) {" LN;
  if ( !fsm.error.empty() ) {
    fout << "  Context* context = fsm->getContext();" LN;
    for ( nit = fsm.error.begin(); nit != fsm.error.end(); ++nit ) {
      fout << "  context->" << *nit << "();" LN;
    }
  }
  fout << "}" LN LN;

  // State's derivatives

  for ( sit = fsm.states.begin(); sit != fsm.states.end(); ++sit) {
    fout << "class " << sit->name << "State : public State {" LN "public:" LN
            "  virtual const char* name() const { return ¥"" << sit->name << "¥"; }" LN
            "  virtual state id() const { return " << sit->name << "; }" LN;
    if ( !sit->enter.empty() ) {
      fout << "  virtual void enter(FSM* fsm);" LN;
    }
    if ( !sit->exit.empty() ) {
      fout << "  virtual void exit(FSM* fsm);" LN;
    }
    for ( eit = sit->events.begin(); eit != sit->events.end(); ++eit ) {
      fout << "  virtual void " << eit->name << "(FSM* fsm);" LN;
    }
    fout << "};" LN LN;

    // State (impl)

    if ( !sit->enter.empty() ) {
      fout << "void " << sit->name << "State::enter(FSM* fsm) {" LN
              "  Context* context = fsm->getContext();" LN;
      for ( nit = sit->enter.begin(); nit != sit->enter.end(); ++nit ) {
        fout << "  context->"  << *nit << "();" LN;
      }
      fout << "}" LN LN;
    }
    if ( !sit->exit.empty() ) {
      fout << "void " << sit->name << "State::exit(FSM* fsm) {" LN
              "  Context* context = fsm->getContext();" LN;
      for ( nit = sit->exit.begin(); nit != sit->exit.end(); ++nit ) {
        fout << "  context->"  << *nit << "();" LN;
      }
      fout << "}" LN LN;
    }
    fout << LN;

  // State's derivatives (impl.)

    for ( eit = sit->events.begin(); eit != sit->events.end(); ++eit ) {
      fout << "void " << sit->name << "State::" << eit->name << "(FSM* fsm) {" LN;
      if ( !eit->actions.empty() ) {
        fout << "  Context* context = fsm->getContext();" LN;
        for ( nit = eit->actions.begin(); nit != eit->actions.end(); ++nit ) {
          fout << "  context->"  << *nit << "();" LN;
        }
      }
      fout << "  fsm->event_ = " << fsm.name << "::none;" LN;
      if ( !eit->transition.empty() ) {
        if ( !sit->exit.empty() ) {
          fout << "  exit(fsm);" LN;
        }
        fout << "  fsm->state_ = FSM::state_v[" << eit->transition << "];" LN
                "  fsm->state_->enter(fsm);" LN;
      }
      fout << "}" LN LN;
    }
    fout <<"static " << sit->name << "State " << sit->name << "_;" LN LN;
  }

  // FSM (impl.)

  fout << "State* FSM::state_v[] = { ";
  for ( nit = fsm.state_names.begin(); nit != fsm.state_names.end(); ++nit ) {
    fout << "&" << *nit << "_, ";
  }
  fout << "0 };" LN;

  fout << "const char* FSM::event_v[] = { ";
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "¥"" << *nit << "¥", ";
  }
  fout << "¥"(none)¥" };" LN;

  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "void FSM::" << *nit << "() { event_ = " << fsm.name << "::"
         << *nit << "; state_->" << *nit << "(this); prologue(); }" LN;
  }

  fout << "void FSM::setContext(Context* context) { context_ = context; context_->fsm_ = this; }" LN
          "state FSM::getState() const { return state_->id(); }" LN
          "const char* FSM::getStateName() const { return state_->name(); }" LN
          "event FSM::getEvent() const { return event_; }" LN
          "const char* FSM::getEventName() const { return event_v[event_]; }" LN LN;

  fout << "void FSM::prologue() {" LN
          "  event_ = post_event_;" LN
          "  post_event_ = none;" LN
          "  switch ( event_ ) {" LN;
  for ( nit = fsm.event_names.begin(); nit != fsm.event_names.end(); ++nit ) {
    fout << "  case " << fsm.name << "::" << *nit  << " : " << *nit << "(); break;" LN;
  }
  fout << "  }" LN
          "}" LN LN;

  fout << "}" LN;
  fout.close();

  return 0;
}