~ubuntu-branches/ubuntu/wily/qtbase-opensource-src/wily

« back to all changes in this revision

Viewing changes to src/tools/qdoc/helpprojectwriter.cpp

  • Committer: Package Import Robot
  • Author(s): Timo Jyrinki
  • Date: 2013-02-05 12:46:17 UTC
  • Revision ID: package-import@ubuntu.com-20130205124617-c8jouts182j002fx
Tags: upstream-5.0.1+dfsg
ImportĀ upstreamĀ versionĀ 5.0.1+dfsg

Show diffs side-by-side

added added

removed removed

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