web-dev-qa-db-ja.com

std :: ifstreamを取得してLF、CR、CRLFを処理しますか?

特に、istream& getline ( istream& is, string& str );に興味があります。 ifstreamコンストラクターに、すべての改行エンコーディングを内部で「\ n」に変換するように指示するオプションはありますか? getlineを呼び出して、すべての行末を適切に処理できるようにしたいと思います。

Update:明確にするために、ほぼどこでもコンパイルし、ほぼどこからでも入力を受け取るコードを記述できるようにします。 「\ n」のない「\ r」を持つまれなファイルを含みます。ソフトウェアのユーザーの不便を最小限に抑える。

この問題を回避するのは簡単ですが、標準では、すべてのテキストファイル形式を柔軟に処理するための正しい方法についてはまだ興味があります。

getlineは、 '\ n'までの完全な行を文字列に読み込みます。 '\ n'はストリームから消費されますが、getlineはそれを文字列に含めません。これで問題ありませんが、文字列に含まれる '\ n'の直前に '\ r'がある場合があります。

種類の行末 テキストファイルで見られます: '\ n'はUnixマシンでは従来の語尾、 '\ r'は古いMacオペレーティングシステムで使用された(と思われる)、Windowsでは使用されますペア、「\ r」の後に「\ n」が続きます。

問題は、getlineが文字列の末尾に '\ r'を残すことです。

_ifstream f("a_text_file_of_unknown_Origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
   // BUT, there might be an '\r' at the end now.
}
_

Editf.good()は私が望んでいたものではないことを指摘してくれたNeilに感謝します。 !f.fail()は私が欲しいものです。

自分で手動で削除できます(この質問の編集を参照)。これは、Windowsテキストファイルにとっては簡単です。しかし、誰かが「\ r」のみを含むファイルをフィードするのではないかと心配しています。その場合、getlineはファイル全体を消費し、1行であると考えます!

..そして、ユニコードも考慮していません:-)

..多分Boostには、テキストファイルタイプから一度に1行を消費する素敵な方法がありますか?

EditWindowsファイルを処理するためにこれを使用していますが、私はまだする必要はないと感じています!そして、これは '\ r'のみのファイルをフォークしません。

_if(!line.empty() && *line.rbegin() == '\r') {
    line.erase( line.length()-1, 1);
}
_
78
Aaron McDaid

Neilが指摘したように、「C++ランタイムは、特定のプラットフォームの行末規則が何であれ、正しく処理する必要があります。」

ただし、異なるプラットフォーム間でテキストファイルを移動するため、十分ではありません。以下は、3つの行末(「\ r」、「\ n」、「\ r\n」)をすべて処理する関数です。

std::istream& safeGetline(std::istream& is, std::string& t)
{
    t.clear();

    // The characters in the stream are read one-by-one using a std::streambuf.
    // That is faster than reading them one-by-one using the std::istream.
    // Code that uses streambuf this way must be guarded by a sentry object.
    // The sentry object performs various tasks,
    // such as thread synchronization and updating the stream state.

    std::istream::sentry se(is, true);
    std::streambuf* sb = is.rdbuf();

    for(;;) {
        int c = sb->sbumpc();
        switch (c) {
        case '\n':
            return is;
        case '\r':
            if(sb->sgetc() == '\n')
                sb->sbumpc();
            return is;
        case std::streambuf::traits_type::eof():
            // Also handle the case when the last line has no line ending
            if(t.empty())
                is.setstate(std::ios::eofbit);
            return is;
        default:
            t += (char)c;
        }
    }
}

次に、テストプログラムを示します。

int main()
{
    std::string path = ...  // insert path to test file here

    std::ifstream ifs(path.c_str());
    if(!ifs) {
        std::cout << "Failed to open the file." << std::endl;
        return EXIT_FAILURE;
    }

    int n = 0;
    std::string t;
    while(!safeGetline(ifs, t).eof())
        ++n;
    std::cout << "The file contains " << n << " lines." << std::endl;
    return EXIT_SUCCESS;
}
106
Johan Råde

