forked from qt-creator/qt-creator
QmlDesigner: Prompt to consider first row as header for CSV files
* Also, a bug is fixed for reading the quoted texts within CSV rows Fixes: QDS-11667 Fixes: QDS-11834 Change-Id: I74242148e38c8e71edeb45f3543308259358ee1a Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -180,6 +180,17 @@ StudioControls.Dialog {
|
||||
|
||||
Spacer {}
|
||||
|
||||
StudioControls.CheckBox {
|
||||
id: csvFirstRowIsHeader
|
||||
|
||||
visible: root.fileExists && fileName.text.endsWith(".csv")
|
||||
text: qsTr("Consider first row as headers")
|
||||
checked: true
|
||||
actionIndicatorVisible: false
|
||||
}
|
||||
|
||||
Spacer {}
|
||||
|
||||
RowLayout {
|
||||
spacing: StudioTheme.Values.sectionRowSpacing
|
||||
|
||||
@@ -194,7 +205,8 @@ StudioControls.Dialog {
|
||||
onClicked: {
|
||||
let collectionImported = root.backendValue.importFile(
|
||||
collectionName.text,
|
||||
fileName.text)
|
||||
fileName.text,
|
||||
csvFirstRowIsHeader.checked)
|
||||
|
||||
if (collectionImported)
|
||||
root.accept()
|
||||
|
||||
@@ -264,6 +264,29 @@ inline static bool isEmptyJsonValue(const QJsonValue &value)
|
||||
return value.isNull() || value.isUndefined() || (value.isString() && value.toString().isEmpty());
|
||||
}
|
||||
|
||||
QStringList csvReadLine(const QString &line)
|
||||
{
|
||||
constexpr QStringView linePattern = u"(?:,\"|^\")(?<value>\"\"|[\\w\\W]*?)(?=\",|\"$)"
|
||||
u"|(?:,(?!\")|^(?!\"))(?<quote>[^,]*?)(?=$|,)|(\\r\\n|\\n)";
|
||||
|
||||
static const QRegularExpression lineRegex(linePattern.toString());
|
||||
static const int valueIndex = lineRegex.namedCaptureGroups().indexOf("value");
|
||||
static const int quoteIndex = lineRegex.namedCaptureGroups().indexOf("quote");
|
||||
Q_ASSERT(valueIndex > 0 && quoteIndex > 0);
|
||||
|
||||
QStringList result;
|
||||
QRegularExpressionMatchIterator iterator = lineRegex.globalMatch(line, 0);
|
||||
while (iterator.hasNext()) {
|
||||
const QRegularExpressionMatch match = iterator.next();
|
||||
|
||||
if (match.hasCaptured(valueIndex))
|
||||
result.append(match.captured(2));
|
||||
else if (match.hasCaptured(quoteIndex))
|
||||
result.append(match.captured(quoteIndex));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class PropertyOrderFinder : public QmlJS::AST::Visitor
|
||||
{
|
||||
public:
|
||||
@@ -706,31 +729,35 @@ void CollectionDetails::registerDeclarativeType()
|
||||
qmlRegisterUncreatableType<DataTypeWarning>("CollectionDetails", 1, 0, "Warning", "Enum type");
|
||||
}
|
||||
|
||||
CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document)
|
||||
CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document,
|
||||
const bool &firstRowIsHeader)
|
||||
{
|
||||
QStringList headers;
|
||||
QJsonArray importedArray;
|
||||
|
||||
QTextStream stream(document);
|
||||
|
||||
if (!stream.atEnd())
|
||||
headers = stream.readLine().split(',');
|
||||
if (firstRowIsHeader && !stream.atEnd()) {
|
||||
headers = Utils::transform(csvReadLine(stream.readLine()),
|
||||
[](const QString &value) -> QString { return value.trimmed(); });
|
||||
}
|
||||
|
||||
for (QString &header : headers)
|
||||
header = header.trimmed();
|
||||
|
||||
if (!headers.isEmpty()) {
|
||||
while (!stream.atEnd()) {
|
||||
const QStringList recordDataList = stream.readLine().split(',');
|
||||
int column = -1;
|
||||
QJsonObject recordData;
|
||||
for (const QString &cellData : recordDataList) {
|
||||
if (++column == headers.size())
|
||||
break;
|
||||
recordData.insert(headers.at(column), cellData);
|
||||
while (!stream.atEnd()) {
|
||||
const QStringList recordDataList = csvReadLine(stream.readLine());
|
||||
int column = -1;
|
||||
QJsonObject recordData;
|
||||
for (const QString &cellData : recordDataList) {
|
||||
if (++column == headers.size()) {
|
||||
QString proposalName;
|
||||
int proposalId = column;
|
||||
do
|
||||
proposalName = QString("Column %1").arg(++proposalId);
|
||||
while (headers.contains(proposalName));
|
||||
headers.append(proposalName);
|
||||
}
|
||||
importedArray.append(recordData);
|
||||
recordData.insert(headers.at(column), cellData);
|
||||
}
|
||||
importedArray.append(recordData);
|
||||
}
|
||||
|
||||
return fromImportedJson(importedArray, headers);
|
||||
|
||||
@@ -125,7 +125,8 @@ public:
|
||||
|
||||
static void registerDeclarativeType();
|
||||
|
||||
static CollectionDetails fromImportedCsv(const QByteArray &document);
|
||||
static CollectionDetails fromImportedCsv(const QByteArray &document,
|
||||
const bool &firstRowIsHeader = true);
|
||||
static CollectionDetails fromImportedJson(const QByteArray &json,
|
||||
QJsonParseError *error = nullptr);
|
||||
static CollectionDetails fromLocalJson(const QJsonDocument &document,
|
||||
|
||||
@@ -202,7 +202,9 @@ bool CollectionWidget::isValidUrlToImport(const QUrl &url) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CollectionWidget::importFile(const QString &collectionName, const QUrl &url)
|
||||
bool CollectionWidget::importFile(const QString &collectionName,
|
||||
const QUrl &url,
|
||||
const bool &firstRowIsHeader)
|
||||
{
|
||||
using Utils::FilePath;
|
||||
m_view->ensureDataStoreExists();
|
||||
@@ -244,7 +246,7 @@ bool CollectionWidget::importFile(const QString &collectionName, const QUrl &url
|
||||
} else if (fileInfo.suffix() == "csv") {
|
||||
if (!loadUrlContent())
|
||||
return false;
|
||||
loadedCollection = CollectionDetails::fromImportedCsv(fileContent);
|
||||
loadedCollection = CollectionDetails::fromImportedCsv(fileContent, firstRowIsHeader);
|
||||
}
|
||||
|
||||
if (loadedCollection.columns()) {
|
||||
|
||||
@@ -40,7 +40,10 @@ public:
|
||||
Q_INVOKABLE bool isCsvFile(const QUrl &url) const;
|
||||
Q_INVOKABLE bool isValidUrlToImport(const QUrl &url) const;
|
||||
|
||||
Q_INVOKABLE bool importFile(const QString &collectionName, const QUrl &url);
|
||||
Q_INVOKABLE bool importFile(const QString &collectionName,
|
||||
const QUrl &url,
|
||||
const bool &firstRowIsHeader = true);
|
||||
|
||||
Q_INVOKABLE bool addCollectionToDataStore(const QString &collectionName);
|
||||
Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName);
|
||||
Q_INVOKABLE void openCollection(const QString &collectionName);
|
||||
|
||||
Reference in New Issue
Block a user