LibreOfficeCalcに関数候補表示機能を付けるまで 第4回 フォルダ名でアタリを付ける

#前回のまとめ
それっぽいところを探してひたすら読んでいったが、完全に無駄足だった。

#関数の説明を探す
##フォルダのアタリを付ける
前回「ポイントを絞って探さないと関係ないやつに殺されて死ぬ」ということを学んだので、今回はちゃんとフォルダ名からアタリを付けてやってみる。
sc/sourceフォルダの中身。

```sh:shell
$ cd sc/source/
$ ls
core  filter  ui
$ cd filter/
$ ls
chart  excel   html                  inc    oox    qpro  services.cxx  xcl97
dif    ftools  importfilterdata.cxx  lotus  orcus  rtf   starcalc      xml
```
`importfilterdata.cxx`なるファイルがあり、excel、ooxなどの名前のフォルダが並び、その中のooxフォルダは前回読んだように外部ファイルを読み込むためのもの。
つまり、filterフォルダは「odsファイル以外のファイルを読み込むためのデータ」が入ったフォルダだと予想。
ということは、求めるフォルダはここじゃない！！

```sh:shell
$ cd sc/source/core/
$ ls
data  inc  opencl  src  tool
```
普通に考えるなら、openclフォルダは無関係で、srcフォルダが正規ルート。でも、前回の検索でちょくちょくopenclフォルダにあやしそうなファイルがあったんだよなー…

```sh:shell
$ cd sc/source/core/src
$ ls
compiler.src
```
`compiler.src`の中身を見てみたが、いかにも無関係そうなファイルだった。じゃあdataかな？

```sh:shell
$ cd sc/source/core/data
$ ls
attarray.cxx         document10.cxx            listenercontext.cxx
attrib.cxx           documentimport.cxx        markarr.cxx
autonamecache.cxx    documentstreamaccess.cxx  markdata.cxx
bcaslot.cxx          dpcache.cxx               mtvelements.cxx
bigrange.cxx         dpdimsave.cxx             olinetab.cxx
celltextattr.cxx     dpfilteredcache.cxx       pagepar.cxx
cellvalue.cxx        dpglobal.cxx              patattr.cxx
cellvalues.cxx       dpgroup.cxx               pivot2.cxx
clipcontext.cxx      dpitemdata.cxx            poolhelp.cxx
clipparam.cxx        dpnumgroupinfo.cxx        postit.cxx
colorscale.cxx       dpobject.cxx              refupdatecontext.cxx
column.cxx           dpoutput.cxx              rowheightcontext.cxx
column2.cxx          dpoutputgeometry.cxx      segmenttree.cxx
column3.cxx          dpresfilter.cxx           sheetevents.cxx
column4.cxx          dpsave.cxx                simpleformulacalc.cxx
columniterator.cxx   dpsdbtab.cxx              sortparam.cxx
columnset.cxx        dpshttab.cxx              stlpool.cxx
columnspanset.cxx    dptabdat.cxx              stlsheet.cxx
compressedarray.cxx  dptabres.cxx              subtotalparam.cxx
conditio.cxx         dptabsrc.cxx              tabbgcolor.cxx
dbdocutl.cxx         dputil.cxx                table1.cxx
dociter.cxx          drawpage.cxx              table2.cxx
docparam.cxx         drwlayer.cxx              table3.cxx
docpool.cxx          edittextiterator.cxx      table4.cxx
documen2.cxx         fillinfo.cxx              table5.cxx
documen3.cxx         formulacell.cxx           table6.cxx
documen4.cxx         formulaiter.cxx           table7.cxx
documen5.cxx         funcdesc.cxx              tabprotection.cxx
documen6.cxx         funcdesc_backup.cxx       types.cxx
documen7.cxx         global.cxx                userdat.cxx
documen8.cxx         global2.cxx               validat.cxx
documen9.cxx         globalx.cxx
document.cxx         grouptokenconverter.cxx
```
うーん、いかにも怪しそう。

