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 
31 using ArgFilter = std::pair<std::shared_ptr<Cutelee::Filter>, Cutelee::Variable>;
32 
33 namespace Cutelee
34 {
35 
37 {
38  FilterExpressionPrivate(FilterExpression *fe) : q_ptr(fe) {}
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 
49 using namespace Cutelee;
50 
51 static const char FILTER_SEPARATOR = '|';
52 static const char FILTER_ARGUMENT_SEPARATOR = ':';
53 
54 static 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 {
97  Q_D(FilterExpression);
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 
182 {
183 }
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 
260 QVariantList 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 
273 QStringList 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.
Definition: outputstream.h:81
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.