// csvfc.C  Version 7.42  Copyleft (c) 杉本典夫
// Time-stamp: <2025-11-03 10:42:24 tac>
//
// csv(Comma Separated Value)ファイル←→dansデータファイルの相互変換を行う。
//
// 使用ライブラリ:libdans.so, libfun.so
// コンパイル法:g++ -O -s -W csvfc csvfc.C -I/usr/local/include
//             -L/usr/local/lib -ldans -lfun

#include <dans.H>               // dans用ヘッダファイル(mat.H含む)

using namespace dans;           // dans名前空間使用(名前空間matvec含む)

const vecstr USAGE =            // プログラム使用法
{
  "csvfc  Version 7.42          Copyleft(Vo\\oV) 2025 杉本典夫",
  "書式:csvfc  [<オプション> …]  [<ファイル> …]",
  "機能:csvファイル←→dansデータファイルの相互変換をするんじゃよ。",
  "<オプション>",
  "  -f=<ファイル名>:指定されたファイルからオプションを読み込むんじゃ。",
  "  -fout={auto|csv|dans}:出力ファイルの種類を指定するんじゃ。",
  "    auto:自動判別(オプション省略時)  csv:DANS形式→csv形式",
  "    dans:csv形式→DANS形式",
  "  -h[=<コマンド名>]:オンラインマニュアルを表示します。(-h:-h=man)",
  "  -item[={-|+}]:項目名の処理指定じゃ。(-item:-item=+)",
  "    -:処理せず  +:処理(オプション省略時)",
  "  -ln={-|+}:DANS出力時の改行文字変換法を指定するんじゃ。",
  "    -:無変換  +:半角スペースに変換(オプション省略時)",
  "  -quot={all|auto|str}:csv出力時にデータを\"で囲む方法を指定するんじゃ。",
  "    all:全てのデータ  auto:必要なデータのみ自動判定",
  "    str:文字データのみ(オプション省略時)",
  "  -title[={-|+}]:タイトルの出力指定じゃ。(-title:-title=+)",
  "    -:出力せず(オプション省略時)  +:出力",
  "    -fout=csvの時にタイトルと層別情報を1行目に出力するんじゃよ。"
};

enum fouttype                   // 出力形式
{
  foutnon,                      // なし
  foutcsv,                      // csv形式
  foutdans,                     // DANS形式
  foutetc                       // その他
};

enum quottype                   // データを"で囲む方式
{
  quotnon,                      // なし(自動判定)
  quotall,                      // 全てのデータ
  quotstr,                      // 文字データのみ
  quotetc                       // その他
};

class option                    // オプション集合体
{
private:
  string v_path;                // プログラムのパス
  string v_pname;               // プログラム名
  fouttype v_fout;              // 出力ファイルの種類
  bool v_item;                  // 項目名変換フラッグ
  bool v_ln;                    // 改行文字の変換フラッグ
  quottype v_quot;              // データを"で囲む方式の種類
  bool v_title;                 // タイトル変換フラッグ
public:
   option(const char* c_pname = nullptr); // コンストラクタ
  ~option()                               // デストラクタ
  {}
  const string& path() const    // プログラムのパスを返す
  { return v_path; }
  const string& pname() const   // プログラム名を返す
  { return v_pname; }
  fouttype fout() const         // 出力ファイルの種類を返す
  { return v_fout; }
  bool item() const             // 項目名変換フラッグを返す
  { return v_item; }
  bool ln() const               // 改行文字の変換方法を返す
  { return v_ln; }
  quottype quot() const         // データを"で囲む方式の種類を返す
  { return v_quot; }
  bool title() const            // 項目名変換フラッグを返す
  { return v_title; }
  void setdef()                 // 省略時オプション指定を設定する
  { getoptdef(*this); }
  void set(const string& optstr, char devc = OPTdev);
  // オプション指定によってオプションを設定する
};

option::option(const char* c_pname) : v_path(), v_pname(), v_fout(foutnon), v_item(true), v_ln(true), v_quot(quotstr), v_title(false)
// コンストラクタ
{
  if (c_pname == nullptr) return; // 初期設定せず
  v_pname = getname(c_pname);     // パス名なしのプログラム名取得
  v_path = c_pname;
  strsize_t idx = v_path.find(v_pname);
  if (idx != string::npos) v_path.erase(idx); // プログラム名削除
}

