From bbb01309b443054724713c2d72826535edc554d5 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Thu, 8 Dec 2022 19:20:48 +0100 Subject: [PATCH] QXmlStreamWriter: prepare for port to QAnyStringView The UTF-8 support in Qt is still lacking, so QUtf8StringView doesn't, yet, have the likes of contains(), endsWith(), etc that the existing QString code uses in Q_ASSERTs. Provide free functions that work for UTF-8 haystacks and ASCII needles by falling back to QByteArrayView or QLatin1StringView. Also break a replace() use into a series of indexOf() + chunked write(). This is rather expensive for QString, so port the writeCDATA() function that uses this to QAnyStringView already, ahead of the bulk of the changes in Mate's follow-up patch. Task-number: QTBUG-103302 Change-Id: Ic66261740817ede2600b78a383cf667a31df7bfc Reviewed-by: Fabian Kosmale Reviewed-by: Marc Mutz Reviewed-by: Ivan Solovev Reviewed-by: Tatiana Borisova --- src/corelib/compat/removed_api.cpp | 11 +++ src/corelib/serialization/qxmlstream.cpp | 92 ++++++++++++++++++++---- src/corelib/serialization/qxmlstream.h | 3 + 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index aad7b58b361..62dc063072f 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -368,6 +368,17 @@ void QXmlStreamReader::addData(const char *data) #endif // QT_CONFIG(xmlstreamreader) +#if QT_CONFIG(xmlstreamwriter) + +#include "qxmlstream.h" + +void QXmlStreamWriter::writeCDATA(const QString &text) +{ + writeCDATA(qToAnyStringViewIgnoringNull(text)); +} + +#endif // QT_CONFIG(xmlstreamwriter) + // inlined API #include "qfloat16.h" #include "qstring.h" diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp index fb9317849c8..792374a95a1 100644 --- a/src/corelib/serialization/qxmlstream.cpp +++ b/src/corelib/serialization/qxmlstream.cpp @@ -44,8 +44,65 @@ auto reversed(Range &r) template void reversed(const Range &&) = delete; + +// implementation of missing QUtf8StringView methods for ASCII-only needles: +auto transform(QLatin1StringView haystack, char needle) +{ + struct R { QLatin1StringView haystack; char16_t needle; }; + return R{haystack, uchar(needle)}; } +auto transform(QStringView haystack, char needle) +{ + struct R { QStringView haystack; char16_t needle; }; + return R{haystack, uchar(needle)}; +} + +auto transform(QUtf8StringView haystack, char needle) +{ + struct R { QByteArrayView haystack; char needle; }; + return R{haystack, needle}; +} + +[[maybe_unused]] +auto transform(QLatin1StringView haystack, QLatin1StringView needle) +{ + struct R { QLatin1StringView haystack; QLatin1StringView needle; }; + return R{haystack, needle}; +} + +[[maybe_unused]] +auto transform(QStringView haystack, QLatin1StringView needle) +{ + struct R { QStringView haystack; QLatin1StringView needle; }; + return R{haystack, needle}; +} + +[[maybe_unused]] +auto transform(QUtf8StringView haystack, QLatin1StringView needle) +{ + struct R { QLatin1StringView haystack; QLatin1StringView needle; }; + return R{QLatin1StringView{QByteArrayView{haystack}}, needle}; +} + +#define WRAP(method, Needle) \ + auto method (QAnyStringView s, Needle needle) noexcept \ + { \ + return s.visit([needle](auto s) { \ + auto r = transform(s, needle); \ + return r.haystack. method (r.needle); \ + }); \ + } \ + /*end*/ + +WRAP(count, char) +WRAP(contains, char) +WRAP(contains, QLatin1StringView) +WRAP(endsWith, char) +WRAP(indexOf, QLatin1StringView) + +} // unnamed namespace + /*! \enum QXmlStreamReader::TokenType @@ -3216,7 +3273,7 @@ void QXmlStreamWriter::writeAttribute(const QString &qualifiedName, const QStrin { Q_D(QXmlStreamWriter); Q_ASSERT(d->inStartElement); - Q_ASSERT(qualifiedName.count(u':') <= 1); + Q_ASSERT(count(qualifiedName, ':') <= 1); d->write(" "); d->write(qualifiedName); d->write("=\""); @@ -3236,7 +3293,7 @@ void QXmlStreamWriter::writeAttribute(const QString &namespaceUri, const QString { Q_D(QXmlStreamWriter); Q_ASSERT(d->inStartElement); - Q_ASSERT(!name.contains(u':')); + Q_ASSERT(!contains(name, ':')); QXmlStreamWriterPrivate::NamespaceDeclaration &namespaceDeclaration = d->findNamespace(namespaceUri, true, true); d->write(" "); if (!namespaceDeclaration.prefix.isEmpty()) { @@ -3295,15 +3352,26 @@ void QXmlStreamWriter::writeAttributes(const QXmlStreamAttributes& attributes) This function mainly exists for completeness. Normally you should not need use it, because writeCharacters() automatically escapes all non-content characters. + + \note In Qt versions prior to 6.5, this function took QString, not + QAnyStringView. */ -void QXmlStreamWriter::writeCDATA(const QString &text) +void QXmlStreamWriter::writeCDATA(QAnyStringView text) { Q_D(QXmlStreamWriter); d->finishStartElement(); - QString copy(text); - copy.replace("]]>"_L1, "]]]]>"_L1); d->write("write(copy); + while (!text.isEmpty()) { + const auto idx = indexOf(text, "]]>"_L1); + if (idx < 0) + break; // no forbidden sequence found + d->write(text.first(idx)); + d->write("]]" // text[idx, idx + 2) + "]]> + ">"); // text[idx + 2, idx + 3) + text = text.sliced(idx + 3); // skip over "]]>" + } + d->write(text); // write remainder d->write("]]>"); } @@ -3329,7 +3397,7 @@ void QXmlStreamWriter::writeCharacters(const QString &text) void QXmlStreamWriter::writeComment(const QString &text) { Q_D(QXmlStreamWriter); - Q_ASSERT(!text.contains("--"_L1) && !text.endsWith(u'-')); + Q_ASSERT(!contains(text, "--"_L1) && !endsWith(text, '-')); if (!d->finishStartElement(false) && d->autoFormatting) d->indent(d->tagStack.size()); d->write("