/**************************************************************************** | |
** | |
** 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 Qt Assistant 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$ | |
** | |
****************************************************************************/ | |
#include "qhelpprojectdata_p.h" | |
#include <QtCore/QCoreApplication> | |
#include <QtCore/QDir> | |
#include <QtCore/QFileInfo> | |
#include <QtCore/QStack> | |
#include <QtCore/QMap> | |
#include <QtCore/QRegExp> | |
#include <QtCore/QUrl> | |
#include <QtCore/QVariant> | |
#include <QtXml/QXmlStreamReader> | |
QT_BEGIN_NAMESPACE | |
class QHelpProjectDataPrivate : public QXmlStreamReader | |
{ | |
public: | |
void readData(const QByteArray &contents); | |
QString virtualFolder; | |
QString namespaceName; | |
QString rootPath; | |
QStringList fileList; | |
QList<QHelpDataCustomFilter> customFilterList; | |
QList<QHelpDataFilterSection> filterSectionList; | |
QMap<QString, QVariant> metaData; | |
QString errorMsg; | |
private: | |
void readProject(); | |
void readCustomFilter(); | |
void readFilterSection(); | |
void readTOC(); | |
void readKeywords(); | |
void readFiles(); | |
void raiseUnknownTokenError(); | |
void addMatchingFiles(const QString &pattern); | |
bool hasValidSyntax(const QString &nameSpace, const QString &vFolder) const; | |
QMap<QString, QStringList> dirEntriesCache; | |
}; | |
void QHelpProjectDataPrivate::raiseUnknownTokenError() | |
{ | |
raiseError(QCoreApplication::translate("QHelpProject", "Unknown token.")); | |
} | |
void QHelpProjectDataPrivate::readData(const QByteArray &contents) | |
{ | |
addData(contents); | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("QtHelpProject") | |
&& attributes().value(QLatin1String("version")) == QLatin1String("1.0")) | |
readProject(); | |
else | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Unknown token. Expected \"QtHelpProject\"!")); | |
} | |
} | |
if (hasError()) { | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Error in line %1: %2").arg(lineNumber()) | |
.arg(errorString())); | |
} | |
} | |
void QHelpProjectDataPrivate::readProject() | |
{ | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("virtualFolder")) { | |
virtualFolder = readElementText(); | |
if (!hasValidSyntax(QLatin1String("test"), virtualFolder)) | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Virtual folder has invalid syntax.")); | |
} else if (name() == QLatin1String("namespace")) { | |
namespaceName = readElementText(); | |
if (!hasValidSyntax(namespaceName, QLatin1String("test"))) | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Namespace has invalid syntax.")); | |
} else if (name() == QLatin1String("customFilter")) { | |
readCustomFilter(); | |
} else if (name() == QLatin1String("filterSection")) { | |
readFilterSection(); | |
} else if (name() == QLatin1String("metaData")) { | |
QString n = attributes().value(QLatin1String("name")).toString(); | |
if (!metaData.contains(n)) | |
metaData[n] | |
= attributes().value(QLatin1String("value")).toString(); | |
else | |
metaData.insert(n, attributes(). | |
value(QLatin1String("value")).toString()); | |
} else { | |
raiseUnknownTokenError(); | |
} | |
} else if (isEndElement() && name() == QLatin1String("QtHelpProject")) { | |
if (namespaceName.isEmpty()) | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Missing namespace in QtHelpProject.")); | |
else if (virtualFolder.isEmpty()) | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Missing virtual folder in QtHelpProject")); | |
break; | |
} | |
} | |
} | |
void QHelpProjectDataPrivate::readCustomFilter() | |
{ | |
QHelpDataCustomFilter filter; | |
filter.name = attributes().value(QLatin1String("name")).toString(); | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("filterAttribute")) | |
filter.filterAttributes.append(readElementText()); | |
else | |
raiseUnknownTokenError(); | |
} else if (isEndElement() && name() == QLatin1String("customFilter")) { | |
break; | |
} | |
} | |
customFilterList.append(filter); | |
} | |
void QHelpProjectDataPrivate::readFilterSection() | |
{ | |
filterSectionList.append(QHelpDataFilterSection()); | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("filterAttribute")) | |
filterSectionList.last().addFilterAttribute(readElementText()); | |
else if (name() == QLatin1String("toc")) | |
readTOC(); | |
else if (name() == QLatin1String("keywords")) | |
readKeywords(); | |
else if (name() == QLatin1String("files")) | |
readFiles(); | |
else | |
raiseUnknownTokenError(); | |
} else if (isEndElement() && name() == QLatin1String("filterSection")) { | |
break; | |
} | |
} | |
} | |
void QHelpProjectDataPrivate::readTOC() | |
{ | |
QStack<QHelpDataContentItem*> contentStack; | |
QHelpDataContentItem *itm = 0; | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("section")) { | |
QString title = attributes().value(QLatin1String("title")).toString(); | |
QString ref = attributes().value(QLatin1String("ref")).toString(); | |
if (contentStack.isEmpty()) { | |
itm = new QHelpDataContentItem(0, title, ref); | |
filterSectionList.last().addContent(itm); | |
} else { | |
itm = new QHelpDataContentItem(contentStack.top(), title, ref); | |
} | |
contentStack.push(itm); | |
} else { | |
raiseUnknownTokenError(); | |
} | |
} else if (isEndElement()) { | |
if (name() == QLatin1String("section")) { | |
contentStack.pop(); | |
continue; | |
} else if (name() == QLatin1String("toc") && contentStack.isEmpty()) { | |
break; | |
} else { | |
raiseUnknownTokenError(); | |
} | |
} | |
} | |
} | |
void QHelpProjectDataPrivate::readKeywords() | |
{ | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("keyword")) { | |
if (attributes().value(QLatin1String("ref")).toString().isEmpty() | |
|| (attributes().value(QLatin1String("name")).toString().isEmpty() | |
&& attributes().value(QLatin1String("id")).toString().isEmpty())) | |
raiseError(QCoreApplication::translate("QHelpProject", | |
"Missing attribute in keyword at line %1.") | |
.arg(lineNumber())); | |
filterSectionList.last() | |
.addIndex(QHelpDataIndexItem(attributes(). | |
value(QLatin1String("name")).toString(), | |
attributes().value(QLatin1String("id")).toString(), | |
attributes().value(QLatin1String("ref")).toString())); | |
} else { | |
raiseUnknownTokenError(); | |
} | |
} else if (isEndElement()) { | |
if (name() == QLatin1String("keyword")) | |
continue; | |
else if (name() == QLatin1String("keywords")) | |
break; | |
else | |
raiseUnknownTokenError(); | |
} | |
} | |
} | |
void QHelpProjectDataPrivate::readFiles() | |
{ | |
while (!atEnd()) { | |
readNext(); | |
if (isStartElement()) { | |
if (name() == QLatin1String("file")) | |
addMatchingFiles(readElementText()); | |
else | |
raiseUnknownTokenError(); | |
} else if (isEndElement()) { | |
if (name() == QLatin1String("file")) | |
continue; | |
else if (name() == QLatin1String("files")) | |
break; | |
else | |
raiseUnknownTokenError(); | |
} | |
} | |
} | |
// Expand file pattern and add matches into list. If the pattern does not match | |
// any files, insert the pattern itself so the QHelpGenerator will emit a | |
// meaningful warning later. | |
void QHelpProjectDataPrivate::addMatchingFiles(const QString &pattern) | |
{ | |
// The pattern matching is expensive, so we skip it if no | |
// wildcard symbols occur in the string. | |
if (!pattern.contains('?') && !pattern.contains('*') | |
&& !pattern.contains('[') && !pattern.contains(']')) { | |
filterSectionList.last().addFile(pattern); | |
return; | |
} | |
QFileInfo fileInfo(rootPath + '/' + pattern); | |
const QDir &dir = fileInfo.dir(); | |
const QString &path = dir.canonicalPath(); | |
// QDir::entryList() is expensive, so we cache the results. | |
QMap<QString, QStringList>::ConstIterator it = dirEntriesCache.find(path); | |
const QStringList &entries = it != dirEntriesCache.constEnd() ? | |
it.value() : dir.entryList(QDir::Files); | |
if (it == dirEntriesCache.constEnd()) | |
dirEntriesCache.insert(path, entries); | |
bool matchFound = false; | |
#ifdef Q_OS_WIN | |
Qt::CaseSensitivity cs = Qt::CaseInsensitive; | |
#else | |
Qt::CaseSensitivity cs = Qt::CaseSensitive; | |
#endif | |
QRegExp regExp(fileInfo.fileName(), cs, QRegExp::Wildcard); | |
foreach (const QString &file, entries) { | |
if (regExp.exactMatch(file)) { | |
matchFound = true; | |
filterSectionList.last(). | |
addFile(QFileInfo(pattern).dir().path() + '/' + file); | |
} | |
} | |
if (!matchFound) | |
filterSectionList.last().addFile(pattern); | |
} | |
bool QHelpProjectDataPrivate::hasValidSyntax(const QString &nameSpace, | |
const QString &vFolder) const | |
{ | |
const QLatin1Char slash('/'); | |
if (nameSpace.contains(slash) || vFolder.contains(slash)) | |
return false; | |
QUrl url; | |
const QLatin1String scheme("qthelp"); | |
url.setScheme(scheme); | |
const QString canonicalNamespace = nameSpace.toLower(); | |
url.setHost(canonicalNamespace); | |
url.setPath(vFolder); | |
const QString expectedUrl(scheme + QLatin1String("://") | |
+ canonicalNamespace + slash + vFolder); | |
return url.isValid() && url.toString() == expectedUrl; | |
} | |
/*! | |
\internal | |
\class QHelpProjectData | |
\since 4.4 | |
\brief The QHelpProjectData class stores all information found | |
in a Qt help project file. | |
The structure is filled with data by calling readData(). The | |
specified file has to have the Qt help project file format in | |
order to be read successfully. Possible reading errors can be | |
retrieved by calling errorMessage(). | |
*/ | |
/*! | |
Constructs a Qt help project data structure. | |
*/ | |
QHelpProjectData::QHelpProjectData() | |
{ | |
d = new QHelpProjectDataPrivate; | |
} | |
/*! | |
Destroys the help project data. | |
*/ | |
QHelpProjectData::~QHelpProjectData() | |
{ | |
delete d; | |
} | |
/*! | |
Reads the file \a fileName and stores the help data. The file has to | |
have the Qt help project file format. Returns true if the file | |
was successfully read, otherwise false. | |
\sa errorMessage() | |
*/ | |
bool QHelpProjectData::readData(const QString &fileName) | |
{ | |
d->rootPath = QFileInfo(fileName).absolutePath(); | |
QFile file(fileName); | |
if (!file.open(QIODevice::ReadOnly)) { | |
d->errorMsg = QCoreApplication::translate("QHelpProject", | |
"The input file %1 could not be opened!").arg(fileName); | |
return false; | |
} | |
d->readData(file.readAll()); | |
return !d->hasError(); | |
} | |
/*! | |
Returns an error message if the reading of the Qt help project | |
file failed. Otherwise, an empty QString is returned. | |
\sa readData() | |
*/ | |
QString QHelpProjectData::errorMessage() const | |
{ | |
if (d->hasError()) | |
return d->errorString(); | |
return d->errorMsg; | |
} | |
/*! | |
\internal | |
*/ | |
QString QHelpProjectData::namespaceName() const | |
{ | |
return d->namespaceName; | |
} | |
/*! | |
\internal | |
*/ | |
QString QHelpProjectData::virtualFolder() const | |
{ | |
return d->virtualFolder; | |
} | |
/*! | |
\internal | |
*/ | |
QList<QHelpDataCustomFilter> QHelpProjectData::customFilters() const | |
{ | |
return d->customFilterList; | |
} | |
/*! | |
\internal | |
*/ | |
QList<QHelpDataFilterSection> QHelpProjectData::filterSections() const | |
{ | |
return d->filterSectionList; | |
} | |
/*! | |
\internal | |
*/ | |
QMap<QString, QVariant> QHelpProjectData::metaData() const | |
{ | |
return d->metaData; | |
} | |
/*! | |
\internal | |
*/ | |
QString QHelpProjectData::rootPath() const | |
{ | |
return d->rootPath; | |
} | |
QT_END_NAMESPACE |