CSVの読み書きについて


超初心者  2009-07-27 23:14:16  No: 70675

毎度お世話になります。

エクセルで作成したcsvの読み込み、書き出しの処理を作成しているのですが、わからないので質問させて頂きたく投稿しました。

あるuser.csvがあるとします。中身は

ID  NAME  PASSWORD
a   山田       a    
b   加藤       b     
c   田中       c    
d   石井       d    

みたいな感じです。

ゆくゆくはこれを読み込んでuser.xmlに書き込む、user.xmlをcsvとして書き出すということをやりたいのですが、一気にやると頭が混乱しますので、まずはcsvを読み込んでAfxMessageBoxで表示(とりあえず、一行単位で読み取ってそのまま表示)、次に読み込んだcsvをそのまま書き出すということをやろうと思っています。

で、現在ファイル選択ダイアログを作ってファイルパスを取得して、そこから中身を読み取る処理を作成中です。
以下ソースを張るので間違いやもっといい方法があればお教え願いたいのですが・・・・

//参照ボタン押下時
void CNoticeDlg::OnBnClickedCompare()
{
    CString fileName;  //ファイル名
    CString fileTitle;  //ファイルタイトル
    CString fileExt;  //ファイルの拡張子

    CString  strBuf;
    POSITION  pos = NULL;

    CFileDialog     fileDlg(
        TRUE,
        NULL,
        NULL, 
        OFN_HIDEREADONLY
  );
    int  err = 0;
    int  lbErr = 0;
    
    // ファイル名リスト用メモリ確保
    if(!err) 
    {
       try
       {
            fileDlg.GetOFN().lpstrFile = strBuf.GetBuffer(MAX_PATH *100);
            fileDlg.GetOFN().nMaxFile = MAX_PATH *100;
       }
       catch(...)
       {
       err = 1;
       }
    }
    //ファイルパスを取得するには、CFileDialog::GetStartPosition()とCFileDialog::GetNextPathName()関数を使って一つずつ取得
    if(!err)
    {
  if (fileDlg.DoModal() != IDOK)
  {
    err = 1;
  }
    }
    if(!err)
    {
  //GetStartPosition():リストの先頭にあるファイルのパス名の示す位置を取得
  if((pos = fileDlg.GetStartPosition()) == NULL)
  {
    err = 1;
  }
    }
    if(!err)
    {
  m_filePath = fileDlg.GetPathName();  //GetPathName():ファイルのフルパスを取得

      
  fileName = fileDlg.GetFileName();  //GetFileName():ファイル名を取得を取得
  fileTitle = fileDlg.GetFileTitle();  //GetFileTitle():ファイルタイトルを取得
  fileExt = fileDlg.GetFileExt();    //GetFileExt():ファイルの拡張子を取得

        if(!err)
        {
    lbErr = m_lcFileList.InsertString(-1, m_filePath);
    if(lbErr == LB_ERR || lbErr == LB_ERRSPACE)
    {
      err = 1;
    }
        }

  AfxMessageBox( _T("ファイルのフルパス:") + m_filePath
      + _T("\r\nファイル名:") + fileName
      + _T("\r\nファイルタイトル:") + fileTitle
      + _T("\r\nファイル拡張子:") + fileExt
  );

  //ダイアログ ボックスが初期化される:FALSE
  //データが取得される:TRUE
        UpdateData(FALSE);
    }
    //GetBufferしたのでReleaseBufferを行う。
    strBuf.ReleaseBuffer();   
    return;

}

//表示ボタン押下時
void CNoticeDlg::OnBnClickedOutPutFileData() 
{
  CStdioFile stdFile;
  CString csText;
  CString csTempText;
  std::vector <CString> vcsTextList;

  if( !m_filePath.IsEmpty() )
  {
    if( stdFile.Open( m_filePath , CFile::modeRead ) )
    {
      while(stdFile.ReadString(csText))
      {
        vcsTextList.push_back(csText);
        
        if(!csTempText.IsEmpty())
        {
          csTempText += _T("\r\n");
        }

        csTempText += csText;
        
        _tprintf(csText.GetBuffer());
        _tprintf(_T("\n"));
      }

      stdFile.Close();
      AfxMessageBox(csTempText);
    }
    else
    {
      AfxMessageBox( _T("ファイルを正常に開くことが出来ませんでした。") );
    }
  }

}

