/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*
htmlgenerator.cpp
*/
#include "codemarker.h"
#include "codeparser.h"
#include "helpprojectwriter.h"
#include "htmlgenerator.h"
#include "node.h"
#include "separator.h"
#include "tree.h"
#include
#include
#include
#include
#include
#include
QT_BEGIN_NAMESPACE
#define COMMAND_VERSION Doc::alias("version")
int HtmlGenerator::id = 0;
bool HtmlGenerator::debugging_on = false;
QString HtmlGenerator::divNavTop;
static bool showBrokenLinks = false;
static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*(@link>)");
static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)(@func>)");
static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(@\\2>)");
static QRegExp spanTag("@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>");
static QRegExp unknownTag("?@[^>]*>");
static void addLink(const QString &linkTarget,
const QStringRef &nestedStuff,
QString *res)
{
if (!linkTarget.isEmpty()) {
*res += "";
*res += nestedStuff;
*res += "";
}
else {
*res += nestedStuff;
}
}
HtmlGenerator::HtmlGenerator()
: helpProjectWriter(0),
inLink(false),
inObsoleteLink(false),
inContents(false),
inSectionHeading(false),
inTableHeader(false),
numTableRows(0),
threeColumnEnumValueTable(true),
funcLeftParen("\\S(\\()"),
obsoleteLinks(false)
{
}
/*!
The destructor deletes the instance of HelpProjectWriter.
*/
HtmlGenerator::~HtmlGenerator()
{
if (helpProjectWriter)
delete helpProjectWriter;
}
void HtmlGenerator::initializeGenerator(const Config &config)
{
static const struct {
const char *key;
const char *left;
const char *right;
} defaults[] = {
{ ATOM_FORMATTING_BOLD, "", "" },
{ ATOM_FORMATTING_INDEX, "" },
{ ATOM_FORMATTING_ITALIC, "", "" },
{ ATOM_FORMATTING_PARAMETER, "", "" },
{ ATOM_FORMATTING_SUBSCRIPT, "", "" },
{ ATOM_FORMATTING_SUPERSCRIPT, "", "" },
{ ATOM_FORMATTING_TELETYPE, "", "" },
{ ATOM_FORMATTING_UICONTROL, "", "" },
{ ATOM_FORMATTING_UNDERLINE, "", "" },
{ 0, 0, 0 }
};
Generator::initializeGenerator(config);
obsoleteLinks = config.getBool(QLatin1String(CONFIG_OBSOLETELINKS));
setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif");
int i = 0;
while (defaults[i].key) {
formattingLeftMap().insert(defaults[i].key, defaults[i].left);
formattingRightMap().insert(defaults[i].key, defaults[i].right);
i++;
}
style = config.getString(HtmlGenerator::format() +
Config::dot +
CONFIG_STYLE);
endHeader = config.getString(HtmlGenerator::format() +
Config::dot +
CONFIG_ENDHEADER);
postHeader = config.getString(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_POSTHEADER);
postPostHeader = config.getString(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_POSTPOSTHEADER);
footer = config.getString(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_FOOTER);
address = config.getString(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_ADDRESS);
pleaseGenerateMacRef = config.getBool(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_GENERATEMACREFS);
noBreadCrumbs = config.getBool(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_NOBREADCRUMBS);
project = config.getString(CONFIG_PROJECT);
projectDescription = config.getString(CONFIG_DESCRIPTION);
if (projectDescription.isEmpty() && !project.isEmpty())
projectDescription = project + " Reference Documentation";
projectUrl = config.getString(CONFIG_URL);
outputEncoding = config.getString(CONFIG_OUTPUTENCODING);
if (outputEncoding.isEmpty())
outputEncoding = QLatin1String("ISO-8859-1");
outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit());
naturalLanguage = config.getString(CONFIG_NATURALLANGUAGE);
if (naturalLanguage.isEmpty())
naturalLanguage = QLatin1String("en");
QSet editionNames = config.subVars(CONFIG_EDITION);
QSet::ConstIterator edition = editionNames.constBegin();
while (edition != editionNames.constEnd()) {
QString editionName = *edition;
QStringList editionModules = config.getStringList(CONFIG_EDITION +
Config::dot +
editionName +
Config::dot +
"modules");
QStringList editionGroups = config.getStringList(CONFIG_EDITION +
Config::dot +
editionName +
Config::dot +
"groups");
if (!editionModules.isEmpty())
editionModuleMap[editionName] = editionModules;
if (!editionGroups.isEmpty())
editionGroupMap[editionName] = editionGroups;
++edition;
}
codeIndent = config.getInt(CONFIG_CODEINDENT);
helpProjectWriter = new HelpProjectWriter(config,
project.toLower() +
".qhp");
// Documentation template handling
headerScripts = config.getString(HtmlGenerator::format() + Config::dot +
CONFIG_HEADERSCRIPTS);
headerStyles = config.getString(HtmlGenerator::format() +
Config::dot +
CONFIG_HEADERSTYLES);
QString prefix = CONFIG_QHP + Config::dot + "Qt" + Config::dot;
manifestDir = "qthelp://" + config.getString(prefix + "namespace");
manifestDir += QLatin1Char('/') + config.getString(prefix + "virtualFolder") + QLatin1Char('/');
/*
If the output files will be in subdirectores in the output
directory, change the references to files in the style and
scripts subdirectories that appear in the headerscipts and
headerstyles string so that they link to the correct files,
whic means prepending "../" to each
*/
if (!baseDir().isEmpty()) {
headerScripts = headerScripts.replace("style/","../style/");
headerScripts = headerScripts.replace("scripts/","../scripts/");
headerStyles = headerStyles.replace("style/","../style/");
headerStyles = headerStyles.replace("scripts/","../scripts/");
}
}
void HtmlGenerator::terminateGenerator()
{
Generator::terminateGenerator();
}
QString HtmlGenerator::format()
{
return "HTML";
}
/*!
This is where the HTML files are written.
\note The HTML file generation is done in the base class,
PageGenerator::generateTree().
*/
void HtmlGenerator::generateTree(Tree *tree)
{
tree_ = tree;
nonCompatClasses.clear();
mainClasses.clear();
compatClasses.clear();
obsoleteClasses.clear();
moduleClassMap.clear();
moduleNamespaceMap.clear();
funcIndex.clear();
legaleseTexts.clear();
serviceClasses.clear();
qmlClasses.clear();
findAllClasses(tree->root());
findAllFunctions(tree->root());
findAllLegaleseTexts(tree->root());
findAllNamespaces(tree->root());
findAllSince(tree->root());
Generator::generateTree(tree);
//reportOrphans(tree->root());
generateCollisionPages();
QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
generateIndex(fileBase, projectUrl, projectDescription);
helpProjectWriter->generate(tree_);
generateManifestFiles();
}
void HtmlGenerator::startText(const Node * /* relative */,
CodeMarker * /* marker */)
{
inLink = false;
inContents = false;
inSectionHeading = false;
inTableHeader = false;
numTableRows = 0;
threeColumnEnumValueTable = true;
link.clear();
sectionNumber.clear();
}
/*!
Generate html from an instance of Atom.
*/
int HtmlGenerator::generateAtom(const Atom *atom,
const Node *relative,
CodeMarker *marker)
{
int skipAhead = 0;
static bool in_para = false;
switch (atom->type()) {
case Atom::AbstractLeft:
if (relative)
relative->doc().location().warning(tr("\abstract is not implemented."));
else
Location::information(tr("\abstract is not implemented."));
break;
case Atom::AbstractRight:
break;
case Atom::AutoLink:
if (!inLink && !inContents && !inSectionHeading) {
const Node *node = 0;
QString link = getLink(atom, relative, marker, &node);
if (!link.isEmpty()) {
beginLink(link, node, relative, marker);
generateLink(atom, relative, marker);
endLink();
}
else {
out() << protectEnc(atom->string());
}
}
else {
out() << protectEnc(atom->string());
}
break;
case Atom::BaseName:
break;
case Atom::BriefLeft:
if (relative->type() == Node::Fake) {
if (relative->subType() != Node::Example) {
skipAhead = skipAtoms(atom, Atom::BriefRight);
break;
}
}
out() << "
\n";
break;
case Atom::C:
// This may at one time have been used to mark up C++ code but it is
// now widely used to write teletype text. As a result, text marked
// with the \c command is not passed to a code marker.
out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
if (inLink) {
out() << protectEnc(plainCode(atom->string()));
}
else {
out() << protectEnc(plainCode(atom->string()));
}
out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
break;
case Atom::CaptionLeft:
out() << "
";
in_para = true;
break;
case Atom::CaptionRight:
endLink();
if (in_para) {
out() << "
\n";
bool needOtherSection = false;
/*
sections is built above for the call to generateTableOfContents().
*/
s = sections.constBegin();
while (s != sections.constEnd()) {
if (s->members.isEmpty() && s->reimpMembers.isEmpty()) {
if (!s->inherited.isEmpty())
needOtherSection = true;
}
else {
if (!s->members.isEmpty()) {
// out() << "\n";
out() << "" << divNavTop << "\n";
out() << "
\n"; // QTBUG-9504
++s;
}
generateFooter(inner);
}
/*!
We delayed generation of the disambiguation pages until now, after
all the other pages have been generated. We do this because we might
encounter a link command that tries to link to a target on a QML
component page, but the link doesn't specify the module identifer
for the component, and the component name without a module
identifier is ambiguous. When such a link is found, qdoc can't find
the target, so it appends the target to the NameCollisionNode. After
the tree has been traversed and all these ambiguous links have been
added to the name collision nodes, this function is called. The list
of collision nodes is traversed here, and the disambiguation page for
each collision is generated. The disambiguation page will not only
disambiguate links to the component pages, but it will also disambiguate
links to properties, section headers, etc.
*/
void HtmlGenerator::generateCollisionPages()
{
if (collisionNodes.isEmpty())
return;
for (int i=0; ichildNodes();
if (!nl.isEmpty()) {
NodeList::ConstIterator it = nl.constBegin();
while (it != nl.constEnd()) {
if (!(*it)->isInternal())
collisions.append(*it);
++it;
}
}
if (collisions.size() <= 1)
continue;
ncn->clearCurrentChild();
beginSubPage(ncn, Generator::fileName(ncn));
QString fullTitle = ncn->fullTitle();
QString htmlTitle = fullTitle;
CodeMarker* marker = CodeMarker::markerForFileName(ncn->location().filePath());
if (ncn->isQmlNode()) {
// Replace the marker with a QML code marker.
if (ncn->isQmlNode())
marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
}
generateHeader(htmlTitle, ncn, marker);
if (!fullTitle.isEmpty())
out() << "
" << protectEnc(fullTitle) << "
\n";
NodeMap nm;
for (int i=0; iqmlModuleIdentifier().isEmpty())
t = n->qmlModuleIdentifier() + "::";
t += protectEnc(fullTitle);
nm.insertMulti(t,n);
}
generateAnnotatedList(ncn, marker, nm, true);
QList targets;
if (!ncn->linkTargets().isEmpty()) {
QMap::ConstIterator t = ncn->linkTargets().constBegin();
while (t != ncn->linkTargets().constEnd()) {
int count = 0;
for (int i=0; i(collisions.at(i));
if (n->findChildNodeByName(t.key())) {
++count;
if (count > 1) {
targets.append(t.key());
break;
}
}
}
++t;
}
}
if (!targets.isEmpty()) {
QList::ConstIterator t = targets.constBegin();
while (t != targets.constEnd()) {
out() << "";
out() << "
" << protectEnc(*t) << "
\n";
out() << "
\n";
for (int i=0; i(collisions.at(i));
Node* p = n->findChildNodeByName(*t);
if (p) {
QString link = linkForNode(p,0);
QString label;
if (!n->qmlModuleIdentifier().isEmpty())
label = n->qmlModuleIdentifier() + "::";
label += n->name() + "::" + p->name();
out() << "
\n";
}
while (!sectionNumber.isEmpty()) {
sectionNumber.removeLast();
}
out() << "
\n";
out() << "
\n";
inContents = false;
inLink = false;
}
QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner,
CodeMarker *marker)
{
QList sections;
QList::ConstIterator s;
sections = marker->sections(inner,
CodeMarker::Subpage,
CodeMarker::Okay);
if (sections.isEmpty())
return QString();
QString fileName = fileBase(inner) + "-members." + fileExtension();
beginSubPage(inner, fileName);
QString title = "List of All Members for " + inner->name();
generateHeader(title, inner, marker);
generateTitle(title, Text(), SmallSubTitle, inner, marker);
out() << "
This is the complete list of members for ";
generateFullName(inner, 0, marker);
out() << ", including inherited members.
\n";
Section section = sections.first();
generateSectionList(section, 0, marker, CodeMarker::Subpage);
generateFooter();
endSubPage();
return fileName;
}
/*!
This function creates an html page on which are listed all
the members of QML class \a qml_cn, including the inherited
members. The \a marker is used for formatting stuff.
*/
QString HtmlGenerator::generateAllQmlMembersFile(const QmlClassNode* qml_cn,
CodeMarker* marker)
{
QList sections;
QList::ConstIterator s;
sections = marker->qmlSections(qml_cn,CodeMarker::Subpage);
if (sections.isEmpty())
return QString();
QString fileName = fileBase(qml_cn) + "-members." + fileExtension();
beginSubPage(qml_cn, fileName);
QString title = "List of All Members for " + qml_cn->name();
generateHeader(title, qml_cn, marker);
generateTitle(title, Text(), SmallSubTitle, qml_cn, marker);
out() << "
This is the complete list of members for ";
generateFullName(qml_cn, 0, marker);
out() << ", including inherited members.
\n";
Section section = sections.first();
generateSectionList(section, 0, marker, CodeMarker::Subpage);
generateFooter();
endSubPage();
return fileName;
}
QString HtmlGenerator::generateLowStatusMemberFile(const InnerNode *inner,
CodeMarker *marker,
CodeMarker::Status status)
{
QList sections = marker->sections(inner,
CodeMarker::Summary,
status);
QMutableListIterator j(sections);
while (j.hasNext()) {
if (j.next().members.size() == 0)
j.remove();
}
if (sections.isEmpty())
return QString();
int i;
QString title;
QString fileName;
if (status == CodeMarker::Compat) {
title = "Compatibility Members for " + inner->name();
fileName = fileBase(inner) + "-compat." + fileExtension();
}
else {
title = "Obsolete Members for " + inner->name();
fileName = fileBase(inner) + "-obsolete." + fileExtension();
}
beginSubPage(inner, fileName);
generateHeader(title, inner, marker);
generateTitle(title, Text(), SmallSubTitle, inner, marker);
if (status == CodeMarker::Compat) {
out() << "
The following class members are part of the "
"Qt compatibility layer. We advise against "
"using them in new code.
\n";
}
else {
out() << "
The following class members are obsolete. "
<< "They are provided to keep old source code working. "
<< "We strongly advise against using them in new code.
\n";
}
/*!
This function finds the common prefix of the names of all
the classes in \a classMap and then generates a compact
list of the class names alphabetized on the part of the
name not including the common prefix. You can tell the
function to use \a comonPrefix as the common prefix, but
normally you let it figure it out itself by looking at
the name of the first and last classes in \a classMap.
*/
void HtmlGenerator::generateCompactList(const Node *relative,
CodeMarker *marker,
const NodeMap &classMap,
bool includeAlphabet,
QString commonPrefix)
{
const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
if (classMap.isEmpty())
return;
/*
If commonPrefix is not empty, then the caller knows what
the common prefix is and has passed it in, so just use that
one. But if the commonPrefix is empty (it normally is), then
compute a common prefix using this simple algorithm. Note we
assume the prefix length is 1, i.e. we will have a single
character as the common prefix.
*/
int commonPrefixLen = commonPrefix.length();
if (commonPrefixLen == 0) {
QVector count(26);
for (int i=0; i<26; ++i)
count[i] = 0;
NodeMap::const_iterator iter = classMap.constBegin();
while (iter != classMap.constEnd()) {
if (!iter.key().contains("::")) {
QChar c = iter.key()[0];
if ((c >= 'A') && (c <= 'Z')) {
int idx = c.unicode() - QChar('A').unicode();
++count[idx];
}
}
++iter;
}
int highest = 0;
int idx = -1;
for (int i=0; i<26; ++i) {
if (count[i] > highest) {
highest = count[i];
idx = i;
}
}
idx += QChar('A').unicode();
QChar common(idx);
commonPrefix = common;
commonPrefixLen = 1;
#if 0
/*
The algorithm below eventually failed, so it was replaced
with the simple (perhaps too simple) algorithm above.
The caller didn't pass in a common prefix, so get the common
prefix by looking at the class names of the first and last
classes in the class map. Discard any namespace names and
just use the bare class names. For Qt, the prefix is "Q".
Note that the algorithm used here to derive the common prefix
from the first and last classes in alphabetical order (QAccel
and QXtWidget in Qt 2.1), fails if either class name does not
begin with Q.
*/
QString first;
QString last;
NodeMap::const_iterator iter = classMap.constBegin();
while (iter != classMap.constEnd()) {
if (!iter.key().contains("::")) {
first = iter.key();
break;
}
++iter;
}
if (first.isEmpty())
first = classMap.constBegin().key();
iter = classMap.constEnd();
while (iter != classMap.constBegin()) {
--iter;
if (!iter.key().contains("::")) {
last = iter.key();
break;
}
}
if (last.isEmpty())
last = classMap.constBegin().key();
if (classMap.size() > 1) {
while (commonPrefixLen < first.length() + 1 &&
commonPrefixLen < last.length() + 1 &&
first[commonPrefixLen] == last[commonPrefixLen])
++commonPrefixLen;
}
commonPrefix = first.left(commonPrefixLen);
#endif
}
/*
Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
underscore (_). QAccel will fall in paragraph 10 (A) and
QXtWidget in paragraph 33 (X). This is the only place where we
assume that NumParagraphs is 37. Each paragraph is a NodeMap.
*/
NodeMap paragraph[NumParagraphs+1];
QString paragraphName[NumParagraphs+1];
QSet usedParagraphNames;
NodeMap::ConstIterator c = classMap.constBegin();
while (c != classMap.constEnd()) {
QStringList pieces = c.key().split("::");
QString key;
int idx = commonPrefixLen;
if (!pieces.last().startsWith(commonPrefix))
idx = 0;
if (pieces.size() == 1)
key = pieces.last().mid(idx).toLower();
else
key = pieces.last().toLower();
int paragraphNr = NumParagraphs - 1;
if (key[0].digitValue() != -1) {
paragraphNr = key[0].digitValue();
}
else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
paragraphNr = 10 + key[0].unicode() - 'a';
}
paragraphName[paragraphNr] = key[0].toUpper();
usedParagraphNames.insert(key[0].toLower().cell());
paragraph[paragraphNr].insert(key, c.value());
++c;
}
/*
Each paragraph j has a size: paragraph[j].count(). In the
discussion, we will assume paragraphs 0 to 5 will have sizes
3, 1, 4, 1, 5, 9.
We now want to compute the paragraph offset. Paragraphs 0 to 6
start at offsets 0, 3, 4, 8, 9, 14, 23.
*/
int paragraphOffset[NumParagraphs + 1]; // 37 + 1
paragraphOffset[0] = 0;
for (int i=0; i";
for (int i = 0; i < 26; i++) {
QChar ch('a' + i);
if (usedParagraphNames.contains(char('a' + i)))
out() << QString("%2 ").arg(ch).arg(ch.toUpper());
}
out() << "\n";
}
/*
Output a
element to contain all the
elements.
*/
out() << "
\n";
numTableRows = 0;
int curParNr = 0;
int curParOffset = 0;
for (int i=0; i.
*/
if (curParOffset == 0) {
if (i > 0)
out() << "\n";
if (++numTableRows % 2 == 1)
out() << "
\n";
}
}
void HtmlGenerator::generateQmlItem(const Node *node,
const Node *relative,
CodeMarker *marker,
bool summary)
{
QString marked = marker->markedUpQmlItem(node,summary);
QRegExp templateTag("(<[^@>]*>)");
if (marked.indexOf(templateTag) != -1) {
QString contents = protectEnc(marked.mid(templateTag.pos(1),
templateTag.cap(1).length()));
marked.replace(templateTag.pos(1), templateTag.cap(1).length(),
contents);
}
marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])@param>"),
"\\1\\2");
marked.replace("<@param>", "");
marked.replace("@param>", "");
if (summary)
marked.replace("@name>", "b>");
marked.replace("<@extra>", "");
marked.replace("@extra>", "");
if (summary) {
marked.remove("<@type>");
marked.remove("@type>");
}
out() << highlightedCode(marked, marker, relative, false, node);
}
void HtmlGenerator::generateOverviewList(const Node *relative, CodeMarker * /* marker */)
{
QMap > fakeNodeMap;
QMap groupTitlesMap;
QMap uncategorizedNodeMap;
QRegExp singleDigit("\\b([0-9])\\b");
const NodeList children = tree_->root()->childNodes();
foreach (Node *child, children) {
if (child->type() == Node::Fake && child != relative) {
FakeNode *fakeNode = static_cast(child);
// Check whether the page is part of a group or is the group
// definition page.
QString group;
bool isGroupPage = false;
if (fakeNode->doc().metaCommandsUsed().contains("group")) {
group = fakeNode->doc().metaCommandArgs("group")[0].first;
isGroupPage = true;
}
// there are too many examples; they would clutter the list
if (fakeNode->subType() == Node::Example)
continue;
// not interested either in individual (Qt Designer etc.) manual chapters
if (fakeNode->links().contains(Node::ContentsLink))
continue;
// Discard external nodes.
if (fakeNode->subType() == Node::ExternalPage)
continue;
QString sortKey = fakeNode->fullTitle().toLower();
if (sortKey.startsWith("the "))
sortKey.remove(0, 4);
sortKey.replace(singleDigit, "0\\1");
if (!group.isEmpty()) {
if (isGroupPage) {
// If we encounter a group definition page, we add all
// the pages in that group to the list for that group.
foreach (Node *member, fakeNode->groupMembers()) {
if (member->type() != Node::Fake)
continue;
FakeNode *page = static_cast(member);
if (page) {
QString sortKey = page->fullTitle().toLower();
if (sortKey.startsWith("the "))
sortKey.remove(0, 4);
sortKey.replace(singleDigit, "0\\1");
fakeNodeMap[const_cast(fakeNode)].insert(sortKey, page);
groupTitlesMap[fakeNode->fullTitle()] = const_cast(fakeNode);
}
}
}
else if (!isGroupPage) {
// If we encounter a page that belongs to a group then
// we add that page to the list for that group.
const FakeNode* gn = tree_->findGroupNode(QStringList(group));
if (gn)
fakeNodeMap[gn].insert(sortKey, fakeNode);
}
}
}
}
// We now list all the pages found that belong to groups.
// If only certain pages were found for a group, but the definition page
// for that group wasn't listed, the list of pages will be intentionally
// incomplete. However, if the group definition page was listed, all the
// pages in that group are listed for completeness.
if (!fakeNodeMap.isEmpty()) {
foreach (const QString &groupTitle, groupTitlesMap.keys()) {
const FakeNode *groupNode = groupTitlesMap[groupTitle];
out() << QString("
\n";
}
int i = 0;
NodeList::ConstIterator m = nl.constBegin();
while (m != nl.constEnd()) {
if ((*m)->access() == Node::Private) {
++m;
continue;
}
if (alignNames) {
out() << "
";
}
else {
if (twoColumn && i == (int) (nl.count() + 1) / 2)
out() << "
\n";
}
int i = 0;
NodeList::ConstIterator m = section.members.constBegin();
while (m != section.members.constEnd()) {
if ((*m)->access() == Node::Private) {
++m;
continue;
}
if (alignNames) {
out() << "
";
}
else {
if (twoColumn && i == (int) (section.members.count() + 1) / 2)
out() << "
The " << protectEnc(enume->flagsType()->name())
<< " type is a typedef for "
<< "QFlags<"
<< protectEnc(enume->name())
<< ">. It stores an OR combination of "
<< protectEnc(enume->name())
<< " values.
\n";
}
}
generateAlsoList(node, marker);
generateExtractionMark(node, EndMark);
}
void HtmlGenerator::findAllClasses(const InnerNode *node)
{
NodeList::const_iterator c = node->childNodes().constBegin();
while (c != node->childNodes().constEnd()) {
if ((*c)->access() != Node::Private && (*c)->url().isEmpty()) {
if ((*c)->type() == Node::Class && !(*c)->doc().isEmpty()) {
QString className = (*c)->name();
if ((*c)->parent() &&
(*c)->parent()->type() == Node::Namespace &&
!(*c)->parent()->name().isEmpty())
className = (*c)->parent()->name()+"::"+className;
if (!(static_cast(*c))->hideFromMainList()) {
if ((*c)->status() == Node::Compat) {
compatClasses.insert(className, *c);
}
else if ((*c)->status() == Node::Obsolete) {
obsoleteClasses.insert(className, *c);
}
else {
nonCompatClasses.insert(className, *c);
if ((*c)->status() == Node::Main)
mainClasses.insert(className, *c);
}
}
QString moduleName = (*c)->moduleName();
if (moduleName == "Qt3SupportLight") {
moduleClassMap[moduleName].insert((*c)->name(), *c);
moduleName = "Qt3Support";
}
if (!moduleName.isEmpty())
moduleClassMap[moduleName].insert((*c)->name(), *c);
QString serviceName =
(static_cast(*c))->serviceName();
if (!serviceName.isEmpty())
serviceClasses.insert(serviceName, *c);
}
else if ((*c)->type() == Node::Fake &&
(*c)->subType() == Node::QmlClass &&
!(*c)->doc().isEmpty()) {
QString qmlClassName = (*c)->name();
/*
Remove the "QML:" prefix, if present.
It shouldn't be present anymore.
*/
if (qmlClassName.startsWith(QLatin1String("QML:")))
qmlClasses.insert(qmlClassName.mid(4),*c);
else
qmlClasses.insert(qmlClassName,*c);
}
else if ((*c)->isInnerNode()) {
findAllClasses(static_cast(*c));
}
}
++c;
}
}
void HtmlGenerator::findAllFunctions(const InnerNode *node)
{
NodeList::ConstIterator c = node->childNodes().constBegin();
while (c != node->childNodes().constEnd()) {
if ((*c)->access() != Node::Private) {
if ((*c)->isInnerNode() && (*c)->url().isEmpty()) {
findAllFunctions(static_cast(*c));
}
else if ((*c)->type() == Node::Function) {
const FunctionNode *func = static_cast(*c);
if ((func->status() > Node::Obsolete) &&
!func->isInternal() &&
(func->metaness() != FunctionNode::Ctor) &&
(func->metaness() != FunctionNode::Dtor)) {
funcIndex[(*c)->name()].insert((*c)->parent()->fullDocumentName(), *c);
}
}
}
++c;
}
}
void HtmlGenerator::findAllLegaleseTexts(const InnerNode *node)
{
NodeList::ConstIterator c = node->childNodes().constBegin();
while (c != node->childNodes().constEnd()) {
if ((*c)->access() != Node::Private) {
if (!(*c)->doc().legaleseText().isEmpty())
legaleseTexts.insertMulti((*c)->doc().legaleseText(), *c);
if ((*c)->isInnerNode())
findAllLegaleseTexts(static_cast(*c));
}
++c;
}
}
void HtmlGenerator::findAllNamespaces(const InnerNode *node)
{
NodeList::ConstIterator c = node->childNodes().constBegin();
while (c != node->childNodes().constEnd()) {
if ((*c)->access() != Node::Private) {
if ((*c)->isInnerNode() && (*c)->url().isEmpty()) {
findAllNamespaces(static_cast(*c));
if ((*c)->type() == Node::Namespace) {
const NamespaceNode *nspace = static_cast(*c);
// Ensure that the namespace's name is not empty (the root
// namespace has no name).
if (!nspace->name().isEmpty()) {
namespaceIndex.insert(nspace->name(), *c);
QString moduleName = (*c)->moduleName();
if (moduleName == "Qt3SupportLight") {
moduleNamespaceMap[moduleName].insert((*c)->name(), *c);
moduleName = "Qt3Support";
}
if (!moduleName.isEmpty())
moduleNamespaceMap[moduleName].insert((*c)->name(), *c);
}
}
}
}
++c;
}
}
int HtmlGenerator::hOffset(const Node *node)
{
switch (node->type()) {
case Node::Namespace:
case Node::Class:
return 2;
case Node::Fake:
return 1;
case Node::Enum:
case Node::Typedef:
case Node::Function:
case Node::Property:
default:
return 3;
}
}
bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom)
{
while (atom != 0 && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) {
if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight))
return true;
atom = atom->next();
}
return false;
}
const Node *HtmlGenerator::findNodeForTarget(const QString &target,
const Node *relative,
CodeMarker *marker,
const Atom *atom)
{
const Node *node = 0;
if (target.isEmpty()) {
node = relative;
}
else if (target.endsWith(".html")) {
node = tree_->root()->findChildNodeByNameAndType(target, Node::Fake);
}
else if (marker) {
node = marker->resolveTarget(target, tree_, relative);
if (!node) {
node = tree_->findFakeNodeByTitle(target, relative);
}
if (!node && atom) {
node = tree_->findUnambiguousTarget(target, *const_cast(&atom), relative);
}
}
if (!node)
relative->doc().location().warning(tr("Cannot link to '%1'").arg(target));
return node;
}
const QPair HtmlGenerator::anchorForNode(const Node *node)
{
QPair anchorPair;
anchorPair.first = Generator::fileName(node);
if (node->type() == Node::Fake) {
const FakeNode *fakeNode = static_cast(node);
anchorPair.second = fakeNode->title();
}
return anchorPair;
}
QString HtmlGenerator::getLink(const Atom *atom,
const Node *relative,
CodeMarker *marker,
const Node** node)
{
QString link;
*node = 0;
inObsoleteLink = false;
if (atom->string().contains(QLatin1Char(':')) &&
(atom->string().startsWith("file:")
|| atom->string().startsWith("http:")
|| atom->string().startsWith("https:")
|| atom->string().startsWith("ftp:")
|| atom->string().startsWith("mailto:"))) {
link = atom->string();
}
else {
QStringList path;
if (atom->string().contains('#')) {
path = atom->string().split('#');
}
else {
path.append(atom->string());
}
Atom *targetAtom = 0;
QString first = path.first().trimmed();
if (first.isEmpty()) {
*node = relative;
}
else if (first.endsWith(".html")) {
/*
This is not a recursive search. That's ok in
this case, because we are searching for a page
node, which must be a direct child of the tree
root.
*/
*node = tree_->root()->findChildNodeByNameAndType(first, Node::Fake);
}
else {
*node = marker->resolveTarget(first, tree_, relative);
if (!*node) {
*node = tree_->findFakeNodeByTitle(first, relative);
}
if (!*node) {
*node = tree_->findUnambiguousTarget(first, targetAtom, relative);
}
}
if (*node) {
if (!(*node)->url().isEmpty()) {
return (*node)->url();
}
else {
path.removeFirst();
}
}
else {
*node = relative;
}
if (*node) {
if ((*node)->status() == Node::Obsolete) {
if (relative) {
if (relative->parent() != *node) {
if (relative->status() != Node::Obsolete) {
bool porting = false;
if (relative->type() == Node::Fake) {
const FakeNode* fake = static_cast(relative);
if (fake->title().startsWith("Porting"))
porting = true;
}
QString name = marker->plainFullName(relative);
if (!porting && !name.startsWith("Q3")) {
if (obsoleteLinks) {
relative->doc().location().warning(tr("Link to obsolete item '%1' in %2")
.arg(atom->string())
.arg(name));
}
inObsoleteLink = true;
}
}
}
}
else {
qDebug() << "Link to Obsolete entity"
<< (*node)->name() << "no relative";
}
}
}
/*
This loop really only makes sense if *node is not 0.
In that case, The node *node points to represents a
qdoc page, so the link will ultimately point to some
target on that page. This loop finds that target on
the page that *node represents. targetAtom is that
target.
*/
while (!path.isEmpty()) {
targetAtom = tree_->findTarget(path.first(), *node);
if (targetAtom == 0)
break;
path.removeFirst();
}
/*
Given that *node is not null, we now cconstruct a link
to the page that *node represents, and then if there is
a target on that page, we connect the target to the link
with '#'.
*/
if (path.isEmpty()) {
link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (targetAtom)
link += QLatin1Char('#') + refForAtom(targetAtom, *node);
}
/*
If the output is going to subdirectories, then if the
two nodes will be output to different directories, then
the link must go up to the parent directory and then
back down into the other subdirectory.
*/
if (link.startsWith("images/")) {
link.prepend(QString("../"));
}
else if (*node && relative && (*node != relative)) {
if ((*node)->outputSubdirectory() != relative->outputSubdirectory()) {
link.prepend(QString("../" + (*node)->outputSubdirectory() + QLatin1Char('/')));
}
}
}
return link;
}
void HtmlGenerator::generateIndex(const QString &fileBase,
const QString &url,
const QString &title)
{
tree_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", url, title);
}
void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker)
{
Text text;
switch (node->status()) {
case Node::Obsolete:
if (node->isInnerNode())
Generator::generateStatus(node, marker);
break;
case Node::Compat:
if (node->isInnerNode()) {
text << Atom::ParaLeft
<< Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
<< "This "
<< typeString(node)
<< " is part of the Qt 3 support library."
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
<< " It is provided to keep old source code working. "
<< "We strongly advise against "
<< "using it in new code. See ";
const FakeNode *fakeNode = tree_->findFakeNodeByTitle("Porting To Qt 4");
Atom *targetAtom = 0;
if (fakeNode && node->type() == Node::Class) {
QString oldName(node->name());
oldName.remove(QLatin1Char('3'));
targetAtom = tree_->findTarget(oldName,
fakeNode);
}
if (targetAtom) {
text << Atom(Atom::Link, linkForNode(fakeNode, node) + QLatin1Char('#') +
refForAtom(targetAtom, fakeNode));
}
else
text << Atom(Atom::Link, "Porting to Qt 4");
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, "Porting to Qt 4")
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
<< " for more information."
<< Atom::ParaRight;
}
generateText(text, node, marker);
break;
default:
Generator::generateStatus(node, marker);
}
}
#ifdef GENERATE_MAC_REFS
/*
No longer valid.
*/
void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker)
{
if (!pleaseGenerateMacRef || marker == 0)
return;
QStringList macRefs = marker->macRefsForNode(node);
foreach (const QString &macRef, macRefs)
out() << "\n";
}
#endif
void HtmlGenerator::beginLink(const QString &link,
const Node *node,
const Node *relative,
CodeMarker *marker)
{
Q_UNUSED(marker)
Q_UNUSED(relative)
this->link = link;
if (link.isEmpty()) {
if (showBrokenLinks)
out() << "";
}
else if (node == 0 ||
(relative != 0 && node->status() == relative->status())) {
out() << "";
}
else {
switch (node->status()) {
case Node::Obsolete:
out() << "";
break;
case Node::Compat:
out() << "";
break;
default:
out() << "";
}
}
inLink = true;
}
void HtmlGenerator::endLink()
{
if (inLink) {
if (link.isEmpty()) {
if (showBrokenLinks)
out() << "";
}
else {
if (inObsoleteLink) {
out() << "(obsolete)";
}
out() << "";
}
}
inLink = false;
inObsoleteLink = false;
}
/*!
Generates the summary for the \a section. Only used for
sections of QML element documentation.
*/
void HtmlGenerator::generateQmlSummary(const Section& section,
const Node *relative,
CodeMarker *marker)
{
if (!section.members.isEmpty()) {
out() << "
\n";
NodeList::ConstIterator m;
m = section.members.constBegin();
while (m != section.members.constEnd()) {
out() << "
\n";
}
}
/*!
Outputs the html detailed documentation for a section
on a QML element reference page.
*/
void HtmlGenerator::generateDetailedQmlMember(Node *node,
const InnerNode *relative,
CodeMarker *marker)
{
QmlPropertyNode* qpn = 0;
#ifdef GENERATE_MAC_REFS
generateMacRef(node, marker);
#endif
generateExtractionMark(node, MemberMark);
out() << "
";
if (node->subType() == Node::QmlPropertyGroup) {
const QmlPropGroupNode* qpgn = static_cast(node);
NodeList::ConstIterator p = qpgn->childNodes().constBegin();
out() << "
";
out() << "
";
while (p != qpgn->childNodes().constEnd()) {
if ((*p)->type() == Node::QmlProperty) {
qpn = static_cast(*p);
out() << "
";
out() << "
";
out() << "";
if (!qpn->isReadOnlySet())
qpn->setReadOnly(!qpn->isWritable(tree_));
if (qpn->isReadOnly())
out() << "read-only";
if (qpn->isDefault())
out() << "default";
generateQmlItem(qpn, relative, marker, false);
out() << "
";
}
++p;
}
out() << "
";
out() << "
";
}
else if (node->type() == Node::QmlProperty) {
qpn = static_cast(node);
/*
If the QML property node has a single subproperty,
override, replace qpn with that override node and
proceed as normal.
*/
if (qpn->qmlPropNodes().size() == 1) {
Node* n = qpn->qmlPropNodes().at(0);
if (n->type() == Node::QmlProperty)
qpn = static_cast(n);
}
/*
Now qpn either has no overrides, or it has more
than 1. If it has none, proceed to output as nortmal.
*/
if (qpn->qmlPropNodes().isEmpty()) {
out() << "
";
out() << "
";
out() << "
";
out() << "
";
out() << "";
if (!qpn->isReadOnlySet()) {
if (qpn->declarativeCppNode())
qpn->setReadOnly(!qpn->isWritable(tree_));
}
if (qpn->isReadOnly())
out() << "read-only";
if (qpn->isDefault())
out() << "default";
generateQmlItem(qpn, relative, marker, false);
out() << "
";
out() << "
";
out() << "
";
}
else {
/*
The QML property node has multiple override nodes.
Process the whole list as we would for a QML property
group.
*/
NodeList::ConstIterator p = qpn->qmlPropNodes().constBegin();
out() << "
";
out() << "
";
while (p != qpn->qmlPropNodes().constEnd()) {
if ((*p)->type() == Node::QmlProperty) {
QmlPropertyNode* q = static_cast(*p);
out() << "
";
out() << "
";
out() << "";
if (!qpn->isReadOnlySet())
qpn->setReadOnly(!qpn->isWritable(tree_));
if (qpn->isReadOnly())
out() << "read-only";
if (qpn->isDefault())
out() << "default";
generateQmlItem(q, relative, marker, false);
out() << "
";
generateExtractionMark(node, EndMark);
}
/*!
Output the "Inherits" line for the QML element,
if there should be one.
*/
void HtmlGenerator::generateQmlInherits(const QmlClassNode* qcn, CodeMarker* marker)
{
if (!qcn)
return;
const FakeNode* base = qcn->qmlBase();
if (base) {
Text text;
text << Atom::ParaLeft << "Inherits ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(base));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, base->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, qcn, marker);
}
}
/*!
Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]"
line for the QML element, if there should be one.
If there is no class node, or if the class node status
is set to Node::Internal, do nothing.
*/
void HtmlGenerator::generateQmlInstantiates(QmlClassNode* qcn, CodeMarker* marker)
{
ClassNode* cn = qcn->classNode();
if (cn && (cn->status() != Node::Internal)) {
Text text;
text << Atom::ParaLeft;
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
QString name = qcn->name();
/*
Remove the "QML:" prefix, if present.
It shouldn't be present anymore.
*/
if (name.startsWith(QLatin1String("QML:")))
name = name.mid(4); // remove the "QML:" prefix
text << Atom(Atom::String, name);
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << " instantiates the C++ class ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, cn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, qcn, marker);
}
}
/*!
Output the "[QmlGraphicsXxx is instantiated by QML element Xxx]"
line for the class, if there should be one.
If there is no QML element, or if the class node status
is set to Node::Internal, do nothing.
*/
void HtmlGenerator::generateInstantiatedBy(ClassNode* cn, CodeMarker* marker)
{
if (cn && cn->status() != Node::Internal && cn->qmlElement() != 0) {
const QmlClassNode* qcn = cn->qmlElement();
Text text;
text << Atom::ParaLeft;
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, cn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << " is instantiated by QML element ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, qcn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, cn, marker);
}
}
void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
{
if (markType != EndMark) {
out() << "\n";
} else {
out() << "\n";
}
}
/*!
This function outputs one or more manifest files in XML.
They are used by Creator.
*/
void HtmlGenerator::generateManifestFiles()
{
generateManifestFile("examples", "example");
generateManifestFile("demos", "demo");
ExampleNode::exampleNodeMap.clear();
}
/*!
This function is called by generaqteManiferstFile(), once
for each manifest file to be generated. \a manifest is the
type of manifest file.
*/
void HtmlGenerator::generateManifestFile(QString manifest, QString element)
{
if (ExampleNode::exampleNodeMap.isEmpty())
return;
QString fileName = manifest +"-manifest.xml";
QFile file(outputDir() + QLatin1Char('/') + fileName);
if (!file.open(QFile::WriteOnly | QFile::Text))
return ;
bool demos = false;
if (manifest == "demos")
demos = true;
bool proceed = false;
ExampleNodeMap::Iterator i = ExampleNode::exampleNodeMap.begin();
while (i != ExampleNode::exampleNodeMap.end()) {
const ExampleNode* en = i.value();
if (demos) {
if (en->name().startsWith("demos")) {
proceed = true;
break;
}
}
else if (!en->name().startsWith("demos")) {
proceed = true;
break;
}
++i;
}
if (!proceed)
return;
QXmlStreamWriter writer(&file);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("instructionals");
writer.writeAttribute("module", project);
writer.writeStartElement(manifest);
i = ExampleNode::exampleNodeMap.begin();
while (i != ExampleNode::exampleNodeMap.end()) {
const ExampleNode* en = i.value();
if (demos) {
if (!en->name().startsWith("demos")) {
++i;
continue;
}
}
else if (en->name().startsWith("demos")) {
++i;
continue;
}
writer.writeStartElement(element);
writer.writeAttribute("name", en->title());
QString docUrl = manifestDir + en->fileBase() + ".html";
writer.writeAttribute("docUrl", docUrl);
foreach (const Node* child, en->childNodes()) {
if (child->subType() == Node::File) {
QString file = child->name();
if (file.endsWith(".pro") || file.endsWith(".qmlproject")) {
if (file.startsWith("demos/"))
file = file.mid(6);
writer.writeAttribute("projectPath", file);
break;
}
}
}
if (!en->imageFileName().isEmpty())
writer.writeAttribute("imageUrl", manifestDir + en->imageFileName());
writer.writeStartElement("description");
Text brief = en->doc().briefText();
if (!brief.isEmpty())
writer.writeCDATA(brief.toString());
else
writer.writeCDATA(QString("No description available"));
writer.writeEndElement(); // description
QStringList tags = en->title().toLower().split(QLatin1Char(' '));
if (!tags.isEmpty()) {
writer.writeStartElement("tags");
bool wrote_one = false;
for (int n=0; n0 && wrote_one)
writer.writeCharacters(",");
writer.writeCharacters(tag);
wrote_one = true;
}
writer.writeEndElement(); // tags
}
QString ename = en->name().mid(en->name().lastIndexOf('/')+1);
QSet usedNames;
foreach (const Node* child, en->childNodes()) {
if (child->subType() == Node::File) {
QString file = child->name();
QString fileName = file.mid(file.lastIndexOf('/')+1);
QString baseName = fileName;
if ((fileName.count(QChar('.')) > 0) &&
(fileName.endsWith(".cpp") ||
fileName.endsWith(".h") ||
fileName.endsWith(".qml")))
baseName.truncate(baseName.lastIndexOf(QChar('.')));
if (baseName.compare(ename, Qt::CaseInsensitive) == 0) {
if (!usedNames.contains(fileName)) {
writer.writeStartElement("fileToOpen");
if (file.startsWith("demos/"))
file = file.mid(6);
writer.writeCharacters(file);
writer.writeEndElement(); // fileToOpen
usedNames.insert(fileName);
}
}
else if (fileName.toLower().endsWith("main.cpp") ||
fileName.toLower().endsWith("main.qml")) {
if (!usedNames.contains(fileName)) {
writer.writeStartElement("fileToOpen");
if (file.startsWith("demos/"))
file = file.mid(6);
writer.writeCharacters(file);
writer.writeEndElement(); // fileToOpen
usedNames.insert(fileName);
}
}
}
}
writer.writeEndElement(); // example
++i;
}
writer.writeEndElement(); // examples
writer.writeEndElement(); // instructionals
writer.writeEndDocument();
file.close();
}
/*!
Find global entities that have documentation but no
\e{relates} comand. Report these as errors if they
are not also marked \e {internal}.
type: Class
type: Namespace
subtype: Example
subtype: External page
subtype: Group
subtype: Header file
subtype: Module
subtype: Page
subtype: QML basic type
subtype: QML class
subtype: QML module
*/
void HtmlGenerator::reportOrphans(const InnerNode* parent)
{
const NodeList& children = parent->childNodes();
if (children.size() == 0)
return;
bool related;
QString message;
for (int i=0; iisInternal() || child->doc().isEmpty())
continue;
if (child->relates()) {
related = true;
message = child->relates()->name();
}
else {
related = false;
message = "has documentation but no \\relates command";
}
switch (child->type()) {
case Node::Namespace:
break;
case Node::Class:
break;
case Node::Fake:
switch (child->subType()) {
case Node::Example:
break;
case Node::HeaderFile:
break;
case Node::File:
break;
case Node::Image:
break;
case Node::Group:
break;
case Node::Module:
break;
case Node::Page:
break;
case Node::ExternalPage:
break;
case Node::QmlClass:
break;
case Node::QmlPropertyGroup:
break;
case Node::QmlBasicType:
break;
case Node::QmlModule:
break;
case Node::Collision:
break;
default:
break;
}
break;
case Node::Enum:
if (!related)
child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message));
break;
case Node::Typedef:
if (!related)
child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message));
break;
case Node::Function:
if (!related) {
const FunctionNode* fn = static_cast(child);
if (fn->isMacro())
child->location().warning(tr("Global macro, %1, %2").arg(child->name()).arg(message));
else
child->location().warning(tr("Global function, %1(), %2").arg(child->name()).arg(message));
}
break;
case Node::Property:
break;
case Node::Variable:
if (!related)
child->location().warning(tr("Global variable, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlProperty:
if (!related)
child->location().warning(tr("Global QML property, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlSignal:
if (!related)
child->location().warning(tr("Global QML, signal, %1 %2").arg(child->name()).arg(message));
break;
case Node::QmlSignalHandler:
if (!related)
child->location().warning(tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlMethod:
if (!related)
child->location().warning(tr("Global QML method, %1, %2").arg(child->name()).arg(message));
break;
default:
break;
}
}
}
/*!
Returns a reference to the XML stream writer currently in use.
There is one XML stream writer open for each XML file being
written, and they are kept on a stack. The one on top of the
stack is the one being written to at the moment. In the HTML
output generator, it is perhaps impossible for there to ever
be more than one writer open.
*/
QXmlStreamWriter& HtmlGenerator::xmlWriter()
{
return *xmlWriterStack.top();
}
/*!
This function is only called for writing ditamaps.
Calls beginSubPage() in the base class to open the file.
Then creates a new XML stream writer using the IO device
from opened file and pushes the XML writer onto a stackj.
Creates the file named \a fileName in the output directory.
Attaches a QTextStream to the created file, which is written
to all over the place using out(). Finally, it sets some
parameters in the XML writer and calls writeStartDocument().
It also ensures that a GUID map is created for the output file.
*/
void HtmlGenerator::beginDitamapPage(const InnerNode* node, const QString& fileName)
{
Generator::beginSubPage(node,fileName);
QXmlStreamWriter* writer = new QXmlStreamWriter(out().device());
xmlWriterStack.push(writer);
writer->setAutoFormatting(true);
writer->setAutoFormattingIndent(4);
writer->writeStartDocument();
}
/*!
This function is only called for writing ditamaps.
Calls writeEndDocument() and then pops the XML stream writer
off the stack and deletes it. Then it calls endSubPage() in
the base class to close the device.
*/
void HtmlGenerator::endDitamapPage()
{
xmlWriter().writeEndDocument();
delete xmlWriterStack.pop();
Generator::endSubPage();
}
/*!
This function is only called for writing ditamaps.
Creates the DITA map from the topicrefs in \a node,
which is a DitaMapNode.
*/
void HtmlGenerator::writeDitaMap(const DitaMapNode* node)
{
beginDitamapPage(node,node->name());
QString doctype = "";
xmlWriter().writeDTD(doctype);
xmlWriter().writeStartElement("map");
xmlWriter().writeStartElement("topicmeta");
xmlWriter().writeStartElement("shortdesc");
xmlWriter().writeCharacters(node->title());
xmlWriter().writeEndElement(); //
xmlWriter().writeEndElement(); //
DitaRefList map = node->map();
writeDitaRefs(map);
endDitamapPage();
}
/*!
Write the \a ditarefs to the current output file.
*/
void HtmlGenerator::writeDitaRefs(const DitaRefList& ditarefs)
{
foreach (DitaRef* t, ditarefs) {
if (t->isMapRef())
xmlWriter().writeStartElement("mapref");
else
xmlWriter().writeStartElement("topicref");
xmlWriter().writeAttribute("navtitle",t->navtitle());
if (t->href().isEmpty()) {
const FakeNode* fn = tree_->findFakeNodeByTitle(t->navtitle());
if (fn)
xmlWriter().writeAttribute("href",fileName(fn));
}
else
xmlWriter().writeAttribute("href",t->href());
if (t->subrefs() && !t->subrefs()->isEmpty())
writeDitaRefs(*(t->subrefs()));
xmlWriter().writeEndElement(); // or
}
}
QT_END_NAMESPACE