Make the Grantlee::Node::render method const.
[grantlee:grantlee.git] / templates / defaulttags / for.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 "for.h"
22
23 #include "../lib/exception.h"
24 #include "parser.h"
25 #include "metaenumvariable_p.h"
26
27 ForNodeFactory::ForNodeFactory()
28 {
29
30 }
31
32 Node* ForNodeFactory::getNode( const QString &tagContent, Parser *p ) const
33 {
34   QStringList expr = smartSplit( tagContent );
35
36   if ( expr.size() < 4 ) {
37     throw Grantlee::Exception( TagSyntaxError,
38         QString::fromLatin1( "'for' statements should have at least four words: %1" ).arg( tagContent ) );
39   }
40
41   expr.takeAt( 0 );
42   QStringList vars;
43
44   int reversed = ForNode::IsNotReversed;
45   if ( expr.last() == QLatin1String( "reversed" ) ) {
46     reversed = ForNode::IsReversed;
47     expr.removeLast();
48   }
49
50   if ( expr.mid( expr.size() - 2 ).first() != QLatin1String( "in" ) ) {
51     throw Grantlee::Exception( TagSyntaxError,
52       QString::fromLatin1( "'for' statements should use the form 'for x in y': %1" ).arg( tagContent ) );
53   }
54
55   Q_FOREACH( const QString &arg, expr.mid( 0, expr.size() - 2 ) ) {
56     vars << arg.split( QLatin1Char( ',' ), QString::SkipEmptyParts );
57   }
58
59   Q_FOREACH( const QString &var, vars ) {
60     if ( var.isNull() )
61       throw Grantlee::Exception( TagSyntaxError, QLatin1String( "'for' tag received invalid argument" ) );
62   }
63
64   FilterExpression fe( expr.last(), p );
65
66   ForNode *n = new ForNode( vars, fe, reversed, p );
67
68   NodeList loopNodes = p->parse( n, QStringList() << QLatin1String( "empty" ) << QLatin1String( "endfor" ) );
69   n->setLoopList( loopNodes );
70
71   NodeList emptyNodes;
72   if ( p->takeNextToken().content.trimmed() == QLatin1String( "empty" ) ) {
73     emptyNodes = p->parse( n, QLatin1String( "endfor" ) );
74     n->setEmptyList( emptyNodes );
75     // skip past the endfor tag
76     p->removeNextToken();
77   }
78
79   return n;
80 }
81
82
83
84 ForNode::ForNode( QStringList loopVars,
85                   FilterExpression fe,
86                   int reversed,
87                   QObject *parent )
88     : Node( parent ),
89     m_loopVars( loopVars ),
90     m_filterExpression( fe ),
91     m_isReversed( reversed )
92 {
93
94 }
95
96 void ForNode::setLoopList( NodeList loopNodeList )
97 {
98   m_loopNodeList = loopNodeList;
99 }
100
101 void ForNode::setEmptyList( NodeList emptyList )
102 {
103   m_emptyNodeList = emptyList;
104 }
105
106 static const char forloop[] = "forloop";
107 static const char parentloop[] = "parentloop";
108
109 void ForNode::insertLoopVariables( Context *c, int listSize, int i )
110 {
111   // some magic variables injected into the context while rendering.
112   static const QString counter0 = QLatin1String( "counter0" );
113   static const QString counter = QLatin1String( "counter" );
114   static const QString revcounter0 = QLatin1String( "revcounter0" );
115   static const QString revcounter = QLatin1String( "revcounter" );
116   static const QString first = QLatin1String( "first" );
117   static const QString last = QLatin1String( "last" );
118
119   QVariantHash forloopHash = c->lookup( QLatin1String( "forloop" ) ).toHash();
120   forloopHash.insert( counter0, i );
121   forloopHash.insert( counter, i + 1 );
122   forloopHash.insert( revcounter, listSize - i );
123   forloopHash.insert( revcounter0, listSize - i - 1 );
124   forloopHash.insert( first, ( i == 0 ) );
125   forloopHash.insert( last, ( i == listSize - 1 ) );
126   c->insert( QLatin1String( forloop ), forloopHash );
127 }
128
129 void ForNode::renderLoop( OutputStream *stream, Context *c ) const
130 {
131   for ( int j = 0; j < m_loopNodeList.size();j++ ) {
132     m_loopNodeList[j]->render( stream, c );
133   }
134 }
135
136 void ForNode::handleHashItem( OutputStream *stream, Context *c, QString key, QVariant value, int listSize, int i, bool unpack )
137 {
138   QVariantList list;
139   insertLoopVariables( c, listSize, i );
140
141   if ( !unpack ) {
142     // Iterating over a hash but not unpacking it.
143     // convert each key-value pair to a list and insert it in the context.
144     list << key << value;
145     c->insert( m_loopVars.first(), list );
146     list.clear();
147   } else {
148     c->insert( m_loopVars.first(), key );
149     c->insert( m_loopVars.at( 1 ), value );
150   }
151   renderLoop( stream, c );
152 }
153
154 void ForNode::iterateHash( OutputStream *stream, Context *c, QVariantHash varHash, bool unpack )
155 {
156   int listSize = varHash.size();
157   int i = 0;
158   QVariantList list;
159
160   QHashIterator<QString, QVariant> it( varHash );
161   if ( m_isReversed == IsReversed ) {
162     while ( it.hasPrevious() ) {
163       it.previous();
164       handleHashItem( stream, c, it.key(), it.value(), listSize, i, unpack );
165       ++i;
166     }
167   } else {
168     while ( it.hasNext() ) {
169       it.next();
170       handleHashItem( stream, c, it.key(), it.value(), listSize, i, unpack );
171       ++i;
172     }
173   }
174 }
175
176 void ForNode::render( OutputStream *stream, Context *c ) const
177 {
178   QVariantHash forloopHash;
179
180   QVariant parentLoopVariant = c->lookup( QLatin1String( forloop ) );
181   if ( parentLoopVariant.isValid() ) {
182     // This is a nested loop.
183     forloopHash = parentLoopVariant.toHash();
184     forloopHash.insert( QLatin1String( parentloop ), parentLoopVariant.toHash() );
185     c->insert( QLatin1String( forloop ), forloopHash );
186   }
187
188   bool unpack = m_loopVars.size() > 1;
189
190   c->push();
191
192 //   if ( var.type() == QVariant::Hash ) {
193 //     QVariantHash varHash = var.toHash();
194 //     result = iterateHash( c, varHash, unpack );
195 //     c->pop();
196 //     return result;
197 //   }
198
199   QVariant varFE = m_filterExpression.resolve( c );
200
201   if (varFE.userType() == qMetaTypeId<MetaEnumVariable>())
202   {
203     const MetaEnumVariable mev = varFE.value<MetaEnumVariable>();
204
205     if ( mev.value != -1 ) {
206       c->pop();
207       return m_emptyNodeList.render( stream, c );
208     }
209
210     QVariantList list;
211     for ( int row = 0; row < mev.enumerator.keyCount(); ++row ) {
212       list << QVariant::fromValue( MetaEnumVariable( mev.enumerator, row ) );
213     }
214     varFE = list;
215   }
216
217   if (!QMetaType::hasRegisteredConverterFunction(varFE.userType(), qMetaTypeId<QtMetaTypePrivate::QSequentialIterableImpl>()))
218   {
219     if (varFE.userType() != qMetaTypeId<QVariantList>() && varFE.userType() != qMetaTypeId<QStringList>() ) {
220       c->pop();
221       return m_emptyNodeList.render( stream, c );
222     }
223   }
224
225   QSequentialIterable iter = varFE.value<QSequentialIterable>();
226   const int listSize = iter.size();
227
228   // If it's an iterable type, iterate, otherwise it's a list of one.
229   if ( listSize < 1 ) {
230     c->pop();
231     return m_emptyNodeList.render( stream, c );
232   }
233
234   NodeList nodeList;
235
236   int i = 0;
237   for (QSequentialIterable::const_iterator it = m_isReversed == IsReversed ? iter.end() -1 : iter.begin();
238        m_isReversed == IsReversed ? it != iter.begin() - 1: it != iter.end();
239        m_isReversed == IsReversed ? --it : ++it) {
240     const QVariant v = *it;
241     insertLoopVariables( c, listSize, i );
242
243     if ( unpack ) {
244       if ( v.type() == QVariant::List ) {
245         QVariantList vList = v.toList();
246         int varsSize = qMin( m_loopVars.size(), vList.size() );
247         int j = 0;
248         for ( ; j < varsSize; ++j ) {
249           c->insert( m_loopVars.at( j ), vList.at( j ) );
250         }
251         // If any of the named vars don't have an item in the context,
252         // insert an invalid object for them.
253         for ( ; j < m_loopVars.size(); ++j ) {
254           c->insert( m_loopVars.at( j ), QVariant() );
255         }
256
257       } else {
258         // We don't have a hash, but we have to unpack several values from each item
259         // in the list. And each item in the list is not itself a list.
260         // Probably have a list of objects that we're taking properties from.
261         Q_FOREACH( const QString &loopVar, m_loopVars ) {
262           c->push();
263           c->insert( QLatin1String( "var" ), v );
264           QVariant v = FilterExpression( QLatin1String( "var." ) + loopVar, 0 ).resolve( c );
265           c->pop();
266           c->insert( loopVar, v );
267         }
268       }
269     } else {
270       c->insert( m_loopVars[0], v );
271     }
272     renderLoop( stream, c );
273     ++i;
274   }
275   c->pop();
276 }
277