ファイルパスから中身を読み取る部分がどうも間違えているような気がしてなりません。また、AfxMessageBoxで表示すると、日本語の部分が文字化け?みたいな感じでうまく表示されないです。

書き出し部分はこれを解決してから進みたいのですが、うまくいかず立ち往生です・・・・・・・。

どなたかアドバイス、または参考サイトなどを教えて頂けると助かります。

環境はvisual studio2005 VC++(MFC) vistaです。宜しくお願い致します。


みい  2009-07-27 23:58:25  No: 70676

上の説明を見ると1つのCSVから読み込みなんだけど、ソースの書き方から見ると複数のCSVファイル対応になってる(その割にOFN_ALLOWMULTISELECTをセットしてないけど)

やりたいのは単ファイル/複数ファイル操作のどっち?


超初心者  2009-07-28 00:23:16  No: 70677

みいさんお世話になります。

最初欲張って複数ファイル対応にしようかと思っていましたが、段階を踏んでやはり単ファイル対応にしようと思っています。ソースはその名残が残ってしまっていますね・・・。わかりずらくて申し訳ありません。


みい  2009-07-28 00:41:56  No: 70678

GetStartPositionのヘルプを見ると書いているのですが、GetStartPositionを使うのは、
「m_ofn.Flags に OFN_ALLOWMULTISELECT フラグを設定した場合」
になります。
単一ファイル操作ならGetStartPositionやメモリ確保部(&解放部)は削除して下さい。

CFileDialogにはファイルフィルタを入れた方がいいです。
<CFileDialog引数の参考HP>
http://www13.ocn.ne.jp/~kancha/progmemo3.html


超初心者  2009-07-28 01:43:33  No: 70679

みいさんお世話になります。

なるほど。フィルタを設定することで条件に一致するファイルだけがファイルの一覧に表示されるわけですね。今回はcsvだけ読み込めばいいので、そうした方が親切ですね。

以下、修正したソースです。

//単一ファイル参照ボタン押下時
void CNoticeDlg::OnBnClickedSingleCompare()
{
  CString fileName;  //ファイル名
  CString fileTitle;  //ファイルタイトル
  CString fileExt;  //ファイルの拡張子

  CFileDialog fileDlg(
          TRUE,
          _T("csv"),
          _T("*.csv"),
          OFN_HIDEREADONLY,
          _T("CSV形式ファイル(*.csv)\0*.csv\0\0"),
          NULL
  );
    
  if (fileDlg.DoModal() == IDOK)
  {
    m_filePath = fileDlg.GetPathName();  //GetPathName():ファイルのフルパスを取得

    fileName = fileDlg.GetFileName();  //GetFileName():ファイル名を取得を取得
    fileTitle = fileDlg.GetFileTitle();  //GetFileTitle():ファイルタイトルを取得
    fileExt = fileDlg.GetFileExt();    //GetFileExt():ファイルの拡張子を取得

    UpdateData(FALSE);
  }

  AfxMessageBox( _T("ファイルのフルパス:") + m_filePath
        + _T("\r\nファイル名:") + fileName
        + _T("\r\nファイルタイトル:") + fileTitle
        + _T("\r\nファイル拡張子:") + fileExt
  );

  m_singleFilePath.SetWindowText( m_filePath );  // ファイルパルを表示

  return;
}

間違い等ないでしょうか?だいぶ簡略化されましたね。ありがとうございます。

これでファイルパスを取得する部分はできましたが、それを読み込んでいるところはどうでしょうか?表示したさいに日本語が化けて表示される問題もわかっておりません。

お手数ですが、アドバイスを宜しくお願い致します。


超初心者  2009-07-28 04:52:59  No: 70680

文字化けの件ですが、MFCアプリケーションウイザードではデフォルトでユニコードビルドとなっているためということが書いてありました。

色々調べてOnBnClickedOutPutFileData()の先頭で

setlocale(LC_ALL, ".ACP");

を追加したところ、日本語の文字化けがなくなりました。しかし、どうして化けなくなったのか今一理解できておりません。

こちらの件も御教え願えますか?宜しくお願い致します。


超初心者  2009-07-29 00:02:29  No: 70681

