Files
WallReel/WallReel/Core/Utils/texttemplate.hpp
T
2026-07-12 19:01:49 +02:00

143 lines
4.7 KiB
C++

#ifndef WALLREEL_UTILS_TEXTTEMPLATE_HPP
#define WALLREEL_UTILS_TEXTTEMPLATE_HPP
#include <QMap>
#include <QRegularExpression>
#include <QString>
namespace WallReel::Core::Utils {
/**
* @brief Replaces {{ key }} style placeholders in a template string with corresponding values from a map.
*
* Supports:
* - Whitespace tolerance: {{ key }}, {{key}}, {{ key }} are all valid.
* - Missing keys are left as-is (no replacement).
* - Nested braces or malformed placeholders are ignored.
* - Empty keys are ignored.
* - Keys are trimmed before lookup.
*
* @param templateStr The template string containing {{ key }} placeholders.
* @param variables A map of key-value pairs for substitution.
* @return The rendered string with placeholders replaced.
*/
inline QString renderTemplate(const QString& templateStr, const QMap<QString, QString>& variables) {
if (templateStr.isEmpty() || variables.isEmpty()) {
return templateStr;
}
// Match {{ ... }} with possible whitespace around the key.
// Use a non-greedy match for the content inside braces to handle multiple placeholders correctly.
static const QRegularExpression regex(
QStringLiteral(R"(\{\{\s*([^{}]+?)\s*\}\})"),
QRegularExpression::DontCaptureOption);
// We need the capture group, so rebuild without DontCaptureOption
static const QRegularExpression placeholderRegex(
QStringLiteral(R"(\{\{\s*([^{}]+?)\s*\}\})"));
QString result;
result.reserve(templateStr.size());
qsizetype lastPos = 0;
QRegularExpressionMatchIterator it = placeholderRegex.globalMatch(templateStr);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const qsizetype matchStart = match.capturedStart(0);
const qsizetype matchLength = match.capturedLength(0);
const QString key = match.captured(1).trimmed();
// Append everything before this match
result.append(templateStr.mid(lastPos, matchStart - lastPos));
if (!key.isEmpty() && variables.contains(key)) {
// Replace with the value from the map
result.append(variables.value(key));
} else {
// Key not found or empty — leave the placeholder as-is
result.append(match.captured(0));
}
lastPos = matchStart + matchLength;
}
// Append any remaining text after the last match
if (lastPos < templateStr.size()) {
result.append(templateStr.mid(lastPos));
}
return result;
}
/**
* @brief Overload accepting QHash for convenience.
*/
inline QString renderTemplate(const QString& templateStr, const QHash<QString, QString>& variables) {
if (templateStr.isEmpty() || variables.isEmpty()) {
return templateStr;
}
static const QRegularExpression placeholderRegex(
QStringLiteral(R"(\{\{\s*([^{}]+?)\s*\}\})"));
QString result;
result.reserve(templateStr.size());
qsizetype lastPos = 0;
QRegularExpressionMatchIterator it = placeholderRegex.globalMatch(templateStr);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const qsizetype matchStart = match.capturedStart(0);
const qsizetype matchLength = match.capturedLength(0);
const QString key = match.captured(1).trimmed();
result.append(templateStr.mid(lastPos, matchStart - lastPos));
if (!key.isEmpty() && variables.contains(key)) {
result.append(variables.value(key));
} else {
result.append(match.captured(0));
}
lastPos = matchStart + matchLength;
}
if (lastPos < templateStr.size()) {
result.append(templateStr.mid(lastPos));
}
return result;
}
/**
* @brief Extracts all placeholder keys from a template string.
*
* @param templateStr The template string to scan.
* @return A list of unique keys found in the template (trimmed).
*/
inline QStringList extractTemplateKeys(const QString& templateStr) {
static const QRegularExpression placeholderRegex(
QStringLiteral(R"(\{\{\s*([^{}]+?)\s*\}\})"));
QSet<QString> seen;
QStringList keys;
QRegularExpressionMatchIterator it = placeholderRegex.globalMatch(templateStr);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const QString key = match.captured(1).trimmed();
if (!key.isEmpty() && !seen.contains(key)) {
seen.insert(key);
keys.append(key);
}
}
return keys;
}
} // namespace WallReel::Core::Utils
#endif // WALLREEL_UTILS_TEXTTEMPLATE_HPP