Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include <QHash> |
43 | #include <QMap> |
44 | |
45 | #include "atom.h" |
46 | #include "helpprojectwriter.h" |
47 | #include "htmlgenerator.h" |
48 | #include "config.h" |
49 | #include "node.h" |
50 | #include "tree.h" |
51 | #include <qdebug.h> |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | HelpProjectWriter::HelpProjectWriter(const Config &config, const QString &defaultFileName) |
56 | { |
57 | // The output directory should already have been checked by the calling |
58 | // generator. |
59 | outputDir = config.getString(CONFIG_OUTPUTDIR); |
60 | |
61 | QStringList names = config.getStringList(CONFIG_QHP + Config::dot + "projects"); |
62 | |
63 | foreach (const QString &projectName, names) { |
64 | HelpProject project; |
65 | project.name = projectName; |
66 | |
67 | QString prefix = CONFIG_QHP + Config::dot + projectName + Config::dot; |
68 | project.helpNamespace = config.getString(prefix + "namespace"); |
69 | project.virtualFolder = config.getString(prefix + "virtualFolder"); |
70 | project.fileName = config.getString(prefix + "file"); |
71 | if (project.fileName.isEmpty()) |
72 | project.fileName = defaultFileName; |
73 | project.extraFiles = config.getStringSet(prefix + "extraFiles"); |
74 | project.indexTitle = config.getString(prefix + "indexTitle"); |
75 | project.indexRoot = config.getString(prefix + "indexRoot"); |
76 | project.filterAttributes = config.getStringList(prefix + "filterAttributes").toSet(); |
77 | QSet<QString> customFilterNames = config.subVars(prefix + "customFilters"); |
78 | foreach (const QString &filterName, customFilterNames) { |
79 | QString name = config.getString(prefix + "customFilters"+ Config::dot + filterName + Config::dot + "name"); |
80 | QSet<QString> filters = config.getStringList(prefix + "customFilters"+ Config::dot + filterName + Config::dot + "filterAttributes").toSet(); |
81 | project.customFilters[name] = filters; |
82 | } |
83 | //customFilters = config.defs. |
84 | |
85 | foreach (QString name, config.getStringSet(prefix + "excluded")) |
86 | project.excluded.insert(name.replace("\\", "/")); |
87 | |
88 | foreach (const QString &name, config.getStringList(prefix + "subprojects")) { |
89 | SubProject subproject; |
90 | QString subprefix = prefix + "subprojects"+ Config::dot + name + Config::dot; |
91 | subproject.title = config.getString(subprefix + "title"); |
92 | subproject.indexTitle = config.getString(subprefix + "indexTitle"); |
93 | subproject.sortPages = config.getBool(subprefix + "sortPages"); |
94 | subproject.type = config.getString(subprefix + "type"); |
95 | readSelectors(subproject, config.getStringList(subprefix + "selectors")); |
96 | project.subprojects[name] = subproject; |
97 | } |
98 | |
99 | if (project.subprojects.isEmpty()) { |
100 | SubProject subproject; |
101 | readSelectors(subproject, config.getStringList(prefix + "selectors")); |
102 | project.subprojects[""] = subproject; |
103 | } |
104 | |
105 | projects.append(project); |
106 | } |
107 | } |
108 | |
109 | void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors) |
110 | { |
111 | QHash<QString, Node::Type> typeHash; |
112 | typeHash["namespace"] = Node::Namespace; |
113 | typeHash["class"] = Node::Class; |
114 | typeHash["fake"] = Node::Fake; |
115 | typeHash["enum"] = Node::Enum; |
116 | typeHash["typedef"] = Node::Typedef; |
117 | typeHash["function"] = Node::Function; |
118 | typeHash["property"] = Node::Property; |
119 | typeHash["variable"] = Node::Variable; |
120 | typeHash["target"] = Node::Target; |
121 | #ifdef QDOC_QML |
122 | typeHash["qmlproperty"] = Node::QmlProperty; |
123 | typeHash["qmlsignal"] = Node::QmlSignal; |
124 | typeHash["qmlmethod"] = Node::QmlMethod; |
125 | #endif |
126 | |
127 | QHash<QString, Node::SubType> subTypeHash; |
128 | subTypeHash["example"] = Node::Example; |
129 | subTypeHash["headerfile"] = Node::HeaderFile; |
130 | subTypeHash["file"] = Node::File; |
131 | subTypeHash["group"] = Node::Group; |
132 | subTypeHash["module"] = Node::Module; |
133 | subTypeHash["page"] = Node::Page; |
134 | subTypeHash["externalpage"] = Node::ExternalPage; |
135 | #ifdef QDOC_QML |
136 | subTypeHash["qmlclass"] = Node::QmlClass; |
137 | subTypeHash["qmlpropertygroup"] = Node::QmlPropertyGroup; |
138 | subTypeHash["qmlbasictype"] = Node::QmlBasicType; |
139 | #endif |
140 | |
141 | QSet<Node::SubType> allSubTypes = QSet<Node::SubType>::fromList(subTypeHash.values()); |
142 | |
143 | foreach (const QString &selector, selectors) { |
144 | QStringList pieces = selector.split(":"); |
145 | if (pieces.size() == 1) { |
146 | QString lower = selector.toLower(); |
147 | if (typeHash.contains(lower)) |
148 | subproject.selectors[typeHash[lower]] = allSubTypes; |
149 | } else if (pieces.size() >= 2) { |
150 | QString lower = pieces[0].toLower(); |
151 | pieces = pieces[1].split(","); |
152 | if (typeHash.contains(lower)) { |
153 | QSet<Node::SubType> subTypes; |
154 | for (int i = 0; i < pieces.size(); ++i) { |
155 | QString lower = pieces[i].toLower(); |
156 | if (subTypeHash.contains(lower)) |
157 | subTypes.insert(subTypeHash[lower]); |
158 | } |
159 | subproject.selectors[typeHash[lower]] = subTypes; |
160 | } |
161 | } |
162 | } |
163 | } |
164 | |
165 | void HelpProjectWriter::addExtraFile(const QString &file) |
166 | { |
167 | for (int i = 0; i < projects.size(); ++i) |
168 | projects[i].extraFiles.insert(file); |
169 | } |
170 | |
171 | void HelpProjectWriter::addExtraFiles(const QSet<QString> &files) |
172 | { |
173 | for (int i = 0; i < projects.size(); ++i) |
174 | projects[i].extraFiles.unite(files); |
175 | } |
176 | |
177 | /* |
178 | Returns a list of strings describing the keyword details for a given node. |
179 | |
180 | The first string is the human-readable name to be shown in Assistant. |
181 | The second string is a unique identifier. |
182 | The third string is the location of the documentation for the keyword. |
183 | */ |
184 | QStringList HelpProjectWriter::keywordDetails(const Node *node) const |
185 | { |
186 | QStringList details; |
187 | |
188 | if (node->type() == Node::QmlProperty) { |
189 | // "name" |
190 | details << node->name(); |
191 | // "id" |
192 | details << node->parent()->parent()->name()+"::"+node->name(); |
193 | } |
194 | else if (node->parent() && !node->parent()->name().isEmpty()) { |
195 | // "name" |
196 | if (node->type() == Node::Enum || node->type() == Node::Typedef) |
197 | details << node->parent()->name()+"::"+node->name(); |
198 | else |
199 | details << node->name(); |
200 | // "id" |
201 | details << node->parent()->name()+"::"+node->name(); |
202 | } |
203 | else if (node->type() == Node::Fake) { |
204 | const FakeNode *fake = static_cast<const FakeNode *>(node); |
205 | if (fake->subType() == Node::QmlClass) { |
206 | details << (QmlClassNode::qmlOnly ? fake->name() : fake->fullTitle()); |
207 | details << "QML."+ fake->name(); |
208 | } |
209 | else { |
210 | details << fake->fullTitle(); |
211 | details << fake->fullTitle(); |
212 | } |
213 | } |
214 | else { |
215 | details << node->name(); |
216 | details << node->name(); |
217 | } |
218 | details << HtmlGenerator::fullDocumentLocation(node); |
219 | return details; |
220 | } |
221 | |
222 | bool HelpProjectWriter::generateSection(HelpProject &project, |
223 | QXmlStreamWriter & /* writer */, |
224 | const Node *node) |
225 | { |
226 | if (!node->url().isEmpty()) |
227 | return false; |
228 | |
229 | if (node->access() == Node::Private || node->status() == Node::Internal) |
230 | return false; |
231 | |
232 | if (node->name().isEmpty()) |
233 | return true; |
234 | |
235 | QString docPath = node->doc().location().filePath(); |
236 | if (!docPath.isEmpty() && project.excluded.contains(docPath)) |
237 | return false; |
238 | |
239 | QString objName; |
240 | if (node->type() == Node::Fake) { |
241 | const FakeNode *fake = static_cast<const FakeNode *>(node); |
242 | objName = fake->fullTitle(); |
243 | } |
244 | else |
245 | objName = tree->fullDocumentName(node); |
246 | |
247 | // Only add nodes to the set for each subproject if they match a selector. |
248 | // Those that match will be listed in the table of contents. |
249 | |
250 | foreach (const QString &name, project.subprojects.keys()) { |
251 | SubProject subproject = project.subprojects[name]; |
252 | // No selectors: accept all nodes. |
253 | if (subproject.selectors.isEmpty()) { |
254 | project.subprojects[name].nodes[objName] = node; |
255 | } |
256 | else if (subproject.selectors.contains(node->type())) { |
257 | // Accept only the node types in the selectors hash. |
258 | if (node->type() != Node::Fake) |
259 | project.subprojects[name].nodes[objName] = node; |
260 | else { |
261 | // Accept only fake nodes with subtypes contained in the selector's |
262 | // mask. |
263 | const FakeNode *fakeNode = static_cast<const FakeNode *>(node); |
264 | if (subproject.selectors[node->type()].contains(fakeNode->subType()) && |
265 | fakeNode->subType() != Node::ExternalPage && |
266 | !fakeNode->fullTitle().isEmpty()) { |
267 | |
268 | project.subprojects[name].nodes[objName] = node; |
269 | } |
270 | } |
271 | } |
272 | } |
273 | |
274 | switch (node->type()) { |
275 | |
276 | case Node::Class: |
277 | project.keywords.append(keywordDetails(node)); |
278 | project.files.insert(HtmlGenerator::fullDocumentLocation(node)); |
279 | break; |
280 | |
281 | case Node::Namespace: |
282 | project.keywords.append(keywordDetails(node)); |
283 | project.files.insert(HtmlGenerator::fullDocumentLocation(node)); |
284 | break; |
285 | |
286 | case Node::Enum: |
287 | project.keywords.append(keywordDetails(node)); |
288 | { |
289 | const EnumNode *enumNode = static_cast<const EnumNode*>(node); |
290 | foreach (const EnumItem &item, enumNode->items()) { |
291 | QStringList details; |
292 | |
293 | if (enumNode->itemAccess(item.name()) == Node::Private) |
294 | continue; |
295 | |
296 | if (!node->parent()->name().isEmpty()) { |
297 | details << node->parent()->name()+"::"+item.name(); // "name" |
298 | details << node->parent()->name()+"::"+item.name(); // "id" |
299 | } else { |
300 | details << item.name(); // "name" |
301 | details << item.name(); // "id" |
302 | } |
303 | details << HtmlGenerator::fullDocumentLocation(node); |
304 | project.keywords.append(details); |
305 | } |
306 | } |
307 | break; |
308 | |
309 | case Node::Property: |
310 | case Node::QmlProperty: |
311 | case Node::QmlSignal: |
312 | case Node::QmlMethod: |
313 | project.keywords.append(keywordDetails(node)); |
314 | break; |
315 | |
316 | case Node::Function: |
317 | { |
318 | const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
319 | |
320 | // Only insert keywords for non-constructors. Constructors are covered |
321 | // by the classes themselves. |
322 | |
323 | if (funcNode->metaness() != FunctionNode::Ctor) |
324 | project.keywords.append(keywordDetails(node)); |
325 | |
326 | // Insert member status flags into the entries for the parent |
327 | // node of the function, or the node it is related to. |
328 | // Since parent nodes should have already been inserted into |
329 | // the set of files, we only need to ensure that related nodes |
330 | // are inserted. |
331 | |
332 | if (node->relates()) { |
333 | project.memberStatus[node->relates()].insert(node->status()); |
334 | project.files.insert(HtmlGenerator::fullDocumentLocation(node->relates())); |
335 | } else if (node->parent()) |
336 | project.memberStatus[node->parent()].insert(node->status()); |
337 | } |
338 | break; |
339 | |
340 | case Node::Typedef: |
341 | { |
342 | const TypedefNode *typedefNode = static_cast<const TypedefNode *>(node); |
343 | QStringList typedefDetails = keywordDetails(node); |
344 | const EnumNode *enumNode = typedefNode->associatedEnum(); |
345 | // Use the location of any associated enum node in preference |
346 | // to that of the typedef. |
347 | if (enumNode) |
348 | typedefDetails[2] = HtmlGenerator::fullDocumentLocation(enumNode); |
349 | |
350 | project.keywords.append(typedefDetails); |
351 | } |
352 | break; |
353 | |
354 | case Node::Variable: |
355 | { |
356 | QString location = HtmlGenerator::fullDocumentLocation(node); |
357 | project.files.insert(location.left(location.lastIndexOf(QLatin1Char( |
358 | project.keywords.append(keywordDetails(node)); |
359 | } |
360 | break; |
361 | |
362 | // Fake nodes (such as manual pages) contain subtypes, titles and other |
363 | // attributes. |
364 | case Node::Fake: { |
365 | const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
366 | if (fakeNode->subType() != Node::ExternalPage && |
367 | !fakeNode->fullTitle().isEmpty()) { |
368 | |
369 | if (fakeNode->subType() != Node::File) { |
370 | if (fakeNode->doc().hasKeywords()) { |
371 | foreach (const Atom *keyword, fakeNode->doc().keywords()) { |
372 | if (!keyword->string().isEmpty()) { |
373 | QStringList details; |
374 | details << keyword->string() |
375 | << keyword->string() |
376 | << HtmlGenerator::fullDocumentLocation(node) + "#"+ Doc::canonicalTitle(keyword->string()); |
377 | project.keywords.append(details); |
378 | } else |
379 | fakeNode->doc().location().warning( |
380 | tr("Bad keyword in %1").arg(HtmlGenerator::fullDocumentLocation(node)) |
381 | ); |
382 | } |
383 | } |
384 | project.keywords.append(keywordDetails(node)); |
385 | } |
386 | /* |
387 | if (fakeNode->doc().hasTableOfContents()) { |
388 | foreach (const Atom *item, fakeNode->doc().tableOfContents()) { |
389 | QString title = Text::sectionHeading(item).toString(); |
390 | if (!title.isEmpty()) { |
391 | QStringList details; |
392 | details << title |
393 | << title |
394 | << HtmlGenerator::fullDocumentLocation(node) + "#" + Doc::canonicalTitle(title); |
395 | project.keywords.append(details); |
396 | } else |
397 | fakeNode->doc().location().warning( |
398 | tr("Bad contents item in %1").arg(HtmlGenerator::fullDocumentLocation(node)) |
399 | ); |
400 | } |
401 | } |
402 | */ |
403 | project.files.insert(HtmlGenerator::fullDocumentLocation(node)); |
404 | } |
405 | break; |
406 | } |
407 | default: |
408 | ; |
409 | } |
410 | |
411 | // Add all images referenced in the page to the set of files to include. |
412 | const Atom *atom = node->doc().body().firstAtom(); |
413 | while (atom) { |
414 | if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) { |
415 | // Images are all placed within a single directory regardless of |
416 | // whether the source images are in a nested directory structure. |
417 | QStringList pieces = atom->string().split("/"); |
418 | project.files.insert("images/"+ pieces.last()); |
419 | } |
420 | atom = atom->next(); |
421 | } |
422 | |
423 | return true; |
424 | } |
425 | |
426 | void HelpProjectWriter::generateSections(HelpProject &project, |
427 | QXmlStreamWriter &writer, const Node *node) |
428 | { |
429 | if (!generateSection(project, writer, node)) |
430 | return; |
431 | |
432 | if (node->isInnerNode()) { |
433 | const InnerNode *inner = static_cast<const InnerNode *>(node); |
434 | |
435 | // Ensure that we don't visit nodes more than once. |
436 | QMap<QString, const Node*> childMap; |
437 | foreach (const Node *node, inner->childNodes()) { |
438 | if (node->access() == Node::Private) |
439 | continue; |
440 | if (node->type() == Node::Fake) { |
441 | /* |
442 | Don't visit QML property group nodes, |
443 | but visit their children, which are all |
444 | QML property nodes. |
445 | */ |
446 | if (node->subType() == Node::QmlPropertyGroup) { |
447 | const InnerNode* inner = static_cast<const InnerNode*>(node); |
448 | foreach (const Node* n, inner->childNodes()) { |
449 | if (n->access() == Node::Private) |
450 | continue; |
451 | childMap[tree->fullDocumentName(n)] = n; |
452 | } |
453 | } |
454 | else |
455 | childMap[static_cast<const FakeNode *>(node)->fullTitle()] = node; |
456 | } |
457 | else { |
458 | if (node->type() == Node::Function) { |
459 | const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
460 | if (funcNode->isOverload()) |
461 | continue; |
462 | } |
463 | childMap[tree->fullDocumentName(node)] = node; |
464 | } |
465 | } |
466 | |
467 | foreach (const Node *child, childMap) |
468 | generateSections(project, writer, child); |
469 | } |
470 | } |
471 | |
472 | void HelpProjectWriter::generate(const Tree *tre) |
473 | { |
474 | this->tree = tre; |
475 | for (int i = 0; i < projects.size(); ++i) |
476 | generateProject(projects[i]); |
477 | } |
478 | |
479 | void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, |
480 | const Node *node) |
481 | { |
482 | QString href = HtmlGenerator::fullDocumentLocation(node); |
483 | QString objName = node->name(); |
484 | |
485 | switch (node->type()) { |
486 | |
487 | case Node::Class: |
488 | writer.writeStartElement("section"); |
489 | writer.writeAttribute("ref", href); |
490 | if (node->parent() && !node->parent()->name().isEmpty()) |
491 | writer.writeAttribute("title", tr( "%1::%2 Class Reference").arg(node->parent()->name()).arg(objName)); |
492 | else |
493 | writer.writeAttribute("title", tr( "%1 Class Reference").arg(objName)); |
494 | |
495 | // Write subsections for all members, obsolete members and Qt 3 |
496 | // members. |
497 | if (!project.memberStatus[node].isEmpty()) { |
498 | QString membersPath = href.left(href.size()-5) + "-members.html"; |
499 | writer.writeStartElement("section"); |
500 | writer.writeAttribute("ref", membersPath); |
501 | writer.writeAttribute("title", tr( "List of all members")); |
502 | writer.writeEndElement(); // section |
503 | project.files.insert(membersPath); |
504 | } |
505 | if (project.memberStatus[node].contains(Node::Compat)) { |
506 | QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
507 | writer.writeStartElement("section"); |
508 | writer.writeAttribute("ref", compatPath); |
509 | writer.writeAttribute("title", tr( "Qt 3 support members")); |
510 | writer.writeEndElement(); // section |
511 | project.files.insert(compatPath); |
512 | } |
513 | if (project.memberStatus[node].contains(Node::Obsolete)) { |
514 | QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
515 | writer.writeStartElement("section"); |
516 | writer.writeAttribute("ref", obsoletePath); |
517 | writer.writeAttribute("title", tr( "Obsolete members")); |
518 | writer.writeEndElement(); // section |
519 | project.files.insert(obsoletePath); |
520 | } |
521 | |
522 | writer.writeEndElement(); // section |
523 | break; |
524 | |
525 | case Node::Namespace: |
526 | writer.writeStartElement("section"); |
527 | writer.writeAttribute("ref", href); |
528 | writer.writeAttribute("title", objName); |
529 | writer.writeEndElement(); // section |
530 | break; |
531 | |
532 | case Node::Fake: { |
533 | // Fake nodes (such as manual pages) contain subtypes, titles and other |
534 | // attributes. |
535 | const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
536 | |
537 | writer.writeStartElement("section"); |
538 | writer.writeAttribute("ref", href); |
539 | writer.writeAttribute("title", fakeNode->fullTitle()); |
540 | |
541 | if ((fakeNode->subType() == Node::HeaderFile) || (fakeNode->subType() == Node::QmlClass)) { |
542 | // Write subsections for all members, obsolete members and Qt 3 |
543 | // members. |
544 | if (!project.memberStatus[node].isEmpty() || (fakeNode->subType() == Node::QmlClass)) { |
545 | QString membersPath = href.left(href.size()-5) + "-members.html"; |
546 | writer.writeStartElement("section"); |
547 | writer.writeAttribute("ref", membersPath); |
548 | writer.writeAttribute("title", tr( "List of all members")); |
549 | writer.writeEndElement(); // section |
550 | project.files.insert(membersPath); |
551 | } |
552 | if (project.memberStatus[node].contains(Node::Compat)) { |
553 | QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
554 | writer.writeStartElement("section"); |
555 | writer.writeAttribute("ref", compatPath); |
556 | writer.writeAttribute("title", tr( "Qt 3 support members")); |
557 | writer.writeEndElement(); // section |
558 | project.files.insert(compatPath); |
559 | } |
560 | if (project.memberStatus[node].contains(Node::Obsolete)) { |
561 | QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
562 | writer.writeStartElement("section"); |
563 | writer.writeAttribute("ref", obsoletePath); |
564 | writer.writeAttribute("title", tr( "Obsolete members")); |
565 | writer.writeEndElement(); // section |
566 | project.files.insert(obsoletePath); |
567 | } |
568 | } |
569 | |
570 | writer.writeEndElement(); // section |
571 | } |
572 | break; |
573 | default: |
574 | ; |
575 | } |
576 | } |
577 | |
578 | void HelpProjectWriter::generateProject(HelpProject &project) |
579 | { |
580 | const Node *rootNode; |
581 | if (!project.indexRoot.isEmpty()) |
582 | rootNode = tree->findFakeNodeByTitle(project.indexRoot); |
583 | else |
584 | rootNode = tree->root(); |
585 | |
586 | if (!rootNode) |
587 | return; |
588 | |
589 | project.files.clear(); |
590 | project.keywords.clear(); |
591 | |
592 | QFile file(outputDir + QDir::separator() + project.fileName); |
593 | if (!file.open(QFile::WriteOnly | QFile::Text)) |
594 | return; |
595 | |
596 | QXmlStreamWriter writer(&file); |
597 | writer.setAutoFormatting(true); |
598 | writer.writeStartDocument(); |
599 | writer.writeStartElement("QtHelpProject"); |
600 | writer.writeAttribute("version", "1.0"); |
601 | |
602 | // Write metaData, virtualFolder and namespace elements. |
603 | writer.writeTextElement("namespace", project.helpNamespace); |
604 | writer.writeTextElement("virtualFolder", project.virtualFolder); |
605 | |
606 | // Write customFilter elements. |
607 | QHash<QString, QSet<QString> >::ConstIterator it; |
608 | for (it = project.customFilters.begin(); it != project.customFilters.end(); ++it) { |
609 | writer.writeStartElement("customFilter"); |
610 | writer.writeAttribute("name", it.key()); |
611 | foreach (const QString &filter, it.value()) |
612 | writer.writeTextElement("filterAttribute", filter); |
613 | writer.writeEndElement(); // customFilter |
614 | } |
615 | |
616 | // Start the filterSection. |
617 | writer.writeStartElement("filterSection"); |
618 | |
619 | // Write filterAttribute elements. |
620 | foreach (const QString &filterName, project.filterAttributes) |
621 | writer.writeTextElement("filterAttribute", filterName); |
622 | |
623 | writer.writeStartElement("toc"); |
624 | writer.writeStartElement("section"); |
625 | QString indexPath = HtmlGenerator::fullDocumentLocation(tree->findFakeNodeByTitle(project.indexTitle)); |
626 | if (indexPath.isEmpty()) |
627 | indexPath = "index.html"; |
628 | writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
629 | writer.writeAttribute("title", project.indexTitle); |
630 | project.files.insert(HtmlGenerator::fullDocumentLocation(rootNode)); |
631 | |
632 | generateSections(project, writer, rootNode); |
633 | |
634 | foreach (const QString &name, project.subprojects.keys()) { |
635 | SubProject subproject = project.subprojects[name]; |
636 | |
637 | if (subproject.type == QLatin1String("manual")) { |
638 | |
639 | const FakeNode *indexPage = tree->findFakeNodeByTitle(subproject.indexTitle); |
640 | if (indexPage) { |
641 | Text indexBody = indexPage->doc().body(); |
642 | const Atom *atom = indexBody.firstAtom(); |
643 | QStack<int> sectionStack; |
644 | bool inItem = false; |
645 | |
646 | while (atom) { |
647 | switch (atom->type()) { |
648 | case Atom::ListLeft: |
649 | sectionStack.push(0); |
650 | break; |
651 | case Atom::ListRight: |
652 | if (sectionStack.pop() > 0) |
653 | writer.writeEndElement(); // section |
654 | break; |
655 | case Atom::ListItemLeft: |
656 | inItem = true; |
657 | break; |
658 | case Atom::ListItemRight: |
659 | inItem = false; |
660 | break; |
661 | case Atom::Link: |
662 | if (inItem) { |
663 | if (sectionStack.top() > 0) |
664 | writer.writeEndElement(); // section |
665 | |
666 | const FakeNode *page = tree->findFakeNodeByTitle(atom->string()); |
667 | writer.writeStartElement("section"); |
668 | QString indexPath = HtmlGenerator::fullDocumentLocation(page); |
669 | writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
670 | writer.writeAttribute("title", atom->string()); |
671 | project.files.insert(indexPath); |
672 | |
673 | sectionStack.top() += 1; |
674 | } |
675 | break; |
676 | default: |
677 | ; |
678 | } |
679 | |
680 | if (atom == indexBody.lastAtom()) |
681 | break; |
682 | atom = atom->next(); |
683 | } |
684 | } else |
685 | rootNode->doc().location().warning( |
686 | tr("Failed to find index: %1").arg(subproject.indexTitle) |
687 | ); |
688 | |
689 | } else { |
690 | |
691 | if (!name.isEmpty()) { |
692 | writer.writeStartElement("section"); |
693 | QString indexPath = HtmlGenerator::fullDocumentLocation(tree->findFakeNodeByTitle(subproject.indexTitle)); |
694 | writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
695 | writer.writeAttribute("title", subproject.title); |
696 | project.files.insert(indexPath); |
697 | } |
698 | if (subproject.sortPages) { |
699 | QStringList titles = subproject.nodes.keys(); |
700 | titles.sort(); |
701 | foreach (const QString &title, titles) { |
702 | writeNode(project, writer, subproject.nodes[title]); |
703 | } |
704 | } else { |
705 | // Find a contents node and navigate from there, using the NextLink values. |
706 | QSet<QString> visited; |
707 | |
708 | foreach (const Node *node, subproject.nodes) { |
709 | QString nextTitle = node->links().value(Node::NextLink).first; |
710 | if (!nextTitle.isEmpty() && |
711 | node->links().value(Node::ContentsLink).first.isEmpty()) { |
712 | |
713 | FakeNode *nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
714 | |
715 | // Write the contents node. |
716 | writeNode(project, writer, node); |
717 | |
718 | while (nextPage) { |
719 | writeNode(project, writer, nextPage); |
720 | nextTitle = nextPage->links().value(Node::NextLink).first; |
721 | if (nextTitle.isEmpty() || visited.contains(nextTitle)) |
722 | break; |
723 | nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
724 | visited.insert(nextTitle); |
725 | } |
726 | break; |
727 | } |
728 | } |
729 | } |
730 | |
731 | if (!name.isEmpty()) |
732 | writer.writeEndElement(); // section |
733 | } |
734 | } |
735 | |
736 | writer.writeEndElement(); // section |
737 | writer.writeEndElement(); // toc |
738 | |
739 | writer.writeStartElement("keywords"); |
740 | foreach (const QStringList &details, project.keywords) { |
741 | writer.writeStartElement("keyword"); |
742 | writer.writeAttribute("name", details[0]); |
743 | writer.writeAttribute("id", details[1]); |
744 | writer.writeAttribute("ref", HtmlGenerator::cleanRef(details[2])); |
745 | writer.writeEndElement(); //keyword |
746 | } |
747 | writer.writeEndElement(); // keywords |
748 | |
749 | writer.writeStartElement("files"); |
750 | foreach (const QString &usedFile, project.files) { |
751 | if (!usedFile.isEmpty()) |
752 | writer.writeTextElement("file", usedFile); |
753 | } |
754 | foreach (const QString &usedFile, project.extraFiles) |
755 | writer.writeTextElement("file", usedFile); |
756 | writer.writeEndElement(); // files |
757 | |
758 | writer.writeEndElement(); // filterSection |
759 | writer.writeEndElement(); // QtHelpProject |
760 | writer.writeEndDocument(); |
761 | file.close(); |
762 | } |
763 | |
764 | QT_END_NAMESPACE |
765 |
Warning: That file was not part of the compilation database. It may have many parsing errors.