C++ランタイムは、特定のプラットフォームのエンドライン規則が何であれ、正しく処理する必要があります。具体的には、このコードはすべてのプラットフォームで動作するはずです。

#include <string>
#include <iostream>
using namespace std;

int main() {
    string line;
    while( getline( cin, line ) ) {
        cout << line << endl;
    }
}

もちろん、別のプラットフォームのファイルを扱っている場合、すべての賭けはオフになっています。

最も一般的な2つのプラットフォーム(LinuxとWindows)は両方とも改行文字で行を終了するため、Windowsの前にキャリッジリターンがあるため、上記のコードのline文字列の最後の文字を調べて確認できますもしそれが \rそして、そうであれば、アプリケーション固有の処理を行う前にそれを削除します。

たとえば、次のようなgetlineスタイル関数を提供できます(テスト目的ではなく、教育目的のみのインデックス、substrなどの使用):

ostream & safegetline( ostream & os, string & line ) {
    string myline;
    if ( getline( os, myline ) ) {
       if ( myline.size() && myline[myline.size()-1] == '\r' ) {
           line = myline.substr( 0, myline.size() - 1 );
       }
       else {
           line = myline;
       }
    }
    return os;
}
10

ファイルを[〜#〜] binary [〜#〜]または[〜#〜] text [〜#〜]モード? [〜#〜] text [〜#〜]モードでは、キャリッジリターン/ラインフィードのペア、[〜#〜] crlf [〜#〜 ][〜#〜] text [〜#〜]行末または行末文字として解釈されますが、[〜#〜] binary [〜#〜]フェッチのみ[〜#〜] one [〜#〜]バイト、一度には、いずれかの文字[〜#〜] [〜#〜]を無視し、バッファーに残して別のバイトとしてフェッチすることを意味します!キャリッジリターンとは、タイプライターで、印刷アームが置かれているタイプライターの車が用紙の右端に達し、左端に戻されることを意味します。これは非常に機械的なモデルであり、機械的なタイプライターのモデルです。次に、ラインフィードとは、ロール紙が少し上に回転することを意味するため、用紙はタイピングの別の行を開始する位置にあります。私が覚えている限りでは、ASCIIの下位桁の1つは、入力せずに右に1文字移動することを意味します。死んだ文字、そしてもちろん\ bはバックスペースを意味します。そのように、拡張キーボードを必要とせずに、前の線に沿って車の位置を調整するだけで、下層(アンダースコア型)、取り消し線(マイナス型)、異なるアクセントの近似、キャンセル(X型)などの特殊効果を追加できます改行を入力します。したがって、バイトサイズのASCII電圧を使用して、間にコンピューターを介さずにタイプライターを自動的に制御できます。自動タイプライターが導入されると、[〜# 〜] automatic [〜#〜]は、用紙の最も遠い端に到達すると、車が左に戻ることを意味します[〜#〜] and [〜#〜] ラインフィードが適用されます。つまり、ロールが上に移動すると車が自動的に返されると想定されます!したがって、両方の制御文字は必要なく、1つ、\ n、改行、または改行だけが必要です。

これはプログラミングとは関係ありませんが、ASCIIは古く、HEY!テキストのことを始めたときに考えていなかった人がいるようです!UNIXプラットフォームは、電気自動タイプマシンを想定しています。より完全で機械式機械の制御が可能になりますが、一部の制御文字はベル文字のようにコンピューターで次第に役に立たなくなります。覚えていると0x07です。そしてそれはモデルを永続させました...

実際には、正しいバリエーションは、\ r、ラインフィード、キャリッジリターンが不要、つまり自動であるだけであるため、次のようになります。

char c;
ifstream is;
is.open("",ios::binary);
...
is.getline(buffer, bufsize, '\r');

//ignore following \n or restore the buffer data
if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c);
...

