1 | // Copyright (C) 2020 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 "tagfilewriter.h" |
5 | |
6 | #include "access.h" |
7 | #include "aggregate.h" |
8 | #include "classnode.h" |
9 | #include "enumnode.h" |
10 | #include "functionnode.h" |
11 | #include "htmlgenerator.h" |
12 | #include "location.h" |
13 | #include "node.h" |
14 | #include "propertynode.h" |
15 | #include "qdocdatabase.h" |
16 | #include "typedefnode.h" |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | /*! |
21 | \class TagFileWriter |
22 | |
23 | This class handles the generation of the QDoc tag files. |
24 | */ |
25 | |
26 | /*! |
27 | Default constructor. \a qdb is the pointer to the |
28 | qdoc database that is used when reading and writing the |
29 | index files. |
30 | */ |
31 | TagFileWriter::TagFileWriter() : m_qdb(QDocDatabase::qdocDB()) { } |
32 | |
33 | /*! |
34 | Generate the tag file section with the given \a writer for the \a parent |
35 | node. |
36 | */ |
37 | void TagFileWriter::generateTagFileCompounds(QXmlStreamWriter &writer, const Aggregate *parent) |
38 | { |
39 | const auto &nonFunctionList = const_cast<Aggregate *>(parent)->nonfunctionList(); |
40 | for (const auto *node : nonFunctionList) { |
41 | if (!node->url().isNull() || node->isPrivate()) |
42 | continue; |
43 | |
44 | QString kind; |
45 | switch (node->nodeType()) { |
46 | case Node::Namespace: |
47 | kind = "namespace" ; |
48 | break; |
49 | case Node::Class: |
50 | case Node::Struct: |
51 | case Node::Union: |
52 | case Node::QmlType: |
53 | kind = "class" ; |
54 | break; |
55 | default: |
56 | continue; |
57 | } |
58 | const auto *aggregate = static_cast<const Aggregate *>(node); |
59 | |
60 | QString access = "public" ; |
61 | if (node->isProtected()) |
62 | access = "protected" ; |
63 | |
64 | QString objName = node->name(); |
65 | |
66 | // Special case: only the root node should have an empty name. |
67 | if (objName.isEmpty() && node != m_qdb->primaryTreeRoot()) |
68 | continue; |
69 | |
70 | // *** Write the starting tag for the element here. *** |
71 | writer.writeStartElement(qualifiedName: "compound" ); |
72 | writer.writeAttribute(qualifiedName: "kind" , value: kind); |
73 | |
74 | if (node->isClassNode()) { |
75 | writer.writeTextElement(qualifiedName: "name" , text: node->fullDocumentName()); |
76 | writer.writeTextElement(qualifiedName: "filename" , text: m_generator->fullDocumentLocation(node, useSubdir: false)); |
77 | |
78 | // Classes contain information about their base classes. |
79 | const auto *classNode = static_cast<const ClassNode *>(node); |
80 | const QList<RelatedClass> &bases = classNode->baseClasses(); |
81 | for (const auto &related : bases) { |
82 | ClassNode *n = related.m_node; |
83 | if (n) |
84 | writer.writeTextElement(qualifiedName: "base" , text: n->name()); |
85 | } |
86 | |
87 | // Recurse to write all members. |
88 | generateTagFileMembers(writer, inner: aggregate); |
89 | writer.writeEndElement(); |
90 | |
91 | // Recurse to write all compounds. |
92 | generateTagFileCompounds(writer, parent: aggregate); |
93 | } else { |
94 | writer.writeTextElement(qualifiedName: "name" , text: node->fullDocumentName()); |
95 | writer.writeTextElement(qualifiedName: "filename" , text: m_generator->fullDocumentLocation(node, useSubdir: false)); |
96 | |
97 | // Recurse to write all members. |
98 | generateTagFileMembers(writer, inner: aggregate); |
99 | writer.writeEndElement(); |
100 | |
101 | // Recurse to write all compounds. |
102 | generateTagFileCompounds(writer, parent: aggregate); |
103 | } |
104 | } |
105 | } |
106 | |
107 | /*! |
108 | Writes all the members of the \a parent node with the \a writer. |
109 | The node represents a C++ class, namespace, etc. |
110 | */ |
111 | void TagFileWriter::generateTagFileMembers(QXmlStreamWriter &writer, const Aggregate *parent) |
112 | { |
113 | auto childNodes = parent->childNodes(); |
114 | std::sort(first: childNodes.begin(), last: childNodes.end(), comp: Node::nodeNameLessThan); |
115 | for (const auto *node : childNodes) { |
116 | if (!node->url().isNull()) |
117 | continue; |
118 | |
119 | QString nodeName; |
120 | QString kind; |
121 | switch (node->nodeType()) { |
122 | case Node::Enum: |
123 | nodeName = "member" ; |
124 | kind = "enumeration" ; |
125 | break; |
126 | case Node::TypeAlias: // Treated as typedef |
127 | case Node::Typedef: |
128 | nodeName = "member" ; |
129 | kind = "typedef" ; |
130 | break; |
131 | case Node::Property: |
132 | nodeName = "member" ; |
133 | kind = "property" ; |
134 | break; |
135 | case Node::Function: |
136 | nodeName = "member" ; |
137 | kind = "function" ; |
138 | break; |
139 | case Node::Namespace: |
140 | nodeName = "namespace" ; |
141 | break; |
142 | case Node::Class: |
143 | case Node::Struct: |
144 | case Node::Union: |
145 | nodeName = "class" ; |
146 | break; |
147 | case Node::Variable: |
148 | default: |
149 | continue; |
150 | } |
151 | |
152 | QString access; |
153 | switch (node->access()) { |
154 | case Access::Public: |
155 | access = "public" ; |
156 | break; |
157 | case Access::Protected: |
158 | access = "protected" ; |
159 | break; |
160 | case Access::Private: |
161 | default: |
162 | continue; |
163 | } |
164 | |
165 | QString objName = node->name(); |
166 | |
167 | // Special case: only the root node should have an empty name. |
168 | if (objName.isEmpty() && node != m_qdb->primaryTreeRoot()) |
169 | continue; |
170 | |
171 | // *** Write the starting tag for the element here. *** |
172 | writer.writeStartElement(qualifiedName: nodeName); |
173 | if (!kind.isEmpty()) |
174 | writer.writeAttribute(qualifiedName: "kind" , value: kind); |
175 | |
176 | switch (node->nodeType()) { |
177 | case Node::Class: |
178 | case Node::Struct: |
179 | case Node::Union: |
180 | writer.writeCharacters(text: node->fullDocumentName()); |
181 | writer.writeEndElement(); |
182 | break; |
183 | case Node::Namespace: |
184 | writer.writeCharacters(text: node->fullDocumentName()); |
185 | writer.writeEndElement(); |
186 | break; |
187 | case Node::Function: { |
188 | /* |
189 | Function nodes contain information about |
190 | the type of function being described. |
191 | */ |
192 | |
193 | const auto *functionNode = static_cast<const FunctionNode *>(node); |
194 | writer.writeAttribute(qualifiedName: "protection" , value: access); |
195 | writer.writeAttribute(qualifiedName: "virtualness" , value: functionNode->virtualness()); |
196 | writer.writeAttribute(qualifiedName: "static" , value: functionNode->isStatic() ? "yes" : "no" ); |
197 | |
198 | if (functionNode->isNonvirtual()) |
199 | writer.writeTextElement(qualifiedName: "type" , text: functionNode->returnType()); |
200 | else |
201 | writer.writeTextElement(qualifiedName: "type" , text: "virtual " + functionNode->returnType()); |
202 | |
203 | writer.writeTextElement(qualifiedName: "name" , text: objName); |
204 | const QStringList pieces = |
205 | m_generator->fullDocumentLocation(node, useSubdir: false).split(sep: QLatin1Char('#')); |
206 | writer.writeTextElement(qualifiedName: "anchorfile" , text: pieces[0]); |
207 | writer.writeTextElement(qualifiedName: "anchor" , text: pieces[1]); |
208 | QString signature = functionNode->signature(options: Node::SignatureReturnType); |
209 | signature = signature.mid(position: signature.indexOf(c: QChar('('))).trimmed(); |
210 | if (functionNode->isConst()) |
211 | signature += " const" ; |
212 | if (functionNode->isFinal()) |
213 | signature += " final" ; |
214 | if (functionNode->isOverride()) |
215 | signature += " override" ; |
216 | if (functionNode->isPureVirtual()) |
217 | signature += " = 0" ; |
218 | writer.writeTextElement(qualifiedName: "arglist" , text: signature); |
219 | } |
220 | writer.writeEndElement(); // member |
221 | break; |
222 | case Node::Property: { |
223 | const auto *propertyNode = static_cast<const PropertyNode *>(node); |
224 | writer.writeAttribute(qualifiedName: "type" , value: propertyNode->dataType()); |
225 | writer.writeTextElement(qualifiedName: "name" , text: objName); |
226 | const QStringList pieces = |
227 | m_generator->fullDocumentLocation(node, useSubdir: false).split(sep: QLatin1Char('#')); |
228 | writer.writeTextElement(qualifiedName: "anchorfile" , text: pieces[0]); |
229 | writer.writeTextElement(qualifiedName: "anchor" , text: pieces[1]); |
230 | writer.writeTextElement(qualifiedName: "arglist" , text: QString()); |
231 | } |
232 | writer.writeEndElement(); // member |
233 | break; |
234 | case Node::Enum: { |
235 | const auto *enumNode = static_cast<const EnumNode *>(node); |
236 | writer.writeTextElement(qualifiedName: "name" , text: objName); |
237 | const QStringList pieces = |
238 | m_generator->fullDocumentLocation(node, useSubdir: false).split(sep: QLatin1Char('#')); |
239 | writer.writeTextElement(qualifiedName: "anchorfile" , text: pieces[0]); |
240 | writer.writeTextElement(qualifiedName: "anchor" , text: pieces[1]); |
241 | writer.writeEndElement(); // member |
242 | |
243 | for (const auto &item : enumNode->items()) { |
244 | writer.writeStartElement(qualifiedName: "member" ); |
245 | writer.writeAttribute(qualifiedName: "kind" , value: "enumvalue" ); |
246 | writer.writeTextElement(qualifiedName: "name" , text: item.name()); |
247 | writer.writeTextElement(qualifiedName: "anchorfile" , text: pieces[0]); |
248 | writer.writeTextElement(qualifiedName: "anchor" , text: pieces[1]); |
249 | writer.writeTextElement(qualifiedName: "arglist" , text: QString()); |
250 | writer.writeEndElement(); // member |
251 | } |
252 | } break; |
253 | case Node::TypeAlias: // Treated as typedef |
254 | case Node::Typedef: { |
255 | const auto *typedefNode = static_cast<const TypedefNode *>(node); |
256 | if (typedefNode->associatedEnum()) |
257 | writer.writeAttribute(qualifiedName: "type" , value: typedefNode->associatedEnum()->fullDocumentName()); |
258 | else |
259 | writer.writeAttribute(qualifiedName: "type" , value: QString()); |
260 | writer.writeTextElement(qualifiedName: "name" , text: objName); |
261 | const QStringList pieces = |
262 | m_generator->fullDocumentLocation(node, useSubdir: false).split(sep: QLatin1Char('#')); |
263 | writer.writeTextElement(qualifiedName: "anchorfile" , text: pieces[0]); |
264 | writer.writeTextElement(qualifiedName: "anchor" , text: pieces[1]); |
265 | writer.writeTextElement(qualifiedName: "arglist" , text: QString()); |
266 | } |
267 | writer.writeEndElement(); // member |
268 | break; |
269 | |
270 | case Node::Variable: |
271 | default: |
272 | break; |
273 | } |
274 | } |
275 | } |
276 | |
277 | /*! |
278 | Writes a tag file named \a fileName. |
279 | */ |
280 | void TagFileWriter::generateTagFile(const QString &fileName, Generator *g) |
281 | { |
282 | QFile file(fileName); |
283 | QFileInfo fileInfo(fileName); |
284 | |
285 | // If no path was specified or it doesn't exist, |
286 | // default to the output directory |
287 | if (fileInfo.fileName() == fileName || !fileInfo.dir().exists()) |
288 | file.setFileName(m_generator->outputDir() + QLatin1Char('/') + fileInfo.fileName()); |
289 | |
290 | if (!file.open(flags: QFile::WriteOnly | QFile::Text)) { |
291 | Location().warning(message: QString("Failed to open %1 for writing." ).arg(a: file.fileName())); |
292 | return; |
293 | } |
294 | |
295 | m_generator = g; |
296 | QXmlStreamWriter writer(&file); |
297 | writer.setAutoFormatting(true); |
298 | writer.writeStartDocument(); |
299 | writer.writeStartElement(qualifiedName: "tagfile" ); |
300 | generateTagFileCompounds(writer, parent: m_qdb->primaryTreeRoot()); |
301 | writer.writeEndElement(); // tagfile |
302 | writer.writeEndDocument(); |
303 | file.close(); |
304 | } |
305 | |
306 | QT_END_NAMESPACE |
307 | |