またまた、ESP8266向けにinifile読込関数を作る

ESP8266の小物を作っていると、WebServer機能でのパラメータ登録機能があると
同じ小物を複数個運用するのに、設定や確認が楽だろうなぁと感じています。
そこでWebServerの修正といきたいのですが、パラメータ登録機能の心臓部である
パラメータの保存・読込機能を考えました。
以前ArduinoIDE向けにinifileの読込関数を作っていたのですが、読込専用で使いにくかったので
思い切って作り直しました。
　＝＞xmlでやれば良かったと何気に後悔中です。


#ESP8266環境で必要なリソース
　SPIFFS / FS等のファイルアクセスの環境
　inifileのファイルサイズ ×2 + 256 程度のメモリ領域 (mallocにて確保・開放します)
　FTPは推奨
　＊本当は最低限のメモリで動作させたかったのですが、途中で断念しました。

#使い方
ソースはこんな風にします。

```Arduino:usage.ino
#include <inifile.h>

const char *iniFileName = "/setup.ini"; // Inifileのファイル名
iniFiles ini(iniFileName);

//==
void setup() {
  Serial.begin(115200);  Serial.flush(); delay(1000);
  Serial.println("program start");
  if( SPIFFS.begin() )
    Serial.println("SPIFFS Initialize....ok");
  else
  {
    Serial.println("SPIFFS Initialization...failed");
    return;
  }
  //SPIFFS.format();
  // メモリに読み込んで、内容を表示した後メモリ開放します
  ini.read();
  if( ini.data()==NULL ) Serial.println( "NO DATA" );
  else                   Serial.println( ini.data() );
  ini.release();
  
  // 直接ファイルに書き出します (メモリに読み込んでいないなら、直接ファイルに書き出します)
  ini.set("SETUP", "SSID", "SSIDADD" );
  ini.set("SETUP", "PASSWORD", "PASS" );
  ini.set("SETUP", "HOSTNAME", "TESTHOST" );

  // メモリに読み込んで、内容をwriteで書き込む
  ini.read();
  ini.set( "SETUP", "radio", "radio3" );
  ini.write();                  // ファイルへの書込み()
  ini.set( "SETUP", "radio", "radioQQQQ" );        // ここはファイルに書き出しません
  Serial.println( ini.get("SETUP", "radio", "**" ) );
  ini.release();

  //
  Serial.println( ini.get( "SETUP", "SSID", "guest" ) );
  Serial.println( ini.get( "SETUP", "PASSWORD", "guest" ) );

  // 内容の表示
  ini.read();
  Serial.print( ini.data() );
  ini.release();
}

void loop() {
}
```

データファイルはこんな風に作成します。　inifileと似た使い方ができると思っています。
＊＊でも時々仕様が違うのとバグがあると思います。

```Arduino:SETUP.ini
[SETUP]
SSID=SSIDADD
PASSWORD=PASS
HOSTNAME="TESTHOST"
radio=radio3
```


#ソースコード
cppには何も書いていません。