すべての種類のファイルを処理する最も正しい方法です。ただし、[〜#〜] text [〜#〜]モードの\ nは実際にはバイトペア0x0d 0x0aですが、0x0d[〜# 〜] is [〜#〜]ちょうど\ r:\ nは[〜#〜] text [〜#〜]モードではなく、 [〜#〜] binary [〜#〜]、したがって、\ nと\ r\nは同等です...これは実際には非常に基本的な業界の混乱であり、一般的な業界のinertia性です。慣例では、すべてのプラットフォームでCRLFについて話し、その後異なるバイナリ解釈に分類されます。厳密に言えば、[〜#〜] only [〜#〜]0x0d(キャリッジリターン)を含むファイルは\ n(CRLFまたは改行)であるため、[〜#〜] text [〜#〜]モード(typewritterマシン:車を返してすべてを取り消す...)、および非行指向のバイナリ形式(\ rまたは\ r\nは行指向を意味します)。したがって、テキストとして読むことはできません。コードは、おそらく何らかのユーザーメッセージで失敗するはずです。これはOSのみに依存するのではなく、Cライブラリの実装にも依存し、混乱と可能なバリエーションを追加します(特に、透明なUNICODE変換レイヤーの場合、バリエーションを混乱させるための明確な別のポイントが追加されます)。

前のコードスニペット(メカニカルタイプライター)の問題は、\ r(自動タイプライターテキスト)の後に\ n文字がない場合、非常に効率が悪いことです。次に、[〜#〜] binary [〜#〜]モードも想定します。このモードでは、Cライブラリはテキストの解釈(ロケール)を無視し、バイトを解放します。両方のモード間で実際のテキスト文字に違いはなく、制御文字のみである必要があります。そのため、一般的には[〜#〜] binary [〜#〜]より読みやすい[〜#〜] text [〜#〜]モード。このソリューションは、[〜#〜] binary [〜#〜]モードではCライブラリのバリエーションとは関係なく典型的なWindows OSテキストファイルに効率的であり、他のプラットフォームテキスト形式(Webを含むテキストへの翻訳)。効率を重視する場合は、関数ポインターを使用して、\ r vs\r\nの行コントロールを任意の方法でテストし、最適なgetlineユーザーコードをポインターに選択してから呼び出すことです。それ。

ちなみに、私はいくつかの\ r\r\nテキストファイルも見つけたのを覚えています...これは、一部の印刷されたテキストコンシューマーがまだ必要としているように、2行テキストに変換します。

1つの解決策は、最初に検索してすべての行末を「\ n」に置き換えることです-例えばGitはデフォルトで行います。

1
user2061057

独自のカスタムハンドラを記述するか、外部ライブラリを使用する以外には、運がありません。最も簡単なことは、line[line.length() - 1]が '\ r'でないことを確認することです。 Linuxでは、ほとんどの行が「\ n」で終わるため、これは不要です。これは、ループ内にある場合、かなりの時間を失うことを意味します。 Windowsでは、これも不要です。ただし、「\ r」で終わるクラシックMacファイルはどうですか? '\ n'と '\ r' '\ n'は両方とも '\ n'で終わるため、std :: getlineはLinuxまたはWindows上のファイルに対して機能しません。 '\ r'を確認する必要はありません。明らかに、これらのファイルで機能するこのようなタスクはうまく機能しません。もちろん、その後、多くのEBCDICシステムが存在します。これは、ほとんどの図書館が取り組むことのないものです。

「\ r」の確認は、おそらくあなたの問題の最良の解決策です。バイナリモードで読み取ると、3つの一般的な行末(「\ r」、「\ r\n」、および「\ n」)をすべて確認できます。古いスタイルのMacの行末があまり長くないはずなので、LinuxとWindowsのみに関心がある場合は、「\ n」のみを確認し、末尾の「\ r」文字を削除します。

1
user539810