Let Qt report the error on an empty container.
[grantlee:mlaurents-grantlee.git] / templates / lib / parser.cpp
1 /*
2   This file is part of the Grantlee 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 "grantlee_version.h"
27 #include "nodebuiltins_p.h"
28 #include "taglibraryinterface.h"
29 #include "template.h"
30 #include "template_p.h"
31
32 #include <QtCore/QFile>
33
34 using namespace Grantlee;
35
36 namespace Grantlee
37 {
38
39 class ParserPrivate
40 {
41 public:
42   ParserPrivate( Parser *parser, const QList<Token> &tokenList )
43       : q_ptr( parser ),
44       m_tokenList( tokenList ) {
45
46   }
47
48   NodeList extendNodeList( NodeList list, Node *node );
49
50   /**
51     Parses the template to create a Nodelist.
52     The given @p parent is the parent of each node in the returned list.
53   */
54   NodeList parse( QObject *parent, const QStringList &stopAt = QStringList() );
55
56   void openLibrary( TagLibraryInterface * library );
57   Q_DECLARE_PUBLIC( Parser )
58   Parser * const q_ptr;
59
60   QList<Token> m_tokenList;
61
62   QHash<QString, AbstractNodeFactory*> m_nodeFactories;
63   QHash<QString, Filter::Ptr> m_filters;
64
65   NodeList m_nodeList;
66 };
67
68 }
69
70 void ParserPrivate::openLibrary( TagLibraryInterface *library )
71 {
72   Q_Q( Parser );
73   QHashIterator<QString, AbstractNodeFactory*> nodeIt( library->nodeFactories() );
74
75   TemplateImpl *ti = qobject_cast<TemplateImpl *>( q->parent() );
76
77   Engine const *cengine = ti->engine();
78   Q_ASSERT( cengine );
79   Engine *engine = const_cast<Engine *>( cengine );
80
81   while ( nodeIt.hasNext() ) {
82     nodeIt.next();
83     nodeIt.value()->setEngine( engine );
84     m_nodeFactories.insert( nodeIt.key(), nodeIt.value() );
85   }
86   QHashIterator<QString, Filter*> filterIt( library->filters() );
87   while ( filterIt.hasNext() ) {
88     filterIt.next();
89     Filter::Ptr f = Filter::Ptr( filterIt.value() );
90     m_filters.insert( filterIt.key(), f );
91   }
92 }
93
94 Parser::Parser( const QList<Token> &tokenList, QObject *parent )
95     : QObject( parent ), d_ptr( new ParserPrivate( this, tokenList ) )
96 {
97   Q_D( Parser );
98
99   TemplateImpl *ti = qobject_cast<TemplateImpl *>( parent );
100
101   Engine const *cengine = ti->engine();
102   Q_ASSERT( cengine );
103
104   Engine *engine = const_cast<Engine *>( cengine );
105   engine->loadDefaultLibraries();
106   Q_FOREACH( const QString &libraryName, engine->defaultLibraries() ) {
107     TagLibraryInterface *library = engine->loadLibrary( libraryName );
108     if ( !library )
109       continue;
110     d->openLibrary( library );
111   }
112 }
113
114 Parser::~Parser()
115 {
116   // Don't delete filters here because filters must out-live the parser in the
117   // filter expressions.
118   qDeleteAll( d_ptr->m_nodeFactories );
119   delete d_ptr;
120 }
121
122
123 void Parser::setTokens( const QList< Token >& tokenList )
124 {
125   Q_D( Parser );
126   d->m_tokenList = tokenList;
127 }
128
129
130 void Parser::loadLib( const QString &name )
131 {
132   Q_D( Parser );
133   TemplateImpl *ti = qobject_cast<TemplateImpl *>( parent() );
134   Engine const *cengine = ti->engine();
135   Q_ASSERT( cengine );
136   Engine *engine = const_cast<Engine *>( cengine );
137   TagLibraryInterface *library = engine->loadLibrary( name );
138   if ( !library )
139     return;
140   d->openLibrary( library );
141 }
142
143 NodeList ParserPrivate::extendNodeList( NodeList list, Node *node )
144 {
145   if ( node->mustBeFirst() && list.containsNonText() ) {
146     throw Grantlee::Exception( TagSyntaxError, QString::fromLatin1(
147         "Node appeared twice in template: %1" ).arg( QLatin1String( node->metaObject()->className() ) ) );
148   }
149
150   list.append( node );
151   return list;
152 }
153
154 void Parser::skipPast( const QString &tag )
155 {
156   while ( hasNextToken() ) {
157     const Token token = takeNextToken();
158     if ( token.tokenType == BlockToken && token.content.trimmed() == tag )
159       return;
160   }
161   throw Grantlee::Exception( UnclosedBlockTagError, QString::fromLatin1( "No closing tag found for %1" ).arg( tag ) );
162 }
163
164 Filter::Ptr Parser::getFilter( const QString &name ) const
165 {
166   Q_D( const Parser );
167   if ( !d->m_filters.contains( name ) )
168     throw Grantlee::Exception( UnknownFilterError, QString::fromLatin1( "Unknown filter: %1" ).arg( name ) );
169   return d->m_filters.value( name );
170 }
171
172 NodeList Parser::parse( Node *parent, const QString &stopAt )
173 {
174   Q_D( Parser );
175   return d->parse( parent, QStringList() << stopAt );
176 }
177
178 NodeList Parser::parse( TemplateImpl *parent, const QStringList &stopAt )
179 {
180   Q_D( Parser );
181   return d->parse( parent, stopAt );
182 }
183
184 NodeList Parser::parse( Node *parent, const QStringList &stopAt )
185 {
186   Q_D( Parser );
187   return d->parse( parent, stopAt );
188 }
189
190 NodeList ParserPrivate::parse( QObject *parent, const QStringList &stopAt )
191 {
192   Q_Q( Parser );
193   NodeList nodeList;
194
195   while ( q->hasNextToken() ) {
196     const Token token = q->takeNextToken();
197     if ( token.tokenType == TextToken ) {
198       nodeList = extendNodeList( nodeList, new TextNode( token.content, parent ) );
199     } else if ( token.tokenType == VariableToken ) {
200       if ( token.content.isEmpty() ) {
201         // Error. Empty variable
202         QString message;
203         Q_ASSERT( q->hasNextToken() );
204         message = QString::fromLatin1( "Empty variable before \"%1\", line %2, %3" ).arg( q->takeNextToken().content.left( 20 ) ).arg( token.linenumber ).arg( q->parent()->objectName() );
205         throw Grantlee::Exception( EmptyVariableError, message );
206       }
207
208       FilterExpression filterExpression;
209       try {
210         filterExpression = FilterExpression( token.content, q );
211       } catch ( Grantlee::Exception e ) {
212         throw Grantlee::Exception( e.errorCode(), QString::fromLatin1( "%1, line %2, %3" ).arg( e.what() )
213                                                                               .arg( token.linenumber )
214                                                                               .arg( q->parent()->objectName() ) );
215       }
216
217       nodeList = extendNodeList( nodeList, new VariableNode( filterExpression, parent ) );
218     } else {
219       Q_ASSERT( token.tokenType == BlockToken );
220       if ( stopAt.contains( token.content ) ) {
221         // put the token back.
222         q->prependToken( token );
223         return nodeList;
224       }
225
226       if ( token.content.isEmpty() ) {
227         QString message;
228         Q_ASSERT( q->hasNextToken() );
229         message = QString::fromLatin1( "Empty block tag before \"%1\", line %2, %3" ).arg( token.content.left( 20 ) ).arg( token.linenumber ).arg( q->parent()->objectName() );
230         throw Grantlee::Exception( EmptyBlockTagError, message );
231       }
232
233       const QStringList tagContents = token.content.split( QLatin1Char( ' ' ) );
234       const QString command = tagContents.first();
235       AbstractNodeFactory *nodeFactory = m_nodeFactories[command];
236
237       // unknown tag.
238       if ( !nodeFactory ) {
239         throw Grantlee::Exception( InvalidBlockTagError, QString::fromLatin1( "Unknown tag: \"%1\", line %2, %3" ).arg( command ).arg( token.linenumber ).arg( q->parent()->objectName() ) );
240       }
241
242       // TODO: Make getNode take a Token instead?
243       Node *n;
244       try {
245         n = nodeFactory->getNode( token.content, q );
246       } catch ( Grantlee::Exception e ) {
247         throw Grantlee::Exception( e.errorCode(), QString::fromLatin1( "%1, line %2, %3" ).arg( e.what() )
248                                                                               .arg( token.linenumber )
249                                                                               .arg( q->parent()->objectName() ) );
250       }
251       if ( !n ) {
252         throw Grantlee::Exception( EmptyBlockTagError, QString::fromLatin1( "Failed to get node from %1, line %2, %3" ).arg( command ).arg( token.linenumber ).arg( q->parent()->objectName() ) );
253       }
254
255       n->setParent( parent );
256
257       nodeList = extendNodeList( nodeList, n );
258     }
259   }
260
261   if ( !stopAt.isEmpty() ) {
262     const QString message = QString::fromLatin1( "Unclosed tag in template %1. Expected one of: (%2)" ).arg( q->parent()->objectName() ).arg( stopAt.join( QChar::fromLatin1( ' ' ) ) );
263     throw Grantlee::Exception( UnclosedBlockTagError, message );
264   }
265
266   return nodeList;
267
268 }
269
270 bool Parser::hasNextToken() const
271 {
272   Q_D( const Parser );
273   return !d->m_tokenList.isEmpty();
274 }
275
276 Token Parser::takeNextToken()
277 {
278   Q_D( Parser );
279   return d->m_tokenList.takeFirst();
280 }
281
282 void Parser::removeNextToken()
283 {
284   Q_D( Parser );
285   d->m_tokenList.removeFirst();
286 }
287
288 void Parser::prependToken( const Token &token )
289 {
290   Q_D( Parser );
291   d->m_tokenList.prepend( token );
292 }
293
294 #include "parser.moc"
295