mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-01-18 03:52:11 +01:00
OP-1538 Added Mustache template renderer by Robert Knight.
Added #include "utils_global.h" Added QTCREATOR_UTILS_EXPORT to all public classes in header file. See file header comments for Original Authors Copyright information. See https://github.com/robertknight/qt-mustache See http://mustache.github.io/
This commit is contained in:
parent
d4d6789198
commit
26eff1bb4e
559
ground/openpilotgcs/src/libs/utils/mustache.cpp
Normal file
559
ground/openpilotgcs/src/libs/utils/mustache.cpp
Normal file
@ -0,0 +1,559 @@
|
||||
/*
|
||||
Copyright 2012, Robert Knight
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
*/
|
||||
|
||||
#include "mustache.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QTextStream>
|
||||
|
||||
using namespace Mustache;
|
||||
|
||||
QString Mustache::renderTemplate(const QString & templateString, const QVariantHash & args)
|
||||
{
|
||||
Mustache::QtVariantContext context(args);
|
||||
Mustache::Renderer renderer;
|
||||
|
||||
return renderer.render(templateString, &context);
|
||||
}
|
||||
|
||||
QString escapeHtml(const QString & input)
|
||||
{
|
||||
QString escaped(input);
|
||||
|
||||
for (int i = 0; i < escaped.count();) {
|
||||
const char *replacement = 0;
|
||||
ushort ch = escaped.at(i).unicode();
|
||||
if (ch == '&') {
|
||||
replacement = "&";
|
||||
} else if (ch == '<') {
|
||||
replacement = "<";
|
||||
} else if (ch == '>') {
|
||||
replacement = ">";
|
||||
} else if (ch == '"') {
|
||||
replacement = """;
|
||||
}
|
||||
if (replacement) {
|
||||
escaped.replace(i, 1, QLatin1String(replacement));
|
||||
i += strlen(replacement);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
QString unescapeHtml(const QString & escaped)
|
||||
{
|
||||
QString unescaped(escaped);
|
||||
|
||||
unescaped.replace(QLatin1String("<"), QLatin1String("<"));
|
||||
unescaped.replace(QLatin1String(">"), QLatin1String(">"));
|
||||
unescaped.replace(QLatin1String("&"), QLatin1String("&"));
|
||||
unescaped.replace(QLatin1String("""), QLatin1String("\""));
|
||||
return unescaped;
|
||||
}
|
||||
|
||||
Context::Context(PartialResolver *resolver)
|
||||
: m_partialResolver(resolver)
|
||||
{}
|
||||
|
||||
PartialResolver *Context::partialResolver() const
|
||||
{
|
||||
return m_partialResolver;
|
||||
}
|
||||
|
||||
QString Context::partialValue(const QString & key) const
|
||||
{
|
||||
if (!m_partialResolver) {
|
||||
return QString();
|
||||
}
|
||||
return m_partialResolver->getPartial(key);
|
||||
}
|
||||
|
||||
bool Context::canEval(const QString &) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Context::eval(const QString & key, const QString & _template, Renderer *renderer)
|
||||
{
|
||||
Q_UNUSED(key);
|
||||
Q_UNUSED(_template);
|
||||
Q_UNUSED(renderer);
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QtVariantContext::QtVariantContext(const QVariant & root, PartialResolver *resolver)
|
||||
: Context(resolver)
|
||||
{
|
||||
m_contextStack << root;
|
||||
}
|
||||
|
||||
QVariant variantMapValue(const QVariant & value, const QString & key)
|
||||
{
|
||||
if (value.userType() == QVariant::Map) {
|
||||
return value.toMap().value(key);
|
||||
} else {
|
||||
return value.toHash().value(key);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant variantMapValueForKeyPath(const QVariant & value, const QStringList keyPath)
|
||||
{
|
||||
if (keyPath.count() > 1) {
|
||||
QVariant firstValue = variantMapValue(value, keyPath.first());
|
||||
return firstValue.isNull() ? QVariant() : variantMapValueForKeyPath(firstValue, keyPath.mid(1));
|
||||
} else if (!keyPath.isEmpty()) {
|
||||
return variantMapValue(value, keyPath.first());
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant QtVariantContext::value(const QString & key) const
|
||||
{
|
||||
if (key == "." && !m_contextStack.isEmpty()) {
|
||||
return m_contextStack.last();
|
||||
}
|
||||
QStringList keyPath = key.split(".");
|
||||
for (int i = m_contextStack.count() - 1; i >= 0; i--) {
|
||||
QVariant value = variantMapValueForKeyPath(m_contextStack.at(i), keyPath);
|
||||
if (!value.isNull()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool QtVariantContext::isFalse(const QString & key) const
|
||||
{
|
||||
QVariant value = this->value(key);
|
||||
|
||||
switch (value.userType()) {
|
||||
case QVariant::Bool:
|
||||
return !value.toBool();
|
||||
|
||||
case QVariant::List:
|
||||
return value.toList().isEmpty();
|
||||
|
||||
case QVariant::Hash:
|
||||
return value.toHash().isEmpty();
|
||||
|
||||
case QVariant::Map:
|
||||
return value.toMap().isEmpty();
|
||||
|
||||
default:
|
||||
return value.toString().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
QString QtVariantContext::stringValue(const QString & key) const
|
||||
{
|
||||
if (isFalse(key)) {
|
||||
return QString();
|
||||
}
|
||||
return value(key).toString();
|
||||
}
|
||||
|
||||
void QtVariantContext::push(const QString & key, int index)
|
||||
{
|
||||
QVariant mapItem = value(key);
|
||||
|
||||
if (index == -1) {
|
||||
m_contextStack << mapItem;
|
||||
} else {
|
||||
QVariantList list = mapItem.toList();
|
||||
m_contextStack << list.value(index, QVariant());
|
||||
}
|
||||
}
|
||||
|
||||
void QtVariantContext::pop()
|
||||
{
|
||||
m_contextStack.pop();
|
||||
}
|
||||
|
||||
int QtVariantContext::listCount(const QString & key) const
|
||||
{
|
||||
if (value(key).userType() == QVariant::List) {
|
||||
return value(key).toList().count();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool QtVariantContext::canEval(const QString & key) const
|
||||
{
|
||||
return value(key).canConvert<fn_t>();
|
||||
}
|
||||
|
||||
QString QtVariantContext::eval(const QString & key, const QString & _template, Renderer *renderer)
|
||||
{
|
||||
QVariant fn = value(key);
|
||||
|
||||
if (fn.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return fn.value<fn_t>() (_template, renderer, this);
|
||||
}
|
||||
|
||||
PartialMap::PartialMap(const QHash<QString, QString> & partials)
|
||||
: m_partials(partials)
|
||||
{}
|
||||
|
||||
QString PartialMap::getPartial(const QString & name)
|
||||
{
|
||||
return m_partials.value(name);
|
||||
}
|
||||
|
||||
PartialFileLoader::PartialFileLoader(const QString & basePath)
|
||||
: m_basePath(basePath)
|
||||
{}
|
||||
|
||||
QString PartialFileLoader::getPartial(const QString & name)
|
||||
{
|
||||
if (!m_cache.contains(name)) {
|
||||
QString path = m_basePath + '/' + name + ".mustache";
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QTextStream stream(&file);
|
||||
m_cache.insert(name, stream.readAll());
|
||||
}
|
||||
}
|
||||
return m_cache.value(name);
|
||||
}
|
||||
|
||||
Renderer::Renderer()
|
||||
: m_errorPos(-1)
|
||||
, m_defaultTagStartMarker("{{")
|
||||
, m_defaultTagEndMarker("}}")
|
||||
{}
|
||||
|
||||
QString Renderer::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
int Renderer::errorPos() const
|
||||
{
|
||||
return m_errorPos;
|
||||
}
|
||||
|
||||
QString Renderer::errorPartial() const
|
||||
{
|
||||
return m_errorPartial;
|
||||
}
|
||||
|
||||
QString Renderer::render(const QString & _template, Context *context)
|
||||
{
|
||||
m_error.clear();
|
||||
m_errorPos = -1;
|
||||
m_errorPartial.clear();
|
||||
|
||||
m_tagStartMarker = m_defaultTagStartMarker;
|
||||
m_tagEndMarker = m_defaultTagEndMarker;
|
||||
|
||||
return render(_template, 0, _template.length(), context);
|
||||
}
|
||||
|
||||
QString Renderer::render(const QString & _template, int startPos, int endPos, Context *context)
|
||||
{
|
||||
QString output;
|
||||
int lastTagEnd = startPos;
|
||||
|
||||
while (m_errorPos == -1) {
|
||||
Tag tag = findTag(_template, lastTagEnd, endPos);
|
||||
if (tag.type == Tag::Null) {
|
||||
output += _template.midRef(lastTagEnd, endPos - lastTagEnd);
|
||||
break;
|
||||
}
|
||||
output += _template.midRef(lastTagEnd, tag.start - lastTagEnd);
|
||||
switch (tag.type) {
|
||||
case Tag::Value:
|
||||
{
|
||||
QString value = context->stringValue(tag.key);
|
||||
if (tag.escapeMode == Tag::Escape) {
|
||||
value = escapeHtml(value);
|
||||
} else if (tag.escapeMode == Tag::Unescape) {
|
||||
value = unescapeHtml(value);
|
||||
}
|
||||
output += value;
|
||||
lastTagEnd = tag.end;
|
||||
}
|
||||
break;
|
||||
case Tag::SectionStart:
|
||||
{
|
||||
Tag endTag = findEndTag(_template, tag, endPos);
|
||||
if (endTag.type == Tag::Null) {
|
||||
if (m_errorPos == -1) {
|
||||
setError("No matching end tag found for section", tag.start);
|
||||
}
|
||||
} else {
|
||||
int listCount = context->listCount(tag.key);
|
||||
if (listCount > 0) {
|
||||
for (int i = 0; i < listCount; i++) {
|
||||
context->push(tag.key, i);
|
||||
output += render(_template, tag.end, endTag.start, context);
|
||||
context->pop();
|
||||
}
|
||||
} else if (context->canEval(tag.key)) {
|
||||
output += context->eval(tag.key, _template.mid(tag.end, endTag.start - tag.end), this);
|
||||
} else if (!context->isFalse(tag.key)) {
|
||||
context->push(tag.key);
|
||||
output += render(_template, tag.end, endTag.start, context);
|
||||
context->pop();
|
||||
}
|
||||
lastTagEnd = endTag.end;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Tag::InvertedSectionStart:
|
||||
{
|
||||
Tag endTag = findEndTag(_template, tag, endPos);
|
||||
if (endTag.type == Tag::Null) {
|
||||
if (m_errorPos == -1) {
|
||||
setError("No matching end tag found for inverted section", tag.start);
|
||||
}
|
||||
} else {
|
||||
if (context->isFalse(tag.key)) {
|
||||
output += render(_template, tag.end, endTag.start, context);
|
||||
}
|
||||
lastTagEnd = endTag.end;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Tag::SectionEnd:
|
||||
setError("Unexpected end tag", tag.start);
|
||||
lastTagEnd = tag.end;
|
||||
break;
|
||||
case Tag::Partial:
|
||||
{
|
||||
QString tagStartMarker = m_tagStartMarker;
|
||||
QString tagEndMarker = m_tagEndMarker;
|
||||
|
||||
m_tagStartMarker = m_defaultTagStartMarker;
|
||||
m_tagEndMarker = m_defaultTagEndMarker;
|
||||
|
||||
m_partialStack.push(tag.key);
|
||||
|
||||
QString partial = context->partialValue(tag.key);
|
||||
output += render(partial, 0, partial.length(), context);
|
||||
lastTagEnd = tag.end;
|
||||
|
||||
m_partialStack.pop();
|
||||
|
||||
m_tagStartMarker = tagStartMarker;
|
||||
m_tagEndMarker = tagEndMarker;
|
||||
}
|
||||
break;
|
||||
case Tag::SetDelimiter:
|
||||
lastTagEnd = tag.end;
|
||||
break;
|
||||
case Tag::Comment:
|
||||
lastTagEnd = tag.end;
|
||||
break;
|
||||
case Tag::Null:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void Renderer::setError(const QString & error, int pos)
|
||||
{
|
||||
Q_ASSERT(!error.isEmpty());
|
||||
Q_ASSERT(pos >= 0);
|
||||
|
||||
m_error = error;
|
||||
m_errorPos = pos;
|
||||
|
||||
if (!m_partialStack.isEmpty()) {
|
||||
m_errorPartial = m_partialStack.top();
|
||||
}
|
||||
}
|
||||
|
||||
Tag Renderer::findTag(const QString & content, int pos, int endPos)
|
||||
{
|
||||
int tagStartPos = content.indexOf(m_tagStartMarker, pos);
|
||||
|
||||
if (tagStartPos == -1 || tagStartPos >= endPos) {
|
||||
return Tag();
|
||||
}
|
||||
|
||||
int tagEndPos = content.indexOf(m_tagEndMarker, tagStartPos + m_tagStartMarker.length());
|
||||
if (tagEndPos == -1) {
|
||||
return Tag();
|
||||
}
|
||||
tagEndPos += m_tagEndMarker.length();
|
||||
|
||||
Tag tag;
|
||||
tag.type = Tag::Value;
|
||||
tag.start = tagStartPos;
|
||||
tag.end = tagEndPos;
|
||||
|
||||
pos = tagStartPos + m_tagStartMarker.length();
|
||||
endPos = tagEndPos - m_tagEndMarker.length();
|
||||
|
||||
QChar typeChar = content.at(pos);
|
||||
|
||||
if (typeChar == '#') {
|
||||
tag.type = Tag::SectionStart;
|
||||
tag.key = readTagName(content, pos + 1, endPos);
|
||||
} else if (typeChar == '^') {
|
||||
tag.type = Tag::InvertedSectionStart;
|
||||
tag.key = readTagName(content, pos + 1, endPos);
|
||||
} else if (typeChar == '/') {
|
||||
tag.type = Tag::SectionEnd;
|
||||
tag.key = readTagName(content, pos + 1, endPos);
|
||||
} else if (typeChar == '!') {
|
||||
tag.type = Tag::Comment;
|
||||
} else if (typeChar == '>') {
|
||||
tag.type = Tag::Partial;
|
||||
tag.key = readTagName(content, pos + 1, endPos);
|
||||
} else if (typeChar == '=') {
|
||||
tag.type = Tag::SetDelimiter;
|
||||
readSetDelimiter(content, pos + 1, tagEndPos - m_tagEndMarker.length());
|
||||
} else {
|
||||
if (typeChar == '&') {
|
||||
tag.escapeMode = Tag::Unescape;
|
||||
++pos;
|
||||
} else if (typeChar == '{') {
|
||||
tag.escapeMode = Tag::Raw;
|
||||
++pos;
|
||||
int endTache = content.indexOf('}', pos);
|
||||
if (endTache == tag.end - m_tagEndMarker.length()) {
|
||||
++tag.end;
|
||||
} else {
|
||||
endPos = endTache;
|
||||
}
|
||||
}
|
||||
tag.type = Tag::Value;
|
||||
tag.key = readTagName(content, pos, endPos);
|
||||
}
|
||||
|
||||
if (tag.type != Tag::Value) {
|
||||
expandTag(tag, content);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
QString Renderer::readTagName(const QString & content, int pos, int endPos)
|
||||
{
|
||||
QString name;
|
||||
|
||||
name.reserve(endPos - pos);
|
||||
while (content.at(pos).isSpace()) {
|
||||
++pos;
|
||||
}
|
||||
while (!content.at(pos).isSpace() && pos < endPos) {
|
||||
name += content.at(pos);
|
||||
++pos;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
void Renderer::readSetDelimiter(const QString & content, int pos, int endPos)
|
||||
{
|
||||
QString startMarker;
|
||||
QString endMarker;
|
||||
|
||||
while (content.at(pos).isSpace() && pos < endPos) {
|
||||
++pos;
|
||||
}
|
||||
|
||||
while (!content.at(pos).isSpace() && pos < endPos) {
|
||||
if (content.at(pos) == '=') {
|
||||
setError("Custom delimiters may not contain '='.", pos);
|
||||
return;
|
||||
}
|
||||
startMarker += content.at(pos);
|
||||
++pos;
|
||||
}
|
||||
|
||||
while (content.at(pos).isSpace() && pos < endPos) {
|
||||
++pos;
|
||||
}
|
||||
|
||||
while (!content.at(pos).isSpace() && pos < endPos - 1) {
|
||||
if (content.at(pos) == '=') {
|
||||
setError("Custom delimiters may not contain '='.", pos);
|
||||
return;
|
||||
}
|
||||
endMarker += content.at(pos);
|
||||
++pos;
|
||||
}
|
||||
|
||||
m_tagStartMarker = startMarker;
|
||||
m_tagEndMarker = endMarker;
|
||||
}
|
||||
|
||||
Tag Renderer::findEndTag(const QString & content, const Tag & startTag, int endPos)
|
||||
{
|
||||
int tagDepth = 1;
|
||||
int pos = startTag.end;
|
||||
|
||||
while (true) {
|
||||
Tag nextTag = findTag(content, pos, endPos);
|
||||
if (nextTag.type == Tag::Null) {
|
||||
return nextTag;
|
||||
} else if (nextTag.type == Tag::SectionStart || nextTag.type == Tag::InvertedSectionStart) {
|
||||
++tagDepth;
|
||||
} else if (nextTag.type == Tag::SectionEnd) {
|
||||
--tagDepth;
|
||||
if (tagDepth == 0) {
|
||||
if (nextTag.key != startTag.key) {
|
||||
setError("Tag start/end key mismatch", nextTag.start);
|
||||
return Tag();
|
||||
}
|
||||
return nextTag;
|
||||
}
|
||||
}
|
||||
pos = nextTag.end;
|
||||
}
|
||||
|
||||
return Tag();
|
||||
}
|
||||
|
||||
void Renderer::setTagMarkers(const QString & startMarker, const QString & endMarker)
|
||||
{
|
||||
m_defaultTagStartMarker = startMarker;
|
||||
m_defaultTagEndMarker = endMarker;
|
||||
}
|
||||
|
||||
void Renderer::expandTag(Tag & tag, const QString & content)
|
||||
{
|
||||
int start = tag.start;
|
||||
int end = tag.end;
|
||||
|
||||
// Move start to beginning of line.
|
||||
while (start > 0 && content.at(start - 1) != QLatin1Char('\n')) {
|
||||
--start;
|
||||
if (!content.at(start).isSpace()) {
|
||||
return; // Not standalone.
|
||||
}
|
||||
}
|
||||
|
||||
// Move end to one past end of line.
|
||||
while (end <= content.size() && content.at(end - 1) != QLatin1Char('\n')) {
|
||||
if (end < content.size() && !content.at(end).isSpace()) {
|
||||
return; // Not standalone.
|
||||
}
|
||||
++end;
|
||||
}
|
||||
|
||||
tag.start = start;
|
||||
tag.end = end;
|
||||
}
|
266
ground/openpilotgcs/src/libs/utils/mustache.h
Normal file
266
ground/openpilotgcs/src/libs/utils/mustache.h
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
Copyright 2012, Robert Knight
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QStack>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include "utils_global.h"
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
#include <functional> /* for std::function */
|
||||
#endif
|
||||
|
||||
namespace Mustache {
|
||||
class PartialResolver;
|
||||
class Renderer;
|
||||
|
||||
/** Context is an interface that Mustache::Renderer::render() uses to
|
||||
* fetch substitutions for template tags.
|
||||
*/
|
||||
class QTCREATOR_UTILS_EXPORT Context {
|
||||
public:
|
||||
/** Create a context. @p resolver is used to fetch the expansions for any {{>partial}} tags
|
||||
* which appear in a template.
|
||||
*/
|
||||
explicit Context(PartialResolver *resolver = 0);
|
||||
virtual ~Context() {}
|
||||
|
||||
/** Returns a string representation of the value for @p key in the current context.
|
||||
* This is used to replace a Mustache value tag.
|
||||
*/
|
||||
virtual QString stringValue(const QString & key) const = 0;
|
||||
|
||||
/** Returns true if the value for @p key is 'false' or an empty list.
|
||||
* 'False' values typically include empty strings, the boolean value false etc.
|
||||
*
|
||||
* When processing a section Mustache tag, the section is not rendered if the key
|
||||
* is false, or for an inverted section tag, the section is only rendered if the key
|
||||
* is false.
|
||||
*/
|
||||
virtual bool isFalse(const QString & key) const = 0;
|
||||
|
||||
/** Returns the number of items in the list value for @p key or 0 if
|
||||
* the value for @p key is not a list.
|
||||
*/
|
||||
virtual int listCount(const QString & key) const = 0;
|
||||
|
||||
/** Set the current context to the value for @p key.
|
||||
* If index is >= 0, set the current context to the @p index'th value
|
||||
* in the list value for @p key.
|
||||
*/
|
||||
virtual void push(const QString & key, int index = -1) = 0;
|
||||
|
||||
/** Exit the current context. */
|
||||
virtual void pop() = 0;
|
||||
|
||||
/** Returns the partial template for a given @p key. */
|
||||
QString partialValue(const QString & key) const;
|
||||
|
||||
/** Returns the partial resolver passed to the constructor. */
|
||||
PartialResolver *partialResolver() const;
|
||||
|
||||
/** Returns true if eval() should be used to render section tags using @p key.
|
||||
* If canEval() returns true for a key, the renderer will pass the literal, unrendered
|
||||
* block of text for the section to eval() and replace the section with the result.
|
||||
*
|
||||
* canEval() and eval() are equivalents for callable objects (eg. lambdas) in other
|
||||
* Mustache implementations.
|
||||
*
|
||||
* The default implementation always returns false.
|
||||
*/
|
||||
virtual bool canEval(const QString & key) const;
|
||||
|
||||
/** Callback used to render a template section with the given @p key.
|
||||
* @p renderer will substitute the original section tag with the result of eval().
|
||||
*
|
||||
* The default implementation returns an empty string.
|
||||
*/
|
||||
virtual QString eval(const QString & key, const QString & _template, Renderer *renderer);
|
||||
|
||||
private:
|
||||
PartialResolver *m_partialResolver;
|
||||
};
|
||||
|
||||
/** A context implementation which wraps a QVariantHash or QVariantMap. */
|
||||
class QTCREATOR_UTILS_EXPORT QtVariantContext : public Context {
|
||||
public:
|
||||
/** Construct a QtVariantContext which wraps a dictionary in a QVariantHash
|
||||
* or a QVariantMap.
|
||||
*/
|
||||
#if __cplusplus >= 201103L
|
||||
typedef std::function<QString(const QString &, Mustache::Renderer *, Mustache::Context *)> fn_t;
|
||||
#else
|
||||
typedef QString (*fn_t)(const QString &, Mustache::Renderer *, Mustache::Context *);
|
||||
#endif
|
||||
explicit QtVariantContext(const QVariant & root, PartialResolver *resolver = 0);
|
||||
|
||||
virtual QString stringValue(const QString & key) const;
|
||||
virtual bool isFalse(const QString & key) const;
|
||||
virtual int listCount(const QString & key) const;
|
||||
virtual void push(const QString & key, int index = -1);
|
||||
virtual void pop();
|
||||
virtual bool canEval(const QString & key) const;
|
||||
virtual QString eval(const QString & key, const QString & _template, Mustache::Renderer *renderer);
|
||||
|
||||
private:
|
||||
QVariant value(const QString & key) const;
|
||||
|
||||
QStack<QVariant> m_contextStack;
|
||||
};
|
||||
|
||||
/** Interface for fetching template partials. */
|
||||
class QTCREATOR_UTILS_EXPORT PartialResolver {
|
||||
public:
|
||||
virtual ~PartialResolver() {}
|
||||
|
||||
/** Returns the partial template with a given @p name. */
|
||||
virtual QString getPartial(const QString & name) = 0;
|
||||
};
|
||||
|
||||
/** A simple partial fetcher which returns templates from a map of (partial name -> template)
|
||||
*/
|
||||
class QTCREATOR_UTILS_EXPORT PartialMap : public PartialResolver {
|
||||
public:
|
||||
explicit PartialMap(const QHash<QString, QString> & partials);
|
||||
|
||||
virtual QString getPartial(const QString & name);
|
||||
|
||||
private:
|
||||
QHash<QString, QString> m_partials;
|
||||
};
|
||||
|
||||
/** A partial fetcher when loads templates from '<name>.mustache' files
|
||||
* in a given directory.
|
||||
*
|
||||
* Once a partial has been loaded, it is cached for future use.
|
||||
*/
|
||||
class QTCREATOR_UTILS_EXPORT PartialFileLoader : public PartialResolver {
|
||||
public:
|
||||
explicit PartialFileLoader(const QString & basePath);
|
||||
|
||||
virtual QString getPartial(const QString & name);
|
||||
|
||||
private:
|
||||
QString m_basePath;
|
||||
QHash<QString, QString> m_cache;
|
||||
};
|
||||
|
||||
/** Holds properties of a tag in a mustache template. */
|
||||
struct Tag {
|
||||
enum Type {
|
||||
Null,
|
||||
Value, /// A {{key}} or {{{key}}} tag
|
||||
SectionStart, /// A {{#section}} tag
|
||||
InvertedSectionStart, /// An {{^inverted-section}} tag
|
||||
SectionEnd, /// A {{/section}} tag
|
||||
Partial, /// A {{^partial}} tag
|
||||
Comment, /// A {{! comment }} tag
|
||||
SetDelimiter /// A {{=<% %>=}} tag
|
||||
};
|
||||
|
||||
enum EscapeMode {
|
||||
Escape,
|
||||
Unescape,
|
||||
Raw
|
||||
};
|
||||
|
||||
Tag()
|
||||
: type(Null)
|
||||
, start(0)
|
||||
, end(0)
|
||||
, escapeMode(Escape)
|
||||
{}
|
||||
|
||||
Type type;
|
||||
QString key;
|
||||
int start;
|
||||
int end;
|
||||
EscapeMode escapeMode;
|
||||
};
|
||||
|
||||
/** Renders Mustache templates, replacing mustache tags with
|
||||
* values from a provided context.
|
||||
*/
|
||||
class QTCREATOR_UTILS_EXPORT Renderer {
|
||||
public:
|
||||
Renderer();
|
||||
|
||||
/** Render a Mustache template, using @p context to fetch
|
||||
* the values used to replace Mustache tags.
|
||||
*/
|
||||
QString render(const QString & _template, Context *context);
|
||||
|
||||
/** Returns a message describing the last error encountered by the previous
|
||||
* render() call.
|
||||
*/
|
||||
QString error() const;
|
||||
|
||||
/** Returns the position in the template where the last error occurred
|
||||
* when rendering the template or -1 if no error occurred.
|
||||
*
|
||||
* If the error occurred in a partial template, the returned position is the offset
|
||||
* in the partial template.
|
||||
*/
|
||||
int errorPos() const;
|
||||
|
||||
/** Returns the name of the partial where the error occurred, or an empty string
|
||||
* if the error occurred in the main template.
|
||||
*/
|
||||
QString errorPartial() const;
|
||||
|
||||
/** Sets the default tag start and end markers.
|
||||
* This can be overridden within a template.
|
||||
*/
|
||||
void setTagMarkers(const QString & startMarker, const QString & endMarker);
|
||||
|
||||
private:
|
||||
QString render(const QString & _template, int startPos, int endPos, Context *context);
|
||||
|
||||
Tag findTag(const QString & content, int pos, int endPos);
|
||||
Tag findEndTag(const QString & content, const Tag & startTag, int endPos);
|
||||
void setError(const QString & error, int pos);
|
||||
|
||||
void readSetDelimiter(const QString & content, int pos, int endPos);
|
||||
static QString readTagName(const QString & content, int pos, int endPos);
|
||||
|
||||
/** Expands @p tag to fill the line, but only if it is standalone.
|
||||
*
|
||||
* The start position is moved to the beginning of the line. The end position is
|
||||
* moved to one past the end of the line. If @p tag is not standalone, it is
|
||||
* left unmodified.
|
||||
*
|
||||
* A tag is standalone if it is the only non-whitespace token on the the line.
|
||||
*/
|
||||
static void expandTag(Tag & tag, const QString & content);
|
||||
|
||||
QStack<QString> m_partialStack;
|
||||
QString m_error;
|
||||
int m_errorPos;
|
||||
QString m_errorPartial;
|
||||
|
||||
QString m_tagStartMarker;
|
||||
QString m_tagEndMarker;
|
||||
|
||||
QString m_defaultTagStartMarker;
|
||||
QString m_defaultTagEndMarker;
|
||||
};
|
||||
|
||||
/** A convenience function which renders a template using the given data. */
|
||||
QString renderTemplate(const QString & templateString, const QVariantHash & args);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Mustache::QtVariantContext::fn_t)
|
@ -56,7 +56,8 @@ SOURCES += reloadpromptutils.cpp \
|
||||
svgimageprovider.cpp \
|
||||
hostosinfo.cpp \
|
||||
logfile.cpp \
|
||||
crc.cpp
|
||||
crc.cpp \
|
||||
mustache.cpp
|
||||
|
||||
SOURCES += xmlconfig.cpp
|
||||
|
||||
@ -115,7 +116,8 @@ HEADERS += utils_global.h \
|
||||
svgimageprovider.h \
|
||||
hostosinfo.h \
|
||||
logfile.h \
|
||||
crc.h
|
||||
crc.h \
|
||||
mustache.h
|
||||
|
||||
|
||||
HEADERS += xmlconfig.h
|
||||
|
Loading…
x
Reference in New Issue
Block a user