doxygenでIF仕様書を作る


# 目的

昔書いた仕様書が更新されなくなって、仕様と実装が乖離する・・・なんてことが起きないようにしたい

# 手段

嫌でも目に入る場所に仕様を書いておくことで乖離した場合に気づくようにする。ヘッダファイルに使い方や仕様を書いて、doxygenでIF仕様を生成してみよう。

## doxygenの使い方
出力フォーマットがいろいろあるらしい。

| フォーマット | 考察 |
|---|---|
|html|見やすいけど、複数ファイルできてしまう。配布には向かない。|
|html→pdf|pdf化することで、単ファイルに集約。ただ、diffをとりづらいので、前回からの変更点に注目したい場合ちょっとしんどい。|
|rtf|見づらい。|
|xml|パースめんどくさい。|
|latex|試してない。|
|docbook|privateメソッドも問答無用で出力される。|

htmlかxmlかdocbookかで悩んだが、結局xml+xsltで実現することにしてみた。


## 手順

cygwin環境だとxsltprocをインストールしないといけない。
libxsltってパッケージを入れた気がする。

doxygenの設定ファイルを生成して、以下の場所だけ変更する

```:doxy_interface
GENERATE_XML           = YES
```

```bash
doxygen doxy_interface
xsltproc xml/combine.xslt xml/index.xml >xml/all.xml #xmlが複数ファイルに分割されているので統合する
sed -i -e "2i \<\?xml-stylesheet type=\"text\/xsl\" href=\"sample\.xsl\"\?\>" xml/all.xml #使用するstylesheetを追記
```

xsltを以下のような感じで作成する。

```xslt:sample.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="html" encoding="UTF-8"/>

  <xsl:template match="/">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="doxygen">
    <HTML>
    <head>
    <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=9"/>
    <meta name="generator" content="Doxygen 1.8.11"/>
    <link href="tabs.css" rel="stylesheet" type="text/css"/>
    <link href="search/search.css" rel="stylesheet" type="text/css"/>
    <link href="doxygen.css" rel="stylesheet" type="text/css" />
    </head>
    <BODY>
    <xsl:apply-templates/>
    </BODY>
    </HTML>
  </xsl:template>

  <xsl:template match="compounddef[@kind='class'][@prot='public']">
    <div id="nav-path" class="navpath">
      <ul>
        <li class="navelem">
            <xsl:value-of select="compoundname"/>
        </li>
      </ul>
    </div>
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="text()">
    <p><xsl:value-of select="."/></p>
  </xsl:template>
  <xsl:template match="parameterlist[@kind='param']">
    <dl class="params">
        <dt>引数</dt>
        <dd>
        <table class="params">
            <xsl:apply-templates/>
        </table>
        </dd>
    </dl>
  </xsl:template>
  <xsl:template match="parameteritem">
    <tr>
        <td class="paramdir">[<xsl:value-of select="parameternamelist/parametername/@direction"/>]</td>
        <td class="paramname"><xsl:value-of select="parameternamelist/parametername"/></td>
        <td><xsl:value-of select="parameterdescription/para"/></td>
    </tr>
  </xsl:template>
  
  <xsl:template match="sectiondef[@kind='public-func']"><xsl:apply-templates/></xsl:template>
  <xsl:template match="sectiondef[@kind='public-attrib']"><xsl:apply-templates/></xsl:template>
  <xsl:template match="sectiondef[@kind='public-type']">
    <div class="memproto">
        <table class="memname">
        <tr>
            <td class="memname">
                <xsl:value-of select="memberdef/@kind"/>　                
                <xsl:value-of select="../compoundname"/>::<xsl:value-of select="memberdef/name"/>
            </td>
        </tr>
        </table>
    </div>
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="memberdef[@kind='function']|memberdef[@kind='variable']">
    <div class="memproto">
        <table class="memname">
        <tr>
        <td class="memname">
            <xsl:value-of select="definition"/>
            <xsl:value-of select="argsstring"/>
        </td>
        </tr>
    </table>
    </div>
    <div class="memdoc">
        <xsl:apply-templates/>
    </div>
  </xsl:template>
  <xsl:template match="memberdef[@kind='enum']">
    <div class="memdoc">
        <table class="fieldtable">
            <tr>
                <th colspan="2">列挙値</th>
            </tr>
            <xsl:apply-templates select="enumvalue"/>
        </table>
    </div>
  </xsl:template>
  <xsl:template match="enumvalue">
    <tr>
        <td class="fieldname"><xsl:value-of select="name"/></td>
        <td class="fielddoc"><p><xsl:value-of select="briefdescription/para"/> </p></td>
    </tr>
  </xsl:template>
  <xsl:template match="simplesect">
    <xsl:element name="dl">
        <xsl:attribute name="class">
            <xsl:value-of select="concat('section ', @kind)"/>
        </xsl:attribute>
        <dt><xsl:value-of select="@kind"/></dt>
        <dd><xsl:value-of select="para"/></dd>
    </xsl:element>
    
  </xsl:template>
  <xsl:template match="xrefsect">
    <xsl:element name="dl">
        <xsl:attribute name="class">
            <xsl:value-of select="substring-before(@id,'_')"/>
        </xsl:attribute>
        <dt><xsl:value-of select="xreftitle"/></dt>
        <dd><xsl:value-of select="xrefdescription"/></dd>
    </xsl:element>
  </xsl:template>
  <xsl:template match="briefdescription"><xsl:apply-templates/></xsl:template>
  <xsl:template match="detaileddescription"><xsl:apply-templates/></xsl:template>
  <xsl:template match="para"><xsl:apply-templates/></xsl:template>
  <!-- 無視する要素 -->
  <xsl:template match="*[@prot='private']"/>
  <xsl:template match="compoundname"/>
  <xsl:template match="listofallmembers"/>
  <xsl:template match="compounddef[@kind='file']"/>
  <xsl:template match="compounddef[@kind='page']"/>
  <xsl:template match="*[@kind='private-func']"/>
  <xsl:template match="*[@kind='private-attrib']"/>
  <xsl:template match="definition"/>
  <xsl:template match="argsstring"/>
  <xsl:template match="type"/>
  <xsl:template match="name"/>
  <xsl:template match="param"/>
  <xsl:template match="declname"/>
</xsl:stylesheet>
```

