Cutelee 6.1.0
parser.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 "parser.h"
22
23#include "engine.h"
24#include "exception.h"
25#include "filter.h"
26#include "cutelee_version.h"
27#include "nodebuiltins_p.h"
28#include "taglibraryinterface.h"
29#include "template.h"
30#include "template_p.h"
31
32using namespace Cutelee;
33
34namespace Cutelee
35{
36
38{
39public:
40 ParserPrivate(Parser *parser, const QList<Token> &tokenList)
41 : q_ptr(parser), m_tokenList(tokenList)
42 {
43 }
44
45 NodeList extendNodeList(NodeList list, Node *node);
46
51 NodeList parse(QObject *parent, const QStringList &stopAt);
52
53 void openLibrary(TagLibraryInterface *library);
54 Q_DECLARE_PUBLIC(Parser)
55 Parser *const q_ptr;
56
57 QList<Token> m_tokenList;
58
59 QHash<QString, AbstractNodeFactory *> m_nodeFactories;
60 QHash<QString, std::shared_ptr<Filter>> m_filters;
61
62 NodeList m_nodeList;
63};
64}
65
66void ParserPrivate::openLibrary(TagLibraryInterface *library)
67{
68 Q_Q(Parser);
69
70 auto ti = qobject_cast<TemplateImpl *>(q->parent());
71
72 auto cengine = ti->engine();
73 Q_ASSERT(cengine);
74 auto engine = const_cast<Engine *>(cengine);
75
76 auto factories = library->nodeFactories();
77 for (auto nodeIt = factories.begin(), nodeEnd = factories.end();
78 nodeIt != nodeEnd; ++nodeIt) {
79 nodeIt.value()->setEngine(engine);
80 m_nodeFactories.insert(nodeIt.key(), nodeIt.value());
81 }
82 auto filters = library->filters();
83 for (auto filterIt = filters.begin(), filterEnd = filters.end();
84 filterIt != filterEnd; ++filterIt) {
85 auto f = std::shared_ptr<Filter>(filterIt.value());
86 m_filters.insert(filterIt.key(), f);
87 }
88}
89
90Parser::Parser(const QList<Token> &tokenList, QObject *parent)
91 : QObject(parent), d_ptr(new ParserPrivate(this, tokenList))
92{
93 Q_D(Parser);
94
95 auto ti = qobject_cast<TemplateImpl *>(parent);
96
97 auto cengine = ti->engine();
98 Q_ASSERT(cengine);
99
100 auto engine = const_cast<Engine *>(cengine);
101 engine->loadDefaultLibraries();
102 const auto libs = engine->defaultLibraries();
103 for (const QString &libraryName : libs) {
104 auto library = engine->loadLibrary(libraryName);
105 if (!library)
106 continue;
107 d->openLibrary(library);
108 }
109}
110
112{
113 // Don't delete filters here because filters must out-live the parser in the
114 // filter expressions.
115 qDeleteAll(d_ptr->m_nodeFactories);
116 delete d_ptr;
117}
118
119void Parser::loadLib(const QString &name)
120{
121 Q_D(Parser);
122 auto ti = qobject_cast<TemplateImpl *>(parent());
123 auto cengine = ti->engine();
124 Q_ASSERT(cengine);
125 auto engine = const_cast<Engine *>(cengine);
126 auto library = engine->loadLibrary(name);
127 if (!library)
128 return;
129 d->openLibrary(library);
130}
131
132NodeList ParserPrivate::extendNodeList(NodeList list, Node *node)
133{
134 if (node->mustBeFirst() && list.containsNonText()) {
135 throw Cutelee::Exception(
136 TagSyntaxError,
137 QStringLiteral("Node appeared twice in template: %1")
138 .arg(QLatin1String(node->metaObject()->className())));
139 }
140
141 list.append(node);
142 return list;
143}
144
145void Parser::skipPast(const QString &tag)
146{
147 while (hasNextToken()) {
148 const auto token = takeNextToken();
149 if (token.tokenType == BlockToken && token.content == tag)
150 return;
151 }
152 throw Cutelee::Exception(
153 UnclosedBlockTagError,
154 QStringLiteral("No closing tag found for %1").arg(tag));
155}
156
157std::shared_ptr<Filter> Parser::getFilter(const QString &name) const
158{
159 Q_D(const Parser);
160 const auto it = d->m_filters.constFind(name);
161 if (Q_LIKELY(it != d->m_filters.constEnd())) {
162 return it.value();
163 }
164 throw Cutelee::Exception(UnknownFilterError,
165 QStringLiteral("Unknown filter: %1").arg(name));
166}
167
168NodeList Parser::parse(Node *parent, const QString &stopAt)
169{
170 Q_D(Parser);
171 return d->parse(parent, {stopAt});
172}
173
174NodeList Parser::parse(TemplateImpl *parent, const QStringList &stopAt)
175{
176 Q_D(Parser);
177 return d->parse(parent, stopAt);
178}
179
180NodeList Parser::parse(Node *parent, const QStringList &stopAt)
181{
182 Q_D(Parser);
183 return d->parse(parent, stopAt);
184}
185
186NodeList ParserPrivate::parse(QObject *parent, const QStringList &stopAt)
187{
188 Q_Q(Parser);
189 NodeList nodeList;
190
191 while (q->hasNextToken()) {
192 const auto token = q->takeNextToken();
193 if (token.tokenType == TextToken) {
194 nodeList = extendNodeList(nodeList, new TextNode(token.content, parent));
195 } else if (token.tokenType == VariableToken) {
196 if (token.content.isEmpty()) {
197 // Error. Empty variable
198 QString message;
199 Q_ASSERT(q->hasNextToken());
200 message = QStringLiteral("Empty variable before \"%1\", line %2, %3")
201 .arg(q->takeNextToken().content.left(20))
202 .arg(token.linenumber)
203 .arg(q->parent()->objectName());
204 throw Cutelee::Exception(EmptyVariableError, message);
205 }
206
207 FilterExpression filterExpression;
208 try {
209 filterExpression = FilterExpression(token.content, q);
210 } catch (const Cutelee::Exception &e) {
211 throw Cutelee::Exception(e.errorCode(),
212 QStringLiteral("%1, line %2, %3")
213 .arg(e.what())
214 .arg(token.linenumber)
215 .arg(q->parent()->objectName()));
216 }
217
218 nodeList = extendNodeList(nodeList,
219 new VariableNode(filterExpression, parent));
220 } else {
221 Q_ASSERT(token.tokenType == BlockToken);
222 const auto command = token.content.section(QLatin1Char(' '), 0, 0);
223 if (stopAt.contains(command)) {
224 // A matching token has been reached. Return control to
225 // the caller. Put the token back on the token list so the
226 // caller knows where it terminated.
227 q->prependToken(token);
228 return nodeList;
229 }
230
231 if (command.isEmpty()) {
232 QString message;
233 Q_ASSERT(q->hasNextToken());
234 message = QStringLiteral("Empty block tag before \"%1\", line %2, %3")
235 .arg(token.content.left(20))
236 .arg(token.linenumber)
237 .arg(q->parent()->objectName());
238 throw Cutelee::Exception(EmptyBlockTagError, message);
239 }
240
241 auto nodeFactory = m_nodeFactories[command];
242
243 // unknown tag.
244 if (!nodeFactory) {
245 q->invalidBlockTag(token, command, stopAt);
246 }
247
248 // TODO: Make getNode take a Token instead?
249 Node *n;
250 try {
251 n = nodeFactory->getNode(token.content, q);
252 } catch (const Cutelee::Exception &e) {
253 throw Cutelee::Exception(e.errorCode(),
254 QStringLiteral("%1, line %2, %3")
255 .arg(e.what())
256 .arg(token.linenumber)
257 .arg(q->parent()->objectName()));
258 }
259 if (!n) {
260 throw Cutelee::Exception(
261 EmptyBlockTagError,
262 QStringLiteral("Failed to get node from %1, line %2, %3")
263 .arg(command)
264 .arg(token.linenumber)
265 .arg(q->parent()->objectName()));
266 }
267
268 n->setParent(parent);
269
270 nodeList = extendNodeList(nodeList, n);
271 }
272 }
273
274 if (!stopAt.isEmpty()) {
275 const auto message
276 = QStringLiteral("Unclosed tag in template %1. Expected one of: (%2)")
277 .arg(q->parent()->objectName(),
278 stopAt.join(QChar::fromLatin1(' ')));
279 throw Cutelee::Exception(UnclosedBlockTagError, message);
280 }
281
282 return nodeList;
283}
284
286{
287 Q_D(const Parser);
288 return !d->m_tokenList.isEmpty();
289}
290
292{
293 Q_D(Parser);
294 return d->m_tokenList.takeFirst();
295}
296
298{
299 Q_D(Parser);
300 d->m_tokenList.removeFirst();
301}
302
303void Parser::invalidBlockTag(const Token &token, const QString &command,
304 const QStringList &stopAt)
305{
306 if (!stopAt.empty()) {
307 throw Cutelee::Exception(
308 InvalidBlockTagError,
309 QStringLiteral("Invalid block tag on line %1: '%2', expected '%3'")
310 .arg(token.linenumber)
311 .arg(command, stopAt.join(QStringLiteral("', '"))));
312 } else {
313 throw Cutelee::Exception(
314 InvalidBlockTagError,
315 QStringLiteral("Invalid block tag on line %1: '%2\''. Did you forget "
316 "to register or load this tag?")
317 .arg(token.linenumber)
318 .arg(command));
319 }
320}
321
322void Parser::prependToken(const Token &token)
323{
324 Q_D(Parser);
325 d->m_tokenList.prepend(token);
326}
Cutelee::Engine is the main entry point for creating Cutelee Templates.
Definition engine.h:121
QStringList defaultLibraries() const
Definition engine.cpp:114
An exception for use when implementing template tags.
Definition exception.h:85
A FilterExpression object represents a filter expression in a template.
A list of Nodes with some convenience API for rendering them.
Definition node.h:148
bool containsNonText() const
Definition node.cpp:175
void append(Cutelee::Node *node)
Definition node.cpp:149
Base class for all nodes.
Definition node.h:78
NodeList parse(QObject *parent, const QStringList &stopAt)
Definition parser.cpp:186
The Parser class processes a string template into a tree of nodes.
Definition parser.h:49
bool hasNextToken() const
Definition parser.cpp:285
void skipPast(const QString &tag)
Definition parser.cpp:145
~Parser() override
Definition parser.cpp:111
void prependToken(const Token &token)
Definition parser.cpp:322
Token takeNextToken()
Definition parser.cpp:291
NodeList parse(Node *parent, const QStringList &stopAt={})
Definition parser.cpp:180
std::shared_ptr< Filter > getFilter(const QString &name) const
Definition parser.cpp:157
Parser(const QList< Token > &tokenList, QObject *parent)
Definition parser.cpp:90
void removeNextToken()
Definition parser.cpp:297
The TagLibraryInterface returns available tags and filters from libraries.
virtual QHash< QString, AbstractNodeFactory * > nodeFactories(const QString &name={})
virtual QHash< QString, Filter * > filters(const QString &name={})
The Cutelee namespace holds all public Cutelee API.
Definition Mainpage.dox:8
@ BlockToken
The Token is a block, ie, part of a tag.
Definition token.h:37
@ TextToken
The Token is a text fragment.
Definition token.h:35
@ VariableToken
The Token is a variable node.
Definition token.h:36
int linenumber
The line number this Token starts at.
Definition token.h:50