##それっぽいファイルを発見
さて、山のように引っかかるのをわかってて、あえてこのフォルダで「function」で検索してみる。
大量の検索結果の中で、`funcdesc.cxx`というファイルを発見。functionのdescriptionという名前が非常に目的と近いので、これを見ていくことにする。

```cpp:/sc/source/core/data/funcdesc.cxx
// class ScFuncDesc:

ScFuncDesc::ScFuncDesc() :
        pFuncName       (NULL),
        pFuncDesc       (NULL),
        pDefArgFlags    (NULL),
        nFIndex         (0),
        nCategory       (0),
        nArgCount       (0),
        bIncomplete     (false),
        bHasSuppressedArgs(false)
{}
```
Calcを起動して適当なセルに「=」を入力すると、このコンストラクタのブレークポイントが発火！
コンストラクタをデバッガのfinコマンドで抜けると、呼び出し元はScFunctionListクラスのコンストラクタだった。

```cpp:/sc/source/core/data/funcdesc.cxx
ScFunctionList::ScFunctionList() :
        nMaxFuncNameLen ( 0 )
{
    ScFuncDesc* pDesc = NULL;
    sal_Int32 nStrLen = 0;
    ::std::list<ScFuncDesc*> tmpFuncList;
    sal_uInt16 nDescBlock[] =
    {
        RID_SC_FUNCTION_DESCRIPTIONS1,
        RID_SC_FUNCTION_DESCRIPTIONS2
    };

    for (sal_uInt16 k = 0; k < SAL_N_ELEMENTS(nDescBlock); ++k)
    {
        boost::scoped_ptr<ScResourcePublisher> pBlock( new ScResourcePublisher( ScResId( nDescBlock[k] ) ) );
        // Browse for all possible OpCodes. This is not the fastest method, but
        // otherwise the sub resources within the resource blocks and the
        // resource blocks themselves would had to be ordered according to
        // OpCodes, which is utopian...
        for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
        {
            ScResId aRes(i);
            aRes.SetRT(RSC_RESOURCE);
            // Sub resource of OpCode available?
            if (pBlock->IsAvailableRes(aRes))
            {
                pDesc = new ScFuncDesc;
                bool bSuppressed = false;
                ScFuncRes aSubRes( aRes, pDesc, bSuppressed);
                // Instead of dealing with this exceptional case at 1001 places
                // we simply don't add an entirely suppressed function to the
                // list and delete it.
                if (bSuppressed)
                    delete pDesc;
                else
                {
                    pDesc->nFIndex = i;
                    tmpFuncList.push_back(pDesc);

                    nStrLen = (*(pDesc->pFuncName)).getLength();
                    if (nStrLen > nMaxFuncNameLen)
                        nMaxFuncNameLen = nStrLen;
                }
            }
        }
    }

    sal_uInt16 nNextId = SC_OPCODE_LAST_OPCODE_ID + 1; // FuncID for AddIn functions

    // Interpretation of AddIn list
    OUString aDefArgNameValue   = "value";
    OUString aDefArgNameString  = "string";
    OUString aDefArgNameValues  = "values";
    OUString aDefArgNameStrings = "strings";
    OUString aDefArgNameCells   = "cells";
    OUString aDefArgNameNone    = "none";
    OUString aDefArgDescValue   = "a value";
    OUString aDefArgDescString  = "a string";
    OUString aDefArgDescValues  = "array of values";
    OUString aDefArgDescStrings = "array of strings";
    OUString aDefArgDescCells   = "range of cells";
    OUString aDefArgDescNone    = "none";

    OUString aArgName, aArgDesc;
    const FuncCollection& rFuncColl = *ScGlobal::GetFuncCollection();
    FuncCollection::const_iterator it = rFuncColl.begin(), itEnd = rFuncColl.end();
    for (; it != itEnd; ++it)
    {
        const FuncData* pAddInFuncData = it->second;
        pDesc = new ScFuncDesc;
        sal_uInt16 nArgs = pAddInFuncData->GetParamCount() - 1;
        pAddInFuncData->getParamDesc( aArgName, aArgDesc, 0 );
        pDesc->nFIndex     = nNextId++; //  ??? OpCode vergeben
        pDesc->nCategory   = ID_FUNCTION_GRP_ADDINS;
        pDesc->pFuncName   = new OUString(pAddInFuncData->GetInternalName().toAsciiUpperCase());

        OUStringBuffer aBuf(aArgDesc);
        aBuf.append('\n');
        aBuf.appendAscii("( AddIn: ");
        aBuf.append(pAddInFuncData->GetModuleName());
        aBuf.appendAscii(" )");
        pDesc->pFuncDesc = new OUString(aBuf.makeStringAndClear());

        pDesc->nArgCount   = nArgs;
        if (nArgs)
        {
            pDesc->maDefArgNames.clear();
            pDesc->maDefArgNames.resize(nArgs);
            pDesc->maDefArgDescs.clear();
            pDesc->maDefArgDescs.resize(nArgs);
            pDesc->pDefArgFlags  = new ScFuncDesc::ParameterFlags[nArgs];
            for (sal_uInt16 j = 0; j < nArgs; ++j)
            {
                pDesc->pDefArgFlags[j].bOptional = false;
                pDesc->pDefArgFlags[j].bSuppress = false;
                pAddInFuncData->getParamDesc( aArgName, aArgDesc, j+1 );
                if ( !aArgName.isEmpty() )
                    pDesc->maDefArgNames[j] = aArgName;
                else
                {
                    switch (pAddInFuncData->GetParamType(j+1))
                    {
                        case ParamType::PTR_DOUBLE:
                            pDesc->maDefArgNames[j] = aDefArgNameValue;
                            break;
                        case ParamType::PTR_STRING:
                            pDesc->maDefArgNames[j] = aDefArgNameString;
                            break;
                        case ParamType::PTR_DOUBLE_ARR:
                            pDesc->maDefArgNames[j] = aDefArgNameValues;
                            break;
                        case ParamType::PTR_STRING_ARR:
                            pDesc->maDefArgNames[j] = aDefArgNameStrings;
                            break;
                        case ParamType::PTR_CELL_ARR:
                            pDesc->maDefArgNames[j] = aDefArgNameCells;
                            break;
                        default:
                            pDesc->maDefArgNames[j] = aDefArgNameNone;
                            break;
                    }
                }
                if ( !aArgDesc.isEmpty() )
                    pDesc->maDefArgDescs[j] = aArgDesc;
                else
                {
                    switch (pAddInFuncData->GetParamType(j+1))
                    {
                        case ParamType::PTR_DOUBLE:
                            pDesc->maDefArgDescs[j] = aDefArgDescValue;
                            break;
                        case ParamType::PTR_STRING:
                            pDesc->maDefArgDescs[j] = aDefArgDescString;
                            break;
                        case ParamType::PTR_DOUBLE_ARR:
                            pDesc->maDefArgDescs[j] = aDefArgDescValues;
                            break;
                        case ParamType::PTR_STRING_ARR:
                            pDesc->maDefArgDescs[j] = aDefArgDescStrings;
                            break;
                        case ParamType::PTR_CELL_ARR:
                            pDesc->maDefArgDescs[j] = aDefArgDescCells;
                            break;
                        default:
                            pDesc->maDefArgDescs[j] = aDefArgDescNone;
                            break;
                    }
                }
            }
        }

        tmpFuncList.push_back(pDesc);
        nStrLen = (*(pDesc->pFuncName)).getLength();
        if ( nStrLen > nMaxFuncNameLen)
            nMaxFuncNameLen = nStrLen;
    }

    // StarOne AddIns

    ScUnoAddInCollection* pUnoAddIns = ScGlobal::GetAddInCollection();
    long nUnoCount = pUnoAddIns->GetFuncCount();
    for (long nFunc=0; nFunc<nUnoCount; nFunc++)
    {
        pDesc = new ScFuncDesc;
        pDesc->nFIndex = nNextId++;

        if ( pUnoAddIns->FillFunctionDesc( nFunc, *pDesc ) )
        {
            tmpFuncList.push_back(pDesc);
            nStrLen = (*(pDesc->pFuncName)).getLength();
            if (nStrLen > nMaxFuncNameLen)
                nMaxFuncNameLen = nStrLen;
        }
        else
            delete pDesc;
    }

    //Move list to vector for better random access performance
    ::std::vector<const ScFuncDesc*> tmp(tmpFuncList.begin(), tmpFuncList.end());
    tmpFuncList.clear();
    aFunctionList.swap(tmp);

    //Initialize iterator
    aFunctionListIter = aFunctionList.end();
}

```
コメントや変数名などから察するに、どうやら前半で普通の（？）関数を読み込んで、後半でアドインで追加された関数を読み込んでいるっぽい。今回は前半の方に興味があるので

