From 02879565a60abf89428656502c84f71d77895e14 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 5 Feb 2025 12:08:16 +1000 Subject: [PATCH] More fixes for use of inline data for SVG content Followup https://github.com/qgis/QGIS/pull/59589 Fixes #60427 --- .../auto_additions/qgsabstractcontentcache.py | 1 + .../qgsabstractcontentcache.sip.in | 17 ++++++++++++++ .../auto_additions/qgsabstractcontentcache.py | 1 + .../qgsabstractcontentcache.sip.in | 17 ++++++++++++++ src/core/qgsabstractcontentcache.cpp | 16 +++++++++++++ src/core/qgsabstractcontentcache.h | 17 ++++++++++++++ src/core/qgsabstractcontentcache_p.h | 6 +++++ tests/src/core/testqgssvgcache.cpp | 1 + tests/src/python/test_qgssvgcache.py | 23 +++++++++++++++++++ 9 files changed, 99 insertions(+) diff --git a/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py b/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py index bf5b43ab27ff..bcc41b6ff594 100644 --- a/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py +++ b/python/PyQt6/core/auto_additions/qgsabstractcontentcache.py @@ -6,6 +6,7 @@ try: QgsAbstractContentCacheBase.__attribute_docs__ = {'remoteContentFetched': 'Emitted when the cache has finished retrieving content from a remote ``url``.\n'} QgsAbstractContentCacheBase.parseBase64DataUrl = staticmethod(QgsAbstractContentCacheBase.parseBase64DataUrl) + QgsAbstractContentCacheBase.parseEmbeddedStringData = staticmethod(QgsAbstractContentCacheBase.parseEmbeddedStringData) QgsAbstractContentCacheBase.isBase64Data = staticmethod(QgsAbstractContentCacheBase.isBase64Data) QgsAbstractContentCacheBase.__signal_arguments__ = {'remoteContentFetched': ['url: str']} except (NameError, AttributeError): diff --git a/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in b/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in index d65dc29a2c09..2d5694ae3f06 100644 --- a/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in +++ b/python/PyQt6/core/auto_generated/qgsabstractcontentcache.sip.in @@ -108,6 +108,23 @@ Data URLs are of the form ``data:[;]base64,``. .. versionadded:: 3.40 %End + + static bool parseEmbeddedStringData( const QString &path, QString *mimeType /Out/ = 0, QString *data /Out/ = 0 ); +%Docstring +Parses a ``path`` to determine if it represents a embedded string data, and if so, extracts the components +of the URL. + +Data URLs are of the form ``data:[;]utf8,``. + +:param path: path to test + +:return: - ``True`` if ``path`` is an embedded string data URL + - mimeType: the extracted mime type if the ``path`` is a data URL + - data: the extracted string data if the ``path`` is a data URL + +.. versionadded:: 3.42 +%End + static bool isBase64Data( const QString &path ); %Docstring Returns ``True`` if ``path`` represents base64 encoded data. diff --git a/python/core/auto_additions/qgsabstractcontentcache.py b/python/core/auto_additions/qgsabstractcontentcache.py index bf5b43ab27ff..bcc41b6ff594 100644 --- a/python/core/auto_additions/qgsabstractcontentcache.py +++ b/python/core/auto_additions/qgsabstractcontentcache.py @@ -6,6 +6,7 @@ try: QgsAbstractContentCacheBase.__attribute_docs__ = {'remoteContentFetched': 'Emitted when the cache has finished retrieving content from a remote ``url``.\n'} QgsAbstractContentCacheBase.parseBase64DataUrl = staticmethod(QgsAbstractContentCacheBase.parseBase64DataUrl) + QgsAbstractContentCacheBase.parseEmbeddedStringData = staticmethod(QgsAbstractContentCacheBase.parseEmbeddedStringData) QgsAbstractContentCacheBase.isBase64Data = staticmethod(QgsAbstractContentCacheBase.isBase64Data) QgsAbstractContentCacheBase.__signal_arguments__ = {'remoteContentFetched': ['url: str']} except (NameError, AttributeError): diff --git a/python/core/auto_generated/qgsabstractcontentcache.sip.in b/python/core/auto_generated/qgsabstractcontentcache.sip.in index d65dc29a2c09..2d5694ae3f06 100644 --- a/python/core/auto_generated/qgsabstractcontentcache.sip.in +++ b/python/core/auto_generated/qgsabstractcontentcache.sip.in @@ -108,6 +108,23 @@ Data URLs are of the form ``data:[;]base64,``. .. versionadded:: 3.40 %End + + static bool parseEmbeddedStringData( const QString &path, QString *mimeType /Out/ = 0, QString *data /Out/ = 0 ); +%Docstring +Parses a ``path`` to determine if it represents a embedded string data, and if so, extracts the components +of the URL. + +Data URLs are of the form ``data:[;]utf8,``. + +:param path: path to test + +:return: - ``True`` if ``path`` is an embedded string data URL + - mimeType: the extracted mime type if the ``path`` is a data URL + - data: the extracted string data if the ``path`` is a data URL + +.. versionadded:: 3.42 +%End + static bool isBase64Data( const QString &path ); %Docstring Returns ``True`` if ``path`` represents base64 encoded data. diff --git a/src/core/qgsabstractcontentcache.cpp b/src/core/qgsabstractcontentcache.cpp index 3f3ecad6bf77..3be9976957e9 100644 --- a/src/core/qgsabstractcontentcache.cpp +++ b/src/core/qgsabstractcontentcache.cpp @@ -71,6 +71,22 @@ bool QgsAbstractContentCacheBase::parseBase64DataUrl( const QString &path, QStri return true; } +bool QgsAbstractContentCacheBase::parseEmbeddedStringData( const QString &path, QString *mimeType, QString *data ) +{ + const thread_local QRegularExpression sRx( QStringLiteral( "^data:([a-zA-Z0-9+\\-]*\\/[a-zA-Z0-9+\\-]*?)\\;utf8,(.*)$" ), QRegularExpression::DotMatchesEverythingOption ); + const QRegularExpressionMatch stringMatch = sRx.match( path ); + + if ( !stringMatch.hasMatch() ) + return false; + + if ( mimeType ) + *mimeType = stringMatch.captured( 1 ); + if ( data ) + *data = stringMatch.captured( 2 ); + + return true; +} + bool QgsAbstractContentCacheBase::isBase64Data( const QString &path ) { return path.startsWith( QLatin1String( "base64:" ) ) diff --git a/src/core/qgsabstractcontentcache.h b/src/core/qgsabstractcontentcache.h index 76a0f8ee8559..0a728ce6dfae 100644 --- a/src/core/qgsabstractcontentcache.h +++ b/src/core/qgsabstractcontentcache.h @@ -156,6 +156,23 @@ class CORE_EXPORT QgsAbstractContentCacheBase: public QObject */ static bool parseBase64DataUrl( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr ); + + /** + * Parses a \a path to determine if it represents a embedded string data, and if so, extracts the components + * of the URL. + * + * Data URLs are of the form ``data:[;]utf8,``. + * + * \param path path to test + * \param mimeType will be set to the extracted mime type if the \a path is a data URL + * \param data will be set to the extracted string data if the \a path is a data URL + * + * \returns TRUE if \a path is an embedded string data URL + * + * \since QGIS 3.42 + */ + static bool parseEmbeddedStringData( const QString &path, QString *mimeType SIP_OUT = nullptr, QString *data SIP_OUT = nullptr ); + /** * Returns TRUE if \a path represents base64 encoded data. * diff --git a/src/core/qgsabstractcontentcache_p.h b/src/core/qgsabstractcontentcache_p.h index b0d38f7b040f..78dff4d56357 100644 --- a/src/core/qgsabstractcontentcache_p.h +++ b/src/core/qgsabstractcontentcache_p.h @@ -53,6 +53,12 @@ QByteArray QgsAbstractContentCache::getContent( const QString &path, const QB { return QByteArray::fromBase64( base64String.toLocal8Bit(), QByteArray::OmitTrailingEquals ); } + // maybe embedded string data + QString embeddedString; + if ( parseEmbeddedStringData( path, nullptr, &embeddedString ) ) + { + return embeddedString.toUtf8(); + } } // maybe it's a url... diff --git a/tests/src/core/testqgssvgcache.cpp b/tests/src/core/testqgssvgcache.cpp index f7b96e7fa4f8..488ba1ebe544 100644 --- a/tests/src/core/testqgssvgcache.cpp +++ b/tests/src/core/testqgssvgcache.cpp @@ -433,5 +433,6 @@ bool TestQgsSvgCache::imageCheck( const QString &testName, QImage &image, int mi mReport += checker.report(); return resultFlag; } + QGSTEST_MAIN( TestQgsSvgCache ) #include "testqgssvgcache.moc" diff --git a/tests/src/python/test_qgssvgcache.py b/tests/src/python/test_qgssvgcache.py index 53f91fd8e05e..9164e3fd4b8d 100644 --- a/tests/src/python/test_qgssvgcache.py +++ b/tests/src/python/test_qgssvgcache.py @@ -333,6 +333,29 @@ def test_inline_svg(self): ) ) + def test_inline_svg_no_block(self): + # note, this is different content to test_inline_svg, we don't want to retrieve a cached version! + inline_svg = """data:image/svg+xml;utf8,""" + image, in_cache = QgsApplication.svgCache().svgAsImage( + inline_svg, + 100, + fill=QColor(0, 0, 0), + stroke=QColor(0, 0, 0), + strokeWidth=0.1, + widthScaleFactor=1, + blocking=False, + ) + self.assertTrue( + self.image_check( + "Inline svg", + "inline_svg", + image, + color_tolerance=2, + allowed_mismatch=20, + use_checkerboard_background=True, + ) + ) + if __name__ == "__main__": unittest.main()