/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** 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 either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** 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.0, 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. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://www.qtsoftware.com/contact. ** $QT_END_LICENSE$ ** ****************************************************************************/ /* htmlgenerator.cpp */ #include "codemarker.h" #include "helpprojectwriter.h" #include "htmlgenerator.h" #include "node.h" #include "separator.h" #include "tree.h" #include #include #include #include QT_BEGIN_NAMESPACE #define COMMAND_VERSION Doc::alias("version") static bool showBrokenLinks = false; static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*()"); static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)()"); static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)()"); static QRegExp spanTag(""); 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:" <).*()"); 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 " 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), funcLeftParen("\\S(\\()"), tre(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); 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); projectDescription = config.getString(CONFIG_DESCRIPTION); if (projectDescription.isEmpty() && !project.isEmpty()) projectDescription = project + " Reference Documentation"; projectUrl = config.getString(CONFIG_URL); 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) { // 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; } tre = 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 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); generateIndex(project.toLower().simplified().replace(" ", "-"), projectUrl, projectDescription); helpProjectWriter->generate(tre); } void HtmlGenerator::startText(const Node * /* relative */, CodeMarker * /* marker */) { inLink = false; inContents = false; inSectionHeading = false; inTableHeader = false; numTableRows = 0; threeColumnEnumValueTable = true; link.clear(); sectionNumber.clear(); } 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() << protect(atom->string()); } } else { out() << protect(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() << protect(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(protect(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 = tre->groups().values(atom->string()); QMap 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->name(),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); } 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. QMap 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 = tre->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); } else if (atom->string() == "obsoleteclasses") { generateCompactList(relative, marker, obsoleteClasses); } 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); } else if (atom->string() == "services") { generateCompactList(relative, marker, serviceClasses); } 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()) { QMap 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::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 " << protect(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() << "

    \n" << "" << "" << "\n"; } else { out() << "

    ConstantValueDescription
    \n" << "\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() << "
    \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() << "
    ConstantValue
    " << protect(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() << "" << protect(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::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() << protect(atom->string()); } break; case Atom::TableLeft: if (in_para) { out() << "

    \n"; in_para = false; } if (!atom->string().isEmpty()) { if (atom->string().contains("%")) out() << "

    string() << "\" " << "align=\"center\" cellpadding=\"2\" " << "cellspacing=\"1\" border=\"0\">\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) << "\""; 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() << "\\" << protect(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; } 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 Reference"; } 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); QString fixedModule = inner->moduleName(); if (fixedModule == "Qt3SupportLight") fixedModule = "Qt3Support"; if (!fixedModule.isEmpty()) subtitleText << "[" << Atom(Atom::AutoLink, fixedModule) << " module]"; if (fixedModule.isEmpty()) { QMultiMap publicGroups = tre->publicGroups(); QList groupNames = publicGroups.values(inner->name()); if (!groupNames.isEmpty()) { qSort(groupNames.begin(), groupNames.end()); subtitleText << "["; for (int j=0; jqmlElement().isEmpty()) { generateInstantiatedBy(classe,marker); } #endif generateBrief(inner, marker); generateIncludes(inner, marker); generateStatus(inner, marker); if (classe) { generateModuleWarning(classe, marker); generateInherits(classe, marker); generateInheritedBy(classe, marker); } generateThreadSafeness(inner, marker); generateSince(inner, marker); out() << "\n"; bool needOtherSection = false; sections = marker->sections(inner, CodeMarker::Summary, CodeMarker::Okay); 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() << "

    " << protect((*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() << "

    " << protect(name) << "

    \n"; generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); } if (!s->inherited.isEmpty()) { out() << "
      \n"; generateSectionInheritedList(*s, inner, marker, true); 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" << "

    " << "Detailed Description" << "

    \n"; generateBody(inner, marker); generateAlsoList(inner, marker); } sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay); s = sections.begin(); while (s != sections.end()) { out() << "
    \n"; out() << "

    " << protect((*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; } ++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); } 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 htmlTitle = fake->fullTitle(); if (fake->subType() == Node::File && !fake->subTitle().isEmpty()) { subTitleSize = SmallSubTitle; htmlTitle += " (" + fake->subTitle() + ")"; } generateHeader(htmlTitle, fake, marker, true); generateTitle(fake->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() << "

    Namespaces

    \n"; generateAnnotatedList(fake, marker, moduleNamespaceMap[fake->name()]); } if (moduleClassMap.contains(fake->name())) { 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); sections = marker->qmlSections(qml_cn,CodeMarker::Summary); s = sections.begin(); while (s != sections.end()) { out() << "\n"; out() << "

    " << protect((*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() << "

    " << protect((*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() << "

    " << protect((*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() << "

    " << "Detailed Description" << "

    \n"; } generateBody(fake, marker); generateAlsoList(fake, marker); if (!fake->groupMembers().isEmpty()) { QMap 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() << "

    " << protect((*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); } } } QString HtmlGenerator::fileExtension(const Node * /* node */) { return "html"; } void HtmlGenerator::generateHeader(const QString& title, const Node *node, CodeMarker *marker, bool mainPage) { out() << "\n"; out() << "\n" "\n"; QString shortVersion; if ((project != "Qtopia") && (project != "Qt Extended")) { shortVersion = project + " " + shortVersion + ": "; if (node && !node->doc().location().isEmpty()) out() << "\n"; shortVersion = tre->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() << "\n" " " << shortVersion << protect(title) << "\n"; if (!style.isEmpty()) out() << " \n"; const QMap &metaMap = node->doc().metaTagMap(); if (!metaMap.isEmpty()) { QMapIterator i(metaMap); while (i.hasNext()) { i.next(); out() << " \n"; } } navigationLinks.clear(); if (node && !node->links().empty()) { QPair linkPair; QPair anchorPair; const Node *linkNode; if (node->links().contains(Node::PreviousLink)) { linkPair = node->links()[Node::PreviousLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; navigationLinks += "[Previous: "; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protect(anchorPair.second); else navigationLinks += protect(linkPair.second); navigationLinks += "]\n"; } if (node->links().contains(Node::ContentsLink)) { linkPair = node->links()[Node::ContentsLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; navigationLinks += "["; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protect(anchorPair.second); else navigationLinks += protect(linkPair.second); navigationLinks += "]\n"; } if (node->links().contains(Node::NextLink)) { linkPair = node->links()[Node::NextLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; navigationLinks += "[Next: "; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protect(anchorPair.second); else navigationLinks += protect(linkPair.second); navigationLinks += "]\n"; } if (node->links().contains(Node::IndexLink)) { linkPair = node->links()[Node::IndexLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; } if (node->links().contains(Node::StartLink)) { linkPair = node->links()[Node::StartLink]; linkNode = findNodeForTarget(linkPair.first, node, marker); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; } } foreach (const QString &stylesheet, stylesheets) { out() << " \n"; } foreach (const QString &customHeadElement, customHeadElements) { out() << " " << customHeadElement << "\n"; } out() << "\n" "\n"; if (mainPage) generateMacRef(node, marker); out() << QString(postHeader).replace("\\" + COMMAND_VERSION, tre->version()); if (node && !node->links().empty()) out() << "

    \n" << navigationLinks << "

    \n"; } void HtmlGenerator::generateTitle(const QString& title, const Text &subTitle, SubTitleSize subTitleSize, const Node *relative, CodeMarker *marker) { out() << "

    " << protect(title); if (!subTitle.isEmpty()) { out() << "
    "; if (subTitleSize == SmallSubTitle) out() << ""; else out() << ""; generateText(subTitle, relative, marker); out() << "\n"; } out() << "

    \n"; } void HtmlGenerator::generateFooter(const Node *node) { if (node && !node->links().empty()) out() << "

    \n" << navigationLinks << "

    \n"; out() << QString(footer).replace("\\" + COMMAND_VERSION, tre->version()) << QString(address).replace("\\" + COMMAND_VERSION, tre->version()) << "\n" "\n"; } void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative) { Text brief = node->doc().briefText(); if (!brief.isEmpty()) { out() << "

    "; generateText(brief, node, marker); if (!relative || node == relative) out() << " More...

    \n"; } } void HtmlGenerator::generateIncludes(const InnerNode *inner, CodeMarker *marker) { if (!inner->includes().isEmpty()) { out() << "
    "
                  << trimmedTrailing(highlightedCode(indent(codeIndent,
                                                            marker->markedUpIncludes(inner->includes())),
                                                     marker,inner))
                  << "
    "; } } void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, Doc::SectioningUnit sectioningUnit, int numColumns, const Node *relative) { if (!node->doc().hasTableOfContents()) return; QList toc = node->doc().tableOfContents(); if (toc.isEmpty()) return; QString nodeName = ""; if (node != relative) nodeName = node->name(); QStringList sectionNumber; int columnSize = 0; QString tdTag; if (numColumns > 1) { tdTag = ""; out() << "

    \n" << tdTag << "\n"; } // disable nested links in table of contents inContents = true; inLink = true; for (int i = 0; i < toc.size(); ++i) { Atom *atom = toc.at(i); int nextLevel = atom->string().toInt(); if (nextLevel > (int)sectioningUnit) continue; if (sectionNumber.size() < nextLevel) { do { out() << "
      "; sectionNumber.append("1"); } while (sectionNumber.size() < nextLevel); } else { while (sectionNumber.size() > nextLevel) { out() << "
    \n"; sectionNumber.removeLast(); } sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); } int numAtoms; Text headingText = Text::sectionHeading(atom); if (sectionNumber.size() == 1 && columnSize > toc.size() / numColumns) { out() << "" << tdTag << "\n"; sectionNumber.removeLast(); } if (numColumns > 1) out() << "

    \n"; inContents = false; inLink = false; } #if 0 void HtmlGenerator::generateNavigationBar(const NavigationBar& bar, const Node *node, CodeMarker *marker) { if (bar.prev.begin() != 0 || bar.current.begin() != 0 || bar.next.begin() != 0) { out() << "

    "; if (bar.prev.begin() != 0) { #if 0 out() << "[Prev: "; generateText(section.previousHeading(), node, marker); out() << "]\n"; #endif } if (bar.current.begin() != 0) { out() << "[Home]\n"; } if (bar.next.begin() != 0) { out() << "[Next: "; generateText(Text::sectionHeading(bar.next.begin()), node, marker); out() << "]\n"; } out() << "

    \n"; } } #endif QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner, CodeMarker *marker) { QList
    sections; QList
    ::ConstIterator s; sections = marker->sections(inner, CodeMarker::SeparateList, CodeMarker::Okay); if (sections.isEmpty()) return QString(); QString fileName = fileBase(inner) + "-members." + fileExtension(inner); beginSubPage(inner->location(), fileName); QString title = "List of All Members for " + inner->name(); generateHeader(title, inner, marker, false); 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::SeparateList); 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 = "Qt 3 Support Members for " + inner->name(); fileName = fileBase(inner) + "-qt3." + fileExtension(inner); } else { title = "Obsolete Members for " + inner->name(); fileName = fileBase(inner) + "-obsolete." + fileExtension(inner); } beginSubPage(inner->location(), fileName); generateHeader(title, inner, marker, false); generateTitle(title, Text(), SmallSubTitle, inner, marker); if (status == CodeMarker::Compat) { out() << "

    The following class members are part of the " "Qt 3 support layer. " "They are provided to help you port old code to Qt 4. 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"; } out() << "

    \n"; for (i = 0; i < sections.size(); ++i) { out() << "

    " << protect(sections.at(i).name) << "

    \n"; generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary); } sections = marker->sections(inner, CodeMarker::Detailed, status); for (i = 0; i < sections.size(); ++i) { out() << "
    \n"; out() << "

    " << protect(sections.at(i).name) << "

    \n"; NodeList::ConstIterator m = sections.at(i).members.begin(); while (m != sections.at(i).members.end()) { if ((*m)->access() != Node::Private) generateDetailedMember(*m, inner, marker); ++m; } } generateFooter(); endSubPage(); return fileName; } void HtmlGenerator::generateClassHierarchy(const Node *relative, CodeMarker *marker, const QMap &classMap) { if (classMap.isEmpty()) return; QMap topLevel; QMap::ConstIterator c = classMap.begin(); while (c != classMap.end()) { const ClassNode *classe = static_cast(*c); if (classe->baseClasses().isEmpty()) topLevel.insert(classe->name(), classe); ++c; } QStack > stack; stack.push(topLevel); out() << "
      \n"; while (!stack.isEmpty()) { if (stack.top().isEmpty()) { stack.pop(); out() << "
    \n"; } else { const ClassNode *child = static_cast(*stack.top().begin()); out() << "
  • "; generateFullName(child, relative, marker); out() << "
  • \n"; stack.top().erase(stack.top().begin()); QMap newTop; foreach (const RelatedClass &d, child->derivedClasses()) { if (d.access != Node::Private) newTop.insert(d.node->name(), d.node); } if (!newTop.isEmpty()) { stack.push(newTop); out() << "
      \n"; } } } } void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker, const QMap &nodeMap) { out() << "

      \n"; int row = 0; foreach (const QString &name, nodeMap.keys()) { const Node *node = nodeMap[name]; if (node->status() == Node::Obsolete) continue; if (++row % 2 == 1) out() << ""; else out() << ""; out() << ""; if (!(node->type() == Node::Fake)) { Text brief = node->doc().trimmedBriefText(name); if (!brief.isEmpty()) { out() << ""; } } else { out() << ""; } out() << "\n"; } out() << "
      "; generateFullName(node, relative, marker); out() << ""; generateText(brief, node, marker); out() << ""; out() << protect(node->doc().briefText().toString()); out() << "

      \n"; } void HtmlGenerator::generateCompactList(const Node *relative, CodeMarker *marker, const QMap &classMap) { const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' const int NumColumns = 4; // number of columns in the result if (classMap.isEmpty()) return; /* First, find out the common prefix of all non-namespaced classes. For Qt, the prefix is Q. It can easily be derived from the first and last classes in alphabetical order (QAccel and QXtWidget in Qt 2.1). */ int commonPrefixLen = 0; QString commonPrefix; QString first; QString last; QMap::const_iterator iter = classMap.begin(); while (iter != classMap.end()) { if (!iter.key().contains("::")) { first = iter.key(); break; } ++iter; } if (first.isEmpty()) first = classMap.begin().key(); iter = classMap.end(); while (iter != classMap.begin()) { --iter; if (!iter.key().contains("::")) { last = iter.key(); break; } } if (last.isEmpty()) last = classMap.begin().key(); if (classMap.size() > 1) { while (commonPrefixLen < first.length() + 1 && commonPrefixLen < last.length() + 1 && first[commonPrefixLen] == last[commonPrefixLen]) ++commonPrefixLen; } commonPrefix = first.left(commonPrefixLen); /* 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 QMap. */ QMap paragraph[NumParagraphs+1]; QString paragraphName[NumParagraphs+1]; QMap::ConstIterator c = classMap.begin(); while (c != classMap.end()) { QStringList pieces = c.key().split("::"); QString key; if (pieces.size() == 1) key = pieces.last().mid(commonPrefixLen).toLower(); else key = pieces.last().toLower(); int paragraphNo = NumParagraphs - 1; if (key[0].digitValue() != -1) { paragraphNo = key[0].digitValue(); } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { paragraphNo = 10 + key[0].unicode() - 'a'; } paragraphName[paragraphNo] = key[0].toUpper(); paragraph[paragraphNo].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 int i, j, k; paragraphOffset[0] = 0; for (j = 0; j < NumParagraphs; j++) // j = 0..36 paragraphOffset[j + 1] = paragraphOffset[j] + paragraph[j].count(); int firstOffset[NumColumns + 1]; // 4 + 1 int currentOffset[NumColumns]; // 4 int currentParagraphNo[NumColumns]; // 4 int currentOffsetInParagraph[NumColumns]; // 4 int numRows = (classMap.count() + NumColumns - 1) / NumColumns; int curParagNo = 0; for (i = 0; i < NumColumns; i++) { // i = 0..3 firstOffset[i] = qMin(i * numRows, classMap.size()); currentOffset[i] = firstOffset[i]; for (j = curParagNo; j < NumParagraphs; j++) { if (paragraphOffset[j] > firstOffset[i]) break; if (paragraphOffset[j] <= firstOffset[i]) curParagNo = j; } currentParagraphNo[i] = curParagNo; currentOffsetInParagraph[i] = firstOffset[i] - paragraphOffset[curParagNo]; } firstOffset[NumColumns] = classMap.count(); out() << "

      \n"; for (k = 0; k < numRows; k++) { out() << "\n"; for (i = 0; i < NumColumns; i++) { if (currentOffset[i] >= firstOffset[i + 1]) { // this column is finished out() << "\n"; } else { while ((currentParagraphNo[i] < NumParagraphs) && (currentOffsetInParagraph[i] == paragraph[currentParagraphNo[i]].count())) { ++currentParagraphNo[i]; currentOffsetInParagraph[i] = 0; } #if 0 if (currentParagraphNo[i] >= NumParagraphs) { qDebug() << "### Internal error ###" << __FILE__ << __LINE__ << currentParagraphNo[i] << NumParagraphs; currentParagraphNo[i] = NumParagraphs - 1; } #endif out() << "\n"; if ((currentParagraphNo[i] < NumParagraphs) && !paragraphName[currentParagraphNo[i]].isEmpty()) { QMap::Iterator it; it = paragraph[currentParagraphNo[i]].begin(); for (j = 0; j < currentOffsetInParagraph[i]; j++) ++it; out() << "\n"; } currentOffset[i]++; currentOffsetInParagraph[i]++; } } out() << "\n"; } out() << "
      \n"; if (currentOffsetInParagraph[i] == 0) { // start a new paragraph out() << "" << paragraphName[currentParagraphNo[i]] << " "; } out() << ""; // Previously, we used generateFullName() for this, but we // require some special formatting. out() << ""; QStringList pieces = fullName(it.value(), relative, marker).split("::"); out() << protect(pieces.last()); out() << ""; if (pieces.size() > 1) { out() << " ("; generateFullName(it.value()->parent(), relative, marker); out() << ")"; } out() << "

      \n"; } void HtmlGenerator::generateFunctionIndex(const Node *relative, CodeMarker *marker) { out() << "

      "; for (int i = 0; i < 26; i++) { QChar ch('a' + i); out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "

      \n"; char nextLetter = 'a'; char currentLetter; #if 1 out() << "
        \n"; #endif QMap >::ConstIterator f = funcIndex.begin(); while (f != funcIndex.end()) { #if 1 out() << "
      • "; #else out() << "

        "; #endif out() << protect(f.key()) << ":"; currentLetter = f.key()[0].unicode(); while (islower(currentLetter) && currentLetter >= nextLetter) { out() << QString("").arg(nextLetter); nextLetter++; } QMap::ConstIterator s = (*f).begin(); while (s != (*f).end()) { out() << " "; generateFullName((*s)->parent(), relative, marker, *s); ++s; } #if 1 out() << "

      • "; #else out() << "

        "; #endif out() << "\n"; ++f; } #if 1 out() << "
      \n"; #endif } void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker) { QMap::ConstIterator it = legaleseTexts.begin(); while (it != legaleseTexts.end()) { Text text = it.key(); out() << "
      \n"; generateText(text, relative, marker); out() << "
        \n"; do { out() << "
      • "; generateFullName(it.value(), relative, marker); out() << "
      • \n"; ++it; } while (it != legaleseTexts.end() && it.key() == text); out() << "
      \n"; } } /*void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protect(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])"), "\\1\\2"); marked.replace("<@param>", ""); marked.replace("", ""); if (style == CodeMarker::Summary) marked.replace("@name>", "b>"); if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "  "); marked.replace("", ""); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("", ""); } out() << highlightedCode(marked, marker, relative); }*/ #ifdef QDOC_QML 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 = protect(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])"), "\\1\\2"); marked.replace("<@param>", ""); marked.replace("", ""); if (summary) marked.replace("@name>", "b>"); marked.replace("<@extra>", "  "); marked.replace("", ""); if (summary) { marked.replace("<@type>", ""); marked.replace("", ""); } out() << highlightedCode(marked, marker, relative); } #endif void HtmlGenerator::generateOverviewList(const Node *relative, CodeMarker * /* marker */) { QMap > fakeNodeMap; QMap groupTitlesMap; QMap uncategorizedNodeMap; QRegExp singleDigit("\\b([0-9])\\b"); const NodeList children = tre->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]; 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 *groupNode = static_cast(tre->root()->findNode(group, Node::Fake)); if (groupNode) fakeNodeMap[groupNode].insert(sortKey, fakeNode); //else // uncategorizedNodeMap.insert(sortKey, fakeNode); }// else // uncategorizedNodeMap.insert(sortKey, fakeNode); }// else // uncategorizedNodeMap.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("

      %2

      \n").arg( linkForNode(groupNode, relative)).arg( protect(groupNode->fullTitle())); if (fakeNodeMap[groupNode].count() == 0) continue; out() << "
        \n"; foreach (const FakeNode *fakeNode, fakeNodeMap[groupNode]) { QString title = fakeNode->fullTitle(); if (title.startsWith("The ")) title.remove(0, 4); out() << "
      • " << protect(title) << "
      • \n"; } out() << "
      \n"; } } if (!uncategorizedNodeMap.isEmpty()) { out() << QString("

      Miscellaneous

      \n"); out() << "
        \n"; foreach (const FakeNode *fakeNode, uncategorizedNodeMap) { QString title = fakeNode->fullTitle(); if (title.startsWith("The ")) title.remove(0, 4); out() << "
      • " << protect(title) << "
      • \n"; } out() << "
      \n"; } } #ifdef QDOC_NAME_ALIGNMENT void HtmlGenerator::generateSection(const NodeList& nl, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool name_alignment = true; if (!nl.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { name_alignment = false; twoColumn = (nl.count() >= 16); } else if (nl.first()->type() == Node::Property) { twoColumn = (nl.count() >= 5); name_alignment = false; } if (name_alignment) { out() << "\n"; } else { if (twoColumn) out() << "

      \n" << "\n"; else out() << "\n"; i++; ++m; } if (name_alignment) out() << "
      "; out() << "
        \n"; } int i = 0; NodeList::ConstIterator m = nl.begin(); while (m != nl.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (name_alignment) { out() << "
      "; } else { if (twoColumn && i == (int) (nl.count() + 1) / 2) out() << "
        \n"; out() << "
      • "; } generateSynopsis(*m, relative, marker, style, name_alignment); if (name_alignment) out() << "
      \n"; else { out() << "
    \n"; if (twoColumn) out() << "\n

    \n"; } } } void HtmlGenerator::generateSectionList(const Section& section, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool name_alignment = true; if (!section.members.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { name_alignment = false; twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); name_alignment = false; } if (name_alignment) { out() << "\n"; } else { if (twoColumn) out() << "

    \n" << "\n"; else out() << "\n"; i++; ++m; } if (name_alignment) out() << "
    "; out() << "
      \n"; } int i = 0; NodeList::ConstIterator m = section.members.begin(); while (m != section.members.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (name_alignment) { out() << "
    "; } else { if (twoColumn && i == (int) (section.members.count() + 1) / 2) out() << "
      \n"; out() << "
    • "; } generateSynopsis(*m, relative, marker, style, name_alignment); if (name_alignment) out() << "
    \n"; else { out() << "