```
ScFuncRes aSubRes( aRes, pDesc, bSuppressed);
```
に着目してみる（ここくらいしか`pDesc`に作用してそうな部分がない）。

```cpp:/sc/source/core/data/funcdesc.cxx
// class ScFuncRes:

ScFuncRes::ScFuncRes( ResId &aRes, ScFuncDesc* pDesc, bool & rbSuppressed )
 : Resource(aRes)
{
    rbSuppressed = (bool)GetNum();
    pDesc->nCategory = GetNum();
    pDesc->sHelpId = ReadByteStringRes();
    pDesc->nArgCount = GetNum();
    sal_uInt16 nArgs = pDesc->nArgCount;
    if (nArgs >= PAIRED_VAR_ARGS)
        nArgs -= PAIRED_VAR_ARGS - 2;
    else if (nArgs >= VAR_ARGS)
        nArgs -= VAR_ARGS - 1;
    if (nArgs)
    {
        pDesc->pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgs];
        for (sal_uInt16 i = 0; i < nArgs; ++i)
        {
            pDesc->pDefArgFlags[i].bOptional = (bool)GetNum();
        }
    }
    // Need to read the value from the resource even if nArgs==0 to advance the
    // resource position pointer, so this can't be in the if(nArgs) block above.
    sal_uInt16 nSuppressed = GetNum();
    if (nSuppressed)
    {
        if (nSuppressed > nArgs)
        {
            SAL_WARN("sc.core", "ScFuncRes: suppressed parameters count mismatch on OpCode " <<
                    aRes.GetId() << ": suppressed " << nSuppressed << " > params " << nArgs);
            nSuppressed = nArgs;    // sanitize
        }
        for (sal_uInt16 i = 0; i < nSuppressed; ++i)
        {
            sal_uInt16 nParam = GetNum();
            if (nParam < nArgs)
            {
                if (pDesc->nArgCount >= PAIRED_VAR_ARGS && nParam >= nArgs-2)
                {
                    SAL_WARN("sc.core", "ScFuncRes: PAIRED_VAR_ARGS parameters can't be suppressed, on OpCode " <<
                            aRes.GetId() << ": param " << nParam << " >= arg " << nArgs << "-2");
                }
                else if (pDesc->nArgCount >= VAR_ARGS && nParam == nArgs-1)
                {
                    SAL_WARN("sc.core", "ScFuncRes: VAR_ARGS parameters can't be suppressed, on OpCode " <<
                            aRes.GetId() << ": param " << nParam << " == arg " << nArgs << "-1");
                }
                else
                {
                    pDesc->pDefArgFlags[nParam].bSuppress = true;
                    pDesc->bHasSuppressedArgs = true;
                }
            }
            else
            {
                SAL_WARN("sc.core", "ScFuncRes: suppressed parameter exceeds count on OpCode " <<
                        aRes.GetId() << ": param " << nParam << " >= args " << nArgs);
            }
        }
    }

    pDesc->pFuncName = new OUString( ScCompiler::GetNativeSymbol( static_cast<OpCode>( aRes.GetId())));
    pDesc->pFuncDesc = new OUString( SC_RESSTR(1) );

    if (nArgs)
    {
        pDesc->maDefArgNames.clear();
        pDesc->maDefArgNames.resize(nArgs);
        pDesc->maDefArgDescs.clear();
        pDesc->maDefArgDescs.resize(nArgs);
        for (sal_uInt16 i = 0; i < nArgs; ++i)
        {
            pDesc->maDefArgNames[i] = SC_RESSTR(2*(i+1)  );
            pDesc->maDefArgDescs[i] = SC_RESSTR(2*(i+1)+1);
        }
    }

    FreeResource();
}
```
やはりこれが引数`pDesc`の設定をしてくれる関数っぽい。

