mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Implement import of databases in CSV (Comma Separated Values) format (i.e. from other password managers)
This commit is contained in:
parent
1e1428c73d
commit
afdf02b4be
@ -31,6 +31,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
|
|||||||
set(keepassx_SOURCES
|
set(keepassx_SOURCES
|
||||||
core/AutoTypeAssociations.cpp
|
core/AutoTypeAssociations.cpp
|
||||||
core/Config.cpp
|
core/Config.cpp
|
||||||
|
core/CsvParser.cpp
|
||||||
core/Database.cpp
|
core/Database.cpp
|
||||||
core/DatabaseIcons.cpp
|
core/DatabaseIcons.cpp
|
||||||
core/Endian.cpp
|
core/Endian.cpp
|
||||||
@ -102,6 +103,9 @@ set(keepassx_SOURCES
|
|||||||
gui/UnlockDatabaseWidget.cpp
|
gui/UnlockDatabaseWidget.cpp
|
||||||
gui/UnlockDatabaseDialog.cpp
|
gui/UnlockDatabaseDialog.cpp
|
||||||
gui/WelcomeWidget.cpp
|
gui/WelcomeWidget.cpp
|
||||||
|
gui/csvImport/CsvImportWidget.cpp
|
||||||
|
gui/csvImport/CsvImportWizard.cpp
|
||||||
|
gui/csvImport/CsvParserModel.cpp
|
||||||
gui/entry/AutoTypeAssociationsModel.cpp
|
gui/entry/AutoTypeAssociationsModel.cpp
|
||||||
gui/entry/EditEntryWidget.cpp
|
gui/entry/EditEntryWidget.cpp
|
||||||
gui/entry/EditEntryWidget_p.h
|
gui/entry/EditEntryWidget_p.h
|
||||||
@ -133,6 +137,7 @@ set(keepassx_FORMS
|
|||||||
gui/AboutDialog.ui
|
gui/AboutDialog.ui
|
||||||
gui/ChangeMasterKeyWidget.ui
|
gui/ChangeMasterKeyWidget.ui
|
||||||
gui/CloneDialog.ui
|
gui/CloneDialog.ui
|
||||||
|
gui/csvImport/CsvImportWidget.ui
|
||||||
gui/DatabaseOpenWidget.ui
|
gui/DatabaseOpenWidget.ui
|
||||||
gui/DatabaseSettingsWidget.ui
|
gui/DatabaseSettingsWidget.ui
|
||||||
gui/CategoryListWidget.ui
|
gui/CategoryListWidget.ui
|
||||||
|
411
src/core/CsvParser.cpp
Normal file
411
src/core/CsvParser.cpp
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QTextCodec>
|
||||||
|
#include <QObject>
|
||||||
|
#include "core/Tools.h"
|
||||||
|
#include "CsvParser.h"
|
||||||
|
|
||||||
|
CsvParser::CsvParser()
|
||||||
|
: m_ch(0)
|
||||||
|
, m_comment('#')
|
||||||
|
, m_currCol(1)
|
||||||
|
, m_currRow(1)
|
||||||
|
, m_isBackslashSyntax(false)
|
||||||
|
, m_isEof(false)
|
||||||
|
, m_isFileLoaded(false)
|
||||||
|
, m_isGood(true)
|
||||||
|
, m_lastPos(-1)
|
||||||
|
, m_maxCols(0)
|
||||||
|
, m_qualifier('"')
|
||||||
|
, m_separator(',')
|
||||||
|
, m_statusMsg("")
|
||||||
|
{
|
||||||
|
m_csv.setBuffer(&m_array);
|
||||||
|
m_ts.setDevice(&m_csv);
|
||||||
|
m_csv.open(QIODevice::ReadOnly);
|
||||||
|
m_ts.setCodec("UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
CsvParser::~CsvParser() {
|
||||||
|
m_csv.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isFileLoaded() {
|
||||||
|
return m_isFileLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::reparse() {
|
||||||
|
reset();
|
||||||
|
return parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool CsvParser::parse(QFile *device) {
|
||||||
|
clear();
|
||||||
|
if (nullptr == device) {
|
||||||
|
m_statusMsg += QObject::tr("NULL device\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!readFile(device)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::readFile(QFile *device) {
|
||||||
|
if (device->isOpen()) {
|
||||||
|
device->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
device->open(QIODevice::ReadOnly);
|
||||||
|
if (!Tools::readAllFromDevice(device, m_array)) {
|
||||||
|
m_statusMsg += QObject::tr("Error reading from device\n");
|
||||||
|
m_isFileLoaded = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device->close();
|
||||||
|
|
||||||
|
m_array.replace("\r\n", "\n");
|
||||||
|
m_array.replace("\r", "\n");
|
||||||
|
if (0 == m_array.size()) {
|
||||||
|
m_statusMsg += QObject::tr("File empty\n");
|
||||||
|
}
|
||||||
|
m_isFileLoaded = true;
|
||||||
|
}
|
||||||
|
return m_isFileLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::reset() {
|
||||||
|
m_ch = 0;
|
||||||
|
m_currCol = 1;
|
||||||
|
m_currRow = 1;
|
||||||
|
m_isEof = false;
|
||||||
|
m_isGood = true;
|
||||||
|
m_lastPos = -1;
|
||||||
|
m_maxCols = 0;
|
||||||
|
m_statusMsg = "";
|
||||||
|
m_ts.seek(0);
|
||||||
|
m_table.clear();
|
||||||
|
//the following are users' concern :)
|
||||||
|
//m_comment = '#';
|
||||||
|
//m_backslashSyntax = false;
|
||||||
|
//m_comment = '#';
|
||||||
|
//m_qualifier = '"';
|
||||||
|
//m_separator = ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::clear() {
|
||||||
|
reset();
|
||||||
|
m_isFileLoaded = false;
|
||||||
|
m_array.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::parseFile() {
|
||||||
|
parseRecord();
|
||||||
|
while (!m_isEof)
|
||||||
|
{
|
||||||
|
if (!skipEndline()) {
|
||||||
|
appendStatusMsg(QObject::tr("malformed string"));
|
||||||
|
}
|
||||||
|
m_currRow++;
|
||||||
|
m_currCol = 1;
|
||||||
|
parseRecord();
|
||||||
|
}
|
||||||
|
fillColumns();
|
||||||
|
return m_isGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseRecord() {
|
||||||
|
csvrow row;
|
||||||
|
if (isComment()) {
|
||||||
|
skipLine();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
do {
|
||||||
|
parseField(row);
|
||||||
|
getChar(m_ch);
|
||||||
|
} while (isSeparator(m_ch) && !m_isEof);
|
||||||
|
|
||||||
|
if (!m_isEof) {
|
||||||
|
ungetChar();
|
||||||
|
}
|
||||||
|
if (isEmptyRow(row)) {
|
||||||
|
row.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_table.push_back(row);
|
||||||
|
if (m_maxCols < row.size()) {
|
||||||
|
m_maxCols = row.size();
|
||||||
|
}
|
||||||
|
m_currCol++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseField(csvrow& row) {
|
||||||
|
QString field;
|
||||||
|
peek(m_ch);
|
||||||
|
if (!isTerminator(m_ch))
|
||||||
|
{
|
||||||
|
if (isQualifier(m_ch)) {
|
||||||
|
parseQuoted(field);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parseSimple(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row.push_back(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseSimple(QString &s) {
|
||||||
|
QChar c;
|
||||||
|
getChar(c);
|
||||||
|
while ((isText(c)) && (!m_isEof))
|
||||||
|
{
|
||||||
|
s.append(c);
|
||||||
|
getChar(c);
|
||||||
|
}
|
||||||
|
if (!m_isEof) {
|
||||||
|
ungetChar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseQuoted(QString &s) {
|
||||||
|
//read and discard initial qualifier (e.g. quote)
|
||||||
|
getChar(m_ch);
|
||||||
|
parseEscaped(s);
|
||||||
|
//getChar(m_ch);
|
||||||
|
if (!isQualifier(m_ch)) {
|
||||||
|
appendStatusMsg(QObject::tr("missing closing quote"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseEscaped(QString &s) {
|
||||||
|
parseEscapedText(s);
|
||||||
|
while (processEscapeMark(s, m_ch)) {
|
||||||
|
parseEscapedText(s);
|
||||||
|
}
|
||||||
|
if (!m_isEof) {
|
||||||
|
ungetChar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::parseEscapedText(QString &s) {
|
||||||
|
getChar(m_ch);
|
||||||
|
while ((!isQualifier(m_ch)) && !m_isEof)
|
||||||
|
{
|
||||||
|
s.append(m_ch);
|
||||||
|
getChar(m_ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::processEscapeMark(QString &s, QChar c) {
|
||||||
|
QChar buf;
|
||||||
|
peek(buf);
|
||||||
|
QChar c2;
|
||||||
|
//escape-character syntax, e.g. \"
|
||||||
|
if (true == m_isBackslashSyntax)
|
||||||
|
{
|
||||||
|
if (c != '\\') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//consume (and append) second qualifier
|
||||||
|
getChar(c2);
|
||||||
|
if (m_isEof){
|
||||||
|
c2='\\';
|
||||||
|
s.append('\\');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s.append(c2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//double quote syntax, e.g. ""
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!isQualifier(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
peek(c2);
|
||||||
|
if (!m_isEof) { //not EOF, can read one char
|
||||||
|
if (isQualifier(c2)) {
|
||||||
|
s.append(c2);
|
||||||
|
getChar(c2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::fillColumns() {
|
||||||
|
//fill the rows with lesser columns with empty fields
|
||||||
|
|
||||||
|
for (int i=0; i<m_table.size(); ++i) {
|
||||||
|
int gap = m_maxCols-m_table.at(i).size();
|
||||||
|
if (gap > 0) {
|
||||||
|
csvrow r = m_table.at(i);
|
||||||
|
for (int j=0; j<gap; ++j) {
|
||||||
|
r.append(QString(""));
|
||||||
|
}
|
||||||
|
m_table.replace(i, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::skipLine() {
|
||||||
|
m_ts.readLine();
|
||||||
|
m_ts.seek(m_ts.pos()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::skipEndline() {
|
||||||
|
getChar(m_ch);
|
||||||
|
return (m_ch == '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CsvParser::getChar(QChar& c) {
|
||||||
|
m_isEof = m_ts.atEnd();
|
||||||
|
if (!m_isEof) {
|
||||||
|
m_lastPos = m_ts.pos();
|
||||||
|
m_ts >> c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::ungetChar() {
|
||||||
|
if (!m_ts.seek(m_lastPos))
|
||||||
|
m_statusMsg += QObject::tr("Internal: unget lower bound exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::peek(QChar& c) {
|
||||||
|
getChar(c);
|
||||||
|
if (!m_isEof) {
|
||||||
|
ungetChar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isQualifier(const QChar c) const {
|
||||||
|
if (true == m_isBackslashSyntax && (c != m_qualifier)) {
|
||||||
|
return (c == '\\');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (c == m_qualifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isComment() {
|
||||||
|
bool result = false;
|
||||||
|
QChar c2;
|
||||||
|
qint64 pos = m_ts.pos();
|
||||||
|
|
||||||
|
do {
|
||||||
|
getChar(c2);
|
||||||
|
} while ((isSpace(c2) || isTab(c2)) && (!m_isEof));
|
||||||
|
|
||||||
|
if (c2 == m_comment) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
m_ts.seek(pos);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isText(QChar c) const {
|
||||||
|
return !( (isCRLF(c)) || (isSeparator(c)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isEmptyRow(csvrow row) const {
|
||||||
|
csvrow::const_iterator it = row.constBegin();
|
||||||
|
for (; it != row.constEnd(); ++it) {
|
||||||
|
if ( ((*it) != "\n") && ((*it) != "") )
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isCRLF(const QChar c) const {
|
||||||
|
return (c == '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isSpace(const QChar c) const {
|
||||||
|
return (c == 0x20);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isTab(const QChar c) const {
|
||||||
|
return (c == '\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isSeparator(const QChar c) const {
|
||||||
|
return (c == m_separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParser::isTerminator(const QChar c) const {
|
||||||
|
return (isSeparator(c) || (c == '\n') || (c == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::setBackslashSyntax(bool set) {
|
||||||
|
m_isBackslashSyntax = set;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::setComment(const QChar c) {
|
||||||
|
m_comment = c.unicode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::setCodec(const QString s) {
|
||||||
|
m_ts.setCodec(QTextCodec::codecForName(s.toLocal8Bit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::setFieldSeparator(const QChar c) {
|
||||||
|
m_separator = c.unicode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParser::setTextQualifier(const QChar c) {
|
||||||
|
m_qualifier = c.unicode();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CsvParser::getFileSize() const {
|
||||||
|
return m_csv.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvtable CsvParser::getCsvTable() const {
|
||||||
|
return m_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CsvParser::getStatus() const {
|
||||||
|
return m_statusMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CsvParser::getCsvCols() const {
|
||||||
|
if ((m_table.size() > 0) && (m_table.at(0).size() > 0))
|
||||||
|
return m_table.at(0).size();
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CsvParser::getCsvRows() const {
|
||||||
|
return m_table.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CsvParser::appendStatusMsg(QString s) {
|
||||||
|
m_statusMsg += s
|
||||||
|
.append(" @" + QString::number(m_currRow))
|
||||||
|
.append(",")
|
||||||
|
.append(QString::number(m_currCol))
|
||||||
|
.append("\n");
|
||||||
|
m_isGood = false;
|
||||||
|
}
|
101
src/core/CsvParser.h
Normal file
101
src/core/CsvParser.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSX_CSVPARSER_H
|
||||||
|
#define KEEPASSX_CSVPARSER_H
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QQueue>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
typedef QStringList csvrow;
|
||||||
|
typedef QList<csvrow> csvtable;
|
||||||
|
|
||||||
|
class CsvParser {
|
||||||
|
|
||||||
|
public:
|
||||||
|
CsvParser();
|
||||||
|
~CsvParser();
|
||||||
|
//read data from device and parse it
|
||||||
|
bool parse(QFile *device);
|
||||||
|
bool isFileLoaded();
|
||||||
|
//reparse the same buffer (device is not opened again)
|
||||||
|
bool reparse();
|
||||||
|
void setCodec(const QString s);
|
||||||
|
void setComment(const QChar c);
|
||||||
|
void setFieldSeparator(const QChar c);
|
||||||
|
void setTextQualifier(const QChar c);
|
||||||
|
void setBackslashSyntax(bool set);
|
||||||
|
int getFileSize() const;
|
||||||
|
int getCsvRows() const;
|
||||||
|
int getCsvCols() const;
|
||||||
|
QString getStatus() const;
|
||||||
|
const csvtable getCsvTable() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
csvtable m_table;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QByteArray m_array;
|
||||||
|
QBuffer m_csv;
|
||||||
|
QChar m_ch;
|
||||||
|
QChar m_comment;
|
||||||
|
unsigned int m_currCol;
|
||||||
|
unsigned int m_currRow;
|
||||||
|
bool m_isBackslashSyntax;
|
||||||
|
bool m_isEof;
|
||||||
|
bool m_isFileLoaded;
|
||||||
|
bool m_isGood;
|
||||||
|
qint64 m_lastPos;
|
||||||
|
int m_maxCols;
|
||||||
|
QChar m_qualifier;
|
||||||
|
QChar m_separator;
|
||||||
|
QString m_statusMsg;
|
||||||
|
QTextStream m_ts;
|
||||||
|
|
||||||
|
void getChar(QChar &c);
|
||||||
|
void ungetChar();
|
||||||
|
void peek(QChar &c);
|
||||||
|
void fillColumns();
|
||||||
|
bool isTerminator(const QChar c) const;
|
||||||
|
bool isSeparator(const QChar c) const;
|
||||||
|
bool isQualifier(const QChar c) const;
|
||||||
|
bool processEscapeMark(QString &s, QChar c);
|
||||||
|
bool isText(QChar c) const;
|
||||||
|
bool isComment();
|
||||||
|
bool isCRLF(const QChar c) const;
|
||||||
|
bool isSpace(const QChar c) const;
|
||||||
|
bool isTab(const QChar c) const;
|
||||||
|
bool isEmptyRow(csvrow row) const;
|
||||||
|
bool parseFile();
|
||||||
|
void parseRecord();
|
||||||
|
void parseField(csvrow& row);
|
||||||
|
void parseSimple(QString& s);
|
||||||
|
void parseQuoted(QString& s);
|
||||||
|
void parseEscaped(QString& s);
|
||||||
|
void parseEscapedText(QString &s);
|
||||||
|
bool readFile(QFile *device);
|
||||||
|
void reset();
|
||||||
|
void clear();
|
||||||
|
bool skipEndline();
|
||||||
|
void skipLine();
|
||||||
|
void appendStatusMsg(QString s);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CSVPARSER_H
|
||||||
|
|
@ -212,6 +212,23 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
|||||||
Q_EMIT messageDismissGlobal();
|
Q_EMIT messageDismissGlobal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseTabWidget::importCsv()
|
||||||
|
{
|
||||||
|
QString fileName = fileDialog()->getOpenFileName(this, tr("Open CSV file"), QString(),
|
||||||
|
tr("CSV file") + " (*.csv);;" + tr("All files (*)"));
|
||||||
|
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database* db = new Database();
|
||||||
|
DatabaseManagerStruct dbStruct;
|
||||||
|
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||||
|
|
||||||
|
insertDatabase(db, dbStruct);
|
||||||
|
dbStruct.dbWidget->switchToImportCsv(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseTabWidget::mergeDatabase()
|
void DatabaseTabWidget::mergeDatabase()
|
||||||
{
|
{
|
||||||
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
|
QString filter = QString("%1 (*.kdbx);;%2 (*)").arg(tr("KeePass 2 Database"), tr("All files"));
|
||||||
|
@ -66,6 +66,7 @@ public:
|
|||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void newDatabase();
|
void newDatabase();
|
||||||
void openDatabase();
|
void openDatabase();
|
||||||
|
void importCsv();
|
||||||
void mergeDatabase();
|
void mergeDatabase();
|
||||||
void importKeePass1Database();
|
void importKeePass1Database();
|
||||||
bool saveDatabase(int index = -1);
|
bool saveDatabase(int index = -1);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -122,6 +122,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
|||||||
m_editGroupWidget->setObjectName("editGroupWidget");
|
m_editGroupWidget->setObjectName("editGroupWidget");
|
||||||
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
|
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
|
||||||
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
|
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
|
||||||
|
m_csvImportWizard = new CsvImportWizard();
|
||||||
|
m_csvImportWizard->setObjectName("csvImportWizard");
|
||||||
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
|
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
|
||||||
headlineLabelFont.setBold(true);
|
headlineLabelFont.setBold(true);
|
||||||
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
|
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
|
||||||
@ -145,6 +147,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
|||||||
addWidget(m_databaseSettingsWidget);
|
addWidget(m_databaseSettingsWidget);
|
||||||
addWidget(m_historyEditEntryWidget);
|
addWidget(m_historyEditEntryWidget);
|
||||||
addWidget(m_databaseOpenWidget);
|
addWidget(m_databaseOpenWidget);
|
||||||
|
addWidget(m_csvImportWizard);
|
||||||
addWidget(m_databaseOpenMergeWidget);
|
addWidget(m_databaseOpenMergeWidget);
|
||||||
addWidget(m_keepass1OpenWidget);
|
addWidget(m_keepass1OpenWidget);
|
||||||
addWidget(m_unlockDatabaseWidget);
|
addWidget(m_unlockDatabaseWidget);
|
||||||
@ -165,6 +168,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
|||||||
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
connect(m_databaseOpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||||
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
|
connect(m_databaseOpenMergeWidget, SIGNAL(editFinished(bool)), SLOT(mergeDatabase(bool)));
|
||||||
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||||
|
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
|
||||||
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||||
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
|
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
|
||||||
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
|
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
|
||||||
@ -624,6 +628,16 @@ void DatabaseWidget::setCurrentWidget(QWidget* widget)
|
|||||||
adjustSize();
|
adjustSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseWidget::csvImportFinished(bool accepted)
|
||||||
|
{
|
||||||
|
if (!accepted) {
|
||||||
|
Q_EMIT closeRequest();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setCurrentWidget(m_mainWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseWidget::switchToView(bool accepted)
|
void DatabaseWidget::switchToView(bool accepted)
|
||||||
{
|
{
|
||||||
if (m_newGroup) {
|
if (m_newGroup) {
|
||||||
@ -844,12 +858,21 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
|
|||||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatabaseWidget::switchToImportCsv(const QString& fileName)
|
||||||
|
{
|
||||||
|
updateFilename(fileName);
|
||||||
|
switchToMasterKeyChange();
|
||||||
|
m_csvImportWizard->load(fileName, m_db);
|
||||||
|
setCurrentWidget(m_csvImportWizard);
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
|
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
|
||||||
{
|
{
|
||||||
m_databaseOpenMergeWidget->load(fileName);
|
m_databaseOpenMergeWidget->load(fileName);
|
||||||
setCurrentWidget(m_databaseOpenMergeWidget);
|
setCurrentWidget(m_databaseOpenMergeWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName, const QString& password,
|
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName, const QString& password,
|
||||||
const QString& keyFile)
|
const QString& keyFile)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "gui/entry/EntryModel.h"
|
#include "gui/entry/EntryModel.h"
|
||||||
#include "gui/MessageWidget.h"
|
#include "gui/MessageWidget.h"
|
||||||
|
#include "gui/csvImport/CsvImportWizard.h"
|
||||||
|
|
||||||
class ChangeMasterKeyWidget;
|
class ChangeMasterKeyWidget;
|
||||||
class DatabaseOpenWidget;
|
class DatabaseOpenWidget;
|
||||||
@ -143,6 +144,8 @@ public Q_SLOTS:
|
|||||||
void switchToDatabaseSettings();
|
void switchToDatabaseSettings();
|
||||||
void switchToOpenDatabase(const QString& fileName);
|
void switchToOpenDatabase(const QString& fileName);
|
||||||
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||||
|
void switchToImportCsv(const QString& fileName);
|
||||||
|
void csvImportFinished(bool accepted);
|
||||||
void switchToOpenMergeDatabase(const QString& fileName);
|
void switchToOpenMergeDatabase(const QString& fileName);
|
||||||
void switchToOpenMergeDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
void switchToOpenMergeDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||||
void switchToImportKeepass1(const QString& fileName);
|
void switchToImportKeepass1(const QString& fileName);
|
||||||
@ -187,6 +190,7 @@ private:
|
|||||||
EditEntryWidget* m_historyEditEntryWidget;
|
EditEntryWidget* m_historyEditEntryWidget;
|
||||||
EditGroupWidget* m_editGroupWidget;
|
EditGroupWidget* m_editGroupWidget;
|
||||||
ChangeMasterKeyWidget* m_changeMasterKeyWidget;
|
ChangeMasterKeyWidget* m_changeMasterKeyWidget;
|
||||||
|
CsvImportWizard* m_csvImportWizard;
|
||||||
DatabaseSettingsWidget* m_databaseSettingsWidget;
|
DatabaseSettingsWidget* m_databaseSettingsWidget;
|
||||||
DatabaseOpenWidget* m_databaseOpenWidget;
|
DatabaseOpenWidget* m_databaseOpenWidget;
|
||||||
DatabaseOpenWidget* m_databaseOpenMergeWidget;
|
DatabaseOpenWidget* m_databaseOpenMergeWidget;
|
||||||
|
@ -250,6 +250,8 @@ MainWindow::MainWindow()
|
|||||||
SLOT(changeMasterKey()));
|
SLOT(changeMasterKey()));
|
||||||
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget,
|
connect(m_ui->actionChangeDatabaseSettings, SIGNAL(triggered()), m_ui->tabWidget,
|
||||||
SLOT(changeDatabaseSettings()));
|
SLOT(changeDatabaseSettings()));
|
||||||
|
connect(m_ui->actionImportCsv, SIGNAL(triggered()), m_ui->tabWidget,
|
||||||
|
SLOT(importCsv()));
|
||||||
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget,
|
connect(m_ui->actionImportKeePass1, SIGNAL(triggered()), m_ui->tabWidget,
|
||||||
SLOT(importKeePass1Database()));
|
SLOT(importKeePass1Database()));
|
||||||
connect(m_ui->actionRepairDatabase, SIGNAL(triggered()), this,
|
connect(m_ui->actionRepairDatabase, SIGNAL(triggered()), this,
|
||||||
@ -487,7 +489,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
m_ui->actionDatabaseNew->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||||
m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
m_ui->actionDatabaseOpen->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||||
m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
m_ui->menuRecentDatabases->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||||
m_ui->actionImportKeePass1->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
m_ui->menuImport->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||||
m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
|
m_ui->actionDatabaseMerge->setEnabled(inDatabaseTabWidget);
|
||||||
m_ui->actionRepairDatabase->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
m_ui->actionRepairDatabase->setEnabled(inDatabaseTabWidgetOrWelcomeWidget);
|
||||||
|
|
||||||
|
@ -176,6 +176,13 @@
|
|||||||
<string>&Recent databases</string>
|
<string>&Recent databases</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuImport">
|
||||||
|
<property name="title">
|
||||||
|
<string>Import</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionImportKeePass1"/>
|
||||||
|
<addaction name="actionImportCsv"/>
|
||||||
|
</widget>
|
||||||
<addaction name="actionDatabaseNew"/>
|
<addaction name="actionDatabaseNew"/>
|
||||||
<addaction name="actionDatabaseOpen"/>
|
<addaction name="actionDatabaseOpen"/>
|
||||||
<addaction name="menuRecentDatabases"/>
|
<addaction name="menuRecentDatabases"/>
|
||||||
@ -187,7 +194,7 @@
|
|||||||
<addaction name="actionChangeDatabaseSettings"/>
|
<addaction name="actionChangeDatabaseSettings"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionDatabaseMerge"/>
|
<addaction name="actionDatabaseMerge"/>
|
||||||
<addaction name="actionImportKeePass1"/>
|
<addaction name="menuImport"/>
|
||||||
<addaction name="actionExportCsv"/>
|
<addaction name="actionExportCsv"/>
|
||||||
<addaction name="actionRepairDatabase"/>
|
<addaction name="actionRepairDatabase"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
@ -394,11 +401,6 @@
|
|||||||
<string>Database settings</string>
|
<string>Database settings</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionImportKeePass1">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Import KeePass 1 database</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionEntryClone">
|
<action name="actionEntryClone">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
@ -506,6 +508,16 @@
|
|||||||
<string>&Export to CSV file</string>
|
<string>&Export to CSV file</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionImportKeePass1">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import KeePass 1 database</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionImportCsv">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import CSV file</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionRepairDatabase">
|
<action name="actionRepairDatabase">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Re&pair database</string>
|
<string>Re&pair database</string>
|
||||||
|
289
src/gui/csvImport/CsvImportWidget.cpp
Normal file
289
src/gui/csvImport/CsvImportWidget.cpp
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CsvImportWidget.h"
|
||||||
|
#include "ui_CsvImportWidget.h"
|
||||||
|
#include "gui/MessageBox.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
//I wanted to make the CSV import GUI future-proof, so if one day you need entries
|
||||||
|
//to have a new field, all you have to do is uncomment a row or two here, and the GUI will follow:
|
||||||
|
//dynamic generation of comboBoxes, labels, placement and so on. Try it for immense fun!
|
||||||
|
const QStringList CsvImportWidget::m_columnheader = QStringList()
|
||||||
|
<< QObject::tr("Group")
|
||||||
|
<< QObject::tr("Title")
|
||||||
|
<< QObject::tr("Username")
|
||||||
|
<< QObject::tr("Password")
|
||||||
|
<< QObject::tr("URL")
|
||||||
|
<< QObject::tr("Notes")
|
||||||
|
// << QObject::tr("Future field1")
|
||||||
|
// << QObject::tr("Future field2")
|
||||||
|
// << QObject::tr("Future field3")
|
||||||
|
;
|
||||||
|
|
||||||
|
CsvImportWidget::CsvImportWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_ui(new Ui::CsvImportWidget())
|
||||||
|
, m_parserModel(new CsvParserModel(this))
|
||||||
|
, m_comboModel(new QStringListModel(this))
|
||||||
|
, m_comboMapper(new QSignalMapper(this))
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
QFont font = m_ui->labelHeadline->font();
|
||||||
|
font.setBold(true);
|
||||||
|
font.setPointSize(font.pointSize() + 2);
|
||||||
|
m_ui->labelHeadline->setFont(font);
|
||||||
|
|
||||||
|
m_ui->comboBoxCodec->addItems(QStringList() <<"UTF-8" <<"Windows-1252" <<"UTF-16" <<"UTF-16LE");
|
||||||
|
m_ui->comboBoxFieldSeparator->addItems(QStringList() <<"," <<";" <<"-" <<":" <<".");
|
||||||
|
m_ui->comboBoxTextQualifier->addItems(QStringList() <<"\"" <<"'" <<":" <<"." <<"|");
|
||||||
|
m_ui->comboBoxComment->addItems(QStringList() <<"#" <<";" <<":" <<"@");
|
||||||
|
|
||||||
|
m_ui->tableViewFields->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
|
m_ui->tableViewFields->setFocusPolicy(Qt::NoFocus);
|
||||||
|
|
||||||
|
for (int i=0; i<m_columnheader.count(); ++i) {
|
||||||
|
QLabel* label = new QLabel(m_columnheader.at(i), this);
|
||||||
|
label->setFixedWidth(label->minimumSizeHint().width());
|
||||||
|
font = label->font();
|
||||||
|
font.setBold(false);
|
||||||
|
label->setFont(font);
|
||||||
|
|
||||||
|
QComboBox* combo = new QComboBox(this);
|
||||||
|
font = combo->font();
|
||||||
|
font.setBold(false);
|
||||||
|
combo->setFont(font);
|
||||||
|
m_combos.append(combo);
|
||||||
|
combo->setModel(m_comboModel);
|
||||||
|
m_comboMapper->setMapping(combo, i);
|
||||||
|
connect(combo, SIGNAL(currentIndexChanged(int)), m_comboMapper, SLOT(map()));
|
||||||
|
|
||||||
|
//layout labels and combo fields in column-first order
|
||||||
|
int combo_rows = 1+(m_columnheader.count()-1)/2;
|
||||||
|
int x=i%combo_rows;
|
||||||
|
int y= 2*(i/combo_rows);
|
||||||
|
m_ui->gridLayout_combos->addWidget(label, x, y);
|
||||||
|
m_ui->gridLayout_combos->addWidget(combo, x, y+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parserModel->setHeaderLabels(m_columnheader);
|
||||||
|
m_ui->tableViewFields->setModel(m_parserModel);
|
||||||
|
|
||||||
|
connect(m_ui->spinBoxSkip, SIGNAL(valueChanged(int)), SLOT(skippedChanged(int)));
|
||||||
|
connect(m_ui->comboBoxCodec, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||||
|
connect(m_ui->comboBoxTextQualifier, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||||
|
connect(m_ui->comboBoxComment, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||||
|
connect(m_ui->comboBoxFieldSeparator, SIGNAL(currentIndexChanged(int)), SLOT(parse()));
|
||||||
|
connect(m_ui->checkBoxBackslash, SIGNAL(toggled(bool)), SLOT(parse()));
|
||||||
|
connect(m_ui->pushButtonWarnings, SIGNAL(clicked()), this, SLOT(showReport()));
|
||||||
|
connect(m_comboMapper, SIGNAL(mapped(int)), this, SLOT(comboChanged(int)));
|
||||||
|
|
||||||
|
connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(writeDatabase()));
|
||||||
|
connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::comboChanged(int comboId) {
|
||||||
|
QComboBox* currentSender = qobject_cast<QComboBox*>(m_comboMapper->mapping(comboId));
|
||||||
|
if (currentSender->currentIndex() != -1) {
|
||||||
|
//here is the line that actually updates the GUI table
|
||||||
|
m_parserModel->mapColumns(currentSender->currentIndex(), comboId);
|
||||||
|
}
|
||||||
|
updateTableview();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::skippedChanged(int rows) {
|
||||||
|
m_parserModel->setSkippedRows(rows);
|
||||||
|
updateTableview();
|
||||||
|
}
|
||||||
|
|
||||||
|
CsvImportWidget::~CsvImportWidget() {}
|
||||||
|
|
||||||
|
void CsvImportWidget::configParser() {
|
||||||
|
m_parserModel->setBackslashSyntax(m_ui->checkBoxBackslash->isChecked());
|
||||||
|
m_parserModel->setComment(m_ui->comboBoxComment->currentText().at(0));
|
||||||
|
m_parserModel->setTextQualifier(m_ui->comboBoxTextQualifier->currentText().at(0));
|
||||||
|
m_parserModel->setCodec(m_ui->comboBoxCodec->currentText());
|
||||||
|
m_parserModel->setFieldSeparator(m_ui->comboBoxFieldSeparator->currentText().at(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::updateTableview() {
|
||||||
|
m_ui->tableViewFields->resizeRowsToContents();
|
||||||
|
m_ui->tableViewFields->resizeColumnsToContents();
|
||||||
|
|
||||||
|
for (int c=0; c<m_ui->tableViewFields->horizontalHeader()->count(); ++c) {
|
||||||
|
m_ui->tableViewFields->horizontalHeader()->setSectionResizeMode(
|
||||||
|
c, QHeaderView::Stretch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::updatePreview() {
|
||||||
|
|
||||||
|
m_ui->labelSizeRowsCols->setText(m_parserModel->getFileInfo());
|
||||||
|
m_ui->spinBoxSkip->setValue(0);
|
||||||
|
m_ui->spinBoxSkip->setMaximum(m_parserModel->rowCount()-1);
|
||||||
|
|
||||||
|
int i;
|
||||||
|
QStringList list(tr("Not present in CSV file"));
|
||||||
|
|
||||||
|
for (i=1; i<m_parserModel->getCsvCols(); i++) {
|
||||||
|
QString s = QString(tr("Column ")) + QString::number(i);
|
||||||
|
list << s;
|
||||||
|
}
|
||||||
|
m_comboModel->setStringList(list);
|
||||||
|
|
||||||
|
i=1;
|
||||||
|
Q_FOREACH (QComboBox* b, m_combos) {
|
||||||
|
if (i < m_parserModel->getCsvCols()) {
|
||||||
|
b->setCurrentIndex(i);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b->setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::load(const QString& filename, Database* const db) {
|
||||||
|
m_db = db;
|
||||||
|
m_parserModel->setFilename(filename);
|
||||||
|
m_ui->labelFilename->setText(filename);
|
||||||
|
Group* group = m_db->rootGroup();
|
||||||
|
group->setUuid(Uuid::random());
|
||||||
|
group->setNotes(tr("Imported from CSV file\nOriginal data: ") + filename);
|
||||||
|
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::parse() {
|
||||||
|
configParser();
|
||||||
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
|
bool good = m_parserModel->parse();
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
updatePreview();
|
||||||
|
m_ui->pushButtonWarnings->setEnabled(!good);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::showReport() {
|
||||||
|
MessageBox::warning(this, tr("Syntax error"), tr("While parsing file...\n")
|
||||||
|
.append(m_parserModel->getStatus()), QMessageBox::Ok, QMessageBox::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::writeDatabase() {
|
||||||
|
|
||||||
|
checkGroupNames();
|
||||||
|
for (int r=0; r<m_parserModel->rowCount(); r++) {
|
||||||
|
//use the validity of second column as a GO/NOGO hint for all others fields
|
||||||
|
if (m_parserModel->data(m_parserModel->index(r, 1)).isValid()) {
|
||||||
|
Entry* entry = new Entry();
|
||||||
|
entry->setUuid(Uuid::random());
|
||||||
|
entry->setGroup(splitGroups(m_parserModel->data(m_parserModel->index(r, 0)).toString()));
|
||||||
|
entry->setTitle( m_parserModel->data(m_parserModel->index(r, 1)).toString());
|
||||||
|
entry->setUsername( m_parserModel->data(m_parserModel->index(r, 2)).toString());
|
||||||
|
entry->setPassword( m_parserModel->data(m_parserModel->index(r, 3)).toString());
|
||||||
|
entry->setUrl( m_parserModel->data(m_parserModel->index(r, 4)).toString());
|
||||||
|
entry->setNotes( m_parserModel->data(m_parserModel->index(r, 5)).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QBuffer buffer;
|
||||||
|
buffer.open(QBuffer::ReadWrite);
|
||||||
|
|
||||||
|
KeePass2Writer writer;
|
||||||
|
writer.writeDatabase(&buffer, m_db);
|
||||||
|
if (writer.hasError()) {
|
||||||
|
MessageBox::warning(this, tr("Error"), tr("CSV import: writer has errors:\n")
|
||||||
|
.append((writer.errorString())), QMessageBox::Ok, QMessageBox::Ok);
|
||||||
|
}
|
||||||
|
Q_EMIT editFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CsvImportWidget::checkGroupNames() {
|
||||||
|
QString groupLabel;
|
||||||
|
QStringList groupList;
|
||||||
|
bool is_root = false
|
||||||
|
, is_empty = false
|
||||||
|
, is_label = false;
|
||||||
|
for (int r=0; r<m_parserModel->rowCount(); r++) {
|
||||||
|
groupLabel = m_parserModel->data(m_parserModel->index(r, 0)).toString();
|
||||||
|
//check if group name is either "root", "" (empty) or some other label
|
||||||
|
groupList = groupLabel.split("/", QString::SkipEmptyParts);
|
||||||
|
if (not groupList.first().compare("Root", Qt::CaseSensitive))
|
||||||
|
is_root = true;
|
||||||
|
else if (not groupLabel.compare(""))
|
||||||
|
is_empty = true;
|
||||||
|
else
|
||||||
|
is_label = true;
|
||||||
|
groupList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((not is_label and (is_empty xor is_root))
|
||||||
|
or (is_label and not is_root)) {
|
||||||
|
m_db->rootGroup()->setName("Root");
|
||||||
|
}
|
||||||
|
else if ((is_empty and is_root)
|
||||||
|
or (is_label and not is_empty and is_root)) {
|
||||||
|
m_db->rootGroup()->setName("CSV IMPORTED");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//SHOULD NEVER GET HERE
|
||||||
|
m_db->rootGroup()->setName("ROOT_FALLBACK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group *CsvImportWidget::splitGroups(QString label) {
|
||||||
|
//extract group names from nested path provided in "label"
|
||||||
|
Group *current = m_db->rootGroup();
|
||||||
|
QStringList groupList = label.split("/", QString::SkipEmptyParts);
|
||||||
|
|
||||||
|
//skip the creation of a subgroup of Root with the same name
|
||||||
|
if (m_db->rootGroup()->name() == "Root" && groupList.first() == "Root") {
|
||||||
|
groupList.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString& groupName : groupList) {
|
||||||
|
Group *children = hasChildren(current, groupName);
|
||||||
|
if (children == nullptr) {
|
||||||
|
Group *brandNew = new Group();
|
||||||
|
brandNew->setParent(current);
|
||||||
|
brandNew->setName(groupName);
|
||||||
|
current = brandNew;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Q_ASSERT(children != nullptr);
|
||||||
|
current = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
Group* CsvImportWidget::hasChildren(Group* current, QString groupName) {
|
||||||
|
//returns the group whose name is "groupName" and is child of "current" group
|
||||||
|
for (Group * group : current->children()) {
|
||||||
|
if (group->name() == groupName) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWidget::reject() {
|
||||||
|
Q_EMIT editFinished(false);
|
||||||
|
}
|
78
src/gui/csvImport/CsvImportWidget.h
Normal file
78
src/gui/csvImport/CsvImportWidget.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSX_CSVIMPORTWIDGET_H
|
||||||
|
#define KEEPASSX_CSVIMPORTWIDGET_H
|
||||||
|
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStringListModel>
|
||||||
|
#include <QSignalMapper>
|
||||||
|
#include <QList>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
|
||||||
|
#include "format/KeePass2Writer.h"
|
||||||
|
#include "gui/csvImport/CsvParserModel.h"
|
||||||
|
#include "keys/PasswordKey.h"
|
||||||
|
#include "core/Metadata.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class CsvImportWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CsvImportWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CsvImportWidget(QWidget *parent = nullptr);
|
||||||
|
virtual ~CsvImportWidget();
|
||||||
|
void load(const QString& filename, Database* const db);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void editFinished(bool accepted);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void parse();
|
||||||
|
void showReport();
|
||||||
|
void comboChanged(int comboId);
|
||||||
|
void skippedChanged(int rows);
|
||||||
|
void writeDatabase();
|
||||||
|
void checkGroupNames();
|
||||||
|
void reject();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(CsvImportWidget)
|
||||||
|
const QScopedPointer<Ui::CsvImportWidget> m_ui;
|
||||||
|
CsvParserModel* const m_parserModel;
|
||||||
|
QStringListModel* const m_comboModel;
|
||||||
|
QSignalMapper* m_comboMapper;
|
||||||
|
QList<QComboBox*> m_combos;
|
||||||
|
Database *m_db;
|
||||||
|
|
||||||
|
KeePass2Writer m_writer;
|
||||||
|
static const QStringList m_columnheader;
|
||||||
|
void configParser();
|
||||||
|
void updatePreview();
|
||||||
|
void updateTableview();
|
||||||
|
Group* splitGroups(QString label);
|
||||||
|
Group* hasChildren(Group* current, QString groupName);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSX_CSVIMPORTWIDGET_H
|
524
src/gui/csvImport/CsvImportWidget.ui
Normal file
524
src/gui/csvImport/CsvImportWidget.ui
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CsvImportWidget</class>
|
||||||
|
<widget class="QWidget" name="CsvImportWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>779</width>
|
||||||
|
<height>691</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QGroupBox" name="Encoding">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>137</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Encoding</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="2" column="2">
|
||||||
|
<spacer name="horizontalSpacer_9">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<spacer name="horizontalSpacer_10">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<spacer name="horizontalSpacer_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelTextQualifier">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Text is qualified by</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="3">
|
||||||
|
<widget class="QPushButton" name="pushButtonWarnings">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Show parser warnings</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<spacer name="horizontalSpacer_7">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxFieldSeparator">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="4">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Preferred</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="4">
|
||||||
|
<spacer name="horizontalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="labelCodec">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Codec</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="4">
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxCodec">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="labelFieldSeparator">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Fields are separated by</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="labelComments">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Comments start with</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxComment">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxTextQualifier">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="editable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>114</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QCheckBox" name="checkBoxBackslash">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Treat '\' as escape character</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelSkipRows">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Skip first</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBoxSkip">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelSkipRows_2">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>rows</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="3">
|
||||||
|
<widget class="QLabel" name="labelWarnings">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
<kerning>true</kerning>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBoxColumnAssociations">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Column layout</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_combos">
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelHeadline">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Import CSV fields</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelFilename">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>filename</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelSizeRowsCols">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>size, rows, columns</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBoxPreview">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>200</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Preview</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTableView" name="tableViewFields">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderVisible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>27</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
72
src/gui/csvImport/CsvImportWizard.cpp
Normal file
72
src/gui/csvImport/CsvImportWizard.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CsvImportWizard.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLabel>
|
||||||
|
#include "gui/MessageBox.h"
|
||||||
|
|
||||||
|
|
||||||
|
CsvImportWizard::CsvImportWizard(QWidget *parent)
|
||||||
|
: DialogyWidget(parent)
|
||||||
|
{
|
||||||
|
m_layout = new QGridLayout(this);
|
||||||
|
m_pages = new QStackedWidget(parent);
|
||||||
|
m_layout->addWidget(m_pages, 0, 0);
|
||||||
|
|
||||||
|
m_pages->addWidget(key = new ChangeMasterKeyWidget(m_pages));
|
||||||
|
m_pages->addWidget(parse = new CsvImportWidget(m_pages));
|
||||||
|
key->headlineLabel()->setText(tr("Import CSV file"));
|
||||||
|
|
||||||
|
connect(key, SIGNAL(editFinished(bool)), this, SLOT(keyFinished(bool)));
|
||||||
|
connect(parse, SIGNAL(editFinished(bool)), this, SLOT(parseFinished(bool)));
|
||||||
|
}
|
||||||
|
|
||||||
|
CsvImportWizard::~CsvImportWizard()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void CsvImportWizard::load(const QString& filename, Database* database)
|
||||||
|
{
|
||||||
|
m_db = database;
|
||||||
|
parse->load(filename, database);
|
||||||
|
key->clearForms();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWizard::keyFinished(bool accepted)
|
||||||
|
{
|
||||||
|
if (!accepted) {
|
||||||
|
Q_EMIT(importFinished(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pages->setCurrentIndex(m_pages->currentIndex()+1);
|
||||||
|
|
||||||
|
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||||
|
bool result = m_db->setKey(key->newMasterKey());
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||||
|
Q_EMIT importFinished(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvImportWizard::parseFinished(bool accepted)
|
||||||
|
{
|
||||||
|
Q_EMIT(importFinished(accepted));
|
||||||
|
}
|
55
src/gui/csvImport/CsvImportWizard.h
Normal file
55
src/gui/csvImport/CsvImportWizard.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSX_CSVIMPORTWIZARD_H
|
||||||
|
#define KEEPASSX_CSVIMPORTWIZARD_H
|
||||||
|
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QGridLayout>
|
||||||
|
|
||||||
|
#include "CsvImportWidget.h"
|
||||||
|
#include "core/Database.h"
|
||||||
|
#include "gui/ChangeMasterKeyWidget.h"
|
||||||
|
#include "gui/DialogyWidget.h"
|
||||||
|
|
||||||
|
class CsvImportWidget;
|
||||||
|
|
||||||
|
class CsvImportWizard : public DialogyWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CsvImportWizard(QWidget *parent = nullptr);
|
||||||
|
virtual ~CsvImportWizard();
|
||||||
|
void load(const QString& filename, Database *database);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void importFinished(bool accepted);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void keyFinished(bool accepted);
|
||||||
|
void parseFinished(bool accepted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Database* m_db;
|
||||||
|
CsvImportWidget* parse;
|
||||||
|
ChangeMasterKeyWidget* key;
|
||||||
|
QStackedWidget *m_pages;
|
||||||
|
QGridLayout *m_layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //KEEPASSX_CSVIMPORTWIZARD_H
|
139
src/gui/csvImport/CsvParserModel.cpp
Normal file
139
src/gui/csvImport/CsvParserModel.cpp
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CsvParserModel.h"
|
||||||
|
|
||||||
|
CsvParserModel::CsvParserModel(QObject *parent)
|
||||||
|
: QAbstractTableModel(parent)
|
||||||
|
, m_skipped(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
CsvParserModel::~CsvParserModel()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void CsvParserModel::setFilename(const QString& filename) {
|
||||||
|
m_filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CsvParserModel::getFileInfo(){
|
||||||
|
QString a(QString::number(getFileSize()).append(tr(" byte, ")));
|
||||||
|
a.append(QString::number(getCsvRows())).append(tr(" rows, "));
|
||||||
|
a.append(QString::number((getCsvCols()-1))).append(tr(" columns"));
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CsvParserModel::parse() {
|
||||||
|
bool r;
|
||||||
|
beginResetModel();
|
||||||
|
m_columnMap.clear();
|
||||||
|
if (CsvParser::isFileLoaded()) {
|
||||||
|
r = CsvParser::reparse();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QFile csv(m_filename);
|
||||||
|
r = CsvParser::parse(&csv);
|
||||||
|
}
|
||||||
|
for (int i=0; i<columnCount(); i++) {
|
||||||
|
m_columnMap.insert(i,0);
|
||||||
|
}
|
||||||
|
addEmptyColumn();
|
||||||
|
endResetModel();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParserModel::addEmptyColumn() {
|
||||||
|
for (int i=0; i<m_table.size(); ++i) {
|
||||||
|
csvrow r = m_table.at(i);
|
||||||
|
r.prepend(QString(""));
|
||||||
|
m_table.replace(i, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParserModel::mapColumns(int csvColumn, int dbColumn) {
|
||||||
|
if ((csvColumn < 0) || (dbColumn < 0) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
if (csvColumn >= getCsvCols()) {
|
||||||
|
m_columnMap[dbColumn] = 0; //map to the empty column
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_columnMap[dbColumn] = csvColumn;
|
||||||
|
}
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParserModel::setSkippedRows(int skipped) {
|
||||||
|
m_skipped = skipped;
|
||||||
|
QModelIndex topLeft = createIndex(skipped,0);
|
||||||
|
QModelIndex bottomRight = createIndex(m_skipped+rowCount(), columnCount());
|
||||||
|
Q_EMIT dataChanged(topLeft, bottomRight);
|
||||||
|
Q_EMIT layoutChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CsvParserModel::setHeaderLabels(QStringList l) {
|
||||||
|
m_columnHeader = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CsvParserModel::rowCount(const QModelIndex &parent) const {
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getCsvRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CsvParserModel::columnCount(const QModelIndex &parent) const {
|
||||||
|
if (parent.isValid()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_columnHeader.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CsvParserModel::data(const QModelIndex &index, int role) const {
|
||||||
|
if ( (index.column() >= m_columnHeader.size())
|
||||||
|
|| (index.row()+m_skipped >= rowCount())
|
||||||
|
|| !index.isValid() )
|
||||||
|
{
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
return m_table.at(index.row()+m_skipped).at(m_columnMap[index.column()]);
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CsvParserModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
if (orientation == Qt::Horizontal) {
|
||||||
|
if ( (section < 0) || (section >= m_columnHeader.size())) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
return m_columnHeader.at(section);
|
||||||
|
}
|
||||||
|
else if (orientation == Qt::Vertical) {
|
||||||
|
if (section+m_skipped >= rowCount()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
return QString::number(section+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
59
src/gui/csvImport/CsvParserModel.h
Normal file
59
src/gui/csvImport/CsvParserModel.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSX_CSVPARSERMODEL_H
|
||||||
|
#define KEEPASSX_CSVPARSERMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractTableModel>
|
||||||
|
#include <QMap>
|
||||||
|
#include "core/Group.h"
|
||||||
|
#include "core/CsvParser.h"
|
||||||
|
|
||||||
|
class CsvParserModel : public QAbstractTableModel, public CsvParser
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CsvParserModel(QObject *parent = nullptr);
|
||||||
|
virtual ~CsvParserModel();
|
||||||
|
void setFilename(const QString& filename);
|
||||||
|
QString getFileInfo();
|
||||||
|
bool parse();
|
||||||
|
|
||||||
|
void setHeaderLabels(QStringList l);
|
||||||
|
void mapColumns(int csvColumn, int dbColumn);
|
||||||
|
|
||||||
|
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||||
|
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||||
|
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
|
||||||
|
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void setSkippedRows(int skipped);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_skipped;
|
||||||
|
QString m_filename;
|
||||||
|
QStringList m_columnHeader;
|
||||||
|
//first column of model must be empty (aka combobox row "Not present in CSV file")
|
||||||
|
void addEmptyColumn();
|
||||||
|
//mapping CSV columns to keepassx columns
|
||||||
|
QMap<int, int> m_columnMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //KEEPASSX_CSVPARSERMODEL_H
|
||||||
|
|
@ -26,6 +26,7 @@
|
|||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "gui/Application.h"
|
#include "gui/Application.h"
|
||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
|
#include "gui/csvImport/CsvImportWizard.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
|
|
||||||
#ifdef QT_STATIC
|
#ifdef QT_STATIC
|
||||||
|
@ -154,6 +154,9 @@ endif()
|
|||||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
|
add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp
|
||||||
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
|
335
tests/TestCsvParser.cpp
Normal file
335
tests/TestCsvParser.cpp
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TestCsvParser.h"
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(TestCsvParser)
|
||||||
|
|
||||||
|
void TestCsvParser::initTestCase()
|
||||||
|
{
|
||||||
|
parser = new CsvParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::cleanupTestCase()
|
||||||
|
{
|
||||||
|
delete parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::init()
|
||||||
|
{
|
||||||
|
file.setFileName("/tmp/keepassXn94do1x.csv");
|
||||||
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate))
|
||||||
|
QFAIL("Cannot open file!");
|
||||||
|
parser->setBackslashSyntax(false);
|
||||||
|
parser->setComment('#');
|
||||||
|
parser->setFieldSeparator(',');
|
||||||
|
parser->setTextQualifier(QChar('"'));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::cleanup()
|
||||||
|
{
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************** TEST CASES ******************/
|
||||||
|
void TestCsvParser::testMissingQuote() {
|
||||||
|
parser->setTextQualifier(':');
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "A,B\n:BM,1";
|
||||||
|
QEXPECT_FAIL("", "Bad format", Continue);
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QWARN(parser->getStatus().toLatin1());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testMalformed() {
|
||||||
|
parser->setTextQualifier(':');
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "A,B,C\n:BM::,1,:2:";
|
||||||
|
QEXPECT_FAIL("", "Bad format", Continue);
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QWARN(parser->getStatus().toLatin1());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testBackslashSyntax() {
|
||||||
|
parser->setBackslashSyntax(true);
|
||||||
|
parser->setTextQualifier(QChar('X'));
|
||||||
|
QTextStream out(&file);
|
||||||
|
//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));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.at(0).at(0) == "one\"\\t\\\"w\no");
|
||||||
|
QVERIFY(t.at(1).at(0) == "13");
|
||||||
|
QVERIFY(t.at(1).at(1) == "2X,");
|
||||||
|
QVERIFY(t.at(1).at(2) == "\"\"3\"X");
|
||||||
|
QVERIFY(t.at(2).at(0) == "3");
|
||||||
|
QVERIFY(t.at(2).at(1) == "\"4\"");
|
||||||
|
QVERIFY(t.at(2).at(2) == "");
|
||||||
|
QVERIFY(t.at(2).at(3) == "");
|
||||||
|
QVERIFY(t.at(3).at(0) == "\\");
|
||||||
|
QVERIFY(t.size() == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testQuoted() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "ro,w,\"end, of \"\"\"\"\"\"row\"\"\"\"\"\n"
|
||||||
|
<< "2\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.at(0).at(0) == "ro");
|
||||||
|
QVERIFY(t.at(0).at(1) == "w");
|
||||||
|
QVERIFY(t.at(0).at(2) == "end, of \"\"\"row\"\"");
|
||||||
|
QVERIFY(t.at(1).at(0) == "2");
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testEmptySimple() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out <<"";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testEmptyQuoted() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out <<"\"\"";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testEmptyNewline() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out <<"\"\n\"";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testEmptyFile()
|
||||||
|
{
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testNewline()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "1,2\n\n\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 1);
|
||||||
|
QVERIFY(t.at(0).at(0) == "1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testCR()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "1,2\r3,4";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
QVERIFY(t.at(0).at(0) == "1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2");
|
||||||
|
QVERIFY(t.at(1).at(0) == "3");
|
||||||
|
QVERIFY(t.at(1).at(1) == "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testLF()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "1,2\n3,4";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
QVERIFY(t.at(0).at(0) == "1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2");
|
||||||
|
QVERIFY(t.at(1).at(0) == "3");
|
||||||
|
QVERIFY(t.at(1).at(1) == "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testCRLF()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "1,2\r\n3,4";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
QVERIFY(t.at(0).at(0) == "1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2");
|
||||||
|
QVERIFY(t.at(1).at(0) == "3");
|
||||||
|
QVERIFY(t.at(1).at(1) == "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testComments()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << " #one\n"
|
||||||
|
<< " \t # two, three \r\n"
|
||||||
|
<< " #, sing\t with\r"
|
||||||
|
<< " #\t me!\n"
|
||||||
|
<< "useful,text #1!";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 1);
|
||||||
|
QVERIFY(t.at(0).at(0) == "useful");
|
||||||
|
QVERIFY(t.at(0).at(1) == "text #1!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testColumns() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "1,2\n"
|
||||||
|
<< ",,,,,,,,,a\n"
|
||||||
|
<< "a,b,c,d\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(parser->getCsvCols() == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testSimple() {
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << ",,2\r,2,3\n"
|
||||||
|
<< "A,,B\"\n"
|
||||||
|
<< " ,,\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 4);
|
||||||
|
QVERIFY(t.at(0).at(0) == "");
|
||||||
|
QVERIFY(t.at(0).at(1) == "");
|
||||||
|
QVERIFY(t.at(0).at(2) == "2");
|
||||||
|
QVERIFY(t.at(1).at(0) == "");
|
||||||
|
QVERIFY(t.at(1).at(1) == "2");
|
||||||
|
QVERIFY(t.at(1).at(2) == "3");
|
||||||
|
QVERIFY(t.at(2).at(0) == "A");
|
||||||
|
QVERIFY(t.at(2).at(1) == "");
|
||||||
|
QVERIFY(t.at(2).at(2) == "B\"");
|
||||||
|
QVERIFY(t.at(3).at(0) == " ");
|
||||||
|
QVERIFY(t.at(3).at(1) == "");
|
||||||
|
QVERIFY(t.at(3).at(2) == "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testSeparator() {
|
||||||
|
parser->setFieldSeparator('\t');
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "\t\t2\r\t2\t3\n"
|
||||||
|
<< "A\t\tB\"\n"
|
||||||
|
<< " \t\t\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 4);
|
||||||
|
QVERIFY(t.at(0).at(0) == "");
|
||||||
|
QVERIFY(t.at(0).at(1) == "");
|
||||||
|
QVERIFY(t.at(0).at(2) == "2");
|
||||||
|
QVERIFY(t.at(1).at(0) == "");
|
||||||
|
QVERIFY(t.at(1).at(1) == "2");
|
||||||
|
QVERIFY(t.at(1).at(2) == "3");
|
||||||
|
QVERIFY(t.at(2).at(0) == "A");
|
||||||
|
QVERIFY(t.at(2).at(1) == "");
|
||||||
|
QVERIFY(t.at(2).at(2) == "B\"");
|
||||||
|
QVERIFY(t.at(3).at(0) == " ");
|
||||||
|
QVERIFY(t.at(3).at(1) == "");
|
||||||
|
QVERIFY(t.at(3).at(2) == "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testMultiline()
|
||||||
|
{
|
||||||
|
parser->setTextQualifier(QChar(':'));
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << ":1\r\n2a::b:,:3\r4:\n"
|
||||||
|
<< "2\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.at(0).at(0) == "1\n2a:b");
|
||||||
|
QVERIFY(t.at(0).at(1) == "3\n4");
|
||||||
|
QVERIFY(t.at(1).at(0) == "2");
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testEmptyReparsing()
|
||||||
|
{
|
||||||
|
parser->parse(nullptr);
|
||||||
|
QVERIFY(parser->reparse());
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testReparsing()
|
||||||
|
{
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << ":te\r\nxt1:,:te\rxt2:,:end of \"this\n string\":\n"
|
||||||
|
<< "2\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
|
||||||
|
QEXPECT_FAIL("", "Wrong qualifier", Continue);
|
||||||
|
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
||||||
|
|
||||||
|
parser->setTextQualifier(QChar(':'));
|
||||||
|
|
||||||
|
QVERIFY(parser->reparse());
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.at(0).at(0) == "te\nxt1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "te\nxt2");
|
||||||
|
QVERIFY(t.at(0).at(2) == "end of \"this\n string\"");
|
||||||
|
QVERIFY(t.at(1).at(0) == "2");
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testQualifier() {
|
||||||
|
parser->setTextQualifier(QChar('X'));
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << "X1X,X2XX,X,\"\"3\"\"\"X\r"
|
||||||
|
<< "3,X\"4\"X,,\n";
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 2);
|
||||||
|
QVERIFY(t.at(0).at(0) == "1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2X,");
|
||||||
|
QVERIFY(t.at(0).at(2) == "\"\"3\"\"\"X");
|
||||||
|
QVERIFY(t.at(1).at(0) == "3");
|
||||||
|
QVERIFY(t.at(1).at(1) == "\"4\"");
|
||||||
|
QVERIFY(t.at(1).at(2) == "");
|
||||||
|
QVERIFY(t.at(1).at(3) == "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCsvParser::testUnicode() {
|
||||||
|
//QString m("Texte en fran\u00e7ais");
|
||||||
|
//CORRECT QString g("\u20AC");
|
||||||
|
//CORRECT QChar g(0x20AC);
|
||||||
|
//ERROR QChar g("\u20AC");
|
||||||
|
parser->setFieldSeparator(QChar('A'));
|
||||||
|
QTextStream out(&file);
|
||||||
|
out << QString("€1A2śA\"3śAż\"Ażac");
|
||||||
|
|
||||||
|
QVERIFY(parser->parse(&file));
|
||||||
|
t = parser->getCsvTable();
|
||||||
|
QVERIFY(t.size() == 1);
|
||||||
|
QVERIFY(t.at(0).at(0) == "€1");
|
||||||
|
QVERIFY(t.at(0).at(1) == "2ś");
|
||||||
|
QVERIFY(t.at(0).at(2) == "3śAż");
|
||||||
|
QVERIFY(t.at(0).at(3) == "żac");
|
||||||
|
}
|
69
tests/TestCsvParser.h
Normal file
69
tests/TestCsvParser.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Enrico Mariotti <enricomariotti@yahoo.it>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 or (at your option)
|
||||||
|
* version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEEPASSX_TESTCSVPARSER_H
|
||||||
|
#define KEEPASSX_TESTCSVPARSER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
#include "core/CsvParser.h"
|
||||||
|
|
||||||
|
class CsvParser;
|
||||||
|
|
||||||
|
class TestCsvParser : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void init();
|
||||||
|
void cleanup();
|
||||||
|
void initTestCase();
|
||||||
|
void cleanupTestCase();
|
||||||
|
|
||||||
|
void testUnicode();
|
||||||
|
void testLF();
|
||||||
|
void testEmptyReparsing();
|
||||||
|
void testSimple();
|
||||||
|
void testEmptyQuoted();
|
||||||
|
void testEmptyNewline();
|
||||||
|
void testSeparator();
|
||||||
|
void testCR();
|
||||||
|
void testCRLF();
|
||||||
|
void testMalformed();
|
||||||
|
void testQualifier();
|
||||||
|
void testNewline();
|
||||||
|
void testEmptySimple();
|
||||||
|
void testMissingQuote();
|
||||||
|
void testComments();
|
||||||
|
void testBackslashSyntax();
|
||||||
|
void testReparsing();
|
||||||
|
void testEmptyFile();
|
||||||
|
void testQuoted();
|
||||||
|
void testMultiline();
|
||||||
|
void testColumns();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QFile file;
|
||||||
|
CsvParser* parser;
|
||||||
|
csvtable t;
|
||||||
|
void dumpRow(csvtable table, int row);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSX_TESTCSVPARSER_H
|
Loading…
Reference in New Issue
Block a user