はじめに

web サイトスクレーピングで全角文字を含む utf8 の html を Visual Studio 環境ではどう扱うのが適切かの検討。

そもそも

  • マルチバイト文字
    • 1 文字を複数バイトで表す。
    • 2 バイト固定長でもマルチバイトと言える。
  • 文字符号化方式
    • 0x うんたらかんたらなら「あ」と解釈するを決める規則の事
  •  UTF-8
    • 文字符号化方式
    • 1-4 バイトの可変長
  • Shift_JIS – ms code page 932
    • 文字符号化方式
    • ほぼ 2 バイト固定
  • UTF-16
    • 文字符号化方式
    • 16bit を 1 単位とし 16bit または 16bit x 2 の可変長
  • ANSI X3.4 (ASCII)
    • 文字集合、符号化方式も定義している
    • cf. 同様に文字集合、符号化方式を定義している物に Unicord がある
  •  Unicode
    • ISO/IEC 10646 で文字集合、文字符号化方式を標準化
    •  文字集合
      • ⇒ 符号空間は0-10FFFF16で1,114,112文字を含むことができる
    • 文字符号化方式
      • UTF-7, 8, 16, 32
    • Q. ブラウザで Unicode を選択した時の符号化方式は何になる?
      • ⇒ html 内で <meta charset=”UTF-8″> 等と指定されているのでその通りエンコーディングすればいいし、メタ情報が書いてなかったら OS の設定に従うとか実装できるしブラウザ側でどうとでも実装できる。

std::wstring のエンコーディング

  • 文字のエンコーディングが Unicode の日本語で書かれたサイト、html 内で <meta charset=”utf-8″> が指定されている。
  • curl で std::string に格納
  • std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter でワイド文字に変換。
    • Q. この時にマルチバイト文字は適切にエンコーディングされているのだろうか?
      • ⇒ utf16 で符号化されている。
  • 上記 std::wstring を std::wofstream file( L”2325.html” ); でファイル出力すると途中までしか出力されない
    • std::wofstream もソースの std::wstring を utf16 で扱ってくれるのでは?
    • しかし、全角の直前で途切れる。なぜ?恐らくファイルの終端扱いされていると思うが。
      • ワイド文字を扱う関数はロケールによって符号化方式が変わるから。ロケールを設定していなければ C ロケールになっており ASCII として扱われている。
  • std::locale::global( std::locale( “japanese” ) ); を指定すると std::wofstream でファイルが途切れずに出力された。
    • Q. std::locale::global( std::locale( “japanese” ) ); で何をしているのか?
      • C/C++ 標準ライブラリは wchar_t をどの様なエンコーディングで扱うかは locale で設定する事になっている。
      • std::locale::global( std::locale( “” ) ); で OS の設定言語から設定が反映される。
      • 何も設定しなければ C ロケールになっている。下記のコードで “C” が返る。
        std::string locstr = std::locale().c_str();
      • C ロケールの場合はマルチバイトを扱う関数はデフォルトでは ASCII コードであるとみなされる。
        • ⇒ これが原因でファイル出力が途切れていた。

std::string のエンコーディング

  • curl で std::string に出力する
  • std::ofstream file( L”2325.html” ); でファイルに出力したら UTF-8 CRLF で出力される。
    • ⇒ 期待した挙動でファイルも途切れていない、文字化けも無い。
  • Q. “連17” という文字列を find() しようとしても npos が返る。
    • ソース上に記述した文字列 “連” をメモリ上で見ると 98 41 つまり SJIS でエンコードされているようだ。
    • utf8 で符号化しているデータを sjis の符号で find() しようとしているのでヒットしない。
      • ⇒ ソース上の文字列 “連” を utf8 でエンコーディングできたら find() でヒットする。
    • Q. Visual Studio で文字列エンコーディングの指定ができないか?
      cf. https://ameblo.jp/tomisams/entry-11254343403.html

      • Visual Studio の設定ではできず、”連” は SJIS でエンコードされる。
      • Visual Studio では L”ほげ” だと UTF16 になる。
      • pragma で制御可能
        #pragma execution_character_set("utf-8")
      • C++11 なら u8 を使用可能。
         printf( u8"ほげほげ" );
  • 結局、日本語を扱う場合は入力が UTF8 だとしたら
    • UTF16 のワイド文字に変換して以降は UTF16 で扱う。文字列リテラル L”ほげ” が UTF16 にエンコーディングされるため。または、
    • UTF8 のまま扱うなら文字列リテラルに u8″ほげ” を使用する。
      • ⇒ sjis にしないと cereal で事故りそうな気がする。cereal では string は sjis 扱いだから。(たぶん)
      • ⇒ u8″” を使用する場合はテキストビジュアライザとかでも(たぶん)途切れてしまうのであまり得策じゃないかも。
      • ⇒ 標準出力もコンソールが sjis でエンコーディングするので日本語が文字化けする。
        // 縺ゅ>縺・∴縺 と表示される。
        printf( u8"あいうえお" );
    • UTF8 別案: 入力が utf8 なら utf16 のワイド文字列にコンバートして sjis にコンバートする
      // utf8 string
      std::string str( "something" );
      
      // utf8 to utf16
      std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
      std::wstring wstr = converter.from_bytes( str );
      
      // utf16 to sjis
      int codePage = 932;    // cp932(sjis)
      int length( ::WideCharToMultiByte( codePage, 0, &wstr[0], -1, NULL, 0, NULL, NULL ) );
      std::vector<char> result( length + 1, '\0' );
      ::WideCharToMultiByte( codePage, 0, &wstr[0], -1, &result[0], length, NULL, NULL );
      • ⇒ もし、sjis で符号化された std::string を std::ofstream でファイルに出力する場合はソースの std::string を utf8 として書き出すので出力したファイルを開くと文字化けする。書き出す前に sjis から utf8 に変換する必要がある。また雑な方法としてはファイル出力してから nkf32 で sjis to utf8 変換を行う。
        // test.xml を -S: sjis と仮定して -w8: utf8 に変換する。
        nkf32.exe -S -w8 --overwrite test.xml