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
templategenerator.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
5
6#include "aggregate.h"
7#include "codemarker.h"
9#include "config.h"
12#include "documentwriter.h"
14#include "injabridge.h"
15#include "ir/builder.h"
16#include "ir/document.h"
17#include "namespacenode.h"
18#include "node.h"
19#include "nodeextractor.h"
20#include "outputcontext.h"
22#include "pagenode.h"
23#include "qdocdatabase.h"
24#include "qmltypenode.h"
25#include "tree.h"
26#include "utilities.h"
27
28#include <utility>
29
30#include <QtCore/qdir.h>
31#include <QtCore/qfile.h>
32#include <QtCore/qfileinfo.h>
33#include <QtCore/qloggingcategory.h>
34#include <QtCore/qtextstream.h>
35
37
38Q_LOGGING_CATEGORY(lcQDocTemplateGenerator, "qt.qdoc.templategenerator")
39
40using namespace Qt::Literals;
41
42/*!
43 \class TemplateGenerator
44 \internal
45 \brief Generates documentation using external templates and a pre-built IR.
46
47 TemplateGenerator implements OutputProducer and DocumentationHandler to
48 generate documentation without inheriting from Generator. It uses
49 DocumentationTraverser for tree traversal and delegates content generation
50 to templates via the IR system.
51
52 \section1 Architecture
53
54 The generator follows a composition-based design:
55 \list
56 \li \b{OutputProducer}: Lifecycle interface (prepare/produce/finalize).
57 \li \b{DocumentationHandler}: Content generation callbacks for
58 traverser.
59 \li \b{DocumentationTraverser}: Shared tree traversal logic.
60 \li \b{DocumentWriter}: Output abstraction (file or string for tests).
61 \endlist
62
63 \sa DocumentationTraverser, DocumentationHandler, OutputProducer,
64 IR::Builder
65*/
66
68 const QString &format)
69 : m_fileResolver(fileResolver)
70 , m_qdb(qdb)
71 , m_format(format.isEmpty() ? u"template"_s : format)
72{
74}
75
80
82{
83 createDefaultWriter();
84
85 const Config &config = Config::instance();
86
87 QString extensionConfig = config.get(m_format + ".extension"_L1).asString();
88 if (!extensionConfig.isEmpty())
89 m_fileExtension = extensionConfig;
90
91 QString templateDirConfig = config.get(m_format + ".templatedir"_L1).asString();
92
93 if (templateDirConfig.isEmpty()) {
94 m_templateDir.clear();
95 } else if (QDir::isAbsolutePath(templateDirConfig)) {
96 m_templateDir = templateDirConfig;
97 } else {
98 // Relative path: resolve relative to output directory
99 const QString &outDir = m_context->outputDir.path();
100 if (!outDir.isEmpty())
101 m_templateDir = outDir + "/"_L1 + templateDirConfig;
102 else
103 m_templateDir = templateDirConfig;
104 }
105
106 bool foundTemplates = false;
107 if (!m_templateDir.isEmpty()) {
108 QDir templateDir(m_templateDir);
109 if (templateDir.exists() && !templateDir.entryList(QDir::Files).isEmpty()) {
110 foundTemplates = true;
111 qCInfo(lcQDocTemplateGenerator) << "Using template directory:" << m_templateDir;
112 } else if (!templateDir.exists()) {
113 qCInfo(lcQDocTemplateGenerator)
114 << "Configured template directory does not exist:" << m_templateDir
115 << "- will use embedded templates";
116 } else {
117 qCInfo(lcQDocTemplateGenerator)
118 << "Configured template directory is empty:" << m_templateDir
119 << "- will use embedded templates";
120 }
121 } else {
122 qCInfo(lcQDocTemplateGenerator)
123 << "No external template directory configured - will use embedded templates";
124 }
125
126 if (!foundTemplates)
127 m_templateDir.clear();
128}
129
131{
132 DocumentationTraverser traverser;
133 Node *root = m_qdb.primaryTreeRoot();
134 if (root)
135 traverser.traverse(root, *this);
136}
137
139{
140 m_writer.reset();
141}
142
144{
145 return m_format;
146}
147
148void TemplateGenerator::beginDocument(const QString &outputFileName)
149{
150 if (m_writer)
151 m_writer->beginDocument(outputFileName);
152}
153
155{
156 if (m_writer)
157 m_writer->endDocument();
158}
159
161{
162 if (!node->url().isEmpty())
163 return node->url();
164
165 // Special case for simple page nodes (\page commands) with explicit
166 // non-.html extensions. Use the normalized fileBase() but preserve
167 // user specified extension
169 QFileInfo originalName(node->name());
170 QString suffix = originalName.suffix();
171 if (!suffix.isEmpty() && suffix != "html"_L1) {
172 // User specified a non-.html extension - use normalized base + original extension
173 QString name = fileBase(node);
174 return name + '.'_L1 + suffix;
175 }
176 }
177
178 QString name = fileBase(node) + '.'_L1;
179 return name + m_fileExtension;
180}
181
183{
184 Q_UNUSED(marker);
185
186 // Placeholder - IR integration pending
187 if (m_writer && m_writer->isOpen()) {
188 m_writer->writeLine(QString(u"<!-- TemplateGenerator: Collection "_s + cn->name() + u" -->"_s));
189 m_writer->writeLine(QString(u"<h1>"_s + cn->fullTitle() + u"</h1>"_s));
190 m_writer->writeLine(u"<p>Template-based output (IR integration pending)</p>"_s);
191 }
192}
193
195{
196 Q_UNUSED(marker);
197
198 // Placeholder - IR integration pending
199 if (m_writer && m_writer->isOpen()) {
200 m_writer->writeLine(QString(u"<!-- TemplateGenerator: Generic Collection "_s + cn->name() + u" -->"_s));
201 m_writer->writeLine(QString(u"<h1>"_s + cn->fullTitle() + u"</h1>"_s));
202 m_writer->writeLine(u"<p>Template-based output (IR integration pending)</p>"_s);
203 }
204}
205
207{
208 Q_UNUSED(marker);
209
211
212 IR::Builder builder;
213 IR::Document ir = builder.buildPageIR(std::move(pm));
214
215 renderDocument(ir, "page"_L1);
216}
217
219{
220 Q_UNUSED(marker);
221
222 // Placeholder - IR integration pending
223 if (m_writer && m_writer->isOpen()) {
224 m_writer->writeLine(QString(u"<!-- TemplateGenerator: C++ Reference Page for "_s
225 + aggregate->name() + u" -->"_s));
226 m_writer->writeLine(QString(u"<h1>"_s + aggregate->fullTitle() + u"</h1>"_s));
227 m_writer->writeLine(u"<p>Template-based output (IR integration pending)</p>"_s);
228 }
229}
230
232{
233 Q_UNUSED(marker);
234
235 // Placeholder - IR integration pending
236 if (m_writer && m_writer->isOpen()) {
237 m_writer->writeLine(QString(u"<!-- TemplateGenerator: QML Type Page for "_s
238 + qcn->name() + u" -->"_s));
239 m_writer->writeLine(QString(u"<h1>"_s + qcn->fullTitle() + u"</h1>"_s));
240 m_writer->writeLine(u"<p>Template-based output (IR integration pending)</p>"_s);
241 }
242}
243
245{
246 Q_UNUSED(marker);
247
248 // Placeholder - IR integration pending
249 if (m_writer && m_writer->isOpen()) {
250 m_writer->writeLine(QString(u"<!-- TemplateGenerator: Proxy Page for "_s
251 + aggregate->name() + u" -->"_s));
252 m_writer->writeLine(QString(u"<h1>"_s + aggregate->fullTitle() + u"</h1>"_s));
253 m_writer->writeLine(u"<p>Template-based output (IR integration pending)</p>"_s);
254 }
255}
256
261
263{
264 return m_fileExtension;
265}
266
267/*!
268 \internal
269 Render phase: Format pre-built IR according to a template.
270
271 This is TemplateGenerator's core responsibility. It receives IR and
272 produces formatted output without any knowledge of Nodes or the database.
273*/
274void TemplateGenerator::renderDocument(const IR::Document &ir, const QString &templateBaseName)
275{
276 const QString templateFileName = templateBaseName + '.'_L1 + m_fileExtension;
277 QString templateContent;
278
279 if (!m_templateDir.isEmpty()) {
280 QString templatePath = m_templateDir + '/'_L1 + templateFileName;
281 QFile templateFile(templatePath);
282
283 if (templateFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
284 templateContent = QString::fromUtf8(templateFile.readAll());
285 templateFile.close();
286 }
287 }
288
289 if (templateContent.isEmpty()) {
290 QFile resourceFile(":/qdoc/templates/"_L1 + templateFileName);
291 if (resourceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
292 templateContent = QString::fromUtf8(resourceFile.readAll());
293 resourceFile.close();
294 }
295 }
296
297 if (templateContent.isEmpty())
298 qFatal("TemplateGenerator: No template file found for extension '%s'. "
299 "Ensure '%s.%s' exists in the configured template directory or in resources.",
300 qPrintable(m_fileExtension), qPrintable(templateBaseName),
301 qPrintable(m_fileExtension));
302
303 auto includeCallback = [this](const QString &name) { return resolveInclude(name); };
304 QString rendered = InjaBridge::render(templateContent, ir.toJson(), includeCallback);
305
306 if (m_writer && m_writer->isOpen())
307 m_writer->write(rendered);
308}
309
310/*!
311 \internal
312 Resolves an Inja \c{{% include %}} directive to template content.
313
314 Searches the filesystem first (for user-customized templates) and then
315 Qt's embedded resource system (for bundled defaults). This enables
316 Inja's include mechanism to work with Qt resources, where
317 \c{std::ifstream} can't open \c{:/} paths.
318
319 Returns the file content as a QString, or an empty QString if the file
320 isn't found in either location.
321*/
322QString TemplateGenerator::resolveInclude(const QString &name) const
323{
324 if (!m_templateDir.isEmpty()) {
325 QFile file(m_templateDir + '/'_L1 + name);
326 if (file.open(QIODevice::ReadOnly | QIODevice::Text))
327 return QString::fromUtf8(file.readAll());
328 }
329
330 QFile resourceFile(":/qdoc/templates/"_L1 + name);
331 if (resourceFile.open(QIODevice::ReadOnly | QIODevice::Text))
332 return QString::fromUtf8(resourceFile.readAll());
333
334 return {};
335}
336
337/*!
338 \internal
339 Returns the output prefix/suffix key for a node based on its genus.
340
341 This is used to look up configured prefixes and suffixes from OutputContext.
342*/
343static QString nodeTypeKey(const Node *node)
344{
345 if (node->isPageNode()) {
346 switch (node->genus()) {
347 case Genus::QML:
348 return u"QML"_s;
349 case Genus::CPP:
350 return u"CPP"_s;
351 default:
352 break;
353 }
354 }
355 return QString();
356}
357
358/*!
359 \internal
360 Computes the base filename for a node, delegating the core computation
361 to Utilities::computeFileBase().
362
363 This handles caching and adapts the TemplateGenerator's OutputContext-based
364 prefix/suffix lookup to the shared interface.
365*/
366QString TemplateGenerator::fileBase(const Node *node) const
367{
368 if (!node->isPageNode() && !node->isCollectionNode())
369 node = node->parent();
370
371 if (node->hasFileNameBase())
372 return node->fileNameBase();
373
374 QString result = Utilities::computeFileBase(
375 node, m_context->project,
376 [this](const Node *n) -> QString {
377 if (n->isCollectionNode())
378 return {};
379 return m_context->outputPrefix(nodeTypeKey(n));
380 },
381 [this](const Node *n) { return m_context->outputSuffix(nodeTypeKey(n)); });
382
383 const_cast<Node *>(node)->setFileNameBase(result);
384 return result;
385}
386
387/*!
388 \internal
389 Creates the production FileDocumentWriter.
390
391 This is called during initialization to create the default writer
392 that writes to the filesystem. For testing, a mock writer can be
393 injected by setting m_writer before calling prepare().
394
395 Also initializes m_context with configuration values, enabling
396 OutputContext-based access to output settings.
397*/
398void TemplateGenerator::createDefaultWriter()
399{
400 // Initialize OutputContext from configuration
401 const Config &config = Config::instance();
402 m_context.emplace(OutputContext::fromConfig(config, format()));
403
404 if (m_writer)
405 return; // Writer already set (e.g., injected for testing)
406
407 m_writer = std::make_unique<FileDocumentWriter>(*m_context);
408}
409
410QT_END_NAMESPACE
A class for holding the members of a collection of doc pages.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:85
Traverses the node tree and dispatches to a handler for documentation generation.
void traverse(Node *root, DocumentationHandler &handler)
Encapsulate the logic that QDoc uses to find files whose path is provided by the user and that are re...
Assembles IR Documents from pre-extracted metadata.
Definition builder.h:15
Document buildPageIR(PageMetadata pm) const
Definition builder.cpp:59
Singleton registry for discovering output producers by format.
void registerProducer(OutputProducer *producer)
Registers producer with this registry.
void unregisterProducer(OutputProducer *producer)
Unregisters producer from this registry.
static OutputProducerRegistry & instance()
Returns the singleton registry instance.
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:19
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
void mergeCollections(CollectionNode *c)
Finds all the collection nodes with the same name and type as c and merges their members into the mem...
Generates documentation using external templates and a pre-built IR.
void generateProxyPage(Aggregate *aggregate, CodeMarker *marker) override
void endDocument() override
void beginDocument(const QString &fileName) override
void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker) override
void generatePageNode(PageNode *pn, CodeMarker *marker) override
void finalize() override
Finalizes output production.
void prepare() override
Prepares the producer for an output run.
QString fileExtension() const
TemplateGenerator(FileResolver &fileResolver, QDocDatabase &qdb, const QString &format=QString())
QString fileName(const Node *node) const override
void produce() override
Produces documentation output.
void generateCollectionNode(CollectionNode *cn, CodeMarker *marker) override
QString format() const override
Returns the format identifier for this producer (e.g., "HTML", "DocBook", "template").
void mergeCollections(CollectionNode *cn) override
void generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker) override
void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker) override
Definition builder.cpp:14
IR::PageMetadata extractPageMetadata(const PageNode *pn)
Combined button and popup list for selecting options.
Intermediate representation for a documentation topic.
Definition document.h:22
The Node class is the base class for all the nodes in QDoc's parse tree.
bool hasFileNameBase() const
Returns true if the node's file name base has been set.
Definition node.h:167
Genus genus() const override
Returns this node's Genus.
Definition node.h:85
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:148
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:153
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:208
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:144
static QString nodeTypeKey(const Node *node)