```Arduino:inifile.h
#ifndef INIFILE_H
#define INIFILE_H

//#define inidebug
#if defined(ESP8266)
#include <FS.h>
#else
#include <SPIFFS.h>
#endif
#include <string.h>

// inifileを読み書きするクラス
//
// メモリ
//   read関数でメモリ確保すると inifileのサイズ分確保します(release関数にて開放
//   get と set 関数では
//   inifileのサイズ x 2 + 256byte程度食います。
// 本来はデータ部を'"'で囲うことでスペースが利用できるのだが現在未対応
// その他本来とは食い違うもの満載と思われる
//
class iniFiles {
    bool inifileSpace( char chData ) {  return ((chData == ' ' || chData == 0x9) ? true : false);  }
    bool inifileCRLF( char chData ) {  return ((chData == 0xD || chData == 0xA) ? true : false);  }
    char fp_read(){  return inifileData[inifilePos++]; }
    bool fp_available( ){ return inifileData[inifilePos]!=0; }

    char *inifilename, *inifileData;
    int inifilePos;
    //
    String dataIO( char *targetSection, char *detail, char *setData ){
    bool hitHeader = false, hitDetail = false;
    String section, title, oldData;
    String detailcmp = detail;
    detailcmp.toUpperCase();
    char ch, str[256], *output;
    int outputPos = 0, startPos, endPos, length;
    bool hit, headerHit = false;
      inifilePos = 0;
      int filesize = strlen( inifileData );
      output = (char*)malloc( filesize+256 );
      while ( fp_available() ){
        // 不要な箇所を読み飛ばす    
        ch = output[outputPos++] = fp_read();
        if( inifileSpace(ch) || inifileCRLF(ch) ){  continue;  }  // 空白と改行は読み飛ばし
        if( ch!='[' ){                                            // セクションの開始文字で無いなら次の改行まで読み飛ばす
          while ( fp_available() )  if( inifileCRLF( output[outputPos++]=fp_read() ) )  break;    // 改行まで読み飛ばし
          continue;
        }
        // セクション名の開始位置を検索する
        startPos = outputPos;
        while ( fp_available() ){
          ch = output[outputPos++] = fp_read();
          if( ch==']' || inifileSpace(ch) || inifileCRLF(ch))  break;         // 空白は読み飛ばし
        }
        if( ch!=']' )  continue;    // セクションの終端では無い
        // セクション名を読み取る
        length = outputPos - startPos - 1;
        memcpy( str, &output[startPos], length );
        str[length] = 0;
        section = str; section.toUpperCase();
#if defined(inidebug)
        Serial.print( "section:" );    Serial.println( section );
#endif
        // セクション名が変更するものと一致しないなら読み飛ばす
        if( section!=targetSection )  continue;
        hitHeader = true;
        // 項目名の開始位置を検索する
        while ( fp_available() ){
          if( (ch = output[outputPos++] = fp_read())=='[' )      break;
          if( inifileSpace(ch) || inifileCRLF(ch) )      continue;      // 空白と改行は読み飛ばし
          // 項目名の開始位置を検索する
          startPos = outputPos;
          while ( fp_available() ){
            if( (ch = output[outputPos++] = fp_read())=='=' )    break;
            if( inifileCRLF(ch) )                        break;         // 改行は読み直し
            if( inifileSpace(ch) )                       continue;      // 空白は読み飛ばし
          }
          if( inifileCRLF(ch) )      continue;                          // 改行は読み飛ばし
          endPos = outputPos;
          // '='が見つかるまで読み飛ばす
          if( inifileSpace(ch) ){
            while ( fp_available() ){
              if( (ch = output[outputPos++] = fp_read())=='=' )  break;
              if( inifileCRLF(ch) )                      break;         // 改行は読み直し
              if( inifileSpace(ch) )                     continue;      // 空白は読み飛ばし
            }
          }
          if( inifileCRLF(ch) )      continue;                          // 改行は読み飛ばし
          if( ch!='=' )   continue;               // 改行は読み飛ばし
          // 項目名を切り出す
          length = endPos - startPos;
          memcpy( str, &output[startPos-1], length );
          str[length] = 0;
          title = str; title.toUpperCase(); 
#if defined(inidebug)
          Serial.print( "title:" );        Serial.println( title );
#endif
          // データ開始まで読み飛ばし
          while ( fp_available() )  if( !inifileSpace( ch = output[outputPos++] = fp_read()) )  break;
          // 改行ならデータ無し
          if( inifileCRLF(ch) )  continue;
          // データ範囲を受け取る
          startPos = outputPos;
          while ( fp_available() )  if( inifileCRLF(ch = output[outputPos++] = fp_read()) )     break;
          endPos = outputPos;
          // お目当てのデータなら変更する
          if( title==detailcmp ){
            if( setData==NULL )
            {
              length = endPos - startPos;
              memcpy( str, &output[startPos-1], length );
              str[length] = 0;
              oldData = str; 
              free( output );
              return oldData;
            }
            memcpy( &output[startPos-1], setData, strlen(setData) );
            outputPos = startPos-1 + strlen(setData);
            output[outputPos++] = ch;
            hitDetail = true;
          }
        }
        if( ch=='[' || inifileCRLF(ch)==true ){
          if( setData==NULL ){
            free( output );
            return "";
          }
          // セクションの末尾を検知したのに、項目が無いなら追加する
          if( hitDetail==false ){
             outputPos--;
             output[outputPos++] = 0x0d;        output[outputPos++] = 0x0a;
             strcpy( &output[outputPos], detail );   outputPos += strlen(detail);
             output[outputPos++] = '=';
             strcpy( &output[outputPos], setData );   outputPos += strlen(setData);
             output[outputPos++] = 0x0d;        output[outputPos++] = 0x0a;
             output[outputPos++] = ch;
             hitDetail = true;
          }
          continue;      // 次のセクション
        }
      }
      // 対応するセクションが無いならデータの末尾に作成する
      if( hitHeader==false || hitDetail==false ){
        if( setData==NULL )  return "";
        if( hitHeader!=true ){
          output[outputPos++] = 0x0d;        output[outputPos++] = 0x0a;
          output[outputPos++] = '[';
          strcpy( &output[outputPos], targetSection );   outputPos += strlen(targetSection);
          output[outputPos++] = ']';
        }
        output[outputPos++] = 0x0d;        output[outputPos++] = 0x0a;
        strcpy( &output[outputPos], detail );   outputPos += strlen(detail);
        output[outputPos++] = '=';
        strcpy( &output[outputPos], setData );   outputPos += strlen(setData);
        output[outputPos++] = 0x0d;        output[outputPos++] = 0x0a;
      }
      output[outputPos++] = 0;
      free( inifileData );
      inifileData = output;
      return "";
    }
public:
    // コンストラクタ
    iniFiles( const char *filename ){
      inifilename = (char*)malloc( strlen(filename)+1 );
      strcpy( inifilename, filename );
      inifileData = NULL;
    }
    bool exists(){ return SPIFFS.exists(inifilename); };
    // 確保しているメモリのポインタを返す
    char *data(){ return inifileData; };
    // 確保しているメモリの開放
    void release(){
      if( inifileData!=NULL ) free( inifileData );
      inifileData = NULL;
    }
    // メモリを確保して読み込む
    void read(){
      if( inifileData!=NULL ) free( inifileData );
      File fp = SPIFFS.open( inifilename, "r" );
      if( fp!=NULL ){
        int filesize = fp.size();
        inifileData = (char*)malloc( filesize+1 );
        fp.read( (uint8_t*)inifileData, filesize );
        inifileData[filesize] = 0;
        fp.close( );
      }else{
        Serial.print("file not found:");  Serial.println( inifilename );
        inifileData = NULL;
      }
    }
    // メモリを書き出す
    void write(){
      if( inifileData==NULL ) return;
      File fp = SPIFFS.open( inifilename, "w" );
      int filesize = strlen( inifileData );
      fp.write( (uint8_t*)inifileData, filesize );
      fp.close( );
    }
    // データの読込
    String get( char *header, char *detail, char *defaultData ){
      bool readData = (inifileData==NULL);
      if( readData ) read();       // メモリに無いなら読み込む
      String data;
      if( inifileData!=NULL )
        data = dataIO( header, detail, NULL );
      else
        data = "";
      if( data.length()==0 ){  data = defaultData;  Serial.print( "default:" );  Serial.println( data );  }
      if( readData ) release();    // メモリに無かったので開放する
      return data;
    }
    // データの読込(利用後はmfreeで開放する必要があります
    char *getChar( char *header, char *detail, char *defaultData ){
      String sData = get( header, detail, defaultData );
      char *cData = (char*)malloc( sData.length()+1 );
      strcpy( cData, sData.c_str() );
      return cData;
    }
    // データの書込
    bool set( char *header, char *detail, char *setData ){
      bool readData = (inifileData==NULL);
      if( readData )  read();                     // メモリに無いなら読み込む
      if( inifileData==NULL ){                    // 読込んでいないなら 0byte データを作成する
        inifileData = (char*)malloc( 2 );
        inifileData[0] = 0;
      }
      String data = dataIO( header, detail, setData );
      if( readData ){ write();     release();  }  // メモリに無かったので書出して開放する
      return data;
    }
    //
};
#endif
```