たびたびすみません。

自分であれこれ試してみたり、探してみたりしているのですが、どうしてもcsvファイルとして書き出す処理がかけません・・・・・・><

どなたか、アドバイス、参考サイトなど知っていたら教えて頂けると助かります。

宜しくお願い致します。


ryo  2009-07-29 10:03:46  No: 70682

>どうしてもcsvファイルとして書き出す処理がかけません・・・・・
これだけじゃ、どういう状態で、どこで困っているのかわからない。

CSVファイルは、「,」記号でデータをわけてあるだけの
ただのテキストファイルです。

たとえば、
aaaa,bbb,ccc
と、文字列が書かれたファイルがあったとして
拡張子が「.txt」ならテキストファイルだし
拡張子が「.csv」ならCSVファイルです。

なので、
テキストファイルを書き出す処理ができれば
あとはファイル名と中身の「文字列」を上手くつくるだけです。


超初心者  2009-07-29 18:24:34  No: 70683

ryoさんお世話になります。

なんというか、読み込み・書き出し対象がcsvなので余計にこんがらがってしまっている自分がいます><

実際詰まっているのは、csvを一行ずつ読みこんだ後の処理ですね。ただ単に
aaaa,bbb,ccc
といったファイルなら、Tokenize()でカンマ区切りで切り出せばいいと思いますが、データの中に「"」や「,」、「\」等が入っていた場合も想定しないといけないので、それを考えているうちにこんがらがってしまった次第です・・・。

書き出し部分にいたっては、どうやってcsvとして書き出すのかすら分からない状態で・・・・><


みい  2009-07-29 19:15:37  No: 70684

テキストファイルの読み書きはできるレベルなのでしょうか。
CSVは拡張子が違うだけ&Excelなどで開いた時にカンマで列が区切られて表示されるだけ。
とりあえず固定文字列の書き込みを書いてみた方がよいかも。

<CSV読込&分解>
http://jrk813.xrea.jp/programing/read_csv-file
<書込>
http://madia.world.coocan.jp/cgi-bin/Vcbbs/wwwlng.cgi?print+200809/08090010.txt

> データの中に「"」や「,」、「\」等が入っていた場合も想定しないといけない
「,」は区切り文字として使用するので、データに入れてはいけません。
他のは特殊処理をしたいなら、文字列を切り出した後にその中にその文字がないか検索すればいい(例えばCStringのFindやFindOneOf)。


超初心者  2009-07-29 19:48:43  No: 70685

みいさんお世話になります。

>テキストファイルの読み書きはできるレベルなのでしょうか。

昨日いろいろやってて、単純にcsvを一行単位でよみこみ、vectorに保持しておいて、それを単純に書き出すという事は出来ました。。
これで出来ているレベルかどうかは疑わしいですが><間違い当あればご指摘宜しくお願い致します。

CStdioFile stdWrite;
//書き込みモード
if( stdWriteFile.Open(ファイルパス, CFile::modeCreate | CFile::modeWrite)==NULL )
{  
    TRACE("error\n");
    return;
}

for( size_t i=0; i<vcsTextList.size(); i++ )
{
    wstr = vcsTextList.at(i);
    TRY
    {
        stdWrite.WriteString(wstr);
    }
    CATCH (CFileException, eP)
    {
    }
    END_CATCH

}
stdWrite.Close();

こんな感じでしょうか。

特殊処理は自分ではReplaceを使うのかなと思っていたのですが、FindやFindOneOfなんでのもあるのですね。参考になります。
提示していただいたのを参考にやってみます。

ところで少しお聞きしたいのですが、TRY、CATCHは出来るだけ行わない方がいいと書かれてあるサイトがあったのですが(チラッと見ただけですので詳しい内容は覚えていません)、これは明確な理由があるのでしょうか?
もし、行わないんであれば

    TRY
    {
        stdWrite.WriteString(wstr);
    }
    CATCH (CFileException, eP)
    {
    }
    END_CATCH

の部分をどうあらわせばいいのでしょう?

TRY、CATCHできるんであれば、行った方が可視性においてもいいかと思っているのですが・・。速度的な問題ですか?


επιστημη  URL  2009-07-30 18:04:36  No: 70686

