LP-311 Remove basic/advanced stabilization tab auto-switch (autotune/txpid lock issues)
[librepilot.git] / ground / gcs / src / libs / utils / mustache.cpp
blob8cb33b73474486b17bd13f1f6d69a520c22fc498
1 /*
2 Copyright 2012, Robert Knight
4 Redistribution and use in source and binary forms, with or without modification,
5 are permitted provided that the following conditions are met:
7 Redistributions of source code must retain the above copyright notice,
8 this list of conditions and the following disclaimer.
10 Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
15 #include "mustache.h"
17 #include <QtCore/QDebug>
18 #include <QtCore/QFile>
19 #include <QtCore/QStringList>
20 #include <QtCore/QTextStream>
22 using namespace Mustache;
24 QString Mustache::renderTemplate(const QString & templateString, const QVariantHash & args)
26 Mustache::QtVariantContext context(args);
27 Mustache::Renderer renderer;
29 return renderer.render(templateString, &context);
32 QString escapeHtml(const QString & input)
34 QString escaped(input);
36 for (int i = 0; i < escaped.count();) {
37 const char *replacement = 0;
38 ushort ch = escaped.at(i).unicode();
39 if (ch == '&') {
40 replacement = "&amp;";
41 } else if (ch == '<') {
42 replacement = "&lt;";
43 } else if (ch == '>') {
44 replacement = "&gt;";
45 } else if (ch == '"') {
46 replacement = "&quot;";
48 if (replacement) {
49 escaped.replace(i, 1, QLatin1String(replacement));
50 i += strlen(replacement);
51 } else {
52 ++i;
55 return escaped;
58 QString unescapeHtml(const QString & escaped)
60 QString unescaped(escaped);
62 unescaped.replace(QLatin1String("&lt;"), QLatin1String("<"));
63 unescaped.replace(QLatin1String("&gt;"), QLatin1String(">"));
64 unescaped.replace(QLatin1String("&amp;"), QLatin1String("&"));
65 unescaped.replace(QLatin1String("&quot;"), QLatin1String("\""));
66 return unescaped;
69 Context::Context(PartialResolver *resolver)
70 : m_partialResolver(resolver)
73 PartialResolver *Context::partialResolver() const
75 return m_partialResolver;
78 QString Context::partialValue(const QString & key) const
80 if (!m_partialResolver) {
81 return QString();
83 return m_partialResolver->getPartial(key);
86 bool Context::canEval(const QString &) const
88 return false;
91 QString Context::eval(const QString & key, const QString & _template, Renderer *renderer)
93 Q_UNUSED(key);
94 Q_UNUSED(_template);
95 Q_UNUSED(renderer);
97 return QString();
100 QtVariantContext::QtVariantContext(const QVariant & root, PartialResolver *resolver)
101 : Context(resolver)
103 m_contextStack << root;
106 QVariant variantMapValue(const QVariant & value, const QString & key)
108 if (value.userType() == QVariant::Map) {
109 return value.toMap().value(key);
110 } else {
111 return value.toHash().value(key);
115 QVariant variantMapValueForKeyPath(const QVariant & value, const QStringList keyPath)
117 if (keyPath.count() > 1) {
118 QVariant firstValue = variantMapValue(value, keyPath.first());
119 return firstValue.isNull() ? QVariant() : variantMapValueForKeyPath(firstValue, keyPath.mid(1));
120 } else if (!keyPath.isEmpty()) {
121 return variantMapValue(value, keyPath.first());
123 return QVariant();
126 QVariant QtVariantContext::value(const QString & key) const
128 if (key == "." && !m_contextStack.isEmpty()) {
129 return m_contextStack.last();
131 QStringList keyPath = key.split(".");
132 for (int i = m_contextStack.count() - 1; i >= 0; i--) {
133 QVariant value = variantMapValueForKeyPath(m_contextStack.at(i), keyPath);
134 if (!value.isNull()) {
135 return value;
138 return QVariant();
141 bool QtVariantContext::isFalse(const QString & key) const
143 QVariant value = this->value(key);
145 switch (value.userType()) {
146 case QVariant::Bool:
147 return !value.toBool();
149 case QVariant::List:
150 return value.toList().isEmpty();
152 case QVariant::Hash:
153 return value.toHash().isEmpty();
155 case QVariant::Map:
156 return value.toMap().isEmpty();
158 default:
159 return value.toString().isEmpty();
163 QString QtVariantContext::stringValue(const QString & key) const
165 if (isFalse(key)) {
166 return QString();
168 return value(key).toString();
171 void QtVariantContext::push(const QString & key, int index)
173 QVariant mapItem = value(key);
175 if (index == -1) {
176 m_contextStack << mapItem;
177 } else {
178 QVariantList list = mapItem.toList();
179 m_contextStack << list.value(index, QVariant());
183 void QtVariantContext::pop()
185 m_contextStack.pop();
188 int QtVariantContext::listCount(const QString & key) const
190 if (value(key).userType() == QVariant::List) {
191 return value(key).toList().count();
193 return 0;
196 bool QtVariantContext::canEval(const QString & key) const
198 return value(key).canConvert<fn_t>();
201 QString QtVariantContext::eval(const QString & key, const QString & _template, Renderer *renderer)
203 QVariant fn = value(key);
205 if (fn.isNull()) {
206 return QString();
208 return fn.value<fn_t>() (_template, renderer, this);
211 PartialMap::PartialMap(const QHash<QString, QString> & partials)
212 : m_partials(partials)
215 QString PartialMap::getPartial(const QString & name)
217 return m_partials.value(name);
220 PartialFileLoader::PartialFileLoader(const QString & basePath)
221 : m_basePath(basePath)
224 QString PartialFileLoader::getPartial(const QString & name)
226 if (!m_cache.contains(name)) {
227 QString path = m_basePath + '/' + name + ".mustache";
228 QFile file(path);
229 if (file.open(QIODevice::ReadOnly)) {
230 QTextStream stream(&file);
231 m_cache.insert(name, stream.readAll());
234 return m_cache.value(name);
237 Renderer::Renderer()
238 : m_errorPos(-1)
239 , m_defaultTagStartMarker("{{")
240 , m_defaultTagEndMarker("}}")
243 QString Renderer::error() const
245 return m_error;
248 int Renderer::errorPos() const
250 return m_errorPos;
253 QString Renderer::errorPartial() const
255 return m_errorPartial;
258 QString Renderer::render(const QString & _template, Context *context)
260 m_error.clear();
261 m_errorPos = -1;
262 m_errorPartial.clear();
264 m_tagStartMarker = m_defaultTagStartMarker;
265 m_tagEndMarker = m_defaultTagEndMarker;
267 return render(_template, 0, _template.length(), context);
270 QString Renderer::render(const QString & _template, int startPos, int endPos, Context *context)
272 QString output;
273 int lastTagEnd = startPos;
275 while (m_errorPos == -1) {
276 Tag tag = findTag(_template, lastTagEnd, endPos);
277 if (tag.type == Tag::Null) {
278 output += _template.midRef(lastTagEnd, endPos - lastTagEnd);
279 break;
281 output += _template.midRef(lastTagEnd, tag.start - lastTagEnd);
282 switch (tag.type) {
283 case Tag::Value:
285 QString value = context->stringValue(tag.key);
286 if (tag.escapeMode == Tag::Escape) {
287 value = escapeHtml(value);
288 } else if (tag.escapeMode == Tag::Unescape) {
289 value = unescapeHtml(value);
291 output += value;
292 lastTagEnd = tag.end;
294 break;
295 case Tag::SectionStart:
297 Tag endTag = findEndTag(_template, tag, endPos);
298 if (endTag.type == Tag::Null) {
299 if (m_errorPos == -1) {
300 setError("No matching end tag found for section", tag.start);
302 } else {
303 int listCount = context->listCount(tag.key);
304 if (listCount > 0) {
305 for (int i = 0; i < listCount; i++) {
306 context->push(tag.key, i);
307 output += render(_template, tag.end, endTag.start, context);
308 context->pop();
310 } else if (context->canEval(tag.key)) {
311 output += context->eval(tag.key, _template.mid(tag.end, endTag.start - tag.end), this);
312 } else if (!context->isFalse(tag.key)) {
313 context->push(tag.key);
314 output += render(_template, tag.end, endTag.start, context);
315 context->pop();
317 lastTagEnd = endTag.end;
320 break;
321 case Tag::InvertedSectionStart:
323 Tag endTag = findEndTag(_template, tag, endPos);
324 if (endTag.type == Tag::Null) {
325 if (m_errorPos == -1) {
326 setError("No matching end tag found for inverted section", tag.start);
328 } else {
329 if (context->isFalse(tag.key)) {
330 output += render(_template, tag.end, endTag.start, context);
332 lastTagEnd = endTag.end;
335 break;
336 case Tag::SectionEnd:
337 setError("Unexpected end tag", tag.start);
338 lastTagEnd = tag.end;
339 break;
340 case Tag::Partial:
342 QString tagStartMarker = m_tagStartMarker;
343 QString tagEndMarker = m_tagEndMarker;
345 m_tagStartMarker = m_defaultTagStartMarker;
346 m_tagEndMarker = m_defaultTagEndMarker;
348 m_partialStack.push(tag.key);
350 QString partial = context->partialValue(tag.key);
351 output += render(partial, 0, partial.length(), context);
352 lastTagEnd = tag.end;
354 m_partialStack.pop();
356 m_tagStartMarker = tagStartMarker;
357 m_tagEndMarker = tagEndMarker;
359 break;
360 case Tag::SetDelimiter:
361 lastTagEnd = tag.end;
362 break;
363 case Tag::Comment:
364 lastTagEnd = tag.end;
365 break;
366 case Tag::Null:
367 break;
371 return output;
374 void Renderer::setError(const QString & error, int pos)
376 Q_ASSERT(!error.isEmpty());
377 Q_ASSERT(pos >= 0);
379 m_error = error;
380 m_errorPos = pos;
382 if (!m_partialStack.isEmpty()) {
383 m_errorPartial = m_partialStack.top();
387 Tag Renderer::findTag(const QString & content, int pos, int endPos)
389 int tagStartPos = content.indexOf(m_tagStartMarker, pos);
391 if (tagStartPos == -1 || tagStartPos >= endPos) {
392 return Tag();
395 int tagEndPos = content.indexOf(m_tagEndMarker, tagStartPos + m_tagStartMarker.length());
396 if (tagEndPos == -1) {
397 return Tag();
399 tagEndPos += m_tagEndMarker.length();
401 Tag tag;
402 tag.type = Tag::Value;
403 tag.start = tagStartPos;
404 tag.end = tagEndPos;
406 pos = tagStartPos + m_tagStartMarker.length();
407 endPos = tagEndPos - m_tagEndMarker.length();
409 QChar typeChar = content.at(pos);
411 if (typeChar == '#') {
412 tag.type = Tag::SectionStart;
413 tag.key = readTagName(content, pos + 1, endPos);
414 } else if (typeChar == '^') {
415 tag.type = Tag::InvertedSectionStart;
416 tag.key = readTagName(content, pos + 1, endPos);
417 } else if (typeChar == '/') {
418 tag.type = Tag::SectionEnd;
419 tag.key = readTagName(content, pos + 1, endPos);
420 } else if (typeChar == '!') {
421 tag.type = Tag::Comment;
422 } else if (typeChar == '>') {
423 tag.type = Tag::Partial;
424 tag.key = readTagName(content, pos + 1, endPos);
425 } else if (typeChar == '=') {
426 tag.type = Tag::SetDelimiter;
427 readSetDelimiter(content, pos + 1, tagEndPos - m_tagEndMarker.length());
428 } else {
429 if (typeChar == '&') {
430 tag.escapeMode = Tag::Unescape;
431 ++pos;
432 } else if (typeChar == '{') {
433 tag.escapeMode = Tag::Raw;
434 ++pos;
435 int endTache = content.indexOf('}', pos);
436 if (endTache == tag.end - m_tagEndMarker.length()) {
437 ++tag.end;
438 } else {
439 endPos = endTache;
442 tag.type = Tag::Value;
443 tag.key = readTagName(content, pos, endPos);
446 if (tag.type != Tag::Value) {
447 expandTag(tag, content);
450 return tag;
453 QString Renderer::readTagName(const QString & content, int pos, int endPos)
455 QString name;
457 name.reserve(endPos - pos);
458 while (content.at(pos).isSpace()) {
459 ++pos;
461 while (!content.at(pos).isSpace() && pos < endPos) {
462 name += content.at(pos);
463 ++pos;
465 return name;
468 void Renderer::readSetDelimiter(const QString & content, int pos, int endPos)
470 QString startMarker;
471 QString endMarker;
473 while (content.at(pos).isSpace() && pos < endPos) {
474 ++pos;
477 while (!content.at(pos).isSpace() && pos < endPos) {
478 if (content.at(pos) == '=') {
479 setError("Custom delimiters may not contain '='.", pos);
480 return;
482 startMarker += content.at(pos);
483 ++pos;
486 while (content.at(pos).isSpace() && pos < endPos) {
487 ++pos;
490 while (!content.at(pos).isSpace() && pos < endPos - 1) {
491 if (content.at(pos) == '=') {
492 setError("Custom delimiters may not contain '='.", pos);
493 return;
495 endMarker += content.at(pos);
496 ++pos;
499 m_tagStartMarker = startMarker;
500 m_tagEndMarker = endMarker;
503 Tag Renderer::findEndTag(const QString & content, const Tag & startTag, int endPos)
505 int tagDepth = 1;
506 int pos = startTag.end;
508 while (true) {
509 Tag nextTag = findTag(content, pos, endPos);
510 if (nextTag.type == Tag::Null) {
511 return nextTag;
512 } else if (nextTag.type == Tag::SectionStart || nextTag.type == Tag::InvertedSectionStart) {
513 ++tagDepth;
514 } else if (nextTag.type == Tag::SectionEnd) {
515 --tagDepth;
516 if (tagDepth == 0) {
517 if (nextTag.key != startTag.key) {
518 setError("Tag start/end key mismatch", nextTag.start);
519 return Tag();
521 return nextTag;
524 pos = nextTag.end;
527 return Tag();
530 void Renderer::setTagMarkers(const QString & startMarker, const QString & endMarker)
532 m_defaultTagStartMarker = startMarker;
533 m_defaultTagEndMarker = endMarker;
536 void Renderer::expandTag(Tag & tag, const QString & content)
538 int start = tag.start;
539 int end = tag.end;
541 // Move start to beginning of line.
542 while (start > 0 && content.at(start - 1) != QLatin1Char('\n')) {
543 --start;
544 if (!content.at(start).isSpace()) {
545 return; // Not standalone.
549 // Move end to one past end of line.
550 while (end <= content.size() && content.at(end - 1) != QLatin1Char('\n')) {
551 if (end < content.size() && !content.at(end).isSpace()) {
552 return; // Not standalone.
554 ++end;
557 tag.start = start;
558 tag.end = end;