```
pDesc->pFuncName = new OUString( ScCompiler::GetNativeSymbol( static_cast<OpCode>( aRes.GetId())));
pDesc->pFuncDesc = new OUString( SC_RESSTR(1) );
```
の部分に着目すると、どうやら関数の名前や説明文を取得する関数があるみたい。これは使えそう。
ただ、`OUString`ってのはなんだろう…？Stringとあるからには文字列なんだろうけど、いまいちわからない。
##OUStringについて調べる

```cpp:/include/rtl/ustring.hxx

class SAL_WARN_UNUSED OUString
{
public:
    /// @cond INTERNAL
    rtl_uString * pData;
    /// @endcond
// あとはずっとメンバ関数の宣言が続くので略
```
で、

```c:/include/rtl/ustring.h

/** @cond INTERNAL */
/** The implementation of a Unicode string.
*/
typedef struct _rtl_uString
{
    oslInterlockedCount refCount; /* opaque */
    sal_Int32           length;
    sal_Unicode         buffer[1];
} rtl_uString;
/** @endcond */
```
refCountはわからないが、おそらくlengthが文字の長さで、bufferに文字が入っているのだろう。なぜbufferの配列長が1なのかはわからないが。
一応、sal_Unicodeの定義は以下。

```cpp:/sfx2/source/doc/syspathw32.cxx
typedef unsigned short sal_uInt16;
#if ( defined(WIN32) && !defined(__MINGW32__) )

    typedef wchar_t             sal_Unicode;
#else
    typedef sal_uInt16          sal_Unicode;
#endif
```