> TRY、CATCHは出来るだけ行わない方がいいと書かれてあるサイトが
> あったのですが(チラッと見ただけですので詳しい内容は覚えて
> いません)、これは明確な理由があるのでしょうか?

そのサイトでどう説明されていたのかわからないので
なんともいえません。
言語仕様の定める try-catch がダメなのか、
マクロによる TRY-CATCH がダメなのか、
そしてなぜダメなのか、サイト主じゃないとわからんです。


tetrapod  2009-07-30 19:33:35  No: 70687

スレ主の見たページとは違いそうだけどとりあえず貼っておこう。
むやみに catch しないでね。ゴールキーパー以外はハンドで反則ですよ
http://d.hatena.ne.jp/NAL-6295/20050909/p1


超初心者  2009-07-30 19:46:49  No: 70688

επιστημηさんお世話になります。

内容は自分もきちんと覚えていないので違うかもしれませんが、確か

「例外処理はC++だとif文に比べすごく遅いので、if文で解決できるエラー処理に例外処理を使用しないほうがいい」

という内容だった気がします。

どれくらい遅くなるのかまではわかりませんが、try/catchに対応している以上は使った方がいいかなと思っている次第です。


επιστημη  URL  2009-07-30 20:01:14  No: 70689

> どれくらい遅くなるのかまではわかりませんが

ファイル読み書き自体があほほど遅いんだから、
例外による速度低下なんざカスみたいなもんでしょ。
それに例外が処理の最中にン千回起こるわけでもなかろうし。


超初心者  2009-07-30 20:04:48  No: 70690

tetrapodさんお世話になります。

自分が見たページとは違いますが、参考になります。ありがとうございます。
自分はtryしたらcatchするもんだろと当り前のように思っていたのですが、このページで書かれている方は必ずしもそうではないとおっしゃられていますね。
では、
    TRY
    {
        stdWrite.WriteString(wstr);
    }
    CATCH (CFileException, eP)
    {
    }
    END_CATCH
はCatchする事に意味があるのかというと、いかんせん素人なので正直わかりません><


超初心者  2009-07-30 20:12:11  No: 70691

επιστημηさんお世話になります。

なるほど。読み書き自体が遅いので例外処理の速度低下なんかは気になるレベルでないと。

例外処理に関しては、そういう考えもあるという程度で捉えておいて、今回に関してはそのままtry/catchを行おうと思います。


επιστημη  URL  2009-07-30 20:15:18  No: 70692

> 自分はtryしたらcatchするもんだろと当り前のように思っていたのですが

てか、例外を捕まえて対処したいから try-catch するんでしょ?
catch時(例外発生時)にやる処理がみつからんなら
なんで try-catch するんです?


超初心者  2009-07-30 20:30:08  No: 70693

επιστημηさんお世話になります。

>catch時(例外発生時)にやる処理がみつからんなら
>なんで try-catch するんです?

おっしゃる通りですね・・・・・。御指摘ありがとうございます。


tetrapod  2009-07-31 00:27:36  No: 70694

まあいろんな考え方があるわけで、以下で述べてる内容が常に正しいわけではないが
とりあえず考え方の1つとしては妥当な範囲だと思うので・・・

・ユーザーの操作によって発生しうるエラー(プログラムの想定内の動き)
・本来、起こってはならない例外(端的に言えばプログラムのバグ)
の2者は異なる、ということ。

・ユーザーが指定したファイルを開くことに失敗した(権限がないとか)
・ファイルに対する書き込みに失敗した(ディスクがいっぱいとか)
というのは、あらかじめ想定される範囲内なので「正しくエラーが起こる」のが動きとして適切。
そういう「エラーが起こることが正しい」時には例外を使うべきでない。

一方で、プログラムのバグとは
・書き込み処理中にぬるぽ発生
・書き込み処理中に自分のプログラム中にてメモリ破壊発生
・書き込み処理中にOS内部に矛盾発生
・起こるはずのない0除算
などというのは、想定されない異常。こういう場合には例外を使うべし。

で、むやみに catch するな、というのは、
「例外というのは本来、回復不可能な異常が起きたときに使うものである」
という立場に立って考える場合の話。
そういう回復不可能な異常を、勝手に catch してもみ消してしまうとデバッグすらできないよ
ということ。

