blob: 4d8906f6a1973797a94392da9cac50cb100e160c [file] [log] [blame]
/****************************************************************************
**
** 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 "config.h"
#include "helpprojectwriter.h"
#include "node.h"
#include "qdocdatabase.h"
#include "separator.h"
#include "quoter.h"
#include "tree.h"
#include <QtCore/qxmlstream.h>
QT_BEGIN_NAMESPACE
static CodeMarker *marker_ = nullptr;
void WebXMLGenerator::initializeGenerator()
{
HtmlGenerator::initializeGenerator();
}
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<const ExampleNode *>(en), file);
writer.writeAttribute("title", title);
writer.writeAttribute("fulltitle", title);
writer.writeAttribute("subtitle", file);
writer.writeStartElement("description");
if (Config::instance().getBool(CONFIG_LOCATIONINFO)) {
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");
if (Config::instance().getBool(CONFIG_LOCATIONINFO)) {
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<CollectionNode *>(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<Text> 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<CollectionNode *>(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<Aggregate *>(node), nullptr);
else if (node->isCollectionNode()) {
if (node->wasSeen()) {
// see remarks in base class impl.
qdb_->mergeCollections(static_cast<CollectionNode *>(node));
generatePageNode(static_cast<PageNode *>(node), nullptr);
}
} else if (node->isTextPageNode())
generatePageNode(static_cast<PageNode *>(node), nullptr);
// else if TODO: anything else?
}
if (node->isAggregate()) {
Aggregate *aggregate = static_cast<Aggregate *>(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<QStringRef> 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<const ExampleNode *>(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<const PropertyNode *>(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<QString, QString> 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