cutelyst 3.9.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
grantleeview.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "action.h"
6#include "application.h"
7#include "config.h"
8#include "context.h"
9#include "grantleeview_p.h"
10#include "response.h"
11
12#include <grantlee/metatype.h>
13#include <grantlee/qtlocalizer.h>
14
15#include <QDirIterator>
16#include <QString>
17#include <QTranslator>
18#include <QtCore/QLoggingCategory>
19
20Q_LOGGING_CATEGORY(CUTELYST_GRANTLEE, "cutelyst.grantlee", QtWarningMsg)
21
22using namespace Cutelyst;
23
24GRANTLEE_BEGIN_LOOKUP(ParamsMultiMap)
25return object.value(property);
26GRANTLEE_END_LOOKUP
27
28GRANTLEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
29return object->property(property.toLatin1().constData());
30GRANTLEE_END_LOOKUP
31
33 : View(new GrantleeViewPrivate, parent, name)
34{
35 Q_D(GrantleeView);
36
37 Grantlee::registerMetaType<ParamsMultiMap>();
38 Grantlee::registerMetaType<Cutelyst::Request *>(); // To be able to access it's properties
39
40 d->loader =
41 QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader);
42
43 d->engine = new Grantlee::Engine(this);
44 d->engine->addTemplateLoader(d->loader);
45
46 // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of grantlee engine
47 const QByteArrayList dirs = QByteArrayList{QByteArrayLiteral(CUTELYST_PLUGINS_DIR)} +
48 qgetenv("CUTELYST_PLUGINS_DIR").split(';');
49 for (const QByteArray &dir : dirs) {
50 d->engine->addPluginPath(QString::fromLocal8Bit(dir));
51 }
52
53 d->engine->addDefaultLibrary(QStringLiteral("grantlee_cutelyst"));
54
56 if (app) {
57 // make sure templates can be found on the current directory
58 setIncludePaths({app->config(QStringLiteral("root")).toString()});
59
60 // If CUTELYST_VAR is set the template might have become
61 // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
62 d->cutelystVar =
63 app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
64
65 app->loadTranslations(QStringLiteral("plugin_view_grantlee"));
66 } else {
67 // make sure templates can be found on the current directory
69 }
70}
71
73{
74 Q_D(const GrantleeView);
75 return d->includePaths;
76}
77
79{
80 Q_D(GrantleeView);
81 d->loader->setTemplateDirs(paths);
82 d->includePaths = paths;
83 Q_EMIT changed();
84}
85
87{
88 Q_D(const GrantleeView);
89 return d->extension;
90}
91
93{
94 Q_D(GrantleeView);
95 d->extension = extension;
96 Q_EMIT changed();
97}
98
100{
101 Q_D(const GrantleeView);
102 return d->wrapper;
103}
104
106{
107 Q_D(GrantleeView);
108 d->wrapper = name;
109 Q_EMIT changed();
110}
111
112void GrantleeView::setCache(bool enable)
113{
114 Q_D(GrantleeView);
115
116 if (enable != d->cache.isNull()) {
117 return; // already enabled
118 }
119
120 delete d->engine;
121 d->engine = new Grantlee::Engine(this);
122
123 if (enable) {
125 new Grantlee::CachingLoaderDecorator(d->loader));
126 d->engine->addTemplateLoader(d->cache);
127 } else {
128 d->cache.clear();
129 d->engine->addTemplateLoader(d->loader);
130 }
131 Q_EMIT changed();
132}
133
134Grantlee::Engine *GrantleeView::engine() const
135{
136 Q_D(const GrantleeView);
137 return d->engine;
138}
139
141{
142 Q_D(GrantleeView);
143
144 if (!isCaching()) {
145 setCache(true);
146 }
147
148 const auto includePaths = d->includePaths;
149 for (const QString &includePath : includePaths) {
150 QDirIterator it(includePath,
151 {QLatin1Char('*') + d->extension},
154 while (it.hasNext()) {
155 QString path = it.next();
156 path.remove(includePath);
157 if (path.startsWith(QLatin1Char('/'))) {
158 path.remove(0, 1);
159 }
160
161 if (d->cache->canLoadTemplate(path)) {
162 d->cache->loadByName(path, d->engine);
163 }
164 }
165 }
166}
167
169{
170 Q_D(const GrantleeView);
171 return !d->cache.isNull();
172}
173
175{
176 Q_D(const GrantleeView);
177
178 QByteArray ret;
179 c->setStash(d->cutelystVar, QVariant::fromValue(c));
180 const QVariantHash stash = c->stash();
181 auto it = stash.constFind(QStringLiteral("template"));
182 QString templateFile;
183 if (it != stash.constEnd()) {
184 templateFile = it.value().toString();
185 } else {
186 if (c->action() && !c->action()->reverse().isEmpty()) {
187 templateFile = c->action()->reverse() + d->extension;
188 if (templateFile.startsWith(QLatin1Char('/'))) {
189 templateFile.remove(0, 1);
190 }
191 }
192
193 if (templateFile.isEmpty()) {
194 c->error(QStringLiteral(
195 "Cannot render template, template name or template stash key not defined"));
196 return ret;
197 }
198 }
199
200 qCDebug(CUTELYST_GRANTLEE) << "Rendering template" << templateFile;
201
202 Grantlee::Context gc(stash);
203
205
206 auto transIt = d->translators.constFind(c->locale());
207 if (transIt != d->translators.constEnd()) {
208 localizer.data()->installTranslator(transIt.value(), transIt.key().name());
209 }
210
211 auto catalogIt = d->translationCatalogs.constBegin();
212 while (catalogIt != d->translationCatalogs.constEnd()) {
213 localizer.data()->loadCatalog(catalogIt.value(), catalogIt.key());
214 ++it;
215 }
216
217 gc.setLocalizer(localizer);
218
219 Grantlee::Template tmpl = d->engine->loadByName(templateFile);
220 if (tmpl->error() != Grantlee::NoError) {
221 c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
222 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
223 return ret;
224 }
225
226 QString content = tmpl->render(&gc);
227 if (tmpl->error() != Grantlee::NoError) {
228 c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
229 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
230 return ret;
231 }
232
233 if (!d->wrapper.isEmpty()) {
234 Grantlee::Template wrapper = d->engine->loadByName(d->wrapper);
235 if (tmpl->error() != Grantlee::NoError) {
236 c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
237 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
238 return ret;
239 }
240
241 Grantlee::SafeString safeContent(content, true);
242 gc.insert(QStringLiteral("content"), safeContent);
243 content = wrapper->render(&gc);
244
245 if (wrapper->error() != Grantlee::NoError) {
246 c->res()->setBody(c->translate("Cutelyst::GrantleeView", "Internal server error."));
247 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
248 return ret;
249 }
250 }
251
252 ret = content.toUtf8();
253 return ret;
254}
255
256void GrantleeView::addTranslator(const QLocale &locale, QTranslator *translator)
257{
258 Q_D(GrantleeView);
259 Q_ASSERT_X(translator, "add translator to GrantleeView", "invalid QTranslator object");
260 d->translators.insert(locale, translator);
261}
262
263void GrantleeView::addTranslator(const QString &locale, QTranslator *translator)
264{
265 addTranslator(QLocale(locale), translator);
266}
267
268void GrantleeView::addTranslationCatalog(const QString &path, const QString &catalog)
269{
270 Q_D(GrantleeView);
271 Q_ASSERT_X(!path.isEmpty(), "add translation catalog to GrantleeView", "empty path");
272 Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to GrantleeView", "empty catalog name");
273 d->translationCatalogs.insert(catalog, path);
274}
275
277{
278 Q_D(GrantleeView);
279 Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
280 d->translationCatalogs.unite(catalogs);
281}
282
284 const QString &directory,
285 const QString &prefix,
286 const QString &suffix)
287{
288 QVector<QLocale> locales;
289
290 if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
291 const QDir i18nDir(directory);
292 if (Q_LIKELY(i18nDir.exists())) {
293 const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
294 const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
295 const QStringList namesFilter =
296 QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
297 const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
298 if (Q_LIKELY(!tsFiles.empty())) {
299 locales.reserve(tsFiles.size());
300 for (const QFileInfo &ts : tsFiles) {
301 const QString fn = ts.fileName();
302 const int prefIdx = fn.indexOf(_prefix);
303 const QString locString =
304 fn.mid(prefIdx + _prefix.length(),
305 fn.length() - prefIdx - _suffix.length() - _prefix.length());
306 QLocale loc(locString);
307 if (Q_LIKELY(loc.language() != QLocale::C)) {
308 auto trans = new QTranslator(this);
309 if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
310 addTranslator(loc, trans);
311 locales.append(loc);
312 qCDebug(CUTELYST_GRANTLEE) << "Loaded translations for locale" << loc
313 << "from" << ts.absoluteFilePath();
314 } else {
315 delete trans;
316 qCWarning(CUTELYST_GRANTLEE)
317 << "Can not load translations for locale" << loc;
318 }
319 } else {
320 qCWarning(CUTELYST_GRANTLEE)
321 << "Can not load translations for invalid locale string" << locString;
322 }
323 }
324 locales.squeeze();
325 } else {
326 qCWarning(CUTELYST_GRANTLEE) << "Can not find translation files for" << filename
327 << "in directory" << directory;
328 }
329 } else {
330 qCWarning(CUTELYST_GRANTLEE)
331 << "Can not load translations from not existing directory:" << directory;
332 }
333 } else {
334 qCWarning(CUTELYST_GRANTLEE)
335 << "Can not load translations for empty file name or empty path.";
336 }
337
338 return locales;
339}
340
341#include "moc_grantleeview.cpp"
QString name() const
Definition component.cpp:33
QString reverse() const
Definition component.cpp:45
The Cutelyst Context.
Definition context.h:39
void stash(const QVariantHash &unite)
Definition context.cpp:566
QLocale locale() const noexcept
Definition context.cpp:466
Response * res() const noexcept
Definition context.cpp:102
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition context.cpp:490
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:217
bool error() const noexcept
Returns true if an error was set.
Definition context.cpp:49
QStringList includePaths() const
Returns the list of include paths.
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
Grantlee::Engine * engine() const
QByteArray render(Context *c) const final
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
void addTranslator(const QLocale &locale, QTranslator *translator)
bool isCaching() const
Returns true if caching is enabled.
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
GrantleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a GrantleeView object with the given parent and name.
QString wrapper() const
Returns the template wrapper.
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
void addTranslationCatalog(const QString &path, const QString &catalog)
void addTranslationCatalogs(const QHash< QString, QString > &catalogs)
QString templateExtension() const
Returns the template extension.
void setBody(QIODevice *body)
Definition response.cpp:100
View(QObject *parent, const QString &name)
Definition view.cpp:18
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap
QString currentPath()
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const const
bool exists() const const
bool empty() const const
Language language() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
QObject * parent() const const
T qobject_cast(QObject *object)
QSharedPointer< T > create(Args &&... args)
QString fromLocal8Bit(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int length() const const
QString mid(int position, int n) const const
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QVariant fromValue(const T &value)
void append(const T &value)
void reserve(int size)
void squeeze()