Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
injabridge.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "injabridge.h"
5
6#include <cmath>
7
9
10static void registerCallbacks(inja::Environment &env)
11{
12 env.add_callback("escape_html", 1, [](inja::Arguments &args) {
13 auto input = args.at(0)->get<std::string>();
14 std::string buffer;
15 buffer.reserve(input.size() + input.size() / 8);
16 for (char c : input) {
17 switch (c) {
18 case '&': buffer += "&amp;"; break;
19 case '"': buffer += "&quot;"; break;
20 case '\'': buffer += "&apos;"; break;
21 case '<': buffer += "&lt;"; break;
22 case '>': buffer += "&gt;"; break;
23 default: buffer += c; break;
24 }
25 }
26 return buffer;
27 });
28}
29
30/*!
31 \class InjaBridge
32 \brief Adapter for converting Qt JSON types to Inja template engine format.
33
34 InjaBridge provides static methods to convert between Qt's native JSON types
35 (QJsonObject, QJsonArray, QJsonValue) and nlohmann::json, which is the data
36 format expected by the Inja template engine.
37
38 This adapter allows QDoc to maintain its Qt-native API while leveraging
39 Inja for template-based documentation generation. All JSON data in QDoc's
40 intermediate representation (IR) uses Qt types, and InjaBridge handles the
41 conversion when rendering templates.
42
43 \note All numbers in QJsonValue are stored as doubles. Whole-number doubles
44 (e.g., 30.0, 2.0) are converted to int64_t so that template output renders
45 them as integers (e.g., "30" not "30.0"). Fractional values pass through
46 as doubles.
47
48 \note Inja and nlohmann::json may report template or data errors. QDoc is
49 built with exceptions disabled (\c{-fno-exceptions}), so such errors are
50 treated as fatal and will terminate the process. A custom \c INJA_THROW
51 override in the header ensures that error details (including source
52 location) are logged via \c qFatal() before termination, rather than
53 calling \c std::abort() silently.
54
55 All render methods register an \c{escape_html()} callback that templates
56 can invoke to escape HTML special characters (\c{&}, \c{<}, \c{>},
57 \c{"}, \c{'}). This keeps format-specific escaping under template author
58 control rather than baking it into the rendering bridge.
59
60 \sa QJsonObject, QJsonArray, QJsonValue
61*/
62
63/*!
64 \brief Converts a QJsonValue, \a value, to nlohmann::json.
65
66 Handles all QJsonValue types: Null, Bool, Double, String, Array, Object,
67 and Undefined. Undefined values are treated as null.
68
69 Returns the equivalent nlohmann::json representation.
70*/
71nlohmann::json InjaBridge::toInjaJson(const QJsonValue &value)
72{
73 switch (value.type()) {
74 case QJsonValue::Null:
75 return nullptr;
76 case QJsonValue::Bool:
77 return value.toBool();
78 case QJsonValue::Double: {
79 double d = value.toDouble();
80 if (std::fmod(d, 1.0) == 0.0)
81 return static_cast<int64_t>(d);
82 return d;
83 }
84 case QJsonValue::String:
85 return value.toString().toUtf8().toStdString();
86 case QJsonValue::Array:
87 return toInjaJson(value.toArray());
88 case QJsonValue::Object:
89 return toInjaJson(value.toObject());
90 case QJsonValue::Undefined:
91 return nullptr;
92 }
93 return nullptr;
94}
95
96/*!
97 \brief Converts a QJsonObject, \a obj, to nlohmann::json.
98
99 Recursively converts all values in the object, preserving the key-value
100 structure. Nested objects and arrays are handled correctly.
101
102 Returns the equivalent nlohmann::json object.
103*/
104nlohmann::json InjaBridge::toInjaJson(const QJsonObject &obj)
105{
106 nlohmann::json result = nlohmann::json::object();
107
108 for (const auto &[key, value] : obj.asKeyValueRange())
109 result[key.toString().toUtf8().toStdString()] = toInjaJson(value);
110
111 return result;
112}
113
114/*!
115 \brief Converts a QJsonArray, \a array, to nlohmann::json.
116
117 Recursively converts all elements in the array, preserving order.
118 Mixed-type arrays are supported.
119
120 Returns the equivalent nlohmann::json array.
121*/
122nlohmann::json InjaBridge::toInjaJson(const QJsonArray &array)
123{
124 nlohmann::json result = nlohmann::json::array();
125
126 for (const QJsonValue &value : array)
127 result.push_back(toInjaJson(value));
128
129 return result;
130}
131
132/*!
133 \brief Renders a template string, \a templateStr, with provided \a data.
134
135 Uses Inja to render the template with the given JSON data. The data
136 is automatically converted from QJsonObject to nlohmann::json.
137
138 The Inja template string, \a templateStr, supports Jinja2 syntax. \a data is
139 the JSON data to use for rendering.
140
141 Returns the rendered template as a QString.
142*/
143QString InjaBridge::render(const QString &templateStr, const QJsonObject &data)
144{
145 inja::Environment env;
146 // Replace Inja's default "##" line statement prefix, which conflicts
147 // with Markdown headings. "%!" echoes Jinja2's "%" (statement) and
148 // QDoc's "!" (documentation marker), and is inert in both HTML and
149 // Markdown.
150 env.set_line_statement("%!");
151 env.set_trim_blocks(true);
152 env.set_lstrip_blocks(true);
153 registerCallbacks(env);
154 nlohmann::json jsonData = toInjaJson(data);
155
156 std::string templateUtf8 = templateStr.toUtf8().toStdString();
157 std::string resultUtf8 = env.render(templateUtf8, jsonData);
158
159 return QString::fromUtf8(resultUtf8.c_str());
160}
161
162/*!
163 \brief Renders a template string, \a templateStr, with provided \a data,
164 using \a includeCallback to resolve \c{{% include %}} directives.
165
166 This overload configures the Inja environment with a custom include
167 callback so that templates can use \c{{% include "name" %}} directives.
168 The \a includeCallback receives the include name and returns the partial's
169 content as a QString. If the callback returns an empty string, the include
170 is treated as missing and a fatal error is raised.
171
172 This enables Inja's include mechanism to work with Qt's resource system,
173 where \c{std::ifstream} cannot open \c{:/} paths.
174
175 Returns the rendered template as a QString.
176*/
177QString InjaBridge::render(const QString &templateStr, const QJsonObject &data,
178 const IncludeCallback &includeCallback)
179{
180 inja::Environment env;
181 env.set_line_statement("%!");
182 env.set_trim_blocks(true);
183 env.set_lstrip_blocks(true);
184 registerCallbacks(env);
185 env.set_search_included_templates_in_files(false);
186 env.set_include_callback(
187 [&includeCallback, &env](const std::filesystem::path & /*path*/,
188 const std::string &name) -> inja::Template {
189 QString content = includeCallback(QString::fromStdString(name));
190 if (content.isEmpty()) {
192 inja::FileError("include not found: '" + name + "'"));
193 }
194 return env.parse(content.toUtf8().toStdString());
195 });
196
197 nlohmann::json jsonData = toInjaJson(data);
198 std::string templateUtf8 = templateStr.toUtf8().toStdString();
199 std::string resultUtf8 = env.render(templateUtf8, jsonData);
200
201 return QString::fromUtf8(resultUtf8.c_str());
202}
203
204/*!
205 \brief Renders a template file with provided data.
206
207 Loads and renders a template from the filesystem, using \a templatePath
208 which holds the absolute path to the template file. The file should use
209 Inja/Jinja2 syntax. \a data is the JSON data to use for rendering.
210
211 Returns the rendered template as a QString.
212*/
213QString InjaBridge::renderFile(const QString &templatePath, const QJsonObject &data)
214{
215 inja::Environment env;
216 env.set_line_statement("%!");
217 env.set_trim_blocks(true);
218 env.set_lstrip_blocks(true);
219 registerCallbacks(env);
220 nlohmann::json jsonData = toInjaJson(data);
221
222 std::string pathUtf8 = templatePath.toUtf8().toStdString();
223 std::string resultUtf8 = env.render_file(pathUtf8, jsonData);
224
225 return QString::fromUtf8(resultUtf8.c_str());
226}
227
228QT_END_NAMESPACE
static QT_BEGIN_NAMESPACE void registerCallbacks(inja::Environment &env)
#define INJA_THROW(exception)
Definition injabridge.h:14