|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 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 #ifdef QDOC_QML |
|
191 if (fake->subType() == Node::QmlClass) { |
|
192 details << (QmlClassNode::qmlOnly ? fake->name() : fake->fullTitle()); |
|
193 details << "QML." + fake->name(); |
|
194 } else |
|
195 #endif |
|
196 { |
|
197 details << fake->fullTitle(); |
|
198 details << fake->fullTitle(); |
|
199 } |
|
200 } else { |
|
201 details << node->name(); |
|
202 details << node->name(); |
|
203 } |
|
204 details << tree->fullDocumentLocation(node); |
|
205 |
|
206 return details; |
|
207 } |
|
208 |
|
209 bool HelpProjectWriter::generateSection(HelpProject &project, |
|
210 QXmlStreamWriter & /* writer */, const Node *node) |
|
211 { |
|
212 if (!node->url().isEmpty()) |
|
213 return false; |
|
214 |
|
215 if (node->access() == Node::Private || node->status() == Node::Internal) |
|
216 return false; |
|
217 |
|
218 if (node->name().isEmpty()) |
|
219 return true; |
|
220 |
|
221 QString docPath = node->doc().location().filePath(); |
|
222 if (!docPath.isEmpty() && project.excluded.contains(docPath)) |
|
223 return false; |
|
224 |
|
225 QString objName; |
|
226 if (node->type() == Node::Fake) { |
|
227 const FakeNode *fake = static_cast<const FakeNode *>(node); |
|
228 objName = fake->fullTitle(); |
|
229 } else |
|
230 objName = tree->fullDocumentName(node); |
|
231 |
|
232 // Only add nodes to the set for each subproject if they match a selector. |
|
233 // Those that match will be listed in the table of contents. |
|
234 |
|
235 foreach (const QString &name, project.subprojects.keys()) { |
|
236 SubProject subproject = project.subprojects[name]; |
|
237 // No selectors: accept all nodes. |
|
238 if (subproject.selectors.isEmpty()) |
|
239 project.subprojects[name].nodes[objName] = node; |
|
240 else if (subproject.selectors.contains(node->type())) { |
|
241 // Accept only the node types in the selectors hash. |
|
242 if (node->type() != Node::Fake) |
|
243 project.subprojects[name].nodes[objName] = node; |
|
244 else { |
|
245 // Accept only fake nodes with subtypes contained in the selector's |
|
246 // mask. |
|
247 const FakeNode *fakeNode = static_cast<const FakeNode *>(node); |
|
248 if (subproject.selectors[node->type()].contains(fakeNode->subType()) && |
|
249 fakeNode->subType() != Node::ExternalPage && |
|
250 !fakeNode->fullTitle().isEmpty()) |
|
251 |
|
252 project.subprojects[name].nodes[objName] = node; |
|
253 } |
|
254 } |
|
255 } |
|
256 |
|
257 switch (node->type()) { |
|
258 |
|
259 case Node::Class: |
|
260 project.keywords.append(keywordDetails(node)); |
|
261 project.files.insert(tree->fullDocumentLocation(node)); |
|
262 break; |
|
263 |
|
264 case Node::Namespace: |
|
265 project.keywords.append(keywordDetails(node)); |
|
266 project.files.insert(tree->fullDocumentLocation(node)); |
|
267 break; |
|
268 |
|
269 case Node::Enum: |
|
270 project.keywords.append(keywordDetails(node)); |
|
271 { |
|
272 const EnumNode *enumNode = static_cast<const EnumNode*>(node); |
|
273 foreach (const EnumItem &item, enumNode->items()) { |
|
274 QStringList details; |
|
275 |
|
276 if (enumNode->itemAccess(item.name()) == Node::Private) |
|
277 continue; |
|
278 |
|
279 if (!node->parent()->name().isEmpty()) { |
|
280 details << node->parent()->name()+"::"+item.name(); // "name" |
|
281 details << node->parent()->name()+"::"+item.name(); // "id" |
|
282 } else { |
|
283 details << item.name(); // "name" |
|
284 details << item.name(); // "id" |
|
285 } |
|
286 details << tree->fullDocumentLocation(node); |
|
287 project.keywords.append(details); |
|
288 } |
|
289 } |
|
290 break; |
|
291 |
|
292 case Node::Property: |
|
293 project.keywords.append(keywordDetails(node)); |
|
294 break; |
|
295 |
|
296 case Node::Function: |
|
297 { |
|
298 const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
|
299 |
|
300 // Only insert keywords for non-constructors. Constructors are covered |
|
301 // by the classes themselves. |
|
302 |
|
303 if (funcNode->metaness() != FunctionNode::Ctor) |
|
304 project.keywords.append(keywordDetails(node)); |
|
305 |
|
306 // Insert member status flags into the entries for the parent |
|
307 // node of the function, or the node it is related to. |
|
308 // Since parent nodes should have already been inserted into |
|
309 // the set of files, we only need to ensure that related nodes |
|
310 // are inserted. |
|
311 |
|
312 if (node->relates()) { |
|
313 project.memberStatus[node->relates()].insert(node->status()); |
|
314 project.files.insert(tree->fullDocumentLocation(node->relates())); |
|
315 } else if (node->parent()) |
|
316 project.memberStatus[node->parent()].insert(node->status()); |
|
317 } |
|
318 break; |
|
319 |
|
320 case Node::Typedef: |
|
321 { |
|
322 const TypedefNode *typedefNode = static_cast<const TypedefNode *>(node); |
|
323 QStringList typedefDetails = keywordDetails(node); |
|
324 const EnumNode *enumNode = typedefNode->associatedEnum(); |
|
325 // Use the location of any associated enum node in preference |
|
326 // to that of the typedef. |
|
327 if (enumNode) |
|
328 typedefDetails[2] = tree->fullDocumentLocation(enumNode); |
|
329 |
|
330 project.keywords.append(typedefDetails); |
|
331 } |
|
332 break; |
|
333 |
|
334 // Fake nodes (such as manual pages) contain subtypes, titles and other |
|
335 // attributes. |
|
336 case Node::Fake: { |
|
337 const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
|
338 if (fakeNode->subType() != Node::ExternalPage && |
|
339 !fakeNode->fullTitle().isEmpty()) { |
|
340 |
|
341 if (fakeNode->subType() != Node::File) { |
|
342 if (fakeNode->doc().hasKeywords()) { |
|
343 foreach (const Atom *keyword, fakeNode->doc().keywords()) { |
|
344 if (!keyword->string().isEmpty()) { |
|
345 QStringList details; |
|
346 details << keyword->string() |
|
347 << keyword->string() |
|
348 << tree->fullDocumentLocation(node) + "#" + Doc::canonicalTitle(keyword->string()); |
|
349 project.keywords.append(details); |
|
350 } else |
|
351 fakeNode->doc().location().warning( |
|
352 tr("Bad keyword in %1").arg(tree->fullDocumentLocation(node)) |
|
353 ); |
|
354 } |
|
355 } |
|
356 project.keywords.append(keywordDetails(node)); |
|
357 } |
|
358 /* |
|
359 if (fakeNode->doc().hasTableOfContents()) { |
|
360 foreach (const Atom *item, fakeNode->doc().tableOfContents()) { |
|
361 QString title = Text::sectionHeading(item).toString(); |
|
362 if (!title.isEmpty()) { |
|
363 QStringList details; |
|
364 details << title |
|
365 << title |
|
366 << tree->fullDocumentLocation(node) + "#" + Doc::canonicalTitle(title); |
|
367 project.keywords.append(details); |
|
368 } else |
|
369 fakeNode->doc().location().warning( |
|
370 tr("Bad contents item in %1").arg(tree->fullDocumentLocation(node)) |
|
371 ); |
|
372 } |
|
373 } |
|
374 */ |
|
375 project.files.insert(tree->fullDocumentLocation(node)); |
|
376 } |
|
377 break; |
|
378 } |
|
379 default: |
|
380 ; |
|
381 } |
|
382 |
|
383 // Add all images referenced in the page to the set of files to include. |
|
384 const Atom *atom = node->doc().body().firstAtom(); |
|
385 while (atom) { |
|
386 if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) { |
|
387 // Images are all placed within a single directory regardless of |
|
388 // whether the source images are in a nested directory structure. |
|
389 QStringList pieces = atom->string().split("/"); |
|
390 project.files.insert("images/" + pieces.last()); |
|
391 } |
|
392 atom = atom->next(); |
|
393 } |
|
394 |
|
395 return true; |
|
396 } |
|
397 |
|
398 void HelpProjectWriter::generateSections(HelpProject &project, |
|
399 QXmlStreamWriter &writer, const Node *node) |
|
400 { |
|
401 if (!generateSection(project, writer, node)) |
|
402 return; |
|
403 |
|
404 if (node->isInnerNode()) { |
|
405 const InnerNode *inner = static_cast<const InnerNode *>(node); |
|
406 |
|
407 // Ensure that we don't visit nodes more than once. |
|
408 QMap<QString, const Node*> childMap; |
|
409 foreach (const Node *node, inner->childNodes()) { |
|
410 if (node->access() == Node::Private) |
|
411 continue; |
|
412 if (node->type() == Node::Fake) |
|
413 childMap[static_cast<const FakeNode *>(node)->fullTitle()] = node; |
|
414 else { |
|
415 if (node->type() == Node::Function) { |
|
416 const FunctionNode *funcNode = static_cast<const FunctionNode *>(node); |
|
417 if (funcNode->isOverload()) |
|
418 continue; |
|
419 } |
|
420 childMap[tree->fullDocumentName(node)] = node; |
|
421 } |
|
422 } |
|
423 |
|
424 foreach (const Node *child, childMap) |
|
425 generateSections(project, writer, child); |
|
426 } |
|
427 } |
|
428 |
|
429 void HelpProjectWriter::generate(const Tree *tre) |
|
430 { |
|
431 this->tree = tre; |
|
432 for (int i = 0; i < projects.size(); ++i) |
|
433 generateProject(projects[i]); |
|
434 } |
|
435 |
|
436 void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, |
|
437 const Node *node) |
|
438 { |
|
439 QString href = tree->fullDocumentLocation(node); |
|
440 QString objName = node->name(); |
|
441 |
|
442 switch (node->type()) { |
|
443 |
|
444 case Node::Class: |
|
445 writer.writeStartElement("section"); |
|
446 writer.writeAttribute("ref", href); |
|
447 if (node->parent() && !node->parent()->name().isEmpty()) |
|
448 writer.writeAttribute("title", tr("%1::%2 Class Reference").arg(node->parent()->name()).arg(objName)); |
|
449 else |
|
450 writer.writeAttribute("title", tr("%1 Class Reference").arg(objName)); |
|
451 |
|
452 // Write subsections for all members, obsolete members and Qt 3 |
|
453 // members. |
|
454 if (!project.memberStatus[node].isEmpty()) { |
|
455 QString membersPath = href.left(href.size()-5) + "-members.html"; |
|
456 writer.writeStartElement("section"); |
|
457 writer.writeAttribute("ref", membersPath); |
|
458 writer.writeAttribute("title", tr("List of all members")); |
|
459 writer.writeEndElement(); // section |
|
460 project.files.insert(membersPath); |
|
461 } |
|
462 if (project.memberStatus[node].contains(Node::Compat)) { |
|
463 QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
|
464 writer.writeStartElement("section"); |
|
465 writer.writeAttribute("ref", compatPath); |
|
466 writer.writeAttribute("title", tr("Qt 3 support members")); |
|
467 writer.writeEndElement(); // section |
|
468 project.files.insert(compatPath); |
|
469 } |
|
470 if (project.memberStatus[node].contains(Node::Obsolete)) { |
|
471 QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
|
472 writer.writeStartElement("section"); |
|
473 writer.writeAttribute("ref", obsoletePath); |
|
474 writer.writeAttribute("title", tr("Obsolete members")); |
|
475 writer.writeEndElement(); // section |
|
476 project.files.insert(obsoletePath); |
|
477 } |
|
478 |
|
479 writer.writeEndElement(); // section |
|
480 break; |
|
481 |
|
482 case Node::Namespace: |
|
483 writer.writeStartElement("section"); |
|
484 writer.writeAttribute("ref", href); |
|
485 writer.writeAttribute("title", objName); |
|
486 writer.writeEndElement(); // section |
|
487 break; |
|
488 |
|
489 case Node::Fake: { |
|
490 // Fake nodes (such as manual pages) contain subtypes, titles and other |
|
491 // attributes. |
|
492 const FakeNode *fakeNode = static_cast<const FakeNode*>(node); |
|
493 |
|
494 writer.writeStartElement("section"); |
|
495 writer.writeAttribute("ref", href); |
|
496 writer.writeAttribute("title", fakeNode->fullTitle()); |
|
497 // qDebug() << "Title:" << fakeNode->fullTitle(); |
|
498 |
|
499 if (fakeNode->subType() == Node::HeaderFile) { |
|
500 |
|
501 // Write subsections for all members, obsolete members and Qt 3 |
|
502 // members. |
|
503 if (!project.memberStatus[node].isEmpty()) { |
|
504 QString membersPath = href.left(href.size()-5) + "-members.html"; |
|
505 writer.writeStartElement("section"); |
|
506 writer.writeAttribute("ref", membersPath); |
|
507 writer.writeAttribute("title", tr("List of all members")); |
|
508 writer.writeEndElement(); // section |
|
509 project.files.insert(membersPath); |
|
510 } |
|
511 if (project.memberStatus[node].contains(Node::Compat)) { |
|
512 QString compatPath = href.left(href.size()-5) + "-qt3.html"; |
|
513 writer.writeStartElement("section"); |
|
514 writer.writeAttribute("ref", compatPath); |
|
515 writer.writeAttribute("title", tr("Qt 3 support members")); |
|
516 writer.writeEndElement(); // section |
|
517 project.files.insert(compatPath); |
|
518 } |
|
519 if (project.memberStatus[node].contains(Node::Obsolete)) { |
|
520 QString obsoletePath = href.left(href.size()-5) + "-obsolete.html"; |
|
521 writer.writeStartElement("section"); |
|
522 writer.writeAttribute("ref", obsoletePath); |
|
523 writer.writeAttribute("title", tr("Obsolete members")); |
|
524 writer.writeEndElement(); // section |
|
525 project.files.insert(obsoletePath); |
|
526 } |
|
527 } |
|
528 |
|
529 writer.writeEndElement(); // section |
|
530 } |
|
531 break; |
|
532 default: |
|
533 ; |
|
534 } |
|
535 } |
|
536 |
|
537 void HelpProjectWriter::generateProject(HelpProject &project) |
|
538 { |
|
539 const Node *rootNode; |
|
540 if (!project.indexRoot.isEmpty()) |
|
541 rootNode = tree->findFakeNodeByTitle(project.indexRoot); |
|
542 else |
|
543 rootNode = tree->root(); |
|
544 |
|
545 if (!rootNode) |
|
546 return; |
|
547 |
|
548 project.files.clear(); |
|
549 project.keywords.clear(); |
|
550 |
|
551 QFile file(outputDir + QDir::separator() + project.fileName); |
|
552 if (!file.open(QFile::WriteOnly | QFile::Text)) |
|
553 return; |
|
554 |
|
555 QXmlStreamWriter writer(&file); |
|
556 writer.setAutoFormatting(true); |
|
557 writer.writeStartDocument(); |
|
558 writer.writeStartElement("QtHelpProject"); |
|
559 writer.writeAttribute("version", "1.0"); |
|
560 |
|
561 // Write metaData, virtualFolder and namespace elements. |
|
562 writer.writeTextElement("namespace", project.helpNamespace); |
|
563 writer.writeTextElement("virtualFolder", project.virtualFolder); |
|
564 |
|
565 // Write customFilter elements. |
|
566 QHash<QString, QSet<QString> >::ConstIterator it; |
|
567 for (it = project.customFilters.begin(); it != project.customFilters.end(); ++it) { |
|
568 writer.writeStartElement("customFilter"); |
|
569 writer.writeAttribute("name", it.key()); |
|
570 foreach (const QString &filter, it.value()) |
|
571 writer.writeTextElement("filterAttribute", filter); |
|
572 writer.writeEndElement(); // customFilter |
|
573 } |
|
574 |
|
575 // Start the filterSection. |
|
576 writer.writeStartElement("filterSection"); |
|
577 |
|
578 // Write filterAttribute elements. |
|
579 foreach (const QString &filterName, project.filterAttributes) |
|
580 writer.writeTextElement("filterAttribute", filterName); |
|
581 |
|
582 writer.writeStartElement("toc"); |
|
583 writer.writeStartElement("section"); |
|
584 QString indexPath = tree->fullDocumentLocation(tree->findFakeNodeByTitle(project.indexTitle)); |
|
585 if (indexPath.isEmpty()) |
|
586 indexPath = "index.html"; |
|
587 writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
|
588 writer.writeAttribute("title", project.indexTitle); |
|
589 project.files.insert(tree->fullDocumentLocation(rootNode)); |
|
590 |
|
591 generateSections(project, writer, rootNode); |
|
592 |
|
593 foreach (const QString &name, project.subprojects.keys()) { |
|
594 SubProject subproject = project.subprojects[name]; |
|
595 |
|
596 if (!name.isEmpty()) { |
|
597 writer.writeStartElement("section"); |
|
598 QString indexPath = tree->fullDocumentLocation(tree->findFakeNodeByTitle(subproject.indexTitle)); |
|
599 writer.writeAttribute("ref", HtmlGenerator::cleanRef(indexPath)); |
|
600 writer.writeAttribute("title", subproject.title); |
|
601 project.files.insert(indexPath); |
|
602 } |
|
603 if (subproject.sortPages) { |
|
604 QStringList titles = subproject.nodes.keys(); |
|
605 titles.sort(); |
|
606 foreach (const QString &title, titles) |
|
607 writeNode(project, writer, subproject.nodes[title]); |
|
608 } else { |
|
609 // Find a contents node and navigate from there, using the NextLink values. |
|
610 foreach (const Node *node, subproject.nodes) { |
|
611 QString nextTitle = node->links().value(Node::NextLink).first; |
|
612 if (!nextTitle.isEmpty() && |
|
613 node->links().value(Node::ContentsLink).first.isEmpty()) { |
|
614 |
|
615 FakeNode *nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
|
616 |
|
617 // Write the contents node. |
|
618 writeNode(project, writer, node); |
|
619 |
|
620 while (nextPage) { |
|
621 writeNode(project, writer, nextPage); |
|
622 nextTitle = nextPage->links().value(Node::NextLink).first; |
|
623 if(nextTitle.isEmpty()) |
|
624 break; |
|
625 nextPage = const_cast<FakeNode *>(tree->findFakeNodeByTitle(nextTitle)); |
|
626 } |
|
627 break; |
|
628 } |
|
629 } |
|
630 } |
|
631 |
|
632 if (!name.isEmpty()) |
|
633 writer.writeEndElement(); // section |
|
634 } |
|
635 |
|
636 writer.writeEndElement(); // section |
|
637 writer.writeEndElement(); // toc |
|
638 |
|
639 writer.writeStartElement("keywords"); |
|
640 foreach (const QStringList &details, project.keywords) { |
|
641 writer.writeStartElement("keyword"); |
|
642 writer.writeAttribute("name", details[0]); |
|
643 writer.writeAttribute("id", details[1]); |
|
644 writer.writeAttribute("ref", HtmlGenerator::cleanRef(details[2])); |
|
645 writer.writeEndElement(); //keyword |
|
646 } |
|
647 writer.writeEndElement(); // keywords |
|
648 |
|
649 writer.writeStartElement("files"); |
|
650 foreach (const QString &usedFile, project.files) { |
|
651 if (!usedFile.isEmpty()) |
|
652 writer.writeTextElement("file", usedFile); |
|
653 } |
|
654 foreach (const QString &usedFile, project.extraFiles) |
|
655 writer.writeTextElement("file", usedFile); |
|
656 writer.writeEndElement(); // files |
|
657 |
|
658 writer.writeEndElement(); // filterSection |
|
659 writer.writeEndElement(); // QtHelpProject |
|
660 writer.writeEndDocument(); |
|
661 file.close(); |
|
662 } |
|
663 |
|
664 QT_END_NAMESPACE |