[frameworks/ktexttemplate] src/lib: Make Context accessible to filters

Volker Krause <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit c770482430b3c7f7979ad416598393bea4fef406 by Volker Krause.
Committed on 16/08/2026 at 10:28.
Pushed by vkrause into branch 'master'.

Make Context accessible to filters

We need this for implementing proper locale-aware filters, e.g. for
date/time formatting.

This is a bit messy to retrofit in a binary-compatible way unfortunately,
as the memory we repurpose as the d pointer has previously not been
initialized, and the Filter ctor has been inline. So it's not enough we
initialize it to null now, existing consumer code will create it
uninitialized still. Instead we initialize this after construction, when
we first get our hands on a new Filter instance.

This only works because Filter is an extension point for the library
rather than something that consumers can use themselves (which is also
why this worked so far with an uninitialized pointer member).

This is also why the theoretical source incompatible change of ensuring
that Filter cannot be copied is ok, it makes no sense to do that on the
consumer side. Not to mention the slicing risk when copying a polymorphic
value.

The next ugly part here is that we cannot pass Context to doFilter without
breaking ABI, so it's set before the call and accessible via a member
function during the call. That obviously wont work in a multi-threading
scenario with filters shared in multiple engines, but that problem is
pre-existing with OutputStream being supplied in the same way, so we are
not making things worse here.

M  +3    -0    src/lib/engine_p.h
M  +42   -8    src/lib/filter.cpp
M  +33   -6    src/lib/filter.h
M  +2    -0    src/lib/filterexpression.cpp
M  +5    -4    src/lib/parser.cpp

https://invent.kde.org/frameworks/ktexttemplate/-/commit/c770482430b3c7f7979ad416598393bea4fef406

diff --git a/src/lib/engine_p.h b/src/lib/engine_p.h
index 03b7cfc2..299089f7 100644
--- a/src/lib/engine_p.h
+++ b/src/lib/engine_p.h
@@ -36,6 +36,9 @@ public:
 
     void setFilters(const QHash<QString, Filter *> &filters)
     {
+        for (const auto filter : filters) {
+            filter->forgottenBaseCtorRemoveInKF7();
+        }
         m_filters = filters;
     }
 
diff --git a/src/lib/filter.cpp b/src/lib/filter.cpp
index 050a9c19..015e1df0 100644
--- a/src/lib/filter.cpp
+++ b/src/lib/filter.cpp
@@ -11,29 +11,50 @@
 
 using namespace KTextTemplate;
 
-Filter::~Filter() = default;
+class KTextTemplate::FilterPrivate
+{
+public:
+    OutputStream *m_stream = nullptr;
+    Context *m_context = nullptr;
+};
+
+Filter::Filter() = default;
+Filter::~Filter()
+{
+    delete d_ptr;
+}
+
+void Filter::forgottenBaseCtorRemoveInKF7()
+{
+    d_ptr = nullptr;
+}
 
 void Filter::setStream(KTextTemplate::OutputStream *stream)
 {
-    m_stream = stream;
+    if (!d_ptr) {
+        d_ptr = new FilterPrivate;
+    }
+    d_ptr->m_stream = stream;
 }
 
 SafeString Filter::escape(const QString &input) const
 {
-    return m_stream->escape(input);
+    return d_ptr->m_stream->escape(input);
 }
 
 SafeString Filter::escape(const SafeString &input) const
 {
-    if (input.isSafe())
-        return {m_stream->escape(input), SafeString::IsSafe};
-    return m_stream->escape(input);
+    if (input.isSafe()) {
+        return {d_ptr->m_stream->escape(input), SafeString::IsSafe};
+    }
+    return d_ptr->m_stream->escape(input);
 }
 
 SafeString Filter::conditionalEscape(const SafeString &input) const
 {
-    if (!input.isSafe())
-        return m_stream->escape(input);
+    if (!input.isSafe()) {
+        return d_ptr->m_stream->escape(input);
+    }
     return input;
 }
 
@@ -41,3 +62,16 @@ bool Filter::isSafe() const
 {
     return false;
 }
+
+Context *Filter::context() const
+{
+    return d_ptr ? d_ptr->m_context : nullptr;
+}
+
+void Filter::setContext(KTextTemplate::Context *context)
+{
+    if (!d_ptr) {
+        d_ptr = new FilterPrivate;
+    }
+    d_ptr->m_context = context;
+}
diff --git a/src/lib/filter.h b/src/lib/filter.h
index 4d9bdddf..b0666f9a 100644
--- a/src/lib/filter.h
+++ b/src/lib/filter.h
@@ -12,6 +12,7 @@
 #ifndef KTEXTTEMPLATE_FILTER_H
 #define KTEXTTEMPLATE_FILTER_H
 
+#include "context.h"
 #include "ktexttemplate_export.h"
 #include "outputstream.h"
 #include "safestring.h"
