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 "block.h"
22 
23 #include "blockcontext.h"
24 #include "exception.h"
25 #include "parser.h"
26 #include "rendercontext.h"
27 #include "template.h"
28 #include "util.h"
29 
30 static const char *const __loadedBlocks = "__loadedBlocks";
31 
32 // Terrible hack warning.
33 #define BLOCK_CONTEXT_KEY 0
34 
BlockNodeFactory(QObject * parent)35 BlockNodeFactory::BlockNodeFactory(QObject *parent)
36     : AbstractNodeFactory(parent)
37 {
38 }
39 
getNode(const QString & tagContent,Parser * p) const40 Node *BlockNodeFactory::getNode(const QString &tagContent, Parser *p) const
41 {
42   const auto expr = tagContent.split(QLatin1Char(' '), QString::SkipEmptyParts);
43 
44   if (expr.size() != 2) {
45     throw Grantlee::Exception(TagSyntaxError,
46                               QStringLiteral("block tag takes one argument"));
47   }
48 
49   const auto blockName = expr.at(1);
50 
51   auto loadedBlocksVariant = p->property(__loadedBlocks);
52   QVariantList blockVariantList;
53 
54   if (loadedBlocksVariant.isValid()
55       && loadedBlocksVariant.userType() == qMetaTypeId<QVariantList>()) {
56     blockVariantList = loadedBlocksVariant.value<QVariantList>();
57     for (auto &item : blockVariantList) {
58       const auto blockNodeName = item.value<QString>();
59 
60       if (blockNodeName == blockName) {
61         throw Grantlee::Exception(
62             TagSyntaxError,
63             QStringLiteral("'block' tag with name '%1' appears more than once.")
64                 .arg(blockName));
65       }
66     }
67   }
68   // Block not already in list.
69   blockVariantList.append(blockName);
70   loadedBlocksVariant = QVariant(blockVariantList);
71 
72   p->setProperty(__loadedBlocks, loadedBlocksVariant);
73 
74   auto n = new BlockNode(blockName, p);
75   const auto list = p->parse(n, QStringLiteral("endblock"));
76 
77   auto endBlock = p->takeNextToken();
78   const QStringList acceptableBlocks{QStringLiteral("endblock"),
79                                      QStringLiteral("endblock ") + blockName};
80   if (!acceptableBlocks.contains(endBlock.content)) {
81     p->invalidBlockTag(endBlock, QStringLiteral("endblock"), acceptableBlocks);
82   }
83 
84   n->setNodeList(list);
85 
86   return n;
87 }
88 
BlockNode(const QString & name,QObject * parent)89 BlockNode::BlockNode(const QString &name, QObject *parent)
90     : Node(parent), m_name(name), m_stream(0)
91 {
92   qRegisterMetaType<Grantlee::SafeString>("Grantlee::SafeString");
93 }
94 
~BlockNode()95 BlockNode::~BlockNode() {}
96 
setNodeList(const NodeList & list) const97 void BlockNode::setNodeList(const NodeList &list) const { m_list = list; }
98 
render(OutputStream * stream,Context * c) const99 void BlockNode::render(OutputStream *stream, Context *c) const
100 {
101   QVariant &variant = c->renderContext()->data(BLOCK_CONTEXT_KEY);
102   auto blockContext = variant.value<BlockContext>();
103 
104   c->push();
105 
106   if (blockContext.isEmpty()) {
107     m_context = c;
108     m_stream = stream;
109     c->insert(QStringLiteral("block"),
110               QVariant::fromValue(
111                   const_cast<QObject *>(static_cast<const QObject *>(this))));
112     m_list.render(stream, c);
113     m_stream = 0;
114   } else {
115     auto block = static_cast<const BlockNode *>(blockContext.pop(m_name));
116     variant.setValue(blockContext);
117     auto push = block;
118     if (!block)
119       block = this;
120 
121     const auto list = block->m_list;
122 
123     block = new BlockNode(block->m_name, 0);
124     block->setNodeList(list);
125     block->m_context = c;
126     block->m_stream = stream;
127     c->insert(QStringLiteral("block"),
128               QVariant::fromValue(
129                   const_cast<QObject *>(static_cast<const QObject *>(block))));
130     list.render(stream, c);
131 
132     delete block;
133     if (push) {
134       blockContext.push(m_name, push);
135       variant.setValue(blockContext);
136     }
137   }
138   c->pop();
139 }
140 
getSuper() const141 SafeString BlockNode::getSuper() const
142 {
143   if (m_context->renderContext()->contains(BLOCK_CONTEXT_KEY)) {
144     QVariant &variant = m_context->renderContext()->data(BLOCK_CONTEXT_KEY);
145     const auto blockContext = variant.value<BlockContext>();
146     auto block = blockContext.getBlock(m_name);
147     if (block) {
148       QString superContent;
149       QTextStream superTextStream(&superContent);
150       auto superStream = m_stream->clone(&superTextStream);
151       const_cast<BlockNode *>(this)->render(superStream.data(), m_context);
152       return markSafe(superContent);
153     }
154   }
155   return SafeString();
156 }
157 
nodeList() const158 NodeList BlockNode::nodeList() const { return m_list; }
159 
name() const160 QString BlockNode::name() const { return m_name; }
161