/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, 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.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $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
QT_BEGIN_NAMESPACE
#define COMMAND_VERSION Doc::alias("version")
int HtmlGenerator::id = 0;
bool HtmlGenerator::debugging_on = false;
QString HtmlGenerator::sinceTitles[] =
{
" New Namespaces",
" New Classes",
" New Member Functions",
" New Functions in Namespaces",
" New Global Functions",
" New Macros",
" New Enum Types",
" New Typedefs",
" New Properties",
" New Variables",
" New QML Elements",
" New Qml Properties",
" New Qml Signals",
" New Qml Methods",
""
};
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)>");
static QRegExp unknownTag("?@[^>]*>");
bool parseArg(const QString &src,
const QString &tag,
int *pos,
int n,
QStringRef *contents,
QStringRef *par1 = 0,
bool debug = false)
{
#define SKIP_CHAR(c) \
if (debug) \
qDebug() << "looking for " << c << " at " << QString(src.data() + i, n - i); \
if (i >= n || src[i] != c) { \
if (debug) \
qDebug() << " char '" << c << "' not found"; \
return false; \
} \
++i;
#define SKIP_SPACE \
while (i < n && src[i] == ' ') \
++i;
int i = *pos;
int j = i;
// assume "<@" has been parsed outside
//SKIP_CHAR('<');
//SKIP_CHAR('@');
if (tag != QStringRef(&src, i, tag.length())) {
if (0 && debug)
qDebug() << "tag " << tag << " not found at " << i;
return false;
}
if (debug)
qDebug() << "haystack:" << src << "needle:" << tag << "i:" <).*(@link>)");
if (par1) {
SKIP_SPACE;
// read parameter name
j = i;
while (i < n && src[i].isLetter())
++i;
if (src[i] == '=') {
if (debug)
qDebug() << "read parameter" << QString(src.data() + j, i - j);
SKIP_CHAR('=');
SKIP_CHAR('"');
// skip parameter name
j = i;
while (i < n && src[i] != '"')
++i;
*par1 = QStringRef(&src, j, i - j);
SKIP_CHAR('"');
SKIP_SPACE;
} else {
if (debug)
qDebug() << "no optional parameter found";
}
}
SKIP_SPACE;
SKIP_CHAR('>');
// find contents up to closing "@tag>
j = i;
for (; true; ++i) {
if (i + 4 + tag.length() > n)
return false;
if (src[i] != '<')
continue;
if (src[i + 1] != '/')
continue;
if (src[i + 2] != '@')
continue;
if (tag != QStringRef(&src, i + 3, tag.length()))
continue;
if (src[i + 3 + tag.length()] != '>')
continue;
break;
}
*contents = QStringRef(&src, j, i - j);
i += tag.length() + 4;
*pos = i;
if (debug)
qDebug() << " tag " << tag << " found: pos now: " << i;
return true;
#undef SKIP_CHAR
}
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),
inContents(false),
inSectionHeading(false),
inTableHeader(false),
numTableRows(0),
threeColumnEnumValueTable(true),
offlineDocs(true),
funcLeftParen("\\S(\\()"),
myTree(0),
slow(false),
obsoleteLinks(false)
{
}
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_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 +
HTMLGENERATOR_STYLE);
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);
project = config.getString(CONFIG_PROJECT);
offlineDocs = !config.getBool(CONFIG_ONLINE);
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.begin();
while (edition != editionNames.end()) {
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;
}
slow = config.getBool(CONFIG_SLOW);
stylesheets = config.getStringList(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_STYLESHEETS);
customHeadElements = config.getStringList(HtmlGenerator::format() +
Config::dot +
HTMLGENERATOR_CUSTOMHEADELEMENTS);
codeIndent = config.getInt(CONFIG_CODEINDENT);
helpProjectWriter = new HelpProjectWriter(config,
project.toLower() +
".qhp");
}
void HtmlGenerator::terminateGenerator()
{
Generator::terminateGenerator();
}
QString HtmlGenerator::format()
{
return "HTML";
}
/*!
This is where the html files and dcf files are written.
\note The html file generation is done in the base class,
PageGenerator::generateTree().
*/
void HtmlGenerator::generateTree(const Tree *tree, CodeMarker *marker)
{
#if 0
// Copy the stylesheets from the directory containing the qdocconf file.
// ### This should be changed to use a special directory in doc/src.
QStringList::ConstIterator styleIter = stylesheets.begin();
QDir configPath = QDir::current();
while (styleIter != stylesheets.end()) {
QString filePath = configPath.absoluteFilePath(*styleIter);
Config::copyFile(Location(), filePath, filePath, outputDir());
++styleIter;
}
#endif
myTree = tree;
nonCompatClasses.clear();
mainClasses.clear();
compatClasses.clear();
obsoleteClasses.clear();
moduleClassMap.clear();
moduleNamespaceMap.clear();
funcIndex.clear();
legaleseTexts.clear();
serviceClasses.clear();
findAllClasses(tree->root());
findAllFunctions(tree->root());
findAllLegaleseTexts(tree->root());
findAllNamespaces(tree->root());
#ifdef ZZZ_QDOC_QML
findAllQmlClasses(tree->root());
#endif
findAllSince(tree->root());
PageGenerator::generateTree(tree, marker);
dcfClassesRoot.ref = "classes.html";
dcfClassesRoot.title = "Classes";
qSort(dcfClassesRoot.subsections);
dcfOverviewsRoot.ref = "overviews.html";
dcfOverviewsRoot.title = "Overviews";
qSort(dcfOverviewsRoot.subsections);
dcfExamplesRoot.ref = "examples.html";
dcfExamplesRoot.title = "Tutorial & Examples";
qSort(dcfExamplesRoot.subsections);
DcfSection qtRoot;
appendDcfSubSection(&qtRoot, dcfClassesRoot);
appendDcfSubSection(&qtRoot, dcfOverviewsRoot);
appendDcfSubSection(&qtRoot, dcfExamplesRoot);
generateDcf(project.toLower().simplified().replace(" ", "-"),
"index.html",
projectDescription, qtRoot);
generateDcf("designer",
"designer-manual.html",
"Qt Designer Manual",
dcfDesignerRoot);
generateDcf("linguist",
"linguist-manual.html",
"Qt Linguist Manual",
dcfLinguistRoot);
generateDcf("assistant",
"assistant-manual.html",
"Qt Assistant Manual",
dcfAssistantRoot);
generateDcf("qmake",
"qmake-manual.html",
"qmake Manual",
dcfQmakeRoot);
QString fileBase = project.toLower().simplified().replace(" ", "-");
generateIndex(fileBase, projectUrl, projectDescription);
generatePageIndex(outputDir() + "/" + fileBase + ".pageindex", marker);
helpProjectWriter->generate(myTree);
}
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:
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) {
skipAhead = skipAtoms(atom, Atom::BriefRight);
break;
}
out() << "";
if (relative->type() == Node::Property ||
relative->type() == Node::Variable) {
QString str;
atom = atom->next();
while (atom != 0 && atom->type() != Atom::BriefRight) {
if (atom->type() == Atom::String ||
atom->type() == Atom::AutoLink)
str += atom->string();
skipAhead++;
atom = atom->next();
}
str[0] = str[0].toLower();
if (str.right(1) == ".")
str.truncate(str.length() - 1);
out() << "This ";
if (relative->type() == Node::Property)
out() << "property";
else
out() << "variable";
QStringList words = str.split(" ");
if (!(words.first() == "contains" || words.first() == "specifies"
|| words.first() == "describes" || words.first() == "defines"
|| words.first() == "holds" || words.first() == "determines"))
out() << " holds ";
else
out() << " ";
out() << str << ".";
}
break;
case Atom::BriefRight:
if (relative->type() != Node::Fake)
out() << "
\n";
break;
case Atom::C:
out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
if (inLink) {
out() << protectEnc(plainCode(atom->string()));
}
else {
out() << highlightedCode(atom->string(), marker, relative);
}
out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
break;
case Atom::Code:
out() << ""
<< trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),
marker,relative))
<< " \n";
break;
#ifdef QDOC_QML
case Atom::Qml:
out() << ""
<< trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),
marker,relative))
<< " \n";
break;
#endif
case Atom::CodeNew:
out() << "you can rewrite it as
\n"
<< ""
<< trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),
marker,relative))
<< " \n";
break;
case Atom::CodeOld:
out() << "For example, if you have code like
\n";
// fallthrough
case Atom::CodeBad:
out() << ""
<< trimmedTrailing(protectEnc(plainCode(indent(codeIndent,atom->string()))))
<< " \n";
break;
case Atom::FootnoteLeft:
// ### For now
if (in_para) {
out() << "
\n";
in_para = false;
}
out() << "";
break;
case Atom::FormatElse:
case Atom::FormatEndif:
case Atom::FormatIf:
break;
case Atom::FormattingLeft:
out() << formattingLeftMap()[atom->string()];
if (atom->string() == ATOM_FORMATTING_PARAMETER) {
if (atom->next() != 0 && atom->next()->type() == Atom::String) {
QRegExp subscriptRegExp("([a-z]+)_([0-9n])");
if (subscriptRegExp.exactMatch(atom->next()->string())) {
out() << subscriptRegExp.cap(1) << ""
<< subscriptRegExp.cap(2) << " ";
skipAhead = 1;
}
}
}
break;
case Atom::FormattingRight:
if (atom->string() == ATOM_FORMATTING_LINK) {
endLink();
}
else {
out() << formattingRightMap()[atom->string()];
}
break;
case Atom::AnnotatedList:
{
QList values = myTree->groups().values(atom->string());
NodeMap nodeMap;
for (int i = 0; i < values.size(); ++i) {
const Node* n = values.at(i);
if ((n->status() != Node::Internal) && (n->access() != Node::Private)) {
nodeMap.insert(n->nameForLists(),n);
}
}
generateAnnotatedList(relative, marker, nodeMap);
}
break;
case Atom::GeneratedList:
if (atom->string() == "annotatedclasses") {
generateAnnotatedList(relative, marker, nonCompatClasses);
}
else if (atom->string() == "classes") {
generateCompactList(relative, marker, nonCompatClasses, true);
}
else if (atom->string().contains("classesbymodule")) {
QString arg = atom->string().trimmed();
QString moduleName = atom->string().mid(atom->string().indexOf(
"classesbymodule") + 15).trimmed();
if (moduleClassMap.contains(moduleName))
generateAnnotatedList(relative, marker, moduleClassMap[moduleName]);
}
else if (atom->string().contains("classesbyedition")) {
QString arg = atom->string().trimmed();
QString editionName = atom->string().mid(atom->string().indexOf(
"classesbyedition") + 16).trimmed();
if (editionModuleMap.contains(editionName)) {
// Add all classes in the modules listed for that edition.
NodeMap editionClasses;
foreach (const QString &moduleName, editionModuleMap[editionName]) {
if (moduleClassMap.contains(moduleName))
editionClasses.unite(moduleClassMap[moduleName]);
}
// Add additional groups and remove groups of classes that
// should be excluded from the edition.
QMultiMap groups = myTree->groups();
foreach (const QString &groupName, editionGroupMap[editionName]) {
QList groupClasses;
if (groupName.startsWith("-")) {
groupClasses = groups.values(groupName.mid(1));
foreach (const Node *node, groupClasses)
editionClasses.remove(node->name());
}
else {
groupClasses = groups.values(groupName);
foreach (const Node *node, groupClasses)
editionClasses.insert(node->name(), node);
}
}
generateAnnotatedList(relative, marker, editionClasses);
}
}
else if (atom->string() == "classhierarchy") {
generateClassHierarchy(relative, marker, nonCompatClasses);
}
else if (atom->string() == "compatclasses") {
generateCompactList(relative, marker, compatClasses, false);
}
else if (atom->string() == "obsoleteclasses") {
generateCompactList(relative, marker, obsoleteClasses, false);
}
else if (atom->string() == "functionindex") {
generateFunctionIndex(relative, marker);
}
else if (atom->string() == "legalese") {
generateLegaleseList(relative, marker);
}
else if (atom->string() == "mainclasses") {
generateCompactList(relative, marker, mainClasses, true);
}
else if (atom->string() == "services") {
generateCompactList(relative, marker, serviceClasses, false);
}
else if (atom->string() == "overviews") {
generateOverviewList(relative, marker);
}
else if (atom->string() == "namespaces") {
generateAnnotatedList(relative, marker, namespaceIndex);
}
else if (atom->string() == "related") {
const FakeNode *fake = static_cast(relative);
if (fake && !fake->groupMembers().isEmpty()) {
NodeMap groupMembersMap;
foreach (const Node *node, fake->groupMembers()) {
if (node->type() == Node::Fake)
groupMembersMap[fullName(node, relative, marker)] = node;
}
generateAnnotatedList(fake, marker, groupMembersMap);
}
}
else if (atom->string() == "relatedinline") {
const FakeNode *fake = static_cast(relative);
if (fake && !fake->groupMembers().isEmpty()) {
// Reverse the list into the original scan order.
// Should be sorted. But on what? It may not be a
// regular class or page definition.
QList list;
foreach (const Node *node, fake->groupMembers())
list.prepend(node);
foreach (const Node *node, list)
generateBody(node, marker);
}
}
break;
case Atom::SinceList:
{
NewSinceMaps::const_iterator nsmap;
nsmap = newSinceMaps.find(atom->string());
NewClassMaps::const_iterator ncmap;
ncmap = newClassMaps.find(atom->string());
NewClassMaps::const_iterator nqcmap;
nqcmap = newQmlClassMaps.find(atom->string());
if ((nsmap != newSinceMaps.constEnd()) && !nsmap.value().isEmpty()) {
QList sections;
QList::ConstIterator s;
for (int i=0; itype()) {
case Node::Fake:
if (node->subType() == Node::QmlClass) {
sections[QmlClass].appendMember((Node*)node);
}
break;
case Node::Namespace:
sections[Namespace].appendMember((Node*)node);
break;
case Node::Class:
sections[Class].appendMember((Node*)node);
break;
case Node::Enum:
sections[Enum].appendMember((Node*)node);
break;
case Node::Typedef:
sections[Typedef].appendMember((Node*)node);
break;
case Node::Function: {
const FunctionNode* fn = static_cast(node);
if (fn->isMacro())
sections[Macro].appendMember((Node*)node);
else {
Node* p = fn->parent();
if (p) {
if (p->type() == Node::Class)
sections[MemberFunction].appendMember((Node*)node);
else if (p->type() == Node::Namespace) {
if (p->name().isEmpty())
sections[GlobalFunction].appendMember((Node*)node);
else
sections[NamespaceFunction].appendMember((Node*)node);
}
else
sections[GlobalFunction].appendMember((Node*)node);
}
else
sections[GlobalFunction].appendMember((Node*)node);
}
break;
}
case Node::Property:
sections[Property].appendMember((Node*)node);
break;
case Node::Variable:
sections[Variable].appendMember((Node*)node);
break;
case Node::QmlProperty:
sections[QmlProperty].appendMember((Node*)node);
break;
case Node::QmlSignal:
sections[QmlSignal].appendMember((Node*)node);
break;
case Node::QmlMethod:
sections[QmlMethod].appendMember((Node*)node);
break;
default:
break;
}
++n;
}
/*
First generate the table of contents.
*/
out() << "\n";
s = sections.constBegin();
while (s != sections.constEnd()) {
if (!(*s).members.isEmpty()) {
out() << ""
<< ""
<< (*s).name
<< " \n";
}
++s;
}
out() << " \n";
int idx = 0;
s = sections.constBegin();
while (s != sections.constEnd()) {
if (!(*s).members.isEmpty()) {
out() << " \n";
out() << "" << protectEnc((*s).name) << " \n";
if (idx == Class)
generateCompactList(0, marker, ncmap.value(), false, QString("Q"));
else if (idx == QmlClass)
generateCompactList(0, marker, nqcmap.value(), false, QString("Q"));
else if (idx == MemberFunction) {
ParentMaps parentmaps;
ParentMaps::iterator pmap;
NodeList::const_iterator i = s->members.constBegin();
while (i != s->members.constEnd()) {
Node* p = (*i)->parent();
pmap = parentmaps.find(p);
if (pmap == parentmaps.end())
pmap = parentmaps.insert(p,NodeMultiMap());
pmap->insert((*i)->name(),(*i));
++i;
}
pmap = parentmaps.begin();
while (pmap != parentmaps.end()) {
NodeList nlist = pmap->values();
out() << "Class ";
out() << "";
QStringList pieces = fullName(pmap.key(), 0, marker).split("::");
out() << protectEnc(pieces.last());
out() << " " << ":
\n";
generateSection(nlist, 0, marker, CodeMarker::Summary);
out() << " ";
++pmap;
}
}
else
generateSection(s->members, 0, marker, CodeMarker::Summary);
}
++idx;
++s;
}
}
}
break;
case Atom::Image:
case Atom::InlineImage:
{
QString fileName = imageFileName(relative, atom->string());
QString text;
if (atom->next() != 0)
text = atom->next()->string();
if (atom->type() == Atom::Image)
out() << "";
if (fileName.isEmpty()) {
out() << "[Missing image "
<< protectEnc(atom->string()) << "] ";
}
else {
out() << " ";
helpProjectWriter->addExtraFile(fileName);
}
if (atom->type() == Atom::Image)
out() << "
";
}
break;
case Atom::ImageText:
break;
case Atom::LegaleseLeft:
out() << "";
break;
case Atom::LegaleseRight:
out() << "
";
break;
case Atom::LineBreak:
out() << " ";
break;
case Atom::Link:
{
const Node *node = 0;
QString myLink = getLink(atom, relative, marker, &node);
if (myLink.isEmpty()) {
relative->doc().location().warning(tr("Cannot link to '%1' in %2")
.arg(atom->string())
.arg(marker->plainFullName(relative)));
}
beginLink(myLink, node, relative, marker);
skipAhead = 1;
}
break;
case Atom::LinkNode:
{
const Node *node = CodeMarker::nodeForString(atom->string());
beginLink(linkForNode(node, relative), node, relative, marker);
skipAhead = 1;
}
break;
case Atom::ListLeft:
if (in_para) {
out() << "\n";
in_para = false;
}
if (atom->string() == ATOM_LIST_BULLET) {
out() << "\n";
}
else if (atom->string() == ATOM_LIST_TAG) {
out() << "\n";
}
else if (atom->string() == ATOM_LIST_VALUE) {
threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
if (threeColumnEnumValueTable) {
out() << "";
// << ""
if (++numTableRows % 2 == 1)
out() << " ";
else
out() << " ";
out() << " Constant "
<< "Value "
<< "Description \n";
}
else {
out() << ""
<< "Constant Value \n";
}
}
else {
out() << "string() == ATOM_LIST_LOWERALPHA) {
out() << "\"a\"";
}
else if (atom->string() == ATOM_LIST_UPPERROMAN) {
out() << "\"I\"";
}
else if (atom->string() == ATOM_LIST_LOWERROMAN) {
out() << "\"i\"";
}
else { // (atom->string() == ATOM_LIST_NUMERIC)
out() << "\"1\"";
}
if (atom->next() != 0 && atom->next()->string().toInt() != 1)
out() << " start=\"" << atom->next()->string() << "\"";
out() << ">\n";
}
break;
case Atom::ListItemNumber:
break;
case Atom::ListTagLeft:
if (atom->string() == ATOM_LIST_TAG) {
out() << "";
}
else { // (atom->string() == ATOM_LIST_VALUE)
// ### Trenton
out() << ""
<< protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(),
relative)))
<< " ";
QString itemValue;
if (relative->type() == Node::Enum) {
const EnumNode *enume = static_cast(relative);
itemValue = enume->itemValue(atom->next()->string());
}
if (itemValue.isEmpty())
out() << "?";
else
out() << "" << protectEnc(itemValue) << " ";
skipAhead = 1;
}
break;
case Atom::ListTagRight:
if (atom->string() == ATOM_LIST_TAG)
out() << "\n";
break;
case Atom::ListItemLeft:
if (atom->string() == ATOM_LIST_TAG) {
out() << "";
}
else if (atom->string() == ATOM_LIST_VALUE) {
if (threeColumnEnumValueTable) {
out() << " ";
if (matchAhead(atom, Atom::ListItemRight))
out() << " ";
}
}
else {
out() << "";
}
if (matchAhead(atom, Atom::ParaLeft))
skipAhead = 1;
break;
case Atom::ListItemRight:
if (atom->string() == ATOM_LIST_TAG) {
out() << "\n";
}
else if (atom->string() == ATOM_LIST_VALUE) {
out() << " \n";
}
else {
out() << "\n";
}
break;
case Atom::ListRight:
if (atom->string() == ATOM_LIST_BULLET) {
out() << "\n";
}
else if (atom->string() == ATOM_LIST_TAG) {
out() << "\n";
}
else if (atom->string() == ATOM_LIST_VALUE) {
out() << "
\n";
}
else {
out() << "\n";
}
break;
case Atom::Nop:
break;
case Atom::ParaLeft:
out() << "";
in_para = true;
break;
case Atom::ParaRight:
endLink();
if (in_para) {
out() << "
\n";
in_para = false;
}
//if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight))
// out() << "\n";
break;
case Atom::QuotationLeft:
out() << "";
break;
case Atom::QuotationRight:
out() << " \n";
break;
case Atom::RawString:
out() << atom->string();
break;
case Atom::SectionLeft:
#if 0
{
int nextLevel = atom->string().toInt();
if (sectionNumber.size() < nextLevel) {
do {
sectionNumber.append("1");
} while (sectionNumber.size() < nextLevel);
}
else {
while (sectionNumber.size() > nextLevel) {
sectionNumber.removeLast();
}
sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1);
}
out() << " \n";
}
#else
out() << " \n";
#endif
break;
case Atom::SectionRight:
break;
case Atom::SectionHeadingLeft:
out() << "string().toInt() + hOffset(relative)) + ">";
inSectionHeading = true;
break;
case Atom::SectionHeadingRight:
out() << " string().toInt() + hOffset(relative)) + ">\n";
inSectionHeading = false;
break;
case Atom::SidebarLeft:
break;
case Atom::SidebarRight:
break;
case Atom::String:
if (inLink && !inContents && !inSectionHeading) {
generateLink(atom, relative, marker);
}
else {
out() << protectEnc(atom->string());
}
break;
case Atom::TableLeft:
if (in_para) {
out() << "\n";
in_para = false;
}
if (!atom->string().isEmpty()) {
if (atom->string().contains("%"))
out() << "\n "; // width=\"" << atom->string() << "\">\n ";
else {
out() << "\n";
}
}
else {
out() << "\n";
}
numTableRows = 0;
break;
case Atom::TableRight:
out() << "
\n";
break;
case Atom::TableHeaderLeft:
out() << "";
inTableHeader = true;
break;
case Atom::TableHeaderRight:
out() << " ";
if (matchAhead(atom, Atom::TableHeaderLeft)) {
skipAhead = 1;
out() << "\n";
}
else {
out() << " \n";
inTableHeader = false;
}
break;
case Atom::TableRowLeft:
if (++numTableRows % 2 == 1)
out() << "";
else
out() << " ";
break;
case Atom::TableRowRight:
out() << " \n";
break;
case Atom::TableItemLeft:
{
if (inTableHeader)
out() << "string().split(",");
if (spans.size() == 2) {
if (spans.at(0) != "1")
out() << " colspan=\"" << spans.at(0) << "\"";
if (spans.at(1) != "1")
out() << " rowspan=\"" << spans.at(1) << "\"";
if (inTableHeader)
out() << ">";
else
out() << ">";
}
if (matchAhead(atom, Atom::ParaLeft))
skipAhead = 1;
}
break;
case Atom::TableItemRight:
if (inTableHeader)
out() << "
";
else
out() << "";
if (matchAhead(atom, Atom::ParaLeft))
skipAhead = 1;
break;
case Atom::TableOfContents:
{
int numColumns = 1;
const Node *node = relative;
Doc::SectioningUnit sectioningUnit = Doc::Section4;
QStringList params = atom->string().split(",");
QString columnText = params.at(0);
QStringList pieces = columnText.split(" ", QString::SkipEmptyParts);
if (pieces.size() >= 2) {
columnText = pieces.at(0);
pieces.pop_front();
QString path = pieces.join(" ").trimmed();
node = findNodeForTarget(path, relative, marker, atom);
}
if (params.size() == 2) {
numColumns = qMax(columnText.toInt(), numColumns);
sectioningUnit = (Doc::SectioningUnit)params.at(1).toInt();
}
if (node)
generateTableOfContents(node,
marker,
sectioningUnit,
numColumns,
relative);
}
break;
case Atom::Target:
out() << "string()) << "\"> ";
break;
case Atom::UnhandledFormat:
out() << "<Missing HTML> ";
break;
case Atom::UnknownCommand:
out() << "\\" << protectEnc(atom->string())
<< "
";
break;
#ifdef QDOC_QML
case Atom::QmlText:
case Atom::EndQmlText:
// don't do anything with these. They are just tags.
break;
#endif
default:
unknownAtom(atom);
}
return skipAhead;
}
/*!
Generate a reference page for a C++ class.
*/
void HtmlGenerator::generateClassLikeNode(const InnerNode *inner,
CodeMarker *marker)
{
QList sections;
QList::ConstIterator s;
const ClassNode *classe = 0;
const NamespaceNode *namespasse = 0;
QString title;
QString rawTitle;
QString fullTitle;
if (inner->type() == Node::Namespace) {
namespasse = static_cast(inner);
rawTitle = marker->plainName(inner);
fullTitle = marker->plainFullName(inner);
title = rawTitle + " Namespace";
}
else if (inner->type() == Node::Class) {
classe = static_cast(inner);
rawTitle = marker->plainName(inner);
fullTitle = marker->plainFullName(inner);
title = rawTitle + " Class Reference";
}
DcfSection classSection;
classSection.title = title;
classSection.ref = linkForNode(inner, 0);
classSection.keywords += qMakePair(inner->name(), classSection.ref);
Text subtitleText;
if (rawTitle != fullTitle)
subtitleText << "(" << Atom(Atom::AutoLink, fullTitle) << ")"
<< Atom(Atom::LineBreak);
#if 0
// No longer used because the modeule name is a breadcrumb.
QString fixedModule = inner->moduleName();
if (fixedModule == "Qt3SupportLight")
fixedModule = "Qt3Support";
if (!fixedModule.isEmpty())
subtitleText << "[" << Atom(Atom::AutoLink, fixedModule) << " module]";
if (fixedModule.isEmpty()) {
QMultiMap publicGroups = myTree->publicGroups();
QList groupNames = publicGroups.values(inner->name());
if (!groupNames.isEmpty()) {
qSort(groupNames.begin(), groupNames.end());
subtitleText << "[";
for (int j=0; jsections(inner, CodeMarker::Summary, CodeMarker::Okay);
generateTableOfContents(inner,marker,§ions);
generateTitle(title, subtitleText, SmallSubTitle, inner, marker);
#ifdef QDOC_QML
if (classe && !classe->qmlElement().isEmpty()) {
generateInstantiatedBy(classe,marker);
}
#endif
generateBrief(inner, marker);
generateIncludes(inner, marker);
generateStatus(inner, marker);
if (classe) {
generateInherits(classe, marker);
generateInheritedBy(classe, marker);
}
generateThreadSafeness(inner, marker);
generateSince(inner, marker);
out() << "\n";
bool needOtherSection = false;
/*
sections is built above for the call to generateTableOfContents().
*/
s = sections.begin();
while (s != sections.end()) {
if (s->members.isEmpty() && s->reimpMembers.isEmpty()) {
if (!s->inherited.isEmpty())
needOtherSection = true;
}
else {
if (!s->members.isEmpty()) {
// out() << " \n";
out() << " \n";
out() << "" << protectEnc((*s).name) << " \n";
generateSection(s->members, inner, marker, CodeMarker::Summary);
}
if (!s->reimpMembers.isEmpty()) {
QString name = QString("Reimplemented ") + (*s).name;
// out() << " \n";
out() << " \n";
out() << "" << protectEnc(name) << " \n";
generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary);
}
if (!s->inherited.isEmpty()) {
out() << "\n";
generateSectionInheritedList(*s, inner, marker);
out() << " \n";
}
}
++s;
}
if (needOtherSection) {
out() << "Additional Inherited Members \n"
"\n";
s = sections.begin();
while (s != sections.end()) {
if (s->members.isEmpty() && !s->inherited.isEmpty())
generateSectionInheritedList(*s, inner, marker);
++s;
}
out() << " \n";
}
out() << " \n";
if (!inner->doc().isEmpty()) {
//out() << " \n"
out() << "\n" // QTBUG-9504
<< "
" << "Detailed Description" << " \n";
generateBody(inner, marker);
out() << "\n"; // QTBUG-9504
generateAlsoList(inner, marker);
}
sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay);
s = sections.begin();
while (s != sections.end()) {
//out() << " \n";
if (!(*s).divClass.isEmpty())
out() << "
\n"; // QTBUG-9504
out() << "" << protectEnc((*s).name) << " \n";
NodeList::ConstIterator m = (*s).members.begin();
while (m != (*s).members.end()) {
if ((*m)->access() != Node::Private) { // ### check necessary?
if ((*m)->type() != Node::Class)
generateDetailedMember(*m, inner, marker);
else {
out() << " class ";
generateFullName(*m, inner, marker);
out() << " ";
generateBrief(*m, marker, inner);
}
QStringList names;
names << (*m)->name();
if ((*m)->type() == Node::Function) {
const FunctionNode *func = reinterpret_cast(*m);
if (func->metaness() == FunctionNode::Ctor ||
func->metaness() == FunctionNode::Dtor ||
func->overloadNumber() != 1)
names.clear();
}
else if ((*m)->type() == Node::Property) {
const PropertyNode *prop = reinterpret_cast(*m);
if (!prop->getters().isEmpty() &&
!names.contains(prop->getters().first()->name()))
names << prop->getters().first()->name();
if (!prop->setters().isEmpty())
names << prop->setters().first()->name();
if (!prop->resetters().isEmpty())
names << prop->resetters().first()->name();
}
else if ((*m)->type() == Node::Enum) {
const EnumNode *enume = reinterpret_cast(*m);
if (enume->flagsType())
names << enume->flagsType()->name();
foreach (const QString &enumName,
enume->doc().enumItemNames().toSet() -
enume->doc().omitEnumItemNames().toSet())
names << plainCode(marker->markedUpEnumValue(enumName,
enume));
}
foreach (const QString &name, names)
classSection.keywords += qMakePair(name,linkForNode(*m,0));
}
++m;
}
if (!(*s).divClass.isEmpty())
out() << "\n"; // QTBUG-9504
++s;
}
generateFooter(inner);
if (!membersLink.isEmpty()) {
DcfSection membersSection;
membersSection.title = "List of all members";
membersSection.ref = membersLink;
appendDcfSubSection(&classSection, membersSection);
}
if (!obsoleteLink.isEmpty()) {
DcfSection obsoleteSection;
obsoleteSection.title = "Obsolete members";
obsoleteSection.ref = obsoleteLink;
appendDcfSubSection(&classSection, obsoleteSection);
}
if (!compatLink.isEmpty()) {
DcfSection compatSection;
compatSection.title = "Qt 3 support members";
compatSection.ref = compatLink;
appendDcfSubSection(&classSection, compatSection);
}
appendDcfSubSection(&dcfClassesRoot, classSection);
}
/*!
Generate the html page for a qdoc file that doesn't map
to an underlying c++ file.
*/
void HtmlGenerator::generateFakeNode(const FakeNode *fake, CodeMarker *marker)
{
SubTitleSize subTitleSize = LargeSubTitle;
DcfSection fakeSection;
fakeSection.title = fake->fullTitle();
fakeSection.ref = linkForNode(fake, 0);
QList sections;
QList::const_iterator s;
QString fullTitle = fake->fullTitle();
QString htmlTitle = fullTitle;
if (fake->subType() == Node::File && !fake->subTitle().isEmpty()) {
subTitleSize = SmallSubTitle;
htmlTitle += " (" + fake->subTitle() + ")";
}
else if (fake->subType() == Node::QmlBasicType) {
fullTitle = "QML Basic Type: " + fullTitle;
htmlTitle = fullTitle;
}
generateHeader(htmlTitle, fake, marker);
/*
Generate the TOC for the new doc format.
Don't generate a TOC for the home page.
*/
if (fake->name() != QString("index.html"))
generateTableOfContents(fake,marker,0);
generateTitle(fullTitle,
Text() << fake->subTitle(),
subTitleSize,
fake,
marker);
if (fake->subType() == Node::Module) {
// Generate brief text and status for modules.
generateBrief(fake, marker);
generateStatus(fake, marker);
if (moduleNamespaceMap.contains(fake->name())) {
out() << " \n";
out() << "Namespaces \n";
generateAnnotatedList(fake, marker, moduleNamespaceMap[fake->name()]);
}
if (moduleClassMap.contains(fake->name())) {
out() << " \n";
out() << "Classes \n";
generateAnnotatedList(fake, marker, moduleClassMap[fake->name()]);
}
}
else if (fake->subType() == Node::HeaderFile) {
// Generate brief text and status for modules.
generateBrief(fake, marker);
generateStatus(fake, marker);
out() << "\n";
if (!membersLink.isEmpty()) {
DcfSection membersSection;
membersSection.title = "List of all members";
membersSection.ref = membersLink;
appendDcfSubSection(&fakeSection, membersSection);
}
if (!obsoleteLink.isEmpty()) {
DcfSection obsoleteSection;
obsoleteSection.title = "Obsolete members";
obsoleteSection.ref = obsoleteLink;
appendDcfSubSection(&fakeSection, obsoleteSection);
}
if (!compatLink.isEmpty()) {
DcfSection compatSection;
compatSection.title = "Qt 3 support members";
compatSection.ref = compatLink;
appendDcfSubSection(&fakeSection, compatSection);
}
}
#ifdef QDOC_QML
else if (fake->subType() == Node::QmlClass) {
const QmlClassNode* qml_cn = static_cast(fake);
const ClassNode* cn = qml_cn->classNode();
generateQmlInherits(qml_cn, marker);
generateQmlInstantiates(qml_cn, marker);
generateBrief(qml_cn, marker);
generateQmlInheritedBy(qml_cn, marker);
sections = marker->qmlSections(qml_cn,CodeMarker::Summary);
s = sections.begin();
while (s != sections.end()) {
out() << " \n";
out() << "" << protectEnc((*s).name) << " \n";
generateQmlSummary(*s,fake,marker);
++s;
}
out() << " \n";
out() << "" << "Detailed Description" << " \n";
generateBody(fake, marker);
if (cn)
generateQmlText(cn->doc().body(), cn, marker, fake->name());
generateAlsoList(fake, marker);
//out() << " \n";
sections = marker->qmlSections(qml_cn,CodeMarker::Detailed);
s = sections.begin();
while (s != sections.end()) {
out() << "" << protectEnc((*s).name) << " \n";
NodeList::ConstIterator m = (*s).members.begin();
while (m != (*s).members.end()) {
generateDetailedQmlMember(*m, fake, marker);
out() << " \n";
fakeSection.keywords += qMakePair((*m)->name(),
linkForNode(*m,0));
++m;
}
++s;
}
generateFooter(fake);
return;
}
#endif
sections = marker->sections(fake, CodeMarker::Summary, CodeMarker::Okay);
s = sections.begin();
while (s != sections.end()) {
out() << " \n";
out() << "" << protectEnc((*s).name) << " \n";
generateSectionList(*s, fake, marker, CodeMarker::Summary);
++s;
}
Text brief = fake->doc().briefText();
if (fake->subType() == Node::Module && !brief.isEmpty()) {
out() << " \n";
out() << "\n"; // QTBUG-9504
out() << "
" << "Detailed Description" << " \n";
}
else
out() << "
\n"; // QTBUG-9504
generateBody(fake, marker);
out() << "
\n"; // QTBUG-9504
generateAlsoList(fake, marker);
if (!fake->groupMembers().isEmpty()) {
NodeMap groupMembersMap;
foreach (const Node *node, fake->groupMembers()) {
if (node->type() == Node::Class || node->type() == Node::Namespace)
groupMembersMap[node->name()] = node;
}
generateAnnotatedList(fake, marker, groupMembersMap);
}
fakeSection.keywords += qMakePair(fakeSection.title, fakeSection.ref);
sections = marker->sections(fake, CodeMarker::Detailed, CodeMarker::Okay);
s = sections.begin();
while (s != sections.end()) {
//out() << "
\n";
out() << "
" << protectEnc((*s).name) << " \n";
NodeList::ConstIterator m = (*s).members.begin();
while (m != (*s).members.end()) {
generateDetailedMember(*m, fake, marker);
fakeSection.keywords += qMakePair((*m)->name(), linkForNode(*m, 0));
++m;
}
++s;
}
generateFooter(fake);
if (fake->subType() == Node::Example) {
appendDcfSubSection(&dcfExamplesRoot, fakeSection);
}
else if (fake->subType() != Node::File) {
QString contentsPage = fake->links().value(Node::ContentsLink).first;
if (contentsPage == "Qt Designer Manual") {
appendDcfSubSection(&dcfDesignerRoot, fakeSection);
}
else if (contentsPage == "Qt Linguist Manual") {
appendDcfSubSection(&dcfLinguistRoot, fakeSection);
}
else if (contentsPage == "Qt Assistant Manual") {
appendDcfSubSection(&dcfAssistantRoot, fakeSection);
}
else if (contentsPage == "qmake Manual") {
appendDcfSubSection(&dcfQmakeRoot, fakeSection);
}
else {
appendDcfSubSection(&dcfOverviewsRoot, fakeSection);
}
}
}
/*!
Returns "html" for this subclass of Generator.
*/
QString HtmlGenerator::fileExtension(const Node * /* node */) const
{
return "html";
}
/*!
Output breadcrumb list in the html file.
*/
void HtmlGenerator::generateBreadCrumbs(const QString& title,
const Node *node,
CodeMarker *marker)
{
Text breadcrumb;
if (node->type() == Node::Class) {
const ClassNode* cn = static_cast
(node);
QString name = node->moduleName();
out() << " Modules ";
if (!name.isEmpty()) {
out() << " ";
breadcrumb << Atom(Atom::AutoLink,name);
generateText(breadcrumb, node, marker);
out() << " \n";
}
if (!cn->name().isEmpty())
out() << " " << cn->name() << " \n";
}
else if (node->type() == Node::Fake) {
const FakeNode* fn = static_cast(node);
if (node->subType() == Node::Module) {
out() << " Modules ";
QString name = node->name();
if (!name.isEmpty())
out() << " " << name << " \n";
}
else if (node->subType() == Node::Group) {
if (fn->name() == QString("modules"))
out() << " Modules ";
else {
out() << " " << title << " ";
}
}
else if (node->subType() == Node::Page) {
if (fn->name() == QString("examples.html")) {
out() << " Examples ";
}
else if (fn->name().startsWith("examples-")) {
out() << " Examples ";
out() << " " << title << " ";
}
else if (fn->name() == QString("namespaces.html")) {
out() << " Namespaces ";
}
else {
out() << " " << title << " ";
}
}
else if (node->subType() == Node::QmlClass) {
out() << " QML Elements ";
out() << " " << title << " ";
}
else if (node->subType() == Node::Example) {
out() << " Examples ";
QStringList sl = fn->name().split('/');
QString name = "examples-" + sl.at(0) + ".html";
QString t = CodeParser::titleFromName(name);
out() << " "
<< t << " ";
out() << " " << title << " ";
}
}
else if (node->type() == Node::Namespace) {
out() << " Namespaces ";
out() << " " << title << " ";
}
}
void HtmlGenerator::generateHeader(const QString& title,
const Node *node,
CodeMarker *marker)
{
out() << QString("\n").arg(outputEncoding);
out() << "\n";
out() << QString("\n").arg(naturalLanguage);
out() << "\n";
out() << " \n";
QString shortVersion;
shortVersion = project + " " + shortVersion + ": ";
if (node && !node->doc().location().isEmpty())
out() << "\n";
shortVersion = myTree->version();
if (shortVersion.count(QChar('.')) == 2)
shortVersion.truncate(shortVersion.lastIndexOf(QChar('.')));
if (!shortVersion.isEmpty()) {
if (project == "QSA")
shortVersion = "QSA " + shortVersion + ": ";
else
shortVersion = "Qt " + shortVersion + ": ";
}
out() << " " << shortVersion << protectEnc(title) << " \n";
//out() << " Qt Reference Documentation ";
if (offlineDocs)
{
out() << " ";
out() << "\n";
out() << "\n"; // narrow mainly for Creator
out() << " \n";
}
else
{
out() << " ";
out() << " \n";
out() << "\n";
out() << "\n";
out() << "\n";
// jquery functions
out() << " \n";
out() << " \n";
// menus and small docs js and css
out() << " \n";
out() << " \n";
out() << " ";
out() << " ";
// syntax highlighter js and css
// out() << " \n";
// out() << " \n";
// out() << " \n";
// out() << " \n";
// out() << " \n";
out() << "\n";
out() << "\n";
}
#ifdef GENERATE_MAC_REFS
if (mainPage)
generateMacRef(node, marker);
#endif
out() << QString(postHeader).replace("\\" + COMMAND_VERSION, myTree->version());
generateBreadCrumbs(title,node,marker);
out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, myTree->version());
#if 0 // Removed for new docf format. MWS
if (node && !node->links().empty())
out() << "\n" << navigationLinks << "
\n";
#endif
}
void HtmlGenerator::generateTitle(const QString& title,
const Text &subTitle,
SubTitleSize subTitleSize,
const Node *relative,
CodeMarker *marker)
{
if (!title.isEmpty())
out() << "" << protectEnc(title) << " \n";
if (!subTitle.isEmpty()) {
out() << "";
else
out() << " class=\"subtitle\">";
generateText(subTitle, relative, marker);
out() << " \n";
}
}
void HtmlGenerator::generateFooter(const Node *node)
{
if (node && !node->links().empty())
out() << "\n" << navigationLinks << "
\n";
out() << QString(footer).replace("\\" + COMMAND_VERSION, myTree->version())
<< QString(address).replace("\\" + COMMAND_VERSION, myTree->version());
if (offlineDocs)
{
out() << "\n";
}
else
{
out() << " \n";
out() << " \n";
out() << "\n";
}
out() << "