8

私は最初から StackOverflow を使用しており、時々質問を投稿したくなることがありますが、私は常に自分でそれらを理解するか、最終的に投稿された回答を見つけました...今まで. これはかなり単純なはずですが、私は何時間もインターネットをさまよい続けてきましたが、うまくいきませんでした。

英語と中国語の文字が混在する、かなり標準的な utf-16 テキスト ファイルがあります。これらの文字が文字列 (技術的には wstring) になるようにしたいと思います。私は多くの関連する質問が回答されているのを見てきましたが (ここや他の場所で)、エンコーディングを知らずに任意のファイルを読み取る、またはエンコーディングを変換するというはるかに困難な問題を解決しようとしているか、単に「Unicode について混乱している」だけです。 " はエンコーディングの範囲です。読み込もうとしているテキスト ファイルのソースはわかっています。常に UTF16 であり、BOM などすべてが含まれており、そのままの状態を保つことができます。

こちらで説明されている解決策を使用していましたが、すべて英語のテキスト ファイルで機能しましたが、特定の文字に遭遇した後、ファイルの読み取りが停止しました。私が見つけた他の唯一の提案は、 ICUを使用することでした。これはおそらく機能しますが、1 つのテキスト ファイルを 1 か所で読み取るためだけに、配布用のアプリケーションに大きなライブラリ全体を含めることは避けたいと思います。ただし、システムの独立性は気にしません。Windows でコンパイルして作業する場合にのみ必要です。その事実に依存しないソリューションの方がきれいだろうもちろん、しかし、Windows アーキテクチャに関する仮定に依存しながら stl を使用したソリューション、または win32 関数または ATL を含むソリューションにも同様に満足しています。ICU のような別の大規模なサードパーティ ライブラリを含める必要はありません。自分ですべて再実装したくない場合は、まだ完全に運が悪いのでしょうか?

編集:私はこの特定のプロジェクトで VS2008 を使用して立ち往生しているため、残念ながら C++11 コードは役に立ちません。

編集 2: 以前に借りていたコードが、思っていたように英語以外の文字で失敗しないことに気付きました。むしろ、':' (全角コロン、U+FF1A) と ')' (全角右括弧、U+FF09) など、テスト ドキュメントの特定の文字で失敗します。bames53 の投稿されたソリューションもほとんど機能しますが、同じキャラクターに困惑していますか?

編集 3 (および答え!): 私が使用していた元のコードはほとんど機能しました。

4

3 に答える 3

11

C++ 11 ソリューション (私の知る限り、2010 年以降、Visual Studio によってサポートされているプラ​​ットフォーム) は次のようになります。

#include <fstream>
#include <iostream>
#include <locale>
#include <codecvt>
int main()
{
    // open as a byte stream
    std::wifstream fin("text.txt", std::ios::binary);
    // apply BOM-sensitive UTF-16 facet
    fin.imbue(std::locale(fin.getloc(),
       new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>));
    // read     
    for(wchar_t c; fin.get(c); )
            std::cout << std::showbase << std::hex << c << '\n';
}
于 2012-05-08T18:25:55.680 に答える
8

UTF-16 でファイルを開く場合は、バイナリ モードで開く必要があります。これは、テキスト モードでは、特定の文字が特別に解釈されるためです。具体的には、0x0d は完全に除外され、0x1a はファイルの終わりを示します。これらのバイトの 1 つを文字コードの半分として持つ UTF-16 文字がいくつかあり、ファイルの読み取りを混乱させます。これはバグではなく、意図的な動作であり、テキスト モードとバイナリ モードを分離する唯一の理由です。

0x1a がファイルの末尾と見なされる理由については、Raymond Chen によるこのブログ投稿を参照して、Ctrl-Z の履歴をたどってください。それは基本的に後方互換性ランアモックです。

于 2012-05-09T03:30:52.343 に答える
4

編集:

そのため、問題は、Windows が特定のマジック バイト シーケンスをテキスト モードのファイルの末尾として扱うことだったようです。これは、バイナリ モードを使用してファイルを読み取り、std::ifstream fin("filename", std::ios::binary);既に行っているようにデータを wstring にコピーすることで解決されます。



最も単純で移植性のない解決策は、ファイル データを wchar_t 配列にコピーすることです。これは、Windows の wchar_t が 2 バイトであり、エンコーディングとして UTF-16 を使用するという事実に依存しています。


完全に移植可能な方法で UTF-16 をロケール固有の wchar_t エンコーディングに変換するのは少し難しいでしょう。

標準 C++ ライブラリで使用できる Unicode 変換機能を次に示します (ただし、VS 10 および 11 は項目 3、4、および 5 のみを実装します)。

  1. codecvt<char32_t,char,mbstate_t>
  2. codecvt<char16_t,char,mbstate_t>
  3. codecvt_utf8
  4. codecvt_utf16
  5. codecvt_utf8_utf16
  6. c32rtomb/mbrtoc32
  7. c16rtomb/mbrtoc16

そして、それぞれが何をするのか

  1. UTF-8 と UTF-32 の間で常に変換する codecvt ファセット
  2. UTF-8 と UTF-16 の間の変換
  3. ターゲット要素のサイズに応じて、UTF-8 と UCS-2 または UCS-4 の間で変換します (BMP の外側の文字はおそらく切り捨てられます)。
  4. UTF-16 エンコーディング スキームと UCS-2 または UCS-4 を使用して一連の文字を変換します
  5. UTF-8 と UTF-16 の間の変換
  6. マクロ__STDC_UTF_32__が定義されている場合、これらの関数は現在のロケールの char エンコーディングと UTF-32 の間で変換します
  7. マクロ__STDC_UTF_16__が定義されている場合、これらの関数は現在のロケールの char エンコーディングと UTF-16 の間で変換します

が定義されている場合、そのマクロは、すべてのロケールの wchar_t 値が Unicode チャーターの短い名前に対応することを示しているため (したがって、wchar_t がそのような値を保持するのに十分な大きさであることを意味します)、__STDC_ISO_10646__直接変換を使用しても問題ありません。codecvt_utf16<wchar_t>

残念ながら、UTF-16 から wchar_t に直接移行するものは定義されていません。UTF-16 -> UCS-4 -> mb (if __STDC_UTF_32__) -> wc に進むことは可能ですが、ロケールのマルチバイト エンコーディングで表現できないものはすべて失われます。もちろん、何があっても、UTF-16 から wchar_t に変換すると、ロケールの wchar_t エンコーディングで表現できないものはすべて失われます。


したがって、おそらく移植する価値はありません。代わりに、データを wchar_t 配列に読み込むか、ファイルの _O_U16TEXT モードなど、他の Windows 固有の機能を使用できます。

これはどこでもビルドして実行する必要がありますが、実際に機能するにはいくつかの仮定があります。

#include <fstream>
#include <sstream>
#include <iostream>

int main ()
{
    std::stringstream ss;
    std::ifstream fin("filename");
    ss << fin.rdbuf(); // dump file contents into a stringstream
    std::string const &s = ss.str();
    if (s.size()%sizeof(wchar_t) != 0)
    {
        std::cerr << "file not the right size\n"; // must be even, two bytes per code unit
        return 1;
    }
    std::wstring ws;
    ws.resize(s.size()/sizeof(wchar_t));
    std::memcpy(&ws[0],s.c_str(),s.size()); // copy data into wstring
}

おそらく、少なくともエンディアンと「BOM」を処理するコードを追加する必要があります。また、Windows の改行は自動的に変換されないため、手動で変換する必要があります。

于 2012-05-08T20:04:29.737 に答える