KTextTemplate

filterexpression.cpp
1/*
2 This file is part of the KTextTemplate library
3
4 SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7
8*/
9
10#include "filterexpression.h"
11
12#include <QRegularExpression>
13
14#include "exception.h"
15#include "filter.h"
16#include "parser.h"
17#include "util.h"
18
19using ArgFilter = std::pair<QSharedPointer<KTextTemplate::Filter>, KTextTemplate::Variable>;
20
21namespace KTextTemplate
22{
23
24class FilterExpressionPrivate
25{
26 FilterExpressionPrivate(FilterExpression *fe)
27 : q_ptr(fe)
28 {
29 }
30
31 Variable m_variable;
32 QList<ArgFilter> m_filters;
33 QStringList m_filterNames;
34
35 Q_DECLARE_PUBLIC(FilterExpression)
36 FilterExpression *const q_ptr;
37};
38}
39
40using namespace KTextTemplate;
41
42static const char FILTER_SEPARATOR = '|';
43static const char FILTER_ARGUMENT_SEPARATOR = ':';
44
45static QRegularExpression getFilterRegexp()
46{
47 const QString filterSep(QRegularExpression::escape(QChar::fromLatin1(FILTER_SEPARATOR)));
48 const QString argSep(QRegularExpression::escape(QChar::fromLatin1(FILTER_ARGUMENT_SEPARATOR)));
49
50 const QLatin1String varChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.");
51 const QLatin1String numChars(R"([-+\.]?\d[\d\.e]*)");
52 const QString i18nOpen(QRegularExpression::escape(QStringLiteral("_(")));
53 const QLatin1String doubleQuoteStringLiteral(R"("[^"\\]*(?:\\.[^"\\]*)*")");
54 const QLatin1String singleQuoteStringLiteral(R"('[^'\\]*(?:\\.[^'\\]*)*')");
55 const QString i18nClose(QRegularExpression::escape(QStringLiteral(")")));
56 const QString variable = QLatin1Char('[') + varChars + QStringLiteral("]+");
57
58 const QString localizedExpression = QStringLiteral("(?:") + i18nOpen + doubleQuoteStringLiteral + i18nClose + QLatin1Char('|') + i18nOpen
59 + singleQuoteStringLiteral + i18nClose + QLatin1Char('|') + i18nOpen + numChars + i18nClose + QLatin1Char('|') + i18nOpen + variable + i18nClose
60 + QLatin1Char(')');
61
62 const QString constantString = QStringLiteral("(?:") + doubleQuoteStringLiteral + QLatin1Char('|') + singleQuoteStringLiteral + QLatin1Char(')');
63
64 const QString filterRawString = QLatin1Char('^') + constantString + QLatin1Char('|') + QLatin1Char('^') + localizedExpression + QLatin1Char('|')
65 + QLatin1Char('^') + variable + QLatin1Char('|') + numChars + QLatin1Char('|') + filterSep + QStringLiteral("\\w+|") + argSep + QStringLiteral("(?:")
66 + constantString + QLatin1Char('|') + localizedExpression + QLatin1Char('|') + variable + QLatin1Char('|') + numChars + QLatin1Char('|') + filterSep
67 + QStringLiteral("\\w+)");
68
69 return QRegularExpression(filterRawString);
70}
71
73 : d_ptr(new FilterExpressionPrivate(this))
74{
76
77 auto pos = 0;
78 auto lastPos = 0;
79 int len;
80 QString subString;
81
82 static const auto sFilterRe = getFilterRegexp();
83
84 // This is one fo the few constructors that can throw so we make sure to
85 // delete its d->pointer.
86 try {
87 auto i = sFilterRe.globalMatch(varString);
88 while (i.hasNext()) {
89 auto match = i.next();
90 len = match.capturedLength();
91 pos = match.capturedStart();
92 subString = match.captured();
93 const auto ssSize = subString.size();
94
95 if (pos != lastPos) {
96 throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse some characters: \"%1\"").arg(varString.mid(lastPos, pos)));
97 }
98
99 if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR))) {
100 subString = subString.right(ssSize - 1);
101 auto f = parser->getFilter(subString);
102
103 Q_ASSERT(f);
104
105 d->m_filterNames << subString;
106 d->m_filters << std::make_pair(f, Variable());
107
108 } else if (subString.startsWith(QLatin1Char(FILTER_ARGUMENT_SEPARATOR))) {
109 if (d->m_filters.isEmpty() || d->m_filters.at(d->m_filters.size() - 1).second.isValid()) {
110 const auto remainder = varString.right(varString.size() - lastPos);
111 throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse the remainder, %1 from %2").arg(remainder, varString));
112 }
113 subString = subString.right(ssSize - 1);
114 const auto lastFilter = d->m_filters.size();
115 if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR)))
116 throw KTextTemplate::Exception(EmptyVariableError, QStringLiteral("Missing argument to filter: %1").arg(d->m_filterNames[lastFilter - 1]));
117
118 d->m_filters[lastFilter - 1].second = Variable(subString);
119 } else {
120 // Token is _("translated"), or "constant", or a variable;
121 d->m_variable = Variable(subString);
122 }
123
124 pos += len;
125 lastPos = pos;
126 }
127
128 const auto remainder = varString.right(varString.size() - lastPos);
129 if (!remainder.isEmpty()) {
130 throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse the remainder, %1 from %2").arg(remainder, varString));
131 }
132 } catch (...) {
133 delete d_ptr;
134 throw;
135 }
136}
137
139 : d_ptr(new FilterExpressionPrivate(this))
140{
141 *this = other;
142}
143
145 : d_ptr(new FilterExpressionPrivate(this))
146{
147}
148
150{
151 Q_D(const FilterExpression);
152 return d->m_variable.isValid();
153}
154
156{
157 delete d_ptr;
158}
159
161{
162 Q_D(const FilterExpression);
163 return d->m_variable;
164}
165
167{
168 if (&other == this)
169 return *this;
170 d_ptr->m_variable = other.d_ptr->m_variable;
171 d_ptr->m_filters = other.d_ptr->m_filters;
172 d_ptr->m_filterNames = other.d_ptr->m_filterNames;
173 return *this;
174}
175
177{
178 Q_D(const FilterExpression);
179 auto var = d->m_variable.resolve(c);
180
181 auto it = d->m_filters.constBegin();
182 const auto end = d->m_filters.constEnd();
183 for (; it != end; ++it) {
184 auto filter = it->first;
185 filter->setStream(stream);
186 const auto argVar = it->second;
187 auto arg = argVar.resolve(c);
188
189 if (arg.isValid()) {
191 if (arg.userType() == qMetaTypeId<KTextTemplate::SafeString>()) {
192 argString = arg.value<KTextTemplate::SafeString>();
193 } else if (arg.userType() == qMetaTypeId<QString>()) {
194 argString = KTextTemplate::SafeString(arg.value<QString>());
195 }
196 if (argVar.isConstant()) {
197 argString = markSafe(argString);
198 }
199 if (!argString.get().isEmpty()) {
200 arg = argString;
201 }
202 }
203
204 const auto varString = getSafeString(var);
205
206 var = filter->doFilter(var, arg, c->autoEscape());
207
208 if (var.userType() == qMetaTypeId<KTextTemplate::SafeString>() || var.userType() == qMetaTypeId<QString>()) {
209 if (filter->isSafe() && varString.isSafe()) {
210 var = markSafe(getSafeString(var));
211 } else if (varString.needsEscape()) {
212 var = markForEscaping(getSafeString(var));
213 } else {
214 var = getSafeString(var);
215 }
216 }
217 }
218 (*stream) << getSafeString(var).get();
219 return var;
220}
221
223{
224 OutputStream _dummy;
225 return resolve(&_dummy, c);
226}
227
228QVariantList FilterExpression::toList(Context *c) const
229{
230 const auto var = resolve(c);
231 if (!var.canConvert<QVariantList>())
232 return {};
233 return var.value<QVariantList>();
234}
235
237{
238 return variantIsTrue(resolve(c));
239}
240
241QStringList FilterExpression::filters() const
242{
243 Q_D(const FilterExpression);
244 return d->m_filterNames;
245}
The Context class holds the context to render a Template with.
Definition context.h:107
An exception for use when implementing template tags.
Definition exception.h:74
A FilterExpression object represents a filter expression in a template.
QVariantList toList(Context *c) const
Returns a list for the FilterExpression.
bool isTrue(Context *c) const
Returns whether the Filter resolves to true in the Context c.
QVariant resolve(OutputStream *stream, Context *c) const
Resolves the FilterExpression in the Context c and writes it to the stream stream.
Variable variable() const
Returns the initial variable in the FilterExpression.
FilterExpression & operator=(const FilterExpression &other)
Assignment operator.
bool isValid() const
Returns whether a filter expression is valid.
FilterExpression()
Constructs an invalid FilterExpression.
The OutputStream class is used to render templates to a QTextStream.
The Parser class processes a string template into a tree of nodes.
Definition parser.h:38
QSharedPointer< Filter > getFilter(const QString &name) const
Returns the filter object called name or an invalid object if no filter with that name is loaded.
Definition parser.cpp:138
A QString wrapper class for containing whether a string is safe or needs to be escaped.
Definition safestring.h:81
const NestedString & get() const
Returns the String held by this SafeString
Definition safestring.h:283
A container for static variables defined in Templates.
Definition variable.h:42
The KTextTemplate namespace holds all public KTextTemplate API.
Definition Mainpage.dox:8
KTextTemplate::SafeString markForEscaping(const KTextTemplate::SafeString &input)
Marks the input as requiring escaping.
Definition util.cpp:81
KTextTemplate::SafeString getSafeString(const QVariant &input)
Retrieves and returns a SafeString from the input.
Definition util.cpp:91
bool variantIsTrue(const QVariant &variant)
Returns whether the variant is evaluated to true.
Definition util.cpp:24
KTextTemplate::SafeString markSafe(const KTextTemplate::SafeString &input)
Marks the input as safe.
Definition util.cpp:74
QChar fromLatin1(char c)
QString escape(QStringView str)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString right(qsizetype n) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
Q_D(Todo)
Utility functions used throughout KTextTemplate.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Apr 27 2024 22:14:09 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.