Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhelpprojectdata.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:insignificant reason:build-tool
4
6
7#include <QtCore/QCoreApplication>
8#include <QtCore/QDir>
9#include <QtCore/QFileInfo>
10#include <QtCore/QStack>
11#include <QtCore/QMap>
12#include <QtCore/QRegularExpression>
13#include <QtCore/QTextStream>
14#include <QtCore/QUrl>
15#include <QtCore/QVariant>
16#include <QtCore/QXmlStreamReader>
17
19
21{
22public:
23 void readData(const QByteArray &contents);
24
29
33
35
36private:
37 void readProject();
38 void readCustomFilter();
39 void readFilterSection();
40 void readTOC();
41 void readKeywords();
42 void readFiles();
43 void skipUnknownToken();
44 void addMatchingFiles(const QString &pattern);
45 bool hasValidSyntax(const QString &nameSpace, const QString &vFolder) const;
46
47 QMap<QString, QStringList> dirEntriesCache;
48};
49
50void QHelpProjectDataPrivate::skipUnknownToken()
51{
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);
56
57 skipCurrentElement();
58}
59
60void QHelpProjectDataPrivate::readData(const QByteArray &contents)
61{
62 addData(contents);
63 while (!atEnd()) {
64 readNext();
65 if (isStartElement()) {
66 if (name() == QLatin1String("QtHelpProject")
67 && attributes().value(QLatin1String("version"))
68 == QLatin1String("1.0")) {
69 readProject();
70 } else {
71 raiseError(QCoreApplication::translate("QHelpProject",
72 "Unknown token. Expected \"QtHelpProject\"."));
73 }
74 }
75 }
76
77 if (hasError()) {
78 raiseError(QCoreApplication::translate("QHelpProject",
79 "Error in line %1: %2").arg(lineNumber())
80 .arg(errorString()));
81 }
82}
83
84void QHelpProjectDataPrivate::readProject()
85{
86 while (!atEnd()) {
87 readNext();
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")) {
100 readCustomFilter();
101 } else if (name() == QLatin1String("filterSection")) {
102 readFilterSection();
103 } else if (name() == QLatin1String("metaData")) {
104 QString n = attributes().value(QLatin1String("name")).toString();
105 if (!metaData.contains(n))
106 metaData[n]
107 = attributes().value(QLatin1String("value")).toString();
108 else
109 metaData.insert(n, attributes().
110 value(QLatin1String("value")).toString());
111 } else {
112 skipUnknownToken();
113 }
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));
121 break;
122 }
123 }
124}
125
126void QHelpProjectDataPrivate::readCustomFilter()
127{
129 filter.name = attributes().value(QLatin1String("name")).toString();
130 while (!atEnd()) {
131 readNext();
132 if (isStartElement()) {
133 if (name() == QLatin1String("filterAttribute"))
134 filter.filterAttributes.append(readElementText());
135 else
136 skipUnknownToken();
137 } else if (isEndElement() && name() == QLatin1String("customFilter")) {
138 break;
139 }
140 }
141 customFilterList.append(filter);
142}
143
144void QHelpProjectDataPrivate::readFilterSection()
145{
146 filterSectionList.append(QHelpDataFilterSection());
147 while (!atEnd()) {
148 readNext();
149 if (isStartElement()) {
150 if (name() == QLatin1String("filterAttribute"))
151 filterSectionList.last().addFilterAttribute(readElementText());
152 else if (name() == QLatin1String("toc"))
153 readTOC();
154 else if (name() == QLatin1String("keywords"))
155 readKeywords();
156 else if (name() == QLatin1String("files"))
157 readFiles();
158 else
159 skipUnknownToken();
160 } else if (isEndElement() && name() == QLatin1String("filterSection")) {
161 break;
162 }
163 }
164}
165
166void QHelpProjectDataPrivate::readTOC()
167{
168 QStack<QHelpDataContentItem*> contentStack;
169 QHelpDataContentItem *itm = nullptr;
170 while (!atEnd()) {
171 readNext();
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);
179 } else {
180 itm = new QHelpDataContentItem(contentStack.top(), title, ref);
181 }
182 contentStack.push(itm);
183 } else {
184 skipUnknownToken();
185 }
186 } else if (isEndElement()) {
187 if (name() == QLatin1String("section")) {
188 contentStack.pop();
189 continue;
190 } else if (name() == QLatin1String("toc") && contentStack.isEmpty()) {
191 return;
192 } else {
193 skipUnknownToken();
194 }
195 }
196 }
197}
198
199static inline QString msgMissingAttribute(const QString &fileName, qint64 lineNumber, const QString &name)
200{
201 QString result;
202 QTextStream str(&result);
203 str << QDir::toNativeSeparators(fileName) << ':' << lineNumber
204 << ": Missing attribute in <keyword";
205 if (!name.isEmpty())
206 str << " name=\"" << name << '"';
207 str << ">.";
208 return result;
209}
210
211void QHelpProjectDataPrivate::readKeywords()
212{
213 while (!atEnd()) {
214 readNext();
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)));
222 continue;
223 }
224 filterSectionList.last()
225 .addIndex(QHelpDataIndexItem(nameAttribute, idAttribute, refAttribute));
226 } else {
227 skipUnknownToken();
228 }
229 } else if (isEndElement()) {
230 if (name() == QLatin1String("keyword"))
231 continue;
232 else if (name() == QLatin1String("keywords"))
233 return;
234 else
235 skipUnknownToken();
236 }
237 }
238}
239
240void QHelpProjectDataPrivate::readFiles()
241{
242 while (!atEnd()) {
243 readNext();
244 if (isStartElement()) {
245 if (name() == QLatin1String("file"))
246 addMatchingFiles(readElementText());
247 else
248 skipUnknownToken();
249 } else if (isEndElement()) {
250 if (name() == QLatin1String("file"))
251 continue;
252 else if (name() == QLatin1String("files"))
253 return;
254 else
255 skipUnknownToken();
256 }
257 }
258}
259
260// Expand file pattern and add matches into list. If the pattern does not match
261// any files, insert the pattern itself so the QHelpGenerator will emit a
262// meaningful warning later.
263void QHelpProjectDataPrivate::addMatchingFiles(const QString &pattern)
264{
265 // The pattern matching is expensive, so we skip it if no
266 // wildcard symbols occur in the string.
267 if (!pattern.contains(QLatin1Char('?')) && !pattern.contains(QLatin1Char('*'))
268 && !pattern.contains(QLatin1Char('[')) && !pattern.contains(QLatin1Char(']'))) {
269 filterSectionList.last().addFile(pattern);
270 return;
271 }
272
273 const QFileInfo fileInfo(rootPath + QLatin1Char('/') + pattern);
274 const QDir &dir = fileInfo.dir();
275 const QString &path = dir.canonicalPath();
276
277 // QDir::entryList() is expensive, so we cache the results.
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);
283
284 bool matchFound = false;
285#ifdef Q_OS_WIN
286 auto cs = QRegularExpression::CaseInsensitiveOption;
287#else
288 auto cs = QRegularExpression::NoPatternOption;
289#endif
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()) {
294 matchFound = true;
295 filterSectionList.last().
296 addFile(QFileInfo(pattern).dir().path() + QLatin1Char('/') + file);
297 }
298 }
299 if (!matchFound)
300 filterSectionList.last().addFile(pattern);
301}
302
303bool QHelpProjectDataPrivate::hasValidSyntax(const QString &nameSpace,
304 const QString &vFolder) const
305{
306 const QLatin1Char slash('/');
307 if (nameSpace.contains(slash) || vFolder.contains(slash))
308 return false;
309 QUrl url;
310 const QLatin1String scheme("qthelp");
311 url.setScheme(scheme);
312 const QString &canonicalNamespace = nameSpace.toLower();
313 url.setHost(canonicalNamespace);
314 url.setPath(slash + vFolder);
315
316 const QString expectedUrl(scheme + QLatin1String("://")
317 + canonicalNamespace + slash + vFolder);
318 return url.isValid() && url.toString() == expectedUrl;
319}
320
321/*!
322 \internal
323 \class QHelpProjectData
324 \since 4.4
325 \brief The QHelpProjectData class stores all information found
326 in a Qt help project file.
327
328 The structure is filled with data by calling readData(). The
329 specified file has to have the Qt help project file format in
330 order to be read successfully. Possible reading errors can be
331 retrieved by calling errorMessage().
332*/
333
334/*!
335 Constructs a Qt help project data structure.
336*/
341
342/*!
343 Destroys the help project data.
344*/
346{
347 delete d;
348}
349
350/*!
351 Reads the file \a fileName and stores the help data. The file has to
352 have the Qt help project file format. Returns true if the file
353 was successfully read, otherwise false.
354
355 \sa errorMessage()
356*/
357bool QHelpProjectData::readData(const QString &fileName)
358{
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);
365 return false;
366 }
367
368 d->readData(file.readAll());
369 return !d->hasError();
370}
371
372/*!
373 Returns an error message if the reading of the Qt help project
374 file failed. Otherwise, an empty QString is returned.
375
376 \sa readData()
377*/
379{
380 if (d->hasError())
381 return d->errorString();
382 return d->errorMsg;
383}
384
385/*!
386 \internal
387*/
389{
390 return d->namespaceName;
391}
392
393/*!
394 \internal
395*/
397{
398 return d->virtualFolder;
399}
400
401/*!
402 \internal
403*/
405{
406 return d->customFilterList;
407}
408
409/*!
410 \internal
411*/
413{
414 return d->filterSectionList;
415}
416
417/*!
418 \internal
419*/
421{
422 return d->metaData;
423}
424
425/*!
426 \internal
427*/
429{
430 return d->rootPath;
431}
432
433QT_END_NAMESPACE
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.
QString rootPath() const
QList< QHelpDataCustomFilter > customFilters() const
Combined button and popup list for selecting options.
static QString msgMissingAttribute(const QString &fileName, qint64 lineNumber, const QString &name)