QJsonModel/QJsonModel.cpp

463 lines
11 KiB
C++

/* QJsonModel.cpp
* Copyright (c) 2011 SCHUTZ Sacha
* Copyright © 2024 Saul D. Beniquez
*
* License:
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "QJsonModel.hpp"
#include "details/QJsonTreeItem.hpp"
#include <QDebug>
#include <QFile>
#include <QFont>
namespace {
using FieldPermissions = QJsonModel::FieldPermissions;
using ErrorFlag = QJsonModel::ErrorFlag;
constexpr int kKeyColumn = 0;
constexpr int kValueColumn = 1;
}
QJsonModel::QJsonModel(QObject* parent, FieldPermissions permissions)
: QAbstractItemModel(parent)
, rootItem{ new QJsonTreeItem }
, editMode(permissions)
{
headers.append("key");
headers.append("value");
}
QJsonModel::QJsonModel(
const QString& fileName, QObject* parent, FieldPermissions permissions
)
: QAbstractItemModel(parent)
, rootItem{ new QJsonTreeItem }
, editMode(permissions)
{
headers.append("key");
headers.append("value");
load(fileName);
}
QJsonModel::QJsonModel(
QIODevice* device, QObject* parent, FieldPermissions permissions
)
: QAbstractItemModel(parent)
, rootItem{ new QJsonTreeItem }
, editMode(permissions)
{
headers.append("key");
headers.append("value");
}
QJsonModel::QJsonModel(
const QByteArray& json, QObject* parent, FieldPermissions permissions
)
: QAbstractItemModel(parent)
, rootItem{ new QJsonTreeItem }
, editMode(permissions)
{
headers.append("key");
headers.append("value");
loadJson(json);
}
QJsonModel::~QJsonModel()
{
delete rootItem;
}
ErrorFlag QJsonModel::load(const QString& fileName)
{
QFile file(fileName);
ErrorFlag result = kSuccess;
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
result = load(&file);
file.close();
} else {
result = kError;
}
return result;
}
ErrorFlag QJsonModel::load(QIODevice* device)
{
QTextStream content(device);
return loadJson(content.readAll().toUtf8());
}
ErrorFlag QJsonModel::loadJson(const QByteArray& json)
{
auto const& jdoc = QJsonDocument::fromJson(json);
if (!jdoc.isNull()) {
beginResetModel();
delete rootItem;
if (jdoc.isArray()) {
rootItem = QJsonTreeItem::load(
QJsonValue(jdoc.array()), exclusions
);
rootItem->setType(QJsonValue::Array);
} else {
rootItem = QJsonTreeItem::load(
QJsonValue(jdoc.object()), exclusions
);
rootItem->setType(QJsonValue::Object);
}
endResetModel();
return kSuccess;
}
qDebug() << Q_FUNC_INFO << "cannot load json";
return kError;
}
QVariant QJsonModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return {};
QJsonTreeItem* item =
static_cast<QJsonTreeItem*>(index.internalPointer());
if (role == Qt::DisplayRole) {
if (index.column() == kKeyColumn)
return QString("%1").arg(item->key());
if (index.column() == kValueColumn)
return item->value();
} else if (Qt::EditRole == role) {
if (index.column() == 1)
return item->value();
}
return {};
}
bool QJsonModel::setData(
const QModelIndex& index, const QVariant& data, int role
)
{
int col = index.column();
if (Qt::EditRole == role) {
if (col == kKeyColumn) {
auto unboxedValue = data.value<QString>();
if (unboxedValue.isEmpty()) {
return false;
}
QJsonTreeItem* item =
static_cast<QJsonTreeItem*>(index.internalPointer());
item->setKey(unboxedValue);
emit dataChanged(index, index, { Qt::EditRole });
return true;
}
if (col == kValueColumn) {
QJsonTreeItem* item =
static_cast<QJsonTreeItem*>(index.internalPointer());
item->setValue(data);
emit dataChanged(index, index, { Qt::EditRole });
return true;
}
}
return false;
}
QVariant
QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return {};
if (orientation == Qt::Horizontal)
return headers.value(section);
else
return {};
}
QModelIndex
QJsonModel::index(int row, int column, const QModelIndex& parent) const
{
if (!hasIndex(row, column, parent))
return {};
QJsonTreeItem* parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem =
static_cast<QJsonTreeItem*>(parent.internalPointer());
QJsonTreeItem* childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return {};
}
QModelIndex QJsonModel::parent(const QModelIndex& index) const
{
if (!index.isValid())
return {};
QJsonTreeItem* childItem =
static_cast<QJsonTreeItem*>(index.internalPointer());
QJsonTreeItem* parentItem = childItem->parent();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int QJsonModel::rowCount(const QModelIndex& parent) const
{
QJsonTreeItem* parentItem;
if (parent.column() > 0)
return 0;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem =
static_cast<QJsonTreeItem*>(parent.internalPointer());
return parentItem->childCount();
}
int QJsonModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return 2;
}
Qt::ItemFlags QJsonModel::flags(const QModelIndex& index) const
{
int column = index.column();
auto item = static_cast<QJsonTreeItem*>(index.internalPointer());
auto isArray = QJsonValue::Array == item->type();
auto isObject = QJsonValue::Object == item->type();
auto result = QAbstractItemModel::flags(index);
if (column == kKeyColumn) {
if (this->editMode & FieldPermissions::kWritableKey) {
result = result | Qt::ItemIsEditable;
}
}
if (!isArray && !isObject) {
if (column == kValueColumn) {
if (this->editMode & FieldPermissions::kWritableValue) {
result = result | Qt::ItemIsEditable;
}
}
}
return result;
}
QByteArray QJsonModel::json(bool compact)
{
auto jsonValue = genJson(rootItem);
QByteArray json;
if (jsonValue.isNull())
return json;
if (jsonValue.isArray())
arrayToJson(jsonValue.toArray(), json, 0, compact);
else
objectToJson(jsonValue.toObject(), json, 0, compact);
return json;
}
void QJsonModel::objectToJson(
QJsonObject jsonObject, QByteArray& json, int indent, bool compact
)
{
json += compact ? "{" : "{\n";
objectContentToJson(
jsonObject, json, indent + (compact ? 0 : 1), compact
);
json += QByteArray(4 * indent, ' ');
json += compact ? "}" : "}\n";
}
void QJsonModel::arrayToJson(
QJsonArray jsonArray, QByteArray& json, int indent, bool compact
)
{
json += compact ? "[" : "[\n";
arrayContentToJson(jsonArray, json, indent + (compact ? 0 : 1), compact);
json += QByteArray(4 * indent, ' ');
json += compact ? "]" : "]\n";
}
void QJsonModel::arrayContentToJson(
QJsonArray jsonArray, QByteArray& json, int indent, bool compact
)
{
if (jsonArray.size() <= 0)
return;
QByteArray indentString(4 * indent, ' ');
int i = 0;
while (1) {
json += indentString;
valueToJson(jsonArray.at(i), json, indent, compact);
if (++i == jsonArray.size()) {
if (!compact)
json += '\n';
break;
}
json += compact ? "," : ",\n";
}
}
void QJsonModel::objectContentToJson(
QJsonObject jsonObject, QByteArray& json, int indent, bool compact
)
{
if (jsonObject.size() <= 0)
return;
QByteArray indentString(4 * indent, ' ');
int i = 0;
while (1) {
QString key = jsonObject.keys().at(i);
json += indentString;
json += '"';
json += escapedString(key);
json += compact ? "\":" : "\": ";
valueToJson(jsonObject.value(key), json, indent, compact);
if (++i == jsonObject.size()) {
if (!compact)
json += '\n';
break;
}
json += compact ? "," : ",\n";
}
}
void QJsonModel::valueToJson(
QJsonValue jsonValue, QByteArray& json, int indent, bool compact
)
{
QJsonValue::Type type = jsonValue.type();
switch (type) {
case QJsonValue::Bool:
json += jsonValue.toBool() ? "true" : "false";
break;
case QJsonValue::Double: {
const double kvalue = jsonValue.toDouble();
if (qIsFinite(kvalue)) {
json += QByteArray::number(
kvalue, 'f', QLocale::FloatingPointShortest
);
} else {
json += "null"; // +INF || -INF || NaN (see
// RFC4627#section2.4)
}
break;
}
case QJsonValue::String:
json += '"';
json += escapedString(jsonValue.toString());
json += '"';
break;
case QJsonValue::Array:
json += compact ? "[" : "[\n";
arrayContentToJson(
jsonValue.toArray(),
json,
indent + (compact ? 0 : 1),
compact
);
json += QByteArray(4 * indent, ' ');
json += ']';
break;
case QJsonValue::Object:
json += compact ? "{" : "{\n";
objectContentToJson(
jsonValue.toObject(),
json,
indent + (compact ? 0 : 1),
compact
);
json += QByteArray(4 * indent, ' ');
json += '}';
break;
case QJsonValue::Null:
default:
json += "null";
}
}
void QJsonModel::addExclusion(const QStringList& exclusions)
{
this->exclusions = exclusions;
}
QJsonValue QJsonModel::genJson(QJsonTreeItem* item) const
{
auto type = item->type();
int nchild = item->childCount();
if (QJsonValue::Object == type) {
QJsonObject jsonObj;
for (int i = 0; i < nchild; ++i) {
auto child = item->child(i);
auto key = child->key();
jsonObj.insert(key, genJson(child));
}
return jsonObj;
} else if (QJsonValue::Array == type) {
QJsonArray arr;
for (int i = 0; i < nchild; ++i) {
auto child = item->child(i);
arr.append(genJson(child));
}
return arr;
} else {
QJsonValue value;
switch (item->value().typeId()) {
case QMetaType::Bool: {
value = item->value().toBool();
break;
}
default:
value = item->value().toString();
break;
}
(item->value());
return value;
}
}
// clang-format off
// vim: set foldmethod=syntax foldlevel=99 foldminlines=10 textwidth=80 ts=4 sts=0 sw=4 noexpandtab ft=cpp.doxygen :