/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
/* | |
pagegenerator.cpp | |
*/ | |
#include <qfile.h> | |
#include <qfileinfo.h> | |
#include <qdebug.h> | |
#include "codemarker.h" | |
#include "pagegenerator.h" | |
#include "tree.h" | |
QT_BEGIN_NAMESPACE | |
/*! | |
Nothing to do in the constructor. | |
*/ | |
PageGenerator::PageGenerator() | |
: outputCodec(0) | |
{ | |
// nothing. | |
} | |
/*! | |
The destructor | |
*/ | |
PageGenerator::~PageGenerator() | |
{ | |
while (!outStreamStack.isEmpty()) | |
endSubPage(); | |
} | |
bool PageGenerator::parseArg(const QString& src, | |
const QString& tag, | |
int* pos, | |
int n, | |
QStringRef* contents, | |
QStringRef* par1, | |
bool debug) | |
{ | |
#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:" <<i; | |
// skip tag | |
i += tag.length(); | |
// parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)"); | |
if (par1) { | |
SKIP_SPACE; | |
// read parameter name | |
j = i; | |
while (i < n && src[i].isLetter()) | |
++i; | |
if (src[i] == '=') { | |
if (debug) | |
qDebug() << "read parameter" << QString(src.data() + j, i - j); | |
SKIP_CHAR('='); | |
SKIP_CHAR('"'); | |
// skip parameter name | |
j = i; | |
while (i < n && src[i] != '"') | |
++i; | |
*par1 = QStringRef(&src, j, i - j); | |
SKIP_CHAR('"'); | |
SKIP_SPACE; | |
} else { | |
if (debug) | |
qDebug() << "no optional parameter found"; | |
} | |
} | |
SKIP_SPACE; | |
SKIP_CHAR('>'); | |
// find contents up to closing "</@tag> | |
j = i; | |
for (; true; ++i) { | |
if (i + 4 + tag.length() > n) | |
return false; | |
if (src[i] != '<') | |
continue; | |
if (src[i + 1] != '/') | |
continue; | |
if (src[i + 2] != '@') | |
continue; | |
if (tag != QStringRef(&src, i + 3, tag.length())) | |
continue; | |
if (src[i + 3 + tag.length()] != '>') | |
continue; | |
break; | |
} | |
*contents = QStringRef(&src, j, i - j); | |
i += tag.length() + 4; | |
*pos = i; | |
if (debug) | |
qDebug() << " tag " << tag << " found: pos now: " << i; | |
return true; | |
#undef SKIP_CHAR | |
} | |
/*! | |
This function is recursive. | |
*/ | |
void PageGenerator::generateTree(const Tree *tree) | |
{ | |
generateInnerNode(tree->root()); | |
} | |
QString PageGenerator::fileBase(const Node *node) const | |
{ | |
if (node->relates()) | |
node = node->relates(); | |
else if (!node->isInnerNode()) | |
node = node->parent(); | |
#ifdef QDOC_QML | |
if (node->subType() == Node::QmlPropertyGroup) { | |
node = node->parent(); | |
} | |
#endif | |
QString base = node->doc().baseName(); | |
if (!base.isEmpty()) | |
return base; | |
const Node *p = node; | |
forever { | |
const Node *pp = p->parent(); | |
base.prepend(p->name()); | |
#ifdef QDOC_QML | |
/* | |
To avoid file name conflicts in the html directory, | |
we prepend a prefix (by default, "qml-") to the file name of QML | |
element doc files. | |
*/ | |
if ((p->subType() == Node::QmlClass) || | |
(p->subType() == Node::QmlBasicType)) { | |
if (!base.startsWith(QLatin1String("QML:"))) | |
base.prepend(outputPrefix(QLatin1String("QML"))); | |
} | |
#endif | |
if (!pp || pp->name().isEmpty() || pp->type() == Node::Fake) | |
break; | |
base.prepend(QLatin1Char('-')); | |
p = pp; | |
} | |
if (node->type() == Node::Fake) { | |
#ifdef QDOC2_COMPAT | |
if (base.endsWith(".html")) | |
base.truncate(base.length() - 5); | |
#endif | |
} | |
// the code below is effectively equivalent to: | |
// base.replace(QRegExp("[^A-Za-z0-9]+"), " "); | |
// base = base.trimmed(); | |
// base.replace(" ", "-"); | |
// base = base.toLower(); | |
// as this function accounted for ~8% of total running time | |
// we optimize a bit... | |
QString res; | |
// +5 prevents realloc in fileName() below | |
res.reserve(base.size() + 5); | |
bool begun = false; | |
for (int i = 0; i != base.size(); ++i) { | |
QChar c = base.at(i); | |
uint u = c.unicode(); | |
if (u >= 'A' && u <= 'Z') | |
u -= 'A' - 'a'; | |
if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) { | |
res += QLatin1Char(u); | |
begun = true; | |
} | |
else if (begun) { | |
res += QLatin1Char('-'); | |
begun = false; | |
} | |
} | |
while (res.endsWith(QLatin1Char('-'))) | |
res.chop(1); | |
return res; | |
} | |
/*! | |
If the \a node has a URL, return the URL as the file name. | |
Otherwise, construct the file name from the fileBase() and | |
the fileExtension(), and return the constructed name. | |
*/ | |
QString PageGenerator::fileName(const Node* node) const | |
{ | |
if (!node->url().isEmpty()) | |
return node->url(); | |
QString name = fileBase(node); | |
name += QLatin1Char('.'); | |
name += fileExtension(node); | |
return name; | |
} | |
/*! | |
Return the current output file name. | |
*/ | |
QString PageGenerator::outFileName() | |
{ | |
return QFileInfo(static_cast<QFile*>(out().device())->fileName()).fileName(); | |
} | |
/*! | |
Creates the file named \a fileName in the output directory. | |
Attaches a QTextStream to the created file, which is written | |
to all over the place using out(). | |
*/ | |
void PageGenerator::beginSubPage(const Location& location, | |
const QString& fileName) | |
{ | |
QFile* outFile = new QFile(outputDir() + "/" + fileName); | |
if (!outFile->open(QFile::WriteOnly)) | |
location.fatal(tr("Cannot open output file '%1'").arg(outFile->fileName())); | |
QTextStream* out = new QTextStream(outFile); | |
if (outputCodec) | |
out->setCodec(outputCodec); | |
outStreamStack.push(out); | |
} | |
/*! | |
Flush the text stream associated with the subpage, and | |
then pop it off the text stream stack and delete it. | |
This terminates output of the subpage. | |
*/ | |
void PageGenerator::endSubPage() | |
{ | |
outStreamStack.top()->flush(); | |
delete outStreamStack.top()->device(); | |
delete outStreamStack.pop(); | |
} | |
/*! | |
Used for writing to the current output stream. Returns a | |
reference to the crrent output stream, which is then used | |
with the \c {<<} operator for writing. | |
*/ | |
QTextStream &PageGenerator::out() | |
{ | |
return *outStreamStack.top(); | |
} | |
/*! | |
Recursive writing of HTML files from the root \a node. | |
*/ | |
void | |
PageGenerator::generateInnerNode(const InnerNode* node) | |
{ | |
if (!node->url().isNull()) | |
return; | |
if (node->type() == Node::Fake) { | |
const FakeNode *fakeNode = static_cast<const FakeNode *>(node); | |
if (fakeNode->subType() == Node::ExternalPage) | |
return; | |
if (fakeNode->subType() == Node::Image) | |
return; | |
if (fakeNode->subType() == Node::QmlPropertyGroup) | |
return; | |
if (fakeNode->subType() == Node::Page) { | |
if (node->count() > 0) | |
qDebug("PAGE %s HAS CHILDREN", qPrintable(fakeNode->title())); | |
} | |
} | |
/* | |
Obtain a code marker for the source file. | |
*/ | |
CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath()); | |
if (node->parent() != 0) { | |
beginSubPage(node->location(), fileName(node)); | |
if (node->type() == Node::Namespace || node->type() == Node::Class) { | |
generateClassLikeNode(node, marker); | |
} | |
else if (node->type() == Node::Fake) { | |
generateFakeNode(static_cast<const FakeNode *>(node), marker); | |
} | |
endSubPage(); | |
} | |
NodeList::ConstIterator c = node->childNodes().begin(); | |
while (c != node->childNodes().end()) { | |
if ((*c)->isInnerNode() && (*c)->access() != Node::Private) | |
generateInnerNode((const InnerNode *) *c); | |
++c; | |
} | |
} | |
QT_END_NAMESPACE |