Doxygen 関連図の Markdown 挿入

このドキュメントでは、Doxygen が生成するグラフ情報を Markdown 版ドキュメントに PlantUML 形式で自動挿入する仕組みについて説明します。

1 背景

Doxygen は HTML ドキュメント生成時に Graphviz (dot) を用いてコールグラフ、インクルード依存図、コラボレーション図などの SVG 画像を生成します。しかし、Doxybook2 を経由した Markdown 変換ではこれらのグラフ情報が失われます。

Doxybook2 はグラフデータをテンプレート変数として公開しておらず、プロジェクト自体も 2022 年にアーカイブされているため、今後の機能追加は見込めません。

本機能は、Doxygen XML に含まれるグラフ構造データを解析し、PlantUML 形式に変換して Markdown ドキュメントに挿入することで、この課題を解決します。

2 処理パイプライン

グラフ抽出は、既存のドキュメント生成パイプラインに統合されています。

グラフ抽出を含むドキュメント生成パイプライン

make docs を実行すると、extract-graphs.pypreprocess.sh の前に自動的に呼び出されます。生成された <plantuml> タグは、既存の PlantUML 変換パイプラインによって Markdown コードフェンスに変換されます。

3 対応するグラフの種類

3.1 ファイルレベルのグラフ

グラフ種別 XML ソース要素 説明
インクルード依存 incdepgraph 対象ファイルがインクルードしているファイルの関係を示します
被インクルード関係 invincdepgraph 対象ファイルをインクルードしているファイルの関係を示します

3.2 クラス/構造体レベルのグラフ

グラフ種別 XML ソース要素 説明
継承関係 inheritancegraph クラスの継承階層を示します
コラボレーション図 collaborationgraph クラス/構造体のメンバ型の関連を示します

3.3 関数レベルのグラフ

グラフ種別 XML ソース要素 説明
コールグラフ references 対象関数が呼び出す関数を示します
呼び出し元グラフ referencedby 対象関数を呼び出す関数を示します

4 Doxygen XML のグラフデータ構造

4.1 graphType 要素

incdepgraphinvincdepgraphinheritancegraphcollaborationgraph は共通の graphType 構造を持ちます。

<incdepgraph>
  <node id="1">
    <label>calculator.c</label>
    <link refid="calculator_8c"/>
    <childnode refid="2" relation="include"/>
    <childnode refid="3" relation="include"/>
  </node>
  <node id="2">
    <label>calculator.h</label>
    <link refid="calculator_8h"/>
  </node>
  <node id="3">
    <label>stdio.h</label>
  </node>
</incdepgraph>

node 要素がグラフのノードを定義し、childnode 要素がノード間のエッジを定義します。relation 属性はエッジの種類を表します。

relation 属性の値と PlantUML 矢印の対応は以下の通りです。

relation 値 意味 PlantUML 矢印
include インクルード関係 -->
usage メンバ使用関係 ..>
public-inheritance public 継承 --\|>
protected-inheritance protected 継承 --\|>
private-inheritance private 継承 --\|>

4.2 references / referencedby 要素

関数間の呼び出し関係は、memberdef 要素の子要素として格納されます。

<memberdef kind="function" id="calculator_8c_1a001">
  <name>add</name>
  <!-- 略 -->
  <references refid="calculator_8c_1a005" compoundref="calculator_8c"
              startline="10" endline="10">validate_input</references>
  <referencedby refid="main_8c_1a099" compoundref="main_8c"
                startline="25" endline="25">main</referencedby>
</memberdef>

references は対象関数が呼び出す関数を、referencedby は対象関数を呼び出す関数を表します。

これらの要素を XML に含めるには、Doxyfile で以下の設定が必要です。

REFERENCED_BY_RELATION = YES
REFERENCES_RELATION    = YES

5 生成される PlantUML の例

5.1 コールグラフ

関数 dividevalidate_inputcheck_zero を呼び出す場合、以下の PlantUML が生成されます。

divide の呼び出し先

5.2 呼び出し元グラフ

