導入
ソースコードからのドキュメント生成ツールDoxygen。多くのプログラミング言語と出力形式に対応しているため、使ったことのある方も多いんじゃないでしょうか。
この記事では、ドキュメントではなくコードの依存関係等をXML出力するためのDoxygen設定を紹介します。
出力のXSLT変換例も少し紹介します。
XML出力は、例えば以下のような独自のコード分析/解析やラッパーの自動生成等に活用できるかもしれません。
- 改修時の変更影響箇所の検討
- 関数呼び出し関係の見える化、コールグラフの作成
- グローバル変数への参照箇所の洗い出し
- 未使用関数や不要なincludeの洗い出し
- メトリクス測定
- 他言語バインディングの自動生成
- テストコードひな形の自動生成
- モックの自動生成
Doxygenの設定
Doxywizard(DoxygenのGUIフロントエンド)だと、Expertタブから以下のような感じです。
- Buildで、出力に含めたい要素をチェック
- Source Browserで、
REFERENCED_BY_RELATION
/REFERENCES_RELATION
にチェック - XMLで、
GENERATE_XML
をチェック。(XML_PROGRAMLISTING
チェックは外してもよい)
結果
前記の設定でdoxygenを実行すると、xmlディレクトリ以下にXMLファイルが出力されます。
XML内容について
自然言語での公式ドキュメントは無さそうです。
スキーマ定義はxml出力先に含まれます(index.xsd, xml.xsd)。
定義や参照関係は出ますが、関数内のブロック構造は出ないようです。
xml抜粋
<memberdef kind="define" id="aitc_8c_1a2512e9f94d9ab9e4c48b78400f9f2d44" prot="public" static="no">
<name>FIPNDH</name>
<param><defname>base</defname></param>
<initializer>((base)+0x60)</initializer>
<briefdescription>
</briefdescription>
<detaileddescription>
</detaileddescription>
<inbodydescription>
</inbodydescription>
<location file="aitc.c" line="79" column="9" bodyfile="aitc.c" bodystart="79" bodyend="-1" />
</memberdef>
...
<memberdef kind="function" id="aitc_8c_1a8353f48d9b6c62f1eaf43a0c0ac6ac9d" prot="public" static="yes" const="no" explicit="no" inline="no" virt="non-virtual">
<type>void</type>
<definition>static void int_source_change</definition>
<argsstring>(SigNode *node, int value, void *clientData)</argsstring>
<name>int_source_change</name>
<param>
<type><ref refid="struct_sig_node" kindref="compound">SigNode</ref> *</type>
<declname>node</declname>
</param>
<param>
<type>int</type>
<declname>value</declname>
</param>
<param>
<type>void *</type>
<declname>clientData</declname>
</param>
<briefdescription>
</briefdescription>
<detaileddescription>
</detaileddescription>
<inbodydescription>
</inbodydescription>
<location file="aitc.c" line="172" column="1" bodyfile="aitc.c" bodystart="172" bodyend="183" />
<references refid="struct_irq_trace_info_1ae85401bf0069a66b078436a712c5e550" compoundref="aitc_8c" startline="111">IrqTraceInfo::aitc</references>
<references refid="struct_aitc_1a920cf4d7d13a06eb8944a7af53eeaa5f" compoundref="aitc_8c" startline="100">Aitc::intsrc</references>
<references refid="struct_irq_trace_info_1af3d6abfd2d5ee95a490b9bb84230ed2a" compoundref="aitc_8c" startline="110">IrqTraceInfo::nr</references>
<references refid="signode_8h_1a4893a579f5b29db39ff56a83d3d0622a" compoundref="signode_8h" startline="24">SIG_LOW</references>
<references refid="aitc_8c_1a102505e8050a3b3e5066d02855ca1238" compoundref="aitc_8c" startline="115" endline="163">update_interrupts</references>
<referencedby refid="aitc_8c_1a1190b3c9b1a45bbd79159dae993001f1" compoundref="aitc_8c" startline="587" endline="623">Aitc_New</referencedby>
</memberdef>
xsd
<?xml version='1.0' encoding='utf-8' ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
<xsd:element name="doxygenindex" type="DoxygenType"/>
<xsd:complexType name="DoxygenType">
<xsd:sequence>
<xsd:element name="compound" type="CompoundType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="version" type="xsd:string" use="required"/>
<xsd:attribute ref="xml:lang" use="required"/>
</xsd:complexType>
<xsd:complexType name="CompoundType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="member" type="MemberType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="refid" type="xsd:string" use="required"/>
<xsd:attribute name="kind" type="CompoundKind" use="required"/>
</xsd:complexType>
<xsd:complexType name="MemberType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="refid" type="xsd:string" use="required"/>
<xsd:attribute name="kind" type="MemberKind" use="required"/>
</xsd:complexType>
<xsd:simpleType name="CompoundKind">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="class"/>
<xsd:enumeration value="struct"/>
<xsd:enumeration value="union"/>
<xsd:enumeration value="interface"/>
<xsd:enumeration value="protocol"/>
<xsd:enumeration value="category"/>
<xsd:enumeration value="exception"/>
<xsd:enumeration value="file"/>
<xsd:enumeration value="namespace"/>
<xsd:enumeration value="group"/>
<xsd:enumeration value="page"/>
<xsd:enumeration value="example"/>
<xsd:enumeration value="dir"/>
<xsd:enumeration value="type"/>
<xsd:enumeration value="concept"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="MemberKind">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="define"/>
<xsd:enumeration value="property"/>
<xsd:enumeration value="event"/>
<xsd:enumeration value="variable"/>
<xsd:enumeration value="typedef"/>
<xsd:enumeration value="enum"/>
<xsd:enumeration value="enumvalue"/>
<xsd:enumeration value="function"/>
<xsd:enumeration value="signal"/>
<xsd:enumeration value="prototype"/>
<xsd:enumeration value="friend"/>
<xsd:enumeration value="dcop"/>
<xsd:enumeration value="slot"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
<?xml version='1.0'?>
<xsd:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xml:lang="en">
<xsd:attribute name="lang" type="xsd:language">
</xsd:attribute>
<xsd:attribute name="space" default="preserve">
<xsd:simpleType>
<xsd:restriction base="xsd:NCName">
<xsd:enumeration value="default"/>
<xsd:enumeration value="preserve"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attributeGroup name="specialAttrs">
<xsd:attribute ref="xml:lang"/>
<xsd:attribute ref="xml:space"/>
</xsd:attributeGroup>
</xsd:schema>
おまけ
XMLファイル結合用PowerShellバッチ
XMLファイルはソースファイルごとに生成されるため、そのままだと扱いづらいです。
1ファイルにまとめる用のXSLT/XML(combine.xslt
/index.xml
)も出力されるため、これを使ってまとめると扱いやすくなります。
Linuxだとxlstprocを使うのが手軽です。xsltproc -o combined.xml xml/combine.xslt xml/index.xml
WindowsだとXSLT処理させるのが面倒なため、以下のようなPowerShellバッチを用意しておくとお手軽便利です。(.NETのXSLT処理系を使うため、別途XSLT処理系の準備が不要となります)
XMLファイル結合用PowerShellバッチ
Doxygen結果のxml格納フォルダを引数で渡すと、xml格納フォルダの隣にxml.xmlファイルを出力します。
# DoxygenのXMLを結合する
# 引数: Doxygen結果のxml格納フォルダ
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$xml_dir
)
# お約束
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$WarningPreference = "Continue"
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
trap {
$Error | foreach {
Write-Debug $_.Exception.Message
Write-Debug $_.InvocationInfo.PositionMessage
}
Exit -1
}
#### 処理
# パス準備
$xml_dir = (Convert-Path $xml_dir)
[string]$xslt_path = (Join-Path $xml_dir combine.xslt)
[string]$index_path = (Join-Path $xml_dir index.xml)
[string]$combined_path = ($xml_dir + ".xml")
# 存在チェック
if (-not (Test-Path $xslt_path)) {
Write-Host ("Error File Not Found: " + $xslt_path) -ForegroundColor red
Exit -1
}
if (-not (Test-Path $index_path)) {
Write-Host ("Error File Not Found: " + $index_path) -ForegroundColor red
Exit -1
}
# 結合
# XSLT読み込み
$xslt = New-Object System.Xml.Xsl.XslCompiledTransform
$xslt_setting = New-Object System.Xml.Xsl.XsltSettings
$xslt_setting.EnableDocumentFunction = $true
$xslt_setting.EnableScript = $true
$xslt.Load($xslt_path, $xslt_setting, $null)
# 出力準備
$writer_setting = New-Object System.Xml.XmlWriterSettings
$writer_setting.Indent = $true
$writer = [System.Xml.XmlWriter]::Create($combined_path, $writer_setting)
# 変換
$xslt.Transform($index_path, $writer)
# 終了
$writer.Close()
Write-Host ("Complete Combine: " + $combined_path) -ForegroundColor green
XSLT
XMLファイルはXSLTで変換/整形できます。
いくつかXSLT例を紹介します。
要素一覧CSV
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="Shift_JIS" />
<xsl:param name="delim" select="'	'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/doxygen">
<xsl:text>#id</xsl:text><xsl:value-of select="$delim" />
<xsl:text>kind</xsl:text><xsl:value-of select="$delim" />
<xsl:text>static</xsl:text><xsl:value-of select="$delim" />
<xsl:text>name</xsl:text><xsl:value-of select="$delim" />
<xsl:text>file</xsl:text><xsl:value-of select="$delim" />
<xsl:text>line</xsl:text><xsl:value-of select="$break" />
<xsl:for-each select="//memberdef">
<xsl:value-of select="./@id" /><xsl:value-of select="$delim" />
<xsl:value-of select="./@kind" /><xsl:value-of select="$delim" />
<xsl:value-of select="./@static" /><xsl:value-of select="$delim" />
<xsl:value-of select="./name" /><xsl:value-of select="$delim" />
<xsl:value-of select="./location/@file" /><xsl:value-of select="$delim" />
<xsl:value-of select="./location/@line" /><xsl:value-of select="$break" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
関数呼び出し一覧CSV
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="Shift_JIS" />
<xsl:param name="delim" select="'	'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/doxygen">
<xsl:text>#src_id</xsl:text><xsl:value-of select="$delim" />
<xsl:text>dst_id</xsl:text><xsl:value-of select="$break" />
<xsl:for-each select="//memberdef[@kind="function"]/references">
<xsl:value-of select="../@id" /><xsl:value-of select="$delim" />
<xsl:value-of select="./@refid" /><xsl:value-of select="$break" />
</xsl:for-each>
<xsl:for-each select="//memberdef[@kind="function"]/referencedby">
<xsl:value-of select="./@refid" /><xsl:value-of select="$delim" />
<xsl:value-of select="../@id" /><xsl:value-of select="$break" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XSLT処理用Windowsバッチ
XSLT処理用PowerShellバッチ
結合済みのXMLファイルを引数で渡すと、バッチと同名のXSLT(XSLT処理.ps1ならXSLT処理.xslt)を適用して、結果をout.csvに出力します。
# 引数: Doxygenの結合XML
# 出力: out.csv
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$xml_path
)
# お約束
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$WarningPreference = "Continue"
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
trap {
$Error | foreach {
Write-Debug $_.Exception.Message
Write-Debug $_.InvocationInfo.PositionMessage
}
Exit -1
}
#### 処理
# パス準備
$xml_path = (Convert-Path $xml_path)
[string]$xslt_path = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, ".xslt")
[string]$out_path = [System.IO.Path]::ChangeExtension($xml_path, "out.tsv")
# 存在チェック
if (-not (Test-Path $xslt_path)) {
Write-Host ("Error File Not Found: " + $xslt_path) -ForegroundColor red
Exit -1
}
# 結合
# XSLT読み込み
$xslt = New-Object System.Xml.Xsl.XslCompiledTransform
$xslt_setting = New-Object System.Xml.Xsl.XsltSettings
$xslt_setting.EnableDocumentFunction = $true
$xslt_setting.EnableScript = $true
$xslt.Load($xslt_path, $xslt_setting, $null)
# 変換
$xslt.Transform($xml_path, $out_path)
# 終了
Write-Host ("Complete: " + $out_path) -ForegroundColor green
もっと知りたい方向け資料
以下のような資料を見つけました。
私の興味と異なるためあまり読み込んでいませんが、皆さんの参考にはなるかもしれません。
doxygen/addon/doxmlparser at master · doxygen/doxygen
doxygenのXML出力の解析サンプル(python)
XML/XSLT examples : Doxygen: Helper tools and scripts
XML/XSLTのサンプル紹介
C言語の構造体を構造分析したい〜静的解析としての doxygen
XMLから、C言語構造体の構造を JSON で吐き出すスクリプト(ruby)
DoxygenのXML出力 - unyaunyaの日記
XMLから、CSVで関数・変数の参照を出力(python)
なお、もっとしっかり解析したい方には、フリーだとclang/LLVM系ツールが おすすめです。
https://clang.llvm.org/docs/Tooling.html