Cutelee 6.1.0
filterexpression.cpp
1/*
2 This file is part of the Cutelee template system.
3
4 Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either version
9 2.1 of the Licence, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library. If not, see <http://www.gnu.org/licenses/>.
18
19*/
20
21#include "filterexpression.h"
22
23#include <QtCore/QRegularExpression>
24
25#include "exception.h"
26#include "filter.h"
27#include "metatype.h"
28#include "parser.h"
29#include "util.h"
30
31using ArgFilter = std::pair<std::shared_ptr<Cutelee::Filter>, Cutelee::Variable>;
32
33namespace Cutelee
34{
35
37{
39
40 Variable m_variable;
41 std::vector<ArgFilter> m_filters;
42 QStringList m_filterNames;
43
44 Q_DECLARE_PUBLIC(FilterExpression)
45 FilterExpression *const q_ptr;
46};
47}
48
49using namespace Cutelee;
50
51static const char FILTER_SEPARATOR = '|';
52static const char FILTER_ARGUMENT_SEPARATOR = ':';
53
54static QRegularExpression getFilterRegexp()
55{
56 const QString filterSep(
57 QRegularExpression::escape(QChar::fromLatin1(FILTER_SEPARATOR)));
58 const QString argSep(
59 QRegularExpression::escape(QChar::fromLatin1(FILTER_ARGUMENT_SEPARATOR)));
60
61 const QLatin1String varChars(
62 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.");
63 const QLatin1String numChars("[-+\\.]?\\d[\\d\\.e]*");
64 const QString i18nOpen(QRegularExpression::escape(QStringLiteral("_(")));
65 const QLatin1String doubleQuoteStringLiteral(
66 "\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"");
67 const QLatin1String singleQuoteStringLiteral(
68 "\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'");
69 const QString i18nClose(QRegularExpression::escape(QStringLiteral(")")));
70 const QString variable = QLatin1Char('[') + varChars + QStringLiteral("]+");
71
72 const QString localizedExpression
73 = QStringLiteral("(?:") + i18nOpen + doubleQuoteStringLiteral + i18nClose
74 + QLatin1Char('|') + i18nOpen + singleQuoteStringLiteral + i18nClose
75 + QLatin1Char('|') + i18nOpen + numChars + i18nClose + QLatin1Char('|')
76 + i18nOpen + variable + i18nClose + QLatin1Char(')');
77
78 const QString constantString = QStringLiteral("(?:")
79 + doubleQuoteStringLiteral + QLatin1Char('|')
80 + singleQuoteStringLiteral + QLatin1Char(')');
81
82 const QString filterRawString
83 = QLatin1Char('^') + constantString + QLatin1Char('|') + QLatin1Char('^')
84 + localizedExpression + QLatin1Char('|') + QLatin1Char('^') + variable
85 + QLatin1Char('|') + numChars + QLatin1Char('|') + filterSep
86 + QStringLiteral("\\w+|") + argSep + QStringLiteral("(?:")
87 + constantString + QLatin1Char('|') + localizedExpression
88 + QLatin1Char('|') + variable + QLatin1Char('|') + numChars
89 + QLatin1Char('|') + filterSep + QStringLiteral("\\w+)");
90
91 return QRegularExpression(filterRawString);
92}
93
95 : d_ptr(new FilterExpressionPrivate(this))
96{
98
99 auto pos = 0;
100 auto lastPos = 0;
101 int len;
102 QString subString;
103
104 auto vs = varString;
105
106 static const auto sFilterRe = getFilterRegexp();
107
108 // This is one fo the few constructors that can throw so we make sure to
109 // delete its d->pointer.
110 try {
111 auto i = sFilterRe.globalMatch(vs);
112 while (i.hasNext()) {
113 auto match = i.next();
114 len = match.capturedLength();
115 pos = match.capturedStart();
116 subString = match.captured();
117 const auto ssSize = subString.size();
118
119 if (pos != lastPos) {
120 throw Cutelee::Exception(
121 TagSyntaxError,
122 QStringLiteral("Could not parse some characters: \"%1\"")
123 .arg(vs.mid(lastPos, pos)));
124 }
125
126 if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR))) {
127 subString = subString.right(ssSize - 1);
128 auto f = parser->getFilter(subString);
129
130 Q_ASSERT(f);
131
132 d->m_filterNames << subString;
133 d->m_filters.push_back({f, Variable()});
134
135 } else if (subString.startsWith(QLatin1Char(FILTER_ARGUMENT_SEPARATOR))) {
136 if (d->m_filters.empty()
137 || d->m_filters.at(d->m_filters.size() - 1).second.isValid()) {
138 const auto remainder = vs.right(vs.size() - lastPos);
139 throw Cutelee::Exception(
140 TagSyntaxError,
141 QStringLiteral("Could not parse the remainder, %1 from %2")
142 .arg(remainder, varString));
143 }
144 subString = subString.right(ssSize - 1);
145 const auto lastFilter = d->m_filters.size();
146 if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR)))
147 throw Cutelee::Exception(
148 EmptyVariableError,
149 QStringLiteral("Missing argument to filter: %1")
150 .arg(d->m_filterNames[lastFilter - 1]));
151
152 d->m_filters[lastFilter - 1].second = Variable(subString);
153 } else {
154 // Token is _("translated"), or "constant", or a variable;
155 d->m_variable = Variable(subString);
156 }
157
158 pos += len;
159 lastPos = pos;
160 }
161
162 const auto remainder = vs.right(vs.size() - lastPos);
163 if (!remainder.isEmpty()) {
164 throw Cutelee::Exception(
165 TagSyntaxError,
166 QStringLiteral("Could not parse the remainder, %1 from %2")
167 .arg(remainder, varString));
168 }
169 } catch (...) {
170 delete d_ptr;
171 throw;
172 }
173}
174
176 : d_ptr(new FilterExpressionPrivate(this))
177{
178 *this = other;
179}
180
184
186{
187 Q_D(const FilterExpression);
188 return d->m_variable.isValid();
189}
190
192
194{
195 Q_D(const FilterExpression);
196 return d->m_variable;
197}
198
200{
201 if (&other == this)
202 return *this;
203 d_ptr->m_variable = other.d_ptr->m_variable;
204 d_ptr->m_filters = other.d_ptr->m_filters;
205 d_ptr->m_filterNames = other.d_ptr->m_filterNames;
206 return *this;
207}
208
210{
211 Q_D(const FilterExpression);
212 auto var = d->m_variable.resolve(c);
213
214 for (const auto &filterPair : d->m_filters) {
215 auto filter = filterPair.first;
216 filter->setStream(stream);
217 const auto argVar = filterPair.second;
218 auto arg = argVar.resolve(c);
219
220 if (arg.isValid()) {
221 Cutelee::SafeString argString;
222 if (arg.userType() == qMetaTypeId<Cutelee::SafeString>()) {
223 argString = arg.value<Cutelee::SafeString>();
224 } else if (arg.userType() == qMetaTypeId<QString>()) {
225 argString = Cutelee::SafeString(arg.toString());
226 }
227 if (argVar.isConstant()) {
228 argString = markSafe(argString);
229 }
230 if (!argString.get().isEmpty()) {
231 arg = argString;
232 }
233 }
234
235 const auto varString = getSafeString(var);
236
237 var = filter->doFilter(var, arg, c->autoEscape());
238
239 if (var.userType() == qMetaTypeId<Cutelee::SafeString>()
240 || var.userType() == qMetaTypeId<QString>()) {
241 if (filter->isSafe() && varString.isSafe()) {
242 var = markSafe(getSafeString(var));
243 } else if (varString.needsEscape()) {
244 var = markForEscaping(getSafeString(var));
245 } else {
246 var = getSafeString(var);
247 }
248 }
249 }
250 (*stream) << getSafeString(var).get();
251 return var;
252}
253
255{
256 OutputStream _dummy;
257 return resolve(&_dummy, c);
258}
259
260QVariantList FilterExpression::toList(Context *c) const
261{
262 const auto var = resolve(c);
263 if (!var.canConvert<QVariantList>())
264 return QVariantList();
265 return var.value<QVariantList>();
266}
267
269{
270 return variantIsTrue(resolve(c));
271}
272
273QStringList FilterExpression::filters() const
274{
275 Q_D(const FilterExpression);
276 return d->m_filterNames;
277}
The Context class holds the context to render a Template with.
Definition context.h:119
An exception for use when implementing template tags.
Definition exception.h:85
A FilterExpression object represents a filter expression in a template.
QVariantList toList(Context *c) const
bool isTrue(Context *c) const
QVariant resolve(OutputStream *stream, Context *c) const
FilterExpression & operator=(const FilterExpression &other)
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:49
std::shared_ptr< Filter > getFilter(const QString &name) const
Definition parser.cpp:157
A QString wrapper class for containing whether a string is safe or needs to be escaped.
Definition safestring.h:92
const NestedString & get() const
Definition safestring.h:340
A container for static variables defined in Templates.
Definition variable.h:53
The Cutelee namespace holds all public Cutelee API.
Definition Mainpage.dox:8
Cutelee::SafeString getSafeString(const QVariant &input)
Definition util.cpp:108
Cutelee::SafeString markSafe(const Cutelee::SafeString &input)
Definition util.cpp:90
Cutelee::SafeString markForEscaping(const Cutelee::SafeString &input)
Definition util.cpp:98
bool variantIsTrue(const QVariant &variant)
Definition util.cpp:39
Utility functions used throughout Cutelee.