mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-09 23:59:36 -04:00
Fix various quirks with CSV import widget and parser
* Fixes #11502 - correct improper handling of text qualifiers * Improve layout of csv import widget * Hide error messages when trying to import again
This commit is contained in:
parent
244ed42231
commit
1b1643b5d1
@ -8682,18 +8682,6 @@ Kernel: %3 %4</source>
|
|||||||
<source>file empty</source>
|
<source>file empty</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>malformed string</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>missing closing quote</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>%1: (row, col) %2,%3</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>AES 256-bit</source>
|
<source>AES 256-bit</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9232,6 +9220,18 @@ This option is deprecated, use --set-key-file instead.</source>
|
|||||||
<source>start minimized to the system tray</source>
|
<source>start minimized to the system tray</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>malformed string, possible unescaped delimiter</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>missing closing delimiter</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>%1, row: %2, column: %3</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>QtIOCompressor</name>
|
<name>QtIOCompressor</name>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||||
*
|
*
|
||||||
@ -53,7 +53,7 @@ bool CsvParser::reparse()
|
|||||||
return parseFile();
|
return parseFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CsvParser::parse(QFile* device)
|
bool CsvParser::parse(QIODevice* device)
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@ -66,7 +66,7 @@ bool CsvParser::parse(QFile* device)
|
|||||||
return parseFile();
|
return parseFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CsvParser::readFile(QFile* device)
|
bool CsvParser::readFile(QIODevice* device)
|
||||||
{
|
{
|
||||||
if (device->isOpen()) {
|
if (device->isOpen()) {
|
||||||
device->close();
|
device->close();
|
||||||
@ -79,6 +79,7 @@ bool CsvParser::readFile(QFile* device)
|
|||||||
} else {
|
} else {
|
||||||
device->close();
|
device->close();
|
||||||
|
|
||||||
|
// Normalize on newline endings
|
||||||
m_array.replace("\r\n", "\n");
|
m_array.replace("\r\n", "\n");
|
||||||
m_array.replace("\r", "\n");
|
m_array.replace("\r", "\n");
|
||||||
if (m_array.isEmpty()) {
|
if (m_array.isEmpty()) {
|
||||||
@ -121,7 +122,7 @@ bool CsvParser::parseFile()
|
|||||||
parseRecord();
|
parseRecord();
|
||||||
while (!m_isEof) {
|
while (!m_isEof) {
|
||||||
if (!skipEndline()) {
|
if (!skipEndline()) {
|
||||||
appendStatusMsg(QObject::tr("malformed string"), true);
|
appendStatusMsg(QObject::tr("malformed string, possible unescaped delimiter"), true);
|
||||||
}
|
}
|
||||||
m_currRow++;
|
m_currRow++;
|
||||||
m_currCol = 1;
|
m_currCol = 1;
|
||||||
@ -161,7 +162,7 @@ void CsvParser::parseField(CsvRow& row)
|
|||||||
{
|
{
|
||||||
QString field;
|
QString field;
|
||||||
peek(m_ch);
|
peek(m_ch);
|
||||||
if (m_ch != m_separator && m_ch != '\n' && m_ch != '\r') {
|
if (m_ch != m_separator && m_ch != '\n') {
|
||||||
if (isQualifier(m_ch)) {
|
if (isQualifier(m_ch)) {
|
||||||
parseQuoted(field);
|
parseQuoted(field);
|
||||||
} else {
|
} else {
|
||||||
@ -190,7 +191,7 @@ void CsvParser::parseQuoted(QString& s)
|
|||||||
getChar(m_ch);
|
getChar(m_ch);
|
||||||
parseEscaped(s);
|
parseEscaped(s);
|
||||||
if (!isQualifier(m_ch)) {
|
if (!isQualifier(m_ch)) {
|
||||||
appendStatusMsg(QObject::tr("missing closing quote"), true);
|
appendStatusMsg(QObject::tr("missing closing delimiter"), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,6 +392,12 @@ int CsvParser::getCsvRows() const
|
|||||||
|
|
||||||
void CsvParser::appendStatusMsg(const QString& s, bool isCritical)
|
void CsvParser::appendStatusMsg(const QString& s, bool isCritical)
|
||||||
{
|
{
|
||||||
m_statusMsg += QObject::tr("%1: (row, col) %2,%3").arg(s, m_currRow, m_currCol).append("\n");
|
if (!m_statusMsg.isEmpty()) {
|
||||||
|
m_statusMsg.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_statusMsg +=
|
||||||
|
QObject::tr("%1, row: %2, column: %3").arg(s, QString::number(m_currRow), QString::number(m_currCol));
|
||||||
|
|
||||||
m_isGood = !isCritical;
|
m_isGood = !isCritical;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
class QFile;
|
class QIODevice;
|
||||||
|
|
||||||
typedef QStringList CsvRow;
|
typedef QStringList CsvRow;
|
||||||
typedef QList<CsvRow> CsvTable;
|
typedef QList<CsvRow> CsvTable;
|
||||||
@ -34,7 +34,7 @@ public:
|
|||||||
CsvParser();
|
CsvParser();
|
||||||
~CsvParser();
|
~CsvParser();
|
||||||
// read data from device and parse it
|
// read data from device and parse it
|
||||||
bool parse(QFile* device);
|
bool parse(QIODevice* device);
|
||||||
bool isFileLoaded();
|
bool isFileLoaded();
|
||||||
// reparse the same buffer (device is not opened again)
|
// reparse the same buffer (device is not opened again)
|
||||||
bool reparse();
|
bool reparse();
|
||||||
@ -85,7 +85,7 @@ private:
|
|||||||
void parseQuoted(QString& s);
|
void parseQuoted(QString& s);
|
||||||
void parseEscaped(QString& s);
|
void parseEscaped(QString& s);
|
||||||
void parseEscapedText(QString& s);
|
void parseEscapedText(QString& s);
|
||||||
bool readFile(QFile* device);
|
bool readFile(QIODevice* device);
|
||||||
void reset();
|
void reset();
|
||||||
void clear();
|
void clear();
|
||||||
bool skipEndline();
|
bool skipEndline();
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "CsvImportWidget.h"
|
#include "CsvImportWidget.h"
|
||||||
|
|
||||||
#include "ui_CsvImportWidget.h"
|
#include "ui_CsvImportWidget.h"
|
||||||
|
|
||||||
#include "core/Clock.h"
|
#include "core/Clock.h"
|
||||||
@ -145,6 +146,13 @@ void CsvImportWidget::updatePreview()
|
|||||||
m_ui->spinBoxSkip->setRange(minSkip, qMax(minSkip, m_parserModel->rowCount() - 1));
|
m_ui->spinBoxSkip->setRange(minSkip, qMax(minSkip, m_parserModel->rowCount() - 1));
|
||||||
m_ui->spinBoxSkip->setValue(minSkip);
|
m_ui->spinBoxSkip->setValue(minSkip);
|
||||||
|
|
||||||
|
// Store the previous column information for comparison later
|
||||||
|
auto prevColumns = m_comboModel->stringList();
|
||||||
|
QList<int> prevComboIndexes;
|
||||||
|
for (auto combo : m_combos) {
|
||||||
|
prevComboIndexes << combo->currentIndex();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList csvColumns(tr("Not Present"));
|
QStringList csvColumns(tr("Not Present"));
|
||||||
auto parser = m_parserModel->parser();
|
auto parser = m_parserModel->parser();
|
||||||
for (int i = 0; i < parser->getCsvCols(); ++i) {
|
for (int i = 0; i < parser->getCsvCols(); ++i) {
|
||||||
@ -159,6 +167,8 @@ void CsvImportWidget::updatePreview()
|
|||||||
csvColumns << QString(tr("Column %1").arg(i));
|
csvColumns << QString(tr("Column %1").arg(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Before setting new columns, see if they changed
|
||||||
|
bool newColumns = prevColumns != csvColumns;
|
||||||
m_comboModel->setStringList(csvColumns);
|
m_comboModel->setStringList(csvColumns);
|
||||||
|
|
||||||
// Try to match named columns to the combo boxes
|
// Try to match named columns to the combo boxes
|
||||||
@ -177,9 +187,10 @@ void CsvImportWidget::updatePreview()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Named column not found, default to "Not Present"
|
// Named column not found, default to "Not Present" or previous index
|
||||||
if (!found) {
|
if (!found) {
|
||||||
m_combos.at(i)->setCurrentIndex(0);
|
auto idx = newColumns ? 0 : prevComboIndexes.at(i);
|
||||||
|
m_combos.at(i)->setCurrentIndex(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,15 +207,19 @@ void CsvImportWidget::load(const QString& filename)
|
|||||||
|
|
||||||
void CsvImportWidget::parse()
|
void CsvImportWidget::parse()
|
||||||
{
|
{
|
||||||
configParser();
|
// Hide any previous messages
|
||||||
|
emit message("");
|
||||||
|
|
||||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
QApplication::processEvents();
|
QApplication::processEvents();
|
||||||
bool good = m_parserModel->parse();
|
|
||||||
updatePreview();
|
configParser();
|
||||||
QApplication::restoreOverrideCursor();
|
if (!m_parserModel->parse()) {
|
||||||
if (!good) {
|
|
||||||
emit message(tr("Failed to parse CSV file: %1").arg(formatStatusText()));
|
emit message(tr("Failed to parse CSV file: %1").arg(formatStatusText()));
|
||||||
}
|
}
|
||||||
|
updatePreview();
|
||||||
|
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Database> CsvImportWidget::buildDatabase()
|
QSharedPointer<Database> CsvImportWidget::buildDatabase()
|
||||||
|
@ -79,7 +79,17 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="4">
|
<item row="0" column="4">
|
||||||
<widget class="QComboBox" name="notesCombo"/>
|
<widget class="QComboBox" name="notesCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="3">
|
<item row="0" column="3">
|
||||||
<widget class="QLabel" name="notesLabel">
|
<widget class="QLabel" name="notesLabel">
|
||||||
@ -120,16 +130,56 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QComboBox" name="urlCombo"/>
|
<widget class="QComboBox" name="urlCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="4">
|
<item row="1" column="4">
|
||||||
<widget class="QComboBox" name="totpCombo"/>
|
<widget class="QComboBox" name="totpCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="4">
|
<item row="4" column="4">
|
||||||
<widget class="QComboBox" name="createdCombo"/>
|
<widget class="QComboBox" name="createdCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="titleCombo"/>
|
<widget class="QComboBox" name="titleCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="5">
|
<item row="5" column="0" colspan="5">
|
||||||
<widget class="QCheckBox" name="checkBoxFieldNames">
|
<widget class="QCheckBox" name="checkBoxFieldNames">
|
||||||
@ -148,10 +198,30 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="4">
|
<item row="2" column="4">
|
||||||
<widget class="QComboBox" name="iconCombo"/>
|
<widget class="QComboBox" name="iconCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QComboBox" name="usernameCombo"/>
|
<widget class="QComboBox" name="usernameCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="passwordLabel">
|
<widget class="QLabel" name="passwordLabel">
|
||||||
@ -192,7 +262,17 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QComboBox" name="passwordCombo"/>
|
<widget class="QComboBox" name="passwordCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="usernameLabel">
|
<widget class="QLabel" name="usernameLabel">
|
||||||
@ -233,7 +313,17 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="4">
|
<item row="3" column="4">
|
||||||
<widget class="QComboBox" name="lastModifiedCombo"/>
|
<widget class="QComboBox" name="lastModifiedCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="3">
|
<item row="4" column="3">
|
||||||
<widget class="QLabel" name="createdLabel">
|
<widget class="QLabel" name="createdLabel">
|
||||||
@ -255,7 +345,17 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="groupCombo"/>
|
<widget class="QComboBox" name="groupCombo">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QComboBox::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="3">
|
<item row="3" column="3">
|
||||||
<widget class="QLabel" name="lastModifiedLabel">
|
<widget class="QLabel" name="lastModifiedLabel">
|
||||||
|
@ -122,7 +122,11 @@ void ImportWizardPageReview::setupCsvImport(const QString& filename)
|
|||||||
|
|
||||||
m_csvWidget = new CsvImportWidget();
|
m_csvWidget = new CsvImportWidget();
|
||||||
connect(m_csvWidget, &CsvImportWidget::message, m_ui->messageWidget, [this](QString message) {
|
connect(m_csvWidget, &CsvImportWidget::message, m_ui->messageWidget, [this](QString message) {
|
||||||
m_ui->messageWidget->showMessage(message, KMessageWidget::Error, -1);
|
if (message.isEmpty()) {
|
||||||
|
m_ui->messageWidget->hideMessage();
|
||||||
|
} else {
|
||||||
|
m_ui->messageWidget->showMessage(message, MessageWidget::Error, -1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
m_csvWidget->load(filename);
|
m_csvWidget->load(filename);
|
||||||
|
@ -22,6 +22,18 @@
|
|||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestCsvParser)
|
QTEST_GUILESS_MAIN(TestCsvParser)
|
||||||
|
|
||||||
|
void TestCsvParser::writeToFile(const QString& contents)
|
||||||
|
{
|
||||||
|
if (!file->open()) {
|
||||||
|
QFAIL("Cannot open temporary file!");
|
||||||
|
}
|
||||||
|
QTextStream out(file.data());
|
||||||
|
out.setCodec("UTF-8");
|
||||||
|
out << contents;
|
||||||
|
out.flush();
|
||||||
|
file->close();
|
||||||
|
}
|
||||||
|
|
||||||
void TestCsvParser::initTestCase()
|
void TestCsvParser::initTestCase()
|
||||||
{
|
{
|
||||||
parser.reset(new CsvParser());
|
parser.reset(new CsvParser());
|
||||||
@ -30,9 +42,7 @@ void TestCsvParser::initTestCase()
|
|||||||
void TestCsvParser::init()
|
void TestCsvParser::init()
|
||||||
{
|
{
|
||||||
file.reset(new QTemporaryFile());
|
file.reset(new QTemporaryFile());
|
||||||
if (not file->open()) {
|
|
||||||
QFAIL("Cannot open file!");
|
|
||||||
}
|
|
||||||
parser->setBackslashSyntax(false);
|
parser->setBackslashSyntax(false);
|
||||||
parser->setComment('#');
|
parser->setComment('#');
|
||||||
parser->setFieldSeparator(',');
|
parser->setFieldSeparator(',');
|
||||||
@ -47,36 +57,34 @@ void TestCsvParser::cleanup()
|
|||||||
/****************** TEST CASES ******************/
|
/****************** TEST CASES ******************/
|
||||||
void TestCsvParser::testMissingQuote()
|
void TestCsvParser::testMissingQuote()
|
||||||
{
|
{
|
||||||
|
writeToFile("A,B\n:BM,1");
|
||||||
parser->setTextQualifier(':');
|
parser->setTextQualifier(':');
|
||||||
QTextStream out(file.data());
|
|
||||||
out << "A,B\n:BM,1";
|
QVERIFY(!parser->parse(file.data()));
|
||||||
QEXPECT_FAIL("", "Bad format", Continue);
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
|
||||||
t = parser->getCsvTable();
|
|
||||||
QWARN(parser->getStatus().toLatin1());
|
QWARN(parser->getStatus().toLatin1());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCsvParser::testMalformed()
|
void TestCsvParser::testMalformed()
|
||||||
{
|
{
|
||||||
|
writeToFile("A,B,C\n:BM::,1,:2:");
|
||||||
parser->setTextQualifier(':');
|
parser->setTextQualifier(':');
|
||||||
QTextStream out(file.data());
|
|
||||||
out << "A,B,C\n:BM::,1,:2:";
|
QVERIFY(!parser->parse(file.data()));
|
||||||
QEXPECT_FAIL("", "Bad format", Continue);
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
|
||||||
t = parser->getCsvTable();
|
|
||||||
QWARN(parser->getStatus().toLatin1());
|
QWARN(parser->getStatus().toLatin1());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCsvParser::testBackslashSyntax()
|
void TestCsvParser::testBackslashSyntax()
|
||||||
{
|
{
|
||||||
|
// attended result: one"\t\"wo
|
||||||
|
writeToFile("Xone\\\"\\\\t\\\\\\\"w\noX\n"
|
||||||
|
"X13X,X2\\X,X,\"\"3\"X\r"
|
||||||
|
"3,X\"4\"X,,\n"
|
||||||
|
"XX\n"
|
||||||
|
"\\");
|
||||||
|
|
||||||
parser->setBackslashSyntax(true);
|
parser->setBackslashSyntax(true);
|
||||||
parser->setTextQualifier(QChar('X'));
|
parser->setTextQualifier(QChar('X'));
|
||||||
QTextStream out(file.data());
|
|
||||||
// attended result: one"\t\"wo
|
|
||||||
out << "Xone\\\"\\\\t\\\\\\\"w\noX\n"
|
|
||||||
<< "X13X,X2\\X,X,\"\"3\"X\r" << "3,X\"4\"X,,\n"
|
|
||||||
<< "XX\n"
|
|
||||||
<< "\\";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no");
|
QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no");
|
||||||
@ -93,9 +101,9 @@ void TestCsvParser::testBackslashSyntax()
|
|||||||
|
|
||||||
void TestCsvParser::testQuoted()
|
void TestCsvParser::testQuoted()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n"
|
||||||
out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n"
|
"2\n");
|
||||||
<< "2\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.at(0).at(0) == "ro");
|
QVERIFY(t.at(0).at(0) == "ro");
|
||||||
@ -107,8 +115,6 @@ void TestCsvParser::testQuoted()
|
|||||||
|
|
||||||
void TestCsvParser::testEmptySimple()
|
void TestCsvParser::testEmptySimple()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
|
||||||
out << "";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.isEmpty());
|
QVERIFY(t.isEmpty());
|
||||||
@ -116,8 +122,8 @@ void TestCsvParser::testEmptySimple()
|
|||||||
|
|
||||||
void TestCsvParser::testEmptyQuoted()
|
void TestCsvParser::testEmptyQuoted()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("\"\"");
|
||||||
out << "\"\"";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.isEmpty());
|
QVERIFY(t.isEmpty());
|
||||||
@ -125,8 +131,8 @@ void TestCsvParser::testEmptyQuoted()
|
|||||||
|
|
||||||
void TestCsvParser::testEmptyNewline()
|
void TestCsvParser::testEmptyNewline()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("\"\n\"");
|
||||||
out << "\"\n\"";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.isEmpty());
|
QVERIFY(t.isEmpty());
|
||||||
@ -141,8 +147,8 @@ void TestCsvParser::testEmptyFile()
|
|||||||
|
|
||||||
void TestCsvParser::testNewline()
|
void TestCsvParser::testNewline()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("1,2\n\n\n");
|
||||||
out << "1,2\n\n\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 1);
|
QVERIFY(t.size() == 1);
|
||||||
@ -152,8 +158,8 @@ void TestCsvParser::testNewline()
|
|||||||
|
|
||||||
void TestCsvParser::testCR()
|
void TestCsvParser::testCR()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("1,2\r3,4");
|
||||||
out << "1,2\r3,4";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 2);
|
QVERIFY(t.size() == 2);
|
||||||
@ -165,8 +171,8 @@ void TestCsvParser::testCR()
|
|||||||
|
|
||||||
void TestCsvParser::testLF()
|
void TestCsvParser::testLF()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("1,2\n3,4");
|
||||||
out << "1,2\n3,4";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 2);
|
QVERIFY(t.size() == 2);
|
||||||
@ -178,8 +184,8 @@ void TestCsvParser::testLF()
|
|||||||
|
|
||||||
void TestCsvParser::testCRLF()
|
void TestCsvParser::testCRLF()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("1,2\r\n3,4");
|
||||||
out << "1,2\r\n3,4";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 2);
|
QVERIFY(t.size() == 2);
|
||||||
@ -191,11 +197,12 @@ void TestCsvParser::testCRLF()
|
|||||||
|
|
||||||
void TestCsvParser::testComments()
|
void TestCsvParser::testComments()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile(" #one\n"
|
||||||
out << " #one\n"
|
" \t # two, three \r\n"
|
||||||
<< " \t # two, three \r\n"
|
" #, sing\t with\r"
|
||||||
<< " #, sing\t with\r" << " #\t me!\n"
|
" #\t me!\n"
|
||||||
<< "useful,text #1!";
|
"useful,text #1!");
|
||||||
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 1);
|
QVERIFY(t.size() == 1);
|
||||||
@ -205,10 +212,10 @@ void TestCsvParser::testComments()
|
|||||||
|
|
||||||
void TestCsvParser::testColumns()
|
void TestCsvParser::testColumns()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile("1,2\n"
|
||||||
out << "1,2\n"
|
",,,,,,,,,a\n"
|
||||||
<< ",,,,,,,,,a\n"
|
"a,b,c,d\n");
|
||||||
<< "a,b,c,d\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(parser->getCsvCols() == 10);
|
QVERIFY(parser->getCsvCols() == 10);
|
||||||
@ -216,10 +223,10 @@ void TestCsvParser::testColumns()
|
|||||||
|
|
||||||
void TestCsvParser::testSimple()
|
void TestCsvParser::testSimple()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile(",,2\r,2,3\n"
|
||||||
out << ",,2\r,2,3\n"
|
"A,,B\"\n"
|
||||||
<< "A,,B\"\n"
|
" ,,\n");
|
||||||
<< " ,,\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 4);
|
QVERIFY(t.size() == 4);
|
||||||
@ -239,11 +246,12 @@ void TestCsvParser::testSimple()
|
|||||||
|
|
||||||
void TestCsvParser::testSeparator()
|
void TestCsvParser::testSeparator()
|
||||||
{
|
{
|
||||||
|
writeToFile("\t\t2\r\t2\t3\n"
|
||||||
|
"A\t\tB\"\n"
|
||||||
|
" \t\t\n");
|
||||||
|
|
||||||
parser->setFieldSeparator('\t');
|
parser->setFieldSeparator('\t');
|
||||||
QTextStream out(file.data());
|
|
||||||
out << "\t\t2\r\t2\t3\n"
|
|
||||||
<< "A\t\tB\"\n"
|
|
||||||
<< " \t\t\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 4);
|
QVERIFY(t.size() == 4);
|
||||||
@ -263,10 +271,11 @@ void TestCsvParser::testSeparator()
|
|||||||
|
|
||||||
void TestCsvParser::testMultiline()
|
void TestCsvParser::testMultiline()
|
||||||
{
|
{
|
||||||
|
writeToFile(":1\r\n2a::b:,:3\r4:\n"
|
||||||
|
"2\n");
|
||||||
|
|
||||||
parser->setTextQualifier(QChar(':'));
|
parser->setTextQualifier(QChar(':'));
|
||||||
QTextStream out(file.data());
|
|
||||||
out << ":1\r\n2a::b:,:3\r4:\n"
|
|
||||||
<< "2\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.at(0).at(0) == "1\n2a:b");
|
QVERIFY(t.at(0).at(0) == "1\n2a:b");
|
||||||
@ -275,41 +284,34 @@ void TestCsvParser::testMultiline()
|
|||||||
QVERIFY(t.size() == 2);
|
QVERIFY(t.size() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCsvParser::testEmptyReparsing()
|
|
||||||
{
|
|
||||||
parser->parse(nullptr);
|
|
||||||
QVERIFY(parser->reparse());
|
|
||||||
t = parser->getCsvTable();
|
|
||||||
QVERIFY(t.isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestCsvParser::testReparsing()
|
void TestCsvParser::testReparsing()
|
||||||
{
|
{
|
||||||
QTextStream out(file.data());
|
writeToFile(":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n"
|
||||||
out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n"
|
"2\n");
|
||||||
<< "2\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
|
|
||||||
QEXPECT_FAIL("", "Wrong qualifier", Continue);
|
QCOMPARE(t.at(0).at(0), QString(":te"));
|
||||||
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
|
||||||
|
|
||||||
parser->setTextQualifier(QChar(':'));
|
parser->setTextQualifier(QChar(':'));
|
||||||
|
|
||||||
QVERIFY(parser->reparse());
|
QVERIFY(parser->reparse());
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
QCOMPARE(t.at(0).at(0), QString("te\nxt1"));
|
||||||
QVERIFY(t.at(0).at(1) == "te\nxt2");
|
QCOMPARE(t.at(0).at(1), QString("te\nxt2"));
|
||||||
QVERIFY(t.at(0).at(2) == "end of \"this\n string\"");
|
QCOMPARE(t.at(0).at(2), QString("end of \"this\n string\""));
|
||||||
QVERIFY(t.at(1).at(0) == "2");
|
QCOMPARE(t.at(1).at(0), QString("2"));
|
||||||
QVERIFY(t.size() == 2);
|
QCOMPARE(t.size(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCsvParser::testQualifier()
|
void TestCsvParser::testQualifier()
|
||||||
{
|
{
|
||||||
|
writeToFile("X1X,X2XX,X,\"\"3\"\"\"X\r"
|
||||||
|
"3,X\"4\"X,,\n");
|
||||||
|
|
||||||
parser->setTextQualifier(QChar('X'));
|
parser->setTextQualifier(QChar('X'));
|
||||||
QTextStream out(file.data());
|
|
||||||
out << "X1X,X2XX,X,\"\"3\"\"\"X\r" << "3,X\"4\"X,,\n";
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
QVERIFY(t.size() == 2);
|
QVERIFY(t.size() == 2);
|
||||||
@ -328,10 +330,9 @@ void TestCsvParser::testUnicode()
|
|||||||
// CORRECT QString g("\u20AC");
|
// CORRECT QString g("\u20AC");
|
||||||
// CORRECT QChar g(0x20AC);
|
// CORRECT QChar g(0x20AC);
|
||||||
// ERROR QChar g("\u20AC");
|
// ERROR QChar g("\u20AC");
|
||||||
|
writeToFile("€1A2śA\"3śAż\"Ażac");
|
||||||
|
|
||||||
parser->setFieldSeparator(QChar('A'));
|
parser->setFieldSeparator(QChar('A'));
|
||||||
QTextStream out(file.data());
|
|
||||||
out.setCodec("UTF-8");
|
|
||||||
out << QString("€1A2śA\"3śAż\"Ażac");
|
|
||||||
|
|
||||||
QVERIFY(parser->parse(file.data()));
|
QVERIFY(parser->parse(file.data()));
|
||||||
t = parser->getCsvTable();
|
t = parser->getCsvTable();
|
||||||
|
@ -37,7 +37,6 @@ private slots:
|
|||||||
|
|
||||||
void testUnicode();
|
void testUnicode();
|
||||||
void testLF();
|
void testLF();
|
||||||
void testEmptyReparsing();
|
|
||||||
void testSimple();
|
void testSimple();
|
||||||
void testEmptyQuoted();
|
void testEmptyQuoted();
|
||||||
void testEmptyNewline();
|
void testEmptyNewline();
|
||||||
@ -58,6 +57,8 @@ private slots:
|
|||||||
void testColumns();
|
void testColumns();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void writeToFile(const QString& contents);
|
||||||
|
|
||||||
QScopedPointer<QTemporaryFile> file;
|
QScopedPointer<QTemporaryFile> file;
|
||||||
QScopedPointer<CsvParser> parser;
|
QScopedPointer<CsvParser> parser;
|
||||||
CsvTable t;
|
CsvTable t;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user