Поведение QTextStream при поиске строки не соответствует ожидаемому

У меня есть несколько строк кода:

QFile file("h:/test.txt");
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);

bool found = false;
uint pos = 0;

do {
QString temp = in.readLine();
int p = temp.indexOf("something");
if (p < 0) {
pos += temp.length() + 1;
} else {
pos += p;
found = true;
}
} while (!found && !in.atEnd());

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

Идея состоит в том, чтобы найти в текстовом файле определенную последовательность символов и, при обнаружении, загрузить файл от начала до появления искомого текста. Входные данные, которые я использовал для тестирования, были:

this is line one, the first line
this is line two, it is second
this is the third line
and this is line 4
line 5 goes here
and finally, there is line number 6

И тут возникает странная часть: если искомая строка находится в одной из строк, кроме последней, я получаю ожидаемое поведение. Работает отлично.

НО если я ищу строку, которая находится в последней строке 6, результат всегда будет на 5 символов короче. Если бы это была 7-я строка, то результатом было бы 6 символов и т. Д., Когда искомая строка находится на прошлой линия, результат всегда lineNumber - 1 символы короче.

Итак, это ошибка или я упускаю что-то очевидное?

РЕДАКТИРОВАТЬ: просто чтобы уточнить, я не прошу альтернативных способов сделать это, я спрашиваю, почему я получаю такое поведение.

7

Решение

Когда вы ищете в последней строке, вы читаете весь поток ввода —
in.atEnd () возвращает true. Похоже, что он каким-то образом повреждает файл или текстовый поток, или отключает их синхронизацию, поэтому поиск больше не действителен.

Если вы замените

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

от

QString text;
if(in.atEnd())
{
file.close();
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in1(&file);
text = in1.read(pos);
}

else
{
in.seek(0);
text = in.read(pos);
}
cout << text.toStdString().c_str() << endl;

Это будет работать как ожидалось.
Постскриптум Может быть, есть какое-то более чистое решение, которое откроет файл заново, но проблема определенно состоит в том, что он достигает конца потока и файла и пытается обработать их после …

4

Другие решения

Очевидно, вы получаете это поведение, потому что readLine () пропускает курсор по размеру строки с символами-разделителями строк (либо LF CRLF, либо CR в зависимости от файла). Буфер, который вы получаете от этого метода, не содержит эти символы, так что вы не берете эти символы в свои расчеты позиции.

Решение состоит в том, чтобы читать не по строкам, а по буферу. Вот ваш код, модифицированный:

QFile file("h:/test.txt");
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);

bool found = false;
uint pos = 0;
qint64 buffSize = 64; // adjust to your needs

do {
QString temp = in.read(buffSize);
int p = temp.indexOf("something");
if (p < 0) {
uint posAdj = buffSize;
if (temp.length() < buffSize)
posAdj = temp.length();
pos += posAdj;
} else {
pos += p;
found = true;
}
} while (!found && !in.atEnd());

in.seek(0);
QString text = in.read(pos);
cout << text.toStdString() << endl;

Приведенный выше код содержит ошибку из-за того, что слово может быть разделено по буферу. Вот пример ввода, который разбивает вещи (при условии, что мы ищем кеки):

test test test test test test
test test test test test test  keks
test test test test test test
test test test test test test
test test test test test test
test test test test test test

Вот полный код что прекрасно работает со всеми входами, которые я пробовал:

#include <QFile>
#include <QTextStream>
#include <iostream>int findPos(const QString& expr, QTextStream& stream) {
if (expr.isEmpty())
return -1;

// buffer size of same length as searched expr should be OK to go
qint64 buffSize = quint64(expr.length());

stream.seek(0);
QString startBuffer = stream.read(buffSize);
int pos = 0;

while(!stream.atEnd()) {
QString cycleBuffer = stream.read(buffSize);
QString searchBuffer = startBuffer + cycleBuffer;
int bufferPos = searchBuffer.indexOf(expr);
if (bufferPos >= 0)
return pos + bufferPos + expr.length();
pos += cycleBuffer.length();
startBuffer = cycleBuffer;
}

return pos;
}

int main(int argc, char *argv[])
{
Q_UNUSED(argc);
Q_UNUSED(argv);

QFile file("test.txt");
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);

int pos = findPos("keks", in);

in.seek(0);
QString text = in.read(pos);
std::cout << text.toUtf8().data() << std::endl;
}
4

Вы знаете разницу между оконными и * nix окончаниями строк (\ r \ n vs \ n). Когда вы открываете файл в текстовом режиме, вы должны знать, что вся последовательность \ r \ n переведена в \ n.

Ваша ошибка в исходном коде в том, что вы пытаетесь вычислить смещение пропущенной строки, но вы не знаете точную длину строки в текстовом файле.