ただ、世の中には考え方が違う人も団体もいて
「想定されたエラー処理の一環として例外を使う」とか
「回復不可能なエラーに対して例外を使わない」とか
そういう仕様の異なるライブラリを使わざるを得ない場合に try/catch/throw が必要。

CStdioFile::WriteString の仕様書を見ると
「ディスクフル等のいくつかの状況に対して例外を投げます」
とあるので、こいつは「想定されたエラー処理の一環として例外を使う」という動きをする。
なので、今挙げているような設計方針のプロジェクトで CStdioFile を使うには
TRY-CATCH (try/catch) が必須ということになる。
(どの層で try/catch すべきかはまた話のレベルが違う)
(どの例外は catch してよくて、どの例外は rethrow しなければならないかも話が違う)

CFileException のうち、一部は catch してエラーに変換する必要がある
ほとんどの例外は catch せずに rethrow する必要がある
ということだ。
どれを catch すべきかはスレ主の仕様しだい。
よくわからないのなら、こんな下のほうの処理で try/catch など書かないほうがいい。


超初心者  2009-07-31 01:19:01  No: 70695

tetrapodさんお世話になります。

詳しく書いて頂き、ありがとうございます。
自分の知識のなさを露呈してしまって恥ずかしい限りですが、書かれていることを熟読して理解を深めていこうと思います。

ありがとうございました。


超初心者  2009-08-03 23:49:51  No: 70696

あれから色々試行錯誤してますが、思うようになりません。

csvを読み込み、内容を構造体にセット。セットした内容をcsvにして出力、といったことをやってるんですが・・・・・・。

csv出力のところで詰まってしまいました。

USER.csv

ID  NAME  PASSWORD
aa   山田     aa    
bb   加藤     bb     
cc   田中     cc    
dd   石井     dd 

ヘッダに定義してある構造体

// ユーザ情報
typedef struct _USER_INFO {
          CString csUserId;        // ユーザID
          CString csUserName;    // ユーザ名  
          CString csUserPass;    // ユーザパスワード
} USER_INFO, *LPUSER_INFO;

// ユーザ情報リスト
typedef std::vector <LPUSER_INFO> USER_INFO_LIST;
typedef std::vector <LPUSER_INFO> * LPUSER_INFO_LIST;

bool CNoticeDlg::OutPutCSVFile()
{
  LPUSER_INFO pstUserInfo;
  pstUserInfo = new USER_INFO;

  HANDLE hFile;
  CStdioFile stdWriteFile;

  CString wstr;
  LPTSTR rstrBuf = NULL;
  int err = 0;

  // ファイル名
  CString csMakeUserCsvFilePath = _T( "C:\\Users\\sampleUsers.csv" );

  // ファイル作成
  hFile = CreateFile(
                    csMakeUserCsvFilePath,      // ファイル名
                    GENERIC_READ|GENERIC_WRITE, // 読み書きアクセス
                    FILE_SHARE_READ,            
                    NULL,                       
                    CREATE_ALWAYS,              
                    FILE_ATTRIBUTE_NORMAL,      // このファイルに特に属性を設定しない
                    NULL
  );
  // 失敗時
  if( hFile == INVALID_HANDLE_VALUE )
  {
    AfxMessageBox(_T("ファイル作成失敗"));
    return FALSE;
  }
  //ハンドルを閉じる
  CloseHandle( hFile );

  CFile file;
  int len;
  char szBuff[128];

  if ( !file.Open( csMakeUserCsvFilePath, CFile::modeCreate | CFile::modeWrite ) )
  {
    MessageBox( csMakeUserCsvFilePath + _T("Open error!") );
    return FALSE;
  }
  
  //書き込み
  for (size_t i=0; i<m_lpvUserInfoList->size(); i++)
  {
    pstUserInfo = m_lpvUserInfoList->at(i);
    
    sprintf_s(szBuff, "%s,%s,%s",
                                pstUserInfo->csUserId,
                                pstUserInfo->csUserName,
                                pstUserInfo->csUserPass
    );

    len = (int)strlen(szBuff);
    szBuff[len]=0x0d;
    len++;
    szBuff[len]=0x0a;
    len++;
    file.Write( szBuff, len );
  } 
  file.Close();
  return TRUE;

}

これで出力すると、極端な話

