Как правильно обрабатывать текстовые потоки с помощью \r? Я хотел бы буферизовать строку, используя Qt

Я использую Qt и QProcess для чтения некоторых данных из других инструментов и их печати в своем приложении. Представьте, например, что это «терминал».

Я обрабатываю данные, используя QProcess::canReadLine() и QProcess:readLine(), и это замечательно. Но некоторые инструменты используют \r для вывода индикаторов выполнения на экран, и это портит мой парсер. Поскольку никогда не бывает строк для чтения, мое приложение просто ждет, пока процесс завершится, чтобы напечатать последнюю строку: многие строки склеены вместе с помощью \r вместо \n.

В любом случае, есть ли способ сказать QProcess, чтобы он также использовал \r в качестве разрыва строки? Я думал о реализации своего подкласса QIODevice, но мне также нужно было бы повторно реализовать QProcess, так что это кажется не оптимальным подходом.

Я подумал об использовании среднего буфера и использовал этот буфер, чтобы сигнализировать «hasLine» моей основной программе. Я бы использовал QProcess::readyRead для заполнения буфера, а затем буфер для заполнения моего основного приложения, но я хотел бы просто сказать Qt, что \r также подходит для разрыва строки. Это возможно?


person Spidey    schedule 18.12.2011    source источник
comment
Вы смотрели на QIODevice::Text и QIODevice::setTextModeEnabled()? Это для перевода \r\n в \n, хотя никогда не слышал, чтобы использовался только \r.   -  person Frank Osterfeld    schedule 18.12.2011
comment
Когда используется только \r, побочным эффектом является то, что последняя строка перезаписывается, поэтому многие программы используют это для вывода информации о ходе выполнения.   -  person Spidey    schedule 18.12.2011


Ответы (2)


Я не думаю, что можно напрямую сказать Qt использовать '\ r' в качестве разрыва строки. Я думал, что QTextStream может это сделать, но сейчас, глядя на его исходники, мне кажется, что я ошибался.

Одним из забавных способов сделать это было бы реализовать собственный подкласс QIODevice, который читает из другого QIODevice и просто заменяет все '\r на '\n, делегируя все остальные методы, кроме разновидностей read(), исходному устройству. Тогда, я думаю, readLine() и QTextStream будут прекрасно работать с результирующим потоком. Однако вам придется как-то справиться с возможной последовательностью '\r\n'. Положительным моментом является то, что вам не нужно выполнять какую-либо буферизацию в этом классе.

Что-то в этом роде:

class CRFilter: public QIODevice {
    Q_OBJECT
public:
    CRFilter(QIODevice *device);
protected:
    virtual qint64 readData(char *data, qint64 maxSize);
    virtual qint64 writeData(const char *data, qint64 maxSize);
private:
    QIODevice *device;
};

CRFilter::CRFilter(QIODevice *device):
device(device)
{
    // delegate the readyRead() signal to this object
    connect(device, SIGNAL(readyRead()), SIGNAL(readyRead()));
    // and maybe other signals like bytesWritten() too...
}

qint64 CRFilter::readData(char *data, qint64 maxSize)
{
    qint64 res = device->read(data, maxSize);
    for (qint64 i = 0; i < res; i++) {
        if (data[i] == '\r')
            data[i] = '\n';
    }
    return res;
}

qint64 CRFilter::writeData(const char *data, qint64 maxSize)
{
    return device->write(data, maxSize);
}

Тогда вы просто делаете это:

QProcess process; // use QProcess methods on this
CRFilter reader(&p); // use QIODevice methods on this
reader.open(QIODevice::ReadWrite); // need this to convince read()/write() methods to work

На самом деле я не тестировал его, поэтому, вероятно, требуется некоторая отладка, чтобы все было правильно. Я также думаю, что это немного уродливо, но не могу придумать действительно элегантного решения.

person Sergei Tachenov    schedule 18.12.2011
comment
Поскольку я использую QProcess, если я получаю QIODevice, мне также придется реализовать методы QProcess. Это перебор. Я создал подкласс QProcess и создал новый сигнал readyProcessedRead(). Я хотел бы скрыть исходный readyRead, думая о наследовании QProcess как частного, а не общедоступного, но это скрыло бы другие методы. Думаю лучше, переделаю нужные методы и спрячу QProcess в приват. - person Spidey; 19.12.2011
comment
@Spidey, нет, я не хотел выводить QProcess. Я хотел реализовать прокси QIODevice, который просто читает из QProcess (на который у него есть указатель) и изменяет содержимое потока по мере его чтения. Если вам нужно использовать методы QProcess, вы используете объект QProcess напрямую, а если вам нужно прочитать из него, вы используете свой прокси. Немного некрасиво, однако, так как вам придется иметь дело с двумя объектами вместо одного. Но это не ваша вина, поскольку корень проблемы в том, что ребята из Qt объединили слишком много вещей в один класс: обработку процессов и ввод-вывод. - person Sergei Tachenov; 19.12.2011
comment
Понимаю. Я новичок в этом, не знал такого подхода. Поскольку QProcess уже является производным от QIODevice, я подумал, что мне придется использовать этот CRFilter, а также получить QProcess, чтобы использовать его вместо QIODevice. - person Spidey; 26.12.2011

