Loading...
Searching...
No Matches
XmlWriter.h
// SPDX-FileCopyrightText: 2025 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later
#ifndef XMLWRITER_H
#define XMLWRITER_H
#include "QXmppUtils_p.h"
#include "Enums.h"
#include "StringLiterals.h"
#include <optional>
#include <QDateTime>
#include <QMimeType>
#include <QUrl>
#include <QUuid>
#include <QXmlStreamWriter>
namespace QXmpp::Private {
class XmlWriter;
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
inline auto toString65(QStringView s) { return s.toString(); }
inline auto toString65(const QByteArray &s) { return QString::fromUtf8(s); }
inline const QString &toString65(const QString &s) { return s; }
inline QString toString65(QString &&s) { return std::move(s); }
#else
#define toString65(x) x
#endif
template<typename T>
struct StringSerializer {
static decltype(auto) serialize(auto &&s) { return std::forward<decltype(s)>(s); }
static bool hasValue(auto &&s) { return !s.isEmpty(); }
};
template<typename T>
concept IsOptionalStringSerializable = requires(const T &value) {
{ StringSerializer<T>::hasValue(value) } -> std::convertible_to<bool>;
};
template<typename U>
struct StringSerializer<std::optional<U>> {
static auto serialize(auto &&value)
{
if (value) {
return StringSerializer<U>::serialize(*value);
}
return std::remove_cvref_t<decltype(StringSerializer<U>::serialize(*value))> {};
}
static bool hasValue(const auto &value) { return value.has_value(); }
};
template<>
struct StringSerializer<bool> {
static auto serialize(bool value) { return value ? u"true"_s : u"false"_s; }
static bool hasValue(bool) { return true; }
};
template<typename Number>
requires std::is_integral_v<Number> || std::is_floating_point_v<Number>
struct StringSerializer<Number> {
static auto serialize(Number value) { return QString::number(value); }
};
template<typename Enum>
requires std::is_enum_v<Enum>
struct StringSerializer<Enum> {
static auto serialize(Enum value) { return Enums::toString(value); }
static bool hasValue(Enum value)
requires Enums::NullableEnum<Enum>
{
return value != Enums::Data<Enum>::NullValue;
}
};
template<>
struct StringSerializer<QDateTime> {
static QString serialize(const QDateTime &datetime);
static bool hasValue(const QDateTime &datetime) { return datetime.isValid(); }
};
template<>
struct StringSerializer<QUuid> {
static auto serialize(QUuid uuid) { return uuid.toString(QUuid::WithoutBraces); }
static bool hasValue(QUuid uuid) { return !uuid.isNull(); }
};
template<>
struct StringSerializer<QUrl> {
static auto serialize(const QUrl &url) { return url.toString(); }
static bool hasValue(const QUrl &url) { return !url.isEmpty(); }
};
template<>
struct StringSerializer<QMimeType> {
static auto serialize(const QMimeType &mimeType) { return mimeType.name(); }
static bool hasValue(const QMimeType &mimeType) { return mimeType.isValid(); }
};
struct Base64 {
const QByteArray &data;
static Base64 fromByteArray(const QByteArray &d) { return { d }; }
};
template<>
struct StringSerializer<Base64> {
static auto serialize(Base64 value) { return value.data.toBase64(); }
static bool hasValue(Base64 value) { return !value.data.isEmpty(); }
};
struct DefaultedBool {
bool value;
bool defaultValue;
};
template<>
struct StringSerializer<DefaultedBool> {
static auto serialize(auto &&v) { return v.value ? u"true"_s : u"false"_s; }
static bool hasValue(auto &&v) { return v.value != v.defaultValue; }
};
// Serializes value to string and converts to type writeable to QXmlStreamWriter
template<typename T>
decltype(auto) xmlS(T &&value)
{
return toString65(StringSerializer<std::decay_t<T>>::serialize(std::forward<T>(value)));
}
template<typename T>
concept XmlSerializeable = XmlWriterSerializeable<T> || QXmlStreamSerializeable<T>;
template<typename T>
concept XmlSerializeableRange =
std::ranges::range<T> && XmlSerializeable<std::ranges::range_value_t<T>>;
class XmlWriter
{
public:
explicit XmlWriter(QXmlStreamWriter *writer) noexcept : w(writer) { }
operator QXmlStreamWriter *() const noexcept { return w; }
QXmlStreamWriter *writer() const noexcept { return w; }
template<XmlSerializeable T>
void write(T &&value)
{
value.toXml(*this);
}
template<IsStdOptional T>
void write(T &&opt)
{
if (opt) {
write(*opt);
}
}
template<typename Container>
requires(XmlSerializeableRange<Container> && !XmlSerializeable<Container>)
void write(Container &&container)
{
for (const auto &value : container) {
write(value);
}
}
template<std::invocable Function>
void write(Function &&f)
{
f();
}
private:
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
using String = QAnyStringView;
#else
using String = const QString &;
#endif
void writeStartElement(String name) { w->writeStartElement(name); }
QXMPP_PRIVATE_EXPORT void writeStartElement(String name, String xmlns);
void writeEndElement() { w->writeEndElement(); }
void writeEmptyElement(String name) { w->writeEmptyElement(name); }
QXMPP_PRIVATE_EXPORT void writeEmptyElement(String name, String xmlns);
QXMPP_PRIVATE_EXPORT void writeTextOrEmptyElement(String name, String value);
QXMPP_PRIVATE_EXPORT void writeTextOrEmptyElement(String name, String xmlns, String value);
QXMPP_PRIVATE_EXPORT void writeSingleAttributeElement(String name, String attribute, String value);
template<typename Tag, typename... Values>
friend struct Element;
template<IsOptionalStringSerializable Enum>
friend struct OptionalEnumElement;
template<typename Tag, typename Value>
friend struct TextElement;
template<typename Tag, IsOptionalStringSerializable Value>
friend struct OptionalTextElement;
template<std::ranges::range Container>
friend struct SingleAttributeElements;
QXmlStreamWriter *w;
};
template<typename Tag, typename... Values>
struct Element {
Tag tag;
std::tuple<Values...> values;
template<typename... V>
Element(Tag tag, V &&...values)
: tag(std::forward<Tag>(tag)), values(std::forward<V>(values)...)
{
}
void toXml(XmlWriter &w)
{
if constexpr (sizeof...(Values) == 0) {
if constexpr (IsXmlTag<Tag>) {
auto &[name, xmlns] = tag;
w.writeEmptyElement(xmlS(name), xmlS(xmlns));
} else {
w.writeEmptyElement(xmlS(tag));
}
} else {
if constexpr (IsXmlTag<Tag>) {
auto &[name, xmlns] = tag;
w.writeStartElement(xmlS(name), xmlS(xmlns));
} else {
w.writeStartElement(xmlS(tag));
}
std::apply([&w](auto &&...value) { (w.write(value), ...); }, values);
w.writeEndElement();
}
}
};
template<typename Name, typename... Values>
requires(!IsXmlTag<Name>)
Element(Name &&, Values &&...) -> Element<Name, Values...>;
template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename... Values>
Element(Tag &&, Values &&...) -> Element<Tag, Values...>;
template<typename Value>
struct Attribute {
QStringView name;
Value value;
void toXml(XmlWriter &w) const
{
w.writer()->writeAttribute(xmlS(name), xmlS(value));
}
};
template<typename Value>
Attribute(QStringView, Value &&) -> Attribute<Value>;
template<IsOptionalStringSerializable Value>
struct OptionalAttribute {
QStringView name;
Value value;
void toXml(QXmlStreamWriter *w) const
{
if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
w->writeAttribute(xmlS(name), xmlS(value));
}
}
};
template<typename Value>
OptionalAttribute(QStringView, Value &&) -> OptionalAttribute<Value>;
template<typename Value>
struct Characters {
Value value;
template<typename V>
Characters(V &&value) : value(std::forward<V>(value)) { }
void toXml(QXmlStreamWriter *w) const
{
w->writeCharacters(xmlS(value));
}
};
template<typename Value>
Characters(Value &&) -> Characters<Value>;
template<IsOptionalStringSerializable Value>
struct OptionalCharacters {
Value value;
template<typename V>
OptionalCharacters(V &&value) : value(std::forward<V>(value)) { }
void toXml(QXmlStreamWriter *w) const
{
if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
w->writeCharacters(xmlS(value));
}
}
};
template<typename T>
OptionalCharacters(T &&) -> OptionalCharacters<T>;
struct DefaultNamespace {
QStringView xmlns;
void toXml(QXmlStreamWriter *w) const
{
w->writeDefaultNamespace(xmlS(xmlns));
}
};
struct Namespace {
QStringView prefix;
QStringView xmlns;
void toXml(QXmlStreamWriter *w) const
{
w->writeNamespace(xmlS(xmlns), xmlS(prefix));
}
};
template<IsOptionalStringSerializable Enum>
struct OptionalEnumElement {
Enum enumeration;
QStringView xmlns = {};
void toXml(XmlWriter &w) const
{
if (StringSerializer<Enum>::hasValue(enumeration)) {
if (xmlns.isNull()) {
w.writeEmptyElement(xmlS(enumeration));
} else {
w.writeEmptyElement(xmlS(enumeration), xmlS(xmlns));
}
}
}
};
template<IsOptionalStringSerializable Enum>
OptionalEnumElement(Enum) -> OptionalEnumElement<Enum>;
template<IsOptionalStringSerializable Enum, std::convertible_to<QStringView> StringView>
OptionalEnumElement(Enum, StringView) -> OptionalEnumElement<Enum>;
template<typename Tag, typename Value>
struct TextElement {
Tag tag;
Value value;
template<typename V>
TextElement(Tag tag, V &&value) : tag(tag), value(std::forward<V>(value)) { }
void toXml(XmlWriter &w)
{
if constexpr (IsXmlTag<Tag>) {
auto &[name, xmlns] = tag;
w.writeTextOrEmptyElement(xmlS(name), xmlS(xmlns), xmlS(value));
} else {
w.writeTextOrEmptyElement(xmlS(tag), xmlS(value));
}
}
};
template<typename Name, typename Value>
requires(!IsXmlTag<Name>)
TextElement(Name &&, Value &&) -> TextElement<Name, Value>;
template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Value>
TextElement(Tag &&, Value &&) -> TextElement<Tag, Value>;
template<typename Tag, std::ranges::range Range>
struct TextElements {
Tag tag;
Range values;
template<typename R>
TextElements(Tag tag, R &&values) : tag(tag), values(std::forward<R>(values)) { }
void toXml(XmlWriter &w)
{
for (const auto &value : values) {
w.write(TextElement { tag, value });
}
}
};
template<typename Name, typename Range>
requires(!IsXmlTag<Name>)
TextElements(Name &&, Range &&) -> TextElements<Name, Range>;
template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Range>
TextElements(Tag &&, Range &&) -> TextElements<Tag, Range>;
template<typename Tag, IsOptionalStringSerializable Value>
struct OptionalTextElement {
Tag tag;
Value value;
template<typename V>
OptionalTextElement(Tag tag, V &&value) : tag(tag), value(std::forward<V>(value)) { }
void toXml(XmlWriter &w)
{
if (StringSerializer<std::decay_t<Value>>::hasValue(value)) {
if constexpr (IsXmlTag<Tag>) {
auto &[name, xmlns] = tag;
w.writeTextOrEmptyElement(xmlS(name), xmlS(xmlns), xmlS(value));
} else {
w.writeTextOrEmptyElement(xmlS(tag), xmlS(value));
}
}
}
};
template<typename Name, typename Value>
requires(!IsXmlTag<Name>)
OptionalTextElement(Name &&, Value &&) -> OptionalTextElement<Name, Value>;
template<IsXmlTag Tag = Tag<QStringView, QStringView>, typename Value>
OptionalTextElement(Tag, Value &&) -> OptionalTextElement<Tag, Value>;
template<std::ranges::range Range>
struct SingleAttributeElements {
QStringView name;
QStringView attribute;
Range values;
template<typename R>
SingleAttributeElements(QStringView name, QStringView attribute, R &&values)
: name(name), attribute(attribute), values(std::forward<R>(values)) { }
void toXml(XmlWriter &w)
{
for (const auto &value : values) {
w.writeSingleAttributeElement(xmlS(name), xmlS(attribute), xmlS(value));
}
}
};
template<typename Range>
SingleAttributeElements(QStringView, QStringView, Range &&) -> SingleAttributeElements<Range>;
template<typename... Values>
struct OptionalContent {
bool condition;
std::tuple<Values...> values;
template<std::convertible_to<bool> Condition, typename... V>
OptionalContent(Condition condition, V &&...values)
: condition(condition), values(std::forward<V>(values)...) { }
void toXml(XmlWriter &w)
{
if (condition) {
std::apply([&w](auto &&...value) { (w.write(value), ...); }, values);
}
}
};
template<std::convertible_to<bool> Condition, typename... Values>
OptionalContent(Condition, Values &&...) -> OptionalContent<Values...>;
} // namespace QXmpp::Private
#endif // XMLWRITER_H
QXmpp::hasValue
bool hasValue(const Result< T > &r)
Definition QXmppGlobal.h:216