doxygenのソースコードに手を加えて自前のフォーマットで簡易データ出力

今更説明するまでも無いような気もするけど,doxygenは多言語に対応しているソースコードドキュメンテーションツール.出力フォーマットはhtml,LATEX,RTF等に対応しており,コード中のコメントを元にいい感じのドキュメントを生成してくれる.
しかし高機能であれこれ出力できる反面,必要な情報だけを簡潔にまとめたドキュメントが欲しいなーという時にはやや使いづらい面もあったり.今回,クラスメンバの一覧だけをコード中から取得したいなーという用途で良いツールが無かったので,doxygenのコードに手を加えてみることにした.

前提

今回使ったのはdoxygenの1.7.6.1.他バージョンのdoxygenだといくらか違う所があるかもしれないが未確認.

まず自前ビルドの環境を用意

ソースからビルドする方法は以下に記載されているので,基本的にはこれ通り進めばOK.

Doxygen

ただしMSVCでビルドする場合,BOMなしのutf-8を正しく認識できていないらしく,多言語対応用のヘッダ群(translator_xx.h)でビルドに失敗する.とりあえず今回は日本語以外いらないので,lang_cfg.hで定義されているLANG_JP以外のdefineをコメントアウトしてビルドを通した.

コードを読んでみる

doxygenのmain関数はいたってシンプル.

int main(int argc,char **argv)
{
  initDoxygen();
  readConfiguration(argc,argv);
  checkConfiguration();
  adjustConfiguration();
  parseInput();
  generateOutput();
  return 0;
}

parseInput()が終わった段階で,グローバル変数の中にパースされた情報が入っている.具体的にはdoxygen.cppの100〜152行目.

// globally accessible variables
ClassSDict      *Doxygen::classSDict = 0;
ClassSDict      *Doxygen::hiddenClasses = 0;
NamespaceSDict  *Doxygen::namespaceSDict = 0;
MemberNameSDict *Doxygen::memberNameSDict = 0;
MemberNameSDict *Doxygen::functionNameSDict = 0;   
FileNameList    *Doxygen::inputNameList = 0;       // all input files
FileNameDict    *Doxygen::inputNameDict = 0;          
GroupSDict      *Doxygen::groupSDict = 0;
FormulaList      Doxygen::formulaList;             // all formulas
FormulaDict      Doxygen::formulaDict(1009);       // all formulas
FormulaDict      Doxygen::formulaNameDict(1009);   // the label name of all formulas
PageSDict       *Doxygen::pageSDict = 0;
PageSDict       *Doxygen::exampleSDict = 0;
SectionDict      Doxygen::sectionDict(257);        // all page sections
CiteDict        *Doxygen::citeDict=0;              // database of bibliographic references
StringDict       Doxygen::aliasDict(257);          // aliases
FileNameDict    *Doxygen::includeNameDict = 0;     // include names
FileNameDict    *Doxygen::exampleNameDict = 0;     // examples
FileNameDict    *Doxygen::imageNameDict = 0;       // images
FileNameDict    *Doxygen::dotFileNameDict = 0;     // dot files
FileNameDict    *Doxygen::mscFileNameDict = 0;     // dot files
StringDict       Doxygen::namespaceAliasDict(257); // all namespace aliases
StringDict       Doxygen::tagDestinationDict(257); // all tag locations
QDict<void>      Doxygen::expandAsDefinedDict(257); // all macros that should be expanded
QIntDict<MemberGroupInfo> Doxygen::memGrpInfoDict(1009); // dictionary of the member groups heading
PageDef         *Doxygen::mainPage = 0;
bool             Doxygen::insideMainPage = FALSE; // are we generating docs for the main page?
FTextStream      Doxygen::tagFile;
NamespaceDef    *Doxygen::globalScope = 0;
QDict<RefList>  *Doxygen::xrefLists = new QDict<RefList>; // dictionary of cross-referenced item lists
bool             Doxygen::parseSourcesNeeded = FALSE;
QTime            Doxygen::runningTime;
SearchIndex *    Doxygen::searchIndex=0;
QDict<DefinitionIntf> *Doxygen::symbolMap;
bool             Doxygen::outputToWizard=FALSE;
QDict<int> *     Doxygen::htmlDirMap = 0;
QCache<LookupInfo> *Doxygen::lookupCache;
DirSDict        *Doxygen::directories;
SDict<DirRelation> Doxygen::dirRelations(257);
ParserManager   *Doxygen::parserManager = 0;
QCString Doxygen::htmlFileExtension;
bool             Doxygen::suppressDocWarnings = FALSE;
ObjCache        *Doxygen::symbolCache = 0;
Store           *Doxygen::symbolStorage;
QCString         Doxygen::objDBFileName;
QCString         Doxygen::entryDBFileName;
bool             Doxygen::gatherDefines = TRUE;
IndexList        Doxygen::indexList;
int              Doxygen::subpageNestingLevel = 0;
bool             Doxygen::userComments = FALSE;
QCString         Doxygen::spaces;
bool             Doxygen::generatingXmlOutput = FALSE;