\n"; if (twoColumn) out() << "\n

\n"; } } if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "
    \n"; generateSectionInheritedList(section, relative, marker, name_alignment); out() << "
\n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative, CodeMarker *marker, bool nameAlignment) { QList >::ConstIterator p = section.inherited.begin(); while (p != section.inherited.end()) { if (nameAlignment) out() << "
  • "; else out() << "
  • "; out() << (*p).second << " "; if ((*p).second == 1) { out() << section.singularMember; } else { out() << section.pluralMember; } out() << " inherited from " << protect(marker->plainFullName((*p).first, relative)) << "
  • \n"; ++p; } } void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style, bool nameAlignment) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protect(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])"), "\\1\\2"); marked.replace("<@param>", ""); marked.replace("", ""); if (style == CodeMarker::Summary) { marked.replace("<@name>", ""); // was "" marked.replace("", ""); // was "" } if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "  "); marked.replace("", ""); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("", ""); } out() << highlightedCode(marked, marker, relative, style, nameAlignment); } QString HtmlGenerator::highlightedCode(const QString& markedCode, CodeMarker *marker, const Node *relative, CodeMarker::SynopsisStyle , bool nameAlignment) { QString src = markedCode; QString html; QStringRef arg; QStringRef par1; const QChar charLangle = '<'; const QChar charAt = '@'; // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*()" static const QString linkTag("link"); bool done = false; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1).unicode() == '@') { if (nameAlignment && !done) {// && (i != 0)) Why was this here? html += ""; done = true; } i += 2; if (parseArg(src, linkTag, &i, n, &arg, &par1)) { html += ""; QString link = linkForNode( CodeMarker::nodeForString(par1.toString()), relative); addLink(link, arg, &html); html += ""; } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } if (slow) { // is this block ever used at all? // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)()" src = html; html = QString(); static const QString funcTag("func"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, funcTag, &i, n, &arg, &par1)) { QString link = linkForNode( marker->resolveTarget(par1.toString(), tre, relative), relative); addLink(link, arg, &html); par1 = QStringRef(); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } } // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)()" tags src = html; html = QString(); static const QString typeTags[] = { "type", "headerfile", "func" }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; bool handled = false; for (int k = 0; k != 3; ++k) { if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { par1 = QStringRef(); QString link = linkForNode( marker->resolveTarget(arg.toString(), tre, relative), relative); addLink(link, arg, &html); handled = true; break; } } if (!handled) { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all // "<@comment>" -> ""; // "<@preprocessor>" -> ""; // "<@string>" -> ""; // "<@char>" -> ""; // "" -> "" src = html; html = QString(); static const QString spanTags[] = { "<@comment>", "", "<@preprocessor>", "", "<@string>", "", "<@char>", "", "", "", "","", "", "", "", "" // "<@char>", "", // "", "", // "<@func>", "", // "", "", // "<@id>", "", // "", "", // "<@keyword>", "", // "", "", // "<@number>", "", // "", "", // "<@op>", "", // "", "", // "<@param>", "", // "", "", // "<@string>", "", // "", "", }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { bool handled = false; for (int k = 0; k != 8; ++k) { const QString & tag = spanTags[2 * k]; if (tag == QStringRef(&src, i, tag.length())) { html += spanTags[2 * k + 1]; i += tag.length(); handled = true; break; } } if (!handled) { ++i; if (src.at(i) == charAt || (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { // drop 'our' unknown tags (the ones still containing '@') while (i < n && src.at(i) != QLatin1Char('>')) ++i; ++i; } else { // retain all others html += charLangle; } } } else { html += src.at(i); ++i; } } return html; } #else void HtmlGenerator::generateSectionList(const Section& section, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { if (!section.members.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::SeparateList) { twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); } if (twoColumn) out() << "

    \n" << "\n
    "; out() << "
      \n"; int i = 0; NodeList::ConstIterator m = section.members.begin(); while (m != section.members.end()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (twoColumn && i == (int) (section.members.count() + 1) / 2) out() << "
      \n"; out() << "
    • "; if (style == CodeMarker::Accessors) out() << ""; generateSynopsis(*m, relative, marker, style); if (style == CodeMarker::Accessors) out() << ""; out() << "
    • \n"; i++; ++m; } out() << "
    \n"; if (twoColumn) out() << "

    \n"; } if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "
      \n"; generateSectionInheritedList(section, relative, marker); out() << "
    \n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative, CodeMarker *marker) { QList >::ConstIterator p = section.inherited.begin(); while (p != section.inherited.end()) { out() << "
  • "; out() << (*p).second << " "; if ((*p).second == 1) { out() << section.singularMember; } else { out() << section.pluralMember; } out() << " inherited from " << protect(marker->plainFullName((*p).first, relative)) << "
  • \n"; ++p; } } void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { QString marked = marker->markedUpSynopsis(node, relative, style); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protect(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])"), "\\1\\2"); marked.replace("<@param>", ""); marked.replace("", ""); if (style == CodeMarker::Summary) marked.replace("@name>", "b>"); if (style == CodeMarker::SeparateList) { QRegExp extraRegExp("<@extra>.*"); extraRegExp.setMinimal(true); marked.replace(extraRegExp, ""); } else { marked.replace("<@extra>", "  "); marked.replace("", ""); } if (style != CodeMarker::Detailed) { marked.replace("<@type>", ""); marked.replace("", ""); } out() << highlightedCode(marked, marker, relative); } QString HtmlGenerator::highlightedCode(const QString& markedCode, CodeMarker *marker, const Node *relative) { QString src = markedCode; QString html; QStringRef arg; QStringRef par1; const QChar charLangle = '<'; const QChar charAt = '@'; // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*()" static const QString linkTag("link"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, linkTag, &i, n, &arg, &par1)) { const Node* node = CodeMarker::nodeForString(par1.toString()); QString link = linkForNode(node, relative); addLink(link, arg, &html); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } if (slow) { // is this block ever used at all? // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)()" src = html; html = QString(); static const QString funcTag("func"); for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, funcTag, &i, n, &arg, &par1)) { QString link = linkForNode( marker->resolveTarget(par1.toString(), tre, relative), relative); addLink(link, arg, &html); par1 = QStringRef(); } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } } // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)()" tags src = html; html = QString(); static const QString typeTags[] = { "type", "headerfile", "func" }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; bool handled = false; for (int k = 0; k != 3; ++k) { if (parseArg(src, typeTags[k], &i, n, &arg, &par1)) { par1 = QStringRef(); QString link = linkForNode( marker->resolveTarget(arg.toString(), tre, relative), relative); addLink(link, arg, &html); handled = true; break; } } if (!handled) { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all // "<@comment>" -> ""; // "<@preprocessor>" -> ""; // "<@string>" -> ""; // "<@char>" -> ""; // "" -> "" src = html; html = QString(); static const QString spanTags[] = { "<@comment>", "", "<@preprocessor>", "", "<@string>", "", "<@char>", "", "", "", "","", "", "", "", "" // "<@char>", "", // "", "", // "<@func>", "", // "", "", // "<@id>", "", // "", "", // "<@keyword>", "", // "", "", // "<@number>", "", // "", "", // "<@op>", "", // "", "", // "<@param>", "", // "", "", // "<@string>", "", // "", "", }; for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { bool handled = false; for (int k = 0; k != 8; ++k) { const QString & tag = spanTags[2 * k]; if (tag == QStringRef(&src, i, tag.length())) { html += spanTags[2 * k + 1]; i += tag.length(); handled = true; break; } } if (!handled) { ++i; if (src.at(i) == charAt || (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { // drop 'our' unknown tags (the ones still containing '@') while (i < n && src.at(i) != QLatin1Char('>')) ++i; ++i; } else { // retain all others html += charLangle; } } } else { html += src.at(i); ++i; } } return html; } #endif void HtmlGenerator::generateLink(const Atom* atom, const Node* /* relative */, CodeMarker* marker) { static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_"); if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) { // hack for C++: move () outside of link int k = funcLeftParen.pos(1); out() << protect(atom->string().left(k)); if (link.isEmpty()) { if (showBrokenLinks) out() << ""; } else { out() << ""; } inLink = false; out() << protect(atom->string().mid(k)); } else if (marker->recognizeLanguage("Java")) { // hack for Java: remove () and use when appropriate bool func = atom->string().endsWith("()"); bool tt = (func || atom->string().contains(camelCase)); if (tt) out() << ""; if (func) { out() << protect(atom->string().left(atom->string().length() - 2)); } else { out() << protect(atom->string()); } out() << ""; } else { out() << protect(atom->string()); } } QString HtmlGenerator::cleanRef(const QString& ref) { QString clean; if (ref.isEmpty()) return clean; clean.reserve(ref.size() + 20); const QChar c = ref[0]; const uint u = c.unicode(); if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9')) { clean += c; } else if (u == '~') { clean += "dtor."; } else if (u == '_') { clean += "underscore."; } else { clean += "A"; } for (int i = 1; i < (int) ref.length(); i++) { const QChar c = ref[i]; const uint u = c.unicode(); if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-' || u == '_' || u == ':' || u == '.') { clean += c; } else if (c.isSpace()) { clean += "-"; } else if (u == '!') { clean += "-not"; } else if (u == '&') { clean += "-and"; } else if (u == '<') { clean += "-lt"; } else if (u == '=') { clean += "-eq"; } else if (u == '>') { clean += "-gt"; } else if (u == '#') { clean += "#"; } else { clean += "-"; clean += QString::number((int)u, 16); } } return clean; } QString HtmlGenerator::registerRef(const QString& ref) { QString clean = HtmlGenerator::cleanRef(ref); for (;;) { QString& prevRef = refMap[clean.toLower()]; if (prevRef.isEmpty()) { prevRef = ref; break; } else if (prevRef == ref) { break; } clean += "x"; } return clean; } QString HtmlGenerator::protect(const QString& string) { #define APPEND(x) \ if (html.isEmpty()) { \ html = string; \ html.truncate(i); \ } \ html += (x); QString html; int n = string.length(); for (int i = 0; i < n; ++i) { QChar ch = string.at(i); if (ch == QLatin1Char('&')) { APPEND("&"); } else if (ch == QLatin1Char('<')) { APPEND("<"); } else if (ch == QLatin1Char('>')) { APPEND(">"); } else if (ch == QLatin1Char('"')) { APPEND("""); } else if (ch.unicode() > 0x007F || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/')) || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) { // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator APPEND("&#x"); html += QString::number(ch.unicode(), 16); html += QLatin1Char(';'); } else { if (!html.isEmpty()) html += ch; } } if (!html.isEmpty()) return html; return string; #undef APPEND } QString HtmlGenerator::fileBase(const Node *node) { QString result; result = PageGenerator::fileBase(node); if (!node->isInnerNode()) { switch (node->status()) { case Node::Compat: result += "-qt3"; break; case Node::Obsolete: result += "-obsolete"; break; default: ; } } return result; } #if 0 QString HtmlGenerator::fileBase(const Node *node, const SectionIterator& section) { QStringList::ConstIterator s = section.sectionNumber().end(); QStringList::ConstIterator b = section.baseNameStack().end(); QString suffix; QString base = fileBase(node); while (s != section.sectionNumber().begin()) { --s; --b; if (!(*b).isEmpty()) { base = *b; break; } suffix.prepend("-" + *s); } return base + suffix; } #endif QString HtmlGenerator::fileName(const Node *node) { if (node->type() == Node::Fake) { if (static_cast(node)->subType() == Node::ExternalPage) return node->name(); } return PageGenerator::fileName(node); } QString HtmlGenerator::refForNode(const Node *node) { const FunctionNode *func; const TypedefNode *typedeffe; QString ref; switch (node->type()) { case Node::Namespace: case Node::Class: default: break; case Node::Enum: ref = node->name() + "-enum"; break; case Node::Typedef: typedeffe = static_cast(node); if (typedeffe->associatedEnum()) { return refForNode(typedeffe->associatedEnum()); } else { ref = node->name() + "-typedef"; } break; case Node::Function: func = static_cast(node); if (func->associatedProperty()) { return refForNode(func->associatedProperty()); } else { ref = func->name(); if (func->overloadNumber() != 1) ref += "-" + QString::number(func->overloadNumber()); } break; case Node::Property: #ifdef QDOC_QML case Node::QmlProperty: #endif ref = node->name() + "-prop"; break; #ifdef QDOC_QML case Node::QmlSignal: ref = node->name() + "-signal"; break; case Node::QmlMethod: ref = node->name() + "-method"; break; #endif case Node::Variable: ref = node->name() + "-var"; break; case Node::Target: return protect(node->name()); } return registerRef(ref); } QString HtmlGenerator::linkForNode(const Node *node, const Node *relative) { QString link; QString fn; QString ref; if (node == 0 || node == relative) return QString(); if (!node->url().isEmpty()) return node->url(); if (fileBase(node).isEmpty()) return QString(); if (node->access() == Node::Private) return QString(); fn = fileName(node); /* if (!node->url().isEmpty()) return fn;*/ #if 0 // ### reintroduce this test, without breaking .dcf files if (fn != outFileName()) #endif link += fn; if (!node->isInnerNode()) { ref = refForNode(node); if (relative && fn == fileName(relative) && ref == refForNode(relative)) return QString(); link += "#"; link += ref; } return link; } QString HtmlGenerator::refForAtom(Atom *atom, const Node * /* node */) { if (atom->type() == Atom::SectionLeft) { return Doc::canonicalTitle(Text::sectionHeading(atom).toString()); } else if (atom->type() == Atom::Target) { return Doc::canonicalTitle(atom->string()); } else { return QString(); } } void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, CodeMarker *marker, const Node *actualNode) { if (actualNode == 0) actualNode = apparentNode; out() << "status() != actualNode->status()) { switch (actualNode->status()) { case Node::Obsolete: out() << "\" class=\"obsolete"; break; case Node::Compat: out() << "\" class=\"compat"; break; default: ; } } out() << "\">"; out() << protect(fullName(apparentNode, relative, marker)); out() << ""; } void HtmlGenerator::generateDetailedMember(const Node *node, const InnerNode *relative, CodeMarker *marker) { const EnumNode *enume; generateMacRef(node, marker); if (node->type() == Node::Enum && (enume = static_cast(node))->flagsType()) { generateMacRef(enume->flagsType(), marker); out() << "

    "; out() << ""; generateSynopsis(enume, relative, marker, CodeMarker::Detailed); out() << "
    "; generateSynopsis(enume->flagsType(), relative, marker, CodeMarker::Detailed); out() << "

    \n"; } else { out() << "

    "; out() << ""; generateSynopsis(node, relative, marker, CodeMarker::Detailed); out() << "

    \n"; } generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); if (node->type() == Node::Property) { const PropertyNode *property = static_cast(node); Section section; section.members += property->getters(); section.members += property->setters(); section.members += property->resetters(); if (!section.members.isEmpty()) { out() << "

    Access functions:

    \n"; generateSectionList(section, node, marker, CodeMarker::Accessors); } } else if (node->type() == Node::Enum) { const EnumNode *enume = static_cast(node); if (enume->flagsType()) { out() << "

    The " << protect(enume->flagsType()->name()) << " type is a typedef for " << "QFlags<" << protect(enume->name()) << ">. It stores an OR combination of " << protect(enume->name()) << " values.

    \n"; } } generateAlsoList(node, marker); } 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)->isInnerNode()) { findAllClasses(static_cast(*c)); } } ++c; } } void HtmlGenerator::findAllFunctions(const InnerNode *node) { NodeList::ConstIterator c = node->childNodes().begin(); while (c != node->childNodes().end()) { 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->metaness() != FunctionNode::Ctor && func->metaness() != FunctionNode::Dtor) { funcIndex[(*c)->name()].insert(tre->fullDocumentName((*c)->parent()), *c); } } } ++c; } } void HtmlGenerator::findAllLegaleseTexts(const InnerNode *node) { NodeList::ConstIterator c = node->childNodes().begin(); while (c != node->childNodes().end()) { 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().begin(); while (c != node->childNodes().end()) { 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; } } #ifdef ZZZ_QDOC_QML /*! This function finds all the qml element nodes and stores them in a map for later use. */ void HtmlGenerator::findAllQmlClasses(const InnerNode *node) { NodeList::const_iterator c = node->childNodes().constBegin(); while (c != node->childNodes().constEnd()) { if ((*c)->type() == Node::Fake) { const FakeNode* fakeNode = static_cast(*c); if (fakeNode->subType() == Node::QmlClass) { const QmlClassNode* qmlNode = static_cast(fakeNode); const Node* n = qmlNode->classNode(); } qmlClasses.insert(fakeNode->name(),*c); } ++c; } } #endif int HtmlGenerator::hOffset(const Node *node) { switch (node->type()) { case Node::Namespace: case Node::Class: return 2; case Node::Fake: if (node->doc().briefText().isEmpty()) return 1; else return 2; 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 = tre->root()->findNode(target, Node::Fake); } else if (marker) { node = marker->resolveTarget(target, tre, relative); if (!node) node = tre->findFakeNodeByTitle(target); if (!node && atom) { node = tre->findUnambiguousTarget(target, *const_cast(&atom)); } } 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 = PageGenerator::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(":") && (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")) { *node = tre->root()->findNode(first, Node::Fake); } else { *node = marker->resolveTarget(first, tre, relative); if (!*node) *node = tre->findFakeNodeByTitle(first); if (!*node) *node = tre->findUnambiguousTarget(first, targetAtom); } 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"; } } #if 0 else if ((*node)->status() == Node::Deprecated) { qDebug() << "Link to Deprecated entity"; } else if ((*node)->status() == Node::Internal) { qDebug() << "Link to Internal entity"; } #endif } while (!path.isEmpty()) { targetAtom = tre->findTarget(path.first(), *node); if (targetAtom == 0) break; path.removeFirst(); } if (path.isEmpty()) { link = linkForNode(*node, relative); if (targetAtom) link += "#" + refForAtom(targetAtom, *node); } } return link; } void HtmlGenerator::generateDcf(const QString &fileBase, const QString &startPage, const QString &title, DcfSection &dcfRoot) { dcfRoot.ref = startPage; dcfRoot.title = title; generateDcfSections(dcfRoot, outputDir() + "/" + fileBase + ".dcf", fileBase + "/reference"); } void HtmlGenerator::generateIndex(const QString &fileBase, const QString &url, const QString &title) { tre->generateIndex(outputDir() + "/" + 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 = tre->findFakeNodeByTitle("Porting To Qt 4"); Atom *targetAtom = 0; if (fakeNode && node->type() == Node::Class) { QString oldName(node->name()); targetAtom = tre->findTarget(oldName.replace("3", ""), fakeNode); } if (targetAtom) { text << Atom(Atom::Link, linkForNode(fakeNode, node) + "#" + 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); } } 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"; } 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; } QT_END_NAMESPACE #ifdef QDOC_QML /*! Generates the summary for for the \a section. Only used for sections of QML element documentation. Currently handles only the QML property group. */ void HtmlGenerator::generateQmlSummary(const Section& section, const Node *relative, CodeMarker *marker) { if (!section.members.isEmpty()) { NodeList::ConstIterator m; int count = section.members.size(); bool twoColumn = false; if (section.members.first()->type() == Node::QmlProperty) { twoColumn = (count >= 5); } if (twoColumn) out() << "

    \n" << "\n
    "; out() << "
      \n"; int row = 0; m = section.members.begin(); while (m != section.members.end()) { if (twoColumn && row == (int) (count + 1) / 2) out() << "
      \n"; out() << "
    • "; generateQmlItem(*m,relative,marker,true); out() << "
    • \n"; row++; ++m; } out() << "
    \n"; if (twoColumn) out() << "

    \n"; } } /*! Outputs the html detailed documentation for a section on a QML element reference page. */ void HtmlGenerator::generateDetailedQmlMember(const Node *node, const InnerNode *relative, CodeMarker *marker) { const QmlPropertyNode* qpn = 0; generateMacRef(node, marker); out() << "
    "; if (node->subType() == Node::QmlPropertyGroup) { const QmlPropGroupNode* qpgn = static_cast(node); NodeList::ConstIterator p = qpgn->childNodes().begin(); out() << "
    "; out() << ""; while (p != qpgn->childNodes().end()) { if ((*p)->type() == Node::QmlProperty) { qpn = static_cast(*p); out() << ""; if (qpgn->isDefault()) { out() << "
    "; out() << ""; generateQmlItem(qpn, relative, marker, false); out() << "
    " << "
    " << "
    " << "
    " << "" << ""; } } ++p; } out() << "
    " << "default
    "; out() << "
    "; } else if (node->type() == Node::QmlSignal) { const QmlSignalNode* qsn = static_cast(node); out() << "
    "; out() << ""; out() << ""; out() << "
    "; out() << ""; generateQmlItem(qsn,relative,marker,false); out() << "
    "; out() << "
    "; } else if (node->type() == Node::QmlMethod) { const QmlMethodNode* qmn = static_cast(node); out() << "
    "; out() << ""; out() << ""; out() << "
    "; out() << ""; generateQmlItem(qmn,relative,marker,false); out() << "
    "; out() << "
    "; } out() << "
    "; generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); generateAlsoList(node, marker); out() << "
    "; out() << "
    "; } /*! Output the "Inherits" line for the QML element, if there should be one. */ void HtmlGenerator::generateQmlInherits(const QmlClassNode* cn, CodeMarker* marker) { if (cn && !cn->links().empty()) { if (cn->links().contains(Node::InheritsLink)) { QPair linkPair; linkPair = cn->links()[Node::InheritsLink]; QStringList strList(linkPair.first); const Node* n = tre->findNode(strList,Node::Fake); if (n && n->subType() == Node::QmlClass) { const QmlClassNode* qcn = static_cast(n); out() << "

    "; Text text; text << "[Inherits "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, linkPair.second); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << "]"; generateText(text, cn, marker); out() << "

    "; } } } } /*! Output the "[Xxx instantiates the C++ class QFxXxx]" 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(const QmlClassNode* qcn, CodeMarker* marker) { const ClassNode* cn = qcn->classNode(); if (cn && (cn->status() != Node::Internal)) { out() << "

    "; Text text; text << "["; 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 << " 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 << "]"; generateText(text, qcn, marker); out() << "

    "; } } /*! Output the "[QFxXxx 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(const ClassNode* cn, CodeMarker* marker) { if (cn && cn->status() != Node::Internal && !cn->qmlElement().isEmpty()) { const Node* n = tre->root()->findNode(cn->qmlElement(),Node::Fake); if (n && n->subType() == Node::QmlClass) { out() << "

    "; Text text; text << "["; 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(n)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, n->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << "]"; generateText(text, cn, marker); out() << "

    "; } } } #endif