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"