pit-rayの備忘録

開発中に得た知識を記事にしていくブログ

【C++】ifstreamでUTF-8のBOMをスキップする方法(boost property tree)

今回は、前回とは逆で、UTF-8 with BOMを読み込むときにBOMを取り外す方法をご紹介します。

例として、boostのproperty_treeでファイルを読み込む場合を考えます。
何故なら、property_treeではBOMに対応してないため、正常にデータを読み取ることができないからです。

まず、BOMとはなにか。
前回の記事を参照してください。
www.pit-ray.com


BOMはファイルの先頭に書き込まれたデータを指します。
つまり、BOM部分をうまく読まないようにすれば、BOMなしのUTF-8として扱うことができます。


ところで先頭には、
0xEF
0xBB
0xBF

というデータが書き込まれています。

ifstreamでは、先頭から順に読んでいきます。
read関数を用いた場合は、読んだ位置まで現在位置を進めます。

この性質を生かします。


また、boostのproperty_treeのread_xml関数を見てみると以下のように、basic_istreamを引数として持つことが分かります。

// In header: <boost/property_tree/xml_parser.hpp>

template<typename Ptree> 
  void read_xml(std::basic_istream< typename Ptree::key_type::value_type > & stream, 
                Ptree & pt, int flags = 0);

(boost公式ドキュメント:Function template read_xml - 1.65.1


ifstreamは現在位置を保持するので
読み取り開始位置がBOMを読み終えた位置にある、basic_istreamを用意すればよい
という方針が立ちました。

以下のようになります。

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <fstream>

void utf8bom_to_utf8( std::ifstream& rhs )
{
    static unsigned char bom[] = { 0xEF, 0xBB, 0xBF } ;
    unsigned char buffer ;

    for( const auto& b : bom ) {
        rhs.read( reinterpret_cast<char*>( &buffer ), sizeof( buffer ) ) ;
        if( buffer == b ) {
            continue ;
        }
        else {
            rhs.seekg( 0 ) ; //bomなしの場合は、位置を先頭に戻す
            return ;
        }
    }
}

int main()
{
    std::ifstream ifs( "u8b.xml" ) ; //UTF-8 with BOMのファイル
    utf8bom_to_utf8( ifs ) ;
    boost::property_tree::ptree pt ;
    boost::property_tree::xml_parser::read_xml( ifs, pt ) ;

    //以下省略
}


プログラム自体は最適化しているわけではないので、もう少し良い設計があると思います。

参考になれば幸いです。
ご指摘等ありましたら、コメントかお問い合わせフォームにてお気軽にお寄せください。

では。

【C++】ofstreamでUTF-8 with BOMを出力する方法

今回は、C++の標準ライブラリのfstreamのwrite関数を用いた、バイトオーダーマーク(BOM)の付け方をご紹介します。

codecvtを用いた方法もあるようですが、私の環境ではうまくできなかったため、直接バイナリデータを書きこむ方法を用います。

バイトオーダーマークの説明をWikipediaから引用すると、以下のようにあります。

Unicodeの符号化形式で符号化したテキストの先頭につける数バイトのデータのことである
~中略~
プログラムがテキストデータを読み込む時、その先頭の数バイトからそのデータがUnicodeで表現されていること、また符号化形式(エンコーディング)としてどれを使用しているかを判別できるようにしたものである
(Wikipedia:バイトオーダーマーク - Wikipedia])

つまり、バイトオーダーマークとは、
どのような文字コードを用いて表現されているかを示す、判別用のデータのことです。
わざと冗長性を持たせているわけです。

よって、UTF-8からUTF-8 with BOMへの変換はそこまで難しくはありません。
プログラマーは、出力するファイルがUTF-8と分かっているので、決められたデータを付与するだけです。

テキストの先頭に付けるデータは
UTF-8の場合、
0xEF
0xBB
0xBF

です。

それぞれ、16進数2桁で表されているので、unsigned char型を使うのがベストです。

バイナリに書き込むには以下のようにwrite関数を用います。

#include <iostream>
#include <fstream>

using namespace std ;
int main()
{
    //BOMあり
    ofstream ofs_bom( "u8b" ) ;
    unsigned char bom[] = { 0xEF, 0xBB, 0xBF } ;
    ofs_bom.write( reinterpret_cast<char*>( bom ), sizeof( bom ) ) ;
    ofs_bom << "UTF-8 with BOM" << endl ;

    //BOMなし
    ofstream ofs( "u8" ) ;
    ofs << "UTF-8" << endl ;

    return 0 ;
}

結果

f:id:pit-ray:20181211021923j:plain
バイナリエディタ(Stirling)による確認:BOMなし
f:id:pit-ray:20181211021852j:plain
バイナリエディタ(Stirling)による確認:BOMあり
このようにBOMありには、書き込んだ先頭データがあるのが分かります。

当然、VSCodeなどのエディタで開くと以下のようになります。

f:id:pit-ray:20181211021529j:plain
VSCodeによる判定:BOMなし
f:id:pit-ray:20181211021532j:plain
VSCodeによる判定:BOMあり


以上です。
記事に対してのご意見等は、コメントやお問い合わせフォームからご指摘いただけると幸いです。