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 
32 using namespace Cutelee;
33 
34 namespace Cutelee
35 {
36 
38 {
39 public:
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 
66 void 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 
90 Parser::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 
119 void 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 
132 NodeList 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 
145 void 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 
157 std::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 
168 NodeList Parser::parse(Node *parent, const QString &stopAt)
169 {
170  Q_D(Parser);
171  return d->parse(parent, {stopAt});
172 }
173 
174 NodeList Parser::parse(TemplateImpl *parent, const QStringList &stopAt)
175 {
176  Q_D(Parser);
177  return d->parse(parent, stopAt);
178 }
179 
180 NodeList Parser::parse(Node *parent, const QStringList &stopAt)
181 {
182  Q_D(Parser);
183  return d->parse(parent, stopAt);
184 }
185 
186 NodeList 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 
303 void 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 
322 void 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, Filter * > filters(const QString &name={})
virtual QHash< QString, AbstractNodeFactory * > nodeFactories(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