@@ -23,6 +24,10 @@
 namespace KTextTemplate
 {
 
+class FilterExpression;
+class FilterPrivate;
+class ScriptableLibraryContainer;
+
 /*!
   \class KTextTemplate::Filter
   \inheaderfile KTextTemplate/Filter
@@ -46,6 +51,7 @@ namespace KTextTemplate
 class KTEXTTEMPLATE_EXPORT Filter
 {
 public:
+    explicit Filter();
     virtual ~Filter();
 
     /*!
@@ -58,35 +64,56 @@ public:
       Escapes and returns \a input. The OutputStream::escape method is used to
       escape \a input.
     */
-    SafeString escape(const QString &input) const;
+    [[nodiscard]] SafeString escape(const QString &input) const;
 
     /*!
       Escapes and returns \a input. The OutputStream::escape method is used to
       escape \a input.
     */
-    SafeString escape(const SafeString &input) const;
+    [[nodiscard]] SafeString escape(const SafeString &input) const;
 
     /*!
       Escapes \a input if not already safe from further escaping and returns it.
       The OutputStream::escape method is used to escape \a input.
     */
-    SafeString conditionalEscape(const SafeString &input) const;
+    [[nodiscard]] SafeString conditionalEscape(const SafeString &input) const;
 
+    // TODO KF7: pass context as an argument to doFilter, rather than holding it temporarily as a member
     /*!
       Reimplement to filter \a input given \a argument.
 
       \a autoescape determines whether the autoescape feature is currently on or
       off. Most filters will not use this.
     */
-    virtual QVariant doFilter(const QVariant &input, const QVariant &argument = {}, bool autoescape = {}) const = 0;
+    [[nodiscard]] virtual QVariant doFilter(const QVariant &input, const QVariant &argument = {}, bool autoescape = {}) const = 0;
 
     /*!
       Reimplement to return whether this filter is safe.
     */
-    virtual bool isSafe() const;
+    [[nodiscard]] virtual bool isSafe() const;
+
+    /*!
+       The context in which the filter is evaluated.
+       \warning Calling this is only valid from within doFilter itself!
+    */
+    [[nodiscard]] Context *context() const;
 
 private:
-    OutputStream *m_stream;
+    Q_DISABLE_COPY(Filter)
+    // TODO KF7 remove this
+    // this is needed as prior to KF 6.29 what became d below was uninitialized
+    // and the default ctor was inline, ie. older external subclasses would leave us
+    // with an uninitialized member variable here
+    friend class ParserPrivate;
+    friend class ScriptableLibraryContainer;
+    KTEXTTEMPLATE_NO_EXPORT void forgottenBaseCtorRemoveInKF7();
+
+    // TODO KF7 remove this if Context becomes an argument to doFilter
+    friend class FilterExpression;
+    KTEXTTEMPLATE_NO_EXPORT void setContext(Context *context);
+
+    // can become a std::unique_ptr in KF7, but not before due to the above issue
+    FilterPrivate *d_ptr = nullptr;
 };
 }
 
diff --git a/src/lib/filterexpression.cpp b/src/lib/filterexpression.cpp
index cac8923e..7b499d9d 100644
--- a/src/lib/filterexpression.cpp
+++ b/src/lib/filterexpression.cpp
@@ -203,7 +203,9 @@ QVariant FilterExpression::resolve(OutputStream *stream, Context *c) const
 
         const auto varString = getSafeString(var);
 
+        filter->setContext(c);
         var = filter->doFilter(var, arg, c->autoEscape());
+        filter->setContext(nullptr);
 
         if (var.userType() == qMetaTypeId<KTextTemplate::SafeString>() || var.userType() == qMetaTypeId<QString>()) {
             if (filter->isSafe() && varString.isSafe()) {
diff --git a/src/lib/parser.cpp b/src/lib/parser.cpp
index 8ece7d4b..ba54531b 100644
--- a/src/lib/parser.cpp
+++ b/src/lib/parser.cpp
@@ -65,10 +65,11 @@ void ParserPrivate::openLibrary(TagLibraryInterface *library)
         nodeIt.value()->setEngine(engine);
         m_nodeFactories.insert(nodeIt.key(), nodeIt.value());
     }
-    auto filters = library->filters();
-    for (auto filterIt = filters.begin(), filterEnd = filters.end(); filterIt != filterEnd; ++filterIt) {
-        auto f = QSharedPointer<Filter>(filterIt.value());
-        m_filters.insert(filterIt.key(), f);
+    const auto filters = library->filters();
+    for (const auto filterIt : filters.asKeyValueRange()) {
+        filterIt.second->forgottenBaseCtorRemoveInKF7();
+        auto f = QSharedPointer<Filter>(filterIt.second);
+        m_filters.insert(filterIt.first, f);
     }
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.