7#include <QtCore/QCoreApplication>
9#include <QtCore/QFileInfo>
10#include <QtCore/QStack>
12#include <QtCore/QRegularExpression>
13#include <QtCore/QTextStream>
15#include <QtCore/QVariant>
16#include <QtCore/QXmlStreamReader>
38 void readCustomFilter();
39 void readFilterSection();
43 void skipUnknownToken();
44 void addMatchingFiles(
const QString &pattern);
45 bool hasValidSyntax(
const QString &nameSpace,
const QString &vFolder)
const;
47 QMap<QString, QStringList> dirEntriesCache;
52 const QString message = QCoreApplication::translate(
"QHelpProject",
53 "Skipping unknown token <%1> in file \"%2\".")
54 .arg(name()).arg(fileName) + QLatin1Char(
'\n');
55 fputs(qPrintable(message), stdout);
65 if (isStartElement()) {
66 if (name() == QLatin1String(
"QtHelpProject")
67 && attributes().value(QLatin1String(
"version"))
68 == QLatin1String(
"1.0")) {
71 raiseError(QCoreApplication::translate(
"QHelpProject",
72 "Unknown token. Expected \"QtHelpProject\"."));
78 raiseError(QCoreApplication::translate(
"QHelpProject",
79 "Error in line %1: %2").arg(lineNumber())
88 if (isStartElement()) {
89 if (name() == QLatin1String(
"virtualFolder")) {
90 virtualFolder = readElementText();
91 if (!hasValidSyntax(QLatin1String(
"test"), virtualFolder))
92 raiseError(QCoreApplication::translate(
"QHelpProject",
93 "Virtual folder has invalid syntax in file: \"%1\"").arg(fileName));
94 }
else if (name() == QLatin1String(
"namespace")) {
95 namespaceName = readElementText();
96 if (!hasValidSyntax(namespaceName, QLatin1String(
"test")))
97 raiseError(QCoreApplication::translate(
"QHelpProject",
98 "Namespace \"%1\" has invalid syntax in file: \"%2\"").arg(namespaceName, fileName));
99 }
else if (name() == QLatin1String(
"customFilter")) {
101 }
else if (name() == QLatin1String(
"filterSection")) {
103 }
else if (name() == QLatin1String(
"metaData")) {
104 QString n = attributes().value(QLatin1String(
"name")).toString();
105 if (!metaData.contains(n))
107 = attributes().value(QLatin1String(
"value")).toString();
109 metaData.insert(n, attributes().
110 value(QLatin1String(
"value")).toString());
114 }
else if (isEndElement() && name() == QLatin1String(
"QtHelpProject")) {
115 if (namespaceName.isEmpty())
116 raiseError(QCoreApplication::translate(
"QHelpProject",
117 "Missing namespace in QtHelpProject file: \"%1\"").arg(fileName));
118 else if (virtualFolder.isEmpty())
119 raiseError(QCoreApplication::translate(
"QHelpProject",
120 "Missing virtual folder in QtHelpProject file: \"%1\"").arg(fileName));
129 filter.name = attributes().value(QLatin1String(
"name")).toString();
132 if (isStartElement()) {
133 if (name() == QLatin1String(
"filterAttribute"))
134 filter.filterAttributes.append(readElementText());
137 }
else if (isEndElement() && name() == QLatin1String(
"customFilter")) {
141 customFilterList.append(filter);
146 filterSectionList.append(QHelpDataFilterSection());
149 if (isStartElement()) {
150 if (name() == QLatin1String(
"filterAttribute"))
151 filterSectionList.last().addFilterAttribute(readElementText());
152 else if (name() == QLatin1String(
"toc"))
154 else if (name() == QLatin1String(
"keywords"))
156 else if (name() == QLatin1String(
"files"))
160 }
else if (isEndElement() && name() == QLatin1String(
"filterSection")) {
168 QStack<QHelpDataContentItem*> contentStack;
169 QHelpDataContentItem *itm =
nullptr;
172 if (isStartElement()) {
173 if (name() == QLatin1String(
"section")) {
174 const QString &title = attributes().value(QLatin1String(
"title")).toString();
175 const QString &ref = attributes().value(QLatin1String(
"ref")).toString();
176 if (contentStack.isEmpty()) {
177 itm =
new QHelpDataContentItem(
nullptr, title, ref);
178 filterSectionList.last().addContent(itm);
180 itm =
new QHelpDataContentItem(contentStack.top(), title, ref);
182 contentStack.push(itm);
186 }
else if (isEndElement()) {
187 if (name() == QLatin1String(
"section")) {
190 }
else if (name() == QLatin1String(
"toc") && contentStack.isEmpty()) {
202 QTextStream str(&result);
203 str << QDir::toNativeSeparators(fileName) <<
':' << lineNumber
204 <<
": Missing attribute in <keyword";
206 str <<
" name=\"" << name <<
'"';
215 if (isStartElement()) {
216 if (name() == QLatin1String(
"keyword")) {
217 const QString &refAttribute = attributes().value(QStringLiteral(
"ref")).toString();
218 const QString &nameAttribute = attributes().value(QStringLiteral(
"name")).toString();
219 const QString &idAttribute = attributes().value(QStringLiteral(
"id")).toString();
220 if (refAttribute.isEmpty() || (nameAttribute.isEmpty() && idAttribute.isEmpty())) {
221 qWarning(
"%s", qPrintable(msgMissingAttribute(fileName, lineNumber(), nameAttribute)));
224 filterSectionList.last()
225 .addIndex(QHelpDataIndexItem(nameAttribute, idAttribute, refAttribute));
229 }
else if (isEndElement()) {
230 if (name() == QLatin1String(
"keyword"))
232 else if (name() == QLatin1String(
"keywords"))
244 if (isStartElement()) {
245 if (name() == QLatin1String(
"file"))
246 addMatchingFiles(readElementText());
249 }
else if (isEndElement()) {
250 if (name() == QLatin1String(
"file"))
252 else if (name() == QLatin1String(
"files"))
267 if (!pattern.contains(QLatin1Char(
'?')) && !pattern.contains(QLatin1Char(
'*'))
268 && !pattern.contains(QLatin1Char(
'[')) && !pattern.contains(QLatin1Char(
']'))) {
269 filterSectionList.last().addFile(pattern);
273 const QFileInfo fileInfo(rootPath + QLatin1Char(
'/') + pattern);
274 const QDir &dir = fileInfo.dir();
275 const QString &path = dir.canonicalPath();
278 const auto &it = dirEntriesCache.constFind(path);
279 const QStringList &entries = it != dirEntriesCache.cend() ?
280 it.value() : dir.entryList(QDir::Files);
281 if (it == dirEntriesCache.cend())
282 dirEntriesCache.insert(path, entries);
284 bool matchFound =
false;
286 auto cs = QRegularExpression::CaseInsensitiveOption;
288 auto cs = QRegularExpression::NoPatternOption;
290 const QRegularExpression regExp(QRegularExpression::wildcardToRegularExpression(fileInfo.fileName()), cs);
291 for (
const QString &file : entries) {
292 auto match = regExp.match(file);
293 if (match.hasMatch()) {
295 filterSectionList.last().
296 addFile(QFileInfo(pattern).dir().path() + QLatin1Char(
'/') + file);
300 filterSectionList.last().addFile(pattern);
304 const QString &vFolder)
const
306 const QLatin1Char slash(
'/');
307 if (nameSpace.contains(slash) || vFolder.contains(slash))
310 const QLatin1String scheme(
"qthelp");
311 url.setScheme(scheme);
312 const QString &canonicalNamespace = nameSpace.toLower();
313 url.setHost(canonicalNamespace);
314 url.setPath(slash + vFolder);
316 const QString expectedUrl(scheme + QLatin1String(
"://")
317 + canonicalNamespace + slash + vFolder);
318 return url.isValid() && url.toString() == expectedUrl;
322
323
324
325
326
327
328
329
330
331
332
335
336
343
344
351
352
353
354
355
356
359 d->fileName = fileName;
360 d->rootPath = QFileInfo(fileName).absolutePath();
361 QFile file(fileName);
362 if (!file.open(QIODevice::ReadOnly)) {
363 d->errorMsg = QCoreApplication::translate(
"QHelpProject",
364 "The input file %1 could not be opened.").arg(fileName);
368 d->readData(file.readAll());
369 return !d->hasError();
373
374
375
376
377
381 return d->errorString();
386
387
390 return d->namespaceName;
394
395
398 return d->virtualFolder;
402
403
406 return d->customFilterList;
410
411
414 return d->filterSectionList;
418
419
426
427
void readData(const QByteArray &contents)
QList< QHelpDataFilterSection > filterSectionList
QMap< QString, QVariant > metaData
QList< QHelpDataCustomFilter > customFilterList
The QHelpProjectData class stores all information found in a Qt help project file.
QHelpProjectData()
Constructs a Qt help project data structure.
~QHelpProjectData()
Destroys the help project data.
QString virtualFolder() const
QMap< QString, QVariant > metaData() const
QString errorMessage() const
Returns an error message if the reading of the Qt help project file failed.
QString namespaceName() const
QList< QHelpDataFilterSection > filterSections() const
bool readData(const QString &fileName)
Reads the file fileName and stores the help data.
QList< QHelpDataCustomFilter > customFilters() const
Combined button and popup list for selecting options.
static QString msgMissingAttribute(const QString &fileName, qint64 lineNumber, const QString &name)