iPEX(アイペックス)コラム:XSLT [Part_01]

この文書では、iPEXにてXSLTを使用し、CSVファイルとXMLファイル間におけるデータの相互変換について解説します。解説にはWindows版C++用ライブラリ形式のiPEXを用います。

作成するプログラム csv2xml.exe、xsltconv.exe
リンク ソースコード csv2xml.zipxsltconv.zip

CSVファイルのXML文書への変換

一般に、CSV形式のデータは表形式のデータ構造しか持たないため、複雑な木構造をもったXML文書へ変換する場合には、変換アプリケーションが各データ項目の格納方法を意識しなければなりません。

しかし、複雑な木構造へ展開するよりも表形式のままXML化するほうがプログラムが複雑にならず、作成したプログラムの利用も容易です。

このサンプルでは、読み込んだCSVデータを表形式のままXML化するプログラムを作成し、その後XSLTを用いて目的の木構造に変換する手順を用います。

次に示すのは、このプログラムの入力データとして用いるCSVファイルbooks1.csvの内容です。

computer,The Art of Computer Programming,Donald Ervin Knuth,134.95,1998,10
novel,Hannibal,Thomas Harris,22.36,1999,07
business,Business @ the Speed of Thought,Bill Gates,13.56,2000,05

次に、ファイルよりCSVファイルを読み込んでDOMオブジェクトツリーを作成し、その内容を表形式のXML文書として標準出力に書き出すアプリケーションcsv2xml.exeのメイン関数を示します。

int main(int argc, char* argv[])
{
  if (argc < 2) {
    cerr << "Usage: csv2xml filename [options]" << endl;
    cerr << " options:" << endl;
    cerr << " /h : read 1st line as header" << endl;
    cerr << " /r [row name] : set tag name indicating row" << endl;
    cerr << " /d [doc name] : set document name" << endl;
    return 1;
  }
  // iPEXの初期化
  Init init;
  iconv::CP932ConverterFactory fCP932(L"shift_jis");

  // ファイルストリームのオープン
  std::ifstream ifs(argv[1]);
  if (std::ifstream::failbit == ifs.rdstate()) {
    cerr << "Could not open file: " << argv[1] << endl;
    return 1;
  }

  // オプションの判定
  bool header = false;
  std::string docname("doc");
  std::string rowname("row");
  if (3 <= argc) {
    for (int i=2; i<argc; i++) {
      if (0 == strcmp("/h", argv[i])) {
        header = true;
      }
      else if (0 == strcmp("/d", argv[i])) {
        i++;
        if (i >= argc) break;
        docname = argv[i];
      }
      else if (0 == strcmp("/r", argv[i])) {
        i++;
        if (i >= argc) break;
        rowname = argv[i];
      }
    }
  }

  // 1行目がヘッダ指定なら、読み込む
  std::vector<std::string> colnames;
  if (true == header) {
    std::string line;
    std::getline(ifs, line);
    CSVTokenizer tokens(line);
    while (true == tokens.hasMoreTokens()) {
      std::string& token = tokens.nextToken();
      colnames.push_back(token);
    }
  }

  auto_ptr<Document> pDoc(csv2dom(ifs, docname, rowname, colnames));
  if (0 == pDoc.get()) {
    cerr << "Could not generate xml from " << argv[1] << endl;
    return 1;
  }

  Stream::OutputStreamByFile ofs(Stream::OutputStreamByFile::STDOUT, L"shift_jis");
  if (!ofs) {
    cerr << "Cannot open stdout" << endl;
    return 1;
  }
  XMLWriter writer(&ofs, L"", true);
  if (!writer.write(pDoc.get())) {
    cerr << "Could not write to stdout" << endl;
    return 1;
  }
  return 0;
}

ヘッダの取得

「一行目をヘッダとして読み込む」オプションが指定された場合、CSVデータの一行目の内容を読み込み、保持します。このプログラムでは、CSVの1行をカンマで区切られた単位に分割して取り出すことの出来るユーティリティクラスCSVTokenizerを作成しています。次にクラスCSVTokenizerの宣言を示します。

class CSVTokenizer
{
public:
  explicit CSVTokenizer(std::string& s);
  std::string& nextToken();
  int countTokens();
  bool hasMoreTokens();
private:
  std::string* _str;
  std::vector<std::string> _tokens;
  int _token_num;
  std::vector<std::string>::iterator _ite;
};

クラスCSVTokenizerは、CSVのカンマで区切られた単位を文字列から切り出すことのできるC++クラスです(JavaのクラスStringTokenizerを参考に設計しました。)。インスタンス作成時にCSVの一行を格納した文字列を渡し、その後カンマで区切られた単位毎に取り出すことができるように設計されています。

次に、クラスCSVTokenizerを用いて、CSVファイルの一行目をヘッダとして取り出す部分のコードを示します。

  // 1行目がヘッダ指定なら、読み込む
  std::vector<std::string> colnames;
  if (true == header) {
    std::string line;
    std::getline(ifs, line);
    CSVTokenizer tokens(line);
    while (true == tokens.hasMoreTokens()) {
      std::string& token = tokens.nextToken();
      colnames.push_back(token);
    }
  }

CSVデータの読み込み

次にCSVデータを読み込み、DOMツリーへ展開する部分関数csv2dom()のコードを示します。

Document* csv2dom(std::ifstream& stream, std::string& docname,
        std::string& rowname, std::vector<std::string>& colnames)
{
  Document* pDoc = IPEXDocument::createDocumentObject();
  Element* doc = createAndAddElement(pDoc, pDoc, docname.c_str());

  Element* eventlist = 0;
  std::string lastStaff;
  std::string line;
  std::getline(stream, line);
  while (0 != line.length()) {
    Element* row = createAndAddElement(pDoc, doc, rowname.c_str());
    CSVTokenizer tokens(line);
    // 1行目がヘッダの場合
    if (true != colnames.empty()) {
      for (int i=0; i<colnames.size(); i++) {
        if (true == tokens.hasMoreTokens()) {
          std::string& token = tokens.nextToken();
          createAndAddTextElement(pDoc, row, colnames.at(i).c_str(), token.c_str());
        }
      }
    }
    // 1行目がデータの場合
    else {
      int count = 1;
      char colname[16];
      while (true == tokens.hasMoreTokens()) {
        sprintf(colname, "column%d", count++);
        std::string& token = tokens.nextToken();
        createAndAddTextElement(pDoc, row, colname, token.c_str());
      }
    }
    line.erase(line.begin(), line.end());
    std::getline(stream, line);
  }
  return pDoc;
}

XSLTについて

XSLT (Extensible Stylesheet Language for Transformations)は、W3C で承認された仕様です。XSLTはXML 構造を別の XML 構造、HTML、または SQL などほかのテキスト ベースのフォーマットに変換できるメカニズムを提供します。

XSLT は、ソースとなるXML文書要素の特定のパターンに、XML、HTML、またはプレーン テキストで書かれた出力を割り当てることができます。

XSLT仕事ではXSLTスタイルシートを使用して、 XML ドキュメントを別の構造を持つXML ドキュメントに変換できます。たとえば、XML ドキュメントの順序の変更、要素の追加または削除、条件テスト、または要素の収集を使用する繰り返しを行うことができます。

© 2001 Infoteria Corporation

XSLT [Part_01]/[Part_02]

このページのトップ