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