で、この後OUStringについて色々調べた結果、以下のことがわかった。

- pDataがポインタなのでてっきりpData,pData+1,pData+2...で文字列になっていると思っていたが、そうではなく、長さ1の配列変数bufferが長さlength分だけ文字を持っているっぽい（buffer[1]やbuffer[2]に別の文字が入っている）
- bufferに入っているデータは普通のUnicodeなので、対応表があれば読める

というわけで、時間はかかるがOUStringの文字列も読めるようになった。先へ進もう。

#今回のまとめ
それっぽいフォルダを見つけた。文字列が読めるようになった。

#次回予告
次回、ついに目的の関数を発見！？
全検索時代もいよいよ大詰めに！ 乞うご期待！


#リンク
##LibreOfficeCalcに関数候補表示機能を付けるまで

- [第1回：目的の設定と準備](http://qiita.com/KRiver1/items/2188d71d316a28ad23ac)
- [第2回：デバッガでメインループを探す](http://qiita.com/KRiver1/items/1f93efcf47d2146c78b8)
- [第3回：メインループと全探索](http://qiita.com/KRiver1/items/d7ab9614d5bbd409c541)
- [第4回：フォルダ名でアタリを付ける](http://qiita.com/KRiver1/items/3531679de3c262332de7)
- [第5回：関数候補の集合を返す](http://qiita.com/KRiver1/items/08ef4586866833048805)
- [第6回：関数候補・説明表示機能を実装する](http://qiita.com/KRiver1/items/c33d62f4bcc73e76b359)
- [第7回：LibreOfficeにコミットしてみる](http://qiita.com/KRiver1/items/7aba2a32cc42672c105d)