/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "webxmlgenerator.h" #include "helpprojectwriter.h" #include "node.h" #include "qdocdatabase.h" #include "separator.h" #include "quoter.h" #include "tree.h" #include QT_BEGIN_NAMESPACE static CodeMarker *marker_ = nullptr; void WebXMLGenerator::initializeGenerator(const Config &config) { HtmlGenerator::initializeGenerator(config); } void WebXMLGenerator::terminateGenerator() { Generator::terminateGenerator(); } QString WebXMLGenerator::format() { return "WebXML"; } QString WebXMLGenerator::fileExtension() const { // As this is meant to be an intermediate format, // use .html for internal references. The name of // the output file is set separately in // beginSubPage() calls. return "html"; } /*! Most of the output is generated by QDocIndexFiles and the append() callback. Some pages produce supplementary output while being generated, and that's handled here. */ int WebXMLGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) { if (supplement && currentWriter) addAtomElements(*currentWriter.data(), atom, relative, marker); return 0; } void WebXMLGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker * /* marker */) { QByteArray data; QXmlStreamWriter writer(&data); writer.setAutoFormatting(true); beginSubPage(aggregate, Generator::fileName(aggregate, "webxml")); writer.writeStartDocument(); writer.writeStartElement("WebXML"); writer.writeStartElement("document"); generateIndexSections(writer, aggregate); writer.writeEndElement(); // document writer.writeEndElement(); // WebXML writer.writeEndDocument(); out() << data; endSubPage(); } void WebXMLGenerator::generatePageNode(PageNode *pn, CodeMarker * /* marker */) { QByteArray data; currentWriter.reset(new QXmlStreamWriter(&data)); currentWriter->setAutoFormatting(true); beginSubPage(pn, Generator::fileName(pn, "webxml")); currentWriter->writeStartDocument(); currentWriter->writeStartElement("WebXML"); currentWriter->writeStartElement("document"); generateIndexSections(*currentWriter.data(), pn); currentWriter->writeEndElement(); // document currentWriter->writeEndElement(); // WebXML currentWriter->writeEndDocument(); out() << data; endSubPage(); } void WebXMLGenerator::generateExampleFilePage(const Node *en, const QString &file, CodeMarker * /* marker */) { QByteArray data; QXmlStreamWriter writer(&data); writer.setAutoFormatting(true); beginFilePage(en, linkForExampleFile(file, en, "webxml")); writer.writeStartDocument(); writer.writeStartElement("WebXML"); writer.writeStartElement("document"); writer.writeStartElement("page"); writer.writeAttribute("name", file); writer.writeAttribute("href", linkForExampleFile(file, en)); QString title = exampleFileTitle(static_cast(en), file); writer.writeAttribute("title", title); writer.writeAttribute("fulltitle", title); writer.writeAttribute("subtitle", file); writer.writeStartElement("description"); QString userFriendlyFilePath; // unused writer.writeAttribute("path", Doc::resolveFile(en->doc().location(), file, &userFriendlyFilePath)); writer.writeAttribute("line", "0"); writer.writeAttribute("column", "0"); Quoter quoter; Doc::quoteFromFile(en->doc().location(), quoter, file); QString code = quoter.quoteTo(en->location(), QString(), QString()); writer.writeTextElement("code", trimmedTrailing(code, QString(), QString())); writer.writeEndElement(); // description writer.writeEndElement(); // page writer.writeEndElement(); // document writer.writeEndElement(); // WebXML writer.writeEndDocument(); out() << data; endFilePage(); } void WebXMLGenerator::generateIndexSections(QXmlStreamWriter &writer, Node *node) { marker_ = CodeMarker::markerForFileName(node->location().filePath()); QDocIndexFiles::qdocIndexFiles()->generateIndexSections(writer, node, this); // generateIndexSections does nothing for groups, so handle them explicitly if (node->isGroup()) QDocIndexFiles::qdocIndexFiles()->generateIndexSection(writer, node, this); } // Handles callbacks from QDocIndexFiles to add documentation to node void WebXMLGenerator::append(QXmlStreamWriter &writer, Node *node) { Q_ASSERT(marker_); writer.writeStartElement("description"); writer.writeAttribute("path", node->doc().location().filePath()); writer.writeAttribute("line", QString::number(node->doc().location().lineNo())); writer.writeAttribute("column", QString::number(node->doc().location().columnNo())); if (node->isTextPageNode()) generateRelations(writer, node); if (node->isModule()) { writer.writeStartElement("generatedlist"); writer.writeAttribute("contents", "classesbymodule"); CollectionNode *cnn = static_cast(node); if (cnn->hasNamespaces()) { writer.writeStartElement("section"); writer.writeStartElement("heading"); writer.writeAttribute("level", "1"); writer.writeCharacters("Namespaces"); writer.writeEndElement(); // heading NodeMap namespaces; cnn->getMemberNamespaces(namespaces); generateAnnotatedList(writer, node, namespaces); writer.writeEndElement(); // section } if (cnn->hasClasses()) { writer.writeStartElement("section"); writer.writeStartElement("heading"); writer.writeAttribute("level", "1"); writer.writeCharacters("Classes"); writer.writeEndElement(); // heading NodeMap classes; cnn->getMemberClasses(classes); generateAnnotatedList(writer, node, classes); writer.writeEndElement(); // section } writer.writeEndElement(); // generatedlist } inLink = inContents = inSectionHeading = hasQuotingInformation = false; numTableRows = 0; const Atom *atom = node->doc().body().firstAtom(); while (atom) atom = addAtomElements(writer, atom, node, marker_); QVector alsoList = node->doc().alsoList(); supplementAlsoList(node, alsoList); if (!alsoList.isEmpty()) { writer.writeStartElement("see-also"); for (int i = 0; i < alsoList.size(); ++i) { const Atom *atom = alsoList.at(i).firstAtom(); while (atom) atom = addAtomElements(writer, atom, node, marker_); } writer.writeEndElement(); // see-also } if (node->isExample()) { supplement = true; generateRequiredLinks(node, marker_); supplement = false; } else if (node->isGroup()) { CollectionNode *cn = static_cast(node); if (!cn->noAutoList()) generateAnnotatedList(writer, node, cn->members()); } writer.writeEndElement(); // description } void WebXMLGenerator::generateDocumentation(Node *node) { // Don't generate nodes that are already processed, or if they're not supposed to // generate output, ie. external, index or images nodes. if (!node->url().isNull() || node->isExternalPage() || node->isIndexNode()) return; if (node->isInternal() && !showInternal_) return; if (node->parent()) { if (node->isNamespace() || node->isClassNode() || node->isHeader()) generateCppReferencePage(static_cast(node), nullptr); else if (node->isCollectionNode()) { if (node->wasSeen()) { // see remarks in base class impl. qdb_->mergeCollections(static_cast(node)); generatePageNode(static_cast(node), nullptr); } } else if (node->isTextPageNode()) generatePageNode(static_cast(node), nullptr); // else if TODO: anything else? } if (node->isAggregate()) { Aggregate *aggregate = static_cast(node); for (auto c : aggregate->childNodes()) { if ((c->isAggregate() || c->isTextPageNode() || c->isCollectionNode()) && !c->isPrivate()) generateDocumentation(c); } } } const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer, const Atom *atom, const Node *relative, CodeMarker *marker) { bool keepQuoting = false; if (!atom) return nullptr; switch (atom->type()) { case Atom::AnnotatedList: { const CollectionNode* cn = qdb_->getCollectionNode(atom->string(), Node::Group); if (cn) generateAnnotatedList(writer, relative, cn->members()); } break; case Atom::AutoLink: if (!inLink && !inSectionHeading) { const Node *node = nullptr; QString link = getLink(atom, relative, &node); if (node) { startLink(writer, atom, node, link); if (inLink) { writer.writeCharacters(atom->string()); writer.writeEndElement(); // link inLink = false; } } else { writer.writeCharacters(atom->string()); } } else { writer.writeCharacters(atom->string()); } break; case Atom::BaseName: break; case Atom::BriefLeft: writer.writeStartElement("brief"); switch (relative->nodeType()) { case Node::Property: writer.writeCharacters("This property"); break; case Node::Variable: writer.writeCharacters("This variable"); break; default: break; } if (relative->isProperty() || relative->isVariable()) { QString str; const Atom *a = atom->next(); while (a != nullptr && a->type() != Atom::BriefRight) { if (a->type() == Atom::String || a->type() == Atom::AutoLink) str += a->string(); a = a->next(); } str[0] = str[0].toLower(); if (str.endsWith('.')) str.chop(1); const QVector words = str.splitRef(' '); if (!words.isEmpty()) { const QStringRef &first(words.at(0)); if (!(first == "contains" || first == "specifies" || first == "describes" || first == "defines" || first == "holds" || first == "determines")) writer.writeCharacters(" holds "); else writer.writeCharacters(" "); } } break; case Atom::BriefRight: if (relative->isProperty() || relative->isVariable()) writer.writeCharacters("."); writer.writeEndElement(); // brief break; case Atom::C: writer.writeStartElement("teletype"); if (inLink) writer.writeAttribute("type", "normal"); else writer.writeAttribute("type", "highlighted"); writer.writeCharacters(plainCode(atom->string())); writer.writeEndElement(); // teletype break; case Atom::Code: if (!hasQuotingInformation) writer.writeTextElement("code", trimmedTrailing(plainCode(atom->string()), QString(), QString())); else keepQuoting = true; break; #ifdef QDOC_QML case Atom::Qml: if (!hasQuotingInformation) writer.writeTextElement("qml", trimmedTrailing(plainCode(atom->string()), QString(), QString())); else keepQuoting = true; #endif case Atom::CodeBad: writer.writeTextElement("badcode", trimmedTrailing(plainCode(atom->string()), QString(), QString())); break; case Atom::CodeNew: writer.writeTextElement("para", "you can rewrite it as"); writer.writeTextElement("newcode", trimmedTrailing(plainCode(atom->string()), QString(), QString())); break; case Atom::CodeOld: writer.writeTextElement("para", "For example, if you have code like"); writer.writeTextElement("oldcode", trimmedTrailing(plainCode(atom->string()), QString(), QString())); break; case Atom::CodeQuoteArgument: if (quoting_) { if (quoteCommand == "dots") { writer.writeAttribute("indent", atom->string()); writer.writeCharacters("..."); } else { writer.writeCharacters(atom->string()); } writer.writeEndElement(); // code keepQuoting = true; } break; case Atom::CodeQuoteCommand: if (quoting_) { quoteCommand = atom->string(); writer.writeStartElement(quoteCommand); } break; case Atom::ExampleFileLink: { if (!inLink) { QString link = linkForExampleFile(atom->string(), relative); if (!link.isEmpty()) startLink(writer, atom, relative, link); } } break; case Atom::ExampleImageLink: { if (!inLink) { QString link = atom->string(); if (!link.isEmpty()) startLink(writer, atom, nullptr, "images/used-in-examples/" + link); } } break; case Atom::FootnoteLeft: writer.writeStartElement("footnote"); break; case Atom::FootnoteRight: writer.writeEndElement(); // footnote break; case Atom::FormatEndif: writer.writeEndElement(); // raw break; case Atom::FormatIf: writer.writeStartElement("raw"); writer.writeAttribute("format", atom->string()); break; case Atom::FormattingLeft: { if (atom->string() == ATOM_FORMATTING_BOLD) writer.writeStartElement("bold"); else if (atom->string() == ATOM_FORMATTING_ITALIC) writer.writeStartElement("italic"); else if (atom->string() == ATOM_FORMATTING_UNDERLINE) writer.writeStartElement("underline"); else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) writer.writeStartElement("subscript"); else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) writer.writeStartElement("superscript"); else if (atom->string() == ATOM_FORMATTING_TELETYPE) writer.writeStartElement("teletype"); else if (atom->string() == ATOM_FORMATTING_PARAMETER) writer.writeStartElement("argument"); else if (atom->string() == ATOM_FORMATTING_INDEX) writer.writeStartElement("index"); } break; case Atom::FormattingRight: { if (atom->string() == ATOM_FORMATTING_BOLD) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_ITALIC) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_UNDERLINE) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_TELETYPE) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_PARAMETER) writer.writeEndElement(); else if (atom->string() == ATOM_FORMATTING_INDEX) writer.writeEndElement(); } if (inLink) { writer.writeEndElement(); // link inLink = false; } break; case Atom::GeneratedList: writer.writeStartElement("generatedlist"); writer.writeAttribute("contents", atom->string()); writer.writeEndElement(); break; case Atom::Image: writer.writeStartElement("image"); writer.writeAttribute("href", imageFileName(relative, atom->string())); writer.writeEndElement(); break; case Atom::InlineImage: writer.writeStartElement("inlineimage"); writer.writeAttribute("href", imageFileName(relative, atom->string())); writer.writeEndElement(); break; case Atom::ImageText: break; case Atom::ImportantLeft: writer.writeStartElement("para"); writer.writeTextElement("bold", "Important:"); writer.writeCharacters(" "); break; case Atom::ImportantRight: writer.writeEndElement(); // para break; case Atom::LegaleseLeft: writer.writeStartElement("legalese"); break; case Atom::LegaleseRight: writer.writeEndElement(); // legalese break; case Atom::Link: case Atom::LinkNode: if (!inLink) { const Node *node = nullptr; QString link = getLink(atom, relative, &node); if (!link.isEmpty()) startLink(writer, atom, node, link); } break; case Atom::ListLeft: writer.writeStartElement("list"); if (atom->string() == ATOM_LIST_BULLET) writer.writeAttribute("type", "bullet"); else if (atom->string() == ATOM_LIST_TAG) writer.writeAttribute("type", "definition"); else if (atom->string() == ATOM_LIST_VALUE) { if (relative->isEnumType()) writer.writeAttribute("type", "enum"); else writer.writeAttribute("type", "definition"); } else { writer.writeAttribute("type", "ordered"); if (atom->string() == ATOM_LIST_UPPERALPHA) writer.writeAttribute("start", "A"); else if (atom->string() == ATOM_LIST_LOWERALPHA) writer.writeAttribute("start", "a"); else if (atom->string() == ATOM_LIST_UPPERROMAN) writer.writeAttribute("start", "I"); else if (atom->string() == ATOM_LIST_LOWERROMAN) writer.writeAttribute("start", "i"); else // (atom->string() == ATOM_LIST_NUMERIC) writer.writeAttribute("start", "1"); } break; case Atom::ListItemNumber: break; case Atom::ListTagLeft: { writer.writeStartElement("definition"); writer.writeTextElement("term", plainCode( marker->markedUpEnumValue(atom->next()->string(), relative))); } break; case Atom::ListTagRight: writer.writeEndElement(); // definition break; case Atom::ListItemLeft: writer.writeStartElement("item"); break; case Atom::ListItemRight: writer.writeEndElement(); // item break; case Atom::ListRight: writer.writeEndElement(); // list break; case Atom::NoteLeft: writer.writeStartElement("para"); writer.writeTextElement("bold", "Note:"); writer.writeCharacters(" "); break; case Atom::NoteRight: writer.writeEndElement(); // para break; case Atom::Nop: break; case Atom::ParaLeft: writer.writeStartElement("para"); break; case Atom::ParaRight: writer.writeEndElement(); // para break; case Atom::QuotationLeft: writer.writeStartElement("quote"); break; case Atom::QuotationRight: writer.writeEndElement(); // quote break; case Atom::RawString: writer.writeCharacters(atom->string()); break; case Atom::SectionLeft: writer.writeStartElement("section"); writer.writeAttribute("id", Doc::canonicalTitle(Text::sectionHeading(atom).toString())); break; case Atom::SectionRight: writer.writeEndElement(); // section break; case Atom::SectionHeadingLeft: { writer.writeStartElement("heading"); int unit = atom->string().toInt(); // + hOffset(relative) writer.writeAttribute("level", QString::number(unit)); inSectionHeading = true; } break; case Atom::SectionHeadingRight: writer.writeEndElement(); // heading inSectionHeading = false; break; case Atom::SidebarLeft: case Atom::SidebarRight: break; case Atom::SnippetCommand: if (quoting_) { writer.writeStartElement(atom->string()); } break; case Atom::SnippetIdentifier: if (quoting_) { writer.writeAttribute("identifier", atom->string()); writer.writeEndElement(); keepQuoting = true; } break; case Atom::SnippetLocation: if (quoting_) { const QString location = atom->string(); writer.writeAttribute("location", location); const QString resolved = Doc::resolveFile(Location(), location); if (!resolved.isEmpty()) writer.writeAttribute("path", resolved); } break; case Atom::String: writer.writeCharacters(atom->string()); break; case Atom::TableLeft: writer.writeStartElement("table"); if (atom->string().contains("%")) writer.writeAttribute("width", atom->string()); break; case Atom::TableRight: writer.writeEndElement(); // table break; case Atom::TableHeaderLeft: writer.writeStartElement("header"); break; case Atom::TableHeaderRight: writer.writeEndElement(); // header break; case Atom::TableRowLeft: writer.writeStartElement("row"); break; case Atom::TableRowRight: writer.writeEndElement(); // row break; case Atom::TableItemLeft: { writer.writeStartElement("item"); QStringList spans = atom->string().split(","); if (spans.size() == 2) { if (spans.at(0) != "1") writer.writeAttribute("colspan", spans.at(0).trimmed()); if (spans.at(1) != "1") writer.writeAttribute("rowspan", spans.at(1).trimmed()); } } break; case Atom::TableItemRight: writer.writeEndElement(); // item break; case Atom::Target: writer.writeStartElement("target"); writer.writeAttribute("name", Doc::canonicalTitle(atom->string())); writer.writeEndElement(); break; case Atom::UnhandledFormat: case Atom::UnknownCommand: writer.writeCharacters(atom->typeString()); break; default: break; } hasQuotingInformation = keepQuoting; return atom->next(); } void WebXMLGenerator::startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node, const QString &link) { QString fullName = link; if (node) fullName = node->fullName(); if (!fullName.isEmpty() && !link.isEmpty()) { writer.writeStartElement("link"); if (!atom->string().isEmpty()) writer.writeAttribute("raw", atom->string()); else writer.writeAttribute("raw", fullName); writer.writeAttribute("href", link); writer.writeAttribute("type", targetType(node)); if (node) { switch (node->nodeType()) { case Node::Enum: writer.writeAttribute("enum", fullName); break; case Node::Example: { const ExampleNode *en = static_cast(node); QString fileTitle = exampleFileTitle(en, atom->string()); if (!fileTitle.isEmpty()) { writer.writeAttribute("page", fileTitle); break; } } Q_FALLTHROUGH(); case Node::Page: writer.writeAttribute("page", fullName); break; case Node::Property: { const PropertyNode *propertyNode = static_cast(node); if (propertyNode->getters().size() > 0) writer.writeAttribute("getter", propertyNode->getters().at(0)->fullName()); } break; default: break; } } inLink = true; } } void WebXMLGenerator::endLink(QXmlStreamWriter &writer) { if (inLink) { writer.writeEndElement(); // link inLink = false; } } void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node) { if (node && !node->links().empty()) { QPair anchorPair; const Node *linkNode; for (auto it = node->links().cbegin(); it != node->links().cend(); ++it) { linkNode = qdb_->findNodeForTarget(it.value().first, node); if (!linkNode) linkNode = node; if (linkNode == node) anchorPair = it.value(); else anchorPair = anchorForNode(linkNode); writer.writeStartElement("relation"); writer.writeAttribute("href", anchorPair.first); writer.writeAttribute("type", targetType(linkNode)); switch (it.key()) { case Node::StartLink: writer.writeAttribute("meta", "start"); break; case Node::NextLink: writer.writeAttribute("meta", "next"); break; case Node::PreviousLink: writer.writeAttribute("meta", "previous"); break; case Node::ContentsLink: writer.writeAttribute("meta", "contents"); break; default: writer.writeAttribute("meta", ""); } writer.writeAttribute("description", anchorPair.second); writer.writeEndElement(); // link } } } void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative, const NodeMap &nodeMap) { generateAnnotatedList(writer, relative, nodeMap.values()); } void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative, const NodeList &nodeList) { writer.writeStartElement("table"); writer.writeAttribute("width", "100%"); for (const auto *node : nodeList) { writer.writeStartElement("row"); writer.writeStartElement("item"); writer.writeStartElement("para"); const QString link = linkForNode(node, relative); startLink(writer, node->doc().body().firstAtom(), node, link); endLink(writer); writer.writeEndElement(); // para writer.writeEndElement(); // item writer.writeStartElement("item"); writer.writeStartElement("para"); writer.writeCharacters(node->doc().briefText().toString()); writer.writeEndElement(); // para writer.writeEndElement(); // item writer.writeEndElement(); // row } writer.writeEndElement(); // table } QT_END_NAMESPACE