void option::set(const string& optstr, char devc)
// オプション指定によってオプションを設定する
{
  string opt[2];                // オプションのopt[0]:項目,opt[1]:内容
  if (getopt(opt, optstr, devc) < 1) return; // オプション指定なし
  int err = 0;
  if (opt[0] == "f")            // オプション指定ファイル
  { err = fgetopt(*this, opt[1], true); }
  else if (opt[0] == "fout")    // 出力ファイルの指定
  {
    if (opt[1].empty() || opt[1] == "auto") v_fout = foutnon;
    else if (opt[1] == "csv") v_fout = foutcsv;
    else if (opt[1] == "dans") v_fout = foutdans;
    else err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0] == "h")       // オンラインヘルプ表示
  { err = help(pname(), USAGE, opt[1]); }
  else if (opt[0] == "item")    // 項目名の変換指定
  {
    if (opt[1].empty() || opt[1] == "+") v_item = true;
    else if (opt[1] == "-") v_item = false;
    else err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0] == "ln")      // 改行文字の変換指定
  {
    if (opt[1].empty() || opt[1] == "+") v_ln = true;
    else if (opt[1] == "-") v_ln = false;
    else err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0] == "quot")    // データを"で囲む方式の指定
  {
    if (opt[1].empty() || opt[1] == "str") v_quot = quotstr;
    else if (opt[1] == "all") v_quot = quotall;
    else if (opt[1] == "auto") v_quot = quotnon;
    else err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0] == "title")   // タイトルの変換指定
  {
    if (opt[1].empty() || opt[1] == "+") v_title = true;
    else if (opt[1] == "-") v_title = false;
    else err = badopt(pname(), optstr, USAGE);
  }
  else                          // 間違ったオプション指定
  { err = nonopt(pname(), optstr, USAGE); }
  if (err > 0) throw help_except("ヘルプ表示");
  if (err < 0) throw option_error(string("オプション指定違反→") + optstr);
}

class csvfcclass                // csvfcメインクラス
{
private:
  string fname;                 // ファイル名
  void dans_csv(ostream& fout, const option& opt, const lstr& slinelst, idd& id) const;
  // dans形式のファイルをcsv形式で出力する
  void put_csvid(ostream& fout, const option& opt, idd& id) const;
  // データ識別情報をCSV形式で出力する
  void csv_dans(ostream& fout, const option& opt, const lstr& slinelst, idd& id) const;
  // csv形式のファイルをdans形式で出力する
  void put_id(ostream& fout, const option& opt, idd& id, const datac& cdata) const;
  // データ識別情報を出力する
  void put_csv(ostream& fout, const option& opt, const datac& cdata) const;
  // ケースデータをCSV形式で出力する
  const string& strtocsv(string& str, const option& opt) const;
  // 文字列をCSV形式に変換する
public:
  csvfcclass() : fname()        // コンストラクタ
  {}
  ~csvfcclass()                 // デストラクタ
  {}
  int get(const char* c_fname, lstr& slinelst); // データを読み込み累積する
  void put(const option& opt, const lstr& slinelst, idd& id) const;
  // データを変換して出力する
};

void csvfcclass::dans_csv(ostream& fout, const option& opt, const lstr& slinelst, idd& id) const
// dans形式のファイルをcsv形式で出力する
{
  datac cdata(id.ni());         // dansデータオブジェクト
  for (string sline : slinelst)
  {
    if ((sline)[0] == CHRtags)  // データ識別情報
    {
      if (id.append(sline) < 0) throw runtime_error(string("データ識別情報が間違っています→") + sline);
    }
    else                        // ケースデータ読み込み
    {
      if (id.nc() < 1)          // 最初の読み込み
      {
        if (id.ni() < 1)        // データ識別情報がない場合
        {
          int ni = cdata.getdata(sline);
          if (ni < 1) throw runtime_error(string("1行目にデータがありません!→") + sline);
          id.set(fname, ni);
        }
        else
        {
          cdata.resize(id.ni());
          cdata.getdata(sline);
        }
        if (opt.title() || opt.item()) put_csvid(fout, opt, id);
      }
      else
      { cdata.getdata(sline); }
      put_csv(fout, opt, cdata);  id.nc(id.nc() + 1);
    }
  }
  if (id.nc() < 1 && (opt.title() || opt.item())) put_csvid(fout, opt, id);
}

void csvfcclass::put_csvid(ostream& fout, const option& opt, idd& id) const
// データ識別情報をCSV形式で出力する
{
  if (opt.title())
  {
    fout << CHRdquot << id.title() << CHRdquot;
    for (size_t i = 0; i < id.ns(); ++i)
    {
      fout << CHRdelm << CHRdquot;
      if (id.estra(i).aval() == avaland) fout << "  かつ  :";
      else if (id.estra(i).aval() == avalor) fout << "  又は  :";
      else fout << "層別内容:";
      fout << id.estra(i).pcdata() << CHRdquot;
    }
    fout << endl;
  }
  if (opt.item() == false || id.ni() < 1) return;
  fout << CHRdquot << id.item(0) << CHRdquot;
  for (size_t i = 1; i < id.ni(); ++i)
  { fout << CHRdelm << CHRdquot << id.item(i) << CHRdquot; }
  fout << endl;
}

void csvfcclass::put_csv(ostream& fout, const option& opt, const datac& cdata) const
// ケースデータをCSV形式で出力する
{
  if (cdata.size() < 1) return;
  string tstr(cdata.sdata(0));
  fout << strtocsv(tstr, opt);
  for (size_t i = 1; i < cdata.size(); ++i)
  {
    tstr = cdata.sdata(i);
    fout << CHRdelm << strtocsv(tstr, opt);
  }
  fout << endl;
}

