2012年4月23日 星期一

可讀取 Unicode 檔案的 wifstreambuf

因發現 wifstream 不能用 Unicode 檔名,覺得太不方便了,所以寫了以下這個 class,但後來發現 gcc 的 wifstream/wistream 也沒辦法處理 Unicode 文字為內容的檔案,wifstream/wistream 只是虛有其名,所以計畫放棄了。

改用 vs2008 來做,vs2008 的 wifstream 可以使用 Unicode 的檔名,但 wifstream 仍不能讀取 Unicode 的文字內容,不過用 wistream 配合以下的 class 卻成功了。

經網友 ice_emissary(燃燒的大地) 提醒是 Unicode BOM 檔頭的問題,修改後果然成功。

另外 wifstream 應該只是用來讀取 ansi 的檔案,只不過會自動轉型成 wchar_t 來使用


以下參考資料的連結
http://www.cplusplus.com/reference/iostream/streambuf/
http://oopweb.com/CPP/Documents/CPPAnnotations/Volume/cplusplus20.html
http://www.diybl.com/course/3_program/c++/cppjs/200822/98427.html
http://www.dreamincode.net/code/snippet2499.htm

以下是 wifstreambuf.h 的內容,只是一個 .h 檔

/*------------------------------------------------------------------------

wifstreambuf.h 1.0.2

Copyright 楊志賢 CxxlMan, 2012
All Rights Reserved

- 可以用 Unicode 檔名讀取 Unicode 文字檔
- 只能處理 win32 平台用的 UTF-16 Unicode
- 供 wistream 使用
- 只能用於 win32 平台,因使用 _wopen() 函數

------------------------------------------------------------------------*/


#ifndef WIFSTREAMBUF_H_INCLUDED
#define WIFSTREAMBUF_H_INCLUDED

#include <istream>
#include <io.h>
#include <fcntl.h>
#include <windows.h>

namespace CxxlMan
{

// 可以用 Unicode 檔名的 streambuf
class wifstreambuf:public std::wstreambuf
{
  int fd; // 保存由 _wopen() 開啟的檔案 handle

  unsigned m_bufsize;
  wchar_t *m_buffer;


  virtual int_type underflow()
  {
    int r = read(fd, m_buffer, m_bufsize * sizeof(*m_buffer));
    if(r == 0)
      return traits_type::eof();

    wchar_t *Start;

    if(*m_buffer == 0xfeff) // 若含有 BOM 標識
      Start = m_buffer + 1;
    else
      Start = m_buffer;

    wchar_t *End = m_buffer + r / sizeof(*m_buffer);

    if(Start >= End)
      return traits_type::eof();

    setg(Start, Start, End);
    return *gptr();
  }

  // seekg() 會叫用到,因只是做讀入用,所以不做 out 的處理
  virtual pos_type seekoff(off_type off, std::ios_base::seekdir dir,
                   std::ios_base::openmode which = std::ios_base::in)
  {
     pos_type pos =  lseek(
                        fd, off * sizeof(*m_buffer),
                        (dir ==  std::ios::beg) ? SEEK_SET :
                        (dir ==  std::ios::cur) ? SEEK_CUR :
                                                  SEEK_END
                      );

    if (pos < 0) return -1;

    setg(m_buffer, m_buffer + 1, m_buffer + 1); // 這樣可以強迫重讀
      return pos;
  }

  virtual std::streamsize xsgetn(char_type *dest, std::streamsize n)
  {
    int nread = 0;

    while (n > 0)
    {
      int avail = in_avail();
      if (avail == 0)
      {
        if(underflow() == traits_type::eof())
           break;
        avail = in_avail();
      }

      if(avail > n)
        avail = n;

      memcpy((char*)(dest + nread), (char*)gptr(), avail*sizeof(*dest));
      gbump(avail);

      nread += avail;
      n -= avail;
    }

    return nread;
  }

public:

  // Constructor
  wifstreambuf(const wchar_t *Filename)
  {
    fd = _wopen(Filename,_O_RDONLY | _O_BINARY);
    m_bufsize = 512;
    m_buffer = new wchar_t[512];

    // 一開始設 buffer 是空的
    setg(m_buffer, m_buffer + m_bufsize, m_buffer + m_bufsize);
  }

  // Destructor
  virtual ~wifstreambuf()
  {
    if(fd != -1)
      close(fd);

    delete [] m_buffer;
  }

  bool isOpen()
  {
    return fd != -1;
  }
};

}  /* namespace CxxlMan */
#endif // WIFSTREAMBUF_H_INCLUDED

以下是測試碼

#include <sstream>
#include <iostream>
#include <fstream>
#include "wifstreambuf.h"

using namespace std;
using namespace CxxlMan;

int main()
{
  // 指定系統現正使用中的地區語言為 wcout 做轉換
  setlocale(LC_CTYPE, "");

  // 可以用 Unicode 的檔名,內容只有 "123 中文" Unicode 文字
  wifstreambuf wfin_buf(L"測試.txt");

  if(wfin_buf.isOpen())
  {
    wistream wfin(&wfin_buf);

    int i; wstring str;
    wfin >> i;
    wfin >> str;
    wcout << i << " " << str << endl;
  }
  else
    cout << "開檔失敗!" << endl;
  return 0;
}





沒有留言:

張貼留言