cssはhtml生成したときに吐き出されていたものをコピーして利用している。
後はブラウザでall.xmlを開けば表示されるはず。

diffをとりたければxmlでdiffとればよろし。

enum、remark、warning、todo、deprecatedに対応してみた。
structはC++においてIFにしてたら設計がおかしいはずなんで対応しないことにしよう。

実際使ってみたらドキュメント付けされていない場所もxmlに出力されていることに気が付いた。
不要な部分は削る必要がありそう。C#はnoobなのでぐちゃぐちゃだが、不要な部分を削るプログラムをC#で書いてみた。

```csharp:Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace doxyparser
{
    class Program
    {
        static bool isRemove(XmlNode node)
        {
            if (node.LocalName == "location" || node.LocalName == "listofallmembers")
            {
                return true;
            }
            else if (node.LocalName == "xrefsect")
            {
                return false;
            }
            else if (node.Attributes != null)
            {
                for (int i = 0; i < node.Attributes.Count; i++)
                {
                    XmlAttribute attr = node.Attributes[i];
                    Console.Write(attr.LocalName + ": ");
                    Console.WriteLine(attr.Value);
                    if (attr.Value.Contains("private"))
                    {
                        Console.WriteLine(" ->delete");
                        return true;
                    }
                    else if (attr.LocalName == "refid")
                    {
                        node.Attributes.Remove(attr);
                        --i;
                    }
                    else if (attr.LocalName == "id")
                    {
                        if (attr.Value == "todo" || attr.Value == "deprecated")
                        {
                            return true;
                        }
                        else
                        {
                            node.Attributes.Remove(attr);
                            --i;
                        }
                    }
                }
            }
            return false;
        }
        static bool search(XmlNode node)
        {
            bool result = isRemove(node);
            if (result)
            {
                node.ParentNode.RemoveChild(node);
            }
            for (int i = 0; i < node.ChildNodes.Count; i++)
            {
                XmlNode child = node.ChildNodes[i];
                if (search(child))
                {
                    --i;
                }
            }
            return result;
        }
        static void Main(string[] args)
        {
          XmlDocument xmlDocument = new XmlDocument();
          xmlDocument.Load(args[0]);
          elem_ = xmlDocument.DocumentElement;

          for (int i = 0; i < elem_.ChildNodes.Count; i++)
          {
              XmlNode child = elem_.ChildNodes[i];
             if( search(child))
             {
                --i;
             }
          }
          xmlDocument.Save("modified.xml");
            
        }
        static XmlElement elem_;
    }
}
```