WingHexExplorer2/3rdparty/QJsonModel/QJsonModel.cpp

519 lines
16 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.
*/
// NOLINTBEGIN
#include "QJsonModel.hpp"
#include <QDebug>
#include <QFile>
#include <QFont>
inline bool contains(const QStringList &list, const QString &value) {
for (auto val : list)
if (value.contains(val, Qt::CaseInsensitive))
return true;
return false;
}
QJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) { mParent = parent; }
QJsonTreeItem::~QJsonTreeItem() { qDeleteAll(mChilds); }
void QJsonTreeItem::appendChild(QJsonTreeItem *item) { mChilds.append(item); }
QJsonTreeItem *QJsonTreeItem::child(int row) { return mChilds.value(row); }
QJsonTreeItem *QJsonTreeItem::parent() { return mParent; }
int QJsonTreeItem::childCount() const { return mChilds.count(); }
int QJsonTreeItem::row() const {
if (mParent)
return mParent->mChilds.indexOf(const_cast<QJsonTreeItem *>(this));
return 0;
}
void QJsonTreeItem::setKey(const QString &key) { mKey = key; }
void QJsonTreeItem::setValue(const QVariant &value) { mValue = value; }
void QJsonTreeItem::setType(const QJsonValue::Type &type) { mType = type; }
QString QJsonTreeItem::key() const { return mKey; }
QVariant QJsonTreeItem::value() const { return mValue; }
QJsonValue::Type QJsonTreeItem::type() const { return mType; }
QJsonTreeItem *QJsonTreeItem::load(const QJsonValue &value,
const QStringList &exceptions,
QJsonTreeItem *parent) {
QJsonTreeItem *rootItem = new QJsonTreeItem(parent);
rootItem->setKey("root");
if (value.isObject()) {
// Get all QJsonValue childs
const QStringList keys =
value.toObject().keys(); // To prevent clazy-range warning
for (const QString &key : keys) {
if (contains(exceptions, key)) {
continue;
}
QJsonValue v = value.toObject().value(key);
QJsonTreeItem *child = load(v, exceptions, rootItem);
child->setKey(key);
child->setType(v.type());
rootItem->appendChild(child);
}
} else if (value.isArray()) {
// Get all QJsonValue childs
int index = 0;
const QJsonArray array =
value.toArray(); // To prevent clazy-range warning
for (const QJsonValue &v : array) {
QJsonTreeItem *child = load(v, exceptions, rootItem);
child->setKey(QString::number(index));
child->setType(v.type());
rootItem->appendChild(child);
++index;
}
} else {
rootItem->setValue(value.toVariant());
rootItem->setType(value.type());
}
return rootItem;
}
//=========================================================================
inline uchar hexdig(uint u) { return (u < 0xa ? '0' + u : 'a' + u - 0xa); }
QByteArray escapedString(const QString &s) {
QByteArray ba(s.length(), Qt::Uninitialized);
uchar *cursor =
reinterpret_cast<uchar *>(const_cast<char *>(ba.constData()));
const uchar *ba_end = cursor + ba.length();
const ushort *src = reinterpret_cast<const ushort *>(s.constBegin());
const ushort *const end = reinterpret_cast<const ushort *>(s.constEnd());
while (src != end) {
if (cursor >= ba_end - 6) {
// ensure we have enough space
int pos = cursor - reinterpret_cast<const uchar *>(ba.constData());
ba.resize(ba.size() * 2);
cursor = reinterpret_cast<uchar *>(ba.data()) + pos;
ba_end =
reinterpret_cast<const uchar *>(ba.constData()) + ba.length();
}
uint u = *src++;
if (u < 0x80) {
if (u < 0x20 || u == 0x22 || u == 0x5c) {
*cursor++ = '\\';
switch (u) {
case 0x22:
*cursor++ = '"';
break;
case 0x5c:
*cursor++ = '\\';
break;
case 0x8:
*cursor++ = 'b';
break;
case 0xc:
*cursor++ = 'f';
break;
case 0xa:
*cursor++ = 'n';
break;
case 0xd:
*cursor++ = 'r';
break;
case 0x9:
*cursor++ = 't';
break;
default:
*cursor++ = 'u';
*cursor++ = '0';
*cursor++ = '0';
*cursor++ = hexdig(u >> 4);
*cursor++ = hexdig(u & 0xf);
}
} else {
*cursor++ = (uchar)u;
}
} else if (QUtf8Functions::toUtf8<QUtf8BaseTraits>(u, cursor, src,
end) < 0) {
// failed to get valid utf8 use JSON escape sequence
*cursor++ = '\\';
*cursor++ = 'u';
*cursor++ = hexdig(u >> 12 & 0x0f);
*cursor++ = hexdig(u >> 8 & 0x0f);
*cursor++ = hexdig(u >> 4 & 0x0f);
*cursor++ = hexdig(u & 0x0f);
}
}
ba.resize(cursor - reinterpret_cast<const uchar *>(ba.constData()));
return ba;
}
QJsonModel::QJsonModel(QObject *parent)
: QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
mHeaders.append(tr("key"));
mHeaders.append(tr("value"));
}
QJsonModel::QJsonModel(const QString &fileName, QObject *parent)
: QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
mHeaders.append(tr("key"));
mHeaders.append(tr("value"));
load(fileName);
}
QJsonModel::QJsonModel(QIODevice *device, QObject *parent)
: QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
mHeaders.append(tr("key"));
mHeaders.append(tr("value"));
load(device);
}
QJsonModel::QJsonModel(const QByteArray &json, QObject *parent)
: QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
mHeaders.append(tr("key"));
mHeaders.append(tr("value"));
loadJson(json);
}
QJsonModel::~QJsonModel() { delete mRootItem; }
bool QJsonModel::load(const QString &fileName) {
QFile file(fileName);
bool success = false;
if (file.open(QIODevice::ReadOnly)) {
success = load(&file);
file.close();
} else {
success = false;
}
return success;
}
bool QJsonModel::load(QIODevice *device) { return loadJson(device->readAll()); }
bool QJsonModel::loadJson(const QByteArray &json) {
QJsonParseError error;
auto const &jdoc = QJsonDocument::fromJson(json, &error);
if (error.error != QJsonParseError::NoError) {
return false;
}
if (!jdoc.isNull()) {
beginResetModel();
delete mRootItem;
if (jdoc.isArray()) {
mRootItem =
QJsonTreeItem::load(QJsonValue(jdoc.array()), mExceptions);
mRootItem->setType(QJsonValue::Array);
} else {
mRootItem =
QJsonTreeItem::load(QJsonValue(jdoc.object()), mExceptions);
mRootItem->setType(QJsonValue::Object);
}
endResetModel();
return true;
}
qDebug() << Q_FUNC_INFO << "cannot load json";
return false;
}
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() == 0)
return QString("%1").arg(item->key());
if (index.column() == 1)
return item->value();
} else if (Qt::EditRole == role) {
if (index.column() == 1)
return item->value();
}
return {};
}
bool QJsonModel::setData(const QModelIndex &index, const QVariant &value,
int role) {
int col = index.column();
if (Qt::EditRole == role) {
if (col == 1) {
QJsonTreeItem *item =
static_cast<QJsonTreeItem *>(index.internalPointer());
item->setValue(value);
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 mHeaders.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 = mRootItem;
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 == mRootItem)
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 = mRootItem;
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 col = index.column();
auto item = static_cast<QJsonTreeItem *>(index.internalPointer());
auto isArray = QJsonValue::Array == item->type();
auto isObject = QJsonValue::Object == item->type();
if ((col == 1) && !(isArray || isObject))
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
else
return QAbstractItemModel::flags(index);
}
QByteArray QJsonModel::json(bool compact) {
auto jsonValue = genJson(mRootItem);
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 d = jsonValue.toDouble();
if (qIsFinite(d)) {
json += QByteArray::number(d, '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::addException(const QStringList &exceptions) {
mExceptions = exceptions;
}
QJsonValue QJsonModel::genJson(QJsonTreeItem *item) const {
auto type = item->type();
int nchild = item->childCount();
if (QJsonValue::Object == type) {
QJsonObject jo;
for (int i = 0; i < nchild; ++i) {
auto ch = item->child(i);
auto key = ch->key();
jo.insert(key, genJson(ch));
}
return jo;
} else if (QJsonValue::Array == type) {
QJsonArray arr;
for (int i = 0; i < nchild; ++i) {
auto ch = item->child(i);
arr.append(genJson(ch));
}
return arr;
} else {
QJsonValue va;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (item->value().typeId()) {
#else
switch (item->value().userType()) {
#endif
case QMetaType::Bool: {
va = item->value().toBool();
break;
}
default:
va = item->value().toString();
break;
}
return va;
}
}