source: trunk/tools/qdoc3/helpprojectwriter.cpp@ 1077

Last change on this file since 1077 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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