Cutelee  6.2.0
for.cpp
1 /*
2  This file is part of the Cutelee 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 "metaenumvariable_p.h"
25 #include "parser.h"
26 
27 #include <QSequentialIterable>
28 
29 ForNodeFactory::ForNodeFactory() = default;
30 
31 Node *ForNodeFactory::getNode(const QString &tagContent, Parser *p) const
32 {
33  auto expr = smartSplit(tagContent);
34 
35  if (expr.size() < 4) {
36  throw Cutelee::Exception(
37  TagSyntaxError,
38  QStringLiteral("'for' statements should have at least four words: %1")
39  .arg(tagContent));
40  }
41 
42  expr.takeAt(0);
43 
44  int reversed = ForNode::IsNotReversed;
45  if (expr.last() == QStringLiteral("reversed")) {
46  reversed = ForNode::IsReversed;
47  expr.removeLast();
48  }
49 
50  if (expr.at(expr.size() - 2) != QStringLiteral("in")) {
51  throw Cutelee::Exception(
52  TagSyntaxError,
53  QStringLiteral("'for' statements should use the form 'for x in y': %1")
54  .arg(tagContent));
55  }
56 
57  QStringList vars;
58  const auto parts = expr.mid(0, expr.size() - 2);
59  for (const QString &arg : parts) {
60  const auto args = arg.split(QLatin1Char(','), Qt::SkipEmptyParts);
61  for (const QString &var : args) {
62  if (var.isEmpty()) {
63  throw Cutelee::Exception(
64  TagSyntaxError,
65  QStringLiteral("'for' tag received invalid argument"));
66  }
67  }
68  vars << args;
69  }
70 
71 
72 
73  FilterExpression fe(expr.last(), p);
74 
75  auto n = new ForNode(vars, fe, reversed, p);
76 
77  auto loopNodes
78  = p->parse(n, {QStringLiteral("empty"), QStringLiteral("endfor")});
79  n->setLoopList(loopNodes);
80 
81  NodeList emptyNodes;
82  if (p->takeNextToken().content == QStringLiteral("empty")) {
83  emptyNodes = p->parse(n, QStringLiteral("endfor"));
84  n->setEmptyList(emptyNodes);
85  // skip past the endfor tag
86  p->removeNextToken();
87  }
88 
89  return n;
90 }
91 
92 ForNode::ForNode(const QStringList &loopVars, const FilterExpression &fe,
93  int reversed, QObject *parent)
94  : Node(parent), m_loopVars(loopVars), m_filterExpression(fe),
95  m_isReversed(reversed)
96 {
97 }
98 
99 void ForNode::setLoopList(const NodeList &loopNodeList)
100 {
101  m_loopNodeList = loopNodeList;
102 }
103 
104 void ForNode::setEmptyList(const NodeList &emptyList)
105 {
106  m_emptyNodeList = emptyList;
107 }
108 
109 static const char forloop[] = "forloop";
110 static const char parentloop[] = "parentloop";
111 
112 void ForNode::insertLoopVariables(Context *c, int listSize, int i)
113 {
114  auto forloopHash = c->lookup(QStringLiteral("forloop")).value<QVariantHash>();
115  // some magic variables injected into the context while rendering.
116  forloopHash.insert(QStringLiteral("counter0"), i);
117  forloopHash.insert(QStringLiteral("counter"), i + 1);
118  forloopHash.insert(QStringLiteral("revcounter"), listSize - i);
119  forloopHash.insert(QStringLiteral("revcounter0"), listSize - i - 1);
120  forloopHash.insert(QStringLiteral("first"), (i == 0));
121  forloopHash.insert(QStringLiteral("last"), (i == listSize - 1));
122  c->insert(QLatin1String(forloop), forloopHash);
123 }
124 
125 void ForNode::renderLoop(OutputStream *stream, Context *c) const
126 {
127  for (auto j = 0; j < m_loopNodeList.size(); j++) {
128  m_loopNodeList[j]->render(stream, c);
129  }
130 }
131 
132 void ForNode::render(OutputStream *stream, Context *c) const
133 {
134  QVariantHash forloopHash;
135 
136  auto parentLoopVariant = c->lookup(QLatin1String(forloop));
137  if (parentLoopVariant.isValid()) {
138  // This is a nested loop.
139  forloopHash = parentLoopVariant.value<QVariantHash>();
140  forloopHash.insert(QLatin1String(parentloop),
141  parentLoopVariant.value<QVariantHash>());
142  c->insert(QLatin1String(forloop), forloopHash);
143  }
144 
145  auto unpack = m_loopVars.size() > 1;
146 
147  c->push();
148 
149  auto varFE = m_filterExpression.resolve(c);
150 
151  if (varFE.userType() == qMetaTypeId<MetaEnumVariable>()) {
152  const auto mev = varFE.value<MetaEnumVariable>();
153 
154  if (mev.value != -1) {
155  c->pop();
156  return m_emptyNodeList.render(stream, c);
157  }
158 
159  QVariantList list;
160  for (auto row = 0; row < mev.enumerator.keyCount(); ++row) {
161  list << QVariant::fromValue(MetaEnumVariable(mev.enumerator, row));
162  }
163  varFE = list;
164  }
165 
166  if (!varFE.canConvert<QVariantList>()) {
167  c->pop();
168  return m_emptyNodeList.render(stream, c);
169  }
170 
171  auto iter = varFE.value<QSequentialIterable>();
172  const auto listSize = iter.size();
173 
174  // If it's an iterable type, iterate, otherwise it's a list of one.
175  if (listSize < 1) {
176  c->pop();
177  return m_emptyNodeList.render(stream, c);
178  }
179 
180  auto i = 0;
181  for (auto it = m_isReversed == IsReversed ? iter.end() - 1 : iter.begin();
182  m_isReversed == IsReversed ? it != iter.begin() - 1 : it != iter.end();
183  m_isReversed == IsReversed ? --it : ++it) {
184  const auto v = *it;
185  insertLoopVariables(c, listSize, i);
186 
187  if (unpack) {
188  if (v.userType() == qMetaTypeId<QVariantList>()) {
189  auto vList = v.value<QVariantList>();
190  auto varsSize = qMin(m_loopVars.size(), vList.size());
191  auto j = 0;
192  for (; j < varsSize; ++j) {
193  c->insert(m_loopVars.at(j), vList.at(j));
194  }
195  // If any of the named vars don't have an item in the context,
196  // insert an invalid object for them.
197  for (; j < m_loopVars.size(); ++j) {
198  c->insert(m_loopVars.at(j), QVariant());
199  }
200 
201  } else {
202  // We don't have a hash, but we have to unpack several values
203  // from each
204  // item
205  // in the list. And each item in the list is not itself a list.
206  // Probably have a list of objects that we're taking properties
207  // from.
208  for (const QString &loopVar : m_loopVars) {
209  c->push();
210  c->insert(QStringLiteral("var"), v);
211  auto v = FilterExpression(QStringLiteral("var.") + loopVar, 0)
212  .resolve(c);
213  c->pop();
214  c->insert(loopVar, v);
215  }
216  }
217  } else {
218  c->insert(m_loopVars[0], v);
219  }
220  renderLoop(stream, c);
221  ++i;
222  }
223  c->pop();
224 }
NodeList parse(Node *parent, const QStringList &stopAt={})
Definition: parser.cpp:180
The Context class holds the context to render a Template with.
Definition: context.h:118
Node * getNode(const QString &tagContent, Parser *p) const override
Definition: for.cpp:31
QVariant fromValue(T &&value)
const_reference at(qsizetype i) const const
void render(OutputStream *stream, Context *c) const override
Definition: for.cpp:132
T value() const const
qsizetype size() const const
qsizetype size() const const
QString content
The content of this Token.
Definition: token.h:51
Base class for all nodes.
Definition: node.h:77
Token takeNextToken()
Definition: parser.cpp:291
SkipEmptyParts
The OutputStream class is used to render templates to a QTextStream.
Definition: outputstream.h:80
QVariant resolve(OutputStream *stream, Context *c) const
The Parser class processes a string template into a tree of nodes.
Definition: parser.h:48
QList< T > mid(qsizetype pos, qsizetype length) const const
A list of Nodes with some convenience API for rendering them.
Definition: node.h:147
A FilterExpression object represents a filter expression in a template.
Q_INVOKABLE QStringList smartSplit(const QString &str) const
Definition: node.cpp:202
virtual QVariant lookup(const QString &str) const
Definition: context.cpp:100
Definition: for.h:37
void removeNextToken()
Definition: parser.cpp:297
void insert(const QString &name, QObject *object)
Definition: context.cpp:145
void render(OutputStream *stream, Context *c) const
Definition: node.cpp:177
An exception for use when implementing template tags.
Definition: exception.h:84