length = number_of_chars + number_of_eol_chars
where number_of_chars == QString::length()
and number_of_eol_chars == (1 if \n) or (2 if \r\n)

Вы не могли обнаружить number_of_eol_chars без необработанного доступа к файлу. И вы не используете его в своем коде, потому что вы открываете файл как текст, а не как бинарный файл. Такая ошибка в вашем коде, что вы жестко закодировали number_of_eol_chars с 1, вместо того, чтобы обнаружить его. Для каждой строки в текстовых файлах Windows (с помощью \ r \ n eol) вы получите ошибку в pos для каждой пропущенной строки.

Фиксированный код:

#include <QFile>
#include <QTextStream>

#include <iostream>
#include <string>int main(int argc, char *argv[])
{
QFile f("test.txt");
const bool isOpened = f.open( QFile::ReadOnly | QFile::Text );
if ( !isOpened )
return 1;
QTextStream in( &f );

const QString searchFor = "finally";

bool found = false;
qint64 pos = 0;

do
{
const qint64 lineStartPos = in.pos();
const QString temp = in.readLine();
const int ofs = temp.indexOf( searchFor );
if ( ofs < 0 )
{
// Here you skip line and increment pos on exact length of line
// You shoud not hardcode "1", because it may be "2" (\n or \r\n)
const qint64 length = in.pos() - lineStartPos;
pos += length;
}
else
{
pos += ofs;
found = true;
}

} while ( !found && !in.atEnd() );

in.seek( 0 );
const QString text = in.read( pos );

std::cout << text.toStdString() << std::endl;

return 0;
}
3

Я не совсем уверен, почему вы видите это поведение, но я подозреваю, что это связано с окончанием строки. Я попробовал твой код и увидел только Последняя линия поведение, когда файл имеет окончания строки CRLF А ТАКЖЕ в конце файла не было новой строки (CRLF). Так что да, странно. Если файл имел LF-окончания, он всегда работал должным образом.

С учетом сказанного, это, вероятно, не очень хорошая идея, чтобы отслеживать позицию, добавив + 1 в конце каждой строки, потому что вы не будете знать, был ли ваш исходный файл CRLF или LF, и QTextStream всегда будет обрезать окончания строки. Вот функция, которая должна работать лучше. Он строит строку вывода построчно, и я не видел странного поведения с ним:

void searchStream( QString fileName, QString searchStr )
{
QFile file( fileName );
if ( file.open(QFile::ReadOnly | QFile::Text) == false )
return;

QString text;
QTextStream in(&file);
QTextStream out(&text);

bool found = false;

do {
QString temp = in.readLine();
int p = temp.indexOf( searchStr );
if (p < 0) {
out << temp << endl;
} else {
found = true;
out << temp.left(p);
}
} while (!found && !in.atEnd());

std::cout << text.toStdString() << std::endl;
}

Он не отслеживает позицию в исходном потоке, поэтому, если вы действительно хотите эту позицию, я бы порекомендовал использовать QTextStream :: pos (), поскольку будет точно знать, является ли файл CRLF или LF.

2

QTextStream.read () Метод принимает в качестве параметра максимальное количество символов для чтения, а не положение файла. Во многих средах положение не является простым подсчетом символов: VMS и Windows приходят на ум как исключения. VMS налагает структуру записи, которая использует много скрытых битов метаданных в файле, а позиции файла являются «волшебными куки»

Единственный независимый от файловой системы способ получить правильное значение — это использовать QTextStream :: поз () когда файл уже расположен в правильном месте, а затем продолжайте чтение, пока позиция файла не вернется в то же место.

(Отредактировано, потому что изначально было неуказанное требование, запрещающее множественные выделения для буферизации текста.)
Однако, учитывая требования программы, нет смысла перечитывать первую часть файла. Начните сохранение текста в начале и остановитесь, когда строка будет найдена:

QString out;
do {
QString temp = in.readLine();
int p = temp.indexOf("something");
if (p < 0) {
out += temp;
} else {
out += temp.substr(pos);  //not sure of the proper function/parameters here
break;
}
} while (!in.atEnd());

cout << out.toStdString() << endl;

Поскольку вы работаете в Windows, обработка текстового файла переводит ‘\ r \ n’ в ‘\ n’, что приводит к несоответствию в позиционировании файла и подсчете символов. Есть несколько способов обойти это, но, возможно, самый простой — это просто обработать файл как двоичный файл (то есть не «текст», удалив текстовый режим) для предотвращения перевода:

file.open(QFile::ReadOnly);

Тогда код должен работать как положено. Это не наносит вреда выводу \ r \ n в Windows, но иногда может вызывать неудобства при использовании текстовых утилит Windows. Если это важно, найдите и замените \ r \ n на \ n, как только текст окажется в памяти.

2
По вопросам рекламы [email protected]