ライセンス | MIT |
URL | http://expat.sourceforge.net/ |
XMLパーサライブラリです。
かなり定番のライブラリで、あちこちで使われています。
コンパイル
Windows版のコンパイル方法は、expatのソースを展開するとwin32というフォルダがあり、
この中のReadme.txtに詳しく書かれています。
VC2008ならば、expat.dswをプロジェクトのインポートで変換できるとあります。
インポートして、7つのプロジェクトが現れたら成功です。
ビルドも難なく成功します。
プロジェクト名に”_w”がついているプロジェクトはUTF-16版だそうです。
なお、サンプルプロジェクトのelements.exeはUTF-8のXMLファイル用です。
テスト実行するときはUTF-8のXMLファイルを用意しましょう。
実行するにはDOSプロンプトから以下のようにします。
> elements < "ファイル名"
使用法
elements.exe、outline.exeを見ると大体の様子がわかりそうです。
まずパーサオブジェクトを作成、XMLの要素、属性等に対するコールバック関数を登録し、
ファイルから順次読み込んで解析していくという流れのようです。
パーサオブジェクトの作成は、以下のようにします。
XML_Parser parser = XML_ParserCreate( NULL );
引数には文字コードを指定することができるそうで、この場合はドキュメントに定義されている
文字コードを無視するそうです。
使い終わったらパーサオブジェクトを開放します。
XML_ParserFree( parser );
コールバックの書式はXMLの構造種別ごとに決まっていて、
それぞれのコールバックを登録する関数があります。
例えば、XML要素開始タグに対するコールバックは以下のように定義します。
static void XMLCALL element_start ( void *userData, const XML_Char *name, const XML_Char **attrs ) { // ここで、 // nameはタグ名、 // attrsは属性・値・属性・値、、、と続く // attrsの終わりにはNULLがある }
コールバックを登録する関数はXML_Set***Handler
という名前の関数群です。
例えば上記のXML要素開始タグを登録するには以下のようにします。
XML_SetStartElementHandler( parser, element_star );
ちなみに、要素開始・終了のコールバックを同時に登録する
XML_SetElementHandler()
関数もあります。
要素の文字列を取得するコールバックは以下のように定義します。
static void XMLCALL char_handler ( void *userData, const XML_Char *s, int len ) { int i; char buf[255]; memcpy( buf, s, len ); buf[len] = ''; printf( "string = %sn", buf ); }
上記の例で、第2引数s
が要素の文字列を格納している引数ですが、
文字列の終端文字は無く、第3引数len
に文字数が入っているので、
上記のように終端文字を入れる処理が必要になります。
このコールバックを登録する関数はXML_SetCharacterDataHandler()
関数です。
そのほかCDATAセクション、処理命令など、
いろいろなコールバックがあるので、リファレンスを見ながら必要な関数を定義していきます。
なお、上記のコールバックの引数userData
はコールバックに渡すユーザデータで、
XML_SetUserData()
関数でセットします。
例:
int nUserData;
XML_Parser parser = XML_ParserCreate( NULL );
・
・
・
XML_SetUserData(parser, &nUserData );
ファイルからの読み込みはfread
を使用します。
すなわち、読み込みは行単位とかではなく、用意したバッファ文を読み込んでしまって、パーサに渡します。
当然途中で切れたりするはずですが、そこはパーサが上手に処理してくれるようです。
(要素データの途中だとそうはならない、下記の注意参照)
読み込みバッファはユーザが用意してもいいですし、expatに用意してもらう方法もあります。
前者の場合はXML_Parse()
でパーサへデータを渡します。
後者の場合はXML_ParseBuffer()
関数で解析を依頼します。バッファの開放はパーサの解放時に行われるようです。
後者の方法での例は以下のとおりです。
char *pBuf; FILE *pfXML; int nLen, nDone; ・ ・ ・ // バッファ確保を依頼する pBuf = (char *)XML_GetBuffer( parser, BUFFSIZE ); // バッファへ読み込み nLen = (int)fread( pBuf, 1, BUFFSIZE, pfXML ); nDone = feof( pfKML ); if ( XML_ParseBuffer( parser, nLen, nDone ) == XML_STATUS_ERROR ) { // エラー処理 }
以下の例は、KMLファイルの点要素の座標値だけを抜き取ってCSVに保存する例です。
(いい加減な例なので注意!)
#include "expat.h" #include <stdio.h> #include <string.h> #define BUFFSIZE 512 bool g_bPoint = false; bool g_bCoord = false; static void XMLCALL startElement(void *userData, const char *name, const char **attrs) { if ( !_strcmpi( name, "Point" ) ) { g_bPoint = true; return; } if ( g_bPoint ) { if ( !_strcmpi( name, "coordinates" ) ) { g_bCoord = true; } } } static void XMLCALL endElement(void *userData, const char *name) { if ( !_strcmpi( name, "Point" ) ) { g_bPoint = false; } else if ( !_strcmpi( name, "coordinates" ) ) { g_bCoord = false; } } static void XMLCALL value_handler( void *userData, const XML_Char *s, int len ) { char buf[100]; memcpy( buf, s, len ); buf[len] = ''; if ( g_bCoord ) { fprintf( *(FILE **)userData, "%sn", buf ); } } int main( int argc, char *argv[] ) { int nDone, nLen; FILE *pfKML, *pfCSV; XML_Parser parser = XML_ParserCreate( NULL ); char *pBuf; if ( !parser ) { exit( 1 ); } if ( fopen_s( &pfKML, argv[1], "r" ) ) { exit( 1 ); } if ( fopen_s( &pfCSV, argv[2], "w" ) ) { exit( 1 ); } pBuf = (char *)XML_GetBuffer( parser, BUFFSIZE ); XML_SetElementHandler( parser, startElement, endElement ); XML_SetCharacterDataHandler( parser, value_handler ); XML_SetUserData( parser, &pfCSV ); while ( 1 ) { nLen = (int)fread( pBuf, 1, BUFFSIZE, pfKML ); nDone = feof( pfKML ); if ( XML_ParseBuffer( parser, nLen, nDone ) == XML_STATUS_ERROR ) { fprintf(stderr, "Parse error at line %u:n%sn", XML_GetCurrentLineNumber( parser ), XML_ErrorString(XML_GetErrorCode( parser ) ) ); fclose( pfKML ); fclose( pfCSV ); XML_ParserFree( parser ); exit( 1 ); } if ( nDone ) break; } XML_ParserFree( parser ); fclose( pfKML ); fclose( pfCSV ); return 0; }
注意!
ファイルから読み込んだバッファが要素データ文字列の途中で切れている場合、
XML_SetCharacterDataHandler()
関数で指定したコールバックに渡される文字列は
その切れたところまでが渡されます。
したがって、XML_SetCharacterDataHandler()
関数で指定したコールバックで
この文字列を数値に変換などの処理をしてしまうと、
切れる前の数値とあとの数値と2回処理してしまいます。
上の例ではその問題が起きる可能性があります。
対策としては、XML_SetCharacterDataHandler()
関数で指定したコールバックではとりあえず一時的に文字列を保存する
だけにしておいて、タグ終了時に呼ばれる、XML_SetEndElementHandler()
関数で指定するコールバック内で
文字列を処理するようにする必要があります。
SHIFT-JISのXMLは?
expatがサポートする文字コードはUTF-8、UTF-16、ISO-8859-1、US-ASCIIだけだそうです。
それ以外の文字コードは読めません。
そんな場合は、XML_SetUnknownEncodingHandler()
関数でその文字コードに対する
処理を指定するのですが、これは大変です。
ですが、ありがたいことに蛭子屋本舗さんで、
SHIFT-JISコードのハンドラを公開してくださっています。
このハンドラをXML_SetUnknownEncodingHandler()
関数に指定すれば、
SHIFT-JISのXMLでも解析することができます。
参考
expatはパーサに渡された文字データを全てUTF-8(expatwならUTF-16)に変換しようと試みます。
なので、例えば上記のハンドラを使ってSHIFT-JISの日本語文字列をパーサに渡すと、
内部でUTF-8に変換してXML_SetCharacterDataHandler()
関数で指定したコールバックに
帰ってきたりします。
参考サイト
quneさんの記事にサンプルがあります。
まずはこちらを参考に特訓です。