2011年10月19日水曜日

XMLを処理するのに…

Windows で C++ にて XML を処理しようと思ったら、考えられそうな選択肢がいくつかあります。

  • LibXML2
  • Expat
  • MSXML
  • TinyXML
  • XMLLite
  • Xerces-C++
そこで、なるべくならプラットフォームを固定して書きたくないので、MSXML と XMLLite が候補から外れます。
LibXML2 でやろうとしたら、ICU か iconv あたりをなんとかしないといけません。正直 iconv はmsvc でコンパイルするのが困難です。libiconv for windows ありますが、vc で、すんなりコンパイルが通りません。じゃあ ICU だわさ?って、ICUを使おうとすると、icu.lib が無いとか変なリンクエラーが出て萎えてしまいます。ICU も大げさな感じがして、使うのをちょっと躊躇われるのという事情もあります。
おまえ、選り好みが激しすぎと怒られそうですが、Xerces-C++ も、なんか、あんまり好きじゃないんです。
TinyXML もいいんですけど、ちょっと考えてしまう…(こんな事ばかり書いてると、いい加減に袋叩きにあいそう)。

そうすると残されたのは、Expat って事になります。ところが、この Expat って、SJIS に対応していないんですよね…。APIで日本語対応するって事も可能なんですが、なんというか、どうもしっくり来ない。XMLの構文を解析するついでに、文字コード変換もやりましょうという思想なんで、1文字か2文字づつコード変換の処理をしなきゃならない。これ、文字コード変換ライブラリのメンテナンスも考えたら、結構大変です。SJIS に限って言えば、SJIS第一バイトの場合は2バイト処理します。なんつーか、もやもや~とするんですよねー。

で、結論は何かと言うと、そもそも wchar_t が嫌い。これに尽きるんですわ。UTF-8 は、ほんと良く出来てると思います。

結局、自分の好みでやってくと、「文字コード変換」と「expatで遊ぼ」を組み合わせて、こんな感じになってしまいました。ただ、encoding の指定が全然関係ないやん…って部分が気に入りません。


#include <iostream>
#include <string>
#include <vector>
#include <expat.h>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>

#include "codepage.hpp"
#include "wconv.hpp"

class xml_node;

class xml_node {
public:
  xml_node( const XML_Char* name ) : node_name_(name) {}
public:
  std::basic_string<XML_Char>  node_name_;
  std::basic_string<XML_Char>  node_value_;
  std::vector< boost::shared_ptr<xml_node> > childs_;
};


class xml_holder {
public:
  std::vector< boost::shared_ptr<xml_node> >  stack_;
  boost::shared_ptr<xml_node>  current_;
};

void starter(void* userData, const XML_Char* name, const XML_Char* attrs[]) {
  xml_holder* holder = reinterpret_cast<xml_holder*>(userData);
  holder->stack_.push_back( holder->current_ );
  boost::shared_ptr<xml_node> element( new xml_node( name ) );
  holder->current_->childs_.push_back( element );
  holder->current_ = element;
}

void ender( void* userData, const XML_Char* name) {
  xml_holder* holder = reinterpret_cast<xml_holder*>(userData);
  holder->current_ = holder->stack_.back();
  holder->stack_.pop_back();
}

void data( void* userData, const XML_Char* s, int len ) {
  xml_holder* holder = reinterpret_cast<xml_holder*>(userData);
  holder->current_->node_value_.assign( s, s + len );
}

void trace( int depth, boost::shared_ptr<xml_node> elem ) {
  for( int i = 0; i < depth; ++i ) { std::cout << " "; }
  std::cout << elem->node_name_ << ":" << code_to_code<CP_ACP,CP_UTF8>(elem->node_value_) << std::endl;
  BOOST_FOREACH( boost::shared_ptr<xml_node> e, elem->childs_ ) {
    trace( depth+1, e );
  }
}

bool parse_xml( const std::string& xml ) {
  xml_holder holder;
  holder.current_ = boost::shared_ptr<xml_node>( new xml_node( "root" ) );
  XML_Parser  parser = XML_ParserCreate("UTF-8");
  if( !parser ) return false;
  XML_SetUserData( parser, &holder );
  XML_SetElementHandler( parser, starter, ender );
  XML_SetCharacterDataHandler( parser, data );
  int iseof = 0;
  XML_Parse( parser, xml.c_str(), xml.size(), iseof );
  XML_ParserFree( parser );
  trace( 0, holder.current_ );
  return !iseof;
}

#include <fstream>

int main(int argc, char* argv[] ) {
  if( argc != 2 ) return 1;
  std::ifstream ifs( argv[1] );
  std::string xml;
  std::string line;
  while( ifs.good() ) {
    std::getline( ifs, line );
    xml += line;
    //std::cout << line << std::endl;
  }
  //std::cout << std::endl;

  parse_xml( code_to_code<CP_UTF8,CP_ACP>(xml) );

  return 0;
}


0 件のコメント: