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

Last change on this file since 494 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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