forked from qt-creator/qt-creator
QmlDesigner: Support more json structures in Model Editor
* A visitor is added to detect the property order of the nested json models. * A pure json object is defined as a json which does not contain any array or object as its member. * All of the json lists which has pure models, will be imported. * A pure object which is a child of another object, will be imported. Fixes: QDS-12546 Change-Id: Ib44e1567e3dde0fc5cb433b4f1dc20358e6a3949 Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -856,6 +856,7 @@ extend_qtc_plugin(QmlDesigner
|
|||||||
collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h
|
collectiondetailssortfiltermodel.cpp collectiondetailssortfiltermodel.h
|
||||||
collectioneditorconstants.h
|
collectioneditorconstants.h
|
||||||
collectioneditorutils.cpp collectioneditorutils.h
|
collectioneditorutils.cpp collectioneditorutils.h
|
||||||
|
collectionjsonparser.cpp collectionjsonparser.h
|
||||||
collectionlistmodel.cpp collectionlistmodel.h
|
collectionlistmodel.cpp collectionlistmodel.h
|
||||||
collectionview.cpp collectionview.h
|
collectionview.cpp collectionview.h
|
||||||
collectionwidget.cpp collectionwidget.h
|
collectionwidget.cpp collectionwidget.h
|
||||||
|
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
#include "collectiondatatypemodel.h"
|
#include "collectiondatatypemodel.h"
|
||||||
#include "collectioneditorutils.h"
|
#include "collectioneditorutils.h"
|
||||||
|
#include "collectionjsonparser.h"
|
||||||
|
|
||||||
#include <utils/span.h>
|
|
||||||
#include <qmljs/parser/qmljsast_p.h>
|
|
||||||
#include <qmljs/parser/qmljsastvisitor_p.h>
|
|
||||||
#include <qmljs/qmljsdocument.h>
|
|
||||||
#include <qqml.h>
|
#include <qqml.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -279,47 +278,6 @@ QStringList csvReadLine(const QString &line)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyOrderFinder : public QmlJS::AST::Visitor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static QStringList parse(const QString &jsonContent)
|
|
||||||
{
|
|
||||||
PropertyOrderFinder finder;
|
|
||||||
QmlJS::Document::MutablePtr jsonDoc = QmlJS::Document::create(Utils::FilePath::fromString(
|
|
||||||
"<expression>"),
|
|
||||||
QmlJS::Dialect::Json);
|
|
||||||
|
|
||||||
jsonDoc->setSource(jsonContent);
|
|
||||||
jsonDoc->parseJavaScript();
|
|
||||||
|
|
||||||
if (!jsonDoc->isParsedCorrectly())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
jsonDoc->ast()->accept(&finder);
|
|
||||||
return finder.m_orderedList;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool visit(QmlJS::AST::PatternProperty *patternProperty) override
|
|
||||||
{
|
|
||||||
const QString propertyName = patternProperty->name->asString();
|
|
||||||
if (!m_propertySet.contains(propertyName)) {
|
|
||||||
m_propertySet.insert(propertyName);
|
|
||||||
m_orderedList.append(propertyName);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void throwRecursionDepthError() override
|
|
||||||
{
|
|
||||||
qWarning() << Q_FUNC_INFO << __LINE__ << "Recursion depth error";
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSet<QString> m_propertySet;
|
|
||||||
QStringList m_orderedList;
|
|
||||||
};
|
|
||||||
|
|
||||||
QString CollectionParseError::errorString() const
|
QString CollectionParseError::errorString() const
|
||||||
{
|
{
|
||||||
switch (errorNo) {
|
switch (errorNo) {
|
||||||
@@ -757,63 +715,24 @@ CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document,
|
|||||||
return fromImportedJson(importedArray, headers);
|
return fromImportedJson(importedArray, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionDetails CollectionDetails::fromImportedJson(const QByteArray &json, QJsonParseError *error)
|
QList<CollectionDetails> CollectionDetails::fromImportedJson(const QByteArray &jsonContent,
|
||||||
|
QJsonParseError *error)
|
||||||
{
|
{
|
||||||
QJsonArray importedCollection;
|
|
||||||
auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray {
|
|
||||||
QJsonArray resultArray;
|
|
||||||
for (const QJsonValue &collectionData : array) {
|
|
||||||
if (collectionData.isObject()) {
|
|
||||||
QJsonObject rowObject = collectionData.toObject();
|
|
||||||
const QStringList rowKeys = rowObject.keys();
|
|
||||||
for (const QString &key : rowKeys) {
|
|
||||||
const QJsonValue cellValue = rowObject.value(key);
|
|
||||||
if (cellValue.isArray())
|
|
||||||
rowObject.remove(key);
|
|
||||||
}
|
|
||||||
resultArray.push_back(rowObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
|
|
||||||
|
QList<CollectionObject> collectionObjects = JsonCollectionParser::parseCollectionObjects(jsonContent,
|
||||||
|
error);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
*error = parseError;
|
*error = parseError;
|
||||||
|
|
||||||
if (parseError.error != QJsonParseError::NoError)
|
if (parseError.error != QJsonParseError::NoError)
|
||||||
return CollectionDetails{};
|
return {};
|
||||||
|
return Utils::transform(collectionObjects, [](const CollectionObject &object) {
|
||||||
if (document.isArray()) {
|
CollectionDetails result = fromImportedJson(object.array, object.propertyOrder);
|
||||||
importedCollection = refineJsonArray(document.array());
|
result.d->reference.name = object.name;
|
||||||
} else if (document.isObject()) {
|
return result;
|
||||||
QJsonObject documentObject = document.object();
|
});
|
||||||
const QStringList mainKeys = documentObject.keys();
|
|
||||||
|
|
||||||
bool arrayFound = false;
|
|
||||||
for (const QString &key : mainKeys) {
|
|
||||||
const QJsonValue value = documentObject.value(key);
|
|
||||||
if (value.isArray()) {
|
|
||||||
arrayFound = true;
|
|
||||||
importedCollection = refineJsonArray(value.toArray());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!arrayFound) {
|
|
||||||
QJsonObject singleObject;
|
|
||||||
for (const QString &key : mainKeys) {
|
|
||||||
const QJsonValue value = documentObject.value(key);
|
|
||||||
|
|
||||||
if (!value.isObject())
|
|
||||||
singleObject.insert(key, value);
|
|
||||||
}
|
|
||||||
importedCollection.push_back(singleObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fromImportedJson(importedCollection, PropertyOrderFinder::parse(QLatin1String(json)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionDetails CollectionDetails::fromLocalJson(const QJsonDocument &document,
|
CollectionDetails CollectionDetails::fromLocalJson(const QJsonDocument &document,
|
||||||
|
@@ -127,7 +127,7 @@ public:
|
|||||||
|
|
||||||
static CollectionDetails fromImportedCsv(const QByteArray &document,
|
static CollectionDetails fromImportedCsv(const QByteArray &document,
|
||||||
const bool &firstRowIsHeader = true);
|
const bool &firstRowIsHeader = true);
|
||||||
static CollectionDetails fromImportedJson(const QByteArray &json,
|
static QList<CollectionDetails> fromImportedJson(const QByteArray &jsonContent,
|
||||||
QJsonParseError *error = nullptr);
|
QJsonParseError *error = nullptr);
|
||||||
static CollectionDetails fromLocalJson(const QJsonDocument &document,
|
static CollectionDetails fromLocalJson(const QJsonDocument &document,
|
||||||
const QString &collectionName,
|
const QString &collectionName,
|
||||||
|
@@ -315,18 +315,25 @@ QJsonObject defaultColorCollection()
|
|||||||
|
|
||||||
FileReader fileReader;
|
FileReader fileReader;
|
||||||
if (!fileReader.fetch(templatePath)) {
|
if (!fileReader.fetch(templatePath)) {
|
||||||
qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the file" << templatePath;
|
qWarning() << __FUNCTION__ << "Can't read the content of the file" << templatePath;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
const CollectionDetails collection = CollectionDetails::fromImportedJson(fileReader.data(),
|
const QList<CollectionDetails> collections = CollectionDetails::fromImportedJson(fileReader.data(),
|
||||||
&parseError);
|
&parseError);
|
||||||
|
|
||||||
if (parseError.error != QJsonParseError::NoError) {
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
qWarning() << Q_FUNC_INFO << __LINE__ << "Error in template file" << parseError.errorString();
|
qWarning() << __FUNCTION__ << "Error in template file" << parseError.errorString();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!collections.size()) {
|
||||||
|
qWarning() << __FUNCTION__ << "Can not generate collections from template file!";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionDetails collection = collections.first();
|
||||||
return collection.toLocalJson();
|
return collection.toLocalJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,257 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "collectionjsonparser.h"
|
||||||
|
|
||||||
|
#include <qmljs/parser/qmljsast_p.h>
|
||||||
|
#include <qmljs/qmljsdocument.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonParseError>
|
||||||
|
|
||||||
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A json object is a plain object, if it has only primitive properties (not arrays or objects)
|
||||||
|
* @return true if @param jsonObject is a plain object
|
||||||
|
*/
|
||||||
|
inline static bool isPlainObject(const QJsonObject &jsonObj)
|
||||||
|
{
|
||||||
|
return !Utils::anyOf(jsonObj, [](const QJsonValueConstRef &val) {
|
||||||
|
return val.isArray() || val.isObject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isPlainObject(const QJsonValueConstRef &value)
|
||||||
|
{
|
||||||
|
if (!value.isObject())
|
||||||
|
return false;
|
||||||
|
return isPlainObject(value.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
static QJsonArray parsePlainObject(const QJsonObject &jsonObj)
|
||||||
|
{
|
||||||
|
QJsonObject result;
|
||||||
|
auto item = jsonObj.constBegin();
|
||||||
|
const auto itemEnd = jsonObj.constEnd();
|
||||||
|
while (item != itemEnd) {
|
||||||
|
QJsonValueConstRef ref = item.value();
|
||||||
|
if (!ref.isArray() && !ref.isObject())
|
||||||
|
result.insert(item.key(), ref);
|
||||||
|
++item;
|
||||||
|
}
|
||||||
|
if (!result.isEmpty())
|
||||||
|
return QJsonArray{result};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static QJsonArray parseArray(const QJsonArray &array,
|
||||||
|
QList<CollectionObject> &plainCollections,
|
||||||
|
JsonKeyChain &chainTracker)
|
||||||
|
{
|
||||||
|
chainTracker.append(0);
|
||||||
|
QJsonArray plainArray;
|
||||||
|
int i = -1;
|
||||||
|
for (const QJsonValueConstRef &item : array) {
|
||||||
|
chainTracker.last() = ++i;
|
||||||
|
if (isPlainObject(item)) {
|
||||||
|
const QJsonObject plainObject = item.toObject();
|
||||||
|
if (plainObject.count())
|
||||||
|
plainArray.append(plainObject);
|
||||||
|
} else if (item.isArray()) {
|
||||||
|
parseArray(item.toArray(), plainCollections, chainTracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chainTracker.removeLast();
|
||||||
|
return plainArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseObject(const QJsonObject &jsonObj,
|
||||||
|
QList<CollectionObject> &plainCollections,
|
||||||
|
JsonKeyChain &chainTracker)
|
||||||
|
{
|
||||||
|
chainTracker.append(QString{});
|
||||||
|
auto item = jsonObj.constBegin();
|
||||||
|
const auto itemEnd = jsonObj.constEnd();
|
||||||
|
while (item != itemEnd) {
|
||||||
|
chainTracker.last() = item.key();
|
||||||
|
QJsonValueConstRef ref = item.value();
|
||||||
|
QJsonArray parsedArray;
|
||||||
|
if (ref.isArray()) {
|
||||||
|
parsedArray = parseArray(ref.toArray(), plainCollections, chainTracker);
|
||||||
|
} else if (ref.isObject()) {
|
||||||
|
if (isPlainObject(ref))
|
||||||
|
parsedArray = parsePlainObject(ref.toObject());
|
||||||
|
else
|
||||||
|
parseObject(ref.toObject(), plainCollections, chainTracker);
|
||||||
|
}
|
||||||
|
if (!parsedArray.isEmpty())
|
||||||
|
plainCollections.append({item.key(), parsedArray, chainTracker});
|
||||||
|
++item;
|
||||||
|
}
|
||||||
|
chainTracker.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
static QList<CollectionObject> parseDocument(const QJsonDocument &document,
|
||||||
|
const QString &defaultName = "Model")
|
||||||
|
{
|
||||||
|
QList<CollectionObject> plainCollections;
|
||||||
|
JsonKeyChain chainTracker;
|
||||||
|
if (document.isObject()) {
|
||||||
|
const QJsonObject documentObject = document.object();
|
||||||
|
if (isPlainObject(documentObject)) {
|
||||||
|
QJsonArray parsedArray = parsePlainObject(documentObject);
|
||||||
|
if (!parsedArray.isEmpty())
|
||||||
|
plainCollections.append({defaultName, parsedArray});
|
||||||
|
} else {
|
||||||
|
parseObject(document.object(), plainCollections, chainTracker);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QJsonArray parsedArray = parseArray(document.array(), plainCollections, chainTracker);
|
||||||
|
if (!parsedArray.isEmpty())
|
||||||
|
plainCollections.append({defaultName, parsedArray, {0}});
|
||||||
|
}
|
||||||
|
return plainCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<CollectionObject> JsonCollectionParser::parseCollectionObjects(const QByteArray &json,
|
||||||
|
QJsonParseError *error)
|
||||||
|
{
|
||||||
|
QJsonParseError parseError;
|
||||||
|
QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
|
||||||
|
if (error)
|
||||||
|
*error = parseError;
|
||||||
|
|
||||||
|
if (parseError.error != QJsonParseError::NoError)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
QList<CollectionObject> allCollections = parseDocument(document);
|
||||||
|
QList<JsonKeyChain> keyChains = Utils::transform(allCollections, [](const CollectionObject &obj) {
|
||||||
|
return obj.keyChain;
|
||||||
|
});
|
||||||
|
|
||||||
|
JsonCollectionParser jsonVisitor(QString::fromLatin1(json), keyChains);
|
||||||
|
|
||||||
|
for (CollectionObject &collection : allCollections)
|
||||||
|
collection.propertyOrder = jsonVisitor.collectionPaths.value(collection.keyChain);
|
||||||
|
|
||||||
|
return allCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonCollectionParser::JsonCollectionParser(const QString &jsonContent,
|
||||||
|
const QList<JsonKeyChain> &keyChains)
|
||||||
|
{
|
||||||
|
for (const JsonKeyChain &chain : keyChains)
|
||||||
|
collectionPaths.insert(chain, {});
|
||||||
|
|
||||||
|
QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create(Utils::FilePath::fromString(
|
||||||
|
"<expression>"),
|
||||||
|
QmlJS::Dialect::Json);
|
||||||
|
|
||||||
|
newDoc->setSource(jsonContent);
|
||||||
|
newDoc->parseExpression();
|
||||||
|
|
||||||
|
if (!newDoc->isParsedCorrectly())
|
||||||
|
return;
|
||||||
|
|
||||||
|
newDoc->ast()->accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonCollectionParser::visit([[maybe_unused]] QmlJS::AST::ObjectPattern *objectPattern)
|
||||||
|
{
|
||||||
|
propertyOrderStack.push({});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonCollectionParser::endVisit([[maybe_unused]] QmlJS::AST::ObjectPattern *objectPattern)
|
||||||
|
|
||||||
|
{
|
||||||
|
if (!propertyOrderStack.isEmpty()) {
|
||||||
|
QStringList objectProperties = propertyOrderStack.top();
|
||||||
|
propertyOrderStack.pop();
|
||||||
|
checkPropertyUpdates(keyStack, objectProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonCollectionParser::visit(QmlJS::AST::PatternProperty *patternProperty)
|
||||||
|
{
|
||||||
|
const QString propertyName = patternProperty->name->asString();
|
||||||
|
if (!propertyOrderStack.isEmpty())
|
||||||
|
propertyOrderStack.top().append(propertyName);
|
||||||
|
|
||||||
|
keyStack.push(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonCollectionParser::endVisit(QmlJS::AST::PatternProperty *patternProperty)
|
||||||
|
{
|
||||||
|
const QString propertyName = patternProperty->name->asString();
|
||||||
|
|
||||||
|
if (auto curIndex = std::get_if<QString>(&keyStack.top())) {
|
||||||
|
if (*curIndex == propertyName)
|
||||||
|
keyStack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonCollectionParser::visit([[maybe_unused]] QmlJS::AST::PatternElementList *patternElementList)
|
||||||
|
{
|
||||||
|
keyStack.push(-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonCollectionParser::endVisit([[maybe_unused]] QmlJS::AST::PatternElementList *patternElementList)
|
||||||
|
{
|
||||||
|
if (auto curIndex = std::get_if<int>(&keyStack.top()))
|
||||||
|
keyStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JsonCollectionParser::visit([[maybe_unused]] QmlJS::AST::PatternElement *patternElement)
|
||||||
|
{
|
||||||
|
if (auto curIndex = std::get_if<int>(&keyStack.top()))
|
||||||
|
*curIndex += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonCollectionParser::checkPropertyUpdates(QStack<JsonKey> stack,
|
||||||
|
const QStringList &objectProperties)
|
||||||
|
{
|
||||||
|
bool shouldUpdate = collectionPaths.contains(stack);
|
||||||
|
if (!shouldUpdate && !stack.isEmpty()) {
|
||||||
|
if (auto lastIndex = std::get_if<int>(&stack.top())) {
|
||||||
|
stack.pop();
|
||||||
|
shouldUpdate = collectionPaths.contains(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!shouldUpdate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QStringList propertyList = collectionPaths.value(stack);
|
||||||
|
QSet<QString> allKeys;
|
||||||
|
for (const QString &val : std::as_const(propertyList))
|
||||||
|
allKeys.insert(val);
|
||||||
|
|
||||||
|
std::optional<QString> prevVal;
|
||||||
|
for (const QString &val : objectProperties) {
|
||||||
|
if (!allKeys.contains(val)) {
|
||||||
|
if (prevVal.has_value()) {
|
||||||
|
const int idx = propertyList.indexOf(prevVal);
|
||||||
|
propertyList.insert(idx + 1, val);
|
||||||
|
} else {
|
||||||
|
propertyList.append(val);
|
||||||
|
}
|
||||||
|
allKeys.insert(val);
|
||||||
|
}
|
||||||
|
prevVal = val;
|
||||||
|
}
|
||||||
|
collectionPaths.insert(stack, propertyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JsonCollectionParser::throwRecursionDepthError()
|
||||||
|
{
|
||||||
|
qWarning() << __FUNCTION__ << "Recursion Depth Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace QmlDesigner
|
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qmljs/parser/qmljsastvisitor_p.h>
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QStack>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
struct QJsonParseError;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
using JsonKey = std::variant<int, QString>; // Key can be either int (index) or string (property name)
|
||||||
|
|
||||||
|
using JsonKeyChain = QList<JsonKey>; // A chain of keys leading to a specific json value
|
||||||
|
|
||||||
|
namespace QmlDesigner {
|
||||||
|
|
||||||
|
struct CollectionObject
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QJsonArray array = {};
|
||||||
|
JsonKeyChain keyChain = {};
|
||||||
|
QStringList propertyOrder = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonCollectionParser : public QmlJS::AST::Visitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static QList<CollectionObject> parseCollectionObjects(const QByteArray &json,
|
||||||
|
QJsonParseError *error = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonCollectionParser(const QString &jsonContent, const QList<JsonKeyChain> &keyChains);
|
||||||
|
|
||||||
|
bool visit(QmlJS::AST::ObjectPattern *objectPattern) override;
|
||||||
|
void endVisit(QmlJS::AST::ObjectPattern *objectPattern) override;
|
||||||
|
|
||||||
|
bool visit(QmlJS::AST::PatternProperty *patternProperty) override;
|
||||||
|
void endVisit(QmlJS::AST::PatternProperty *patternProperty) override;
|
||||||
|
|
||||||
|
bool visit(QmlJS::AST::PatternElementList *patternElementList) override;
|
||||||
|
void endVisit(QmlJS::AST::PatternElementList *patternElementList) override;
|
||||||
|
|
||||||
|
bool visit(QmlJS::AST::PatternElement *patternElement) override;
|
||||||
|
|
||||||
|
void checkPropertyUpdates(QStack<JsonKey> stack, const QStringList &objectProperties);
|
||||||
|
|
||||||
|
void throwRecursionDepthError() override;
|
||||||
|
|
||||||
|
QStack<JsonKey> keyStack;
|
||||||
|
QStack<QStringList> propertyOrderStack;
|
||||||
|
QMap<JsonKeyChain, QStringList> collectionPaths; // Key chains, Priorities
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace QmlDesigner
|
@@ -212,7 +212,6 @@ bool CollectionWidget::importFile(const QString &collectionName,
|
|||||||
|
|
||||||
FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
|
FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile()
|
||||||
: url.toString());
|
: url.toString());
|
||||||
CollectionDetails loadedCollection;
|
|
||||||
QByteArray fileContent;
|
QByteArray fileContent;
|
||||||
|
|
||||||
auto loadUrlContent = [&]() -> bool {
|
auto loadUrlContent = [&]() -> bool {
|
||||||
@@ -231,17 +230,31 @@ bool CollectionWidget::importFile(const QString &collectionName,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
QJsonParseError parseError;
|
QJsonParseError parseError;
|
||||||
loadedCollection = CollectionDetails::fromImportedJson(fileContent, &parseError);
|
const QList<CollectionDetails> loadedCollections = CollectionDetails::fromImportedJson(
|
||||||
|
fileContent, &parseError);
|
||||||
if (parseError.error != QJsonParseError::NoError) {
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
warn(tr("Json file Import error"),
|
warn(tr("Json file Import error"),
|
||||||
tr("Cannot parse json content\n%1").arg(parseError.errorString()));
|
tr("Cannot parse json content\n%1").arg(parseError.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (loadedCollections.size() > 1) {
|
||||||
|
for (const CollectionDetails &loadedCollection : loadedCollections) {
|
||||||
|
m_view->addNewCollection(loadedCollection.reference().name,
|
||||||
|
loadedCollection.toLocalJson());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (loadedCollections.size() == 1) {
|
||||||
|
m_view->addNewCollection(collectionName, loadedCollections.first().toLocalJson());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
warn(tr("Can not add a model to the JSON file"),
|
||||||
|
tr("The imported model is empty or is not supported."));
|
||||||
}
|
}
|
||||||
} else if (fileInfo.suffix() == "csv") {
|
} else if (fileInfo.suffix() == "csv") {
|
||||||
|
CollectionDetails loadedCollection;
|
||||||
if (!loadUrlContent())
|
if (!loadUrlContent())
|
||||||
return false;
|
return false;
|
||||||
loadedCollection = CollectionDetails::fromImportedCsv(fileContent, firstRowIsHeader);
|
loadedCollection = CollectionDetails::fromImportedCsv(fileContent, firstRowIsHeader);
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedCollection.columns()) {
|
if (loadedCollection.columns()) {
|
||||||
m_view->addNewCollection(collectionName, loadedCollection.toLocalJson());
|
m_view->addNewCollection(collectionName, loadedCollection.toLocalJson());
|
||||||
return true;
|
return true;
|
||||||
@@ -249,6 +262,8 @@ bool CollectionWidget::importFile(const QString &collectionName,
|
|||||||
warn(tr("Can not add a model to the JSON file"),
|
warn(tr("Can not add a model to the JSON file"),
|
||||||
tr("The imported model is empty or is not supported."));
|
tr("The imported model is empty or is not supported."));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user