|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the tools applications of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
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 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include <QtXml> |
|
43 #include <QHash> |
|
44 #include <QMap> |
|
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 |
|
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 readSelectors(subproject, config.getStringList(subprefix + "selectors")); |
|
95 project.subprojects[name] = subproject; |
|
96 } |
|
97 |
|
98 if (project.subprojects.isEmpty()) { |
|
99 SubProject subproject; |
|
100 readSelectors(subproject, config.getStringList(prefix + "selectors")); |
|
101 project.subprojects[""] = subproject; |
|
102 } |
|
103 |
|
104 projects.append(project); |
|
105 } |
|
106 } |
|
107 |
|
108 void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors) |
|
109 { |
|
110 QHash<QString, Node::Type> typeHash; |
|
111 typeHash["namespace"] = Node::Namespace; |
|
112 typeHash["class"] = Node::Class; |
|
113 typeHash["fake"] = Node::Fake; |
|
114 typeHash["enum"] = Node::Enum; |
|
115 typeHash["typedef"] = Node::Typedef; |
|
116 typeHash["function"] = Node::Function; |
|
117 typeHash["property"] = Node::Property; |
|
118 typeHash["variable"] = Node::Variable; |
|
119 typeHash["target"] = Node::Target; |
|
120 |
|
121 QHash<QString, Node::SubType> subTypeHash; |
|
122 subTypeHash["example"] = Node::Example; |
|
123 subTypeHash["headerfile"] = Node::HeaderFile; |
|
124 subTypeHash["file"] = Node::File; |
|
125 subTypeHash["group"] = Node::Group; |
|
126 subTypeHash["module"] = Node::Module; |
|
127 subTypeHash["page"] = Node::Page; |
|
128 subTypeHash["externalpage"] = Node::ExternalPage; |
|
129 #ifdef QDOC_QML |
|
130 subTypeHash["qmlclass"] = Node::QmlClass; |
|
131 #endif |
|
132 |
|
133 QSet<Node::SubType> allSubTypes = QSet<Node::SubType>::fromList(subTypeHash.values()); |
|
134 |
|
135 foreach (const QString &selector, selectors) { |
|
136 QStringList pieces = selector.split(":"); |
|
137 if (pieces.size() == 1) { |
|
138 QString lower = selector.toLower(); |
|
139 if (typeHash.contains(lower)) |
|
140 subproject.selectors[typeHash[lower]] = allSubTypes; |
|
141 } else if (pieces.size() >= 2) { |
|
142 QString lower = pieces[0].toLower(); |
|
143 pieces = pieces[1].split(","); |
|
144 if (typeHash.contains(lower)) { |
|
145 QSet<Node::SubType> subTypes; |
|
146 for (int i = 0; i < pieces.size(); ++i) { |
|
147 QString lower = pieces[i].toLower(); |
|
148 if (subTypeHash.contains(lower)) |
|
149 subTypes.insert(subTypeHash[lower]); |
|
150 } |
|
151 subproject.selectors[typeHash[lower]] = subTypes; |
|
152 } |
|
153 } |
|
154 } |
|
155 } |
|
156 |
|
157 void HelpProjectWriter::addExtraFile(const QString &file) |
|
158 { |
|
159 for (int i = 0; i < projects.size(); ++i) |
|
160 projects[i].extraFiles.insert(file); |
|
161 } |
|
162 |
|
163 void HelpProjectWriter::addExtraFiles(const QSet<QString> &files) |
|
164 { |
|
165 for (int i = 0; i < projects.size(); ++i) |
|
166 projects[i].extraFiles.unite(files); |
|
167 } |
|
168 |
|
169 /* |
|
170 Returns a list of strings describing the keyword details for a given node. |
|
171 |
|
172 The first string is the human-readable name to be shown in Assistant. |
|
173 The second string is a unique identifier. |
|
174 The third string is the location of the documentation for the keyword. |
|
175 */ |
|
176 QStringList HelpProjectWriter::keywordDetails(const Node *node) const |
|
177 { |
|
178 QStringList details; |
|
179 |
|
180 if (node->parent() && !node->parent()->name().isEmpty()) { |
|
181 // "name" |
|
182 if (node->type() == Node::Enum || node->type() == Node::Typedef) |
|
183 details << node->parent()->name()+"::"+node->name(); |
|
184 else |
|
185 details << node->name(); |
|
186 // "id" |
|
187 details << node->parent()->name()+"::"+node->name(); |
|
188 } else if (node->type() == Node::Fake) { |
|
189 const FakeNode *fake = static_cast<const FakeNode *>(node); |
|
190 details << fake->fullTitle(); |
|
191 details << fake->fullTitle(); |
|
192 } else { |
|
193 details << node->name(); |
|
194 details << node->name(); |
|
195 } |
|
196 details << tree->fullDocumentLocation(node); |
|
197 |
|
198 return details; |
|
199 } |
|
200 |
|
201 bool HelpProjectWriter::generateSection(HelpProject &project, |
|
202 QXmlStreamWriter & /* writer */, const Node *node) |
|
203 { |
|
204 if (!node->url().isEmpty()) |
|
205 return false; |
|
206 |
|
207 if (node->access() == Node::Private || node->status() == Node::Internal) |
|
208 return false; |
|
209 |
|
210 if (node->name().isEmpty()) |
|
211 return true; |
|
212 |
|
213 QString docPath = node->doc().location().filePath(); |
|
214 if (!docPath.isEmpty() && project.excluded.contains(docPath)) |
|
215 return false; |
|
216 |
|
217 QString objName; |
|
218 if (node->type() == Node::Fake) { |
|
219 const FakeNode *fake = static_cast<const FakeNode *>(node); |
|
220 objName = fake->fullTitle(); |
|
221 } else |
|
222 objName = tree->fullDocumentName(node); |
|
223 |
|
224 // Only add nodes to the set for each subproject if they match a selector. |
|
225 // Those that match will be listed in the table of contents. |
|
226 |
|
227 foreach (const QString &name, project.subprojects.keys()) { |
|
228 SubProject subproject = project.subprojects[name]; |
|
229 // No selectors: accept all nodes. |
|
230 if (subproject.selectors.isEmpty()) |
|
231 project.subprojects[name].nodes[objName] = node; |
|
232 else if (subproject.selectors.contains(node->type())) { |
|
233 // Accept only the node types in the selectors hash. |
|
234 if (node->type() != Node::Fake) |
|
235 project.subprojects[name].nodes[objName] = node; |
|
236 else { |
|
237 // Accept only fake nodes with subtypes contained in the selector's |
|
238 // mask. |
|
239 const FakeNode *fakeNode = static_cast<const FakeNode *>(node); |
|
240 if (subproject.selectors[node->type()].contains(fakeNode->subType()) && |
|
241 fakeNode->subType() != Node::ExternalPage && |
|
242 !fakeNode->fullTitle().isEmpty()) |
|
243 |
|
244 project.subprojects[name].nodes[objName] = node; |
|
245 } |
|
246 } |
|
247 } |
|
248 |
|
249 switch (node->type()) { |
|
250 |
|
251 case Node::Class: |
|
252 project.keywords.append(keywordDetails(node)); |
|
253 project.files.insert(tree->fullDocumentLocation(node)); |
|
254 break; |
|
255 |
|
256 case Node::Namespace: |
|
257 project.keywords.append(keywordDetails(node)); |
|
258 project.files.insert(tree->fullDocumentLocation(node)); |
|
259 break; |
|
260 |
|
261 case Node::Enum: |
|
262 project.keywords.append(keywordDetails(node)); |
|
263 { |
|
264 const EnumNode *enumNode = static_cast<const EnumNode*>(node); |
|
265 foreach (const EnumItem &item, enumNode->items()) { |
|
266 QStringList details; |
|
267 |
|
268 if (enumNode->itemAccess(item.name()) == Node::Private) |
|
269 continue; |
|
270 |
|
271 if (!node->parent()->name().isEmpty()) { |
|
272 details << node->parent()->name()+"::"+item.name(); // "name" |
|
273 details << node->parent()->name()+"::"+item.name(); // "id" |
|
274 } else { |
|
275 details << item.name(); // "name" |
|
276 details << item.name(); // "id" |
|
277 } |
|
278 details << tree->fullDocumentLocation(node); |
|
279 project.keywords.append(details); |
|
280 } |
|
281 } |
|
282 break; |
|
283 |
|
284 case Node::Property: |
|
285 project.keywords.append(keywordDetails(node)); |
|
286 break; |
|
287 |
|
288 case Node::Function: |
|
289 { |
|
290 const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
|
291 |
|
292 // Only insert keywords for non-constructors. Constructors are covered |
|
293 // by the classes themselves. |
|
294 |
|
295 if (funcNode->metaness() != FunctionNode::Ctor) |
|
296 project.keywords.append(keywordDetails(node)); |
|
297 |
|
298 // Insert member status flags into the entries for the parent |
|
299 // node of the function, or the node it is related to. |
|
300 // Since parent nodes should have already been inserted into |
|
301 // the set of files, we only need to ensure that related nodes |
|
302 // are inserted. |
|
303 |
|
304 if (node->relates()) { |
|
305 project.memberStatus[node->relates()].insert(node->status()); |
|
306 project.files.insert(tree->fullDocumentLocation(node->relates())); |
|
307 } else if (node->parent()) |
|
308 project.memberStatus[node->parent()].insert(node->status()); |
|
309 } |
|
310 break; |
|
311 |
|
312 case Node::Typedef: |
|
313 { |
|
314 const TypedefNode *typedefNode = static_cast<const TypedefNode *>(node); |
|
315 QStringList typedefDetails = keywordDetails(node); |
|
316 const EnumNode *enumNode = typedefNode->associatedEnum(); |
|
317 // Use the location of any associated enum node in preference |
|
318 // to that of the typedef. |
|
319 if (enumNode) |
|
320 typedefDetails[2] = tree->fullDocumentLocation(enumNode); |
|
321 |
|
322 project.keywords.append(typedefDetails); |
|
323 } |
|
324 break; |
|
325 |
|
326 // Fake nodes (such as manual pages) contain subtypes, titles and other |
|
327 // attributes. |
|
328 case Node::Fake: { |
|
329 const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
|
330 if (fakeNode->subType() != Node::ExternalPage && |
|
331 !fakeNode->fullTitle().isEmpty()) { |
|
332 |
|
333 if (fakeNode->subType() != Node::File) { |
|
334 if (fakeNode->doc().hasKeywords()) { |
|
335 foreach (const Atom *keyword, fakeNode->doc().keywords()) { |
|
336 if (!keyword->string().isEmpty()) { |
|
337 QStringList details; |
|
338 details << keyword->string() |
|
339 << keyword->string() |
|
340 << tree->fullDocumentLocation(node) + "#" + Doc::canonicalTitle(keyword->string()); |
|
341 project.keywords.append(details); |
|
342 } else |
|
343 fakeNode->doc().location().warning( |
|
344 tr("Bad keyword in %1").arg(tree->fullDocumentLocation(node)) |
|
345 ); |
|
346 } |
|
347 } |
|
348 project.keywords.append(keywordDetails(node)); |
|
349 } |
|
350 /* |
|
351 if (fakeNode->doc().hasTableOfContents()) { |
|
352 foreach (const Atom *item, fakeNode->doc().tableOfContents()) { |
|
353 QString title = Text::sectionHeading(item).toString(); |
|
354 if (!title.isEmpty()) { |
|
355 QStringList details; |
|
356 details << title |
|
357 << title |
|
358 << tree->fullDocumentLocation(node) + "#" + Doc::canonicalTitle(title); |
|
359 project.keywords.append(details); |
|
360 } else |
|
361 fakeNode->doc().location().warning( |
|
362 tr("Bad contents item in %1").arg(tree->fullDocumentLocation(node)) |
|
363 ); |
|
364 } |
|
365 } |
|
366 */ |
|
367 project.files.insert(tree->fullDocumentLocation(node)); |
|
368 } |
|
369 break; |
|
370 } |
|
371 default: |
|
372 ; |
|
373 } |
|
374 |
|
375 // Add all images referenced in the page to the set of files to include. |
|
376 const Atom *atom = node->doc().body().firstAtom(); |
|
377 while (atom) { |
|
378 if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) { |
|
379 // Images are all placed within a single directory regardless of |
|
380 // whether the source images are in a nested directory structure. |
|
381 QStringList pieces = atom->string().split("/"); |
|
382 project.files.insert("images/" + pieces.last()); |
|
383 } |
|
384 atom = atom->next(); |
|
385 } |
|
386 |
|
387 return true; |
|
388 } |
|
389 |
|
390 void HelpProjectWriter::generateSections(HelpProject &project, |
|
391 QXmlStreamWriter &writer, const Node *node) |
|
392 { |
|
393 if (!generateSection(project, writer, node)) |
|
394 return; |
|
395 |
|
396 if (node->isInnerNode()) { |
|
397 const InnerNode *inner = static_cast<const InnerNode *>(node); |
|
398 |
|
399 // Ensure that we don't visit nodes more than once. |
|
400 QMap<QString, const Node*> childMap; |
|
401 foreach (const Node *node, inner->childNodes()) { |
|
402 if (node->access() == Node::Private) |
|
403 continue; |
|
404 if (node->type() == Node::Fake) |
|
405 childMap[static_cast<const FakeNode *>(node)->fullTitle()] = node; |
|
406 else { |
|
407 if (node->type() == Node::Function) { |
|
408 const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
|
409 if (funcNode->isOverload()) |
|
410 continue; |
|
411 } |
|
412 childMap[tree->fullDocumentName(node)] = node; |
|
413 } |
|
414 } |
|
415 |
|
416 foreach (const Node *child, childMap) |
|
417 generateSections(project, writer, child); |
|
418 } |
|
419 } |
|
420 |
|
421 void HelpProjectWriter::generate(const Tree *tre) |
|
422 { |
|
423 this->tree = tre; |
|
424 for (int i = 0; i < projects.size(); ++i) |
|
425 generateProject(projects[i]); |
|
426 } |
|
427 |
|
428 void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, |
|
429 const Node *node) |
|
430 { |
|
431 QString href = tree->fullDocumentLocation(node); |
|
432 QString objName = node->name(); |
|
433 |
|
434 switch (node->type()) { |
|
435 |
|
436 case Node::Class: |
|
437 writer.writeStartElement("section"); |
|
438 writer.writeAttribute("ref", href); |
|
439 if (node->parent() && !node->parent()->name().isEmpty()) |
|
440 writer.writeAttribute("title", tr("%1::%2 Class Reference").arg(node->parent()->name()).arg(objName)); |
|
441 else |
|
442 writer.writeAttribute("title", tr("%1 Class Reference").arg(objName)); |
|
443 |
|
444 // Write subsections for all members, obsolete members and Qt 3 |
|
445 // members. |
|
446 if (!project.memberStatus[node].isEmpty()) { |
|
447 QString membersPath = href.left(href.size()-5) + "-members.html"; |
|
448 writer.writeStartElement("section"); |
|
449 writer.writeAttribute("ref", membersPath); |
|
450 writer.writeAttribute("title", tr("List of all members")); |
|
451 writer.writeEndElement(); // section |
|
452 project.files.insert(membersPath); |
|
453 } |
|
454 if (project.memberStatus[node].contains(Node::Compat)) { |
|
455 QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
|
456 writer.writeStartElement("section"); |
|
457 writer.writeAttribute("ref", compatPath); |
|
458 writer.writeAttribute("title", tr("Qt 3 support members")); |
|
459 writer.writeEndElement(); // section |
|
460 project.files.insert(compatPath); |
|
461 } |
|
462 if (project.memberStatus[node].contains(Node::Obsolete)) { |
|
463 QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
|
464 writer.writeStartElement("section"); |
|
465 writer.writeAttribute("ref", obsoletePath); |
|
466 writer.writeAttribute("title", tr("Obsolete members")); |
|
467 writer.writeEndElement(); // section |
|
468 project.files.insert(obsoletePath); |
|
469 } |
|
470 |
|
471 writer.writeEndElement(); // section |
|
472 break; |
|
473 |
|
474 case Node::Namespace: |
|
475 writer.writeStartElement("section"); |
|
476 writer.writeAttribute("ref", href); |
|
477 writer.writeAttribute("title", objName); |
|
478 writer.writeEndElement(); // section |
|
479 break; |
|
480 |
|
481 case Node::Fake: { |
|
482 // Fake nodes (such as manual pages) contain subtypes, titles and other |
|
483 // attributes. |
|
484 const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
|
485 |
|
486 writer.writeStartElement("section"); |
|
487 writer.writeAttribute("ref", href); |
|
488 writer.writeAttribute("title", fakeNode->fullTitle()); |
|
489 // qDebug() << "Title:" << fakeNode->fullTitle(); |
|
490 |
|
491 if (fakeNode->subType() == Node::HeaderFile) { |
|
492 |
|
493 // Write subsections for all members, obsolete members and Qt 3 |
|
494 // members. |
|
495 if (!project.memberStatus[node].isEmpty()) { |
|
496 QString membersPath = href.left(href.size()-5) + "-members.html"; |
|
497 writer.writeStartElement("section"); |
|
498 writer.writeAttribute("ref", membersPath); |
|
499 writer.writeAttribute("title", tr("List of all members")); |
|
500 writer.writeEndElement(); // section |
|
501 project.files.insert(membersPath); |
|
502 } |
|
503 if (project.memberStatus[node].contains(Node::Compat)) { |
|
504 QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
|
505 writer.writeStartElement("section"); |
|
506 writer.writeAttribute("ref", compatPath); |
|
507 writer.writeAttribute("title", tr("Qt 3 support members")); |
|
508 writer.writeEndElement(); // section |
|
509 project.files.insert(compatPath); |
|
510 } |
|
511 if (project.memberStatus[node].contains(Node::Obsolete)) { |
|
512 QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
|
513 writer.writeStartElement("section"); |
|
514 writer.writeAttribute("ref", obsoletePath); |
|
515 writer.writeAttribute("title", tr("Obsolete members")); |
|
516 writer.writeEndElement(); // section |
|
517 project.files.insert(obsoletePath); |
|
518 } |
|
519 } |
|
520 |
|
521 writer.writeEndElement(); // section |
|
522 } |
|
523 break; |
|
524 default: |
|
525 ; |
|
526 } |
|
527 } |
|
528 |
|
529 void HelpProjectWriter::generateProject(HelpProject &project) |
|
530 { |
|
531 const Node *rootNode; |
|
532 if (!project.indexRoot.isEmpty()) |
|
533 rootNode = tree->findFakeNodeByTitle(project.indexRoot); |
|
534 else |
|
535 rootNode = tree->root(); |
|
536 |
|
537 if (!rootNode) |
|
538 return; |
|
539 |
|
540 project.files.clear(); |
|
541 project.keywords.clear(); |
|
542 |
|
543 QFile file(outputDir + QDir::separator() + project.fileName); |
|
544 if (!file.open(QFile::WriteOnly | QFile::Text)) |
|
545 return; |
|
546 |
|
547 QXmlStreamWriter writer(&file); |
|
548 writer.setAutoFormatting(true); |
|
549 writer.writeStartDocument(); |
|
550 writer.writeStartElement("QtHelpProject"); |
|
551 writer.writeAttribute("version", "1.0"); |
|
552 |
|
553 // Write metaData, virtualFolder and namespace elements. |
|
554 writer.writeTextElement("namespace", project.helpNamespace); |
|
555 writer.writeTextElement("virtualFolder", project.virtualFolder); |
|
556 |
|
557 // Write customFilter elements. |
|
558 QHash<QString, QSet<QString> >::ConstIterator it; |
|
559 for (it = project.customFilters.begin(); it != project.customFilters.end(); ++it) { |
|
560 writer.writeStartElement("customFilter"); |
|
561 writer.writeAttribute("name", it.key()); |
|
562 foreach (const QString &filter, it.value()) |
|
563 writer.writeTextElement("filterAttribute", filter); |
|
564 writer.writeEndElement(); // customFilter |
|
565 } |
|
566 |
|
567 // Start the filterSection. |
|
568 writer.writeStartElement("filterSection"); |
|
569 |
|
570 // Write filterAttribute elements. |
|
571 foreach (const QString &filterName, project.filterAttributes) |
|
572 writer.writeTextElement("filterAttribute", filterName); |
|
573 |
|
574 writer.writeStartElement("toc"); |
|
575 writer.writeStartElement("section"); |
|
576 QString indexPath = tree->fullDocumentLocation(tree->findFakeNodeByTitle(project.indexTitle)); |
|
577 if (indexPath.isEmpty()) |
|
578 indexPath = "index.html"; |
|
579 writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
|
580 writer.writeAttribute("title", project.indexTitle); |
|
581 project.files.insert(tree->fullDocumentLocation(rootNode)); |
|
582 |
|
583 generateSections(project, writer, rootNode); |
|
584 |
|
585 foreach (const QString &name, project.subprojects.keys()) { |
|
586 SubProject subproject = project.subprojects[name]; |
|
587 |
|
588 if (!name.isEmpty()) { |
|
589 writer.writeStartElement("section"); |
|
590 QString indexPath = tree->fullDocumentLocation(tree->findFakeNodeByTitle(subproject.indexTitle)); |
|
591 writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
|
592 writer.writeAttribute("title", subproject.title); |
|
593 project.files.insert(indexPath); |
|
594 } |
|
595 if (subproject.sortPages) { |
|
596 QStringList titles = subproject.nodes.keys(); |
|
597 titles.sort(); |
|
598 foreach (const QString &title, titles) |
|
599 writeNode(project, writer, subproject.nodes[title]); |
|
600 } else { |
|
601 // Find a contents node and navigate from there, using the NextLink values. |
|
602 foreach (const Node *node, subproject.nodes) { |
|
603 QString nextTitle = node->links().value(Node::NextLink).first; |
|
604 if (!nextTitle.isEmpty() && |
|
605 node->links().value(Node::ContentsLink).first.isEmpty()) { |
|
606 |
|
607 FakeNode *nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
|
608 |
|
609 // Write the contents node. |
|
610 writeNode(project, writer, node); |
|
611 |
|
612 while (nextPage) { |
|
613 writeNode(project, writer, nextPage); |
|
614 nextTitle = nextPage->links().value(Node::NextLink).first; |
|
615 if(nextTitle.isEmpty()) |
|
616 break; |
|
617 nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
|
618 } |
|
619 break; |
|
620 } |
|
621 } |
|
622 } |
|
623 |
|
624 if (!name.isEmpty()) |
|
625 writer.writeEndElement(); // section |
|
626 } |
|
627 |
|
628 writer.writeEndElement(); // section |
|
629 writer.writeEndElement(); // toc |
|
630 |
|
631 writer.writeStartElement("keywords"); |
|
632 foreach (const QStringList &details, project.keywords) { |
|
633 writer.writeStartElement("keyword"); |
|
634 writer.writeAttribute("name", details[0]); |
|
635 writer.writeAttribute("id", details[1]); |
|
636 writer.writeAttribute("ref", HtmlGenerator::cleanRef(details[2])); |
|
637 writer.writeEndElement(); //keyword |
|
638 } |
|
639 writer.writeEndElement(); // keywords |
|
640 |
|
641 writer.writeStartElement("files"); |
|
642 foreach (const QString &usedFile, project.files) { |
|
643 if (!usedFile.isEmpty()) |
|
644 writer.writeTextElement("file", usedFile); |
|
645 } |
|
646 foreach (const QString &usedFile, project.extraFiles) |
|
647 writer.writeTextElement("file", usedFile); |
|
648 writer.writeEndElement(); // files |
|
649 |
|
650 writer.writeEndElement(); // filterSection |
|
651 writer.writeEndElement(); // QtHelpProject |
|
652 writer.writeEndDocument(); |
|
653 file.close(); |
|
654 } |
|
655 |
|
656 QT_END_NAMESPACE |