I  N    P
a  山   a    
b  加   b     
c  田   c    
d  石   d 

と先頭の一文字しか出力されず、しかも日本語の部分が文字化けして表示されてしまいます。構造体のなかにはきちんと化けずにセットされています。

原因がちょっとわかってないので、処理が間違っている等どなたかアドバイスしていただけるとありがたいです。

どうぞ宜しくお願い致します。


超初心者  2009-08-07 04:43:28  No: 70697

すみません、毎度お世話になります。

アレから何度か試していて、

    sprintf_s(szBuff, "%s,%s,%s",
                                pstUserInfo->csUserId,
                                pstUserInfo->csUserName,
                                pstUserInfo->csUserPass
    );

のpstUserInfo->csUserIdがCStringだったのがいけないのかと思って、CStringをcharに変換しようと思うのですが、strcpy_sでの変換が上手くいきません。

誰か使い方、またはCStringからcharへの変換の仕方を教えてもらえないでしょうか??宜しくお願いいたします。


επιστημη  URL  2009-08-07 06:38:43  No: 70698

const char* にキャストするだけでえぇんとちゃいます?


超初心者  2009-08-10 17:46:41  No: 70699

επιστημηさん、お世話になります。

const char* にキャストするやり方がいまいちわからなくて、未だ実現できておりません。

どのようにキャストするのかお教え頂けないでしょうか?宜しくお願い致します。


επιστημη  URL  2009-08-10 18:16:48  No: 70700

> const char* にキャストするやり方がいまいちわからなくて、

「いまいちわからなくて」ということは「あと少しでわかる」わけですね。
あと少し、なにがわからないのでしょうか。


超初心者  2009-08-10 18:31:58  No: 70701

επιστημηさんお世話になります。
すみません、いまいちと言ってますが実のところ全然わからないです・・。

色々調べて見て、strcpy_sを使わなくても

char  szBuff[4096];

sprintf_s(szBuff, "%s,%s,%s",
    (LPCTSTR)pstUserInfo->csUserId,
    (LPCTSTR)pstUserInfo->csUserName,
    (LPCTSTR)pstUserInfo->csUserPass
);

これでいけるんじゃないかと思ったんですが、コンパイルは通るのですが結果は同じでした・・。

申し訳ありませんが、ご指導宜しくお願い致します。


επιστημη  URL  2009-08-10 18:50:54  No: 70702

いやだから、そもそもsprintf_sを使っていいんですか?
UNICODEなんじゃなかったけ?


超初心者  2009-08-10 19:52:51  No: 70703

επιστημηさんお世話になります。

調べてみました。VS2005からはデフォルトの文字セットがUnicodeになったため、 
TCHARはすべてwchar_t型にコンパイル時に置換される、とありましたがこれが関係していますでしょうか?

だとしたらsprintf_sではなくswprintf_sを使うという事でよろしいでしょうか?


επιστημη  URL  2009-08-10 19:57:46  No: 70704

> だとしたらsprintf_sではなくswprintf_sを使うという事でよろしいでしょうか?

ここまでに示されたコードのスタイルだと SBCS/MBCS でも UNICODE でも
正しく動作することを考慮してるんですよね?
ならば 
char szBuff[4096]; → TCHAR szBuff[4096];
sprintf_s → _stprintf_s 
ですがー。


επιστημη  URL  2009-08-10 19:59:40  No: 70705

で、CSVファイルのエンコードはどうするんです? 
Shift_JIS? UTF-8? UTF-16? ほかのなにか?


超初心者  2009-08-11 02:01:09  No: 70706

επιστημηさん、毎度お手数おかけします。

>SBCS/MBCS でも UNICODE でも正しく動作することを考慮してるんですよね?

はい、そうです。charではなくTCHAR、sprintf_sではなく_stprintf_sを使うのですね。早速試してみます。

>CSVファイルのエンコードはどうするんです?

Shift_JISで出力したいと思っています。


επιστημη  URL  2009-08-11 02:25:15  No: 70707

>>CSVファイルのエンコードはどうするんです?
> Shift_JISで出力したいと思っています。

ならば MBCSのときはそのまま出力でよさそうだけど、
UNICODE時にはUTF-16からShift_JISへの変換をかけにゃならんですね。
# 読み込み時にはその逆。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加