1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "persistentsettings.h"
6
7#include <QDebug>
8#include <QDir>
9#include <QStack>
10#include <QXmlStreamAttributes>
11#include <QXmlStreamReader>
12#include <QXmlStreamWriter>
13#include <QDateTime>
14#include <QTextStream>
15#include <QRegExp>
16#include <QRect>
17#include <QCoreApplication>
18
19#ifdef QT_GUI_LIB
20#include <QMessageBox>
21#endif
22
23#include "common/util/qtcassert.h"
24
25
26struct Context // Basic context containing element name string constants.
27{
28 Context() {}
29 const QString qtCreatorElement = QString("qtcreator");
30 const QString dataElement = QString("data");
31 const QString variableElement = QString("variable");
32 const QString typeAttribute = QString("type");
33 const QString valueElement = QString("value");
34 const QString valueListElement = QString("valuelist");
35 const QString valueMapElement = QString("valuemap");
36 const QString keyAttribute = QString("key");
37};
38
39struct ParseValueStackEntry
40{
41 explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
42 explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
43
44 QVariant value() const;
45 void addChild(const QString &key, const QVariant &v);
46
47 QVariant::Type type;
48 QString key;
49 QVariant simpleValue;
50 QVariantList listValue;
51 QVariantMap mapValue;
52};
53
54ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
55 type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
56{
57 QTC_ASSERT(simpleValue.isValid(), return);
58}
59
60QVariant ParseValueStackEntry::value() const
61{
62 switch (type) {
63 case QVariant::Invalid:
64 return QVariant();
65 case QVariant::Map:
66 return QVariant(mapValue);
67 case QVariant::List:
68 return QVariant(listValue);
69 default:
70 break;
71 }
72 return simpleValue;
73}
74
75void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
76{
77 switch (type) {
78 case QVariant::Map:
79 mapValue.insert(key, v);
80 break;
81 case QVariant::List:
82 listValue.push_back(v);
83 break;
84 default:
85 qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
86 << QVariant::typeToName(type) << value();
87 break;
88 }
89}
90
91class ParseContext : public Context
92{
93public:
94 QVariantMap parse(QFile &file);
95
96private:
97 enum Element { QtCreatorElement, DataElement, VariableElement,
98 SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
99
100 Element element(const QStringRef &r) const;
101 static inline bool isValueElement(Element e)
102 { return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
103 QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
104
105 bool handleStartElement(QXmlStreamReader &r);
106 bool handleEndElement(const QStringRef &name);
107
108 static QString formatWarning(const QXmlStreamReader &r, const QString &message);
109
110 QStack<ParseValueStackEntry> m_valueStack;
111 QVariantMap m_result;
112 QString m_currentVariableName;
113};
114
115QVariantMap ParseContext::parse(QFile &file)
116{
117 QXmlStreamReader r(&file);
118
119 m_result.clear();
120 m_currentVariableName.clear();
121
122 while (!r.atEnd()) {
123 switch (r.readNext()) {
124 case QXmlStreamReader::StartElement:
125 if (handleStartElement(r))
126 return m_result;
127 break;
128 case QXmlStreamReader::EndElement:
129 if (handleEndElement(r.name()))
130 return m_result;
131 break;
132 case QXmlStreamReader::Invalid:
133 qWarning("Error reading %s:%d: %s", qPrintable(file.fileName()),
134 int(r.lineNumber()), qPrintable(r.errorString()));
135 return QVariantMap();
136 default:
137 break;
138 } // switch token
139 } // while (!r.atEnd())
140 return m_result;
141}
142
143bool ParseContext::handleStartElement(QXmlStreamReader &r)
144{
145 const QStringRef name = r.name();
146 const Element e = element(name);
147 if (e == VariableElement) {
148 m_currentVariableName = r.readElementText();
149 return false;
150 }
151 if (!ParseContext::isValueElement(e))
152 return false;
153
154 const QXmlStreamAttributes attributes = r.attributes();
155 const QString key = attributes.hasAttribute(keyAttribute) ?
156 attributes.value(keyAttribute).toString() : QString();
157 switch (e) {
158 case SimpleValueElement: {
159 // This reads away the end element, so, handle end element right here.
160 const QVariant v = readSimpleValue(r, attributes);
161 if (!v.isValid()) {
162 qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString()));
163 return false;
164 }
165 m_valueStack.push_back(ParseValueStackEntry(v, key));
166 return handleEndElement(name);
167 }
168 case ListValueElement:
169 m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
170 break;
171 case MapValueElement:
172 m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
173 break;
174 default:
175 break;
176 }
177 return false;
178}
179
180bool ParseContext::handleEndElement(const QStringRef &name)
181{
182 const Element e = element(name);
183 if (ParseContext::isValueElement(e)) {
184 QTC_ASSERT(!m_valueStack.isEmpty(), return true);
185 const ParseValueStackEntry top = m_valueStack.pop();
186 if (m_valueStack.isEmpty()) {
187 QTC_ASSERT(!m_currentVariableName.isEmpty(), return true);
188 m_result.insert(m_currentVariableName, top.value());
189 m_currentVariableName.clear();
190 return false;
191 }
192 m_valueStack.top().addChild(top.key, top.value());
193 }
194 return e == QtCreatorElement;
195}
196
197QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message)
198{
199 QString result = QLatin1String("Warning reading ");
200 if (const QIODevice *device = r.device())
201 if (const auto file = qobject_cast<const QFile *>(device))
202 result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':');
203 result += QString::number(r.lineNumber());
204 result += QLatin1String(": ");
205 result += message;
206 return result;
207}
208
209ParseContext::Element ParseContext::element(const QStringRef &r) const
210{
211 if (r == valueElement)
212 return SimpleValueElement;
213 if (r == valueListElement)
214 return ListValueElement;
215 if (r == valueMapElement)
216 return MapValueElement;
217 if (r == qtCreatorElement)
218 return QtCreatorElement;
219 if (r == dataElement)
220 return DataElement;
221 if (r == variableElement)
222 return VariableElement;
223 return UnknownElement;
224}
225
226QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
227{
228 // Simple value
229 const QStringRef type = attributes.value(typeAttribute);
230 const QString text = r.readElementText();
231 if (type == QLatin1String("QChar")) {
232 QTC_ASSERT(text.size() == 1, return QVariant());
233 return QVariant(QChar(text.at(0)));
234 }
235 QVariant value;
236 value.setValue(text);
237 value.convert(QMetaType::type(type.toLatin1().data()));
238 return value;
239}
240
241// =================================== PersistentSettingsReader
242
243PersistentSettingsReader::PersistentSettingsReader() = default;
244
245QVariant PersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const
246{
247 if (valueMap.contains(variable))
248 return valueMap.value(variable);
249 return defaultValue;
250}
251
252QVariantMap PersistentSettingsReader::restoreValues() const
253{
254 return valueMap;
255}
256
257bool PersistentSettingsReader::load(const QString &fileName)
258{
259 valueMap.clear();
260
261 QFile file(fileName);
262 if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
263 return false;
264 ParseContext ctx;
265 valueMap = ctx.parse(file);
266 file.close();
267 return true;
268}
269
270static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx,
271 const QVariant &variant, const QString &key = QString())
272{
273 switch (static_cast<int>(variant.type())) {
274 case static_cast<int>(QVariant::StringList):
275 case static_cast<int>(QVariant::List):
276 w.writeStartElement(ctx.valueListElement);
277 w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List)));
278 if (!key.isEmpty())
279 w.writeAttribute(ctx.keyAttribute, key);
280 foreach (const QVariant &var, variant.toList())
281 writeVariantValue(w, ctx, var);
282 w.writeEndElement();
283 break;
284 case static_cast<int>(QVariant::Map): {
285 w.writeStartElement(ctx.valueMapElement);
286 w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map)));
287 if (!key.isEmpty())
288 w.writeAttribute(ctx.keyAttribute, key);
289 const QVariantMap varMap = variant.toMap();
290 const QVariantMap::const_iterator cend = varMap.constEnd();
291 for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i)
292 writeVariantValue(w, ctx, i.value(), i.key());
293 w.writeEndElement();
294 }
295 break;
296 case static_cast<int>(QMetaType::QObjectStar): // ignore QObjects!
297 case static_cast<int>(QMetaType::VoidStar): // ignore void pointers!
298 break;
299 default:
300 w.writeStartElement(ctx.valueElement);
301 w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName()));
302 if (!key.isEmpty()) {
303 w.writeAttribute(ctx.keyAttribute, key);
304 w.writeCharacters(variant.toString());
305 }
306 w.writeEndElement();
307 break;
308 }
309}
310
311PersistentSettingsWriter::PersistentSettingsWriter(const QString &_fileName, const QString &_docType) :
312 fileName(_fileName), docType(_docType)
313{ }
314
315PersistentSettingsWriter::~PersistentSettingsWriter()
316{
317 write(savedData, nullptr);
318}
319
320bool PersistentSettingsWriter::save(const QVariantMap &data, QString *errorString) const
321{
322 if (data == savedData)
323 return true;
324 return write(data, errorString);
325}
326
327#ifdef QT_GUI_LIB
328bool PersistentSettingsWriter::save(const QVariantMap &data, QWidget *parent) const
329{
330 QString errorString;
331 const bool success = save(data, &errorString);
332 if (!success)
333 QMessageBox::critical(parent,
334 QCoreApplication::translate("Utils::FileSaverBase", "File Error"),
335 errorString);
336 return success;
337}
338#endif // QT_GUI_LIB
339
340QString PersistentSettingsWriter::getFileName() const
341{ return fileName; }
342
343void PersistentSettingsWriter::setContents(const QVariantMap &data)
344{
345 savedData = data;
346}
347
348bool PersistentSettingsWriter::write(const QVariantMap &data, QString *errorString) const
349{
350 QDir tmp;
351 tmp.mkpath(fileName);
352// FileSaver saver(m_fileName, QIODevice::Text);
353 QFile file(fileName);
354 if (/*!saver.hasError()*/ file.open(QIODevice::WriteOnly | QIODevice::Text)) {
355 const Context ctx;
356 QXmlStreamWriter w(&file);
357 w.setAutoFormatting(true);
358 w.setAutoFormattingIndent(1); // Historical, used to be QDom.
359 w.writeStartDocument();
360 w.writeDTD(QLatin1String("<!DOCTYPE ") + docType + QLatin1Char('>'));
361 w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. ").
362 arg(QCoreApplication::applicationName(),
363 QCoreApplication::applicationVersion(),
364 QDateTime::currentDateTime().toString(Qt::ISODate)));
365 w.writeStartElement(ctx.qtCreatorElement);
366 const QVariantMap::const_iterator cend = data.constEnd();
367 for (QVariantMap::const_iterator it = data.constBegin(); it != cend; ++it) {
368 w.writeStartElement(ctx.dataElement);
369 w.writeTextElement(ctx.variableElement, it.key());
370 writeVariantValue(w, ctx, it.value());
371 w.writeEndElement();
372 }
373 w.writeEndDocument();
374
375// saver.setResult(&w);
376 }
377
378 bool ok = /*saver.finalize();*/file.flush();
379 if (ok) {
380 savedData = data;
381 } else if (errorString) {
382 savedData.clear();
383// *errorString = saver.errorString();
384 }
385
386 return ok;
387}
388