KTextTemplate

parser.cpp
1/*
2 This file is part of the KTextTemplate library
3
4 SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7
8*/
9
10#include "parser.h"
11
12#include "engine.h"
13#include "exception.h"
14#include "nodebuiltins_p.h"
15#include "taglibraryinterface.h"
16#include "template.h"
17
18using namespace KTextTemplate;
19
20namespace KTextTemplate
21{
22
23class ParserPrivate
24{
25public:
26 ParserPrivate(Parser *parser, const QList<Token> &tokenList)
27 : q_ptr(parser)
28 , m_tokenList(tokenList)
29 {
30 }
31
32 NodeList extendNodeList(NodeList list, Node *node);
33
34 /**
35 Parses the template to create a Nodelist.
36 The given @p parent is the parent of each node in the returned list.
37 */
38 NodeList parse(QObject *parent, const QStringList &stopAt);
39
40 void openLibrary(TagLibraryInterface *library);
41 Q_DECLARE_PUBLIC(Parser)
42 Parser *const q_ptr;
43
44 QList<Token> m_tokenList;
45
48
49 NodeList m_nodeList;
50};
51}
52
53void ParserPrivate::openLibrary(TagLibraryInterface *library)
54{
55 Q_Q(Parser);
56
57 auto ti = qobject_cast<TemplateImpl *>(q->parent());
58
59 auto cengine = ti->engine();
60 Q_ASSERT(cengine);
61 auto engine = const_cast<Engine *>(cengine);
62
63 auto factories = library->nodeFactories();
64 for (auto nodeIt = factories.begin(), nodeEnd = factories.end(); nodeIt != nodeEnd; ++nodeIt) {
65 nodeIt.value()->setEngine(engine);
66 m_nodeFactories.insert(nodeIt.key(), nodeIt.value());
67 }
68 auto filters = library->filters();
69 for (auto filterIt = filters.begin(), filterEnd = filters.end(); filterIt != filterEnd; ++filterIt) {
70 auto f = QSharedPointer<Filter>(filterIt.value());
71 m_filters.insert(filterIt.key(), f);
72 }
73}
74
75Parser::Parser(const QList<Token> &tokenList, QObject *parent)
76 : QObject(parent)
77 , d_ptr(new ParserPrivate(this, tokenList))
78{
79 Q_D(Parser);
80
81 auto ti = qobject_cast<TemplateImpl *>(parent);
82
83 auto cengine = ti->engine();
84 Q_ASSERT(cengine);
85
86 auto engine = const_cast<Engine *>(cengine);
87 engine->loadDefaultLibraries();
88 for (const QString &libraryName : engine->defaultLibraries()) {
89 auto library = engine->loadLibrary(libraryName);
90 if (!library)
91 continue;
92 d->openLibrary(library);
93 }
94}
95
97{
98 // Don't delete filters here because filters must out-live the parser in the
99 // filter expressions.
100 qDeleteAll(d_ptr->m_nodeFactories);
101 delete d_ptr;
102}
103
104void Parser::loadLib(const QString &name)
105{
106 Q_D(Parser);
107 auto ti = qobject_cast<TemplateImpl *>(parent());
108 auto cengine = ti->engine();
109 Q_ASSERT(cengine);
110 auto engine = const_cast<Engine *>(cengine);
111 auto library = engine->loadLibrary(name);
112 if (!library)
113 return;
114 d->openLibrary(library);
115}
116
117NodeList ParserPrivate::extendNodeList(NodeList list, Node *node)
118{
119 if (node->mustBeFirst() && list.containsNonText()) {
120 throw KTextTemplate::Exception(TagSyntaxError,
121 QStringLiteral("Node appeared twice in template: %1").arg(QLatin1String(node->metaObject()->className())));
122 }
123
124 list.append(node);
125 return list;
126}
127
128void Parser::skipPast(const QString &tag)
129{
130 while (hasNextToken()) {
131 const auto token = takeNextToken();
132 if (token.tokenType == BlockToken && token.content == tag)
133 return;
134 }
135 throw KTextTemplate::Exception(UnclosedBlockTagError, QStringLiteral("No closing tag found for %1").arg(tag));
136}
137
139{
140 Q_D(const Parser);
141 const auto it = d->m_filters.constFind(name);
142 if (it != d->m_filters.constEnd()) {
143 return it.value();
144 }
145 throw KTextTemplate::Exception(UnknownFilterError, QStringLiteral("Unknown filter: %1").arg(name));
146}
147
148NodeList Parser::parse(Node *parent, const QString &stopAt)
149{
150 Q_D(Parser);
151 return d->parse(parent, {stopAt});
152}
153
154NodeList Parser::parse(TemplateImpl *parent, const QStringList &stopAt)
155{
156 Q_D(Parser);
157 return d->parse(parent, stopAt);
158}
159
160NodeList Parser::parse(Node *parent, const QStringList &stopAt)
161{
162 Q_D(Parser);
163 return d->parse(parent, stopAt);
164}
165
166NodeList ParserPrivate::parse(QObject *parent, const QStringList &stopAt)
167{
168 Q_Q(Parser);
169 NodeList nodeList;
170
171 while (q->hasNextToken()) {
172 const auto token = q->takeNextToken();
173 if (token.tokenType == TextToken) {
174 nodeList = extendNodeList(nodeList, new TextNode(token.content, parent));
175 } else if (token.tokenType == VariableToken) {
176 if (token.content.isEmpty()) {
177 // Error. Empty variable
178 QString message;
179 Q_ASSERT(q->hasNextToken());
180 message = QStringLiteral("Empty variable before \"%1\", line %2, %3")
181 .arg(q->takeNextToken().content.left(20))
182 .arg(token.linenumber)
183 .arg(q->parent()->objectName());
184 throw KTextTemplate::Exception(EmptyVariableError, message);
185 }
186
187 FilterExpression filterExpression;
188 try {
189 filterExpression = FilterExpression(token.content, q);
190 } catch (const KTextTemplate::Exception &e) {
191 throw KTextTemplate::Exception(e.errorCode(),
192 QStringLiteral("%1, line %2, %3").arg(e.what()).arg(token.linenumber).arg(q->parent()->objectName()));
193 }
194
195 nodeList = extendNodeList(nodeList, new VariableNode(filterExpression, parent));
196 } else {
197 Q_ASSERT(token.tokenType == BlockToken);
198 const auto command = token.content.section(QLatin1Char(' '), 0, 0);
199 if (stopAt.contains(command)) {
200 // A matching token has been reached. Return control to
201 // the caller. Put the token back on the token list so the
202 // caller knows where it terminated.
203 q->prependToken(token);
204 return nodeList;
205 }
206
207 if (command.isEmpty()) {
208 QString message;
209 Q_ASSERT(q->hasNextToken());
210 message = QStringLiteral("Empty block tag before \"%1\", line %2, %3")
211 .arg(token.content.left(20))
212 .arg(token.linenumber)
213 .arg(q->parent()->objectName());
214 throw KTextTemplate::Exception(EmptyBlockTagError, message);
215 }
216
217 auto nodeFactory = m_nodeFactories[command];
218
219 // unknown tag.
220 if (!nodeFactory) {
221 q->invalidBlockTag(token, command, stopAt);
222 }
223
224 // TODO: Make getNode take a Token instead?
225 Node *n;
226 try {
227 n = nodeFactory->getNode(token.content, q);
228 } catch (const KTextTemplate::Exception &e) {
229 throw KTextTemplate::Exception(e.errorCode(),
230 QStringLiteral("%1, line %2, %3").arg(e.what()).arg(token.linenumber).arg(q->parent()->objectName()));
231 }
232 if (!n) {
234 EmptyBlockTagError,
235 QStringLiteral("Failed to get node from %1, line %2, %3").arg(command).arg(token.linenumber).arg(q->parent()->objectName()));
236 }
237
238 n->setParent(parent);
239
240 nodeList = extendNodeList(nodeList, n);
241 }
242 }
243
244 if (!stopAt.isEmpty()) {
245 const auto message =
246 QStringLiteral("Unclosed tag in template %1. Expected one of: (%2)").arg(q->parent()->objectName(), stopAt.join(QChar::fromLatin1(' ')));
247 throw KTextTemplate::Exception(UnclosedBlockTagError, message);
248 }
249
250 return nodeList;
251}
252
254{
255 Q_D(const Parser);
256 return !d->m_tokenList.isEmpty();
257}
258
260{
261 Q_D(Parser);
262 return d->m_tokenList.takeFirst();
263}
264
266{
267 Q_D(Parser);
268 d->m_tokenList.removeFirst();
269}
270
271void Parser::invalidBlockTag(const Token &token, const QString &command, const QStringList &stopAt)
272{
273 if (!stopAt.empty()) {
275 InvalidBlockTagError,
276 QStringLiteral("Invalid block tag on line %1: '%2', expected '%3'").arg(token.linenumber).arg(command, stopAt.join(QStringLiteral("', '"))));
277 }
278 throw KTextTemplate::Exception(InvalidBlockTagError,
279 QStringLiteral("Invalid block tag on line %1: '%2\''. Did you forget "
280 "to register or load this tag?")
281 .arg(token.linenumber)
282 .arg(command));
283}
284
285void Parser::prependToken(const Token &token)
286{
287 Q_D(Parser);
288 d->m_tokenList.prepend(token);
289}
290
291#include "moc_parser.cpp"
KTextTemplate::Engine is the main entry point for creating KTextTemplate Templates.
Definition engine.h:110
An exception for use when implementing template tags.
Definition exception.h:74
A FilterExpression object represents a filter expression in a template.
A list of Nodes with some convenience API for rendering them.
Definition node.h:141
Base class for all nodes.
Definition node.h:72
The Parser class processes a string template into a tree of nodes.
Definition parser.h:38
bool hasNextToken() const
Returns whether the parser has another token to process.
Definition parser.cpp:253
void skipPast(const QString &tag)
Advances the parser to the tag tag.
Definition parser.cpp:128
~Parser() override
Destructor.
Definition parser.cpp:96
void prependToken(const Token &token)
Puts the token token to the front of the list to be processed by the parser.
Definition parser.cpp:285
Token takeNextToken()
Returns the next token to be processed by the parser.
Definition parser.cpp:259
QSharedPointer< Filter > getFilter(const QString &name) const
Returns the filter object called name or an invalid object if no filter with that name is loaded.
Definition parser.cpp:138
NodeList parse(Node *parent, const QStringList &stopAt={})
Advance the parser, using parent as the parent of new Nodes.
Definition parser.cpp:160
Parser(const QList< Token > &tokenList, QObject *parent)
Constructor.
Definition parser.cpp:75
void removeNextToken()
Deletes the next token available to the parser.
Definition parser.cpp:265
The TagLibraryInterface returns available tags and filters from libraries.
virtual QHash< QString, AbstractNodeFactory * > nodeFactories(const QString &name={})
Returns the AbstractNodeFactory implementations available in this library.
virtual QHash< QString, Filter * > filters(const QString &name={})
Returns the Filter implementations available in this library.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
The KTextTemplate namespace holds all public KTextTemplate API.
Definition Mainpage.dox:8
@ TextToken
The Token is a text fragment.
Definition token.h:24
@ VariableToken
The Token is a variable node.
Definition token.h:25
@ BlockToken
The Token is a block, ie, part of a tag.
Definition token.h:26
QChar fromLatin1(char c)
void append(QList< T > &&value)
bool empty() const const
bool isEmpty() const const
const char * className() const const
virtual const QMetaObject * metaObject() const const
QObject * parent() const const
void setParent(QObject *parent)
QString arg(Args &&... args) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
A token in a parse stream for a template.
Definition token.h:37
int linenumber
The line number this Token starts at.
Definition token.h:39
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:42 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.