const string& csvfcclass::strtocsv(string& str, const option& opt) const
// 文字列をCSV形式に変換する
{
  strsize_t n = str.size();
  bool quot = false;
  if (strreplace(str, "\"", "\"\"", true) > 0) quot = true; // ""は""で囲む
  switch (opt.quot())
  {
  case quotall : quot = true;
    break;
  case quotstr : if (n > 0 && strtodf(str) == BLANKf) quot = true;
  }
  if (quot == false)
  {
    if (str.find(CHRdelm) != string::npos)
    { quot = true; }
    else if (str.find('\n') != string::npos)
    { quot = true; }
    else if (str.find(CHRsquot) != string::npos)
    { quot = true; }
    else
    {
      string tstr(str);
      if (size_t(strtrm(tstr)) < str.size()) quot = true; // 前後に空白がある
    }
  }
  if (quot) str = CHRdquot + str + CHRdquot;
  return str;
}

void csvfcclass::csv_dans(ostream& fout, const option& opt, const lstr& slinelst, idd& id) const
// csv形式のファイルをdans形式で出力する
{
  datac cdata(id.ni());         // dansデータオブジェクト
  for (string sline : slinelst)
  {
    strreplace(sline, "\\", "\\\\", true); // \を\\にして\として読み込む
    strreplace(sline, "\"\"", "\\\"", true); // ""を\"にして"として読み込む
    if (opt.ln()) strreplace(sline, "\n", " ", true); // 改行文字変換
    cdata.getdata(sline);
    for (size_t i = 0; i < cdata.size(); ++i)
    {
      if (strpmatch(cdata.sdata(i), "^\"+$")) // "だけの時
      {
        string tstr(cdata.sdata(i));
        strreplace(tstr, "\"", ""); // "を1つ削除する
        cdata.assign(i, tstr);
      }
    }
    if (id.nc() < 1)
    {
      put_id(fout, opt, id, cdata);
      if (opt.item() == false) cdata.put(fout);
    }
    else
    { cdata.put(fout); }
    id.nc(id.nc() + 1);
  }
}

void csvfcclass::put_id(ostream& fout, const option& opt, idd& id, const datac& cdata) const
// データ識別情報を出力する
{
  if (opt.item())
  {
    for (size_t i = 0; i < cdata.size(); ++i) id.append_item(cdata.sdata(i), attnon, avalnon);
  }
  id.set(fname, cdata.size());  // ファイル名をデータ名にする
  id.put(fout, formtag);
}

int csvfcclass::get(const char* c_fname, lstr& slinelst) // データを読み込む
{
  istream* finp = nullptr;
  if (c_fname == nullptr)
  { finp = &cin;  c_fname = "stdin"; }
  else
  {
    finp = new ifstream(c_fname);
    if (!(*finp)) throw runtime_error(string("ファイルがありません→") + string(c_fname));
  }
  if (fname.empty()) fname = c_fname;
  string sline;
  int nl = 0;
  while (getsline(sline, *finp) != EOF)
  {
    strchomp(sline);
    slinelst.push_back(sline);
    ++nl;
  }
  if (finp != &cin) delete finp;
  return nl;                    // 読み込んだ行数を返す
}

void csvfcclass::put(const option& opt, const lstr& slinelst, idd& id) const
// データを変換して出力する
{
  switch (opt.fout())
  {
  case foutcsv : dans_csv(cout, opt, slinelst, id);
    break;
  case foutdans : csv_dans(cout, opt, slinelst, id);
    break;
  default :
    if ((slinelst.front())[0] == CHRtags) dans_csv(cout, opt, slinelst, id);
    else csv_dans(cout, opt, slinelst, id);
  }
}

int main(int argc, char* argv[]) // メインルーチン
{
  csvfcclass csvfc;             // メインクラス変数
  option opt(argv[0]);          // オプション
  lstr slinelst;                // 文字列行リスト
  idd id;                       // データ識別情報
  try
  {
    opt.setdef();               // オプションの初期設定
    bool fok = true;            // ファイル処理用フラッグ
    for (int i = 1; i <= argc; ++i)
    {
      if (i >= argc)
      {
        if (fok) csvfc.get(nullptr, slinelst); // 標準入力から読み込む
      }
      else if (*(argv[i]) == CHRopt)
      { opt.set(argv[i] + 1);  fok = true; }
      else
      { csvfc.get(argv[i], slinelst);  fok = false; }
    }
    csvfc.put(opt, slinelst, id);
  }
  catch (help_except& helpex)   // ヘルプファイル表示
  { return 0; }
  catch (option_error& opterr)  // オプション指定エラー
  { return 1; }
  catch (runtime_error& rterr)  // 実行時エラー
  {
    disperr(opt.pname(), rterr.what());
    return 2;
  }
  catch (...)                   // 予期せぬ例外
  {
    disperr(opt.pname(), "内容不明なエラー発生！");
    return 255;
  }
  return 0;
}