関数 addmaintest_add から呼び出される場合、以下の PlantUML が生成されます。

add の呼び出し元

5.3 インクルード依存グラフ

calculator.ccalculator.hstdio.h をインクルードする場合、以下の PlantUML が生成されます。

calculator.c のインクルード元

5.4 コラボレーション図

構造体 UserInfochar * 型のメンバを持つ場合、以下の PlantUML が生成されます。

UserInfo のコラボレーション図

6 設定と制御

6.1 グラフ生成の制御

グラフの生成は Doxyfile の設定によって制御されます。

Doxyfile 設定 対応するグラフ
INCLUDE_GRAPH = YES インクルード依存グラフ
INCLUDED_BY_GRAPH = YES 被インクルード関係グラフ
CLASS_GRAPH = YES 継承グラフ
COLLABORATION_GRAPH = YES コラボレーション図
REFERENCES_RELATION = YES コールグラフ
REFERENCED_BY_RELATION = YES 呼び出し元グラフ

これらの設定を NO にすると、対応する XML 要素が生成されなくなり、Markdown への図の挿入もスキップされます。

6.2 ノード数の上限

extract-graphs.py では、グラフあたりの最大ノード数を 50 に設定しています。この値は Doxygen の DOT_GRAPH_MAX_NODES (デフォルト 50) に合わせています。この値を超えるグラフは生成をスキップします。上限値はスクリプト先頭の DOT_GRAPH_MAX_NODES 定数で変更できます。

DOT_GRAPH_MAX_NODES = 50

6.3 インクルードグラフのラベル表示モード

インクルード依存グラフ・被インクルード関係グラフのノードラベルをファイル名のみにするかフルパスにするかを制御できます。スクリプト先頭の INC_GRAPH_LABEL_BASENAME_ONLY 定数で設定します。

INC_GRAPH_LABEL_BASENAME_ONLY = True

デフォルト値は True (ファイル名のみ) です。同一ファイル名が別フォルダに存在しない前提でこのモードを利用することを推奨します。フルパスで区別が必要な場合は False に変更してください。

6.4 スキップ条件

以下の条件に該当する場合、グラフの生成はスキップされます。

  • ノード数が MAX_GRAPH_NODES を超える場合
  • エッジが 1 つもない場合 (ノードが 1 つだけで関係のないグラフ)
  • index.xmlcombine.xslt 等のインデックスファイル

7 技術的な詳細

7.1 XML 書式の保持

extract-graphs.py は XML の解析に正規表現ベースのテキスト操作を使用しています。ElementTree 等の XML パーサを用いた場合、ファイル再書き込み時に属性の順序変更、空要素の展開、名前空間宣言の変更などが発生する可能性があります。正規表現による操作は、変更箇所以外の XML 書式を完全に保持します。

7.2 挿入位置

生成された <plantuml> タグは、対象要素の <detaileddescription> 閉じタグの直前に <simplesect kind="par"> 要素で囲んで挿入されます。Doxybook2 の details.tmpl はこれを par セクションとして処理し、#### 見出し付きの独立セクションとして出力します。

compound レベルのグラフ (インクルード依存図等) は、content.rfind() により compounddef 自身の </detaileddescription> を検索するため、memberdef 内ではなく正しい位置に挿入されます。

<detaileddescription>
  <para>既存の説明テキスト</para>
  <para><simplesect kind="par"><title>... の呼び出し先</title><para><plantuml>
caption ... の呼び出し先
rectangle "**...**" as current #LightBlue
...
</plantuml></para></simplesect></para>     ← ここに挿入
</detaileddescription>

7.3 PlantUML 変換の流れ

挿入された <plantuml> タグは、preprocess.sh の既存の変換ルールによって Markdown コードフェンスに変換されます。

変換前 (XML):   <plantuml>caption Title ...</plantuml>
変換後 (テキスト): ```plantuml\n@startuml\ncaption Title ...@enduml\n```

doxybook2 はこのテキストを Markdown にそのまま出力するため、最終的な Markdown ファイルに PlantUML コードブロックが含まれます。