File indexing completed on 2025-02-09 04:28:38
0001 /* 0002 This file is part of the KTextTemplate library 0003 0004 SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.1-or-later 0007 0008 */ 0009 0010 #include "parser.h" 0011 0012 #include "engine.h" 0013 #include "exception.h" 0014 #include "nodebuiltins_p.h" 0015 #include "taglibraryinterface.h" 0016 #include "template.h" 0017 0018 using namespace KTextTemplate; 0019 0020 namespace KTextTemplate 0021 { 0022 0023 class ParserPrivate 0024 { 0025 public: 0026 ParserPrivate(Parser *parser, const QList<Token> &tokenList) 0027 : q_ptr(parser) 0028 , m_tokenList(tokenList) 0029 { 0030 } 0031 0032 NodeList extendNodeList(NodeList list, Node *node); 0033 0034 /** 0035 Parses the template to create a Nodelist. 0036 The given @p parent is the parent of each node in the returned list. 0037 */ 0038 NodeList parse(QObject *parent, const QStringList &stopAt); 0039 0040 void openLibrary(TagLibraryInterface *library); 0041 Q_DECLARE_PUBLIC(Parser) 0042 Parser *const q_ptr; 0043 0044 QList<Token> m_tokenList; 0045 0046 QHash<QString, AbstractNodeFactory *> m_nodeFactories; 0047 QHash<QString, QSharedPointer<Filter>> m_filters; 0048 0049 NodeList m_nodeList; 0050 }; 0051 } 0052 0053 void ParserPrivate::openLibrary(TagLibraryInterface *library) 0054 { 0055 Q_Q(Parser); 0056 0057 auto ti = qobject_cast<TemplateImpl *>(q->parent()); 0058 0059 auto cengine = ti->engine(); 0060 Q_ASSERT(cengine); 0061 auto engine = const_cast<Engine *>(cengine); 0062 0063 auto factories = library->nodeFactories(); 0064 for (auto nodeIt = factories.begin(), nodeEnd = factories.end(); nodeIt != nodeEnd; ++nodeIt) { 0065 nodeIt.value()->setEngine(engine); 0066 m_nodeFactories.insert(nodeIt.key(), nodeIt.value()); 0067 } 0068 auto filters = library->filters(); 0069 for (auto filterIt = filters.begin(), filterEnd = filters.end(); filterIt != filterEnd; ++filterIt) { 0070 auto f = QSharedPointer<Filter>(filterIt.value()); 0071 m_filters.insert(filterIt.key(), f); 0072 } 0073 } 0074 0075 Parser::Parser(const QList<Token> &tokenList, QObject *parent) 0076 : QObject(parent) 0077 , d_ptr(new ParserPrivate(this, tokenList)) 0078 { 0079 Q_D(Parser); 0080 0081 auto ti = qobject_cast<TemplateImpl *>(parent); 0082 0083 auto cengine = ti->engine(); 0084 Q_ASSERT(cengine); 0085 0086 auto engine = const_cast<Engine *>(cengine); 0087 engine->loadDefaultLibraries(); 0088 for (const QString &libraryName : engine->defaultLibraries()) { 0089 auto library = engine->loadLibrary(libraryName); 0090 if (!library) 0091 continue; 0092 d->openLibrary(library); 0093 } 0094 } 0095 0096 Parser::~Parser() 0097 { 0098 // Don't delete filters here because filters must out-live the parser in the 0099 // filter expressions. 0100 qDeleteAll(d_ptr->m_nodeFactories); 0101 delete d_ptr; 0102 } 0103 0104 void Parser::loadLib(const QString &name) 0105 { 0106 Q_D(Parser); 0107 auto ti = qobject_cast<TemplateImpl *>(parent()); 0108 auto cengine = ti->engine(); 0109 Q_ASSERT(cengine); 0110 auto engine = const_cast<Engine *>(cengine); 0111 auto library = engine->loadLibrary(name); 0112 if (!library) 0113 return; 0114 d->openLibrary(library); 0115 } 0116 0117 NodeList ParserPrivate::extendNodeList(NodeList list, Node *node) 0118 { 0119 if (node->mustBeFirst() && list.containsNonText()) { 0120 throw KTextTemplate::Exception(TagSyntaxError, 0121 QStringLiteral("Node appeared twice in template: %1").arg(QLatin1String(node->metaObject()->className()))); 0122 } 0123 0124 list.append(node); 0125 return list; 0126 } 0127 0128 void Parser::skipPast(const QString &tag) 0129 { 0130 while (hasNextToken()) { 0131 const auto token = takeNextToken(); 0132 if (token.tokenType == BlockToken && token.content == tag) 0133 return; 0134 } 0135 throw KTextTemplate::Exception(UnclosedBlockTagError, QStringLiteral("No closing tag found for %1").arg(tag)); 0136 } 0137 0138 QSharedPointer<Filter> Parser::getFilter(const QString &name) const 0139 { 0140 Q_D(const Parser); 0141 const auto it = d->m_filters.constFind(name); 0142 if (it != d->m_filters.constEnd()) { 0143 return it.value(); 0144 } 0145 throw KTextTemplate::Exception(UnknownFilterError, QStringLiteral("Unknown filter: %1").arg(name)); 0146 } 0147 0148 NodeList Parser::parse(Node *parent, const QString &stopAt) 0149 { 0150 Q_D(Parser); 0151 return d->parse(parent, {stopAt}); 0152 } 0153 0154 NodeList Parser::parse(TemplateImpl *parent, const QStringList &stopAt) 0155 { 0156 Q_D(Parser); 0157 return d->parse(parent, stopAt); 0158 } 0159 0160 NodeList Parser::parse(Node *parent, const QStringList &stopAt) 0161 { 0162 Q_D(Parser); 0163 return d->parse(parent, stopAt); 0164 } 0165 0166 NodeList ParserPrivate::parse(QObject *parent, const QStringList &stopAt) 0167 { 0168 Q_Q(Parser); 0169 NodeList nodeList; 0170 0171 while (q->hasNextToken()) { 0172 const auto token = q->takeNextToken(); 0173 if (token.tokenType == TextToken) { 0174 nodeList = extendNodeList(nodeList, new TextNode(token.content, parent)); 0175 } else if (token.tokenType == VariableToken) { 0176 if (token.content.isEmpty()) { 0177 // Error. Empty variable 0178 QString message; 0179 Q_ASSERT(q->hasNextToken()); 0180 message = QStringLiteral("Empty variable before \"%1\", line %2, %3") 0181 .arg(q->takeNextToken().content.left(20)) 0182 .arg(token.linenumber) 0183 .arg(q->parent()->objectName()); 0184 throw KTextTemplate::Exception(EmptyVariableError, message); 0185 } 0186 0187 FilterExpression filterExpression; 0188 try { 0189 filterExpression = FilterExpression(token.content, q); 0190 } catch (const KTextTemplate::Exception &e) { 0191 throw KTextTemplate::Exception(e.errorCode(), 0192 QStringLiteral("%1, line %2, %3").arg(e.what()).arg(token.linenumber).arg(q->parent()->objectName())); 0193 } 0194 0195 nodeList = extendNodeList(nodeList, new VariableNode(filterExpression, parent)); 0196 } else { 0197 Q_ASSERT(token.tokenType == BlockToken); 0198 const auto command = token.content.section(QLatin1Char(' '), 0, 0); 0199 if (stopAt.contains(command)) { 0200 // A matching token has been reached. Return control to 0201 // the caller. Put the token back on the token list so the 0202 // caller knows where it terminated. 0203 q->prependToken(token); 0204 return nodeList; 0205 } 0206 0207 if (command.isEmpty()) { 0208 QString message; 0209 Q_ASSERT(q->hasNextToken()); 0210 message = QStringLiteral("Empty block tag before \"%1\", line %2, %3") 0211 .arg(token.content.left(20)) 0212 .arg(token.linenumber) 0213 .arg(q->parent()->objectName()); 0214 throw KTextTemplate::Exception(EmptyBlockTagError, message); 0215 } 0216 0217 auto nodeFactory = m_nodeFactories[command]; 0218 0219 // unknown tag. 0220 if (!nodeFactory) { 0221 q->invalidBlockTag(token, command, stopAt); 0222 } 0223 0224 // TODO: Make getNode take a Token instead? 0225 Node *n; 0226 try { 0227 n = nodeFactory->getNode(token.content, q); 0228 } catch (const KTextTemplate::Exception &e) { 0229 throw KTextTemplate::Exception(e.errorCode(), 0230 QStringLiteral("%1, line %2, %3").arg(e.what()).arg(token.linenumber).arg(q->parent()->objectName())); 0231 } 0232 if (!n) { 0233 throw KTextTemplate::Exception( 0234 EmptyBlockTagError, 0235 QStringLiteral("Failed to get node from %1, line %2, %3").arg(command).arg(token.linenumber).arg(q->parent()->objectName())); 0236 } 0237 0238 n->setParent(parent); 0239 0240 nodeList = extendNodeList(nodeList, n); 0241 } 0242 } 0243 0244 if (!stopAt.isEmpty()) { 0245 const auto message = 0246 QStringLiteral("Unclosed tag in template %1. Expected one of: (%2)").arg(q->parent()->objectName(), stopAt.join(QChar::fromLatin1(' '))); 0247 throw KTextTemplate::Exception(UnclosedBlockTagError, message); 0248 } 0249 0250 return nodeList; 0251 } 0252 0253 bool Parser::hasNextToken() const 0254 { 0255 Q_D(const Parser); 0256 return !d->m_tokenList.isEmpty(); 0257 } 0258 0259 Token Parser::takeNextToken() 0260 { 0261 Q_D(Parser); 0262 return d->m_tokenList.takeFirst(); 0263 } 0264 0265 void Parser::removeNextToken() 0266 { 0267 Q_D(Parser); 0268 d->m_tokenList.removeFirst(); 0269 } 0270 0271 void Parser::invalidBlockTag(const Token &token, const QString &command, const QStringList &stopAt) 0272 { 0273 if (!stopAt.empty()) { 0274 throw KTextTemplate::Exception( 0275 InvalidBlockTagError, 0276 QStringLiteral("Invalid block tag on line %1: '%2', expected '%3'").arg(token.linenumber).arg(command, stopAt.join(QStringLiteral("', '")))); 0277 } 0278 throw KTextTemplate::Exception(InvalidBlockTagError, 0279 QStringLiteral("Invalid block tag on line %1: '%2\''. Did you forget " 0280 "to register or load this tag?") 0281 .arg(token.linenumber) 0282 .arg(command)); 0283 } 0284 0285 void Parser::prependToken(const Token &token) 0286 { 0287 Q_D(Parser); 0288 d->m_tokenList.prepend(token); 0289 } 0290 0291 #include "moc_parser.cpp"