QXmpp Version: 1.11.3
Loading...
Searching...
No Matches
XmlWriter.h
1// SPDX-FileCopyrightText: 2025 Linus Jahn <lnj@kaidan.im>
2//
3// SPDX-License-Identifier: LGPL-2.1-or-later
4
5#ifndef XMLWRITER_H
6#define XMLWRITER_H
7
8#include "QXmppUtils_p.h"
9
10#include "Enums.h"
11#include "StringLiterals.h"
12
13#include <optional>
14
15#include <QDateTime>
16#include <QMimeType>
17#include <QUrl>
18#include <QUuid>
19#include <QXmlStreamWriter>
20
21namespace QXmpp::Private {
22
23class XmlWriter;
24
25#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
26inline auto toString65(QStringView s) { return s.toString(); }
27inline auto toString65(const QByteArray &s) { return QString::fromUtf8(s); }
28inline const QString &toString65(const QString &s) { return s; }
29inline QString toString65(QString &&s) { return std::move(s); }
30#else
31#define toString65(x) x
32#endif
33
34template<typename T>
35struct StringSerializer {
36 static decltype(auto) serialize(auto &&s) { return std::forward<decltype(s)>(s); }
37 static bool hasValue(auto &&s) { return !s.isEmpty(); }
38};
39
40template<typename T>
41concept IsOptionalStringSerializable = requires(const T &value) {
42 { StringSerializer<T>::hasValue(value) } -> std::convertible_to<bool>;
43};
44
45template<typename U>
46struct StringSerializer<std::optional<U>> {
47 static auto serialize(auto &&value)
48 {
49 if (value) {
50 return StringSerializer<U>::serialize(*value);
51 }
52 return std::remove_cvref_t<decltype(StringSerializer<U>::serialize(*value))> {};
53 }
54 static bool hasValue(const auto &value) { return value.has_value(); }
55};
56
57template<>
58struct StringSerializer<bool> {
59 static auto serialize(bool value) { return value ? u"true"_s : u"false"_s; }
60 static bool hasValue(bool) { return true; }
61};
62
63template<typename Number>
64 requires std::is_integral_v<Number> || std::is_floating_point_v<Number>
65struct StringSerializer<Number> {
66 static auto serialize(Number value) { return QString::number(value); }
67};
68
69template<typename Enum>
70 requires std::is_enum_v<Enum>
71struct StringSerializer<Enum> {
72 static auto serialize(Enum value) { return Enums::toString(value); }
73 static bool hasValue(Enum value)
74 requires Enums::NullableEnum<Enum>
75 {
76 return value != Enums::Data<Enum>::NullValue;
77 }
78};
79
80template<>
81struct StringSerializer<QDateTime> {
82 static QString serialize(const QDateTime &datetime);
83 static bool hasValue(const QDateTime &datetime) { return datetime.isValid(); }
84};
85
86template<>
87struct StringSerializer<QUuid> {
88 static auto serialize(QUuid uuid) { return uuid.toString(QUuid::WithoutBraces); }
89 static bool hasValue(QUuid uuid) { return !uuid.isNull(); }
90};
91
92template<>
93struct StringSerializer<QUrl> {
94 static auto serialize(const QUrl &url) { return url.toString(); }
95 static bool hasValue(const QUrl &url) { return !url.isEmpty(); }
96};
97
98template<>
99struct StringSerializer<QMimeType> {
100 static auto serialize(const QMimeType &mimeType) { return mimeType.name(); }
101 static bool hasValue(const QMimeType &mimeType) { return mimeType.isValid(); }
102};
103
104struct Base64 {
105 const QByteArray &data;
106
107 static Base64 fromByteArray(const QByteArray &d) { return { d }; }
108};
109
110template<>
111struct StringSerializer<Base64> {
112 static auto serialize(Base64 value) { return value.data.toBase64(); }
113 static bool hasValue(Base64 value) { return !value.data.isEmpty(); }
114};
115
116struct DefaultedBool {
117 bool value;
118 bool defaultValue;
119};
120
121template<>
122struct StringSerializer<DefaultedBool> {
123 static auto serialize(auto &&v) { return v.value ? u"true"_s : u"false"_s; }
124 static bool hasValue(auto &&v) { return v.value != v.defaultValue; }
125};
126
127// Serializes value to string and converts to type writeable to QXmlStreamWriter
128template<typename T>
129decltype(auto) xmlS(T &&value)
130{
131 return toString65(StringSerializer<std::decay_t<T>>::serialize(std::forward<T>(value)));
132}
133
134template<typename T>
135concept XmlSerializeable = XmlWriterSerializeable<T> || QXmlStreamSerializeable<T>;
136
137template<typename T>
138concept XmlSerializeableRange =
139 std::ranges::range<T> && XmlSerializeable<std::ranges::range_value_t<T>>;
140
141class XmlWriter
142{
143public:
144 explicit XmlWriter(QXmlStreamWriter *writer) noexcept : w(writer) { }
145 operator QXmlStreamWriter *() const noexcept { return w; }
146 QXmlStreamWriter *writer() const noexcept { return w; }
147
148 template<XmlSerializeable T>
149 void write(T &&value)
150 {
151 value.toXml(*this);
152 }
153 template<IsStdOptional T>
154 void write(T &&opt)
155 {
156 if (opt) {
157 write(*opt);
158 }
159 }
160 template<typename Container>
161 requires(XmlSerializeableRange<Container> && !XmlSerializeable<Container>)
162 void write(Container &&container)
163 {
164 for (const auto &value : container) {
165 write(value);
166 }
167 }
168 template<std::invocable Function>
169 void write(Function &&f)
170 {
171 f();
172 }
173
174private:
175#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
176 using String = QAnyStringView;
177#else
178 using String = const QString &;
179#endif
180
181 void writeStartElement(String name) { w->writeStartElement(name); }
182 QXMPP_PRIVATE_EXPORT void writeStartElement(String name, String xmlns);
183 void writeEndElement() { w->writeEndElement(); }
184 void writeEmptyElement(String name) { w->writeEmptyElement(name); }
185 QXMPP_PRIVATE_EXPORT void writeEmptyElement(String name, String xmlns);
186 QXMPP_PRIVATE_EXPORT void writeTextOrEmptyElement(String name, String value);
187 QXMPP_PRIVATE_EXPORT void writeTextOrEmptyElement(String name, String xmlns, String value);
188 QXMPP_PRIVATE_EXPORT void writeSingleAttributeElement(String name, String attribute, String value);
189
190 template<typename Tag, typename... Values>
191 friend struct Element;
192
193 template<IsOptionalStringSerializable Enum>
194 friend struct OptionalEnumElement;
195
196 template<typename Tag, typename Value>
197 friend struct TextElement;
198
199 template<typename Tag, IsOptionalStringSerializable Value>
200 friend struct OptionalTextElement;
201
202 template<std::ranges::range Container>
203 friend struct SingleAttributeElements;
204
205 QXmlStreamWriter *w;
206};
207
208template<typename Tag, typename... Values>
209struct Element {
210 Tag tag;
211 std::tuple<Values...> values;
212
213 template<typename... V>
214 Element(Tag tag, V &&...values)
215 : tag(std::forward<Tag>(tag)), values(std::forward<V>(values)...)
216 {
217 }
218
219 void toXml(XmlWriter &w)
220 {
221 if constexpr (sizeof...(Values) == 0) {
222 if constexpr (IsXmlTag<Tag>) {
223 auto &[name, xmlns] = tag;
224 w.writeEmptyElement(xmlS(name), xmlS(xmlns));
225 } else {
226 w.writeEmptyElement(xmlS(tag));
227 }
228 } else {
229 if constexpr (IsXmlTag<Tag>) {
230 auto &[name, xmlns] = tag;
231 w.writeStartElement(xmlS(name), xmlS(xmlns));
232 } else {
233 w.writeStartElement(xmlS(tag));
234 }
235 std::apply([&w](auto &&...value) { (w.write(value), ...); }, values);
236 w.writeEndElement();
237 }
238 }
239};
240
241template<typename Name, typename... Values>
242 requires(!IsXmlTag<Name>)
243Element(Name &&, Values &&...) -> Element<Name, Values...>;
244
245template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename... Values>
246Element(Tag &&, Values &&...) -> Element<Tag, Values...>;
247
248template<typename Value>
249struct Attribute {
250 QStringView name;
251 Value value;
252
253 void toXml(XmlWriter &w) const
254 {
255 w.writer()->writeAttribute(xmlS(name), xmlS(value));
256 }
257};
258
259template<typename Value>
260Attribute(QStringView, Value &&) -> Attribute<Value>;
261
262template<IsOptionalStringSerializable Value>
263struct OptionalAttribute {
264 QStringView name;
265 Value value;
266
267 void toXml(QXmlStreamWriter *w) const
268 {
269 if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
270 w->writeAttribute(xmlS(name), xmlS(value));
271 }
272 }
273};
274
275template<typename Value>
276OptionalAttribute(QStringView, Value &&) -> OptionalAttribute<Value>;
277
278template<typename Value>
279struct Characters {
280 Value value;
281
282 template<typename V>
283 Characters(V &&value) : value(std::forward<V>(value)) { }
284
285 void toXml(QXmlStreamWriter *w) const
286 {
287 w->writeCharacters(xmlS(value));
288 }
289};
290
291template<typename Value>
292Characters(Value &&) -> Characters<Value>;
293
294template<IsOptionalStringSerializable Value>
295struct OptionalCharacters {
296 Value value;
297
298 template<typename V>
299 OptionalCharacters(V &&value) : value(std::forward<V>(value)) { }
300
301 void toXml(QXmlStreamWriter *w) const
302 {
303 if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
304 w->writeCharacters(xmlS(value));
305 }
306 }
307};
308
309template<typename T>
310OptionalCharacters(T &&) -> OptionalCharacters<T>;
311
312struct DefaultNamespace {
313 QStringView xmlns;
314
315 void toXml(QXmlStreamWriter *w) const
316 {
317 w->writeDefaultNamespace(xmlS(xmlns));
318 }
319};
320
321struct Namespace {
322 QStringView prefix;
323 QStringView xmlns;
324
325 void toXml(QXmlStreamWriter *w) const
326 {
327 w->writeNamespace(xmlS(xmlns), xmlS(prefix));
328 }
329};
330
331template<IsOptionalStringSerializable Enum>
332struct OptionalEnumElement {
333 Enum enumeration;
334 QStringView xmlns = {};
335
336 void toXml(XmlWriter &w) const
337 {
338 if (StringSerializer<Enum>::hasValue(enumeration)) {
339 if (xmlns.isNull()) {
340 w.writeEmptyElement(xmlS(enumeration));
341 } else {
342 w.writeEmptyElement(xmlS(enumeration), xmlS(xmlns));
343 }
344 }
345 }
346};
347
348template<IsOptionalStringSerializable Enum>
349OptionalEnumElement(Enum) -> OptionalEnumElement<Enum>;
350template<IsOptionalStringSerializable Enum, std::convertible_to<QStringView> StringView>
351OptionalEnumElement(Enum, StringView) -> OptionalEnumElement<Enum>;
352
353template<typename Tag, typename Value>
354struct TextElement {
355 Tag tag;
356 Value value;
357
358 template<typename V>
359 TextElement(Tag tag, V &&value) : tag(tag), value(std::forward<V>(value)) { }
360
361 void toXml(XmlWriter &w)
362 {
363 if constexpr (IsXmlTag<Tag>) {
364 auto &[name, xmlns] = tag;
365 w.writeTextOrEmptyElement(xmlS(name), xmlS(xmlns), xmlS(value));
366 } else {
367 w.writeTextOrEmptyElement(xmlS(tag), xmlS(value));
368 }
369 }
370};
371
372template<typename Name, typename Value>
373 requires(!IsXmlTag<Name>)
374TextElement(Name &&, Value &&) -> TextElement<Name, Value>;
375
376template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Value>
377TextElement(Tag &&, Value &&) -> TextElement<Tag, Value>;
378
379template<typename Tag, std::ranges::range Range>
380struct TextElements {
381 Tag tag;
382 Range values;
383
384 template<typename R>
385 TextElements(Tag tag, R &&values) : tag(tag), values(std::forward<R>(values)) { }
386
387 void toXml(XmlWriter &w)
388 {
389 for (const auto &value : values) {
390 w.write(TextElement { tag, value });
391 }
392 }
393};
394
395template<typename Name, typename Range>
396 requires(!IsXmlTag<Name>)
397TextElements(Name &&, Range &&) -> TextElements<Name, Range>;
398
399template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Range>
400TextElements(Tag &&, Range &&) -> TextElements<Tag, Range>;
401
402template<typename Tag, IsOptionalStringSerializable Value>
403struct OptionalTextElement {
404 Tag tag;
405 Value value;
406
407 template<typename V>
408 OptionalTextElement(Tag tag, V &&value) : tag(tag), value(std::forward<V>(value)) { }
409
410 void toXml(XmlWriter &w)
411 {
412 if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
413 if constexpr (IsXmlTag<Tag>) {
414 auto &[name, xmlns] = tag;
415 w.writeTextOrEmptyElement(xmlS(name), xmlS(xmlns), xmlS(value));
416 } else {
417 w.writeTextOrEmptyElement(xmlS(tag), xmlS(value));
418 }
419 }
420 }
421};
422
423template<typename Name, typename Value>
424 requires(!IsXmlTag<Name>)
425OptionalTextElement(Name &&, Value &&) -> OptionalTextElement<Name, Value>;
426
427template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Value>
428OptionalTextElement(Tag, Value &&) -> OptionalTextElement<Tag, Value>;
429
430template<std::ranges::range Range>
431struct SingleAttributeElements {
432 QStringView name;
433 QStringView attribute;
434 Range values;
435
436 template<typename R>
437 SingleAttributeElements(QStringView name, QStringView attribute, R &&values)
438 : name(name), attribute(attribute), values(std::forward<R>(values)) { }
439
440 void toXml(XmlWriter &w)
441 {
442 for (const auto &value : values) {
443 w.writeSingleAttributeElement(xmlS(name), xmlS(attribute), xmlS(value));
444 }
445 }
446};
447
448template<typename Range>
449SingleAttributeElements(QStringView, QStringView, Range &&) -> SingleAttributeElements<Range>;
450
451template<typename... Values>
452struct OptionalContent {
453 bool condition;
454 std::tuple<Values...> values;
455
456 template<std::convertible_to<bool> Condition, typename... V>
457 OptionalContent(Condition condition, V &&...values)
458 : condition(condition), values(std::forward<V>(values)...) { }
459
460 void toXml(XmlWriter &w)
461 {
462 if (condition) {
463 std::apply([&w](auto &&...value) { (w.write(value), ...); }, values);
464 }
465 }
466};
467
468template<std::convertible_to<bool> Condition, typename... Values>
469OptionalContent(Condition, Values &&...) -> OptionalContent<Values...>;
470
471} // namespace QXmpp::Private
472
473#endif // XMLWRITER_H