クラス情報はDoxygen::classSDict, 名前空間Doxygen::namespaceSDictあたりを手繰っていけば,だいたいどこに自分の欲しい情報が入っているのかが分かる.ちなみにdoxygen内部のコードにはあまりdoxygen形式のコメントが付けられていない.公式のWish Listによると,doxygenソースコードdoxygenコメントを付けて欲しいという要望はあるが,それに対するDifficulty Levelは10レベル中7で「結構しんどい」らしい.

手を加える

内部では様々な出力フォーマットに対応するためにOutputGeneratorを継承したHtmlGeneratorやLatexGeneratorクラスが定義されているが,残念ながら多態的な実装はされておらず,各所にif-else節が散らばっているために自前のSubClassを定義しただけでは動作しない.めんどくさい….

とはいえ今回は一部の情報を取り出したいだけなので,適当なモジュールの中に標準出力へのプリントを挟む込み形にした.というわけでクラスに関するパース情報を持っているClassDefに対してoperator << を以下のように定義してやる.

#undef strlen
#include <iostream>
#include <string>
#define strlen qstrlen

namespace {
  static const char* sep = " ";
  static const int indent_width = 1;

  std::ostream& indent(std::ostream& os, int indent_level) 
  {
    os << std::string(indent_width * indent_level, ' ');
    return os;
  }

  std::ostream& brief(std::ostream& os, const MemberDef& memberDef) 
  {
    if (!memberDef.briefDescription().isEmpty())
      os << sep << memberDef.briefDescription();
    return os;
  }

  std::ostream& print(std::ostream& os, const MemberDef& memberDef, int indent_level = 0) 
  {
    indent(os, indent_level) << memberDef.typeString() << sep << memberDef.name();
    brief(os, memberDef);
    return os;
  }

  std::ostream& print(std::ostream& os, const ClassDef& def, int indent_level = 0) 
  {
    MemberNameInfoSDict *dict = def.memberNameInfoSDict();
    MemberNameInfoSDict::Iterator mnii(*dict);
    MemberNameInfo *mni;

    for (mnii.toFirst(); mni = mnii.current(); ++mnii)
    {
      MemberInfo *mi = mni->first();
      while (mi)
      {
        MemberDef *md=mi->memberDef;
        ClassDef *d1 = md->getClassDef();
        ClassDef *d2 = md->category();
				
        print(os, *md, indent_level + 1);
        os << std::endl;
        ClassDef *def = Doxygen::classSDict->find(md->typeString());
        if (def) print(os, *def, indent_level + 1);
				
        mi = mni->next();
      }
    }
    return os;
  }

  inline std::ostream& operator << (std::ostream& os, const ClassDef& def) 
  {
    os << def.name();
    if (!def.briefDescription().isEmpty())
      os << sep << def.briefDescription();
    os << std::endl;
    return print(os, def);
  }
}

#undef strlenは,内部で使っているqtoolsとMSVCとの衝突を避けるために入れている.上記のコードを,doxygen.cppのgenerateClassList関数の上に追加.そしてgenerateClassListの中に一行コードを追加する.

static void generateClassList(ClassSDict &classSDict)
{
  ClassSDict::Iterator cli(classSDict);

  for ( ; cli.current() ; ++cli )
  {
    ClassDef *cd=cli.current();
    std::cout << *cd; // この一行を追加

   // 以下略

これでビルドすれば,本来の機能を果たしつつ標準出力に何かしらの情報を吐き出せるdoxygenが出来る.今回のコードだと,こんなソースを解析すると

struct Bar {
  int k; ///< fugafuga.
  int l; ///< hogehoge.
};

struct Foo {
  int i;
  short j;
  Bar bar; ///< bar.
};

こんなのを吐き出すようになった.自分が欲しいのはCの構造体に対するテキストファイルだったので,メンバ関数の存在は考慮されていない.あと必要無かったので修飾子も吐いていない.手抜き.そこらへんも必要ならClassDefやMemberDefのgetterを使えば良いはず.

Bar bar
 int k fugafuga.
 int l hogehoge.
Foo
 int i
 short j
 Bar bar bar.
  int k fugafuga.
  int l hogehoge.

今回大したコードは書いてないので,ちょっと手を加えればYAMLでもJSONでも好きな形式に変更するのは簡単.デフォルトだと解析途中にメッセージも標準出力に吐き出すので,DoxyfileでQUIET=YESにしておくと良い.

まとめ

コード解析してちょっと何か出力したいなーという時,良いツールが無ければdoxygenに乗っかると楽できるかも.