Поскольку я не использую полиморфизм с этим, нет проблем с публичным наследованием и переопределением некоторых методов и сигналов:

QCLIProcess.h

#ifndef QCLIPROCESS_H
#define QCLIPROCESS_H

#include <QProcess>

class QCLIProcess : public QProcess
{
    Q_OBJECT

public:
    explicit QCLIProcess(QObject *parent = 0);
    bool canReadLine() const;
    QString readLine();

signals:
    void readyRead();

private slots:
    void processLine();

private:
    QByteArray buffer;
    QStringList lines;
};

#endif // QCLIPROCESS_H

QCLIProcess.cpp

#include "QCLIProcess.h"
#include <QtCore>

QCLIProcess::QCLIProcess(QObject *parent) :
    QProcess(parent)
{
    setReadChannelMode(QProcess::MergedChannels);
    connect((QProcess *)this, SIGNAL(readyRead()), this, SLOT(processLine()));
}

void QCLIProcess::processLine(){
    buffer.append(readAll());

    int last = 0;
    for(int i=0; i<buffer.size(); i++){
        if (buffer.at(i) == '\n' || buffer.at(i) == '\r'){
            QString line(buffer.mid(last, i-last));
            line.append('\n');
            if (!line.isEmpty()) lines << line;
            last = i+1;
        }
    }
    buffer.remove(0, last);
    emit readyRead();
}

bool QCLIProcess::canReadLine() const {
    return !lines.isEmpty();
}

QString QCLIProcess::readLine(){
    QString line;

    if (!lines.isEmpty()){
        line = lines.at(0);
        lines.removeFirst();
    }

    return line;
}

ОБНОВЛЕНИЕ: я завершил инкапсуляцию QProcess в новый класс, а не производный от него. Таким образом, я мог контролировать, какие сигналы и какие слоты я хочу открыть.

QLineBufferedCRFilteredProcess.h #ifndef QCLIPROCESS_H #define QCLIPROCESS_H

#include <QProcess>

class QLineBufferedCRFilteredProcess : public QObject
{
    Q_OBJECT

public:
    explicit QLineBufferedCRFilteredProcess(QObject *parent = 0);
    bool canReadLine() const;
    QString readLine();

    void start(const QString &program, const QStringList &arguments);
    void close();

signals:
    void readyRead();
    void finished(int exitCode, QProcess::ExitStatus exitStatus);

private slots:
    void processLine();

private:
    QProcess process;
    QByteArray buffer;
    QStringList lines;
};

#endif // QCLIPROCESS_H

QLineBufferedCRFilteredProcess.cpp #include "QLineBufferedCRFilteredProcess.h" #include

QLineBufferedCRFilteredProcess::QLineBufferedCRFilteredProcess(QObject *parent) :
    QObject(parent)
{
    process.setReadChannelMode(QProcess::MergedChannels);
    connect(&process, SIGNAL(readyRead()), SLOT(processLine()));
    connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), SIGNAL(finished(int,QProcess::ExitStatus)));
}

void QLineBufferedCRFilteredProcess::processLine()
{
    buffer.append(process.readAll());

    int last = 0;
    for(int i=0; i<buffer.size(); i++){
        if (buffer.at(i) == '\n' || buffer.at(i) == '\r'){
            QString line(buffer.mid(last, i-last));
            line.append('\n');
            if (!line.isEmpty()) lines << line;
            last = i+1;
        }
    }
    buffer.remove(0, last);
    emit readyRead();
}

bool QLineBufferedCRFilteredProcess::canReadLine() const
{
    return !lines.isEmpty();
}

QString QLineBufferedCRFilteredProcess::readLine()
{
    QString line;

    if (!lines.isEmpty()){
        line = lines.at(0);
        lines.removeFirst();
    }

    return line;
}

void QLineBufferedCRFilteredProcess::start(const QString &program, const QStringList &arguments)
{
    process.start(program, arguments);
}

void QLineBufferedCRFilteredProcess::close()
{
    process.close();
}
person Spidey    schedule 18.12.2011