dblp.xml から expat をつかって共著者を抽出

最近、dblp データのスナップショットが、web で公開されているのを知りました (copyrightは、dblp.dtd内にあります)。これを使って、自分と距離が近い研究者のグラフが表示したいなぁと思います。今日は、expat を使って、対象の著者の共著者リストを取り出すとこまでの覚書です。

(追記:ここに、ソースファイルを掲載しました。よろしければどうぞ)


まずは、xml のパーサ選びからはじめます。言語はとりあえずc/c++ってことにしています。xerces, expat, rxp などがあるようです。2009年7月現在でdblp.xmlのサイズが約600MBあります。全体の構造を保持したりするのはちょっと大変ですね。それから、DOMも必要ないし、パフォーマンス重視ということで、expat を採用しました。

expat を google で検索すると、簡単なチュートリアルが見つかると思います。基本はそちらで確認できます。とっても参考になりました。今回は、少しつまずいた箇所についての覚書を残しておきたいと思います。

次の要素は実体参照を含む dblp.xml の一部ですが、ここら辺の扱いで迷いました。最初の注意としては、要素authorを読んでいるとき、その内容に実体参照がある場合は、G, & uuml;, nther Goumlrzの三回連続で XML_SetCharacterDataHandler で設定した外部関数が呼ばれます。基本なのかもしれませんが、XML_SetCharacterDataHandlerで指定される関数の第二引数に G& uuml;nther Goumlrz がそのまま代入されると思って実装していました。ふむむ。

<article mdate="2003-01-31" key="tr/ibm/IWBS66">
    <author>G&uuml;nther Goumlrz</author>
    <title>Verarbeitung nat&uuml;rlicher Sprache</title>
    <journal>IWBS Report</journal>
    <volume>66</volume>
    <year>1989</year>
    <publisher>IBM Germany Science Center, Institute for Knowledge Based Systems</publisher>
</article>

次は、実体参照を解決するやり方です。例えば、& uuml; → ü は、dblp.dtdで実体が定義されています。dblp.dtdは、dblp.xml と同様にwebで公開されています。これをプログラムと同じディレクトリにコピーしちゃいます。そして、次のようにパーサを定義した後で実体参照を操作する関数を指定します。あ、あと、コンパイル時には、引数として-XML_DTDを指定しておいて下さい。

  /* constructing parser */
  XML_Parser parser = XML_ParserCreate("ISO-8859-1");

  /* parsing external DTD */
  XML_SetParamEntityParsing(parser,
      XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);
  XML_SetBase(parser, "dblp.dtd");
  XML_SetExternalEntityRefHandler(parser,
      externalEntityRefHandler);

それから、externalEntityRefHander は、例えば次のように書けます。これは、expat のソース内の test ディレクトリにあるコードを参考にしています。

int externalEntityRefHandler(XML_Parser parser,
    const XML_Char* content, const XML_Char* base,
    const XML_Char* systemId, const XML_Char* publicId)
{
  XML_Parser extparser = XML_ExternalEntityParserCreate(
       parser, content, "ISO-8859-1");

  FILE* fp = fopen(base, "r");
  if (fp == 0) {
    fprintf(stderr, "open: %s", base);
    return 0;
  }
  int ret = 1;
  size_t BUFSIZE = 4096;
  for (;;) {
    char *buf = (char*) XML_GetBuffer(extparser, BUFSIZE);
    if (!buf) {
      ret = 0;
      break;
    }
    size_t nread = fread(buf, sizeof(char), BUFSIZE, fp);
    if (ferror(fp)) {
      ret = 0;
      break;
    }
    if (!XML_ParseBuffer(extparser, nread, feof(fp))) {
      ret = 0;
      break;
    }
    if (feof(fp)) {
      break;
    }
  }
  fclose(fp);
  XML_ParserFree(extparser);
  return ret;
}

最後に、大学院時代の恩師をキーとして共著者を引いてみました。自分のだと実体参照がある共著者がいなかったためです。また、自分の環境(MacBookPro 2.26GHz)では、下のリストを出力するのに約30秒かかりました。思っていたより速かったです。まだ、ちょっと問題はあります(Peterさんが2回カウントされてしまう)が、今日はここまでにします。

...
Antonio Hernández-Barrera
...
Jirí Matousek
...
Peter Brass
Peter Braß
...